python-cqrs 5.0.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.0.0/src/python_cqrs.egg-info → python_cqrs-5.2.0}/PKG-INFO +88 -77
  2. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/README.md +80 -74
  3. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/pyproject.toml +10 -3
  4. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/__init__.py +85 -0
  5. python_cqrs-5.2.0/src/cqrs/adapters/__init__.py +10 -0
  6. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/adapters/circuit_breaker.py +6 -0
  7. {python_cqrs-5.0.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.0.0 → python_cqrs-5.2.0}/src/cqrs/bootstrap/events.py +13 -2
  10. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/bootstrap/requests.py +41 -0
  11. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/bootstrap/saga.py +14 -1
  12. {python_cqrs-5.0.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.0.0 → python_cqrs-5.2.0}/src/cqrs/container/dependency_injector.py +6 -0
  17. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/container/di.py +7 -0
  18. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/container/dishka.py +14 -1
  19. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/container/protocol.py +18 -0
  20. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/container/scope.py +29 -1
  21. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/dispatcher/__init__.py +9 -0
  22. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/events/__init__.py +9 -1
  23. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/events/event_emitter.py +14 -0
  24. {python_cqrs-5.0.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.0.0 → python_cqrs-5.2.0}/src/cqrs/handlers/__init__.py +11 -0
  27. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/handlers/cor.py +59 -1
  28. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/handlers/event.py +17 -0
  29. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/handlers/request.py +69 -1
  30. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/handlers/saga.py +30 -0
  31. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/mapping/events.py +13 -0
  32. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/mapping/outbox.py +40 -0
  33. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/mapping/requests.py +37 -0
  34. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/mediators/event.py +12 -0
  35. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/mediators/request.py +19 -10
  36. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/mediators/saga.py +16 -1
  37. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/mediators/streaming.py +23 -3
  38. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/mermaid/cor.py +13 -0
  39. {python_cqrs-5.0.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.0.0 → python_cqrs-5.2.0}/src/cqrs/message_brokers/amqp.py +9 -0
  42. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/message_brokers/kafka.py +9 -0
  43. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/message_brokers/producer.py +27 -1
  44. {python_cqrs-5.0.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.0.0 → python_cqrs-5.2.0}/src/cqrs/middlewares/base.py +29 -0
  47. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/middlewares/logging.py +7 -0
  48. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/models/event.py +54 -0
  49. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/models/pydantic.py +34 -4
  50. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/models/request.py +38 -4
  51. {python_cqrs-5.0.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.0.0 → python_cqrs-5.2.0}/src/cqrs/outbox/sqlalchemy.py +33 -5
  54. python_cqrs-5.2.0/src/cqrs/outbox/sqlmodel.py +200 -0
  55. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/requests/fallback.py +8 -0
  56. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/saga/fallback.py +15 -1
  57. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/saga/models.py +15 -0
  58. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/saga/recovery.py +18 -0
  59. {python_cqrs-5.0.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.0.0 → python_cqrs-5.2.0}/src/cqrs/saga/storage/memory.py +18 -1
  62. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/saga/storage/protocol.py +8 -0
  63. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/saga/storage/sqlalchemy.py +88 -45
  64. python_cqrs-5.2.0/src/cqrs/saga/storage/sqlmodel.py +215 -0
  65. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/serializers/json.py +8 -1
  66. {python_cqrs-5.0.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.2.0/src/cqrs/sqlmodel_base.py +45 -0
  69. {python_cqrs-5.0.0 → python_cqrs-5.2.0/src/python_cqrs.egg-info}/PKG-INFO +88 -77
  70. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/python_cqrs.egg-info/SOURCES.txt +4 -0
  71. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/python_cqrs.egg-info/requires.txt +8 -2
  72. python_cqrs-5.0.0/src/cqrs/bootstrap/__init__.py +0 -5
  73. python_cqrs-5.0.0/src/cqrs/compressors/protocol.py +0 -11
  74. python_cqrs-5.0.0/src/cqrs/compressors/zlib.py +0 -9
  75. python_cqrs-5.0.0/src/cqrs/container/__init__.py +0 -18
  76. python_cqrs-5.0.0/src/cqrs/generic_utils.py +0 -43
  77. python_cqrs-5.0.0/src/cqrs/message_brokers/__init__.py +0 -6
  78. python_cqrs-5.0.0/src/cqrs/middlewares/__init__.py +0 -3
  79. python_cqrs-5.0.0/src/cqrs/outbox/repository.py +0 -101
  80. python_cqrs-5.0.0/src/cqrs/saga/storage/__init__.py +0 -11
  81. python_cqrs-5.0.0/src/cqrs/serializers/protocol.py +0 -21
  82. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/LICENSE +0 -0
  83. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/setup.cfg +0 -0
  84. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/_dataclass_utils.py +0 -0
  85. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/adapters/amqp.py +0 -0
  86. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/adapters/protocol.py +0 -0
  87. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/compressors/__init__.py +0 -0
  88. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/deserializers/__init__.py +0 -0
  89. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/deserializers/exceptions.py +0 -0
  90. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/deserializers/json.py +0 -0
  91. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/deserializers/protobuf.py +0 -0
  92. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/dispatcher/event.py +0 -0
  93. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/dispatcher/exceptions.py +0 -0
  94. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/dispatcher/models.py +0 -0
  95. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/dispatcher/request.py +0 -0
  96. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/dispatcher/saga.py +0 -0
  97. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/dispatcher/streaming.py +0 -0
  98. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/events/event_processor.py +0 -0
  99. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/mapping/__init__.py +0 -0
  100. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/mediators/__init__.py +0 -0
  101. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/mediators/_common.py +0 -0
  102. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/mermaid/__init__.py +0 -0
  103. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/message_brokers/devnull.py +0 -0
  104. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/models/__init__.py +0 -0
  105. {python_cqrs-5.0.0/src/cqrs/adapters → python_cqrs-5.2.0/src/cqrs/outbox}/__init__.py +0 -0
  106. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/outbox/mock.py +0 -0
  107. /python_cqrs-5.0.0/src/cqrs/outbox/__init__.py → /python_cqrs-5.2.0/src/cqrs/py.typed +0 -0
  108. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/requests/__init__.py +0 -0
  109. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/saga/__init__.py +0 -0
  110. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/saga/circuit_breaker.py +0 -0
  111. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/saga/compensation.py +0 -0
  112. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/saga/execution.py +0 -0
  113. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/saga/storage/enums.py +0 -0
  114. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/saga/storage/models.py +0 -0
  115. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/saga/validation.py +0 -0
  116. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/serializers/__init__.py +0 -0
  117. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/serializers/default.py +0 -0
  118. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/sqlalchemy_types.py +0 -0
  119. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/cqrs/types.py +0 -0
  120. {python_cqrs-5.0.0 → python_cqrs-5.2.0}/src/python_cqrs.egg-info/dependency_links.txt +0 -0
  121. {python_cqrs-5.0.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.0.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,13 +22,16 @@ 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
29
29
  Requires-Dist: pydantic==2.*; extra == "pydantic"
30
30
  Provides-Extra: sqlalchemy
31
31
  Requires-Dist: sqlalchemy[asyncio]==2.0.*; extra == "sqlalchemy"
32
+ Provides-Extra: sqlmodel
33
+ Requires-Dist: sqlmodel>=0.0.22; extra == "sqlmodel"
34
+ Requires-Dist: sqlalchemy[asyncio]==2.0.*; extra == "sqlmodel"
32
35
  Provides-Extra: dev
33
36
  Requires-Dist: pycln==2.5.0; extra == "dev"
34
37
  Requires-Dist: pre-commit==3.8.0; extra == "dev"
@@ -54,12 +57,14 @@ Requires-Dist: dishka>=1.0; extra == "dev"
54
57
  Requires-Dist: protobuf>=4.25.8; extra == "dev"
55
58
  Requires-Dist: pydantic==2.*; extra == "dev"
56
59
  Requires-Dist: sqlalchemy[asyncio]==2.0.*; extra == "dev"
60
+ Requires-Dist: sqlmodel>=0.0.22; extra == "dev"
61
+ Requires-Dist: aiosqlite>=0.20.0; extra == "dev"
57
62
  Provides-Extra: examples
58
63
  Requires-Dist: fastapi==0.109.*; extra == "examples"
59
64
  Requires-Dist: faststream[kafka]==0.5.28; extra == "examples"
60
65
  Requires-Dist: faker>=37.12.0; extra == "examples"
61
66
  Requires-Dist: uvicorn==0.32.0; extra == "examples"
62
- Requires-Dist: aiohttp==3.14.3; extra == "examples"
67
+ Requires-Dist: aiohttp==3.14.4; extra == "examples"
63
68
  Requires-Dist: protobuf>=4.25.8; extra == "examples"
64
69
  Requires-Dist: aiosqlite>=0.20.0; extra == "examples"
65
70
  Requires-Dist: dishka>=1.0; extra == "examples"
@@ -98,32 +103,17 @@ Dynamic: license-file
98
103
  <a href="https://codecov.io/gh/vadikko2/python-cqrs">
99
104
  <img src="https://img.shields.io/codecov/c/github/vadikko2/python-cqrs?logo=codecov&logoColor=white" alt="Coverage">
100
105
  </a>
101
- <a href="https://codspeed.io/vadikko2/python-cqrs?utm_source=badge">
102
- <img src="https://img.shields.io/endpoint?url=https://codspeed.io/badge.json" alt="CodSpeed">
103
- </a>
104
- <a href="https://mkdocs.python-cqrs.dev/">
105
- <img src="https://img.shields.io/badge/docs-mkdocs-blue?logo=readthedocs" alt="Documentation">
106
- </a>
107
- <a href="https://deepwiki.com/vadikko2/python-cqrs">
108
- <img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki">
109
- </a>
110
106
  <a href="https://github.com/mjhea0/awesome-fastapi#developer-tools">
111
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">
112
108
  </a>
113
109
  </p>
114
110
  </div>
115
111
 
116
- > [!WARNING]
117
- > **Breaking Changes in v5.0.0**
118
- >
119
- > Starting with version 5.0.0, Pydantic support will become optional. The default implementations of `Request`, `Response`, `DomainEvent`, and `NotificationEvent` will be migrated to dataclasses-based implementations.
120
-
121
112
  ## Table of Contents
122
113
 
123
114
  - [Overview](#overview)
124
115
  - [When NOT to use python-cqrs](#when-not-to-use-python-cqrs)
125
116
  - [Installation](#installation)
126
- - [Version Support](#version-support)
127
117
  - [Quick Start](#quick-start)
128
118
  - [Request and Response Types](#request-and-response-types)
129
119
  - [Request Handlers](#request-handlers)
@@ -148,9 +138,6 @@ Dynamic: license-file
148
138
 
149
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.
150
140
 
151
- This package is a fork of the [diator](https://github.com/akhundMurad/diator)
152
- project ([documentation](https://akhundmurad.github.io/diator/)) with several enhancements, ordered by importance:
153
-
154
141
  **Core framework**
155
142
 
156
143
  1. Redesigned the event and request mapping mechanism to handlers;
@@ -166,7 +153,7 @@ project ([documentation](https://akhundmurad.github.io/diator/)) with several en
166
153
 
167
154
  - **Typing:** Pydantic [v2.*](https://docs.pydantic.dev/2.8/) and `IRequest`/`IResponse` interfaces — use Pydantic-based, dataclass-based, or custom Request/Response implementations.
168
155
  - **Broker:** Kafka via [aiokafka](https://github.com/aio-libs/aiokafka).
169
- - **Storage:** SQLAlchemy-based Outbox and Saga storage on MySQL/MariaDB and PostgreSQL.
156
+ - **Storage:** SQLAlchemy and SQLModel outbox and saga storage on MySQL/MariaDB and PostgreSQL.
170
157
  - **Integration:** Ready for integration with FastAPI and FastStream.
171
158
  - **Documentation:** Built-in Mermaid diagram generation (Sequence and Class diagrams).
172
159
  - **Protobuf:** Interface-level support for converting Notification events to Protobuf and back.
@@ -189,11 +176,12 @@ Do not add a Saga for a single `UPDATE` in one service, and do not use a Saga as
189
176
  pip install python-cqrs
190
177
  ```
191
178
 
192
- Optional extras (5.0+ — pydantic and SQLAlchemy are no longer core dependencies):
179
+ Optional extras (5.0+ — pydantic and SQLAlchemy are no longer core dependencies; SQLModel storage is 5.1+):
193
180
 
194
181
  ```bash
195
182
  pip install "python-cqrs[pydantic]" # PydanticRequest / Pydantic* events
196
183
  pip install "python-cqrs[sqlalchemy]" # Outbox / Saga SQLAlchemy storage
184
+ pip install "python-cqrs[sqlmodel]" # Outbox / Saga SQLModel storage
197
185
  pip install "python-cqrs[kafka]" # Kafka broker (aiokafka)
198
186
  pip install "python-cqrs[examples]" # FastAPI, FastStream, uvicorn, etc.
199
187
  pip install "python-cqrs[aiobreaker]" # Circuit breaker for saga fallbacks
@@ -203,37 +191,8 @@ Default `Request` / `Response` / `Event` / `DomainEvent` / `NotificationEvent` a
203
191
  are dataclass-based (`DC*`). Use `Pydantic*` types with the pydantic extra when you
204
192
  need validation.
205
193
 
206
- ### Import paths (5.0+)
207
-
208
- | Area | Import |
209
- |------|--------|
210
- | Bootstrap | `from cqrs.bootstrap import requests, events, saga` |
211
- | Mediators | `from cqrs.mediators import RequestMediator, SagaMediator, …` |
212
- | Handlers | `from cqrs.handlers import RequestHandler, EventHandler, …` |
213
- | Models | `from cqrs.models import Request, Event, …` / `cqrs.models.pydantic` |
214
- | Maps | `from cqrs.mapping import RequestMap, EventMap, OutboxedEventMap` |
215
- | Mermaid | `from cqrs.mermaid import CoRMermaid, SagaMermaid` |
216
- | Outbox producer | `from cqrs.message_brokers.producer import EventProducer` |
217
-
218
- `SagaMediator.execute(...)` replaces the former `SagaMediator.stream(...)`
219
- (StreamingRequestMediator.stream is unchanged).
220
-
221
- ## Version Support
222
-
223
- | Line | Git branch | What changes |
224
- |------|------------|--------------|
225
- | **4.x** | `4.x` | Bug fixes and security only (~12 months after 5.0.0) |
226
- | **5.x** | `master` | Features and breaking changes |
227
-
228
- Stay on 4.x until you migrate:
194
+ Coding agents in an application can follow [`templates/AGENTS.md`](templates/AGENTS.md). Copy that file into the application repository.
229
195
 
230
- ```bash
231
- pip install "python-cqrs>=4,<5"
232
- ```
233
-
234
- Docs: [4.x](https://mkdocs.python-cqrs.dev/4.0/) and [5.x / latest](https://mkdocs.python-cqrs.dev/latest/) (version switcher after mike deploy). See [SECURITY.md](SECURITY.md) for the support window and vulnerability reporting, and [CONTRIBUTING.md](CONTRIBUTING.md#8-maintaining--fixing-4x) for how to contribute 4.x bugfixes.
235
-
236
- A package-level EOL `DeprecationWarning` toward 5.x will be added on the **first 4.x patch after 5.0.0** — not before.
237
196
 
238
197
  ## Quick Start
239
198
 
@@ -244,12 +203,12 @@ import di
244
203
  import cqrs
245
204
  from cqrs.bootstrap import requests as bootstrap
246
205
 
247
- class CreateOrderCommand(cqrs.Request):
206
+ class CreateOrderCommand(cqrs.Request[None]):
248
207
  order_id: str
249
208
  amount: float
250
209
 
251
- class CreateOrderHandler(cqrs.RequestHandler[CreateOrderCommand, None]):
252
- async def handle(self, request: CreateOrderCommand) -> None:
210
+ class CreateOrderHandler(cqrs.RequestHandler[CreateOrderCommand]):
211
+ async def handle(self, request: CreateOrderCommand):
253
212
  print(f"Order {request.order_id}, amount {request.amount}")
254
213
 
255
214
  def commands_mapper(mapper: cqrs.RequestMap) -> None:
@@ -326,14 +285,20 @@ Request handlers can be divided into two main types:
326
285
  Command Handler executes the received command. The logic of the handler may include, for example, modifying the state of
327
286
  the domain model.
328
287
  As a result of executing the command, an event may be produced to the broker.
288
+
329
289
  > [!TIP]
330
290
  > By default, the command handler does not return any result, but it is not mandatory.
331
291
 
332
292
  ```python
293
+ import cqrs
333
294
  from cqrs.handlers.request import RequestHandler
334
295
  from cqrs.models.event import Event
335
296
 
336
- 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]):
337
302
 
338
303
  def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
339
304
  self._meetings_api = meetings_api
@@ -343,25 +308,36 @@ class JoinMeetingCommandHandler(RequestHandler[JoinMeetingCommand, None]):
343
308
  def events(self) -> typing.List[events.Event]:
344
309
  return self._events
345
310
 
346
- async def handle(self, request: JoinMeetingCommand) -> None:
311
+ async def handle(self, request: JoinMeetingCommand):
347
312
  await self._meetings_api.join_user(request.user_id, request.meeting_id)
348
313
  ```
349
314
 
350
315
  A complete example can be found in
351
- 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).
352
319
 
353
320
  ### Query handler
354
321
 
355
322
  Query Handler returns a representation of the requested data, for example, from
356
323
  the [read model](https://radekmaziarka.pl/2018/01/08/cqrs-third-step-simple-read-model/#simple-read-model---to-the-rescue).
324
+
357
325
  > [!TIP]
358
326
  > The read model can be constructed based on domain events produced by the `Command Handler`.
359
327
 
360
328
  ```python
329
+ import cqrs
361
330
  from cqrs.handlers.request import RequestHandler
362
331
  from cqrs.models.event import Event
363
332
 
364
- 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]):
365
341
 
366
342
  def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
367
343
  self._meetings_api = meetings_api
@@ -371,14 +347,16 @@ class ReadMeetingQueryHandler(RequestHandler[ReadMeetingQuery, ReadMeetingQueryR
371
347
  def events(self) -> typing.List[events.Event]:
372
348
  return self._events
373
349
 
374
- async def handle(self, request: ReadMeetingQuery) -> ReadMeetingQueryResult:
350
+ async def handle(self, request: ReadMeetingQuery):
375
351
  link = await self._meetings_api.get_link(request.meeting_id)
376
352
  return ReadMeetingQueryResult(link=link, meeting_id=request.meeting_id)
377
353
 
378
354
  ```
379
355
 
380
356
  A complete example can be found in
381
- 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).
382
360
 
383
361
  ### Streaming Request Handler
384
362
 
@@ -389,11 +367,18 @@ real-time progress updates.
389
367
  `StreamingRequestHandler` works with `StreamingRequestMediator` that streams results to clients in real-time.
390
368
 
391
369
  ```python
392
- import typing
370
+ import cqrs
393
371
  from cqrs.handlers.request import StreamingRequestHandler
394
372
  from cqrs.models.event import Event
395
373
 
396
- 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]):
397
382
  def __init__(self):
398
383
  self._events: list[Event] = []
399
384
 
@@ -404,7 +389,7 @@ class ProcessFilesCommandHandler(StreamingRequestHandler[ProcessFilesCommand, Fi
404
389
  def clear_events(self) -> None:
405
390
  self._events.clear()
406
391
 
407
- async def handle(self, request: ProcessFilesCommand) -> typing.AsyncIterator[FileProcessedResult]:
392
+ async def handle(self, request: ProcessFilesCommand):
408
393
  for file_id in request.file_ids:
409
394
  # Process file
410
395
  result = FileProcessedResult(file_id=file_id, status="completed", ...)
@@ -414,7 +399,9 @@ class ProcessFilesCommandHandler(StreamingRequestHandler[ProcessFilesCommand, Fi
414
399
  ```
415
400
 
416
401
  A complete example can be found in
417
- 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).
418
405
 
419
406
  ### Chain of Responsibility Request Handler
420
407
 
@@ -427,10 +414,18 @@ when a handler successfully processes the request or when all handlers have been
427
414
 
428
415
  ```python
429
416
  import typing
417
+ import cqrs
430
418
  from cqrs.handlers.cor import CORRequestHandler
431
419
  from cqrs.models.event import Event
432
420
 
433
- 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]):
434
429
  def __init__(self, payment_service: PaymentServiceProtocol) -> None:
435
430
  self._payment_service = payment_service
436
431
  self._events: typing.List[Event] = []
@@ -439,7 +434,7 @@ class CreditCardPaymentHandler(CORRequestHandler[ProcessPaymentCommand, PaymentR
439
434
  def events(self) -> typing.List[Event]:
440
435
  return self._events
441
436
 
442
- async def handle(self, request: ProcessPaymentCommand) -> PaymentResult | None:
437
+ async def handle(self, request: ProcessPaymentCommand):
443
438
  if request.payment_method == "credit_card":
444
439
  # Process credit card payment
445
440
  result = await self._payment_service.process_credit_card(request)
@@ -449,7 +444,7 @@ class CreditCardPaymentHandler(CORRequestHandler[ProcessPaymentCommand, PaymentR
449
444
  # Pass to next handler
450
445
  return await self.next(request)
451
446
 
452
- class PayPalPaymentHandler(CORRequestHandler[ProcessPaymentCommand, PaymentResult]):
447
+ class PayPalPaymentHandler(CORRequestHandler[ProcessPaymentCommand]):
453
448
  def __init__(self, paypal_service: PayPalServiceProtocol) -> None:
454
449
  self._paypal_service = paypal_service
455
450
  self._events: typing.List[Event] = []
@@ -458,7 +453,7 @@ class PayPalPaymentHandler(CORRequestHandler[ProcessPaymentCommand, PaymentResul
458
453
  def events(self) -> typing.List[Event]:
459
454
  return self._events
460
455
 
461
- async def handle(self, request: ProcessPaymentCommand) -> PaymentResult | None:
456
+ async def handle(self, request: ProcessPaymentCommand):
462
457
  if request.payment_method == "paypal":
463
458
  # Process PayPal payment
464
459
  result = await self._paypal_service.process_payment(request)
@@ -477,7 +472,9 @@ def payment_mapper(mapper: cqrs.RequestMap) -> None:
477
472
  ```
478
473
 
479
474
  A complete example can be found in
480
- 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).
481
478
 
482
479
  #### Mermaid Diagram Generation
483
480
 
@@ -849,7 +846,11 @@ Complete example: [Saga Mermaid Diagrams](https://github.com/vadikko2/python-cqr
849
846
  During the handling of a command, `cqrs.NotificationEvent` events may be generated and then sent to the broker.
850
847
 
851
848
  ```python
852
- 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]):
853
854
  def __init__(self):
854
855
  self._events = []
855
856
 
@@ -857,7 +858,7 @@ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
857
858
  def events(self):
858
859
  return self._events
859
860
 
860
- async def handle(self, request: JoinMeetingCommand) -> None:
861
+ async def handle(self, request: JoinMeetingCommand):
861
862
  print(f"User {request.user_id} joined meeting {request.meeting_id}")
862
863
  self._events.append(
863
864
  cqrs.NotificationEvent[UserJoinedNotificationPayload](
@@ -917,7 +918,11 @@ The package implements the [Transactional Outbox](https://microservices.io/patte
917
918
  pattern, which ensures that messages are produced to the broker according to the at-least-once semantics.
918
919
 
919
920
  ```python
920
- 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]):
921
926
  def __init__(self, outbox: cqrs.OutboxedEventRepository):
922
927
  self.outbox = outbox
923
928
 
@@ -925,7 +930,7 @@ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
925
930
  def events(self):
926
931
  return []
927
932
 
928
- async def handle(self, request: JoinMeetingCommand) -> None:
933
+ async def handle(self, request: JoinMeetingCommand):
929
934
  print(f"User {request.user_id} joined meeting {request.meeting_id}")
930
935
  # Outbox repository is bound to a session (e.g. via DI request scope).
931
936
  # add() takes only the event; commit() persists the outbox and your changes.
@@ -962,12 +967,14 @@ A runnable FastAPI flow (route → command → transactional outbox → publishe
962
967
  > The Outbox table is named `outbox` by default. To rename it, or to attach the model to your own declarative
963
968
  > base, use `cqrs.rebind_outbox_model(OutboxModel, Base, table_name="my_outbox_table")` at startup — before
964
969
  > `create_all()`, before generating migrations and before the first query.
970
+
965
971
  > [!TIP]
966
972
  > `SqlAlchemyOutboxedEventRepository` works on **MySQL/MariaDB** and **PostgreSQL**: the outbox columns are
967
973
  > rendered with dialect-aware types (`BINARY(16)` on MySQL, `UUID`/`BYTEA` on PostgreSQL), with a portable
968
974
  > `BLOB` fallback for other dialects. Switching databases is a DSN change only — see the
969
975
  > [Database Support](https://mkdocs.python-cqrs.dev/outbox/databases/) docs for the DDL, the Alembic recipe and
970
976
  > how to register a native type for your own database.
977
+
971
978
  > [!TIP]
972
979
  > For Protobuf events, implement `proto()` / `from_proto()` and register a
973
980
  > `ProtobufEventSerializer` on an `OutboxedEventMap`. JSON stays the default —
@@ -1032,7 +1039,11 @@ To configure event handling, you need to implement a broker consumer on the side
1032
1039
  Below is an example of `Kafka event consuming` that can be used in the Presentation Layer.
1033
1040
 
1034
1041
  ```python
1035
- 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]):
1036
1047
  def __init__(self):
1037
1048
  self._events = []
1038
1049
 
@@ -1040,7 +1051,7 @@ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
1040
1051
  def events(self):
1041
1052
  return self._events
1042
1053
 
1043
- async def handle(self, request: JoinMeetingCommand) -> None:
1054
+ async def handle(self, request: JoinMeetingCommand):
1044
1055
  STORAGE[request.meeting_id].append(request.user_id)
1045
1056
  self._events.append(
1046
1057
  UserJoined(user_id=request.user_id, meeting_id=request.meeting_id),