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.
Files changed (50) hide show
  1. {python_cqrs-0.0.18/src/python_cqrs.egg-info → python_cqrs-0.1.2}/PKG-INFO +29 -5
  2. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/README.md +27 -2
  3. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/pyproject.toml +1 -3
  4. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/dispatcher/dispatcher.py +29 -9
  5. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/events/event_emitter.py +21 -10
  6. python_cqrs-0.1.2/src/cqrs/events/event_handler.py +46 -0
  7. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/mediator.py +7 -7
  8. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/middlewares/base.py +7 -7
  9. python_cqrs-0.1.2/src/cqrs/requests/request_handler.py +86 -0
  10. {python_cqrs-0.0.18 → python_cqrs-0.1.2/src/python_cqrs.egg-info}/PKG-INFO +29 -5
  11. python_cqrs-0.0.18/src/cqrs/events/event_handler.py +0 -24
  12. python_cqrs-0.0.18/src/cqrs/requests/request_handler.py +0 -45
  13. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/LICENSE +0 -0
  14. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/setup.cfg +0 -0
  15. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/__init__.py +0 -0
  16. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/adapters/__init__.py +0 -0
  17. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/adapters/amqp.py +0 -0
  18. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/adapters/kafka.py +0 -0
  19. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/compressors/__init__.py +0 -0
  20. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/compressors/protocol.py +0 -0
  21. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/compressors/zlib.py +0 -0
  22. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/container/__init__.py +0 -0
  23. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/container/di.py +0 -0
  24. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/container/protocol.py +0 -0
  25. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/dispatcher/__init__.py +0 -0
  26. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/events/__init__.py +0 -0
  27. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/events/bootstrap.py +0 -0
  28. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/events/event.py +0 -0
  29. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/events/map.py +0 -0
  30. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/message_brokers/__init__.py +0 -0
  31. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/message_brokers/amqp.py +0 -0
  32. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/message_brokers/devnull.py +0 -0
  33. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/message_brokers/kafka.py +0 -0
  34. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/message_brokers/protocol.py +0 -0
  35. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/middlewares/__init__.py +0 -0
  36. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/middlewares/logging.py +0 -0
  37. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/outbox/__init__.py +0 -0
  38. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/outbox/producer.py +0 -0
  39. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/outbox/protocol.py +0 -0
  40. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/outbox/repository.py +0 -0
  41. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/outbox/sqlalchemy.py +0 -0
  42. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/requests/__init__.py +0 -0
  43. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/requests/bootstrap.py +0 -0
  44. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/requests/map.py +0 -0
  45. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/requests/request.py +0 -0
  46. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/cqrs/response.py +0 -0
  47. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/python_cqrs.egg-info/SOURCES.txt +0 -0
  48. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/python_cqrs.egg-info/dependency_links.txt +0 -0
  49. {python_cqrs-0.0.18 → python_cqrs-0.1.2}/src/python_cqrs.egg-info/requires.txt +0 -0
  50. {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.0.18
3
+ Version: 0.1.2
4
4
  Summary: Python CQRS pattern implementation
5
- Author: Nikita Kunov
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.0.18"
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 res,
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
- Resp = typing.TypeVar("Resp", res.Response, None, contravariant=True)
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[Resp]):
23
- response: Resp = pydantic.Field(default=None)
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
- wrapped_handle = self._middleware_chain.wrap(handler.handle)
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[cqrs_events.Event]],
84
+ handle_type: typing.Type[cqrs_events.EventHandler],
68
85
  ):
69
- handler = await self._container.resolve(handle_type)
70
- await handler.handle(event)
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: container.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: event.Event) -> None: ...
33
+ async def emit(self, event: event_model.Event) -> None: ...
28
34
 
29
35
  @emit.register
30
- async def _(self, event: event.DomainEvent) -> None:
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(handler_type)
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
- await handler.handle(event)
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: event.NotificationEvent) -> None:
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: event.ECSTEvent) -> None:
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: event.NotificationEvent | event.ECSTEvent,
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
- Resp = typing.TypeVar("Resp", response.Response, None, contravariant=True)
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: events.EventEmitter | None = None,
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) -> Resp:
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[events.Event]) -> None:
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: events.EventMap,
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: events.Event) -> None:
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
- Req = typing.TypeVar("Req", bound=requests.Request, contravariant=True)
7
- Res = typing.TypeVar("Res", response.Response, None, contravariant=True)
8
- HandleType = typing.Callable[[Req], typing.Awaitable[Res]]
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: requests.Request, handle: HandleType) -> Res: ...
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: list[Middleware] = []
17
+ self._chain: typing.List[Middleware] = []
18
18
 
19
- def set(self, chain: list[Middleware]) -> None:
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.0.18
3
+ Version: 0.1.2
4
4
  Summary: Python CQRS pattern implementation
5
- Author: Nikita Kunov
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