python-cqrs 4.13.0__tar.gz → 4.15.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.
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/PKG-INFO +76 -26
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/README.md +68 -25
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/pyproject.toml +6 -1
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/__init__.py +27 -1
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/adapters/kafka.py +27 -6
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/adapters/protocol.py +7 -0
- python_cqrs-4.15.0/src/cqrs/container/__init__.py +18 -0
- python_cqrs-4.15.0/src/cqrs/container/di.py +75 -0
- python_cqrs-4.15.0/src/cqrs/container/dishka.py +48 -0
- python_cqrs-4.15.0/src/cqrs/container/protocol.py +43 -0
- python_cqrs-4.15.0/src/cqrs/container/scope.py +321 -0
- python_cqrs-4.15.0/src/cqrs/deserializers/__init__.py +11 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/deserializers/exceptions.py +16 -0
- python_cqrs-4.15.0/src/cqrs/deserializers/protobuf.py +51 -0
- python_cqrs-4.15.0/src/cqrs/dispatcher/event.py +111 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/dispatcher/request.py +67 -46
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/dispatcher/saga.py +8 -4
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/dispatcher/streaming.py +70 -47
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/events/bootstrap.py +9 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/events/event_emitter.py +106 -51
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/mediator.py +114 -10
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/message_brokers/amqp.py +10 -2
- python_cqrs-4.15.0/src/cqrs/message_brokers/kafka.py +45 -0
- python_cqrs-4.15.0/src/cqrs/message_brokers/protocol.py +74 -0
- python_cqrs-4.15.0/src/cqrs/outbox/map.py +103 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/outbox/mock.py +18 -2
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/outbox/repository.py +4 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/outbox/sqlalchemy.py +39 -11
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/producer.py +8 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/requests/bootstrap.py +51 -50
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/saga/bootstrap.py +28 -32
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/saga/compensation.py +77 -15
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/saga/execution.py +141 -125
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/saga/recovery.py +29 -10
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/saga/saga.py +49 -19
- python_cqrs-4.15.0/src/cqrs/serializers/__init__.py +12 -0
- python_cqrs-4.15.0/src/cqrs/serializers/default.py +86 -0
- python_cqrs-4.15.0/src/cqrs/serializers/json.py +22 -0
- python_cqrs-4.15.0/src/cqrs/serializers/protobuf.py +48 -0
- python_cqrs-4.15.0/src/cqrs/serializers/protocol.py +21 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/python_cqrs.egg-info/PKG-INFO +76 -26
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/python_cqrs.egg-info/SOURCES.txt +6 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/python_cqrs.egg-info/requires.txt +9 -0
- python_cqrs-4.13.0/src/cqrs/container/__init__.py +0 -3
- python_cqrs-4.13.0/src/cqrs/container/di.py +0 -26
- python_cqrs-4.13.0/src/cqrs/container/protocol.py +0 -20
- python_cqrs-4.13.0/src/cqrs/deserializers/__init__.py +0 -8
- python_cqrs-4.13.0/src/cqrs/dispatcher/event.py +0 -96
- python_cqrs-4.13.0/src/cqrs/message_brokers/kafka.py +0 -18
- python_cqrs-4.13.0/src/cqrs/message_brokers/protocol.py +0 -44
- python_cqrs-4.13.0/src/cqrs/outbox/map.py +0 -24
- python_cqrs-4.13.0/src/cqrs/serializers/__init__.py +0 -3
- python_cqrs-4.13.0/src/cqrs/serializers/default.py +0 -24
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/LICENSE +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/setup.cfg +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/adapters/__init__.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/adapters/amqp.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/adapters/circuit_breaker.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/circuit_breaker.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/compressors/__init__.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/compressors/protocol.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/compressors/zlib.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/container/dependency_injector.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/deserializers/json.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/dispatcher/__init__.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/dispatcher/exceptions.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/dispatcher/models.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/events/__init__.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/events/event.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/events/event_handler.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/events/event_processor.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/events/fallback.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/events/map.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/generic_utils.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/message_brokers/__init__.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/message_brokers/devnull.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/middlewares/__init__.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/middlewares/base.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/middlewares/logging.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/outbox/__init__.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/requests/__init__.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/requests/cor_request_handler.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/requests/fallback.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/requests/map.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/requests/mermaid.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/requests/request.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/requests/request_handler.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/response.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/saga/__init__.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/saga/circuit_breaker.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/saga/fallback.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/saga/mermaid.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/saga/models.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/saga/step.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/saga/storage/__init__.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/saga/storage/enums.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/saga/storage/memory.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/saga/storage/models.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/saga/storage/protocol.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/saga/storage/sqlalchemy.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/saga/validation.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/sqlalchemy_types.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/cqrs/types.py +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.0}/src/python_cqrs.egg-info/dependency_links.txt +0 -0
- {python_cqrs-4.13.0 → python_cqrs-4.15.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: 4.
|
|
3
|
+
Version: 4.15.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>
|
|
@@ -48,6 +48,8 @@ Requires-Dist: asyncmy==0.2.9; extra == "dev"
|
|
|
48
48
|
Requires-Dist: asyncpg>=0.29.0; extra == "dev"
|
|
49
49
|
Requires-Dist: redis>=5.0.0; extra == "dev"
|
|
50
50
|
Requires-Dist: aiobreaker>=0.3.0; extra == "dev"
|
|
51
|
+
Requires-Dist: dishka>=1.0; extra == "dev"
|
|
52
|
+
Requires-Dist: protobuf>=4.25.8; extra == "dev"
|
|
51
53
|
Provides-Extra: examples
|
|
52
54
|
Requires-Dist: fastapi==0.109.*; extra == "examples"
|
|
53
55
|
Requires-Dist: faststream[kafka]==0.5.28; extra == "examples"
|
|
@@ -56,13 +58,18 @@ Requires-Dist: uvicorn==0.32.0; extra == "examples"
|
|
|
56
58
|
Requires-Dist: aiohttp==3.14.3; extra == "examples"
|
|
57
59
|
Requires-Dist: protobuf>=4.25.8; extra == "examples"
|
|
58
60
|
Requires-Dist: aiosqlite>=0.20.0; extra == "examples"
|
|
61
|
+
Requires-Dist: dishka>=1.0; extra == "examples"
|
|
59
62
|
Provides-Extra: kafka
|
|
60
63
|
Requires-Dist: aiokafka==0.10.0; extra == "kafka"
|
|
61
64
|
Requires-Dist: retry-async==0.1.*; extra == "kafka"
|
|
65
|
+
Provides-Extra: protobuf
|
|
66
|
+
Requires-Dist: protobuf>=4.25.8; extra == "protobuf"
|
|
62
67
|
Provides-Extra: rabbit
|
|
63
68
|
Requires-Dist: aio-pika==9.3.0; extra == "rabbit"
|
|
64
69
|
Provides-Extra: sqlalchemy
|
|
65
70
|
Requires-Dist: sqlalchemy[asyncio]==2.0.*; extra == "sqlalchemy"
|
|
71
|
+
Provides-Extra: dishka
|
|
72
|
+
Requires-Dist: dishka>=1.0; extra == "dishka"
|
|
66
73
|
Dynamic: license-file
|
|
67
74
|
|
|
68
75
|
<div align="center">
|
|
@@ -117,6 +124,7 @@ Dynamic: license-file
|
|
|
117
124
|
- [Request Handlers](#request-handlers)
|
|
118
125
|
- [Mapping](#mapping)
|
|
119
126
|
- [DI container](#di-container)
|
|
127
|
+
- [Scoped dependencies](#scoped-dependencies)
|
|
120
128
|
- [Bootstrap](#bootstrap)
|
|
121
129
|
- [Saga Pattern](#saga-pattern)
|
|
122
130
|
- [Producing Notification Events](#producing-notification-events)
|
|
@@ -264,7 +272,7 @@ class CustomResponse(cqrs.IResponse):
|
|
|
264
272
|
return cls(result=kwargs["result"], status=kwargs["status"])
|
|
265
273
|
```
|
|
266
274
|
|
|
267
|
-
A complete example can be found in [request_response_types.py](https://github.com/vadikko2/python-cqrs/blob/master/examples/request_response_types.py)
|
|
275
|
+
A complete example can be found in [request_response_types.py](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/request_response_types.py)
|
|
268
276
|
|
|
269
277
|
## Request Handlers
|
|
270
278
|
|
|
@@ -297,7 +305,7 @@ class JoinMeetingCommandHandler(RequestHandler[JoinMeetingCommand, None]):
|
|
|
297
305
|
```
|
|
298
306
|
|
|
299
307
|
A complete example can be found in
|
|
300
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/request_handler.py)
|
|
308
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/request_handler.py)
|
|
301
309
|
|
|
302
310
|
### Query handler
|
|
303
311
|
|
|
@@ -327,7 +335,7 @@ class ReadMeetingQueryHandler(RequestHandler[ReadMeetingQuery, ReadMeetingQueryR
|
|
|
327
335
|
```
|
|
328
336
|
|
|
329
337
|
A complete example can be found in
|
|
330
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/request_handler.py)
|
|
338
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/request_handler.py)
|
|
331
339
|
|
|
332
340
|
### Streaming Request Handler
|
|
333
341
|
|
|
@@ -363,7 +371,7 @@ class ProcessFilesCommandHandler(StreamingRequestHandler[ProcessFilesCommand, Fi
|
|
|
363
371
|
```
|
|
364
372
|
|
|
365
373
|
A complete example can be found in
|
|
366
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/streaming_handler_parallel_events.py)
|
|
374
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/streaming/streaming_handler_parallel_events.py)
|
|
367
375
|
|
|
368
376
|
### Chain of Responsibility Request Handler
|
|
369
377
|
|
|
@@ -426,7 +434,7 @@ def payment_mapper(mapper: cqrs.RequestMap) -> None:
|
|
|
426
434
|
```
|
|
427
435
|
|
|
428
436
|
A complete example can be found in
|
|
429
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/cor_request_handler.py)
|
|
437
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/cor/cor_request_handler.py)
|
|
430
438
|
|
|
431
439
|
#### Mermaid Diagram Generation
|
|
432
440
|
|
|
@@ -446,7 +454,7 @@ sequence_diagram = generator.sequence()
|
|
|
446
454
|
class_diagram = generator.class_diagram()
|
|
447
455
|
```
|
|
448
456
|
|
|
449
|
-
Complete example: [CoR Mermaid Diagrams](https://github.com/vadikko2/python-cqrs/blob/master/examples/cor_mermaid.py)
|
|
457
|
+
Complete example: [CoR Mermaid Diagrams](https://github.com/vadikko2/python-cqrs/blob/master/examples/cor/cor_mermaid.py)
|
|
450
458
|
|
|
451
459
|
## Mapping
|
|
452
460
|
|
|
@@ -507,7 +515,11 @@ def saga_mapper(mapper: cqrs.SagaMap) -> None:
|
|
|
507
515
|
Use the following example to set up dependency injection in your command, query and event handlers. This will make
|
|
508
516
|
dependency management simpler.
|
|
509
517
|
|
|
510
|
-
The package supports
|
|
518
|
+
The package supports three DI container libraries:
|
|
519
|
+
|
|
520
|
+
- [`di`](#di-library) (default)
|
|
521
|
+
- [`dependency-injector`](#dependency-injector-library)
|
|
522
|
+
- [dishka](https://github.com/reagento/dishka) via `DishkaCQRSContainer` (`pip install python-cqrs[dishka]`)
|
|
511
523
|
|
|
512
524
|
### di library
|
|
513
525
|
|
|
@@ -536,7 +548,7 @@ def setup_di() -> di.Container:
|
|
|
536
548
|
```
|
|
537
549
|
|
|
538
550
|
A complete example can be found in
|
|
539
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/
|
|
551
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/di/di_basic.py)
|
|
540
552
|
|
|
541
553
|
### dependency-injector library
|
|
542
554
|
|
|
@@ -552,7 +564,8 @@ class ApplicationContainer(containers.DeclarativeContainer):
|
|
|
552
564
|
service = providers.Factory(ServiceImplementation)
|
|
553
565
|
|
|
554
566
|
# Create CQRS container adapter
|
|
555
|
-
cqrs_container = DependencyInjectorCQRSContainer(
|
|
567
|
+
cqrs_container = DependencyInjectorCQRSContainer()
|
|
568
|
+
cqrs_container.attach_external_container(ApplicationContainer())
|
|
556
569
|
|
|
557
570
|
# Use with bootstrap
|
|
558
571
|
mediator = bootstrap.bootstrap(
|
|
@@ -563,8 +576,32 @@ mediator = bootstrap.bootstrap(
|
|
|
563
576
|
```
|
|
564
577
|
|
|
565
578
|
Complete examples can be found in:
|
|
566
|
-
- [Simple example](https://github.com/vadikko2/python-cqrs/blob/master/examples/
|
|
567
|
-
- [Practical example with FastAPI](https://github.com/vadikko2/python-cqrs/blob/master/examples/
|
|
579
|
+
- [Simple example](https://github.com/vadikko2/python-cqrs/blob/master/examples/di/dependency_injector_simple.py)
|
|
580
|
+
- [Practical example with FastAPI](https://github.com/vadikko2/python-cqrs/blob/master/examples/di/dependency_injector_practical.py)
|
|
581
|
+
|
|
582
|
+
### Scoped dependencies
|
|
583
|
+
|
|
584
|
+
By default, the container opens and closes a scope **inside** `resolve()`. A generator such as `async def session() -> AsyncIterator[AsyncSession]` therefore finishes **before** `handle`, which is why people inject factories.
|
|
585
|
+
|
|
586
|
+
Pass `scope_strategy=ScopeStrategy.SEND` and that session lives until `mediator.send()` returns — including domain-event handlers that run afterwards.
|
|
587
|
+
|
|
588
|
+
If you do not use generator providers, leave the default `ScopeStrategy.NONE` and change nothing.
|
|
589
|
+
|
|
590
|
+
```python
|
|
591
|
+
mediator = bootstrap.bootstrap(
|
|
592
|
+
di_container=container,
|
|
593
|
+
commands_mapper=commands_mapper,
|
|
594
|
+
scope_strategy=cqrs.ScopeStrategy.SEND,
|
|
595
|
+
)
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
Copy-paste SQLAlchemy session + outbox under one SEND scope: the [Tutorial](https://mkdocs.python-cqrs.dev/scoped_dependencies/tutorial/) (`examples/di/scoped_dependencies_sqlalchemy.py`). Minimal `di` UoW: `examples/di/scoped_dependencies_di.py`.
|
|
599
|
+
|
|
600
|
+
- **SEND** — one UoW per `send()` (command, fallback, and domain events).
|
|
601
|
+
- **HANDLER** — a fresh UoW per handler, including fallback (the primary rolls back first).
|
|
602
|
+
- **NONE** — legacy one-shot resolve; the default.
|
|
603
|
+
|
|
604
|
+
`di`'s `scope="request"` is provider lifetime, not a CQRS scope — that is only `scope_strategy=`. Docs: [Scoped Dependencies](https://mkdocs.python-cqrs.dev/scoped_dependencies/).
|
|
568
605
|
|
|
569
606
|
## Bootstrap
|
|
570
607
|
|
|
@@ -729,6 +766,7 @@ await recover_saga(
|
|
|
729
766
|
context_builder=OrderContext,
|
|
730
767
|
container=di_container, # Same container used in bootstrap
|
|
731
768
|
storage=storage,
|
|
769
|
+
scope_strategy=cqrs.ScopeStrategy.SEND, # Match the original run
|
|
732
770
|
)
|
|
733
771
|
|
|
734
772
|
# Access execution history (SagaLog) for monitoring and debugging
|
|
@@ -761,7 +799,7 @@ sequence_diagram = generator.sequence()
|
|
|
761
799
|
class_diagram = generator.class_diagram()
|
|
762
800
|
```
|
|
763
801
|
|
|
764
|
-
Complete example: [Saga Mermaid Diagrams](https://github.com/vadikko2/python-cqrs/blob/master/examples/saga_mermaid.py)
|
|
802
|
+
Complete example: [Saga Mermaid Diagrams](https://github.com/vadikko2/python-cqrs/blob/master/examples/saga/saga_mermaid.py)
|
|
765
803
|
|
|
766
804
|
## Producing Notification Events
|
|
767
805
|
|
|
@@ -801,7 +839,7 @@ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
|
|
|
801
839
|
```
|
|
802
840
|
|
|
803
841
|
A complete example can be found in
|
|
804
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/event_producing.py)
|
|
842
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/event_producing.py)
|
|
805
843
|
|
|
806
844
|
After processing the command/request, if there are any Notification/ECST events,
|
|
807
845
|
the EventEmitter is invoked to produce the events via the message broker.
|
|
@@ -872,10 +910,10 @@ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
|
|
|
872
910
|
```
|
|
873
911
|
|
|
874
912
|
A complete example can be found in
|
|
875
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/save_events_into_outbox.py)
|
|
913
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/outbox/save_events_into_outbox.py)
|
|
876
914
|
|
|
877
915
|
A runnable FastAPI flow (route → command → transactional outbox → publisher stub) is in
|
|
878
|
-
[fastapi_outbox.py](https://github.com/vadikko2/python-cqrs/blob/master/examples/fastapi_outbox.py)
|
|
916
|
+
[fastapi_outbox.py](https://github.com/vadikko2/python-cqrs/blob/master/examples/outbox/fastapi_outbox.py)
|
|
879
917
|
|
|
880
918
|
> [!TIP]
|
|
881
919
|
> The Outbox table is named `outbox` by default. To rename it, or to attach the model to your own declarative
|
|
@@ -888,9 +926,10 @@ A runnable FastAPI flow (route → command → transactional outbox → publishe
|
|
|
888
926
|
> [Database Support](https://mkdocs.python-cqrs.dev/outbox/databases/) docs for the DDL, the Alembic recipe and
|
|
889
927
|
> how to register a native type for your own database.
|
|
890
928
|
> [!TIP]
|
|
891
|
-
>
|
|
892
|
-
>
|
|
893
|
-
|
|
929
|
+
> For Protobuf events, implement `proto()` / `from_proto()` and register a
|
|
930
|
+
> `ProtobufEventSerializer` on an `OutboxedEventMap`. JSON stays the default —
|
|
931
|
+
> see [Protobuf messaging](#protobuf-messaging) and the full guide:
|
|
932
|
+
> [Protobuf Integration](https://mkdocs.python-cqrs.dev/protobuf/).
|
|
894
933
|
|
|
895
934
|
## Producing Events from Outbox to Kafka
|
|
896
935
|
|
|
@@ -929,7 +968,7 @@ async with session_factory() as session:
|
|
|
929
968
|
```
|
|
930
969
|
|
|
931
970
|
A complete example can be found in
|
|
932
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/kafka_outboxed_event_producing.py)
|
|
971
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/outbox/kafka_outboxed_event_producing.py)
|
|
933
972
|
|
|
934
973
|
## Transaction log tailing
|
|
935
974
|
|
|
@@ -972,7 +1011,7 @@ class UserJoinedEventHandler(cqrs.EventHandler[UserJoined]):
|
|
|
972
1011
|
```
|
|
973
1012
|
|
|
974
1013
|
A complete example can be found in
|
|
975
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/domain_event_handler.py)
|
|
1014
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/domain_event_handler.py)
|
|
976
1015
|
|
|
977
1016
|
### Parallel Event Processing
|
|
978
1017
|
|
|
@@ -1003,6 +1042,7 @@ mediator = bootstrap.bootstrap_streaming(
|
|
|
1003
1042
|
> - Set `max_concurrent_event_handlers` to limit the number of simultaneously running event handlers
|
|
1004
1043
|
> - Set `concurrent_event_handle_enable=False` to disable parallel processing and process events sequentially
|
|
1005
1044
|
> - The default value for `max_concurrent_event_handlers` is `10` for `StreamingRequestMediator` and `1` for `RequestMediator`
|
|
1045
|
+
> - `ScopeStrategy.SEND` always uses sequential events (`concurrent=None` → `False`). Passing `concurrent_event_handle_enable=True` with `SEND` raises `ValueError`; use `HANDLER` or `NONE` for parallel events.
|
|
1006
1046
|
|
|
1007
1047
|
## Integration with presentation layers
|
|
1008
1048
|
|
|
@@ -1040,10 +1080,10 @@ async def join_metting(
|
|
|
1040
1080
|
```
|
|
1041
1081
|
|
|
1042
1082
|
A complete example can be found in
|
|
1043
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/fastapi_integration.py)
|
|
1083
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/fastapi_integration.py)
|
|
1044
1084
|
|
|
1045
1085
|
End-to-end command + outbox sample (business row and outbox event in one transaction, plus a publisher stub):
|
|
1046
|
-
[fastapi_outbox.py](https://github.com/vadikko2/python-cqrs/blob/master/examples/fastapi_outbox.py)
|
|
1086
|
+
[fastapi_outbox.py](https://github.com/vadikko2/python-cqrs/blob/master/examples/outbox/fastapi_outbox.py)
|
|
1047
1087
|
|
|
1048
1088
|
### Kafka events consuming
|
|
1049
1089
|
|
|
@@ -1091,7 +1131,7 @@ async def hello_world_event_handler(
|
|
|
1091
1131
|
```
|
|
1092
1132
|
|
|
1093
1133
|
A complete example can be found in
|
|
1094
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/kafka_event_consuming.py)
|
|
1134
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/kafka_event_consuming.py)
|
|
1095
1135
|
|
|
1096
1136
|
### FastAPI SSE Streaming
|
|
1097
1137
|
|
|
@@ -1112,7 +1152,7 @@ def streaming_mediator_factory() -> cqrs.StreamingRequestMediator:
|
|
|
1112
1152
|
domain_events_mapper=domain_events_mapper,
|
|
1113
1153
|
message_broker=broker,
|
|
1114
1154
|
max_concurrent_event_handlers=3,
|
|
1115
|
-
concurrent_event_handle_enable=True,
|
|
1155
|
+
concurrent_event_handle_enable=True, # HANDLER/NONE only; SEND forbids this
|
|
1116
1156
|
)
|
|
1117
1157
|
|
|
1118
1158
|
@app.post("/process-files")
|
|
@@ -1139,7 +1179,9 @@ async def process_files_stream(
|
|
|
1139
1179
|
```
|
|
1140
1180
|
|
|
1141
1181
|
A complete example can be found in
|
|
1142
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/fastapi_sse_streaming.py)
|
|
1182
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/streaming/fastapi_sse_streaming.py)
|
|
1183
|
+
|
|
1184
|
+
If the client disconnects, close the iterator (`aclose()` / `async with aclosing(...)`) so a `SEND` UoW is not held until GC.
|
|
1143
1185
|
|
|
1144
1186
|
## Protobuf messaging
|
|
1145
1187
|
|
|
@@ -1189,6 +1231,14 @@ class UserJoinedNotificationEvent(cqrs.NotificationEvent[UserJoinedPayload]):
|
|
|
1189
1231
|
)
|
|
1190
1232
|
```
|
|
1191
1233
|
|
|
1234
|
+
JSON remains the default. Opt-in Protobuf wiring (`ProtobufEventSerializer` on an
|
|
1235
|
+
`OutboxedEventMap`, `Message.payload_bytes`, consume via `ProtobufDeserializer`) is
|
|
1236
|
+
documented in the full guide:
|
|
1237
|
+
[Protobuf Integration](https://mkdocs.python-cqrs.dev/protobuf/).
|
|
1238
|
+
Runnable example:
|
|
1239
|
+
[protobuf_outbox.py](https://github.com/vadikko2/python-cqrs/blob/master/examples/outbox/protobuf_outbox.py)
|
|
1240
|
+
(`pip install "python-cqrs[protobuf]"`).
|
|
1241
|
+
|
|
1192
1242
|
## Contributing
|
|
1193
1243
|
|
|
1194
1244
|
Contributions are welcome. To develop locally:
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
- [Request Handlers](#request-handlers)
|
|
51
51
|
- [Mapping](#mapping)
|
|
52
52
|
- [DI container](#di-container)
|
|
53
|
+
- [Scoped dependencies](#scoped-dependencies)
|
|
53
54
|
- [Bootstrap](#bootstrap)
|
|
54
55
|
- [Saga Pattern](#saga-pattern)
|
|
55
56
|
- [Producing Notification Events](#producing-notification-events)
|
|
@@ -197,7 +198,7 @@ class CustomResponse(cqrs.IResponse):
|
|
|
197
198
|
return cls(result=kwargs["result"], status=kwargs["status"])
|
|
198
199
|
```
|
|
199
200
|
|
|
200
|
-
A complete example can be found in [request_response_types.py](https://github.com/vadikko2/python-cqrs/blob/master/examples/request_response_types.py)
|
|
201
|
+
A complete example can be found in [request_response_types.py](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/request_response_types.py)
|
|
201
202
|
|
|
202
203
|
## Request Handlers
|
|
203
204
|
|
|
@@ -230,7 +231,7 @@ class JoinMeetingCommandHandler(RequestHandler[JoinMeetingCommand, None]):
|
|
|
230
231
|
```
|
|
231
232
|
|
|
232
233
|
A complete example can be found in
|
|
233
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/request_handler.py)
|
|
234
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/request_handler.py)
|
|
234
235
|
|
|
235
236
|
### Query handler
|
|
236
237
|
|
|
@@ -260,7 +261,7 @@ class ReadMeetingQueryHandler(RequestHandler[ReadMeetingQuery, ReadMeetingQueryR
|
|
|
260
261
|
```
|
|
261
262
|
|
|
262
263
|
A complete example can be found in
|
|
263
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/request_handler.py)
|
|
264
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/request_handler.py)
|
|
264
265
|
|
|
265
266
|
### Streaming Request Handler
|
|
266
267
|
|
|
@@ -296,7 +297,7 @@ class ProcessFilesCommandHandler(StreamingRequestHandler[ProcessFilesCommand, Fi
|
|
|
296
297
|
```
|
|
297
298
|
|
|
298
299
|
A complete example can be found in
|
|
299
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/streaming_handler_parallel_events.py)
|
|
300
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/streaming/streaming_handler_parallel_events.py)
|
|
300
301
|
|
|
301
302
|
### Chain of Responsibility Request Handler
|
|
302
303
|
|
|
@@ -359,7 +360,7 @@ def payment_mapper(mapper: cqrs.RequestMap) -> None:
|
|
|
359
360
|
```
|
|
360
361
|
|
|
361
362
|
A complete example can be found in
|
|
362
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/cor_request_handler.py)
|
|
363
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/cor/cor_request_handler.py)
|
|
363
364
|
|
|
364
365
|
#### Mermaid Diagram Generation
|
|
365
366
|
|
|
@@ -379,7 +380,7 @@ sequence_diagram = generator.sequence()
|
|
|
379
380
|
class_diagram = generator.class_diagram()
|
|
380
381
|
```
|
|
381
382
|
|
|
382
|
-
Complete example: [CoR Mermaid Diagrams](https://github.com/vadikko2/python-cqrs/blob/master/examples/cor_mermaid.py)
|
|
383
|
+
Complete example: [CoR Mermaid Diagrams](https://github.com/vadikko2/python-cqrs/blob/master/examples/cor/cor_mermaid.py)
|
|
383
384
|
|
|
384
385
|
## Mapping
|
|
385
386
|
|
|
@@ -440,7 +441,11 @@ def saga_mapper(mapper: cqrs.SagaMap) -> None:
|
|
|
440
441
|
Use the following example to set up dependency injection in your command, query and event handlers. This will make
|
|
441
442
|
dependency management simpler.
|
|
442
443
|
|
|
443
|
-
The package supports
|
|
444
|
+
The package supports three DI container libraries:
|
|
445
|
+
|
|
446
|
+
- [`di`](#di-library) (default)
|
|
447
|
+
- [`dependency-injector`](#dependency-injector-library)
|
|
448
|
+
- [dishka](https://github.com/reagento/dishka) via `DishkaCQRSContainer` (`pip install python-cqrs[dishka]`)
|
|
444
449
|
|
|
445
450
|
### di library
|
|
446
451
|
|
|
@@ -469,7 +474,7 @@ def setup_di() -> di.Container:
|
|
|
469
474
|
```
|
|
470
475
|
|
|
471
476
|
A complete example can be found in
|
|
472
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/
|
|
477
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/di/di_basic.py)
|
|
473
478
|
|
|
474
479
|
### dependency-injector library
|
|
475
480
|
|
|
@@ -485,7 +490,8 @@ class ApplicationContainer(containers.DeclarativeContainer):
|
|
|
485
490
|
service = providers.Factory(ServiceImplementation)
|
|
486
491
|
|
|
487
492
|
# Create CQRS container adapter
|
|
488
|
-
cqrs_container = DependencyInjectorCQRSContainer(
|
|
493
|
+
cqrs_container = DependencyInjectorCQRSContainer()
|
|
494
|
+
cqrs_container.attach_external_container(ApplicationContainer())
|
|
489
495
|
|
|
490
496
|
# Use with bootstrap
|
|
491
497
|
mediator = bootstrap.bootstrap(
|
|
@@ -496,8 +502,32 @@ mediator = bootstrap.bootstrap(
|
|
|
496
502
|
```
|
|
497
503
|
|
|
498
504
|
Complete examples can be found in:
|
|
499
|
-
- [Simple example](https://github.com/vadikko2/python-cqrs/blob/master/examples/
|
|
500
|
-
- [Practical example with FastAPI](https://github.com/vadikko2/python-cqrs/blob/master/examples/
|
|
505
|
+
- [Simple example](https://github.com/vadikko2/python-cqrs/blob/master/examples/di/dependency_injector_simple.py)
|
|
506
|
+
- [Practical example with FastAPI](https://github.com/vadikko2/python-cqrs/blob/master/examples/di/dependency_injector_practical.py)
|
|
507
|
+
|
|
508
|
+
### Scoped dependencies
|
|
509
|
+
|
|
510
|
+
By default, the container opens and closes a scope **inside** `resolve()`. A generator such as `async def session() -> AsyncIterator[AsyncSession]` therefore finishes **before** `handle`, which is why people inject factories.
|
|
511
|
+
|
|
512
|
+
Pass `scope_strategy=ScopeStrategy.SEND` and that session lives until `mediator.send()` returns — including domain-event handlers that run afterwards.
|
|
513
|
+
|
|
514
|
+
If you do not use generator providers, leave the default `ScopeStrategy.NONE` and change nothing.
|
|
515
|
+
|
|
516
|
+
```python
|
|
517
|
+
mediator = bootstrap.bootstrap(
|
|
518
|
+
di_container=container,
|
|
519
|
+
commands_mapper=commands_mapper,
|
|
520
|
+
scope_strategy=cqrs.ScopeStrategy.SEND,
|
|
521
|
+
)
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
Copy-paste SQLAlchemy session + outbox under one SEND scope: the [Tutorial](https://mkdocs.python-cqrs.dev/scoped_dependencies/tutorial/) (`examples/di/scoped_dependencies_sqlalchemy.py`). Minimal `di` UoW: `examples/di/scoped_dependencies_di.py`.
|
|
525
|
+
|
|
526
|
+
- **SEND** — one UoW per `send()` (command, fallback, and domain events).
|
|
527
|
+
- **HANDLER** — a fresh UoW per handler, including fallback (the primary rolls back first).
|
|
528
|
+
- **NONE** — legacy one-shot resolve; the default.
|
|
529
|
+
|
|
530
|
+
`di`'s `scope="request"` is provider lifetime, not a CQRS scope — that is only `scope_strategy=`. Docs: [Scoped Dependencies](https://mkdocs.python-cqrs.dev/scoped_dependencies/).
|
|
501
531
|
|
|
502
532
|
## Bootstrap
|
|
503
533
|
|
|
@@ -662,6 +692,7 @@ await recover_saga(
|
|
|
662
692
|
context_builder=OrderContext,
|
|
663
693
|
container=di_container, # Same container used in bootstrap
|
|
664
694
|
storage=storage,
|
|
695
|
+
scope_strategy=cqrs.ScopeStrategy.SEND, # Match the original run
|
|
665
696
|
)
|
|
666
697
|
|
|
667
698
|
# Access execution history (SagaLog) for monitoring and debugging
|
|
@@ -694,7 +725,7 @@ sequence_diagram = generator.sequence()
|
|
|
694
725
|
class_diagram = generator.class_diagram()
|
|
695
726
|
```
|
|
696
727
|
|
|
697
|
-
Complete example: [Saga Mermaid Diagrams](https://github.com/vadikko2/python-cqrs/blob/master/examples/saga_mermaid.py)
|
|
728
|
+
Complete example: [Saga Mermaid Diagrams](https://github.com/vadikko2/python-cqrs/blob/master/examples/saga/saga_mermaid.py)
|
|
698
729
|
|
|
699
730
|
## Producing Notification Events
|
|
700
731
|
|
|
@@ -734,7 +765,7 @@ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
|
|
|
734
765
|
```
|
|
735
766
|
|
|
736
767
|
A complete example can be found in
|
|
737
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/event_producing.py)
|
|
768
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/event_producing.py)
|
|
738
769
|
|
|
739
770
|
After processing the command/request, if there are any Notification/ECST events,
|
|
740
771
|
the EventEmitter is invoked to produce the events via the message broker.
|
|
@@ -805,10 +836,10 @@ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
|
|
|
805
836
|
```
|
|
806
837
|
|
|
807
838
|
A complete example can be found in
|
|
808
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/save_events_into_outbox.py)
|
|
839
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/outbox/save_events_into_outbox.py)
|
|
809
840
|
|
|
810
841
|
A runnable FastAPI flow (route → command → transactional outbox → publisher stub) is in
|
|
811
|
-
[fastapi_outbox.py](https://github.com/vadikko2/python-cqrs/blob/master/examples/fastapi_outbox.py)
|
|
842
|
+
[fastapi_outbox.py](https://github.com/vadikko2/python-cqrs/blob/master/examples/outbox/fastapi_outbox.py)
|
|
812
843
|
|
|
813
844
|
> [!TIP]
|
|
814
845
|
> The Outbox table is named `outbox` by default. To rename it, or to attach the model to your own declarative
|
|
@@ -821,9 +852,10 @@ A runnable FastAPI flow (route → command → transactional outbox → publishe
|
|
|
821
852
|
> [Database Support](https://mkdocs.python-cqrs.dev/outbox/databases/) docs for the DDL, the Alembic recipe and
|
|
822
853
|
> how to register a native type for your own database.
|
|
823
854
|
> [!TIP]
|
|
824
|
-
>
|
|
825
|
-
>
|
|
826
|
-
|
|
855
|
+
> For Protobuf events, implement `proto()` / `from_proto()` and register a
|
|
856
|
+
> `ProtobufEventSerializer` on an `OutboxedEventMap`. JSON stays the default —
|
|
857
|
+
> see [Protobuf messaging](#protobuf-messaging) and the full guide:
|
|
858
|
+
> [Protobuf Integration](https://mkdocs.python-cqrs.dev/protobuf/).
|
|
827
859
|
|
|
828
860
|
## Producing Events from Outbox to Kafka
|
|
829
861
|
|
|
@@ -862,7 +894,7 @@ async with session_factory() as session:
|
|
|
862
894
|
```
|
|
863
895
|
|
|
864
896
|
A complete example can be found in
|
|
865
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/kafka_outboxed_event_producing.py)
|
|
897
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/outbox/kafka_outboxed_event_producing.py)
|
|
866
898
|
|
|
867
899
|
## Transaction log tailing
|
|
868
900
|
|
|
@@ -905,7 +937,7 @@ class UserJoinedEventHandler(cqrs.EventHandler[UserJoined]):
|
|
|
905
937
|
```
|
|
906
938
|
|
|
907
939
|
A complete example can be found in
|
|
908
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/domain_event_handler.py)
|
|
940
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/domain_event_handler.py)
|
|
909
941
|
|
|
910
942
|
### Parallel Event Processing
|
|
911
943
|
|
|
@@ -936,6 +968,7 @@ mediator = bootstrap.bootstrap_streaming(
|
|
|
936
968
|
> - Set `max_concurrent_event_handlers` to limit the number of simultaneously running event handlers
|
|
937
969
|
> - Set `concurrent_event_handle_enable=False` to disable parallel processing and process events sequentially
|
|
938
970
|
> - The default value for `max_concurrent_event_handlers` is `10` for `StreamingRequestMediator` and `1` for `RequestMediator`
|
|
971
|
+
> - `ScopeStrategy.SEND` always uses sequential events (`concurrent=None` → `False`). Passing `concurrent_event_handle_enable=True` with `SEND` raises `ValueError`; use `HANDLER` or `NONE` for parallel events.
|
|
939
972
|
|
|
940
973
|
## Integration with presentation layers
|
|
941
974
|
|
|
@@ -973,10 +1006,10 @@ async def join_metting(
|
|
|
973
1006
|
```
|
|
974
1007
|
|
|
975
1008
|
A complete example can be found in
|
|
976
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/fastapi_integration.py)
|
|
1009
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/fastapi_integration.py)
|
|
977
1010
|
|
|
978
1011
|
End-to-end command + outbox sample (business row and outbox event in one transaction, plus a publisher stub):
|
|
979
|
-
[fastapi_outbox.py](https://github.com/vadikko2/python-cqrs/blob/master/examples/fastapi_outbox.py)
|
|
1012
|
+
[fastapi_outbox.py](https://github.com/vadikko2/python-cqrs/blob/master/examples/outbox/fastapi_outbox.py)
|
|
980
1013
|
|
|
981
1014
|
### Kafka events consuming
|
|
982
1015
|
|
|
@@ -1024,7 +1057,7 @@ async def hello_world_event_handler(
|
|
|
1024
1057
|
```
|
|
1025
1058
|
|
|
1026
1059
|
A complete example can be found in
|
|
1027
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/kafka_event_consuming.py)
|
|
1060
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/kafka_event_consuming.py)
|
|
1028
1061
|
|
|
1029
1062
|
### FastAPI SSE Streaming
|
|
1030
1063
|
|
|
@@ -1045,7 +1078,7 @@ def streaming_mediator_factory() -> cqrs.StreamingRequestMediator:
|
|
|
1045
1078
|
domain_events_mapper=domain_events_mapper,
|
|
1046
1079
|
message_broker=broker,
|
|
1047
1080
|
max_concurrent_event_handlers=3,
|
|
1048
|
-
concurrent_event_handle_enable=True,
|
|
1081
|
+
concurrent_event_handle_enable=True, # HANDLER/NONE only; SEND forbids this
|
|
1049
1082
|
)
|
|
1050
1083
|
|
|
1051
1084
|
@app.post("/process-files")
|
|
@@ -1072,7 +1105,9 @@ async def process_files_stream(
|
|
|
1072
1105
|
```
|
|
1073
1106
|
|
|
1074
1107
|
A complete example can be found in
|
|
1075
|
-
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/fastapi_sse_streaming.py)
|
|
1108
|
+
the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/streaming/fastapi_sse_streaming.py)
|
|
1109
|
+
|
|
1110
|
+
If the client disconnects, close the iterator (`aclose()` / `async with aclosing(...)`) so a `SEND` UoW is not held until GC.
|
|
1076
1111
|
|
|
1077
1112
|
## Protobuf messaging
|
|
1078
1113
|
|
|
@@ -1122,6 +1157,14 @@ class UserJoinedNotificationEvent(cqrs.NotificationEvent[UserJoinedPayload]):
|
|
|
1122
1157
|
)
|
|
1123
1158
|
```
|
|
1124
1159
|
|
|
1160
|
+
JSON remains the default. Opt-in Protobuf wiring (`ProtobufEventSerializer` on an
|
|
1161
|
+
`OutboxedEventMap`, `Message.payload_bytes`, consume via `ProtobufDeserializer`) is
|
|
1162
|
+
documented in the full guide:
|
|
1163
|
+
[Protobuf Integration](https://mkdocs.python-cqrs.dev/protobuf/).
|
|
1164
|
+
Runnable example:
|
|
1165
|
+
[protobuf_outbox.py](https://github.com/vadikko2/python-cqrs/blob/master/examples/outbox/protobuf_outbox.py)
|
|
1166
|
+
(`pip install "python-cqrs[protobuf]"`).
|
|
1167
|
+
|
|
1125
1168
|
## Contributing
|
|
1126
1169
|
|
|
1127
1170
|
Contributions are welcome. To develop locally:
|
|
@@ -31,7 +31,7 @@ maintainers = [{ name = "Vadim Kozyrevskiy", email = "vadikko2@mail.ru" }]
|
|
|
31
31
|
name = "python-cqrs"
|
|
32
32
|
readme = "README.md"
|
|
33
33
|
requires-python = ">=3.10"
|
|
34
|
-
version = "4.
|
|
34
|
+
version = "4.15.0"
|
|
35
35
|
|
|
36
36
|
[project.optional-dependencies]
|
|
37
37
|
aiobreaker = ["aiobreaker>=0.3.0"]
|
|
@@ -59,6 +59,8 @@ dev = [
|
|
|
59
59
|
"redis>=5.0.0",
|
|
60
60
|
# Circuit breaker for tests
|
|
61
61
|
"aiobreaker>=0.3.0", # from aiobreaker
|
|
62
|
+
"dishka>=1.0", # scoped-deps dishka adapter tests / examples
|
|
63
|
+
"protobuf>=4.25.8", # protobuf codec / deserializer unit tests
|
|
62
64
|
]
|
|
63
65
|
examples = [
|
|
64
66
|
"fastapi==0.109.*",
|
|
@@ -68,10 +70,13 @@ examples = [
|
|
|
68
70
|
"aiohttp==3.14.3",
|
|
69
71
|
"protobuf>=4.25.8",
|
|
70
72
|
"aiosqlite>=0.20.0",
|
|
73
|
+
"dishka>=1.0",
|
|
71
74
|
]
|
|
72
75
|
kafka = ["aiokafka==0.10.0", "retry-async==0.1.*"]
|
|
76
|
+
protobuf = ["protobuf>=4.25.8"]
|
|
73
77
|
rabbit = ["aio-pika==9.3.0"]
|
|
74
78
|
sqlalchemy = ["sqlalchemy[asyncio]==2.0.*"]
|
|
79
|
+
dishka = ["dishka>=1.0"]
|
|
75
80
|
|
|
76
81
|
[project.urls]
|
|
77
82
|
Documentation = "https://mkdocs.python-cqrs.dev/"
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
from cqrs.compressors import Compressor, ZlibCompressor
|
|
2
2
|
from cqrs.container.di import DIContainer
|
|
3
|
-
from cqrs.container.protocol import Container
|
|
3
|
+
from cqrs.container.protocol import Container, SupportsScope
|
|
4
|
+
from cqrs.container.scope import (
|
|
5
|
+
ScopeAwareContainer,
|
|
6
|
+
ScopeStrategy,
|
|
7
|
+
bind_scope,
|
|
8
|
+
current_container,
|
|
9
|
+
enter_scope,
|
|
10
|
+
)
|
|
4
11
|
from cqrs.circuit_breaker import ICircuitBreaker
|
|
5
12
|
from cqrs.events import EventMap
|
|
6
13
|
from cqrs.events.fallback import EventHandlerFallback
|
|
@@ -37,6 +44,13 @@ from cqrs.outbox.sqlalchemy import (
|
|
|
37
44
|
SqlAlchemyOutboxedEventRepository,
|
|
38
45
|
)
|
|
39
46
|
from cqrs.producer import EventProducer
|
|
47
|
+
from cqrs.deserializers import DeserializeProtobufError, ProtobufDeserializer
|
|
48
|
+
from cqrs.serializers import (
|
|
49
|
+
EventCodec,
|
|
50
|
+
EventSerializer,
|
|
51
|
+
JsonEventSerializer,
|
|
52
|
+
ProtobufEventSerializer,
|
|
53
|
+
)
|
|
40
54
|
from cqrs.requests.fallback import RequestHandlerFallback
|
|
41
55
|
from cqrs.requests.map import RequestMap, SagaMap
|
|
42
56
|
from cqrs.requests.mermaid import CoRMermaid
|
|
@@ -104,8 +118,20 @@ __all__ = (
|
|
|
104
118
|
"OutboxedEventRepository",
|
|
105
119
|
"SqlAlchemyOutboxedEventRepository",
|
|
106
120
|
"EventProducer",
|
|
121
|
+
"EventSerializer",
|
|
122
|
+
"EventCodec",
|
|
123
|
+
"JsonEventSerializer",
|
|
124
|
+
"ProtobufEventSerializer",
|
|
125
|
+
"ProtobufDeserializer",
|
|
126
|
+
"DeserializeProtobufError",
|
|
107
127
|
"Container",
|
|
128
|
+
"SupportsScope",
|
|
108
129
|
"DIContainer",
|
|
130
|
+
"ScopeAwareContainer",
|
|
131
|
+
"ScopeStrategy",
|
|
132
|
+
"bind_scope",
|
|
133
|
+
"current_container",
|
|
134
|
+
"enter_scope",
|
|
109
135
|
"Compressor",
|
|
110
136
|
"ZlibCompressor",
|
|
111
137
|
"rebind_outbox_model",
|