python-cqrs 5.0.0__tar.gz → 5.1.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 (110) hide show
  1. {python_cqrs-5.0.0/src/python_cqrs.egg-info → python_cqrs-5.1.0}/PKG-INFO +9 -43
  2. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/README.md +3 -42
  3. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/pyproject.toml +5 -1
  4. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/__init__.py +14 -0
  5. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/outbox/sqlalchemy.py +7 -5
  6. python_cqrs-5.1.0/src/cqrs/outbox/sqlmodel.py +189 -0
  7. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/saga/storage/sqlalchemy.py +72 -45
  8. python_cqrs-5.1.0/src/cqrs/saga/storage/sqlmodel.py +203 -0
  9. python_cqrs-5.1.0/src/cqrs/sqlmodel_base.py +45 -0
  10. {python_cqrs-5.0.0 → python_cqrs-5.1.0/src/python_cqrs.egg-info}/PKG-INFO +9 -43
  11. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/python_cqrs.egg-info/SOURCES.txt +3 -0
  12. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/python_cqrs.egg-info/requires.txt +6 -0
  13. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/LICENSE +0 -0
  14. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/setup.cfg +0 -0
  15. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/_dataclass_utils.py +0 -0
  16. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/adapters/__init__.py +0 -0
  17. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/adapters/amqp.py +0 -0
  18. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/adapters/circuit_breaker.py +0 -0
  19. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/adapters/kafka.py +0 -0
  20. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/adapters/protocol.py +0 -0
  21. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/bootstrap/__init__.py +0 -0
  22. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/bootstrap/events.py +0 -0
  23. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/bootstrap/requests.py +0 -0
  24. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/bootstrap/saga.py +0 -0
  25. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/circuit_breaker.py +0 -0
  26. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/compressors/__init__.py +0 -0
  27. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/compressors/protocol.py +0 -0
  28. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/compressors/zlib.py +0 -0
  29. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/container/__init__.py +0 -0
  30. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/container/dependency_injector.py +0 -0
  31. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/container/di.py +0 -0
  32. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/container/dishka.py +0 -0
  33. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/container/protocol.py +0 -0
  34. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/container/scope.py +0 -0
  35. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/deserializers/__init__.py +0 -0
  36. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/deserializers/exceptions.py +0 -0
  37. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/deserializers/json.py +0 -0
  38. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/deserializers/protobuf.py +0 -0
  39. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/dispatcher/__init__.py +0 -0
  40. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/dispatcher/event.py +0 -0
  41. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/dispatcher/exceptions.py +0 -0
  42. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/dispatcher/models.py +0 -0
  43. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/dispatcher/request.py +0 -0
  44. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/dispatcher/saga.py +0 -0
  45. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/dispatcher/streaming.py +0 -0
  46. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/events/__init__.py +0 -0
  47. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/events/event_emitter.py +0 -0
  48. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/events/event_processor.py +0 -0
  49. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/events/fallback.py +0 -0
  50. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/generic_utils.py +0 -0
  51. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/handlers/__init__.py +0 -0
  52. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/handlers/cor.py +0 -0
  53. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/handlers/event.py +0 -0
  54. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/handlers/request.py +0 -0
  55. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/handlers/saga.py +0 -0
  56. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/mapping/__init__.py +0 -0
  57. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/mapping/events.py +0 -0
  58. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/mapping/outbox.py +0 -0
  59. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/mapping/requests.py +0 -0
  60. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/mediators/__init__.py +0 -0
  61. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/mediators/_common.py +0 -0
  62. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/mediators/event.py +0 -0
  63. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/mediators/request.py +0 -0
  64. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/mediators/saga.py +0 -0
  65. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/mediators/streaming.py +0 -0
  66. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/mermaid/__init__.py +0 -0
  67. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/mermaid/cor.py +0 -0
  68. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/mermaid/saga.py +0 -0
  69. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/message_brokers/__init__.py +0 -0
  70. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/message_brokers/amqp.py +0 -0
  71. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/message_brokers/devnull.py +0 -0
  72. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/message_brokers/kafka.py +0 -0
  73. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/message_brokers/producer.py +0 -0
  74. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/message_brokers/protocol.py +0 -0
  75. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/middlewares/__init__.py +0 -0
  76. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/middlewares/base.py +0 -0
  77. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/middlewares/logging.py +0 -0
  78. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/models/__init__.py +0 -0
  79. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/models/event.py +0 -0
  80. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/models/pydantic.py +0 -0
  81. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/models/request.py +0 -0
  82. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/models/response.py +0 -0
  83. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/outbox/__init__.py +0 -0
  84. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/outbox/mock.py +0 -0
  85. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/outbox/repository.py +0 -0
  86. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/requests/__init__.py +0 -0
  87. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/requests/fallback.py +0 -0
  88. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/saga/__init__.py +0 -0
  89. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/saga/circuit_breaker.py +0 -0
  90. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/saga/compensation.py +0 -0
  91. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/saga/execution.py +0 -0
  92. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/saga/fallback.py +0 -0
  93. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/saga/models.py +0 -0
  94. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/saga/recovery.py +0 -0
  95. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/saga/saga.py +0 -0
  96. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/saga/storage/__init__.py +0 -0
  97. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/saga/storage/enums.py +0 -0
  98. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/saga/storage/memory.py +0 -0
  99. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/saga/storage/models.py +0 -0
  100. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/saga/storage/protocol.py +0 -0
  101. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/saga/validation.py +0 -0
  102. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/serializers/__init__.py +0 -0
  103. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/serializers/default.py +0 -0
  104. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/serializers/json.py +0 -0
  105. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/serializers/protobuf.py +0 -0
  106. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/serializers/protocol.py +0 -0
  107. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/sqlalchemy_types.py +0 -0
  108. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/cqrs/types.py +0 -0
  109. {python_cqrs-5.0.0 → python_cqrs-5.1.0}/src/python_cqrs.egg-info/dependency_links.txt +0 -0
  110. {python_cqrs-5.0.0 → python_cqrs-5.1.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.1.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>
@@ -29,6 +29,9 @@ 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,6 +57,8 @@ 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"
@@ -98,9 +103,6 @@ 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
106
  <a href="https://mkdocs.python-cqrs.dev/">
105
107
  <img src="https://img.shields.io/badge/docs-mkdocs-blue?logo=readthedocs" alt="Documentation">
106
108
  </a>
@@ -113,17 +115,11 @@ Dynamic: license-file
113
115
  </p>
114
116
  </div>
115
117
 
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
118
  ## Table of Contents
122
119
 
123
120
  - [Overview](#overview)
124
121
  - [When NOT to use python-cqrs](#when-not-to-use-python-cqrs)
125
122
  - [Installation](#installation)
126
- - [Version Support](#version-support)
127
123
  - [Quick Start](#quick-start)
128
124
  - [Request and Response Types](#request-and-response-types)
129
125
  - [Request Handlers](#request-handlers)
@@ -166,7 +162,7 @@ project ([documentation](https://akhundmurad.github.io/diator/)) with several en
166
162
 
167
163
  - **Typing:** Pydantic [v2.*](https://docs.pydantic.dev/2.8/) and `IRequest`/`IResponse` interfaces — use Pydantic-based, dataclass-based, or custom Request/Response implementations.
168
164
  - **Broker:** Kafka via [aiokafka](https://github.com/aio-libs/aiokafka).
169
- - **Storage:** SQLAlchemy-based Outbox and Saga storage on MySQL/MariaDB and PostgreSQL.
165
+ - **Storage:** SQLAlchemy and SQLModel outbox and saga storage on MySQL/MariaDB and PostgreSQL.
170
166
  - **Integration:** Ready for integration with FastAPI and FastStream.
171
167
  - **Documentation:** Built-in Mermaid diagram generation (Sequence and Class diagrams).
172
168
  - **Protobuf:** Interface-level support for converting Notification events to Protobuf and back.
@@ -189,11 +185,12 @@ Do not add a Saga for a single `UPDATE` in one service, and do not use a Saga as
189
185
  pip install python-cqrs
190
186
  ```
191
187
 
192
- Optional extras (5.0+ — pydantic and SQLAlchemy are no longer core dependencies):
188
+ Optional extras (5.0+ — pydantic and SQLAlchemy are no longer core dependencies; SQLModel storage is 5.1+):
193
189
 
194
190
  ```bash
195
191
  pip install "python-cqrs[pydantic]" # PydanticRequest / Pydantic* events
196
192
  pip install "python-cqrs[sqlalchemy]" # Outbox / Saga SQLAlchemy storage
193
+ pip install "python-cqrs[sqlmodel]" # Outbox / Saga SQLModel storage
197
194
  pip install "python-cqrs[kafka]" # Kafka broker (aiokafka)
198
195
  pip install "python-cqrs[examples]" # FastAPI, FastStream, uvicorn, etc.
199
196
  pip install "python-cqrs[aiobreaker]" # Circuit breaker for saga fallbacks
@@ -203,37 +200,6 @@ Default `Request` / `Response` / `Event` / `DomainEvent` / `NotificationEvent` a
203
200
  are dataclass-based (`DC*`). Use `Pydantic*` types with the pydantic extra when you
204
201
  need validation.
205
202
 
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:
229
-
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
203
 
238
204
  ## Quick Start
239
205
 
@@ -20,9 +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://codspeed.io/vadikko2/python-cqrs?utm_source=badge">
24
- <img src="https://img.shields.io/endpoint?url=https://codspeed.io/badge.json" alt="CodSpeed">
25
- </a>
26
23
  <a href="https://mkdocs.python-cqrs.dev/">
27
24
  <img src="https://img.shields.io/badge/docs-mkdocs-blue?logo=readthedocs" alt="Documentation">
28
25
  </a>
@@ -35,17 +32,11 @@
35
32
  </p>
36
33
  </div>
37
34
 
38
- > [!WARNING]
39
- > **Breaking Changes in v5.0.0**
40
- >
41
- > 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.
42
-
43
35
  ## Table of Contents
44
36
 
45
37
  - [Overview](#overview)
46
38
  - [When NOT to use python-cqrs](#when-not-to-use-python-cqrs)
47
39
  - [Installation](#installation)
48
- - [Version Support](#version-support)
49
40
  - [Quick Start](#quick-start)
50
41
  - [Request and Response Types](#request-and-response-types)
51
42
  - [Request Handlers](#request-handlers)
@@ -88,7 +79,7 @@ project ([documentation](https://akhundmurad.github.io/diator/)) with several en
88
79
 
89
80
  - **Typing:** Pydantic [v2.*](https://docs.pydantic.dev/2.8/) and `IRequest`/`IResponse` interfaces — use Pydantic-based, dataclass-based, or custom Request/Response implementations.
90
81
  - **Broker:** Kafka via [aiokafka](https://github.com/aio-libs/aiokafka).
91
- - **Storage:** SQLAlchemy-based Outbox and Saga storage on MySQL/MariaDB and PostgreSQL.
82
+ - **Storage:** SQLAlchemy and SQLModel outbox and saga storage on MySQL/MariaDB and PostgreSQL.
92
83
  - **Integration:** Ready for integration with FastAPI and FastStream.
93
84
  - **Documentation:** Built-in Mermaid diagram generation (Sequence and Class diagrams).
94
85
  - **Protobuf:** Interface-level support for converting Notification events to Protobuf and back.
@@ -111,11 +102,12 @@ Do not add a Saga for a single `UPDATE` in one service, and do not use a Saga as
111
102
  pip install python-cqrs
112
103
  ```
113
104
 
114
- Optional extras (5.0+ — pydantic and SQLAlchemy are no longer core dependencies):
105
+ Optional extras (5.0+ — pydantic and SQLAlchemy are no longer core dependencies; SQLModel storage is 5.1+):
115
106
 
116
107
  ```bash
117
108
  pip install "python-cqrs[pydantic]" # PydanticRequest / Pydantic* events
118
109
  pip install "python-cqrs[sqlalchemy]" # Outbox / Saga SQLAlchemy storage
110
+ pip install "python-cqrs[sqlmodel]" # Outbox / Saga SQLModel storage
119
111
  pip install "python-cqrs[kafka]" # Kafka broker (aiokafka)
120
112
  pip install "python-cqrs[examples]" # FastAPI, FastStream, uvicorn, etc.
121
113
  pip install "python-cqrs[aiobreaker]" # Circuit breaker for saga fallbacks
@@ -125,37 +117,6 @@ Default `Request` / `Response` / `Event` / `DomainEvent` / `NotificationEvent` a
125
117
  are dataclass-based (`DC*`). Use `Pydantic*` types with the pydantic extra when you
126
118
  need validation.
127
119
 
128
- ### Import paths (5.0+)
129
-
130
- | Area | Import |
131
- |------|--------|
132
- | Bootstrap | `from cqrs.bootstrap import requests, events, saga` |
133
- | Mediators | `from cqrs.mediators import RequestMediator, SagaMediator, …` |
134
- | Handlers | `from cqrs.handlers import RequestHandler, EventHandler, …` |
135
- | Models | `from cqrs.models import Request, Event, …` / `cqrs.models.pydantic` |
136
- | Maps | `from cqrs.mapping import RequestMap, EventMap, OutboxedEventMap` |
137
- | Mermaid | `from cqrs.mermaid import CoRMermaid, SagaMermaid` |
138
- | Outbox producer | `from cqrs.message_brokers.producer import EventProducer` |
139
-
140
- `SagaMediator.execute(...)` replaces the former `SagaMediator.stream(...)`
141
- (StreamingRequestMediator.stream is unchanged).
142
-
143
- ## Version Support
144
-
145
- | Line | Git branch | What changes |
146
- |------|------------|--------------|
147
- | **4.x** | `4.x` | Bug fixes and security only (~12 months after 5.0.0) |
148
- | **5.x** | `master` | Features and breaking changes |
149
-
150
- Stay on 4.x until you migrate:
151
-
152
- ```bash
153
- pip install "python-cqrs>=4,<5"
154
- ```
155
-
156
- 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.
157
-
158
- A package-level EOL `DeprecationWarning` toward 5.x will be added on the **first 4.x patch after 5.0.0** — not before.
159
120
 
160
121
  ## Quick Start
161
122
 
@@ -29,12 +29,13 @@ 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.0.0"
32
+ version = "5.1.0"
33
33
 
34
34
  [project.optional-dependencies]
35
35
  aiobreaker = ["aiobreaker>=0.3.0"]
36
36
  pydantic = ["pydantic==2.*"]
37
37
  sqlalchemy = ["sqlalchemy[asyncio]==2.0.*"]
38
+ sqlmodel = ["sqlmodel>=0.0.22", "sqlalchemy[asyncio]==2.0.*"]
38
39
  dev = [
39
40
  # Develope tools
40
41
  "pycln==2.5.0",
@@ -64,6 +65,8 @@ dev = [
64
65
  # Full suite extras
65
66
  "pydantic==2.*",
66
67
  "sqlalchemy[asyncio]==2.0.*",
68
+ "sqlmodel>=0.0.22",
69
+ "aiosqlite>=0.20.0",
67
70
  ]
68
71
  examples = [
69
72
  "fastapi==0.109.*",
@@ -95,6 +98,7 @@ testpaths = ["tests"]
95
98
  markers = [
96
99
  "pydantic: tests that require the pydantic optional extra",
97
100
  "sqlalchemy: tests that require the sqlalchemy optional extra",
101
+ "sqlmodel: tests that require the sqlmodel optional extra",
98
102
  ]
99
103
 
100
104
  [tool.setuptools.packages.find]
@@ -12,6 +12,7 @@ if typing.TYPE_CHECKING:
12
12
  SqlAlchemyOutboxedEventRepository,
13
13
  rebind_outbox_model,
14
14
  )
15
+ from cqrs.outbox.sqlmodel import SqlModelOutboxedEventRepository
15
16
  from cqrs.sqlalchemy_types import (
16
17
  Binary16,
17
18
  DialectAwareType,
@@ -129,6 +130,7 @@ __all__ = (
129
130
  "PydanticResponse",
130
131
  "OutboxedEventRepository",
131
132
  "SqlAlchemyOutboxedEventRepository",
133
+ "SqlModelOutboxedEventRepository",
132
134
  "EventProducer",
133
135
  "EventSerializer",
134
136
  "EventCodec",
@@ -187,6 +189,13 @@ _LAZY_SQLALCHEMY = {
187
189
  "PayloadBinary": ("cqrs.sqlalchemy_types", "PayloadBinary"),
188
190
  }
189
191
 
192
+ _LAZY_SQLMODEL = {
193
+ "SqlModelOutboxedEventRepository": (
194
+ "cqrs.outbox.sqlmodel",
195
+ "SqlModelOutboxedEventRepository",
196
+ ),
197
+ }
198
+
190
199
 
191
200
  def __getattr__(name: str) -> typing.Any:
192
201
  if name in _LAZY_PYDANTIC:
@@ -201,4 +210,9 @@ def __getattr__(name: str) -> typing.Any:
201
210
  # ImportError message is raised by the sqlalchemy modules themselves.
202
211
  module = __import__(module_name, fromlist=[attr])
203
212
  return getattr(module, attr)
213
+ if name in _LAZY_SQLMODEL:
214
+ module_name, attr = _LAZY_SQLMODEL[name]
215
+ # ImportError message is raised by the sqlmodel modules themselves.
216
+ module = __import__(module_name, fromlist=[attr])
217
+ return getattr(module, attr)
204
218
  raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -173,11 +173,13 @@ class SqlAlchemyOutboxedEventRepository(repository.OutboxedEventRepository):
173
173
  *,
174
174
  serializer: EventCodec | None = None,
175
175
  event_map: map.OutboxedEventMap | None = None,
176
+ model: typing.Any = None,
176
177
  ):
177
178
  self.session = session
178
179
  self._compressor = compressor
179
180
  self._serializer = serializer or JsonEventSerializer()
180
181
  self._event_map = event_map if event_map is not None else map.OutboxedEventMap
182
+ self._model = OutboxModel if model is None else model
181
183
 
182
184
  def _resolve_codec(self, event_name: str) -> EventCodec:
183
185
  return self._event_map.get_serializer(event_name) or self._serializer
@@ -211,7 +213,7 @@ class SqlAlchemyOutboxedEventRepository(repository.OutboxedEventRepository):
211
213
  bytes_payload = self._compressor.compress(bytes_payload)
212
214
 
213
215
  self.session.add(
214
- OutboxModel(
216
+ self._model(
215
217
  event_id=event.event_id,
216
218
  event_id_bin=event.event_id.bytes,
217
219
  event_name=event.event_name,
@@ -221,7 +223,7 @@ class SqlAlchemyOutboxedEventRepository(repository.OutboxedEventRepository):
221
223
  ),
222
224
  )
223
225
 
224
- def _process_events(self, model: OutboxModel) -> repository.OutboxedEvent | None:
226
+ def _process_events(self, model: typing.Any) -> repository.OutboxedEvent | None:
225
227
  event_dict = model.row_to_dict()
226
228
  event_name = event_dict["event_name"]
227
229
 
@@ -260,8 +262,8 @@ class SqlAlchemyOutboxedEventRepository(repository.OutboxedEventRepository):
260
262
  batch_size: int = 100,
261
263
  topic: typing.Text | None = None,
262
264
  ) -> typing.List[repository.OutboxedEvent]:
263
- events: typing.Sequence[OutboxModel] = (
264
- (await self.session.execute(OutboxModel.get_batch_query(batch_size, topic))).scalars().all()
265
+ events: typing.Sequence[typing.Any] = (
266
+ (await self.session.execute(self._model.get_batch_query(batch_size, topic))).scalars().all()
265
267
  )
266
268
 
267
269
  result = []
@@ -285,7 +287,7 @@ class SqlAlchemyOutboxedEventRepository(repository.OutboxedEventRepository):
285
287
  new_status: repository.EventStatus,
286
288
  ) -> None:
287
289
  await self.session.execute(
288
- statement=OutboxModel.update_status_query(outboxed_event_id, new_status),
290
+ statement=self._model.update_status_query(outboxed_event_id, new_status),
289
291
  )
290
292
 
291
293
  async def commit(self):
@@ -0,0 +1,189 @@
1
+ """SQLModel outbox: same table, queries, and repository behavior as SQLAlchemy."""
2
+
3
+ import datetime
4
+ import typing
5
+ import uuid
6
+
7
+ from cqrs import compressors
8
+ from cqrs.mapping import outbox as map
9
+ from cqrs.outbox import repository
10
+ from cqrs.serializers.protocol import EventCodec
11
+
12
+ try:
13
+ from cqrs.outbox.sqlalchemy import (
14
+ MAX_FLUSH_COUNTER_VALUE,
15
+ BinaryUUID,
16
+ OutboxModel as SqlAlchemyOutboxModel,
17
+ SqlAlchemyOutboxedEventRepository,
18
+ rebind_outbox_model,
19
+ )
20
+
21
+ import sqlalchemy
22
+ from sqlalchemy import func
23
+ from sqlmodel import Field
24
+ from sqlmodel.ext.asyncio.session import AsyncSession
25
+
26
+ from cqrs import sqlalchemy_types
27
+ from cqrs.sqlmodel_base import CqrsSQLModel, SqlAlchemyExecuteSession
28
+ except ImportError:
29
+ raise ImportError(
30
+ "You are trying to use the SQLModel outbox implementation, "
31
+ "but 'sqlmodel' is not installed. "
32
+ "Please install it using: pip install python-cqrs[sqlmodel]",
33
+ ) from None
34
+
35
+ Base = CqrsSQLModel
36
+
37
+ DEFAULT_OUTBOX_TABLE_NAME = "outbox"
38
+
39
+
40
+ class OutboxModel(Base, table=True):
41
+ __tablename__ = DEFAULT_OUTBOX_TABLE_NAME # type: ignore[assignment]
42
+
43
+ __table_args__ = (
44
+ sqlalchemy.UniqueConstraint(
45
+ "event_id_bin",
46
+ "event_name",
47
+ name="event_id_unique_index",
48
+ ),
49
+ )
50
+
51
+ id: int | None = Field(
52
+ default=None,
53
+ sa_column=sqlalchemy.Column(
54
+ sqlalchemy.BigInteger,
55
+ sqlalchemy.Identity(),
56
+ primary_key=True,
57
+ nullable=False,
58
+ autoincrement=True,
59
+ comment="Identity",
60
+ ),
61
+ )
62
+ event_id: uuid.UUID = Field(
63
+ sa_column=sqlalchemy.Column(
64
+ sqlalchemy_types.UUIDBinary,
65
+ nullable=False,
66
+ comment="Event idempotency id",
67
+ ),
68
+ )
69
+ event_id_bin: bytes = Field(
70
+ sa_column=sqlalchemy.Column(
71
+ sqlalchemy_types.Binary16,
72
+ nullable=False,
73
+ comment="Event idempotency id in 16 bit presentation",
74
+ ),
75
+ )
76
+ event_status: repository.EventStatus = Field(
77
+ default=repository.EventStatus.NEW,
78
+ sa_column=sqlalchemy.Column(
79
+ sqlalchemy.Enum(repository.EventStatus),
80
+ nullable=False,
81
+ default=repository.EventStatus.NEW,
82
+ comment="Event producing status",
83
+ ),
84
+ )
85
+ flush_counter: int = Field(
86
+ default=0,
87
+ sa_column=sqlalchemy.Column(
88
+ sqlalchemy.SmallInteger,
89
+ nullable=False,
90
+ default=0,
91
+ comment="Event producing flush counter",
92
+ ),
93
+ )
94
+ event_name: str = Field(
95
+ sa_column=sqlalchemy.Column(
96
+ sqlalchemy.String(255),
97
+ nullable=False,
98
+ comment="Event name",
99
+ ),
100
+ )
101
+ topic: str = Field(
102
+ default="",
103
+ sa_column=sqlalchemy.Column(
104
+ sqlalchemy.String(255),
105
+ nullable=False,
106
+ default="",
107
+ comment="Event topic",
108
+ ),
109
+ )
110
+ created_at: datetime.datetime | None = Field(
111
+ default=None,
112
+ sa_column=sqlalchemy.Column(
113
+ sqlalchemy.DateTime,
114
+ nullable=False,
115
+ server_default=func.now(),
116
+ comment="Event creation timestamp",
117
+ ),
118
+ )
119
+ payload: bytes = Field(
120
+ sa_column=sqlalchemy.Column(
121
+ sqlalchemy_types.PayloadBinary,
122
+ nullable=False,
123
+ comment="Event payload",
124
+ ),
125
+ )
126
+
127
+ def row_to_dict(self) -> typing.Dict[typing.Text, typing.Any]:
128
+ table = typing.cast(typing.Any, self).__table__
129
+ return {column.name: getattr(self, column.name) for column in table.columns}
130
+
131
+ @classmethod
132
+ def get_batch_query(
133
+ cls,
134
+ size: int,
135
+ topic: typing.Text | None = None,
136
+ ) -> sqlalchemy.Select:
137
+ query = SqlAlchemyOutboxModel.get_batch_query
138
+ return query.__func__(cls, size, topic) # type: ignore[attr-defined]
139
+
140
+ @classmethod
141
+ def update_status_query(
142
+ cls,
143
+ outboxed_event_id: int,
144
+ status: repository.EventStatus,
145
+ ) -> sqlalchemy.Update:
146
+ query = SqlAlchemyOutboxModel.update_status_query
147
+ return query.__func__(cls, outboxed_event_id, status) # type: ignore[attr-defined]
148
+
149
+ @classmethod
150
+ def status_sorting_case(cls) -> sqlalchemy.Case:
151
+ query = SqlAlchemyOutboxModel.status_sorting_case
152
+ return query.__func__(cls) # type: ignore[attr-defined]
153
+
154
+
155
+ class SqlModelOutboxedEventRepository(SqlAlchemyOutboxedEventRepository):
156
+ """Outbox repository that stores rows as SQLModel ``OutboxModel`` instances.
157
+
158
+ Pass a ``sqlmodel.ext.asyncio.session.AsyncSession``. Behavior matches
159
+ ``SqlAlchemyOutboxedEventRepository``: the same batch query, flush budget,
160
+ status ordering, codecs, and compressor hooks.
161
+ """
162
+
163
+ def __init__(
164
+ self,
165
+ session: AsyncSession,
166
+ compressor: compressors.Compressor | None = None,
167
+ *,
168
+ serializer: EventCodec | None = None,
169
+ event_map: map.OutboxedEventMap | None = None,
170
+ ):
171
+ super().__init__(
172
+ typing.cast(typing.Any, SqlAlchemyExecuteSession(session)),
173
+ compressor,
174
+ serializer=serializer,
175
+ event_map=event_map,
176
+ model=OutboxModel,
177
+ )
178
+
179
+
180
+ __all__ = (
181
+ "AsyncSession",
182
+ "Base",
183
+ "BinaryUUID",
184
+ "DEFAULT_OUTBOX_TABLE_NAME",
185
+ "MAX_FLUSH_COUNTER_VALUE",
186
+ "OutboxModel",
187
+ "SqlModelOutboxedEventRepository",
188
+ "rebind_outbox_model",
189
+ )