python-cqrs 4.13.0__tar.gz → 4.14.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 (95) hide show
  1. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/PKG-INFO +68 -26
  2. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/README.md +63 -25
  3. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/pyproject.toml +4 -1
  4. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/__init__.py +14 -1
  5. python_cqrs-4.14.0/src/cqrs/container/__init__.py +18 -0
  6. python_cqrs-4.14.0/src/cqrs/container/di.py +75 -0
  7. python_cqrs-4.14.0/src/cqrs/container/dishka.py +48 -0
  8. python_cqrs-4.14.0/src/cqrs/container/protocol.py +43 -0
  9. python_cqrs-4.14.0/src/cqrs/container/scope.py +321 -0
  10. python_cqrs-4.14.0/src/cqrs/dispatcher/event.py +111 -0
  11. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/dispatcher/request.py +67 -46
  12. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/dispatcher/saga.py +8 -4
  13. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/dispatcher/streaming.py +70 -47
  14. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/events/bootstrap.py +9 -0
  15. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/events/event_emitter.py +90 -51
  16. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/mediator.py +114 -10
  17. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/requests/bootstrap.py +29 -50
  18. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/saga/bootstrap.py +20 -32
  19. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/saga/compensation.py +77 -15
  20. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/saga/execution.py +141 -125
  21. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/saga/recovery.py +29 -10
  22. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/saga/saga.py +49 -19
  23. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/python_cqrs.egg-info/PKG-INFO +68 -26
  24. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/python_cqrs.egg-info/SOURCES.txt +2 -0
  25. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/python_cqrs.egg-info/requires.txt +5 -0
  26. python_cqrs-4.13.0/src/cqrs/container/__init__.py +0 -3
  27. python_cqrs-4.13.0/src/cqrs/container/di.py +0 -26
  28. python_cqrs-4.13.0/src/cqrs/container/protocol.py +0 -20
  29. python_cqrs-4.13.0/src/cqrs/dispatcher/event.py +0 -96
  30. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/LICENSE +0 -0
  31. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/setup.cfg +0 -0
  32. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/adapters/__init__.py +0 -0
  33. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/adapters/amqp.py +0 -0
  34. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/adapters/circuit_breaker.py +0 -0
  35. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/adapters/kafka.py +0 -0
  36. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/adapters/protocol.py +0 -0
  37. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/circuit_breaker.py +0 -0
  38. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/compressors/__init__.py +0 -0
  39. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/compressors/protocol.py +0 -0
  40. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/compressors/zlib.py +0 -0
  41. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/container/dependency_injector.py +0 -0
  42. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/deserializers/__init__.py +0 -0
  43. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/deserializers/exceptions.py +0 -0
  44. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/deserializers/json.py +0 -0
  45. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/dispatcher/__init__.py +0 -0
  46. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/dispatcher/exceptions.py +0 -0
  47. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/dispatcher/models.py +0 -0
  48. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/events/__init__.py +0 -0
  49. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/events/event.py +0 -0
  50. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/events/event_handler.py +0 -0
  51. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/events/event_processor.py +0 -0
  52. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/events/fallback.py +0 -0
  53. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/events/map.py +0 -0
  54. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/generic_utils.py +0 -0
  55. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/message_brokers/__init__.py +0 -0
  56. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/message_brokers/amqp.py +0 -0
  57. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/message_brokers/devnull.py +0 -0
  58. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/message_brokers/kafka.py +0 -0
  59. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/message_brokers/protocol.py +0 -0
  60. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/middlewares/__init__.py +0 -0
  61. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/middlewares/base.py +0 -0
  62. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/middlewares/logging.py +0 -0
  63. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/outbox/__init__.py +0 -0
  64. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/outbox/map.py +0 -0
  65. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/outbox/mock.py +0 -0
  66. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/outbox/repository.py +0 -0
  67. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/outbox/sqlalchemy.py +0 -0
  68. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/producer.py +0 -0
  69. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/requests/__init__.py +0 -0
  70. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/requests/cor_request_handler.py +0 -0
  71. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/requests/fallback.py +0 -0
  72. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/requests/map.py +0 -0
  73. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/requests/mermaid.py +0 -0
  74. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/requests/request.py +0 -0
  75. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/requests/request_handler.py +0 -0
  76. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/response.py +0 -0
  77. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/saga/__init__.py +0 -0
  78. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/saga/circuit_breaker.py +0 -0
  79. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/saga/fallback.py +0 -0
  80. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/saga/mermaid.py +0 -0
  81. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/saga/models.py +0 -0
  82. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/saga/step.py +0 -0
  83. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/saga/storage/__init__.py +0 -0
  84. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/saga/storage/enums.py +0 -0
  85. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/saga/storage/memory.py +0 -0
  86. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/saga/storage/models.py +0 -0
  87. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/saga/storage/protocol.py +0 -0
  88. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/saga/storage/sqlalchemy.py +0 -0
  89. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/saga/validation.py +0 -0
  90. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/serializers/__init__.py +0 -0
  91. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/serializers/default.py +0 -0
  92. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/sqlalchemy_types.py +0 -0
  93. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/cqrs/types.py +0 -0
  94. {python_cqrs-4.13.0 → python_cqrs-4.14.0}/src/python_cqrs.egg-info/dependency_links.txt +0 -0
  95. {python_cqrs-4.13.0 → python_cqrs-4.14.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.13.0
3
+ Version: 4.14.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,7 @@ 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"
51
52
  Provides-Extra: examples
52
53
  Requires-Dist: fastapi==0.109.*; extra == "examples"
53
54
  Requires-Dist: faststream[kafka]==0.5.28; extra == "examples"
@@ -56,6 +57,7 @@ Requires-Dist: uvicorn==0.32.0; extra == "examples"
56
57
  Requires-Dist: aiohttp==3.14.3; extra == "examples"
57
58
  Requires-Dist: protobuf>=4.25.8; extra == "examples"
58
59
  Requires-Dist: aiosqlite>=0.20.0; extra == "examples"
60
+ Requires-Dist: dishka>=1.0; extra == "examples"
59
61
  Provides-Extra: kafka
60
62
  Requires-Dist: aiokafka==0.10.0; extra == "kafka"
61
63
  Requires-Dist: retry-async==0.1.*; extra == "kafka"
@@ -63,6 +65,8 @@ Provides-Extra: rabbit
63
65
  Requires-Dist: aio-pika==9.3.0; extra == "rabbit"
64
66
  Provides-Extra: sqlalchemy
65
67
  Requires-Dist: sqlalchemy[asyncio]==2.0.*; extra == "sqlalchemy"
68
+ Provides-Extra: dishka
69
+ Requires-Dist: dishka>=1.0; extra == "dishka"
66
70
  Dynamic: license-file
67
71
 
68
72
  <div align="center">
@@ -117,6 +121,7 @@ Dynamic: license-file
117
121
  - [Request Handlers](#request-handlers)
118
122
  - [Mapping](#mapping)
119
123
  - [DI container](#di-container)
124
+ - [Scoped dependencies](#scoped-dependencies)
120
125
  - [Bootstrap](#bootstrap)
121
126
  - [Saga Pattern](#saga-pattern)
122
127
  - [Producing Notification Events](#producing-notification-events)
@@ -264,7 +269,7 @@ class CustomResponse(cqrs.IResponse):
264
269
  return cls(result=kwargs["result"], status=kwargs["status"])
265
270
  ```
266
271
 
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)
272
+ 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
273
 
269
274
  ## Request Handlers
270
275
 
@@ -297,7 +302,7 @@ class JoinMeetingCommandHandler(RequestHandler[JoinMeetingCommand, None]):
297
302
  ```
298
303
 
299
304
  A complete example can be found in
300
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/request_handler.py)
305
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/request_handler.py)
301
306
 
302
307
  ### Query handler
303
308
 
@@ -327,7 +332,7 @@ class ReadMeetingQueryHandler(RequestHandler[ReadMeetingQuery, ReadMeetingQueryR
327
332
  ```
328
333
 
329
334
  A complete example can be found in
330
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/request_handler.py)
335
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/request_handler.py)
331
336
 
332
337
  ### Streaming Request Handler
333
338
 
@@ -363,7 +368,7 @@ class ProcessFilesCommandHandler(StreamingRequestHandler[ProcessFilesCommand, Fi
363
368
  ```
364
369
 
365
370
  A complete example can be found in
366
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/streaming_handler_parallel_events.py)
371
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/streaming/streaming_handler_parallel_events.py)
367
372
 
368
373
  ### Chain of Responsibility Request Handler
369
374
 
@@ -426,7 +431,7 @@ def payment_mapper(mapper: cqrs.RequestMap) -> None:
426
431
  ```
427
432
 
428
433
  A complete example can be found in
429
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/cor_request_handler.py)
434
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/cor/cor_request_handler.py)
430
435
 
431
436
  #### Mermaid Diagram Generation
432
437
 
@@ -446,7 +451,7 @@ sequence_diagram = generator.sequence()
446
451
  class_diagram = generator.class_diagram()
447
452
  ```
448
453
 
449
- Complete example: [CoR Mermaid Diagrams](https://github.com/vadikko2/python-cqrs/blob/master/examples/cor_mermaid.py)
454
+ Complete example: [CoR Mermaid Diagrams](https://github.com/vadikko2/python-cqrs/blob/master/examples/cor/cor_mermaid.py)
450
455
 
451
456
  ## Mapping
452
457
 
@@ -507,7 +512,11 @@ def saga_mapper(mapper: cqrs.SagaMap) -> None:
507
512
  Use the following example to set up dependency injection in your command, query and event handlers. This will make
508
513
  dependency management simpler.
509
514
 
510
- The package supports two DI container libraries:
515
+ The package supports three DI container libraries:
516
+
517
+ - [`di`](#di-library) (default)
518
+ - [`dependency-injector`](#dependency-injector-library)
519
+ - [dishka](https://github.com/reagento/dishka) via `DishkaCQRSContainer` (`pip install python-cqrs[dishka]`)
511
520
 
512
521
  ### di library
513
522
 
@@ -536,7 +545,7 @@ def setup_di() -> di.Container:
536
545
  ```
537
546
 
538
547
  A complete example can be found in
539
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/dependency_injection.py)
548
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/di/di_basic.py)
540
549
 
541
550
  ### dependency-injector library
542
551
 
@@ -552,7 +561,8 @@ class ApplicationContainer(containers.DeclarativeContainer):
552
561
  service = providers.Factory(ServiceImplementation)
553
562
 
554
563
  # Create CQRS container adapter
555
- cqrs_container = DependencyInjectorCQRSContainer(ApplicationContainer())
564
+ cqrs_container = DependencyInjectorCQRSContainer()
565
+ cqrs_container.attach_external_container(ApplicationContainer())
556
566
 
557
567
  # Use with bootstrap
558
568
  mediator = bootstrap.bootstrap(
@@ -563,8 +573,32 @@ mediator = bootstrap.bootstrap(
563
573
  ```
564
574
 
565
575
  Complete examples can be found in:
566
- - [Simple example](https://github.com/vadikko2/python-cqrs/blob/master/examples/dependency_injector_integration_simple_example.py)
567
- - [Practical example with FastAPI](https://github.com/vadikko2/python-cqrs/blob/master/examples/dependency_injector_integration_practical_example.py)
576
+ - [Simple example](https://github.com/vadikko2/python-cqrs/blob/master/examples/di/dependency_injector_simple.py)
577
+ - [Practical example with FastAPI](https://github.com/vadikko2/python-cqrs/blob/master/examples/di/dependency_injector_practical.py)
578
+
579
+ ### Scoped dependencies
580
+
581
+ 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.
582
+
583
+ Pass `scope_strategy=ScopeStrategy.SEND` and that session lives until `mediator.send()` returns — including domain-event handlers that run afterwards.
584
+
585
+ If you do not use generator providers, leave the default `ScopeStrategy.NONE` and change nothing.
586
+
587
+ ```python
588
+ mediator = bootstrap.bootstrap(
589
+ di_container=container,
590
+ commands_mapper=commands_mapper,
591
+ scope_strategy=cqrs.ScopeStrategy.SEND,
592
+ )
593
+ ```
594
+
595
+ 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`.
596
+
597
+ - **SEND** — one UoW per `send()` (command, fallback, and domain events).
598
+ - **HANDLER** — a fresh UoW per handler, including fallback (the primary rolls back first).
599
+ - **NONE** — legacy one-shot resolve; the default.
600
+
601
+ `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
602
 
569
603
  ## Bootstrap
570
604
 
@@ -729,6 +763,7 @@ await recover_saga(
729
763
  context_builder=OrderContext,
730
764
  container=di_container, # Same container used in bootstrap
731
765
  storage=storage,
766
+ scope_strategy=cqrs.ScopeStrategy.SEND, # Match the original run
732
767
  )
733
768
 
734
769
  # Access execution history (SagaLog) for monitoring and debugging
@@ -761,7 +796,7 @@ sequence_diagram = generator.sequence()
761
796
  class_diagram = generator.class_diagram()
762
797
  ```
763
798
 
764
- Complete example: [Saga Mermaid Diagrams](https://github.com/vadikko2/python-cqrs/blob/master/examples/saga_mermaid.py)
799
+ Complete example: [Saga Mermaid Diagrams](https://github.com/vadikko2/python-cqrs/blob/master/examples/saga/saga_mermaid.py)
765
800
 
766
801
  ## Producing Notification Events
767
802
 
@@ -801,7 +836,7 @@ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
801
836
  ```
802
837
 
803
838
  A complete example can be found in
804
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/event_producing.py)
839
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/event_producing.py)
805
840
 
806
841
  After processing the command/request, if there are any Notification/ECST events,
807
842
  the EventEmitter is invoked to produce the events via the message broker.
@@ -872,10 +907,10 @@ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
872
907
  ```
873
908
 
874
909
  A complete example can be found in
875
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/save_events_into_outbox.py)
910
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/outbox/save_events_into_outbox.py)
876
911
 
877
912
  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)
913
+ [fastapi_outbox.py](https://github.com/vadikko2/python-cqrs/blob/master/examples/outbox/fastapi_outbox.py)
879
914
 
880
915
  > [!TIP]
881
916
  > The Outbox table is named `outbox` by default. To rename it, or to attach the model to your own declarative
@@ -888,9 +923,13 @@ A runnable FastAPI flow (route → command → transactional outbox → publishe
888
923
  > [Database Support](https://mkdocs.python-cqrs.dev/outbox/databases/) docs for the DDL, the Alembic recipe and
889
924
  > how to register a native type for your own database.
890
925
  > [!TIP]
891
- > If you use the protobuf events you should specify `OutboxedEventRepository`
892
- > by [protobuf serialize](https://github.com/vadikko2/python-cqrs/blob/master/src/cqrs/serializers/protobuf.py). A complete example can be found in
893
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/save_proto_events_into_outbox.py)
926
+ > For Protobuf events, implement `proto()` / `from_proto()` on the notification
927
+ > event (see [Protobuf messaging](#protobuf-messaging) and the schema assets in
928
+ > [examples/proto/](https://github.com/vadikko2/python-cqrs/tree/master/examples/proto)).
929
+ > Outbox persistence is unchanged — use
930
+ > [save_events_into_outbox.py](https://github.com/vadikko2/python-cqrs/blob/master/examples/outbox/save_events_into_outbox.py);
931
+ > Protobuf applies when producing to the broker. 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
 
@@ -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 two DI container libraries:
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/dependency_injection.py)
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(ApplicationContainer())
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/dependency_injector_integration_simple_example.py)
500
- - [Practical example with FastAPI](https://github.com/vadikko2/python-cqrs/blob/master/examples/dependency_injector_integration_practical_example.py)
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,13 @@ 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
- > If you use the protobuf events you should specify `OutboxedEventRepository`
825
- > by [protobuf serialize](https://github.com/vadikko2/python-cqrs/blob/master/src/cqrs/serializers/protobuf.py). A complete example can be found in
826
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/save_proto_events_into_outbox.py)
855
+ > For Protobuf events, implement `proto()` / `from_proto()` on the notification
856
+ > event (see [Protobuf messaging](#protobuf-messaging) and the schema assets in
857
+ > [examples/proto/](https://github.com/vadikko2/python-cqrs/tree/master/examples/proto)).
858
+ > Outbox persistence is unchanged — use
859
+ > [save_events_into_outbox.py](https://github.com/vadikko2/python-cqrs/blob/master/examples/outbox/save_events_into_outbox.py);
860
+ > Protobuf applies when producing to the broker. Full guide:
861
+ > [Protobuf Integration](https://mkdocs.python-cqrs.dev/protobuf/).
827
862
 
828
863
  ## Producing Events from Outbox to Kafka
829
864
 
@@ -862,7 +897,7 @@ async with session_factory() as session:
862
897
  ```
863
898
 
864
899
  A complete example can be found in
865
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/kafka_outboxed_event_producing.py)
900
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/outbox/kafka_outboxed_event_producing.py)
866
901
 
867
902
  ## Transaction log tailing
868
903
 
@@ -905,7 +940,7 @@ class UserJoinedEventHandler(cqrs.EventHandler[UserJoined]):
905
940
  ```
906
941
 
907
942
  A complete example can be found in
908
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/domain_event_handler.py)
943
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/domain_event_handler.py)
909
944
 
910
945
  ### Parallel Event Processing
911
946
 
@@ -936,6 +971,7 @@ mediator = bootstrap.bootstrap_streaming(
936
971
  > - Set `max_concurrent_event_handlers` to limit the number of simultaneously running event handlers
937
972
  > - Set `concurrent_event_handle_enable=False` to disable parallel processing and process events sequentially
938
973
  > - The default value for `max_concurrent_event_handlers` is `10` for `StreamingRequestMediator` and `1` for `RequestMediator`
974
+ > - `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
975
 
940
976
  ## Integration with presentation layers
941
977
 
@@ -973,10 +1009,10 @@ async def join_metting(
973
1009
  ```
974
1010
 
975
1011
  A complete example can be found in
976
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/fastapi_integration.py)
1012
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/fastapi_integration.py)
977
1013
 
978
1014
  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)
1015
+ [fastapi_outbox.py](https://github.com/vadikko2/python-cqrs/blob/master/examples/outbox/fastapi_outbox.py)
980
1016
 
981
1017
  ### Kafka events consuming
982
1018
 
@@ -1024,7 +1060,7 @@ async def hello_world_event_handler(
1024
1060
  ```
1025
1061
 
1026
1062
  A complete example can be found in
1027
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/kafka_event_consuming.py)
1063
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/events/kafka_event_consuming.py)
1028
1064
 
1029
1065
  ### FastAPI SSE Streaming
1030
1066
 
@@ -1045,7 +1081,7 @@ def streaming_mediator_factory() -> cqrs.StreamingRequestMediator:
1045
1081
  domain_events_mapper=domain_events_mapper,
1046
1082
  message_broker=broker,
1047
1083
  max_concurrent_event_handlers=3,
1048
- concurrent_event_handle_enable=True,
1084
+ concurrent_event_handle_enable=True, # HANDLER/NONE only; SEND forbids this
1049
1085
  )
1050
1086
 
1051
1087
  @app.post("/process-files")
@@ -1072,7 +1108,9 @@ async def process_files_stream(
1072
1108
  ```
1073
1109
 
1074
1110
  A complete example can be found in
1075
- the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/fastapi_sse_streaming.py)
1111
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/streaming/fastapi_sse_streaming.py)
1112
+
1113
+ If the client disconnects, close the iterator (`aclose()` / `async with aclosing(...)`) so a `SEND` UoW is not held until GC.
1076
1114
 
1077
1115
  ## Protobuf messaging
1078
1116
 
@@ -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.13.0"
34
+ version = "4.14.0"
35
35
 
36
36
  [project.optional-dependencies]
37
37
  aiobreaker = ["aiobreaker>=0.3.0"]
@@ -59,6 +59,7 @@ 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
62
63
  ]
63
64
  examples = [
64
65
  "fastapi==0.109.*",
@@ -68,10 +69,12 @@ examples = [
68
69
  "aiohttp==3.14.3",
69
70
  "protobuf>=4.25.8",
70
71
  "aiosqlite>=0.20.0",
72
+ "dishka>=1.0",
71
73
  ]
72
74
  kafka = ["aiokafka==0.10.0", "retry-async==0.1.*"]
73
75
  rabbit = ["aio-pika==9.3.0"]
74
76
  sqlalchemy = ["sqlalchemy[asyncio]==2.0.*"]
77
+ dishka = ["dishka>=1.0"]
75
78
 
76
79
  [project.urls]
77
80
  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
@@ -105,7 +112,13 @@ __all__ = (
105
112
  "SqlAlchemyOutboxedEventRepository",
106
113
  "EventProducer",
107
114
  "Container",
115
+ "SupportsScope",
108
116
  "DIContainer",
117
+ "ScopeAwareContainer",
118
+ "ScopeStrategy",
119
+ "bind_scope",
120
+ "current_container",
121
+ "enter_scope",
109
122
  "Compressor",
110
123
  "ZlibCompressor",
111
124
  "rebind_outbox_model",
@@ -0,0 +1,18 @@
1
+ from cqrs.container.protocol import Container, SupportsScope
2
+ from cqrs.container.scope import (
3
+ ScopeAwareContainer,
4
+ ScopeStrategy,
5
+ bind_scope,
6
+ current_container,
7
+ enter_scope,
8
+ )
9
+
10
+ __all__ = (
11
+ "Container",
12
+ "SupportsScope",
13
+ "ScopeAwareContainer",
14
+ "ScopeStrategy",
15
+ "bind_scope",
16
+ "current_container",
17
+ "enter_scope",
18
+ )