busline 0.3.0__tar.gz → 0.5.0__tar.gz

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 (63) hide show
  1. busline-0.5.0/PKG-INFO +215 -0
  2. busline-0.5.0/README.md +200 -0
  3. busline-0.5.0/busline/client/client.py +27 -0
  4. {busline-0.3.0/src/eventbus_client → busline-0.5.0/busline/client}/eventbus_connector.py +5 -10
  5. busline-0.5.0/busline/client/multiclient.py +41 -0
  6. busline-0.5.0/busline/client/publisher/publisher.py +60 -0
  7. busline-0.5.0/busline/client/pubsub_client.py +128 -0
  8. busline-0.5.0/busline/client/subscriber/event_handler/closure_event_handler.py +18 -0
  9. busline-0.5.0/busline/client/subscriber/event_handler/event_handler.py +15 -0
  10. busline-0.5.0/busline/client/subscriber/event_handler/multi_handler.py +30 -0
  11. busline-0.5.0/busline/client/subscriber/subscriber.py +109 -0
  12. busline-0.5.0/busline/client/subscriber/topic_subscriber.py +79 -0
  13. busline-0.5.0/busline/event/event.py +47 -0
  14. busline-0.5.0/busline/event/registry.py +67 -0
  15. busline-0.5.0/busline/event/test.py +47 -0
  16. busline-0.5.0/busline/exceptions.py +8 -0
  17. busline-0.5.0/busline/local/__init__.py +3 -0
  18. busline-0.5.0/busline/local/eventbus/__init__.py +0 -0
  19. busline-0.5.0/busline/local/eventbus/async_local_eventbus.py +26 -0
  20. busline-0.5.0/busline/local/eventbus/eventbus.py +86 -0
  21. busline-0.5.0/busline/local/eventbus/local_eventbus.py +23 -0
  22. busline-0.5.0/busline/local/local_pubsub_client.py +54 -0
  23. busline-0.5.0/busline/local/publisher/__init__.py +0 -0
  24. busline-0.5.0/busline/local/publisher/local_publisher.py +38 -0
  25. busline-0.5.0/busline/local/subscriber/__init__.py +0 -0
  26. busline-0.5.0/busline/local/subscriber/local_subscriber.py +43 -0
  27. busline-0.5.0/busline/local/test.py +156 -0
  28. busline-0.5.0/busline.egg-info/PKG-INFO +215 -0
  29. busline-0.5.0/busline.egg-info/SOURCES.txt +38 -0
  30. busline-0.5.0/busline.egg-info/top_level.txt +1 -0
  31. {busline-0.3.0 → busline-0.5.0}/pyproject.toml +1 -1
  32. busline-0.3.0/PKG-INFO +0 -104
  33. busline-0.3.0/README.md +0 -90
  34. busline-0.3.0/src/busline.egg-info/PKG-INFO +0 -104
  35. busline-0.3.0/src/busline.egg-info/SOURCES.txt +0 -32
  36. busline-0.3.0/src/busline.egg-info/top_level.txt +0 -4
  37. busline-0.3.0/src/event/event.py +0 -25
  38. busline-0.3.0/src/event/event_content.py +0 -18
  39. busline-0.3.0/src/event/event_metadata.py +0 -26
  40. busline-0.3.0/src/eventbus/async_local_eventbus.py +0 -32
  41. busline-0.3.0/src/eventbus/eventbus.py +0 -112
  42. busline-0.3.0/src/eventbus/exceptions.py +0 -2
  43. busline-0.3.0/src/eventbus/queued_local_eventbus.py +0 -50
  44. busline-0.3.0/src/eventbus/topic.py +0 -35
  45. busline-0.3.0/src/eventbus_client/eventbus_client.py +0 -99
  46. busline-0.3.0/src/eventbus_client/exceptions.py +0 -4
  47. busline-0.3.0/src/eventbus_client/local_eventbus_client.py +0 -25
  48. busline-0.3.0/src/eventbus_client/publisher/local_eventbus_publisher.py +0 -35
  49. busline-0.3.0/src/eventbus_client/publisher/publisher.py +0 -59
  50. busline-0.3.0/src/eventbus_client/subscriber/closure_event_listener.py +0 -19
  51. busline-0.3.0/src/eventbus_client/subscriber/event_listener.py +0 -15
  52. busline-0.3.0/src/eventbus_client/subscriber/local_eventbus_closure_subscriber.py +0 -17
  53. busline-0.3.0/src/eventbus_client/subscriber/local_eventbus_subscriber.py +0 -40
  54. busline-0.3.0/src/eventbus_client/subscriber/subscriber.py +0 -93
  55. {busline-0.3.0 → busline-0.5.0}/LICENSE +0 -0
  56. {busline-0.3.0/src → busline-0.5.0/busline}/__init__.py +0 -0
  57. {busline-0.3.0/src/event → busline-0.5.0/busline/client}/__init__.py +0 -0
  58. {busline-0.3.0/src/eventbus → busline-0.5.0/busline/client/publisher}/__init__.py +0 -0
  59. {busline-0.3.0/src/eventbus_client → busline-0.5.0/busline/client/subscriber}/__init__.py +0 -0
  60. {busline-0.3.0/src/eventbus_client/publisher → busline-0.5.0/busline/client/subscriber/event_handler}/__init__.py +0 -0
  61. {busline-0.3.0/src/eventbus_client/subscriber → busline-0.5.0/busline/event}/__init__.py +0 -0
  62. {busline-0.3.0/src → busline-0.5.0}/busline.egg-info/dependency_links.txt +0 -0
  63. {busline-0.3.0 → busline-0.5.0}/setup.cfg +0 -0
busline-0.5.0/PKG-INFO ADDED
@@ -0,0 +1,215 @@
1
+ Metadata-Version: 2.4
2
+ Name: busline
3
+ Version: 0.5.0
4
+ Summary: Agnostic eventbus for Python
5
+ Author-email: Nicola Ricciardi <ricciardincl@gmail.com>
6
+ Project-URL: Homepage, https://github.com/nricciardi/py-busline
7
+ Project-URL: Issues, https://github.com/nricciardi/py-busline/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Dynamic: license-file
15
+
16
+ from busline.local.subscriber.local_subscriber import LocalEventBusSubscriberfrom busline.local.eventbus.local_eventbus import LocalEventBus
17
+
18
+ # Busline for Python
19
+
20
+ Agnostic eventbus for Python.
21
+
22
+ Official eventbus library for [Orbitalis](https://github.com/orbitalis-framework/py-orbitalis)
23
+
24
+ ## Get Start
25
+
26
+ ### Local EventBus
27
+
28
+ #### Using Publisher/Subscriber
29
+
30
+ ```python
31
+ local_eventbus_instance1 = LocalEventBus() # singleton
32
+ local_eventbus_instance2 = LocalEventBus() # singleton
33
+
34
+ subscriber = LocalEventBusSubscriber(
35
+ eventbus=local_eventbus_instance1,
36
+ fallback_event_handler=ClosureEventHandler(lambda t, e: ...)
37
+ )
38
+ publisher = LocalEventBusPublisher(eventbus=local_eventbus_instance2)
39
+
40
+ await subscriber.connect()
41
+ await publisher.connect()
42
+
43
+ await subscriber.subscribe("topic-name")
44
+
45
+ await publisher.publish("topic-name", Event()) # publish empty event
46
+
47
+ # ...subscriber receives Event() thanks to singleton LocalEventBus()
48
+
49
+ await subscriber.disconnect()
50
+ await publisher.disconnect()
51
+ ```
52
+
53
+ #### Using EventBusClient
54
+
55
+ ```python
56
+ client = LocalPubSubClientBuilder()\
57
+ .with_default_publisher()\
58
+ .with_closure_subscriber(lambda t, e: ...)\
59
+ .build()
60
+
61
+ # NOTE: both publisher and subscriber will use singleton local eventbus (default)
62
+
63
+ await client.connect()
64
+
65
+ await client.subscribe("topic-name")
66
+
67
+ await client.publish("topic-name", Event()) # publish empty event
68
+
69
+ # ...client receives Event()
70
+
71
+ await client.disconnect()
72
+ ```
73
+
74
+ #### MultiClient
75
+
76
+ ```python
77
+ local_eventbus_instance1 = AsyncLocalEventBus() # not singleton
78
+ local_eventbus_instance2 = AsyncLocalEventBus() # not singleton
79
+
80
+ client1 = LocalPubSubClientBuilder(local_eventbus_instance1)\
81
+ .with_default_publisher()\
82
+ .with_closure_subscriber(lambda t, e: ...)\
83
+ .build()
84
+
85
+ # NOTE: client1 pub/sub use `local_eventbus_instance1`
86
+
87
+ client2 = LocalPubSubClientBuilder(local_eventbus_instance2)\
88
+ .with_default_publisher()\
89
+ .with_closure_subscriber(lambda t, e: ...)\
90
+ .build()
91
+
92
+ # NOTE: client2 pub/sub use `local_eventbus_instance2`
93
+
94
+ multi_client = EventBusMultiClient([
95
+ client1,
96
+ client2
97
+ ])
98
+
99
+ await multi_client.connect()
100
+
101
+ await multi_client.subscribe("topic-name", handler=ClosureEventHandler(lambda t, e: ...))
102
+
103
+ await multi_client.publish("topic-name", Event())
104
+
105
+ # ...both clients receive Event() and handle it using `on_event_callback`
106
+
107
+ await multi_client.disconnect()
108
+ ```
109
+
110
+ #### Specifying EventBus
111
+
112
+ Local eventbuses use an internal implemented `EventBus`, this sort of architecture is not required in other scenarios such
113
+ as MQTT, because the "eventbus" is the broken.
114
+
115
+ Anyway, you may want to specify what `EventBus` instance your pub/sub components should use:
116
+
117
+ ```python
118
+ local_eventbus_instance = AsyncLocalEventBus()
119
+
120
+ subscriber = LocalEventBusClosureSubscriber(callback, eventbus_instance=local_eventbus_instance)
121
+ publisher = LocalEventBusPublisher(eventbus_instance=local_eventbus_instance2)
122
+ ```
123
+
124
+ ### EventRegistry
125
+
126
+ In order to help `event_type` management, a basic `EventRegistry` is provided to auto-build right inherited class of `Event`:
127
+
128
+ ```python
129
+ class Event1(Event):
130
+ def my_value1(self) -> int:
131
+ return self.content
132
+
133
+ class Event2(Event):
134
+ def my_value2(self) -> int:
135
+ return self.content
136
+
137
+
138
+ event_registry = EventRegistry() # singleton
139
+
140
+ event_registry.register("event1", Event1)
141
+ event_registry.register("event2", Event2)
142
+
143
+ generic_event1 = Event(content=1, event_type="event1")
144
+ generic_event2 = Event(content=2, event_type="event2")
145
+ generic_unknown_event = Event(content=2, event_type="unknown")
146
+
147
+ # first approach
148
+ event1_class = event_registry.retrive_class(generic_event1)
149
+ event1_class: Type[Event1] = event1_class
150
+ event1 = event1_class.from_event(generic_event1)
151
+
152
+ # second approach
153
+ event2: Event2 = event_registry.convert(generic_event2)
154
+ ```
155
+
156
+ ### Create Agnostic EventBus
157
+
158
+ Implement business logic of your `Publisher` and `Subscriber` and... done. Nothing more.
159
+
160
+ ```python
161
+ class YourEventBusPublisher(Publisher):
162
+
163
+ async def _internal_publish(self, topic_name: str, event: Event, **kwargs):
164
+ pass # send events to your eventbus (maybe in cloud?)
165
+ ```
166
+
167
+ ```python
168
+ class YourEventBusSubscriber(Subscriber):
169
+
170
+ async def on_event(self, topic_name: str, event: Event, **kwargs):
171
+ pass # receive your events
172
+ ```
173
+
174
+ You could create a client to allow components to use it instead of become a publisher or subscriber.
175
+
176
+ ```python
177
+ subscriber = YourEventBusSubscriber(...)
178
+ publisher = YourEventBusPublisher(...)
179
+
180
+ client = PubSubClient(publisher, subscriber)
181
+ ```
182
+
183
+
184
+ ## Subscriber
185
+
186
+ `Subscriber` is the component which receives events. It is a `EventHandler`, therefore it has `on_event` method in which
187
+ every event (and related topic) is passed.
188
+
189
+ ### TopicSubscriber
190
+
191
+ `TopicSubscriber` is an enhanced subscriber which manages an handler for each topic. We can specify a _fallback handler_,
192
+ which is run if no handler is spefied for a subscribed topic.
193
+
194
+ If the subscriber is not subscribed to a topic, fallback handler is not called.
195
+
196
+ A local implementation is already provided:
197
+
198
+ ```python
199
+ subscriber = LocalEventBusSubscriber(fallback_event_handler=...)
200
+
201
+ await subscriber.subscribe("t1")
202
+ await subscriber.subscribe("t2", handler=...)
203
+ ```
204
+
205
+
206
+
207
+
208
+
209
+
210
+
211
+
212
+
213
+
214
+
215
+
@@ -0,0 +1,200 @@
1
+ from busline.local.subscriber.local_subscriber import LocalEventBusSubscriberfrom busline.local.eventbus.local_eventbus import LocalEventBus
2
+
3
+ # Busline for Python
4
+
5
+ Agnostic eventbus for Python.
6
+
7
+ Official eventbus library for [Orbitalis](https://github.com/orbitalis-framework/py-orbitalis)
8
+
9
+ ## Get Start
10
+
11
+ ### Local EventBus
12
+
13
+ #### Using Publisher/Subscriber
14
+
15
+ ```python
16
+ local_eventbus_instance1 = LocalEventBus() # singleton
17
+ local_eventbus_instance2 = LocalEventBus() # singleton
18
+
19
+ subscriber = LocalEventBusSubscriber(
20
+ eventbus=local_eventbus_instance1,
21
+ fallback_event_handler=ClosureEventHandler(lambda t, e: ...)
22
+ )
23
+ publisher = LocalEventBusPublisher(eventbus=local_eventbus_instance2)
24
+
25
+ await subscriber.connect()
26
+ await publisher.connect()
27
+
28
+ await subscriber.subscribe("topic-name")
29
+
30
+ await publisher.publish("topic-name", Event()) # publish empty event
31
+
32
+ # ...subscriber receives Event() thanks to singleton LocalEventBus()
33
+
34
+ await subscriber.disconnect()
35
+ await publisher.disconnect()
36
+ ```
37
+
38
+ #### Using EventBusClient
39
+
40
+ ```python
41
+ client = LocalPubSubClientBuilder()\
42
+ .with_default_publisher()\
43
+ .with_closure_subscriber(lambda t, e: ...)\
44
+ .build()
45
+
46
+ # NOTE: both publisher and subscriber will use singleton local eventbus (default)
47
+
48
+ await client.connect()
49
+
50
+ await client.subscribe("topic-name")
51
+
52
+ await client.publish("topic-name", Event()) # publish empty event
53
+
54
+ # ...client receives Event()
55
+
56
+ await client.disconnect()
57
+ ```
58
+
59
+ #### MultiClient
60
+
61
+ ```python
62
+ local_eventbus_instance1 = AsyncLocalEventBus() # not singleton
63
+ local_eventbus_instance2 = AsyncLocalEventBus() # not singleton
64
+
65
+ client1 = LocalPubSubClientBuilder(local_eventbus_instance1)\
66
+ .with_default_publisher()\
67
+ .with_closure_subscriber(lambda t, e: ...)\
68
+ .build()
69
+
70
+ # NOTE: client1 pub/sub use `local_eventbus_instance1`
71
+
72
+ client2 = LocalPubSubClientBuilder(local_eventbus_instance2)\
73
+ .with_default_publisher()\
74
+ .with_closure_subscriber(lambda t, e: ...)\
75
+ .build()
76
+
77
+ # NOTE: client2 pub/sub use `local_eventbus_instance2`
78
+
79
+ multi_client = EventBusMultiClient([
80
+ client1,
81
+ client2
82
+ ])
83
+
84
+ await multi_client.connect()
85
+
86
+ await multi_client.subscribe("topic-name", handler=ClosureEventHandler(lambda t, e: ...))
87
+
88
+ await multi_client.publish("topic-name", Event())
89
+
90
+ # ...both clients receive Event() and handle it using `on_event_callback`
91
+
92
+ await multi_client.disconnect()
93
+ ```
94
+
95
+ #### Specifying EventBus
96
+
97
+ Local eventbuses use an internal implemented `EventBus`, this sort of architecture is not required in other scenarios such
98
+ as MQTT, because the "eventbus" is the broken.
99
+
100
+ Anyway, you may want to specify what `EventBus` instance your pub/sub components should use:
101
+
102
+ ```python
103
+ local_eventbus_instance = AsyncLocalEventBus()
104
+
105
+ subscriber = LocalEventBusClosureSubscriber(callback, eventbus_instance=local_eventbus_instance)
106
+ publisher = LocalEventBusPublisher(eventbus_instance=local_eventbus_instance2)
107
+ ```
108
+
109
+ ### EventRegistry
110
+
111
+ In order to help `event_type` management, a basic `EventRegistry` is provided to auto-build right inherited class of `Event`:
112
+
113
+ ```python
114
+ class Event1(Event):
115
+ def my_value1(self) -> int:
116
+ return self.content
117
+
118
+ class Event2(Event):
119
+ def my_value2(self) -> int:
120
+ return self.content
121
+
122
+
123
+ event_registry = EventRegistry() # singleton
124
+
125
+ event_registry.register("event1", Event1)
126
+ event_registry.register("event2", Event2)
127
+
128
+ generic_event1 = Event(content=1, event_type="event1")
129
+ generic_event2 = Event(content=2, event_type="event2")
130
+ generic_unknown_event = Event(content=2, event_type="unknown")
131
+
132
+ # first approach
133
+ event1_class = event_registry.retrive_class(generic_event1)
134
+ event1_class: Type[Event1] = event1_class
135
+ event1 = event1_class.from_event(generic_event1)
136
+
137
+ # second approach
138
+ event2: Event2 = event_registry.convert(generic_event2)
139
+ ```
140
+
141
+ ### Create Agnostic EventBus
142
+
143
+ Implement business logic of your `Publisher` and `Subscriber` and... done. Nothing more.
144
+
145
+ ```python
146
+ class YourEventBusPublisher(Publisher):
147
+
148
+ async def _internal_publish(self, topic_name: str, event: Event, **kwargs):
149
+ pass # send events to your eventbus (maybe in cloud?)
150
+ ```
151
+
152
+ ```python
153
+ class YourEventBusSubscriber(Subscriber):
154
+
155
+ async def on_event(self, topic_name: str, event: Event, **kwargs):
156
+ pass # receive your events
157
+ ```
158
+
159
+ You could create a client to allow components to use it instead of become a publisher or subscriber.
160
+
161
+ ```python
162
+ subscriber = YourEventBusSubscriber(...)
163
+ publisher = YourEventBusPublisher(...)
164
+
165
+ client = PubSubClient(publisher, subscriber)
166
+ ```
167
+
168
+
169
+ ## Subscriber
170
+
171
+ `Subscriber` is the component which receives events. It is a `EventHandler`, therefore it has `on_event` method in which
172
+ every event (and related topic) is passed.
173
+
174
+ ### TopicSubscriber
175
+
176
+ `TopicSubscriber` is an enhanced subscriber which manages an handler for each topic. We can specify a _fallback handler_,
177
+ which is run if no handler is spefied for a subscribed topic.
178
+
179
+ If the subscriber is not subscribed to a topic, fallback handler is not called.
180
+
181
+ A local implementation is already provided:
182
+
183
+ ```python
184
+ subscriber = LocalEventBusSubscriber(fallback_event_handler=...)
185
+
186
+ await subscriber.subscribe("t1")
187
+ await subscriber.subscribe("t2", handler=...)
188
+ ```
189
+
190
+
191
+
192
+
193
+
194
+
195
+
196
+
197
+
198
+
199
+
200
+
@@ -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
+