python-cqrs 0.1.4__tar.gz → 0.2.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.
- {python_cqrs-0.1.4/src/python_cqrs.egg-info → python_cqrs-0.2.0}/PKG-INFO +1 -1
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/pyproject.toml +1 -1
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/__init__.py +8 -5
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/adapters/kafka.py +22 -4
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/dispatcher/dispatcher.py +2 -1
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/events/event_emitter.py +2 -1
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/events/map.py +7 -4
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/message_brokers/protocol.py +1 -1
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/middlewares/base.py +2 -1
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/outbox/repository.py +7 -1
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/outbox/sqlalchemy.py +22 -5
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/requests/map.py +6 -1
- {python_cqrs-0.1.4 → python_cqrs-0.2.0/src/python_cqrs.egg-info}/PKG-INFO +1 -1
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/LICENSE +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/README.md +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/setup.cfg +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/adapters/__init__.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/adapters/amqp.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/compressors/__init__.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/compressors/protocol.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/compressors/zlib.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/container/__init__.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/container/di.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/container/protocol.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/dispatcher/__init__.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/events/__init__.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/events/bootstrap.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/events/event.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/events/event_handler.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/mediator.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/message_brokers/__init__.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/message_brokers/amqp.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/message_brokers/devnull.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/message_brokers/kafka.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/middlewares/__init__.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/middlewares/logging.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/outbox/__init__.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/outbox/producer.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/outbox/protocol.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/requests/__init__.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/requests/bootstrap.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/requests/request.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/requests/request_handler.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/cqrs/response.py +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/python_cqrs.egg-info/SOURCES.txt +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/python_cqrs.egg-info/dependency_links.txt +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/python_cqrs.egg-info/requires.txt +0 -0
- {python_cqrs-0.1.4 → python_cqrs-0.2.0}/src/python_cqrs.egg-info/top_level.txt +0 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
from cqrs.compressors.protocol import Compressor
|
|
2
2
|
from cqrs.compressors.zlib import ZlibCompressor
|
|
3
|
-
from cqrs.
|
|
3
|
+
from cqrs.container.di import DIContainer
|
|
4
|
+
from cqrs.container.protocol import Container
|
|
5
|
+
from cqrs.events.event import DomainEvent, ECSTEvent, Event, NotificationEvent
|
|
4
6
|
from cqrs.events.event_emitter import EventEmitter
|
|
5
|
-
from cqrs.events.event_handler import EventHandler
|
|
7
|
+
from cqrs.events.event_handler import EventHandler, SyncEventHandler
|
|
6
8
|
from cqrs.mediator import EventMediator, RequestMediator
|
|
7
9
|
from cqrs.outbox.producer import EventProducer
|
|
8
10
|
from cqrs.outbox.repository import OutboxedEventRepository
|
|
9
11
|
from cqrs.outbox.sqlalchemy import SqlAlchemyOutboxedEventRepository
|
|
10
12
|
from cqrs.requests.request import Request
|
|
11
|
-
from cqrs.requests.request_handler import RequestHandler
|
|
13
|
+
from cqrs.requests.request_handler import RequestHandler, SyncRequestHandler
|
|
12
14
|
from cqrs.response import Response
|
|
13
|
-
from cqrs.container.protocol import Container
|
|
14
|
-
from cqrs.container.di import DIContainer
|
|
15
15
|
|
|
16
16
|
__all__ = (
|
|
17
17
|
"RequestMediator",
|
|
@@ -19,10 +19,13 @@ __all__ = (
|
|
|
19
19
|
"DomainEvent",
|
|
20
20
|
"NotificationEvent",
|
|
21
21
|
"ECSTEvent",
|
|
22
|
+
"Event",
|
|
22
23
|
"EventEmitter",
|
|
23
24
|
"EventHandler",
|
|
25
|
+
"SyncEventHandler",
|
|
24
26
|
"Request",
|
|
25
27
|
"RequestHandler",
|
|
28
|
+
"SyncRequestHandler",
|
|
26
29
|
"Response",
|
|
27
30
|
"OutboxedEventRepository",
|
|
28
31
|
"SqlAlchemyOutboxedEventRepository",
|
|
@@ -25,8 +25,19 @@ _retry = functools.partial(
|
|
|
25
25
|
is_async=True,
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
-
SecurityProtocol: typing.TypeAlias = typing.Literal[
|
|
29
|
-
|
|
28
|
+
SecurityProtocol: typing.TypeAlias = typing.Literal[
|
|
29
|
+
"PLAINTEXT",
|
|
30
|
+
"SSL",
|
|
31
|
+
"SASL_PLAINTEXT",
|
|
32
|
+
"SASL_SSL",
|
|
33
|
+
]
|
|
34
|
+
SaslMechanism: typing.TypeAlias = typing.Literal[
|
|
35
|
+
"PLAIN",
|
|
36
|
+
"GSSAPI",
|
|
37
|
+
"SCRAM-SHA-256",
|
|
38
|
+
"SCRAM-SHA-512",
|
|
39
|
+
"OAUTHBEARER",
|
|
40
|
+
]
|
|
30
41
|
|
|
31
42
|
logger = logging.getLogger("cqrs")
|
|
32
43
|
logger.setLevel(logging.DEBUG)
|
|
@@ -74,7 +85,9 @@ class KafkaProducer(metaclass=_Singleton):
|
|
|
74
85
|
Produces event to kafka broker.
|
|
75
86
|
Tries to reconnect if connect has been lost or has not been opened.
|
|
76
87
|
"""
|
|
77
|
-
await _retry(tries=self._retry_count, delay=self._retry_delay)(self._produce)(
|
|
88
|
+
await _retry(tries=self._retry_count, delay=self._retry_delay)(self._produce)(
|
|
89
|
+
message,
|
|
90
|
+
)
|
|
78
91
|
|
|
79
92
|
|
|
80
93
|
def kafka_producer_factory(
|
|
@@ -99,4 +112,9 @@ def kafka_producer_factory(
|
|
|
99
112
|
sasl_plain_password=password,
|
|
100
113
|
loop=loop,
|
|
101
114
|
)
|
|
102
|
-
return KafkaProducer(
|
|
115
|
+
return KafkaProducer(
|
|
116
|
+
producer=producer,
|
|
117
|
+
topics=topics,
|
|
118
|
+
retry_count=retry_count,
|
|
119
|
+
retry_delay=retry_delay,
|
|
120
|
+
)
|
|
@@ -30,7 +30,8 @@ class EventEmitter:
|
|
|
30
30
|
self._message_broker = message_broker
|
|
31
31
|
|
|
32
32
|
@functools.singledispatchmethod
|
|
33
|
-
async def emit(self, event: event_model.Event) -> None:
|
|
33
|
+
async def emit(self, event: event_model.Event) -> None:
|
|
34
|
+
pass
|
|
34
35
|
|
|
35
36
|
@emit.register
|
|
36
37
|
async def _(self, event: event_model.DomainEvent) -> None:
|
|
@@ -2,17 +2,20 @@ import typing
|
|
|
2
2
|
|
|
3
3
|
from cqrs.events import event, event_handler
|
|
4
4
|
|
|
5
|
+
TEventHandler = typing.TypeVar(
|
|
6
|
+
"TEventHandler",
|
|
7
|
+
bound=typing.Type[event_handler.EventHandler | event_handler.SyncEventHandler],
|
|
8
|
+
)
|
|
9
|
+
|
|
5
10
|
_KT = typing.TypeVar("_KT", bound=typing.Type[event.Event])
|
|
6
|
-
_VT: typing.TypeAlias = typing.List[
|
|
7
|
-
typing.Type[event_handler.EventHandler[event.Event]]
|
|
8
|
-
]
|
|
11
|
+
_VT: typing.TypeAlias = typing.List[TEventHandler]
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
class EventMap(typing.Dict[_KT, _VT]):
|
|
12
15
|
def bind(
|
|
13
16
|
self,
|
|
14
17
|
event_type: _KT,
|
|
15
|
-
handler_type:
|
|
18
|
+
handler_type: TEventHandler,
|
|
16
19
|
) -> None:
|
|
17
20
|
if event_type not in self:
|
|
18
21
|
self[event_type] = [handler_type]
|
|
@@ -9,7 +9,8 @@ HandleType = typing.Callable[[_Req], typing.Awaitable[_Res] | _Res]
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class Middleware(typing.Protocol[_Req, _Res]):
|
|
12
|
-
async def __call__(self, request: _Req, handle: HandleType) -> _Res:
|
|
12
|
+
async def __call__(self, request: _Req, handle: HandleType) -> _Res:
|
|
13
|
+
raise NotImplementedError
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class MiddlewareChain:
|
|
@@ -34,7 +34,12 @@ class OutboxedEventRepository(abc.ABC, typing.Generic[Session]):
|
|
|
34
34
|
"""end transaction"""
|
|
35
35
|
|
|
36
36
|
@abc.abstractmethod
|
|
37
|
-
def add(
|
|
37
|
+
def add(
|
|
38
|
+
self,
|
|
39
|
+
session: Session,
|
|
40
|
+
event: Event,
|
|
41
|
+
topic: typing.Text | None = None,
|
|
42
|
+
) -> None:
|
|
38
43
|
"""Add an event to the repository."""
|
|
39
44
|
|
|
40
45
|
@abc.abstractmethod
|
|
@@ -46,6 +51,7 @@ class OutboxedEventRepository(abc.ABC, typing.Generic[Session]):
|
|
|
46
51
|
self,
|
|
47
52
|
session: Session,
|
|
48
53
|
batch_size: int = 100,
|
|
54
|
+
topic: typing.Text | None = None,
|
|
49
55
|
) -> typing.List[Event]:
|
|
50
56
|
"""Get many events from the repository."""
|
|
51
57
|
|
|
@@ -78,7 +78,12 @@ class OutboxModel(Base):
|
|
|
78
78
|
nullable=False,
|
|
79
79
|
comment="Event name",
|
|
80
80
|
)
|
|
81
|
-
|
|
81
|
+
topic = sqlalchemy.Column(
|
|
82
|
+
sqlalchemy.String(255),
|
|
83
|
+
nullable=False,
|
|
84
|
+
comment="Event topic",
|
|
85
|
+
default="",
|
|
86
|
+
)
|
|
82
87
|
created_at = sqlalchemy.Column(
|
|
83
88
|
sqlalchemy.DateTime,
|
|
84
89
|
nullable=False,
|
|
@@ -98,7 +103,11 @@ class OutboxModel(Base):
|
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
@classmethod
|
|
101
|
-
def get_batch_query(
|
|
106
|
+
def get_batch_query(
|
|
107
|
+
cls,
|
|
108
|
+
size: int,
|
|
109
|
+
topic: typing.Text | None = None,
|
|
110
|
+
) -> sqlalchemy.Select:
|
|
102
111
|
return (
|
|
103
112
|
sqlalchemy.select(cls)
|
|
104
113
|
.select_from(cls)
|
|
@@ -111,6 +120,7 @@ class OutboxModel(Base):
|
|
|
111
120
|
],
|
|
112
121
|
),
|
|
113
122
|
cls.flush_counter < MAX_FLUSH_COUNTER_VALUE,
|
|
123
|
+
(cls.topic == topic) if topic is not None else sqlalchemy.true(),
|
|
114
124
|
),
|
|
115
125
|
)
|
|
116
126
|
.order_by(cls.status_sorting_case().asc())
|
|
@@ -184,7 +194,12 @@ class SqlAlchemyOutboxedEventRepository(
|
|
|
184
194
|
EventType.ECST_EVENT: ev.ECSTEvent,
|
|
185
195
|
}
|
|
186
196
|
|
|
187
|
-
def add(
|
|
197
|
+
def add(
|
|
198
|
+
self,
|
|
199
|
+
session: sql_session.AsyncSession,
|
|
200
|
+
event,
|
|
201
|
+
topic: typing.Text | None = None,
|
|
202
|
+
) -> None:
|
|
188
203
|
bytes_payload = orjson.dumps(event.payload)
|
|
189
204
|
if self._compressor:
|
|
190
205
|
bytes_payload = self._compressor.compress(bytes_payload)
|
|
@@ -196,6 +211,7 @@ class SqlAlchemyOutboxedEventRepository(
|
|
|
196
211
|
event_name=event.event_name,
|
|
197
212
|
created_at=event.event_timestamp,
|
|
198
213
|
payload=bytes_payload,
|
|
214
|
+
topic=topic or "",
|
|
199
215
|
),
|
|
200
216
|
)
|
|
201
217
|
|
|
@@ -215,9 +231,10 @@ class SqlAlchemyOutboxedEventRepository(
|
|
|
215
231
|
self,
|
|
216
232
|
session: sql_session.AsyncSession,
|
|
217
233
|
batch_size: int = 100,
|
|
234
|
+
topic: typing.Text | None = None,
|
|
218
235
|
) -> typing.List[repository.Event]:
|
|
219
236
|
events: typing.Sequence[OutboxModel] = (
|
|
220
|
-
(await session.execute(OutboxModel.get_batch_query(batch_size)))
|
|
237
|
+
(await session.execute(OutboxModel.get_batch_query(batch_size, topic)))
|
|
221
238
|
.scalars()
|
|
222
239
|
.all()
|
|
223
240
|
)
|
|
@@ -234,7 +251,7 @@ class SqlAlchemyOutboxedEventRepository(
|
|
|
234
251
|
async def get_one(
|
|
235
252
|
self,
|
|
236
253
|
session: sql_session.AsyncSession,
|
|
237
|
-
event_id
|
|
254
|
+
event_id,
|
|
238
255
|
) -> repository.Event | None:
|
|
239
256
|
event: OutboxModel | None = (
|
|
240
257
|
await session.execute(OutboxModel.get_event_query(event_id))
|
|
@@ -3,7 +3,12 @@ import typing
|
|
|
3
3
|
from cqrs.requests import request, request_handler
|
|
4
4
|
|
|
5
5
|
_KT = typing.TypeVar("_KT", bound=typing.Type[request.Request])
|
|
6
|
-
_VT = typing.TypeVar(
|
|
6
|
+
_VT = typing.TypeVar(
|
|
7
|
+
"_VT",
|
|
8
|
+
bound=typing.Type[
|
|
9
|
+
request_handler.RequestHandler | request_handler.SyncRequestHandler
|
|
10
|
+
],
|
|
11
|
+
)
|
|
7
12
|
|
|
8
13
|
|
|
9
14
|
class RequestMap(typing.Dict[_KT, _VT]):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|