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.
- busline-0.5.0/PKG-INFO +215 -0
- busline-0.5.0/README.md +200 -0
- busline-0.5.0/busline/client/client.py +27 -0
- {busline-0.3.0/src/eventbus_client → busline-0.5.0/busline/client}/eventbus_connector.py +5 -10
- busline-0.5.0/busline/client/multiclient.py +41 -0
- busline-0.5.0/busline/client/publisher/publisher.py +60 -0
- busline-0.5.0/busline/client/pubsub_client.py +128 -0
- busline-0.5.0/busline/client/subscriber/event_handler/closure_event_handler.py +18 -0
- busline-0.5.0/busline/client/subscriber/event_handler/event_handler.py +15 -0
- busline-0.5.0/busline/client/subscriber/event_handler/multi_handler.py +30 -0
- busline-0.5.0/busline/client/subscriber/subscriber.py +109 -0
- busline-0.5.0/busline/client/subscriber/topic_subscriber.py +79 -0
- busline-0.5.0/busline/event/event.py +47 -0
- busline-0.5.0/busline/event/registry.py +67 -0
- busline-0.5.0/busline/event/test.py +47 -0
- busline-0.5.0/busline/exceptions.py +8 -0
- busline-0.5.0/busline/local/__init__.py +3 -0
- busline-0.5.0/busline/local/eventbus/__init__.py +0 -0
- busline-0.5.0/busline/local/eventbus/async_local_eventbus.py +26 -0
- busline-0.5.0/busline/local/eventbus/eventbus.py +86 -0
- busline-0.5.0/busline/local/eventbus/local_eventbus.py +23 -0
- busline-0.5.0/busline/local/local_pubsub_client.py +54 -0
- busline-0.5.0/busline/local/publisher/__init__.py +0 -0
- busline-0.5.0/busline/local/publisher/local_publisher.py +38 -0
- busline-0.5.0/busline/local/subscriber/__init__.py +0 -0
- busline-0.5.0/busline/local/subscriber/local_subscriber.py +43 -0
- busline-0.5.0/busline/local/test.py +156 -0
- busline-0.5.0/busline.egg-info/PKG-INFO +215 -0
- busline-0.5.0/busline.egg-info/SOURCES.txt +38 -0
- busline-0.5.0/busline.egg-info/top_level.txt +1 -0
- {busline-0.3.0 → busline-0.5.0}/pyproject.toml +1 -1
- busline-0.3.0/PKG-INFO +0 -104
- busline-0.3.0/README.md +0 -90
- busline-0.3.0/src/busline.egg-info/PKG-INFO +0 -104
- busline-0.3.0/src/busline.egg-info/SOURCES.txt +0 -32
- busline-0.3.0/src/busline.egg-info/top_level.txt +0 -4
- busline-0.3.0/src/event/event.py +0 -25
- busline-0.3.0/src/event/event_content.py +0 -18
- busline-0.3.0/src/event/event_metadata.py +0 -26
- busline-0.3.0/src/eventbus/async_local_eventbus.py +0 -32
- busline-0.3.0/src/eventbus/eventbus.py +0 -112
- busline-0.3.0/src/eventbus/exceptions.py +0 -2
- busline-0.3.0/src/eventbus/queued_local_eventbus.py +0 -50
- busline-0.3.0/src/eventbus/topic.py +0 -35
- busline-0.3.0/src/eventbus_client/eventbus_client.py +0 -99
- busline-0.3.0/src/eventbus_client/exceptions.py +0 -4
- busline-0.3.0/src/eventbus_client/local_eventbus_client.py +0 -25
- busline-0.3.0/src/eventbus_client/publisher/local_eventbus_publisher.py +0 -35
- busline-0.3.0/src/eventbus_client/publisher/publisher.py +0 -59
- busline-0.3.0/src/eventbus_client/subscriber/closure_event_listener.py +0 -19
- busline-0.3.0/src/eventbus_client/subscriber/event_listener.py +0 -15
- busline-0.3.0/src/eventbus_client/subscriber/local_eventbus_closure_subscriber.py +0 -17
- busline-0.3.0/src/eventbus_client/subscriber/local_eventbus_subscriber.py +0 -40
- busline-0.3.0/src/eventbus_client/subscriber/subscriber.py +0 -93
- {busline-0.3.0 → busline-0.5.0}/LICENSE +0 -0
- {busline-0.3.0/src → busline-0.5.0/busline}/__init__.py +0 -0
- {busline-0.3.0/src/event → busline-0.5.0/busline/client}/__init__.py +0 -0
- {busline-0.3.0/src/eventbus → busline-0.5.0/busline/client/publisher}/__init__.py +0 -0
- {busline-0.3.0/src/eventbus_client → busline-0.5.0/busline/client/subscriber}/__init__.py +0 -0
- {busline-0.3.0/src/eventbus_client/publisher → busline-0.5.0/busline/client/subscriber/event_handler}/__init__.py +0 -0
- {busline-0.3.0/src/eventbus_client/subscriber → busline-0.5.0/busline/event}/__init__.py +0 -0
- {busline-0.3.0/src → busline-0.5.0}/busline.egg-info/dependency_links.txt +0 -0
- {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
|
+
|
busline-0.5.0/README.md
ADDED
@@ -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
|
10
|
+
Abstract class which provides methods to interact with eventbus
|
8
11
|
|
9
12
|
Author: Nicola Ricciardi
|
10
13
|
"""
|
11
14
|
|
12
|
-
|
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
|
+
|