busline 0.3.1__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. busline/client/client.py +27 -0
  2. busline/{eventbus_client → client}/eventbus_connector.py +5 -10
  3. busline/client/multiclient.py +41 -0
  4. busline/client/publisher/publisher.py +60 -0
  5. busline/client/pubsub_client.py +128 -0
  6. busline/client/subscriber/event_handler/closure_event_handler.py +18 -0
  7. busline/client/subscriber/event_handler/event_handler.py +15 -0
  8. busline/client/subscriber/event_handler/multi_handler.py +30 -0
  9. busline/client/subscriber/subscriber.py +109 -0
  10. busline/client/subscriber/topic_subscriber.py +79 -0
  11. busline/event/event.py +43 -21
  12. busline/event/registry.py +67 -0
  13. busline/event/test.py +47 -0
  14. busline/exceptions.py +8 -0
  15. busline/local/__init__.py +3 -0
  16. busline/local/eventbus/__init__.py +0 -0
  17. busline/local/eventbus/async_local_eventbus.py +26 -0
  18. busline/local/eventbus/eventbus.py +86 -0
  19. busline/local/eventbus/local_eventbus.py +23 -0
  20. busline/local/local_pubsub_client.py +54 -0
  21. busline/local/publisher/__init__.py +0 -0
  22. busline/local/publisher/local_publisher.py +38 -0
  23. busline/local/subscriber/__init__.py +0 -0
  24. busline/local/subscriber/local_subscriber.py +43 -0
  25. busline/local/test.py +156 -0
  26. busline-0.5.0.dist-info/METADATA +215 -0
  27. busline-0.5.0.dist-info/RECORD +36 -0
  28. {busline-0.3.1.dist-info → busline-0.5.0.dist-info}/WHEEL +1 -1
  29. busline/event/event_content.py +0 -18
  30. busline/event/event_metadata.py +0 -26
  31. busline/eventbus/async_local_eventbus.py +0 -32
  32. busline/eventbus/eventbus.py +0 -112
  33. busline/eventbus/exceptions.py +0 -2
  34. busline/eventbus/queued_local_eventbus.py +0 -50
  35. busline/eventbus/topic.py +0 -35
  36. busline/eventbus_client/eventbus_client.py +0 -99
  37. busline/eventbus_client/exceptions.py +0 -4
  38. busline/eventbus_client/local_eventbus_client.py +0 -25
  39. busline/eventbus_client/publisher/local_eventbus_publisher.py +0 -35
  40. busline/eventbus_client/publisher/publisher.py +0 -59
  41. busline/eventbus_client/subscriber/closure_event_listener.py +0 -19
  42. busline/eventbus_client/subscriber/event_listener.py +0 -15
  43. busline/eventbus_client/subscriber/local_eventbus_closure_subscriber.py +0 -17
  44. busline/eventbus_client/subscriber/local_eventbus_subscriber.py +0 -40
  45. busline/eventbus_client/subscriber/subscriber.py +0 -93
  46. busline-0.3.1.dist-info/METADATA +0 -111
  47. busline-0.3.1.dist-info/RECORD +0 -30
  48. /busline/{eventbus → client}/__init__.py +0 -0
  49. /busline/{eventbus_client → client/publisher}/__init__.py +0 -0
  50. /busline/{eventbus_client/publisher → client/subscriber}/__init__.py +0 -0
  51. /busline/{eventbus_client/subscriber → client/subscriber/event_handler}/__init__.py +0 -0
  52. {busline-0.3.1.dist-info → busline-0.5.0.dist-info/licenses}/LICENSE +0 -0
  53. {busline-0.3.1.dist-info → busline-0.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,27 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Optional
3
+
4
+ from busline.event.event import Event
5
+
6
+
7
+ class EventBusClient(ABC):
8
+
9
+ @abstractmethod
10
+ async def connect(self):
11
+ pass
12
+
13
+ @abstractmethod
14
+ async def disconnect(self):
15
+ pass
16
+
17
+ @abstractmethod
18
+ async def publish(self, topic: str, event: Event, **kwargs):
19
+ pass
20
+
21
+ @abstractmethod
22
+ async def subscribe(self, topic: str, **kwargs):
23
+ pass
24
+
25
+ @abstractmethod
26
+ async def unsubscribe(self, topic: Optional[str] = None, **kwargs):
27
+ pass
@@ -1,24 +1,19 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from uuid import uuid4
3
+ from typing import Optional
4
+ from dataclasses import dataclass, field
3
5
 
4
6
 
7
+ @dataclass(kw_only=True)
5
8
  class EventBusConnector(ABC):
6
9
  """
7
- Abstract class which is used as base class to create a component which interacts with eventbus
10
+ Abstract class which provides methods to interact with eventbus
8
11
 
9
12
  Author: Nicola Ricciardi
10
13
  """
11
14
 
12
- def __init__(self, connector_id: str = str(uuid4())):
13
- self._id = connector_id
15
+ identifier: str = field(default=str(uuid4()))
14
16
 
15
- @property
16
- def id(self) -> str:
17
- return self._id
18
-
19
- @id.setter
20
- def id(self, value):
21
- self._id = value
22
17
 
23
18
  @abstractmethod
24
19
  async def connect(self):
@@ -0,0 +1,41 @@
1
+ import asyncio
2
+ from dataclasses import dataclass
3
+ from typing import List, Optional, override
4
+
5
+ from busline.client.client import EventBusClient
6
+ from busline.event.event import Event
7
+
8
+
9
+ @dataclass
10
+ class EventBusMultiClient(EventBusClient):
11
+
12
+ clients: List[EventBusClient]
13
+
14
+ @classmethod
15
+ def from_client(cls, client: EventBusClient):
16
+ return cls([client])
17
+
18
+ @override
19
+ async def connect(self):
20
+ tasks = [client.connect() for client in self.clients]
21
+ await asyncio.gather(*tasks)
22
+
23
+ @override
24
+ async def disconnect(self):
25
+ tasks = [client.disconnect() for client in self.clients]
26
+ await asyncio.gather(*tasks)
27
+
28
+ @override
29
+ async def publish(self, topic: str, event: Event, **kwargs):
30
+ tasks = [client.publish(topic, event, **kwargs) for client in self.clients]
31
+ await asyncio.gather(*tasks)
32
+
33
+ @override
34
+ async def subscribe(self, topic: str, **kwargs):
35
+ tasks = [client.subscribe(topic, **kwargs) for client in self.clients]
36
+ await asyncio.gather(*tasks)
37
+
38
+ @override
39
+ async def unsubscribe(self, topic: Optional[str] = None, **kwargs):
40
+ tasks = [client.unsubscribe(topic, **kwargs) for client in self.clients]
41
+ await asyncio.gather(*tasks)
@@ -0,0 +1,60 @@
1
+ import logging
2
+ from abc import ABC, abstractmethod
3
+ from dataclasses import dataclass
4
+ from busline.event.event import Event
5
+ from busline.client.eventbus_connector import EventBusConnector
6
+
7
+
8
+ @dataclass
9
+ class Publisher(EventBusConnector, ABC):
10
+ """
11
+ Abstract class which can be implemented by your components which must be able to publish on eventbus
12
+
13
+ Author: Nicola Ricciardi
14
+ """
15
+
16
+ def __repr__(self) -> str:
17
+ return f"Publisher({self.identifier})"
18
+
19
+ @abstractmethod
20
+ async def _internal_publish(self, topic: str, event: Event, **kwargs):
21
+ """
22
+ Actual publish on topic the event
23
+
24
+ :param topic:
25
+ :param event:
26
+ :return:
27
+ """
28
+
29
+ async def publish(self, topic: str, event: Event, **kwargs):
30
+ """
31
+ Publish on topic the event
32
+
33
+ :param topic:
34
+ :param event:
35
+ :return:
36
+ """
37
+
38
+ logging.info(f"{self}: publish on {topic} -> {event}")
39
+ await self.on_publishing(topic, event, **kwargs)
40
+ await self._internal_publish(topic, event, **kwargs)
41
+ await self.on_published(topic, event, **kwargs)
42
+
43
+
44
+ async def on_publishing(self, topic: str, event: Event, **kwargs):
45
+ """
46
+ Callback called on publishing start
47
+
48
+ :param topic:
49
+ :param event:
50
+ :return:
51
+ """
52
+
53
+ async def on_published(self, topic: str, event: Event, **kwargs):
54
+ """
55
+ Callback called on publishing end
56
+
57
+ :param topic:
58
+ :param event:
59
+ :return:
60
+ """
@@ -0,0 +1,128 @@
1
+ import asyncio
2
+ from dataclasses import dataclass, field
3
+ from typing import Optional, override, List, Self
4
+
5
+ from busline.client.client import EventBusClient
6
+ from busline.client.publisher.publisher import Publisher
7
+ from busline.event.event import Event
8
+ from busline.client.subscriber.subscriber import Subscriber
9
+
10
+
11
+ @dataclass
12
+ class PubSubClient(EventBusClient):
13
+ """
14
+ Eventbus client which should used by components which wouldn't be a publisher/subscriber, but they need them
15
+
16
+ Author: Nicola Ricciardi
17
+ """
18
+
19
+ publishers: List[Publisher]
20
+ subscribers: List[Subscriber]
21
+
22
+ @classmethod
23
+ def from_pubsub(cls, publisher: Optional[Publisher] = None, subscriber: Optional[Subscriber] = None) -> Self:
24
+
25
+ publishers = []
26
+ if publisher is not None:
27
+ publishers = [publisher]
28
+
29
+ subscribers = []
30
+ if subscriber is not None:
31
+ subscribers = [subscriber]
32
+
33
+ return cls(publishers, subscribers)
34
+
35
+ @classmethod
36
+ def from_pubsub_client(cls, client: 'PubSubClient') -> Self:
37
+ return cls(client.publishers.copy(), client.subscribers.copy())
38
+
39
+ @override
40
+ async def connect(self):
41
+ """
42
+ Connect all publishers and subscribers
43
+ """
44
+
45
+ tasks = [publisher.connect() for publisher in self.publishers]
46
+ tasks += [subscriber.connect() for subscriber in self.subscribers]
47
+
48
+ await asyncio.gather(*tasks)
49
+
50
+ @override
51
+ async def disconnect(self):
52
+ """
53
+ Disconnect all publishers and subscribers
54
+ """
55
+
56
+ tasks = [publisher.disconnect() for publisher in self.publishers]
57
+ tasks += [subscriber.disconnect() for subscriber in self.subscribers]
58
+
59
+ await asyncio.gather(*tasks)
60
+
61
+ @override
62
+ async def publish(self, topic: str, event: Event, **kwargs):
63
+ """
64
+ Publish event using all publishers
65
+ """
66
+
67
+ await asyncio.gather(*[
68
+ publisher.publish(topic, event, **kwargs) for publisher in self.publishers
69
+ ])
70
+
71
+ @override
72
+ async def subscribe(self, topic: str, **kwargs):
73
+ """
74
+ Subscribe all subscribers on topic
75
+ """
76
+
77
+ await asyncio.gather(*[
78
+ subscriber.subscribe(topic, **kwargs) for subscriber in self.subscribers
79
+ ])
80
+
81
+ @override
82
+ async def unsubscribe(self, topic: Optional[str] = None, **kwargs):
83
+ """
84
+ Alias of `client.subscriber.unsubscribe(...)`
85
+ """
86
+
87
+ await asyncio.gather(*[
88
+ subscriber.unsubscribe(topic, **kwargs) for subscriber in self.subscribers
89
+ ])
90
+
91
+
92
+ @dataclass
93
+ class PubSubClientBuilder:
94
+ """
95
+ Builder for a pub/sub client.
96
+
97
+ Author: Nicola Ricciardi
98
+ """
99
+
100
+ base_client: PubSubClient = field(
101
+ default_factory=lambda: PubSubClient([], []),
102
+ kw_only=True
103
+ )
104
+
105
+
106
+ def with_publisher(self, publisher: Publisher) -> Self:
107
+ self.base_client.publishers.append(publisher)
108
+
109
+ return self
110
+
111
+ def with_publishers(self, publishers: List[Publisher]) -> Self:
112
+ self.base_client.publishers.extend(publishers)
113
+
114
+ return self
115
+
116
+ def with_subscriber(self, subscriber: Subscriber) -> Self:
117
+ self.base_client.subscribers.append(subscriber)
118
+
119
+ return self
120
+
121
+ def with_subscribers(self, subscribers: List[Subscriber]) -> Self:
122
+ self.base_client.subscribers.extend(subscribers)
123
+
124
+ return self
125
+
126
+ def build(self) -> PubSubClient:
127
+ return self.base_client
128
+
@@ -0,0 +1,18 @@
1
+ from typing import Callable
2
+ from dataclasses import dataclass
3
+ from busline.event.event import Event
4
+ from busline.client.subscriber.event_handler.event_handler import EventHandler
5
+
6
+
7
+ @dataclass
8
+ class ClosureEventHandler(EventHandler):
9
+ """
10
+ Event handler which use a pre-defined callback as `on_event`
11
+
12
+ Author: Nicola Ricciardi
13
+ """
14
+
15
+ on_event_callback: Callable[[str, Event], None]
16
+
17
+ async def handle(self, topic: str, event: Event):
18
+ self.on_event_callback(topic, event)
@@ -0,0 +1,15 @@
1
+ from abc import ABC, abstractmethod
2
+ from busline.event.event import Event
3
+
4
+
5
+ class EventHandler(ABC):
6
+
7
+ @abstractmethod
8
+ async def handle(self, topic: str, event: Event):
9
+ """
10
+ Manage an event of a topic
11
+
12
+ :param topic:
13
+ :param event:
14
+ :return:
15
+ """
@@ -0,0 +1,30 @@
1
+ from typing import List
2
+ import asyncio
3
+ from dataclasses import dataclass
4
+ from busline.event.event import Event
5
+ from busline.client.subscriber.event_handler.event_handler import EventHandler
6
+
7
+
8
+ @dataclass
9
+ class MultiEventHandler(EventHandler):
10
+ """
11
+ Call a batch of pre-defined handlers.
12
+
13
+ Another parameter can be specified. If strict order is False, async capabilities can be exploited
14
+
15
+ Author: Nicola Ricciardi
16
+ """
17
+
18
+ handlers: List[EventHandler]
19
+ strict_order: bool = False
20
+
21
+
22
+ async def handle(self, topic: str, event: Event):
23
+ if self.strict_order:
24
+ for handler in self.handlers:
25
+ await handler.handle(topic, event)
26
+ else:
27
+ tasks = [handler.handle(topic, event) for handler in self.handlers]
28
+
29
+ await asyncio.gather(*tasks)
30
+
@@ -0,0 +1,109 @@
1
+ import logging
2
+ from abc import ABC, abstractmethod
3
+ from dataclasses import dataclass
4
+ from typing import Optional
5
+
6
+ from busline.client.eventbus_connector import EventBusConnector
7
+ from busline.event.event import Event
8
+
9
+
10
+ @dataclass
11
+ class Subscriber(EventBusConnector, ABC):
12
+ """
13
+ Abstract class which can be implemented by your components which must be able to subscribe on eventbus
14
+
15
+ Author: Nicola Ricciardi
16
+ """
17
+
18
+ def __repr__(self) -> str:
19
+ return f"Subscriber({self.identifier})"
20
+
21
+ @abstractmethod
22
+ async def on_event(self, topic: str, event: Event):
23
+ """
24
+ Callback called when an event arrives from a topic
25
+ """
26
+
27
+ async def notify(self, topic: str, event: Event, **kwargs):
28
+ """
29
+ Notify subscriber
30
+ """
31
+
32
+ logging.info(f"{self}: incoming event on {topic} -> {event}")
33
+ await self.on_event(topic, event)
34
+
35
+ @abstractmethod
36
+ async def _internal_subscribe(self, topic: str, **kwargs):
37
+ """
38
+ Actual subscribe to topic
39
+
40
+ :param topic:
41
+ :return:
42
+ """
43
+
44
+ @abstractmethod
45
+ async def _internal_unsubscribe(self, topic: Optional[str] = None, **kwargs):
46
+ """
47
+ Actual unsubscribe to topic
48
+
49
+ :param topic:
50
+ :return:
51
+ """
52
+
53
+ async def subscribe(self, topic: str, **kwargs):
54
+ """
55
+ Subscribe to topic
56
+
57
+ :param topic:
58
+ :return:
59
+ """
60
+
61
+ logging.info(f"{self}: subscribe on topic {topic}")
62
+ await self._on_subscribing(topic, **kwargs)
63
+ await self._internal_subscribe(topic, **kwargs)
64
+ await self._on_subscribed(topic, **kwargs)
65
+
66
+ async def unsubscribe(self, topic: Optional[str] = None, **kwargs):
67
+ """
68
+ Unsubscribe to topic
69
+
70
+ :param topic:
71
+ :return:
72
+ """
73
+
74
+ logging.info(f"{self}: unsubscribe from topic {topic}")
75
+ await self._on_unsubscribing(topic, **kwargs)
76
+ await self._internal_unsubscribe(topic, **kwargs)
77
+ await self._on_unsubscribed(topic, **kwargs)
78
+
79
+ async def _on_subscribing(self, topic: str, **kwargs):
80
+ """
81
+ Callback called on subscribing
82
+
83
+ :param topic:
84
+ :return:
85
+ """
86
+
87
+ async def _on_subscribed(self, topic: str, **kwargs):
88
+ """
89
+ Callback called on subscribed
90
+
91
+ :param topic:
92
+ :return:
93
+ """
94
+
95
+ async def _on_unsubscribing(self, topic: Optional[str], **kwargs):
96
+ """
97
+ Callback called on unsubscribing
98
+
99
+ :param topic:
100
+ :return:
101
+ """
102
+
103
+ async def _on_unsubscribed(self, topic: Optional[str], **kwargs):
104
+ """
105
+ Callback called on unsubscribed
106
+
107
+ :param topic:
108
+ :return:
109
+ """
@@ -0,0 +1,79 @@
1
+ import asyncio
2
+ import logging
3
+ from abc import ABC
4
+ from typing import Dict, List, Callable, Optional, override
5
+ from dataclasses import dataclass, field
6
+ from busline.client.subscriber.event_handler.event_handler import EventHandler
7
+ from busline.client.subscriber.subscriber import Subscriber
8
+ from busline.event.event import Event
9
+ from busline.exceptions import EventHandlerNotFound
10
+
11
+
12
+ @dataclass(kw_only=True)
13
+ class TopicSubscriber(Subscriber, ABC):
14
+ """
15
+ Handles different topic events using ad hoc handlers defined by user,
16
+ else it uses fallback handler if provided (otherwise throws an exception)
17
+
18
+ Attributes:
19
+ fallback_event_handler: event handler used for a topic if no event handler is specified for that topic
20
+ handlers: event handler for each topic (i.e. key); notice that key can also be a string with wildcards
21
+ topic_names_matcher: function used to check match between two topic name (with wildcards); default "t1 == t2"
22
+ event_handler_always_required: raise an exception if no handlers are found for a topic
23
+
24
+ Author: Nicola Ricciardi
25
+ """
26
+
27
+ fallback_event_handler: Optional[EventHandler] = field(default=None)
28
+ handlers: Dict[str, EventHandler] = field(default_factory=dict)
29
+ topic_names_matcher: Callable[[str, str], bool] = field(repr=False, default=lambda t1, t2: t1 == t2)
30
+ event_handler_always_required: bool = field(default=False)
31
+
32
+ @override
33
+ async def _on_subscribing(self, topic: str, handler: Optional[EventHandler] = None, **kwargs):
34
+
35
+ if self.fallback_event_handler is None:
36
+ if self.event_handler_always_required:
37
+ raise EventHandlerNotFound()
38
+ else:
39
+ logging.warning(f"{self}: event handler for topic '{topic}' not found")
40
+
41
+ @override
42
+ async def _on_subscribed(self, topic: str, handler: Optional[EventHandler] = None, **kwargs):
43
+
44
+ self.handlers[topic] = handler
45
+
46
+ @override
47
+ async def _on_unsubscribed(self, topic: str | None, **kwargs):
48
+
49
+ if topic is None:
50
+ self.handlers = {}
51
+ else:
52
+ del self.handlers[topic]
53
+
54
+ def __get_handlers_of_topic(self, topic: str) -> List[EventHandler]:
55
+
56
+ handlers = []
57
+ for t, h in self.handlers.items():
58
+ if self.topic_names_matcher(topic, t):
59
+ if h is not None:
60
+ handlers.append(h)
61
+ else:
62
+ if self.fallback_event_handler is not None:
63
+ handlers.append(self.fallback_event_handler)
64
+ else:
65
+ if self.event_handler_always_required:
66
+ raise EventHandlerNotFound()
67
+ else:
68
+ logging.warning(f"{self}: event handler for topic '{topic}' not found")
69
+
70
+ return handlers
71
+
72
+ @override
73
+ async def on_event(self, topic: str, event: Event):
74
+
75
+ handlers_of_topic: List[EventHandler] = self.__get_handlers_of_topic(topic)
76
+
77
+ if len(handlers_of_topic) > 0:
78
+ tasks = [handler.handle(topic, event) for handler in handlers_of_topic]
79
+ await asyncio.gather(*tasks)
busline/event/event.py CHANGED
@@ -1,25 +1,47 @@
1
+ import json
1
2
  import uuid
2
- from busline.event.event_content import EventContent
3
- from busline.event.event_metadata import EventMetadata
3
+ from typing import Any, Self
4
+ import datetime
5
+ from dataclasses import dataclass, field
6
+ from typing import Optional
4
7
 
5
8
 
9
+ @dataclass(frozen=True)
6
10
  class Event:
7
-
8
- def __init__(self, content: EventContent = None, metadata: EventMetadata = EventMetadata()):
9
-
10
- self._identifier = str(uuid.uuid4())
11
- self._content = content
12
- self._metadata = metadata
13
-
14
-
15
- @property
16
- def identifier(self) -> str:
17
- return self._identifier
18
-
19
- @property
20
- def content(self) -> EventContent:
21
- return self._content
22
-
23
- @property
24
- def metadata(self) -> EventMetadata:
25
- return self._metadata
11
+ """
12
+ Event publishable in an eventbus
13
+
14
+ Author: Nicola Ricciardi
15
+ """
16
+
17
+ identifier: str = field(default=str(uuid.uuid4()))
18
+ content: Any = field(default=None)
19
+ content_type: Optional[str] = field(default=None)
20
+ event_type: Optional[str] = field(default=None)
21
+ timestamp: float = field(default_factory=lambda: datetime.datetime.now(datetime.timezone.utc).timestamp())
22
+ metadata: dict = field(default_factory=dict)
23
+
24
+ @classmethod
25
+ def from_json(cls, json_str: str) -> Self:
26
+ """
27
+ Build event object from JSON string
28
+ """
29
+
30
+ return cls(**json.loads(json_str))
31
+
32
+ @classmethod
33
+ def from_event(cls, event: 'Event') -> Self:
34
+ """
35
+ Build event object based on Event (base) class.
36
+
37
+ This method can be useful when Event class is inherited and an event registry is used.
38
+ """
39
+
40
+ return cls(
41
+ identifier=event.identifier,
42
+ content=event.content,
43
+ content_type=event.content_type,
44
+ event_type=event.event_type,
45
+ timestamp=event.timestamp,
46
+ metadata=event.metadata
47
+ )
@@ -0,0 +1,67 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Dict, Type
3
+
4
+ from busline.event.event import Event
5
+
6
+ class _Singleton(type):
7
+ _instances = {}
8
+
9
+ def __call__(cls, *args, **kwargs):
10
+ if cls not in cls._instances:
11
+ cls._instances[cls] = super().__call__(*args, **kwargs)
12
+ return cls._instances[cls]
13
+
14
+
15
+ @dataclass
16
+ class EventRegistry(metaclass=_Singleton):
17
+ """
18
+ Registry to manage different event types
19
+
20
+ Author: Nicola Ricciardi
21
+ """
22
+
23
+ associations: Dict[str, Type[Event]] = field(default_factory=dict)
24
+
25
+ def unregister(self, event_type: str):
26
+ """
27
+ Remove an event type association
28
+ """
29
+
30
+ self.associations.pop(event_type)
31
+
32
+ def register(self, event_type: str, event_class: Type[Event]):
33
+ """
34
+ Add a new association between an event type and an event class
35
+ """
36
+
37
+ self.associations[event_type] = event_class
38
+
39
+ def retrive_class(self, event) -> Type[Event]:
40
+ """
41
+ Retrive event class of event input based on saved associations and given event type
42
+
43
+ KeyError is raised if no association is found
44
+ """
45
+
46
+ return self.associations[event.event_type]
47
+
48
+ def convert(self, event: Event, raise_on_miss: bool = True) -> Event:
49
+ """
50
+ Convert a generic event, auto-building the right event class based on event type.
51
+
52
+ If raise_on_miss=True, a KeyError exception is raised. Otherwise, input is returned in output.
53
+ """
54
+
55
+ if event.event_type not in self.associations and not raise_on_miss:
56
+ return event
57
+
58
+ event_class: Type[Event] = self.retrive_class(event)
59
+
60
+ return event_class.from_event(event)
61
+
62
+
63
+
64
+
65
+
66
+
67
+