python-cqrs 5.1.0__tar.gz → 5.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.
Files changed (121) hide show
  1. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/PKG-INFO +81 -36
  2. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/README.md +78 -33
  3. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/pyproject.toml +6 -3
  4. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/__init__.py +71 -0
  5. python_cqrs-5.2.0/src/cqrs/adapters/__init__.py +10 -0
  6. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/adapters/circuit_breaker.py +6 -0
  7. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/adapters/kafka.py +10 -0
  8. python_cqrs-5.2.0/src/cqrs/bootstrap/__init__.py +21 -0
  9. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/bootstrap/events.py +13 -2
  10. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/bootstrap/requests.py +41 -0
  11. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/bootstrap/saga.py +14 -1
  12. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/circuit_breaker.py +10 -0
  13. python_cqrs-5.2.0/src/cqrs/compressors/protocol.py +30 -0
  14. python_cqrs-5.2.0/src/cqrs/compressors/zlib.py +18 -0
  15. python_cqrs-5.2.0/src/cqrs/container/__init__.py +30 -0
  16. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/container/dependency_injector.py +6 -0
  17. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/container/di.py +7 -0
  18. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/container/dishka.py +14 -1
  19. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/container/protocol.py +18 -0
  20. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/container/scope.py +29 -1
  21. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/dispatcher/__init__.py +9 -0
  22. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/events/__init__.py +9 -1
  23. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/events/event_emitter.py +14 -0
  24. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/events/fallback.py +6 -0
  25. python_cqrs-5.2.0/src/cqrs/generic_utils.py +119 -0
  26. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/handlers/__init__.py +11 -0
  27. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/handlers/cor.py +59 -1
  28. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/handlers/event.py +17 -0
  29. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/handlers/request.py +69 -1
  30. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/handlers/saga.py +30 -0
  31. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/mapping/events.py +13 -0
  32. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/mapping/outbox.py +40 -0
  33. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/mapping/requests.py +37 -0
  34. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/mediators/event.py +12 -0
  35. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/mediators/request.py +19 -10
  36. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/mediators/saga.py +16 -1
  37. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/mediators/streaming.py +23 -3
  38. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/mermaid/cor.py +13 -0
  39. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/mermaid/saga.py +11 -0
  40. python_cqrs-5.2.0/src/cqrs/message_brokers/__init__.py +17 -0
  41. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/message_brokers/amqp.py +9 -0
  42. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/message_brokers/kafka.py +9 -0
  43. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/message_brokers/producer.py +27 -1
  44. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/message_brokers/protocol.py +17 -0
  45. python_cqrs-5.2.0/src/cqrs/middlewares/__init__.py +14 -0
  46. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/middlewares/base.py +29 -0
  47. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/middlewares/logging.py +7 -0
  48. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/models/event.py +54 -0
  49. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/models/pydantic.py +34 -4
  50. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/models/request.py +38 -4
  51. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/models/response.py +20 -0
  52. python_cqrs-5.2.0/src/cqrs/outbox/repository.py +156 -0
  53. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/outbox/sqlalchemy.py +26 -0
  54. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/outbox/sqlmodel.py +12 -1
  55. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/requests/fallback.py +8 -0
  56. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/saga/fallback.py +15 -1
  57. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/saga/models.py +15 -0
  58. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/saga/recovery.py +18 -0
  59. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/saga/saga.py +37 -14
  60. python_cqrs-5.2.0/src/cqrs/saga/storage/__init__.py +23 -0
  61. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/saga/storage/memory.py +18 -1
  62. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/saga/storage/protocol.py +8 -0
  63. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/saga/storage/sqlalchemy.py +16 -0
  64. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/saga/storage/sqlmodel.py +13 -1
  65. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/serializers/json.py +8 -1
  66. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/serializers/protobuf.py +9 -1
  67. python_cqrs-5.2.0/src/cqrs/serializers/protocol.py +55 -0
  68. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/python_cqrs.egg-info/PKG-INFO +81 -36
  69. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/python_cqrs.egg-info/SOURCES.txt +1 -0
  70. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/python_cqrs.egg-info/requires.txt +2 -2
  71. python_cqrs-5.1.0/src/cqrs/bootstrap/__init__.py +0 -5
  72. python_cqrs-5.1.0/src/cqrs/compressors/protocol.py +0 -11
  73. python_cqrs-5.1.0/src/cqrs/compressors/zlib.py +0 -9
  74. python_cqrs-5.1.0/src/cqrs/container/__init__.py +0 -18
  75. python_cqrs-5.1.0/src/cqrs/generic_utils.py +0 -43
  76. python_cqrs-5.1.0/src/cqrs/message_brokers/__init__.py +0 -6
  77. python_cqrs-5.1.0/src/cqrs/middlewares/__init__.py +0 -3
  78. python_cqrs-5.1.0/src/cqrs/outbox/repository.py +0 -101
  79. python_cqrs-5.1.0/src/cqrs/saga/storage/__init__.py +0 -11
  80. python_cqrs-5.1.0/src/cqrs/serializers/protocol.py +0 -21
  81. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/LICENSE +0 -0
  82. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/setup.cfg +0 -0
  83. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/_dataclass_utils.py +0 -0
  84. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/adapters/amqp.py +0 -0
  85. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/adapters/protocol.py +0 -0
  86. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/compressors/__init__.py +0 -0
  87. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/deserializers/__init__.py +0 -0
  88. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/deserializers/exceptions.py +0 -0
  89. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/deserializers/json.py +0 -0
  90. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/deserializers/protobuf.py +0 -0
  91. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/dispatcher/event.py +0 -0
  92. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/dispatcher/exceptions.py +0 -0
  93. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/dispatcher/models.py +0 -0
  94. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/dispatcher/request.py +0 -0
  95. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/dispatcher/saga.py +0 -0
  96. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/dispatcher/streaming.py +0 -0
  97. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/events/event_processor.py +0 -0
  98. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/mapping/__init__.py +0 -0
  99. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/mediators/__init__.py +0 -0
  100. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/mediators/_common.py +0 -0
  101. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/mermaid/__init__.py +0 -0
  102. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/message_brokers/devnull.py +0 -0
  103. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/models/__init__.py +0 -0
  104. {python_cqrs-5.1.0/src/cqrs/adapters → python_cqrs-5.2.0/src/cqrs/outbox}/__init__.py +0 -0
  105. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/outbox/mock.py +0 -0
  106. /python_cqrs-5.1.0/src/cqrs/outbox/__init__.py → /python_cqrs-5.2.0/src/cqrs/py.typed +0 -0
  107. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/requests/__init__.py +0 -0
  108. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/saga/__init__.py +0 -0
  109. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/saga/circuit_breaker.py +0 -0
  110. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/saga/compensation.py +0 -0
  111. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/saga/execution.py +0 -0
  112. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/saga/storage/enums.py +0 -0
  113. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/saga/storage/models.py +0 -0
  114. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/saga/validation.py +0 -0
  115. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/serializers/__init__.py +0 -0
  116. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/serializers/default.py +0 -0
  117. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/sqlalchemy_types.py +0 -0
  118. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/sqlmodel_base.py +0 -0
  119. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/cqrs/types.py +0 -0
  120. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/python_cqrs.egg-info/dependency_links.txt +0 -0
  121. {python_cqrs-5.1.0 → python_cqrs-5.2.0}/src/python_cqrs.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-cqrs
3
- Version: 5.1.0
3
+ Version: 5.2.0
4
4
  Summary: Event-Driven Architecture Framework for Distributed Systems
5
5
  Author-email: Vadim Kozyrevskiy <vadikko2@mail.ru>, Dmitry Kutlubaev <kutlubaev00@mail.ru>
6
6
  Maintainer-email: Vadim Kozyrevskiy <vadikko2@mail.ru>
@@ -22,7 +22,7 @@ Requires-Dist: di[anyio]==0.*
22
22
  Requires-Dist: dependency-injector>=4.0
23
23
  Requires-Dist: orjson==3.*
24
24
  Requires-Dist: python-dotenv==1.*
25
- Requires-Dist: typing-extensions>=4.0
25
+ Requires-Dist: typing-extensions>=4.7
26
26
  Provides-Extra: aiobreaker
27
27
  Requires-Dist: aiobreaker>=0.3.0; extra == "aiobreaker"
28
28
  Provides-Extra: pydantic
@@ -64,7 +64,7 @@ Requires-Dist: fastapi==0.109.*; extra == "examples"
64
64
  Requires-Dist: faststream[kafka]==0.5.28; extra == "examples"
65
65
  Requires-Dist: faker>=37.12.0; extra == "examples"
66
66
  Requires-Dist: uvicorn==0.32.0; extra == "examples"
67
- Requires-Dist: aiohttp==3.14.3; extra == "examples"
67
+ Requires-Dist: aiohttp==3.14.4; extra == "examples"
68
68
  Requires-Dist: protobuf>=4.25.8; extra == "examples"
69
69
  Requires-Dist: aiosqlite>=0.20.0; extra == "examples"
70
70
  Requires-Dist: dishka>=1.0; extra == "examples"
@@ -103,12 +103,6 @@ Dynamic: license-file
103
103
  <a href="https://codecov.io/gh/vadikko2/python-cqrs">
104
104
  <img src="https://img.shields.io/codecov/c/github/vadikko2/python-cqrs?logo=codecov&logoColor=white" alt="Coverage">
105
105
  </a>
106
- <a href="https://mkdocs.python-cqrs.dev/">
107
- <img src="https://img.shields.io/badge/docs-mkdocs-blue?logo=readthedocs" alt="Documentation">
108
- </a>
109
- <a href="https://deepwiki.com/vadikko2/python-cqrs">
110
- <img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki">
111
- </a>
112
106
  <a href="https://github.com/mjhea0/awesome-fastapi#developer-tools">
113
107
  <img src="https://img.shields.io/badge/Awesome%20FastAPI-✓%20in%20list-purple?logo=eye&logoColor=white&style=flat-square" alt="Included in Awesome FastAPI">
114
108
  </a>
@@ -144,9 +138,6 @@ Dynamic: license-file
144
138
 
145
139
  An event-driven framework for building distributed systems in Python. It centers on CQRS (Command Query Responsibility Segregation) and extends into messaging, sagas, and reliable event delivery — so you can separate read and write flows, react to events from the bus, run distributed transactions with compensation, and publish events via Transaction Outbox. The result is clearer structure, better scalability, and easier evolution of the application.
146
140
 
147
- This package is a fork of the [diator](https://github.com/akhundMurad/diator)
148
- project ([documentation](https://akhundmurad.github.io/diator/)) with several enhancements, ordered by importance:
149
-
150
141
  **Core framework**
151
142
 
152
143
  1. Redesigned the event and request mapping mechanism to handlers;
@@ -200,6 +191,8 @@ Default `Request` / `Response` / `Event` / `DomainEvent` / `NotificationEvent` a
200
191
  are dataclass-based (`DC*`). Use `Pydantic*` types with the pydantic extra when you
201
192
  need validation.
202
193
 
194
+ Coding agents in an application can follow [`templates/AGENTS.md`](templates/AGENTS.md). Copy that file into the application repository.
195
+
203
196
 
204
197
  ## Quick Start
205
198
 
@@ -210,12 +203,12 @@ import di
210
203
  import cqrs
211
204
  from cqrs.bootstrap import requests as bootstrap
212
205
 
213
- class CreateOrderCommand(cqrs.Request):
206
+ class CreateOrderCommand(cqrs.Request[None]):
214
207
  order_id: str
215
208
  amount: float
216
209
 
217
- class CreateOrderHandler(cqrs.RequestHandler[CreateOrderCommand, None]):
218
- async def handle(self, request: CreateOrderCommand) -> None:
210
+ class CreateOrderHandler(cqrs.RequestHandler[CreateOrderCommand]):
211
+ async def handle(self, request: CreateOrderCommand):
219
212
  print(f"Order {request.order_id}, amount {request.amount}")
220
213
 
221
214
  def commands_mapper(mapper: cqrs.RequestMap) -> None:
@@ -292,14 +285,20 @@ Request handlers can be divided into two main types:
292
285
  Command Handler executes the received command. The logic of the handler may include, for example, modifying the state of
293
286
  the domain model.
294
287
  As a result of executing the command, an event may be produced to the broker.
288
+
295
289
  > [!TIP]
296
290
  > By default, the command handler does not return any result, but it is not mandatory.
297
291
 
298
292
  ```python
293
+ import cqrs
299
294
  from cqrs.handlers.request import RequestHandler
300
295
  from cqrs.models.event import Event
301
296
 
302
- class JoinMeetingCommandHandler(RequestHandler[JoinMeetingCommand, None]):
297
+ class JoinMeetingCommand(cqrs.Request[None]):
298
+ user_id: str
299
+ meeting_id: str
300
+
301
+ class JoinMeetingCommandHandler(RequestHandler[JoinMeetingCommand]):
303
302
 
304
303
  def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
305
304
  self._meetings_api = meetings_api
@@ -309,25 +308,36 @@ class JoinMeetingCommandHandler(RequestHandler[JoinMeetingCommand, None]):
309
308
  def events(self) -> typing.List[events.Event]:
310
309
  return self._events
311
310
 
312
- async def handle(self, request: JoinMeetingCommand) -> None:
311
+ async def handle(self, request: JoinMeetingCommand):
313
312
  await self._meetings_api.join_user(request.user_id, request.meeting_id)
314
313
  ```
315
314
 
316
315
  A complete example can be found in
317
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/request_handler.py)
316
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/request_handler.py).
317
+ The one-argument form is in
318
+ [`examples/events/typed_request_handler.py`](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/typed_request_handler.py).
318
319
 
319
320
  ### Query handler
320
321
 
321
322
  Query Handler returns a representation of the requested data, for example, from
322
323
  the [read model](https://radekmaziarka.pl/2018/01/08/cqrs-third-step-simple-read-model/#simple-read-model---to-the-rescue).
324
+
323
325
  > [!TIP]
324
326
  > The read model can be constructed based on domain events produced by the `Command Handler`.
325
327
 
326
328
  ```python
329
+ import cqrs
327
330
  from cqrs.handlers.request import RequestHandler
328
331
  from cqrs.models.event import Event
329
332
 
330
- class ReadMeetingQueryHandler(RequestHandler[ReadMeetingQuery, ReadMeetingQueryResult]):
333
+ class ReadMeetingQueryResult(cqrs.Response):
334
+ link: str
335
+ meeting_id: str
336
+
337
+ class ReadMeetingQuery(cqrs.Request[ReadMeetingQueryResult]):
338
+ meeting_id: str
339
+
340
+ class ReadMeetingQueryHandler(RequestHandler[ReadMeetingQuery]):
331
341
 
332
342
  def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
333
343
  self._meetings_api = meetings_api
@@ -337,14 +347,16 @@ class ReadMeetingQueryHandler(RequestHandler[ReadMeetingQuery, ReadMeetingQueryR
337
347
  def events(self) -> typing.List[events.Event]:
338
348
  return self._events
339
349
 
340
- async def handle(self, request: ReadMeetingQuery) -> ReadMeetingQueryResult:
350
+ async def handle(self, request: ReadMeetingQuery):
341
351
  link = await self._meetings_api.get_link(request.meeting_id)
342
352
  return ReadMeetingQueryResult(link=link, meeting_id=request.meeting_id)
343
353
 
344
354
  ```
345
355
 
346
356
  A complete example can be found in
347
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/request_handler.py)
357
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/request_handler.py).
358
+ The one-argument form is in
359
+ [`examples/events/typed_request_handler.py`](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/typed_request_handler.py).
348
360
 
349
361
  ### Streaming Request Handler
350
362
 
@@ -355,11 +367,18 @@ real-time progress updates.
355
367
  `StreamingRequestHandler` works with `StreamingRequestMediator` that streams results to clients in real-time.
356
368
 
357
369
  ```python
358
- import typing
370
+ import cqrs
359
371
  from cqrs.handlers.request import StreamingRequestHandler
360
372
  from cqrs.models.event import Event
361
373
 
362
- class ProcessFilesCommandHandler(StreamingRequestHandler[ProcessFilesCommand, FileProcessedResult]):
374
+ class FileProcessedResult(cqrs.Response):
375
+ file_id: str
376
+ status: str
377
+
378
+ class ProcessFilesCommand(cqrs.Request[FileProcessedResult]):
379
+ file_ids: list[str]
380
+
381
+ class ProcessFilesCommandHandler(StreamingRequestHandler[ProcessFilesCommand]):
363
382
  def __init__(self):
364
383
  self._events: list[Event] = []
365
384
 
@@ -370,7 +389,7 @@ class ProcessFilesCommandHandler(StreamingRequestHandler[ProcessFilesCommand, Fi
370
389
  def clear_events(self) -> None:
371
390
  self._events.clear()
372
391
 
373
- async def handle(self, request: ProcessFilesCommand) -> typing.AsyncIterator[FileProcessedResult]:
392
+ async def handle(self, request: ProcessFilesCommand):
374
393
  for file_id in request.file_ids:
375
394
  # Process file
376
395
  result = FileProcessedResult(file_id=file_id, status="completed", ...)
@@ -380,7 +399,9 @@ class ProcessFilesCommandHandler(StreamingRequestHandler[ProcessFilesCommand, Fi
380
399
  ```
381
400
 
382
401
  A complete example can be found in
383
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/streaming/streaming_handler_parallel_events.py)
402
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/streaming/streaming_handler_parallel_events.py).
403
+ The one-argument form is in
404
+ [`examples/streaming/typed_streaming_handler.py`](https://github.com/vadikko2/python-cqrs/blob/master/examples/streaming/typed_streaming_handler.py).
384
405
 
385
406
  ### Chain of Responsibility Request Handler
386
407
 
@@ -393,10 +414,18 @@ when a handler successfully processes the request or when all handlers have been
393
414
 
394
415
  ```python
395
416
  import typing
417
+ import cqrs
396
418
  from cqrs.handlers.cor import CORRequestHandler
397
419
  from cqrs.models.event import Event
398
420
 
399
- class CreditCardPaymentHandler(CORRequestHandler[ProcessPaymentCommand, PaymentResult]):
421
+ class PaymentResult(cqrs.Response):
422
+ success: bool
423
+ transaction_id: str
424
+
425
+ class ProcessPaymentCommand(cqrs.Request[PaymentResult]):
426
+ payment_method: str
427
+
428
+ class CreditCardPaymentHandler(CORRequestHandler[ProcessPaymentCommand]):
400
429
  def __init__(self, payment_service: PaymentServiceProtocol) -> None:
401
430
  self._payment_service = payment_service
402
431
  self._events: typing.List[Event] = []
@@ -405,7 +434,7 @@ class CreditCardPaymentHandler(CORRequestHandler[ProcessPaymentCommand, PaymentR
405
434
  def events(self) -> typing.List[Event]:
406
435
  return self._events
407
436
 
408
- async def handle(self, request: ProcessPaymentCommand) -> PaymentResult | None:
437
+ async def handle(self, request: ProcessPaymentCommand):
409
438
  if request.payment_method == "credit_card":
410
439
  # Process credit card payment
411
440
  result = await self._payment_service.process_credit_card(request)
@@ -415,7 +444,7 @@ class CreditCardPaymentHandler(CORRequestHandler[ProcessPaymentCommand, PaymentR
415
444
  # Pass to next handler
416
445
  return await self.next(request)
417
446
 
418
- class PayPalPaymentHandler(CORRequestHandler[ProcessPaymentCommand, PaymentResult]):
447
+ class PayPalPaymentHandler(CORRequestHandler[ProcessPaymentCommand]):
419
448
  def __init__(self, paypal_service: PayPalServiceProtocol) -> None:
420
449
  self._paypal_service = paypal_service
421
450
  self._events: typing.List[Event] = []
@@ -424,7 +453,7 @@ class PayPalPaymentHandler(CORRequestHandler[ProcessPaymentCommand, PaymentResul
424
453
  def events(self) -> typing.List[Event]:
425
454
  return self._events
426
455
 
427
- async def handle(self, request: ProcessPaymentCommand) -> PaymentResult | None:
456
+ async def handle(self, request: ProcessPaymentCommand):
428
457
  if request.payment_method == "paypal":
429
458
  # Process PayPal payment
430
459
  result = await self._paypal_service.process_payment(request)
@@ -443,7 +472,9 @@ def payment_mapper(mapper: cqrs.RequestMap) -> None:
443
472
  ```
444
473
 
445
474
  A complete example can be found in
446
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/cor/cor_request_handler.py)
475
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/cor/cor_request_handler.py).
476
+ The one-argument form is in
477
+ [`examples/cor/typed_cor_request_handler.py`](https://github.com/vadikko2/python-cqrs/blob/master/examples/cor/typed_cor_request_handler.py).
447
478
 
448
479
  #### Mermaid Diagram Generation
449
480
 
@@ -815,7 +846,11 @@ Complete example: [Saga Mermaid Diagrams](https://github.com/vadikko2/python-cqr
815
846
  During the handling of a command, `cqrs.NotificationEvent` events may be generated and then sent to the broker.
816
847
 
817
848
  ```python
818
- class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
849
+ class JoinMeetingCommand(cqrs.Request[None]):
850
+ user_id: str
851
+ meeting_id: str
852
+
853
+ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand]):
819
854
  def __init__(self):
820
855
  self._events = []
821
856
 
@@ -823,7 +858,7 @@ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
823
858
  def events(self):
824
859
  return self._events
825
860
 
826
- async def handle(self, request: JoinMeetingCommand) -> None:
861
+ async def handle(self, request: JoinMeetingCommand):
827
862
  print(f"User {request.user_id} joined meeting {request.meeting_id}")
828
863
  self._events.append(
829
864
  cqrs.NotificationEvent[UserJoinedNotificationPayload](
@@ -883,7 +918,11 @@ The package implements the [Transactional Outbox](https://microservices.io/patte
883
918
  pattern, which ensures that messages are produced to the broker according to the at-least-once semantics.
884
919
 
885
920
  ```python
886
- class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
921
+ class JoinMeetingCommand(cqrs.Request[None]):
922
+ user_id: str
923
+ meeting_id: str
924
+
925
+ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand]):
887
926
  def __init__(self, outbox: cqrs.OutboxedEventRepository):
888
927
  self.outbox = outbox
889
928
 
@@ -891,7 +930,7 @@ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
891
930
  def events(self):
892
931
  return []
893
932
 
894
- async def handle(self, request: JoinMeetingCommand) -> None:
933
+ async def handle(self, request: JoinMeetingCommand):
895
934
  print(f"User {request.user_id} joined meeting {request.meeting_id}")
896
935
  # Outbox repository is bound to a session (e.g. via DI request scope).
897
936
  # add() takes only the event; commit() persists the outbox and your changes.
@@ -928,12 +967,14 @@ A runnable FastAPI flow (route → command → transactional outbox → publishe
928
967
  > The Outbox table is named `outbox` by default. To rename it, or to attach the model to your own declarative
929
968
  > base, use `cqrs.rebind_outbox_model(OutboxModel, Base, table_name="my_outbox_table")` at startup — before
930
969
  > `create_all()`, before generating migrations and before the first query.
970
+
931
971
  > [!TIP]
932
972
  > `SqlAlchemyOutboxedEventRepository` works on **MySQL/MariaDB** and **PostgreSQL**: the outbox columns are
933
973
  > rendered with dialect-aware types (`BINARY(16)` on MySQL, `UUID`/`BYTEA` on PostgreSQL), with a portable
934
974
  > `BLOB` fallback for other dialects. Switching databases is a DSN change only — see the
935
975
  > [Database Support](https://mkdocs.python-cqrs.dev/outbox/databases/) docs for the DDL, the Alembic recipe and
936
976
  > how to register a native type for your own database.
977
+
937
978
  > [!TIP]
938
979
  > For Protobuf events, implement `proto()` / `from_proto()` and register a
939
980
  > `ProtobufEventSerializer` on an `OutboxedEventMap`. JSON stays the default —
@@ -998,7 +1039,11 @@ To configure event handling, you need to implement a broker consumer on the side
998
1039
  Below is an example of `Kafka event consuming` that can be used in the Presentation Layer.
999
1040
 
1000
1041
  ```python
1001
- class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
1042
+ class JoinMeetingCommand(cqrs.Request[None]):
1043
+ user_id: str
1044
+ meeting_id: str
1045
+
1046
+ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand]):
1002
1047
  def __init__(self):
1003
1048
  self._events = []
1004
1049
 
@@ -1006,7 +1051,7 @@ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
1006
1051
  def events(self):
1007
1052
  return self._events
1008
1053
 
1009
- async def handle(self, request: JoinMeetingCommand) -> None:
1054
+ async def handle(self, request: JoinMeetingCommand):
1010
1055
  STORAGE[request.meeting_id].append(request.user_id)
1011
1056
  self._events.append(
1012
1057
  UserJoined(user_id=request.user_id, meeting_id=request.meeting_id),
@@ -20,12 +20,6 @@
20
20
  <a href="https://codecov.io/gh/vadikko2/python-cqrs">
21
21
  <img src="https://img.shields.io/codecov/c/github/vadikko2/python-cqrs?logo=codecov&logoColor=white" alt="Coverage">
22
22
  </a>
23
- <a href="https://mkdocs.python-cqrs.dev/">
24
- <img src="https://img.shields.io/badge/docs-mkdocs-blue?logo=readthedocs" alt="Documentation">
25
- </a>
26
- <a href="https://deepwiki.com/vadikko2/python-cqrs">
27
- <img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki">
28
- </a>
29
23
  <a href="https://github.com/mjhea0/awesome-fastapi#developer-tools">
30
24
  <img src="https://img.shields.io/badge/Awesome%20FastAPI-✓%20in%20list-purple?logo=eye&logoColor=white&style=flat-square" alt="Included in Awesome FastAPI">
31
25
  </a>
@@ -61,9 +55,6 @@
61
55
 
62
56
  An event-driven framework for building distributed systems in Python. It centers on CQRS (Command Query Responsibility Segregation) and extends into messaging, sagas, and reliable event delivery — so you can separate read and write flows, react to events from the bus, run distributed transactions with compensation, and publish events via Transaction Outbox. The result is clearer structure, better scalability, and easier evolution of the application.
63
57
 
64
- This package is a fork of the [diator](https://github.com/akhundMurad/diator)
65
- project ([documentation](https://akhundmurad.github.io/diator/)) with several enhancements, ordered by importance:
66
-
67
58
  **Core framework**
68
59
 
69
60
  1. Redesigned the event and request mapping mechanism to handlers;
@@ -117,6 +108,8 @@ Default `Request` / `Response` / `Event` / `DomainEvent` / `NotificationEvent` a
117
108
  are dataclass-based (`DC*`). Use `Pydantic*` types with the pydantic extra when you
118
109
  need validation.
119
110
 
111
+ Coding agents in an application can follow [`templates/AGENTS.md`](templates/AGENTS.md). Copy that file into the application repository.
112
+
120
113
 
121
114
  ## Quick Start
122
115
 
@@ -127,12 +120,12 @@ import di
127
120
  import cqrs
128
121
  from cqrs.bootstrap import requests as bootstrap
129
122
 
130
- class CreateOrderCommand(cqrs.Request):
123
+ class CreateOrderCommand(cqrs.Request[None]):
131
124
  order_id: str
132
125
  amount: float
133
126
 
134
- class CreateOrderHandler(cqrs.RequestHandler[CreateOrderCommand, None]):
135
- async def handle(self, request: CreateOrderCommand) -> None:
127
+ class CreateOrderHandler(cqrs.RequestHandler[CreateOrderCommand]):
128
+ async def handle(self, request: CreateOrderCommand):
136
129
  print(f"Order {request.order_id}, amount {request.amount}")
137
130
 
138
131
  def commands_mapper(mapper: cqrs.RequestMap) -> None:
@@ -209,14 +202,20 @@ Request handlers can be divided into two main types:
209
202
  Command Handler executes the received command. The logic of the handler may include, for example, modifying the state of
210
203
  the domain model.
211
204
  As a result of executing the command, an event may be produced to the broker.
205
+
212
206
  > [!TIP]
213
207
  > By default, the command handler does not return any result, but it is not mandatory.
214
208
 
215
209
  ```python
210
+ import cqrs
216
211
  from cqrs.handlers.request import RequestHandler
217
212
  from cqrs.models.event import Event
218
213
 
219
- class JoinMeetingCommandHandler(RequestHandler[JoinMeetingCommand, None]):
214
+ class JoinMeetingCommand(cqrs.Request[None]):
215
+ user_id: str
216
+ meeting_id: str
217
+
218
+ class JoinMeetingCommandHandler(RequestHandler[JoinMeetingCommand]):
220
219
 
221
220
  def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
222
221
  self._meetings_api = meetings_api
@@ -226,25 +225,36 @@ class JoinMeetingCommandHandler(RequestHandler[JoinMeetingCommand, None]):
226
225
  def events(self) -> typing.List[events.Event]:
227
226
  return self._events
228
227
 
229
- async def handle(self, request: JoinMeetingCommand) -> None:
228
+ async def handle(self, request: JoinMeetingCommand):
230
229
  await self._meetings_api.join_user(request.user_id, request.meeting_id)
231
230
  ```
232
231
 
233
232
  A complete example can be found in
234
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/request_handler.py)
233
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/request_handler.py).
234
+ The one-argument form is in
235
+ [`examples/events/typed_request_handler.py`](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/typed_request_handler.py).
235
236
 
236
237
  ### Query handler
237
238
 
238
239
  Query Handler returns a representation of the requested data, for example, from
239
240
  the [read model](https://radekmaziarka.pl/2018/01/08/cqrs-third-step-simple-read-model/#simple-read-model---to-the-rescue).
241
+
240
242
  > [!TIP]
241
243
  > The read model can be constructed based on domain events produced by the `Command Handler`.
242
244
 
243
245
  ```python
246
+ import cqrs
244
247
  from cqrs.handlers.request import RequestHandler
245
248
  from cqrs.models.event import Event
246
249
 
247
- class ReadMeetingQueryHandler(RequestHandler[ReadMeetingQuery, ReadMeetingQueryResult]):
250
+ class ReadMeetingQueryResult(cqrs.Response):
251
+ link: str
252
+ meeting_id: str
253
+
254
+ class ReadMeetingQuery(cqrs.Request[ReadMeetingQueryResult]):
255
+ meeting_id: str
256
+
257
+ class ReadMeetingQueryHandler(RequestHandler[ReadMeetingQuery]):
248
258
 
249
259
  def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
250
260
  self._meetings_api = meetings_api
@@ -254,14 +264,16 @@ class ReadMeetingQueryHandler(RequestHandler[ReadMeetingQuery, ReadMeetingQueryR
254
264
  def events(self) -> typing.List[events.Event]:
255
265
  return self._events
256
266
 
257
- async def handle(self, request: ReadMeetingQuery) -> ReadMeetingQueryResult:
267
+ async def handle(self, request: ReadMeetingQuery):
258
268
  link = await self._meetings_api.get_link(request.meeting_id)
259
269
  return ReadMeetingQueryResult(link=link, meeting_id=request.meeting_id)
260
270
 
261
271
  ```
262
272
 
263
273
  A complete example can be found in
264
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/request_handler.py)
274
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/request_handler.py).
275
+ The one-argument form is in
276
+ [`examples/events/typed_request_handler.py`](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/typed_request_handler.py).
265
277
 
266
278
  ### Streaming Request Handler
267
279
 
@@ -272,11 +284,18 @@ real-time progress updates.
272
284
  `StreamingRequestHandler` works with `StreamingRequestMediator` that streams results to clients in real-time.
273
285
 
274
286
  ```python
275
- import typing
287
+ import cqrs
276
288
  from cqrs.handlers.request import StreamingRequestHandler
277
289
  from cqrs.models.event import Event
278
290
 
279
- class ProcessFilesCommandHandler(StreamingRequestHandler[ProcessFilesCommand, FileProcessedResult]):
291
+ class FileProcessedResult(cqrs.Response):
292
+ file_id: str
293
+ status: str
294
+
295
+ class ProcessFilesCommand(cqrs.Request[FileProcessedResult]):
296
+ file_ids: list[str]
297
+
298
+ class ProcessFilesCommandHandler(StreamingRequestHandler[ProcessFilesCommand]):
280
299
  def __init__(self):
281
300
  self._events: list[Event] = []
282
301
 
@@ -287,7 +306,7 @@ class ProcessFilesCommandHandler(StreamingRequestHandler[ProcessFilesCommand, Fi
287
306
  def clear_events(self) -> None:
288
307
  self._events.clear()
289
308
 
290
- async def handle(self, request: ProcessFilesCommand) -> typing.AsyncIterator[FileProcessedResult]:
309
+ async def handle(self, request: ProcessFilesCommand):
291
310
  for file_id in request.file_ids:
292
311
  # Process file
293
312
  result = FileProcessedResult(file_id=file_id, status="completed", ...)
@@ -297,7 +316,9 @@ class ProcessFilesCommandHandler(StreamingRequestHandler[ProcessFilesCommand, Fi
297
316
  ```
298
317
 
299
318
  A complete example can be found in
300
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/streaming/streaming_handler_parallel_events.py)
319
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/streaming/streaming_handler_parallel_events.py).
320
+ The one-argument form is in
321
+ [`examples/streaming/typed_streaming_handler.py`](https://github.com/vadikko2/python-cqrs/blob/master/examples/streaming/typed_streaming_handler.py).
301
322
 
302
323
  ### Chain of Responsibility Request Handler
303
324
 
@@ -310,10 +331,18 @@ when a handler successfully processes the request or when all handlers have been
310
331
 
311
332
  ```python
312
333
  import typing
334
+ import cqrs
313
335
  from cqrs.handlers.cor import CORRequestHandler
314
336
  from cqrs.models.event import Event
315
337
 
316
- class CreditCardPaymentHandler(CORRequestHandler[ProcessPaymentCommand, PaymentResult]):
338
+ class PaymentResult(cqrs.Response):
339
+ success: bool
340
+ transaction_id: str
341
+
342
+ class ProcessPaymentCommand(cqrs.Request[PaymentResult]):
343
+ payment_method: str
344
+
345
+ class CreditCardPaymentHandler(CORRequestHandler[ProcessPaymentCommand]):
317
346
  def __init__(self, payment_service: PaymentServiceProtocol) -> None:
318
347
  self._payment_service = payment_service
319
348
  self._events: typing.List[Event] = []
@@ -322,7 +351,7 @@ class CreditCardPaymentHandler(CORRequestHandler[ProcessPaymentCommand, PaymentR
322
351
  def events(self) -> typing.List[Event]:
323
352
  return self._events
324
353
 
325
- async def handle(self, request: ProcessPaymentCommand) -> PaymentResult | None:
354
+ async def handle(self, request: ProcessPaymentCommand):
326
355
  if request.payment_method == "credit_card":
327
356
  # Process credit card payment
328
357
  result = await self._payment_service.process_credit_card(request)
@@ -332,7 +361,7 @@ class CreditCardPaymentHandler(CORRequestHandler[ProcessPaymentCommand, PaymentR
332
361
  # Pass to next handler
333
362
  return await self.next(request)
334
363
 
335
- class PayPalPaymentHandler(CORRequestHandler[ProcessPaymentCommand, PaymentResult]):
364
+ class PayPalPaymentHandler(CORRequestHandler[ProcessPaymentCommand]):
336
365
  def __init__(self, paypal_service: PayPalServiceProtocol) -> None:
337
366
  self._paypal_service = paypal_service
338
367
  self._events: typing.List[Event] = []
@@ -341,7 +370,7 @@ class PayPalPaymentHandler(CORRequestHandler[ProcessPaymentCommand, PaymentResul
341
370
  def events(self) -> typing.List[Event]:
342
371
  return self._events
343
372
 
344
- async def handle(self, request: ProcessPaymentCommand) -> PaymentResult | None:
373
+ async def handle(self, request: ProcessPaymentCommand):
345
374
  if request.payment_method == "paypal":
346
375
  # Process PayPal payment
347
376
  result = await self._paypal_service.process_payment(request)
@@ -360,7 +389,9 @@ def payment_mapper(mapper: cqrs.RequestMap) -> None:
360
389
  ```
361
390
 
362
391
  A complete example can be found in
363
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/cor/cor_request_handler.py)
392
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/cor/cor_request_handler.py).
393
+ The one-argument form is in
394
+ [`examples/cor/typed_cor_request_handler.py`](https://github.com/vadikko2/python-cqrs/blob/master/examples/cor/typed_cor_request_handler.py).
364
395
 
365
396
  #### Mermaid Diagram Generation
366
397
 
@@ -732,7 +763,11 @@ Complete example: [Saga Mermaid Diagrams](https://github.com/vadikko2/python-cqr
732
763
  During the handling of a command, `cqrs.NotificationEvent` events may be generated and then sent to the broker.
733
764
 
734
765
  ```python
735
- class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
766
+ class JoinMeetingCommand(cqrs.Request[None]):
767
+ user_id: str
768
+ meeting_id: str
769
+
770
+ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand]):
736
771
  def __init__(self):
737
772
  self._events = []
738
773
 
@@ -740,7 +775,7 @@ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
740
775
  def events(self):
741
776
  return self._events
742
777
 
743
- async def handle(self, request: JoinMeetingCommand) -> None:
778
+ async def handle(self, request: JoinMeetingCommand):
744
779
  print(f"User {request.user_id} joined meeting {request.meeting_id}")
745
780
  self._events.append(
746
781
  cqrs.NotificationEvent[UserJoinedNotificationPayload](
@@ -800,7 +835,11 @@ The package implements the [Transactional Outbox](https://microservices.io/patte
800
835
  pattern, which ensures that messages are produced to the broker according to the at-least-once semantics.
801
836
 
802
837
  ```python
803
- class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
838
+ class JoinMeetingCommand(cqrs.Request[None]):
839
+ user_id: str
840
+ meeting_id: str
841
+
842
+ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand]):
804
843
  def __init__(self, outbox: cqrs.OutboxedEventRepository):
805
844
  self.outbox = outbox
806
845
 
@@ -808,7 +847,7 @@ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
808
847
  def events(self):
809
848
  return []
810
849
 
811
- async def handle(self, request: JoinMeetingCommand) -> None:
850
+ async def handle(self, request: JoinMeetingCommand):
812
851
  print(f"User {request.user_id} joined meeting {request.meeting_id}")
813
852
  # Outbox repository is bound to a session (e.g. via DI request scope).
814
853
  # add() takes only the event; commit() persists the outbox and your changes.
@@ -845,12 +884,14 @@ A runnable FastAPI flow (route → command → transactional outbox → publishe
845
884
  > The Outbox table is named `outbox` by default. To rename it, or to attach the model to your own declarative
846
885
  > base, use `cqrs.rebind_outbox_model(OutboxModel, Base, table_name="my_outbox_table")` at startup — before
847
886
  > `create_all()`, before generating migrations and before the first query.
887
+
848
888
  > [!TIP]
849
889
  > `SqlAlchemyOutboxedEventRepository` works on **MySQL/MariaDB** and **PostgreSQL**: the outbox columns are
850
890
  > rendered with dialect-aware types (`BINARY(16)` on MySQL, `UUID`/`BYTEA` on PostgreSQL), with a portable
851
891
  > `BLOB` fallback for other dialects. Switching databases is a DSN change only — see the
852
892
  > [Database Support](https://mkdocs.python-cqrs.dev/outbox/databases/) docs for the DDL, the Alembic recipe and
853
893
  > how to register a native type for your own database.
894
+
854
895
  > [!TIP]
855
896
  > For Protobuf events, implement `proto()` / `from_proto()` and register a
856
897
  > `ProtobufEventSerializer` on an `OutboxedEventMap`. JSON stays the default —
@@ -915,7 +956,11 @@ To configure event handling, you need to implement a broker consumer on the side
915
956
  Below is an example of `Kafka event consuming` that can be used in the Presentation Layer.
916
957
 
917
958
  ```python
918
- class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
959
+ class JoinMeetingCommand(cqrs.Request[None]):
960
+ user_id: str
961
+ meeting_id: str
962
+
963
+ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand]):
919
964
  def __init__(self):
920
965
  self._events = []
921
966
 
@@ -923,7 +968,7 @@ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
923
968
  def events(self):
924
969
  return self._events
925
970
 
926
- async def handle(self, request: JoinMeetingCommand) -> None:
971
+ async def handle(self, request: JoinMeetingCommand):
927
972
  STORAGE[request.meeting_id].append(request.user_id)
928
973
  self._events.append(
929
974
  UserJoined(user_id=request.user_id, meeting_id=request.meeting_id),
@@ -22,14 +22,14 @@ dependencies = [
22
22
  "dependency-injector>=4.0",
23
23
  "orjson==3.*",
24
24
  "python-dotenv==1.*",
25
- "typing-extensions>=4.0",
25
+ "typing-extensions>=4.7",
26
26
  ]
27
27
  description = "Event-Driven Architecture Framework for Distributed Systems"
28
28
  maintainers = [{ name = "Vadim Kozyrevskiy", email = "vadikko2@mail.ru" }]
29
29
  name = "python-cqrs"
30
30
  readme = "README.md"
31
31
  requires-python = ">=3.10"
32
- version = "5.1.0"
32
+ version = "5.2.0"
33
33
 
34
34
  [project.optional-dependencies]
35
35
  aiobreaker = ["aiobreaker>=0.3.0"]
@@ -73,7 +73,7 @@ examples = [
73
73
  "faststream[kafka]==0.5.28",
74
74
  "faker>=37.12.0",
75
75
  "uvicorn==0.32.0",
76
- "aiohttp==3.14.3",
76
+ "aiohttp==3.14.4",
77
77
  "protobuf>=4.25.8",
78
78
  "aiosqlite>=0.20.0",
79
79
  "dishka>=1.0",
@@ -103,3 +103,6 @@ markers = [
103
103
 
104
104
  [tool.setuptools.packages.find]
105
105
  where = ["src"]
106
+
107
+ [tool.setuptools.package-data]
108
+ cqrs = ["py.typed"]