Finalize project

This commit is contained in:
2021-06-18 15:29:43 +02:00
parent cf7bb3b853
commit 22e06ba6a3
129 changed files with 3 additions and 434 deletions
@@ -0,0 +1,3 @@
build
dist
dse_shared_libs.egg-info
+8
View File
@@ -0,0 +1,8 @@
### Contains shared libraries like rabbitMQ wrapper
* To have life changes (develop package only links to this folder): `pip install -e .`
* To install as independent package: `pip install .`
* Create installable package via `python setup.py sdist`
### Access shared libs via
* `from dse_shared_libs.* import *`
* e.g. `from dse_shared_libs.message_broker_wrapper import MBWrapper`
@@ -0,0 +1,26 @@
from dataclasses import dataclass
from datetime import datetime
import geopy.distance
@dataclass
class DAF:
"""
"Datenaufzeichnung für automatisiertes Fahren" (DAF), see 2021_DSE_Exercise_V1.0_Final.pdf page 7
"""
vehicle_identification_number: str
gps_location: geopy.Point
velocity: float
near_crash_event: bool
timestamp: datetime
def to_dict(self):
return {'vehicle_identification_number': self.vehicle_identification_number,
'gps_location': {'latitude': self.gps_location.latitude,
'longitude': self.gps_location.longitude
},
'velocity': self.velocity,
'near_crash_event': self.near_crash_event,
'timestamp': self.timestamp.timestamp()
}
@@ -0,0 +1,149 @@
import time
from threading import Thread
from typing import Union
import pika
from pika.exceptions import AMQPConnectionError
class MBWrapper:
host: str
exchange_type: str
exchange_name: str
callback: callable
_logger: Union['MBWrapper', None]
_type: str = None
_connection: pika.BlockingConnection
_channel: pika.BlockingConnection.channel
def __init__(self,
host: str = 'rabbitmq',
exchange_type: str = 'fanout',
exchange_name: str = None,
callback: callable = None,
verbose: bool = False):
"""
Initializes a new Message Broker Wrapper (MBWrapper). Initialize this object afterwards with
self.setup_sender() or self.setup_receiver() if messages will be published to or received from the queue.
If the exchange_name is not "logger", the MBWrapper will also initialize an sending queue which forwards
EVERY request to this exchange, too. Therefore, every request routed via a MBWrapper is logged. Except if it is
the logger MBWrapper. This one is initialized in the EventStore Service and receives all messages.
:param host: of running rabbitMQ instance
:param exchange_type: rabbitMQ exchange type to use
:param exchange_name: name of exchange
:param callback: callable callback to execute if message receiver will be set up, can also be set later on.
:param verbose: print handled messages to console
"""
assert exchange_name, 'Please define an exchange name'
# append a connection to the logging broker, if it isn't the logging broker itself
if exchange_name != 'logger':
self._logger = MBWrapper(exchange_name='logger')
self._logger.setup_sender()
else:
self._logger = None
self.host = host
self.exchange_type = exchange_type
self.exchange_name = exchange_name
self.callback = callback
self.verbose = verbose
def print(self, *msg):
"""
Modified print which prints only to stout with self.verbose.
:param msg: to print
"""
if self.verbose:
print(*msg)
def setup_sender(self):
"""
Setup the MBWrapper as sender.
"""
assert self._type != 'receiver', 'MBWrapper is already a receiver. Use another MBWrapper.'
self._type = 'sender'
self._setup_channel()
def setup_receiver(self, callback: callable = None):
"""
Setup the MBWrapper as a receiver.
A callback method which can handle the response bytes as input is mandatory.
Set it here if not already done on init.
"""
if callback:
self.callback = callback
assert self._type != 'sender', 'MBWrapper is already a sender. Use another MBWrapper.'
assert self.callback, \
'Please setup MBWrapper with "on response" self.callback which can handle a byte string as input.'
def consumer():
"""
If initialized as receiver: Consumer thread which waits for incoming messages, invokes self._receive
"""
self._setup_channel()
result = self._channel.queue_declare(queue='', exclusive=True)
queue_name = result.method.queue
self._channel.queue_bind(exchange=self.exchange_name, queue=queue_name)
self.print('[{}] Waiting for messages.'.format(self.exchange_name))
self._channel.basic_consume(
queue=queue_name, on_message_callback=self._receive, auto_ack=True)
self._channel.start_consuming()
Thread(target=consumer).start()
def send(self, message: bytes):
"""
Send the message to the queue. Forward also to logger.
:param message: msg to send
"""
if type(message) is not bytes:
message = str(message).encode()
self._channel.basic_publish(exchange=self.exchange_name, routing_key='', body=message)
self.print("[{}] Sent {}".format(self.exchange_name, message))
# has a logger IFF not 'logger' as exchange name -> forwards everything to logger exchange, too
if self.__getattribute__('_logger'):
# print('logging')
self._logger.send(message)
def close(self):
"""
Closes the connection.
"""
self._connection.close()
def _setup_channel(self):
"""
Setup the rabbitMQ channel. Retry if not succeeded (Allow the dependencies to
boot). Wait one second between retries.
"""
connect_succeeded = False
while not connect_succeeded:
try:
self._connection = pika.BlockingConnection(
pika.ConnectionParameters(host=self.host))
connect_succeeded = True
except pika.exceptions.AMQPConnectionError:
print('Could not connect, retry')
time.sleep(1)
self._channel = self._connection.channel()
self._channel.exchange_declare(exchange=self.exchange_name, exchange_type=self.exchange_type)
def _receive(self, ch, method, properties, body):
"""
Reduce complexity, only forward the message body to the callback method. The others are not necessary for our
application example.
"""
self.callback(body)
@@ -0,0 +1,10 @@
class MyDate:
timestamp = None
@classmethod
def set_timestamp(cls, timestamp):
cls.timestamp = timestamp
@classmethod
def utcnow(cls):
return cls.timestamp
@@ -0,0 +1,43 @@
class MyQueue:
@property
def queue(self):
return None
class MyMethod:
method = MyQueue()
class MyChannel:
@staticmethod
def exchange_declare(*args, **kwargs):
return None
@staticmethod
def queue_declare(*args, **kwargs):
return MyMethod
@staticmethod
def queue_bind(*args, **kwargs):
return None
@staticmethod
def basic_consume(*args, **kwargs):
return None
@staticmethod
def basic_publish(*args, **kwargs):
return None
@staticmethod
def start_consuming(*args, **kwargs):
return None
class MyBlockingConnection:
def __init__(self, *args, **kwargs):
...
@staticmethod
def channel():
return MyChannel()
@@ -0,0 +1,7 @@
class MyResponse:
def __init__(self, *args, **kwargs):
...
@staticmethod
def json(*args, **kwargs):
return {'cursor': []}
@@ -0,0 +1,15 @@
from dataclasses import dataclass
from datetime import datetime
@dataclass
class TargetVelocity:
vin: str
target_velocity: float
timestamp: datetime
def to_dict(self):
return {'vin': self.vin,
'target_velocity': self.target_velocity,
'timestamp': self.timestamp.timestamp()
}
@@ -0,0 +1,6 @@
from enum import Enum
class TrafficLightColor(Enum):
GREEN = 0
RED = 1
@@ -0,0 +1,16 @@
from dataclasses import dataclass
from datetime import datetime
from dse_shared_libs.traffic_light_color import TrafficLightColor
@dataclass
class TrafficLightState:
tlid: str
color: TrafficLightColor
last_switch: datetime
def to_dict(self):
return {'tlid': self.tlid,
'color': self.color.name,
'last_switch': self.last_switch.timestamp()}
+9
View File
@@ -0,0 +1,9 @@
from setuptools import find_packages, setup
setup(name='dse-shared-libs',
version=1,
packages=find_packages(),
include_package_data=True,
license='BSD License', # example licensez
install_requires=['pika', 'geopy']
)