python-cqrs 0.0.18__tar.gz → 0.1.2__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.0.18/src/python_cqrs.egg-info → python_cqrs-0.1.2}/PKG-INFO +29 -5
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/README.md +27 -2
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/pyproject.toml +1 -3
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/dispatcher/dispatcher.py +29 -9
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/events/event_emitter.py +21 -10
- python_cqrs-0.1.2/src/cqrs/events/event_handler.py +46 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/mediator.py +7 -7
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/middlewares/base.py +7 -7
- python_cqrs-0.1.2/src/cqrs/requests/request_handler.py +86 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2/src/python_cqrs.egg-info}/PKG-INFO +29 -5
- python_cqrs-0.0.18/src/cqrs/events/event_handler.py +0 -24
- python_cqrs-0.0.18/src/cqrs/requests/request_handler.py +0 -45
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/LICENSE +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/setup.cfg +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/__init__.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/adapters/__init__.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/adapters/amqp.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/adapters/kafka.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/compressors/__init__.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/compressors/protocol.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/compressors/zlib.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/container/__init__.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/container/di.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/container/protocol.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/dispatcher/__init__.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/events/__init__.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/events/bootstrap.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/events/event.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/events/map.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/message_brokers/__init__.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/message_brokers/amqp.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/message_brokers/devnull.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/message_brokers/kafka.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/message_brokers/protocol.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/middlewares/__init__.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/middlewares/logging.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/outbox/__init__.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/outbox/producer.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/outbox/protocol.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/outbox/repository.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/outbox/sqlalchemy.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/requests/__init__.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/requests/bootstrap.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/requests/map.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/requests/request.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/response.py +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/python_cqrs.egg-info/SOURCES.txt +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/python_cqrs.egg-info/dependency_links.txt +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/python_cqrs.egg-info/requires.txt +0 -0
- {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/python_cqrs.egg-info/top_level.txt +0 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-cqrs
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Python CQRS pattern implementation
|
|
5
|
-
Author:
|
|
6
|
-
Author-email: Dmitriy Kutlubaev <kutlubaev00@mail.ru>, Vadim Kozyrevskiy <vadikko2@mail.ru>
|
|
5
|
+
Author-email: Vadim Kozyrevskiy <vadikko2@mail.ru>
|
|
7
6
|
Maintainer-email: Vadim Kozyrevskiy <vadikko2@mail.ru>
|
|
8
7
|
Project-URL: Issues, https://github.com/vadikko2/python-cqrs/issues
|
|
9
8
|
Project-URL: Repository, https://github.com/vadikko2/python-cqrs
|
|
@@ -65,7 +64,7 @@ As a result of executing the command, an event may be produced to the broker.
|
|
|
65
64
|
> By default, the command handler does not return any result, but it is not mandatory.
|
|
66
65
|
|
|
67
66
|
```python
|
|
68
|
-
from cqrs.requests.request_handler import RequestHandler
|
|
67
|
+
from cqrs.requests.request_handler import RequestHandler, SyncRequestHandler
|
|
69
68
|
from cqrs.events.event import Event
|
|
70
69
|
|
|
71
70
|
class JoinMeetingCommandHandler(RequestHandler[JoinMeetingCommand, None]):
|
|
@@ -80,6 +79,21 @@ class JoinMeetingCommandHandler(RequestHandler[JoinMeetingCommand, None]):
|
|
|
80
79
|
|
|
81
80
|
async def handle(self, request: JoinMeetingCommand) -> None:
|
|
82
81
|
await self._meetings_api.join_user(request.user_id, request.meeting_id)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class SyncJoinMeetingCommandHandler(SyncRequestHandler[JoinMeetingCommand, None]):
|
|
85
|
+
|
|
86
|
+
def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
|
|
87
|
+
self._meetings_api = meetings_api
|
|
88
|
+
self.events: list[Event] = []
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def events(self) -> typing.List[events.Event]:
|
|
92
|
+
return self._events
|
|
93
|
+
|
|
94
|
+
def handle(self, request: JoinMeetingCommand) -> None:
|
|
95
|
+
# do some sync logic
|
|
96
|
+
...
|
|
83
97
|
```
|
|
84
98
|
|
|
85
99
|
### Query handler
|
|
@@ -105,6 +119,7 @@ class ReadMeetingQueryHandler(RequestHandler[ReadMeetingQuery, ReadMeetingQueryR
|
|
|
105
119
|
async def handle(self, request: ReadMeetingQuery) -> ReadMeetingQueryResult:
|
|
106
120
|
link = await self._meetings_api.get_link(request.meeting_id)
|
|
107
121
|
return ReadMeetingQueryResult(link=link, meeting_id=request.meeting_id)
|
|
122
|
+
|
|
108
123
|
```
|
|
109
124
|
|
|
110
125
|
|
|
@@ -115,7 +130,7 @@ To configure event handling, you need to implement a broker consumer on the side
|
|
|
115
130
|
Below is an example of `Kafka event consuming` that can be used in the Presentation Layer.
|
|
116
131
|
|
|
117
132
|
```python
|
|
118
|
-
from cqrs.events import EventHandler
|
|
133
|
+
from cqrs.events import EventHandler, SyncEventHandler
|
|
119
134
|
|
|
120
135
|
class UserJoinedEventHandler(EventHandler[UserJoinedEventHandler]):
|
|
121
136
|
|
|
@@ -124,6 +139,15 @@ class UserJoinedEventHandler(EventHandler[UserJoinedEventHandler]):
|
|
|
124
139
|
|
|
125
140
|
async def handle(self, event: UserJoinedEventHandler) -> None:
|
|
126
141
|
await self._meetings_api.notify_room(event.meeting_id, "New user joined!")
|
|
142
|
+
|
|
143
|
+
class SyncUserJoinedEventHandler(SyncEventHandler[UserJoinedEventHandler]):
|
|
144
|
+
|
|
145
|
+
def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
|
|
146
|
+
self._meetings_api = meetings_api
|
|
147
|
+
|
|
148
|
+
def handle(self, event: UserJoinedEventHandler) -> None:
|
|
149
|
+
# do some sync logic
|
|
150
|
+
...
|
|
127
151
|
```
|
|
128
152
|
|
|
129
153
|
## Producing Notification/ECST Events
|
|
@@ -27,7 +27,7 @@ As a result of executing the command, an event may be produced to the broker.
|
|
|
27
27
|
> By default, the command handler does not return any result, but it is not mandatory.
|
|
28
28
|
|
|
29
29
|
```python
|
|
30
|
-
from cqrs.requests.request_handler import RequestHandler
|
|
30
|
+
from cqrs.requests.request_handler import RequestHandler, SyncRequestHandler
|
|
31
31
|
from cqrs.events.event import Event
|
|
32
32
|
|
|
33
33
|
class JoinMeetingCommandHandler(RequestHandler[JoinMeetingCommand, None]):
|
|
@@ -42,6 +42,21 @@ class JoinMeetingCommandHandler(RequestHandler[JoinMeetingCommand, None]):
|
|
|
42
42
|
|
|
43
43
|
async def handle(self, request: JoinMeetingCommand) -> None:
|
|
44
44
|
await self._meetings_api.join_user(request.user_id, request.meeting_id)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SyncJoinMeetingCommandHandler(SyncRequestHandler[JoinMeetingCommand, None]):
|
|
48
|
+
|
|
49
|
+
def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
|
|
50
|
+
self._meetings_api = meetings_api
|
|
51
|
+
self.events: list[Event] = []
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def events(self) -> typing.List[events.Event]:
|
|
55
|
+
return self._events
|
|
56
|
+
|
|
57
|
+
def handle(self, request: JoinMeetingCommand) -> None:
|
|
58
|
+
# do some sync logic
|
|
59
|
+
...
|
|
45
60
|
```
|
|
46
61
|
|
|
47
62
|
### Query handler
|
|
@@ -67,6 +82,7 @@ class ReadMeetingQueryHandler(RequestHandler[ReadMeetingQuery, ReadMeetingQueryR
|
|
|
67
82
|
async def handle(self, request: ReadMeetingQuery) -> ReadMeetingQueryResult:
|
|
68
83
|
link = await self._meetings_api.get_link(request.meeting_id)
|
|
69
84
|
return ReadMeetingQueryResult(link=link, meeting_id=request.meeting_id)
|
|
85
|
+
|
|
70
86
|
```
|
|
71
87
|
|
|
72
88
|
|
|
@@ -77,7 +93,7 @@ To configure event handling, you need to implement a broker consumer on the side
|
|
|
77
93
|
Below is an example of `Kafka event consuming` that can be used in the Presentation Layer.
|
|
78
94
|
|
|
79
95
|
```python
|
|
80
|
-
from cqrs.events import EventHandler
|
|
96
|
+
from cqrs.events import EventHandler, SyncEventHandler
|
|
81
97
|
|
|
82
98
|
class UserJoinedEventHandler(EventHandler[UserJoinedEventHandler]):
|
|
83
99
|
|
|
@@ -86,6 +102,15 @@ class UserJoinedEventHandler(EventHandler[UserJoinedEventHandler]):
|
|
|
86
102
|
|
|
87
103
|
async def handle(self, event: UserJoinedEventHandler) -> None:
|
|
88
104
|
await self._meetings_api.notify_room(event.meeting_id, "New user joined!")
|
|
105
|
+
|
|
106
|
+
class SyncUserJoinedEventHandler(SyncEventHandler[UserJoinedEventHandler]):
|
|
107
|
+
|
|
108
|
+
def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
|
|
109
|
+
self._meetings_api = meetings_api
|
|
110
|
+
|
|
111
|
+
def handle(self, event: UserJoinedEventHandler) -> None:
|
|
112
|
+
# do some sync logic
|
|
113
|
+
...
|
|
89
114
|
```
|
|
90
115
|
|
|
91
116
|
## Producing Notification/ECST Events
|
|
@@ -4,8 +4,6 @@ requires = ["setuptools>=42", "wheel"]
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
authors = [
|
|
7
|
-
{name = "Dmitriy Kutlubaev", email = "kutlubaev00@mail.ru"},
|
|
8
|
-
{name = "Nikita Kunov"},
|
|
9
7
|
{name = "Vadim Kozyrevskiy", email = "vadikko2@mail.ru"}
|
|
10
8
|
]
|
|
11
9
|
classifiers = [
|
|
@@ -29,7 +27,7 @@ maintainers = [{name = "Vadim Kozyrevskiy", email = "vadikko2@mail.ru"}]
|
|
|
29
27
|
name = "python-cqrs"
|
|
30
28
|
readme = "README.md"
|
|
31
29
|
requires-python = ">=3.10"
|
|
32
|
-
version = "0.
|
|
30
|
+
version = "0.1.2"
|
|
33
31
|
|
|
34
32
|
[project.optional-dependencies]
|
|
35
33
|
dev = [
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import functools
|
|
1
3
|
import logging
|
|
2
4
|
import typing
|
|
3
5
|
|
|
@@ -8,19 +10,28 @@ from cqrs import (
|
|
|
8
10
|
events as cqrs_events,
|
|
9
11
|
middlewares,
|
|
10
12
|
requests,
|
|
11
|
-
response as
|
|
13
|
+
response as resp,
|
|
12
14
|
)
|
|
15
|
+
from cqrs.events import event_handler
|
|
16
|
+
from cqrs.requests import request_handler
|
|
13
17
|
|
|
14
18
|
logger = logging.getLogger("cqrs")
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
_Resp = typing.TypeVar("_Resp", resp.Response, None, contravariant=True)
|
|
21
|
+
|
|
22
|
+
_RequestHandler: typing.TypeAlias = (
|
|
23
|
+
request_handler.RequestHandler | request_handler.SyncRequestHandler
|
|
24
|
+
)
|
|
25
|
+
_EventHandler: typing.TypeAlias = (
|
|
26
|
+
event_handler.EventHandler | event_handler.SyncEventHandler
|
|
27
|
+
)
|
|
17
28
|
|
|
18
29
|
|
|
19
30
|
class RequestHandlerDoesNotExist(Exception): ...
|
|
20
31
|
|
|
21
32
|
|
|
22
|
-
class RequestDispatchResult(pydantic.BaseModel, typing.Generic[
|
|
23
|
-
response:
|
|
33
|
+
class RequestDispatchResult(pydantic.BaseModel, typing.Generic[_Resp]):
|
|
34
|
+
response: _Resp = pydantic.Field(default=None)
|
|
24
35
|
events: typing.List[cqrs_events.Event] = pydantic.Field(default_factory=list)
|
|
25
36
|
|
|
26
37
|
|
|
@@ -41,9 +52,15 @@ class RequestDispatcher:
|
|
|
41
52
|
raise RequestHandlerDoesNotExist(
|
|
42
53
|
f"RequestHandler not found matching Request type {type(request)}",
|
|
43
54
|
)
|
|
44
|
-
handler = await self._container.resolve(handler_type)
|
|
45
|
-
|
|
55
|
+
handler: _RequestHandler = await self._container.resolve(handler_type)
|
|
56
|
+
if asyncio.iscoroutinefunction(handler.handle):
|
|
57
|
+
wrapped_handle = self._middleware_chain.wrap(handler.handle)
|
|
58
|
+
else:
|
|
59
|
+
wrapped_handle = self._middleware_chain.wrap(
|
|
60
|
+
functools.partial(asyncio.to_thread, handler.handle),
|
|
61
|
+
)
|
|
46
62
|
response = await wrapped_handle(request)
|
|
63
|
+
|
|
47
64
|
return RequestDispatchResult(response=response, events=handler.events)
|
|
48
65
|
|
|
49
66
|
|
|
@@ -64,10 +81,13 @@ class EventDispatcher:
|
|
|
64
81
|
async def _handle_event(
|
|
65
82
|
self,
|
|
66
83
|
event: cqrs_events.Event,
|
|
67
|
-
handle_type: typing.Type[cqrs_events.EventHandler
|
|
84
|
+
handle_type: typing.Type[cqrs_events.EventHandler],
|
|
68
85
|
):
|
|
69
|
-
handler = await self._container.resolve(handle_type)
|
|
70
|
-
|
|
86
|
+
handler: _EventHandler = await self._container.resolve(handle_type)
|
|
87
|
+
if asyncio.iscoroutinefunction(handler.handle):
|
|
88
|
+
await handler.handle(event)
|
|
89
|
+
else:
|
|
90
|
+
await asyncio.to_thread(handler.handle, event)
|
|
71
91
|
|
|
72
92
|
async def dispatch(self, event: cqrs_events.Event) -> None:
|
|
73
93
|
handler_types = self._event_map.get(type(event), [])
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import functools
|
|
2
3
|
import logging
|
|
4
|
+
import typing
|
|
3
5
|
|
|
4
|
-
from cqrs import container, message_brokers
|
|
5
|
-
from cqrs.events import event, map
|
|
6
|
+
from cqrs import container as di_container, message_brokers
|
|
7
|
+
from cqrs.events import event as event_model, event_handler, map
|
|
6
8
|
|
|
7
9
|
logger = logging.getLogger("cqrs")
|
|
8
10
|
|
|
11
|
+
_EventHandler: typing.TypeAlias = (
|
|
12
|
+
event_handler.EventHandler | event_handler.SyncEventHandler
|
|
13
|
+
)
|
|
14
|
+
|
|
9
15
|
|
|
10
16
|
class EventEmitter:
|
|
11
17
|
"""
|
|
@@ -16,7 +22,7 @@ class EventEmitter:
|
|
|
16
22
|
def __init__(
|
|
17
23
|
self,
|
|
18
24
|
event_map: map.EventMap,
|
|
19
|
-
container:
|
|
25
|
+
container: di_container.Container,
|
|
20
26
|
message_broker: message_brokers.MessageBroker | None = None,
|
|
21
27
|
) -> None:
|
|
22
28
|
self._event_map = event_map
|
|
@@ -24,10 +30,10 @@ class EventEmitter:
|
|
|
24
30
|
self._message_broker = message_broker
|
|
25
31
|
|
|
26
32
|
@functools.singledispatchmethod
|
|
27
|
-
async def emit(self, event:
|
|
33
|
+
async def emit(self, event: event_model.Event) -> None: ...
|
|
28
34
|
|
|
29
35
|
@emit.register
|
|
30
|
-
async def _(self, event:
|
|
36
|
+
async def _(self, event: event_model.DomainEvent) -> None:
|
|
31
37
|
handlers_types = self._event_map.get(type(event), [])
|
|
32
38
|
if not handlers_types:
|
|
33
39
|
logger.warning(
|
|
@@ -35,16 +41,21 @@ class EventEmitter:
|
|
|
35
41
|
type(event).__name__,
|
|
36
42
|
)
|
|
37
43
|
for handler_type in handlers_types:
|
|
38
|
-
handler = await self._container.resolve(
|
|
44
|
+
handler: _EventHandler = await self._container.resolve(
|
|
45
|
+
handler_type,
|
|
46
|
+
)
|
|
39
47
|
logger.debug(
|
|
40
48
|
"Handling Event(%s) via event handler(%s)",
|
|
41
49
|
type(event).__name__,
|
|
42
50
|
handler_type.__name__,
|
|
43
51
|
)
|
|
44
|
-
|
|
52
|
+
if asyncio.iscoroutinefunction(handler.handle):
|
|
53
|
+
await handler.handle(event)
|
|
54
|
+
else:
|
|
55
|
+
await asyncio.to_thread(handler.handle, event)
|
|
45
56
|
|
|
46
57
|
@emit.register
|
|
47
|
-
async def _(self, event:
|
|
58
|
+
async def _(self, event: event_model.NotificationEvent) -> None:
|
|
48
59
|
if not self._message_broker:
|
|
49
60
|
raise RuntimeError(
|
|
50
61
|
"To use NotificationEvent, message_broker argument must be specified.",
|
|
@@ -61,7 +72,7 @@ class EventEmitter:
|
|
|
61
72
|
await self._message_broker.send_message(message)
|
|
62
73
|
|
|
63
74
|
@emit.register
|
|
64
|
-
async def _(self, event:
|
|
75
|
+
async def _(self, event: event_model.ECSTEvent) -> None:
|
|
65
76
|
if not self._message_broker:
|
|
66
77
|
raise RuntimeError(
|
|
67
78
|
"To use ECSTEvent, message_broker argument must be specified.",
|
|
@@ -79,7 +90,7 @@ class EventEmitter:
|
|
|
79
90
|
|
|
80
91
|
|
|
81
92
|
def _build_message(
|
|
82
|
-
event:
|
|
93
|
+
event: event_model.NotificationEvent | event_model.ECSTEvent,
|
|
83
94
|
) -> message_brokers.Message:
|
|
84
95
|
payload = event.model_dump(mode="json")
|
|
85
96
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from cqrs.events import event as event_models
|
|
5
|
+
|
|
6
|
+
E = typing.TypeVar("E", bound=event_models.Event, contravariant=True)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EventHandler(abc.ABC, typing.Generic[E]):
|
|
10
|
+
"""
|
|
11
|
+
The event handler interface.
|
|
12
|
+
|
|
13
|
+
Usage::
|
|
14
|
+
|
|
15
|
+
class UserJoinedEventHandler(EventHandler[UserJoinedEventHandler])
|
|
16
|
+
def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
|
|
17
|
+
self._meetings_api = meetings_api
|
|
18
|
+
|
|
19
|
+
async def handle(self, event: UserJoinedEventHandler) -> None:
|
|
20
|
+
await self._meetings_api.notify_room(event.meeting_id, "New user joined!")
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@abc.abstractmethod
|
|
25
|
+
async def handle(self, event: E) -> None:
|
|
26
|
+
raise NotImplementedError
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SyncEventHandler(abc.ABC, typing.Generic[E]):
|
|
30
|
+
"""
|
|
31
|
+
The event handler interface.
|
|
32
|
+
|
|
33
|
+
Usage::
|
|
34
|
+
|
|
35
|
+
class UserJoinedEventHandler(SyncEventHandler[UserJoinedEventHandler])
|
|
36
|
+
def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
|
|
37
|
+
self._meetings_api = meetings_api
|
|
38
|
+
|
|
39
|
+
def handle(self, event: UserJoinedEventHandler) -> None:
|
|
40
|
+
self._meetings_api.notify_room(event.meeting_id, "New user joined!")
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
@abc.abstractmethod
|
|
45
|
+
def handle(self, event: E) -> None:
|
|
46
|
+
raise NotImplementedError
|
|
@@ -3,13 +3,13 @@ import typing
|
|
|
3
3
|
from cqrs import (
|
|
4
4
|
container as di_container,
|
|
5
5
|
dispatcher,
|
|
6
|
-
events,
|
|
6
|
+
events as ev,
|
|
7
7
|
middlewares,
|
|
8
8
|
requests,
|
|
9
9
|
response,
|
|
10
10
|
)
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
_Resp = typing.TypeVar("_Resp", response.Response, None, contravariant=True)
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class RequestMediator:
|
|
@@ -44,7 +44,7 @@ class RequestMediator:
|
|
|
44
44
|
self,
|
|
45
45
|
request_map: requests.RequestMap,
|
|
46
46
|
container: di_container.Container,
|
|
47
|
-
event_emitter:
|
|
47
|
+
event_emitter: ev.EventEmitter | None = None,
|
|
48
48
|
middleware_chain: middlewares.MiddlewareChain | None = None,
|
|
49
49
|
*,
|
|
50
50
|
dispatcher_type: typing.Type[
|
|
@@ -58,7 +58,7 @@ class RequestMediator:
|
|
|
58
58
|
middleware_chain=middleware_chain, # type: ignore
|
|
59
59
|
)
|
|
60
60
|
|
|
61
|
-
async def send(self, request: requests.Request) ->
|
|
61
|
+
async def send(self, request: requests.Request) -> _Resp:
|
|
62
62
|
dispatch_result = await self._dispatcher.dispatch(request)
|
|
63
63
|
|
|
64
64
|
if dispatch_result.events:
|
|
@@ -66,7 +66,7 @@ class RequestMediator:
|
|
|
66
66
|
|
|
67
67
|
return dispatch_result.response
|
|
68
68
|
|
|
69
|
-
async def _send_events(self, events: typing.List[
|
|
69
|
+
async def _send_events(self, events: typing.List[ev.Event]) -> None:
|
|
70
70
|
if not self._event_emitter:
|
|
71
71
|
return
|
|
72
72
|
|
|
@@ -93,7 +93,7 @@ class EventMediator:
|
|
|
93
93
|
|
|
94
94
|
def __init__(
|
|
95
95
|
self,
|
|
96
|
-
event_map:
|
|
96
|
+
event_map: ev.EventMap,
|
|
97
97
|
container: di_container.Container,
|
|
98
98
|
middleware_chain: middlewares.MiddlewareChain | None = None,
|
|
99
99
|
*,
|
|
@@ -107,5 +107,5 @@ class EventMediator:
|
|
|
107
107
|
middleware_chain=middleware_chain, # type: ignore
|
|
108
108
|
)
|
|
109
109
|
|
|
110
|
-
async def send(self, event:
|
|
110
|
+
async def send(self, event: ev.Event) -> None:
|
|
111
111
|
await self._dispatcher.dispatch(event)
|
|
@@ -3,20 +3,20 @@ import typing
|
|
|
3
3
|
|
|
4
4
|
from cqrs import requests, response
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
HandleType = typing.Callable[[
|
|
6
|
+
_Req = typing.TypeVar("_Req", bound=requests.Request, contravariant=True)
|
|
7
|
+
_Res = typing.TypeVar("_Res", response.Response, None, covariant=True)
|
|
8
|
+
HandleType = typing.Callable[[_Req], typing.Awaitable[_Res] | _Res]
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class Middleware(typing.Protocol):
|
|
12
|
-
async def __call__(self, request:
|
|
11
|
+
class Middleware(typing.Protocol[_Req, _Res]):
|
|
12
|
+
async def __call__(self, request: _Req, handle: HandleType) -> _Res: ...
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class MiddlewareChain:
|
|
16
16
|
def __init__(self) -> None:
|
|
17
|
-
self._chain:
|
|
17
|
+
self._chain: typing.List[Middleware] = []
|
|
18
18
|
|
|
19
|
-
def set(self, chain:
|
|
19
|
+
def set(self, chain: typing.List[Middleware]) -> None:
|
|
20
20
|
self._chain = chain
|
|
21
21
|
|
|
22
22
|
def add(self, middleware: Middleware) -> None:
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from cqrs import response
|
|
5
|
+
from cqrs.events import event
|
|
6
|
+
from cqrs.requests import request as r
|
|
7
|
+
|
|
8
|
+
_Req = typing.TypeVar("_Req", bound=r.Request, contravariant=True)
|
|
9
|
+
_Resp = typing.TypeVar("_Resp", response.Response, None, covariant=True)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RequestHandler(abc.ABC, typing.Generic[_Req, _Resp]):
|
|
13
|
+
"""
|
|
14
|
+
The request handler interface.
|
|
15
|
+
|
|
16
|
+
The request handler is an object, which gets a request as input and may return a response as a result.
|
|
17
|
+
|
|
18
|
+
Command handler example::
|
|
19
|
+
|
|
20
|
+
class JoinMeetingCommandHandler(RequestHandler[JoinMeetingCommand, None])
|
|
21
|
+
def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
|
|
22
|
+
self._meetings_api = meetings_api
|
|
23
|
+
self.events: list[Event] = []
|
|
24
|
+
|
|
25
|
+
async def handle(self, request: JoinMeetingCommand) -> None:
|
|
26
|
+
await self._meetings_api.join_user(request.user_id, request.meeting_id)
|
|
27
|
+
|
|
28
|
+
Query handler example::
|
|
29
|
+
|
|
30
|
+
class ReadMeetingQueryHandler(RequestHandler[ReadMeetingQuery, ReadMeetingQueryResult])
|
|
31
|
+
def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
|
|
32
|
+
self._meetings_api = meetings_api
|
|
33
|
+
self.events: list[Event] = []
|
|
34
|
+
|
|
35
|
+
async def handle(self, request: ReadMeetingQuery) -> ReadMeetingQueryResult:
|
|
36
|
+
link = await self._meetings_api.get_link(request.meeting_id)
|
|
37
|
+
return ReadMeetingQueryResult(link=link, meeting_id=request.meeting_id)
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
@abc.abstractmethod
|
|
43
|
+
def events(self) -> list[event.Event]:
|
|
44
|
+
raise NotImplementedError
|
|
45
|
+
|
|
46
|
+
@abc.abstractmethod
|
|
47
|
+
async def handle(self, request: _Req) -> _Resp:
|
|
48
|
+
raise NotImplementedError
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class SyncRequestHandler(abc.ABC, typing.Generic[_Req, _Resp]):
|
|
52
|
+
"""
|
|
53
|
+
The synchronous request handler interface.
|
|
54
|
+
|
|
55
|
+
The request handler is an object, which gets a request as input and may return a response as a result.
|
|
56
|
+
|
|
57
|
+
Command handler example::
|
|
58
|
+
|
|
59
|
+
class JoinMeetingCommandHandler(SyncRequestHandler[JoinMeetingCommand, None])
|
|
60
|
+
def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
|
|
61
|
+
self._meetings_api = meetings_api
|
|
62
|
+
self.events: list[Event] = []
|
|
63
|
+
|
|
64
|
+
def handle(self, request: JoinMeetingCommand) -> None:
|
|
65
|
+
self._meetings_api.join_user(request.user_id, request.meeting_id)
|
|
66
|
+
|
|
67
|
+
Query handler example::
|
|
68
|
+
|
|
69
|
+
class ReadMeetingQueryHandler(SyncRequestHandler[ReadMeetingQuery, ReadMeetingQueryResult])
|
|
70
|
+
def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
|
|
71
|
+
self._meetings_api = meetings_api
|
|
72
|
+
self.events: list[Event] = []
|
|
73
|
+
|
|
74
|
+
def handle(self, request: ReadMeetingQuery) -> ReadMeetingQueryResult:
|
|
75
|
+
link = self._meetings_api.get_link(request.meeting_id)
|
|
76
|
+
return ReadMeetingQueryResult(link=link, meeting_id=request.meeting_id)
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
@abc.abstractmethod
|
|
81
|
+
def events(self) -> list[event.Event]:
|
|
82
|
+
raise NotImplementedError
|
|
83
|
+
|
|
84
|
+
@abc.abstractmethod
|
|
85
|
+
def handle(self, request: _Req) -> _Resp:
|
|
86
|
+
raise NotImplementedError
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-cqrs
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Python CQRS pattern implementation
|
|
5
|
-
Author:
|
|
6
|
-
Author-email: Dmitriy Kutlubaev <kutlubaev00@mail.ru>, Vadim Kozyrevskiy <vadikko2@mail.ru>
|
|
5
|
+
Author-email: Vadim Kozyrevskiy <vadikko2@mail.ru>
|
|
7
6
|
Maintainer-email: Vadim Kozyrevskiy <vadikko2@mail.ru>
|
|
8
7
|
Project-URL: Issues, https://github.com/vadikko2/python-cqrs/issues
|
|
9
8
|
Project-URL: Repository, https://github.com/vadikko2/python-cqrs
|
|
@@ -65,7 +64,7 @@ As a result of executing the command, an event may be produced to the broker.
|
|
|
65
64
|
> By default, the command handler does not return any result, but it is not mandatory.
|
|
66
65
|
|
|
67
66
|
```python
|
|
68
|
-
from cqrs.requests.request_handler import RequestHandler
|
|
67
|
+
from cqrs.requests.request_handler import RequestHandler, SyncRequestHandler
|
|
69
68
|
from cqrs.events.event import Event
|
|
70
69
|
|
|
71
70
|
class JoinMeetingCommandHandler(RequestHandler[JoinMeetingCommand, None]):
|
|
@@ -80,6 +79,21 @@ class JoinMeetingCommandHandler(RequestHandler[JoinMeetingCommand, None]):
|
|
|
80
79
|
|
|
81
80
|
async def handle(self, request: JoinMeetingCommand) -> None:
|
|
82
81
|
await self._meetings_api.join_user(request.user_id, request.meeting_id)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class SyncJoinMeetingCommandHandler(SyncRequestHandler[JoinMeetingCommand, None]):
|
|
85
|
+
|
|
86
|
+
def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
|
|
87
|
+
self._meetings_api = meetings_api
|
|
88
|
+
self.events: list[Event] = []
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def events(self) -> typing.List[events.Event]:
|
|
92
|
+
return self._events
|
|
93
|
+
|
|
94
|
+
def handle(self, request: JoinMeetingCommand) -> None:
|
|
95
|
+
# do some sync logic
|
|
96
|
+
...
|
|
83
97
|
```
|
|
84
98
|
|
|
85
99
|
### Query handler
|
|
@@ -105,6 +119,7 @@ class ReadMeetingQueryHandler(RequestHandler[ReadMeetingQuery, ReadMeetingQueryR
|
|
|
105
119
|
async def handle(self, request: ReadMeetingQuery) -> ReadMeetingQueryResult:
|
|
106
120
|
link = await self._meetings_api.get_link(request.meeting_id)
|
|
107
121
|
return ReadMeetingQueryResult(link=link, meeting_id=request.meeting_id)
|
|
122
|
+
|
|
108
123
|
```
|
|
109
124
|
|
|
110
125
|
|
|
@@ -115,7 +130,7 @@ To configure event handling, you need to implement a broker consumer on the side
|
|
|
115
130
|
Below is an example of `Kafka event consuming` that can be used in the Presentation Layer.
|
|
116
131
|
|
|
117
132
|
```python
|
|
118
|
-
from cqrs.events import EventHandler
|
|
133
|
+
from cqrs.events import EventHandler, SyncEventHandler
|
|
119
134
|
|
|
120
135
|
class UserJoinedEventHandler(EventHandler[UserJoinedEventHandler]):
|
|
121
136
|
|
|
@@ -124,6 +139,15 @@ class UserJoinedEventHandler(EventHandler[UserJoinedEventHandler]):
|
|
|
124
139
|
|
|
125
140
|
async def handle(self, event: UserJoinedEventHandler) -> None:
|
|
126
141
|
await self._meetings_api.notify_room(event.meeting_id, "New user joined!")
|
|
142
|
+
|
|
143
|
+
class SyncUserJoinedEventHandler(SyncEventHandler[UserJoinedEventHandler]):
|
|
144
|
+
|
|
145
|
+
def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
|
|
146
|
+
self._meetings_api = meetings_api
|
|
147
|
+
|
|
148
|
+
def handle(self, event: UserJoinedEventHandler) -> None:
|
|
149
|
+
# do some sync logic
|
|
150
|
+
...
|
|
127
151
|
```
|
|
128
152
|
|
|
129
153
|
## Producing Notification/ECST Events
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import typing
|
|
2
|
-
|
|
3
|
-
from cqrs.events import event as event_models
|
|
4
|
-
|
|
5
|
-
E = typing.TypeVar("E", bound=event_models.Event, contravariant=True)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class EventHandler(typing.Protocol[E]):
|
|
9
|
-
"""
|
|
10
|
-
The event handler interface.
|
|
11
|
-
|
|
12
|
-
Usage::
|
|
13
|
-
|
|
14
|
-
class UserJoinedEventHandler(EventHandler[UserJoinedEventHandler])
|
|
15
|
-
def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
|
|
16
|
-
self._meetings_api = meetings_api
|
|
17
|
-
|
|
18
|
-
async def handle(self, event: UserJoinedEventHandler) -> None:
|
|
19
|
-
await self._meetings_api.notify_room(event.meeting_id, "New user joined!")
|
|
20
|
-
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
async def handle(self, event: E) -> None:
|
|
24
|
-
raise NotImplementedError
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import typing
|
|
2
|
-
|
|
3
|
-
from cqrs import response
|
|
4
|
-
from cqrs.events import event
|
|
5
|
-
from cqrs.requests import request
|
|
6
|
-
|
|
7
|
-
Req = typing.TypeVar("Req", bound=request.Request, contravariant=True)
|
|
8
|
-
Res = typing.TypeVar("Res", response.Response, None, covariant=True)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class RequestHandler(typing.Protocol[Req, Res]):
|
|
12
|
-
"""
|
|
13
|
-
The request handler interface.
|
|
14
|
-
|
|
15
|
-
The request handler is an object, which gets a request as input and may return a response as a result.
|
|
16
|
-
|
|
17
|
-
Command handler example::
|
|
18
|
-
|
|
19
|
-
class JoinMeetingCommandHandler(RequestHandler[JoinMeetingCommand, None])
|
|
20
|
-
def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
|
|
21
|
-
self._meetings_api = meetings_api
|
|
22
|
-
self.events: list[Event] = []
|
|
23
|
-
|
|
24
|
-
async def handle(self, request: JoinMeetingCommand) -> None:
|
|
25
|
-
await self._meetings_api.join_user(request.user_id, request.meeting_id)
|
|
26
|
-
|
|
27
|
-
Query handler example::
|
|
28
|
-
|
|
29
|
-
class ReadMeetingQueryHandler(RequestHandler[ReadMeetingQuery, ReadMeetingQueryResult])
|
|
30
|
-
def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
|
|
31
|
-
self._meetings_api = meetings_api
|
|
32
|
-
self.events: list[Event] = []
|
|
33
|
-
|
|
34
|
-
async def handle(self, request: ReadMeetingQuery) -> ReadMeetingQueryResult:
|
|
35
|
-
link = await self._meetings_api.get_link(request.meeting_id)
|
|
36
|
-
return ReadMeetingQueryResult(link=link, meeting_id=request.meeting_id)
|
|
37
|
-
|
|
38
|
-
"""
|
|
39
|
-
|
|
40
|
-
@property
|
|
41
|
-
def events(self) -> list[event.Event]:
|
|
42
|
-
...
|
|
43
|
-
|
|
44
|
-
async def handle(self, request: Req) -> Res:
|
|
45
|
-
raise NotImplementedError
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|