python-event-sourcery 0.5.3a4__tar.gz → 0.6.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 (233) hide show
  1. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/.github/workflows/main.yml +12 -2
  2. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/AGENTS.md +13 -6
  3. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/CHANGELOG.md +9 -0
  4. python_event_sourcery-0.6.0/CONTRIBUTING.md +54 -0
  5. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/PKG-INFO +3 -1
  6. python_event_sourcery-0.6.0/docs/adr/20260926-typed-event-context-components-and-enrichers.md +204 -0
  7. python_event_sourcery-0.6.0/docs/code/test_context_enrichers.py +40 -0
  8. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/code/test_get_started.py +5 -3
  9. python_event_sourcery-0.6.0/docs/code/test_otel.py +38 -0
  10. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/get_started.md +11 -1
  11. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/index.md +4 -0
  12. python_event_sourcery-0.6.0/docs/recipes/context_enrichment.md +17 -0
  13. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/recipes/integrate.md +1 -0
  14. python_event_sourcery-0.6.0/docs/recipes/opentelemetry.md +25 -0
  15. python_event_sourcery-0.6.0/docs/reference/event_store/event/ContextComponent.md +1 -0
  16. python_event_sourcery-0.6.0/docs/reference/event_store/event/EventContextEnricher.md +1 -0
  17. python_event_sourcery-0.6.0/docs/reference/event_store/event/EventCorrelation.md +1 -0
  18. python_event_sourcery-0.6.0/docs/reference/otel/OpenTelemetryCarrier.md +1 -0
  19. python_event_sourcery-0.6.0/docs/reference/otel/OpenTelemetryContextEnricher.md +1 -0
  20. python_event_sourcery-0.6.0/docs/reference/otel/get_parent_context.md +1 -0
  21. python_event_sourcery-0.6.0/docs/reference/otel/use_parent_context.md +1 -0
  22. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/backend.py +18 -1
  23. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/event/dto.py +59 -13
  24. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/event/serde.py +22 -12
  25. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/event_store.py +44 -4
  26. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/event.py +6 -0
  27. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/exceptions.py +5 -0
  28. python_event_sourcery-0.6.0/event_sourcery_otel/__init__.py +12 -0
  29. python_event_sourcery-0.6.0/event_sourcery_otel/_carrier.py +9 -0
  30. python_event_sourcery-0.6.0/event_sourcery_otel/_context.py +38 -0
  31. python_event_sourcery-0.6.0/event_sourcery_otel/_enricher.py +27 -0
  32. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/pyproject.toml +85 -1
  33. python_event_sourcery-0.6.0/tests/AGENTS.md +95 -0
  34. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/bdd.py +9 -1
  35. python_event_sourcery-0.6.0/tests/event_store/event/test_dto.py +100 -0
  36. python_event_sourcery-0.6.0/tests/event_store/test_context_enrichment.py +174 -0
  37. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_save_retrieval.py +28 -14
  38. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/matchers.py +28 -3
  39. python_event_sourcery-0.6.0/tests/otel/AGENTS.md +23 -0
  40. python_event_sourcery-0.6.0/tests/otel/bdd.py +67 -0
  41. python_event_sourcery-0.6.0/tests/otel/conftest.py +15 -0
  42. python_event_sourcery-0.6.0/tests/otel/test_context_propagation.py +64 -0
  43. python_event_sourcery-0.6.0/tests/otel/test_parent_context.py +73 -0
  44. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/uv.lock +18 -2
  45. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/zensical.toml +13 -3
  46. python_event_sourcery-0.5.3a4/CONTRIBUTING.md +0 -40
  47. python_event_sourcery-0.5.3a4/Makefile +0 -20
  48. python_event_sourcery-0.5.3a4/tests/event_store/event/test_dto.py +0 -30
  49. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/.adr-dir +0 -0
  50. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/.github/workflows/bump-version-and-publish.yml +0 -0
  51. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/.github/workflows/deploy-docs.yml +0 -0
  52. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/.github/workflows/publish-release.yml +0 -0
  53. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/.gitignore +0 -0
  54. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/LICENSE +0 -0
  55. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/README.md +0 -0
  56. /python_event_sourcery-0.5.3a4/docker-compose.yml → /python_event_sourcery-0.6.0/compose.yml +0 -0
  57. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/django_manage.py +0 -0
  58. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20220730-record-architecture-decisions.md +0 -0
  59. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20230903-explicit-init-arguments-for-sqlalchemy-s-models.md +0 -0
  60. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20231027_1-packaging-of-the-project.md +0 -0
  61. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20231027_2-leave-implementation-of-synchronous-dispatcher-to-users-of-the-library.md +0 -0
  62. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20231029-in-memory-event-store.md +0 -0
  63. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20231230-in_transaction_subscription.md +0 -0
  64. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20240726-dependencies-and-distribution.md +0 -0
  65. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20241007-multitenancy.md +0 -0
  66. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20250411-packaging-of-the-project-revised.md +0 -0
  67. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20250729-privacy-data-encryption-and-shredding.md +0 -0
  68. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20251004-packaging-of-the-event_store-package.md +0 -0
  69. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20260314-aggregate-metadata.md +0 -0
  70. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/code/__init__.py +0 -0
  71. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/code/recipes/__init__.py +0 -0
  72. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/code/recipes/integrate/__init__.py +0 -0
  73. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/code/recipes/integrate/kurrent_01.py +0 -0
  74. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/code/test_recipes.py +0 -0
  75. /python_event_sourcery-0.5.3a4/docs/concepts/basics.md → /python_event_sourcery-0.6.0/docs/concepts.md +0 -0
  76. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/recipes/data_privacy.md +0 -0
  77. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/recipes/defining_events.md +0 -0
  78. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/recipes/event_sourcing.md +0 -0
  79. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/recipes/multitenancy.md +0 -0
  80. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/recipes/outbox.md +0 -0
  81. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/recipes/saving_events.md +0 -0
  82. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/recipes/snapshots.md +0 -0
  83. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/recipes/subscriptions.md +0 -0
  84. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/backends/django.md +0 -0
  85. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/backends/in_memory.md +0 -0
  86. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/backends/kurrentdb.md +0 -0
  87. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/backends/sqlalchemy.md +0 -0
  88. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_sourcing/Aggregate.md +0 -0
  89. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_sourcing/Repository.md +0 -0
  90. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_sourcing/WrappedAggregate.md +0 -0
  91. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/DEFAULT_TENANT.md +0 -0
  92. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/EventStore.md +0 -0
  93. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/NO_VERSIONING.md +0 -0
  94. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/StreamCategory.md +0 -0
  95. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/StreamId.md +0 -0
  96. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/StreamUUID.md +0 -0
  97. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/TenantId.md +0 -0
  98. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/backend/Backend.md +0 -0
  99. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/backend/TransactionalBackend.md +0 -0
  100. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/backend/not_configured.md +0 -0
  101. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/backend/singleton.md +0 -0
  102. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/encryption/DataSubject.md +0 -0
  103. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/encryption/Encrypted.md +0 -0
  104. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/encryption/Encryption.md +0 -0
  105. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/encryption/NoEncryptionStrategy.md +0 -0
  106. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/encryption/NoKeyStorageStrategy.md +0 -0
  107. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/event/Context.md +0 -0
  108. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/event/Entry.md +0 -0
  109. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/event/Event.md +0 -0
  110. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/event/Position.md +0 -0
  111. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/event/RawEvent.md +0 -0
  112. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/event/Recorded.md +0 -0
  113. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/event/RecordedRaw.md +0 -0
  114. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/event/WrappedEvent.md +0 -0
  115. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/exceptions.md +0 -0
  116. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/in_transaction/Dispatcher.md +0 -0
  117. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/in_transaction/Listener.md +0 -0
  118. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/in_transaction/Listeners.md +0 -0
  119. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/interfaces/EncryptionKeyStorageStrategy.md +0 -0
  120. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/interfaces/EncryptionStrategy.md +0 -0
  121. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/interfaces/OutboxFiltererStrategy.md +0 -0
  122. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/interfaces/OutboxStorageStrategy.md +0 -0
  123. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/interfaces/StorageStrategy.md +0 -0
  124. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/interfaces/SubscriptionStrategy.md +0 -0
  125. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/outbox/NoOutboxStorageStrategy.md +0 -0
  126. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/outbox/Outbox.md +0 -0
  127. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/outbox/no_filter.md +0 -0
  128. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/subscription/BuildPhase.md +0 -0
  129. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/subscription/FilterPhase.md +0 -0
  130. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/subscription/PositionPhase.md +0 -0
  131. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/subscription/SubscriptionBuilder.md +0 -0
  132. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/res/eda_example.plantuml +0 -0
  133. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/res/eda_example.png +0 -0
  134. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/__init__.py +0 -0
  135. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/__init__.py +0 -0
  136. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/event/__init__.py +0 -0
  137. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/event/encryption.py +0 -0
  138. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/event/registry.py +0 -0
  139. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/in_memory.py +0 -0
  140. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/outbox.py +0 -0
  141. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/stream_id.py +0 -0
  142. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/subscription/__init__.py +0 -0
  143. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/subscription/builder.py +0 -0
  144. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/subscription/in_transaction.py +0 -0
  145. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/subscription/interfaces.py +0 -0
  146. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/tenant_id.py +0 -0
  147. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/versioning.py +0 -0
  148. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/backend.py +0 -0
  149. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/encryption.py +0 -0
  150. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/event_sourcing/__init__.py +0 -0
  151. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/event_sourcing/aggregate.py +0 -0
  152. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/event_sourcing/repository.py +0 -0
  153. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/in_transaction.py +0 -0
  154. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/interfaces.py +0 -0
  155. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/outbox.py +0 -0
  156. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/py.typed +0 -0
  157. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/read_model/__init__.py +0 -0
  158. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/read_model/cursors_dao.py +0 -0
  159. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/read_model/projector.py +0 -0
  160. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/subscription.py +0 -0
  161. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_django/__init__.py +0 -0
  162. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_django/dto.py +0 -0
  163. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_django/event_store.py +0 -0
  164. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_django/migrations/0001_initial.py +0 -0
  165. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_django/migrations/__init__.py +0 -0
  166. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_django/models.py +0 -0
  167. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_django/outbox.py +0 -0
  168. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_django/py.typed +0 -0
  169. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_django/subscription.py +0 -0
  170. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_kurrentdb/__init__.py +0 -0
  171. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_kurrentdb/dto.py +0 -0
  172. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_kurrentdb/event_store.py +0 -0
  173. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_kurrentdb/outbox.py +0 -0
  174. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_kurrentdb/py.typed +0 -0
  175. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_kurrentdb/stream.py +0 -0
  176. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_kurrentdb/subscription.py +0 -0
  177. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/__init__.py +0 -0
  178. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/cursors_dao.py +0 -0
  179. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/dto.py +0 -0
  180. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/event_store.py +0 -0
  181. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/guid.py +0 -0
  182. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/jsonb.py +0 -0
  183. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/models/__init__.py +0 -0
  184. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/models/base.py +0 -0
  185. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/models/default.py +0 -0
  186. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/outbox.py +0 -0
  187. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/py.typed +0 -0
  188. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/subscription.py +0 -0
  189. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/__init__.py +0 -0
  190. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/backend/__init__.py +0 -0
  191. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/backend/django.py +0 -0
  192. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/backend/in_memory.py +0 -0
  193. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/backend/kurrentdb.py +0 -0
  194. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/backend/sqlalchemy.py +0 -0
  195. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/backend/test_sqlalchemy.py +0 -0
  196. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/conftest.py +0 -0
  197. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_sourcing/__init__.py +0 -0
  198. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_sourcing/conftest.py +0 -0
  199. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_sourcing/light_switch.py +0 -0
  200. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_sourcing/test_aggregate.py +0 -0
  201. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_sourcing/test_wrapped_aggregate.py +0 -0
  202. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/__init__.py +0 -0
  203. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/conftest.py +0 -0
  204. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/event/__init__.py +0 -0
  205. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/event/test_privacy.py +0 -0
  206. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/event/test_registry.py +0 -0
  207. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/outbox/__init__.py +0 -0
  208. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/outbox/conftest.py +0 -0
  209. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/outbox/test_filters.py +0 -0
  210. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/outbox/test_multitenancy.py +0 -0
  211. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/outbox/test_outbox.py +0 -0
  212. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/subscriptions/__init__.py +0 -0
  213. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/subscriptions/other_client.py +0 -0
  214. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/subscriptions/test_batch.py +0 -0
  215. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/subscriptions/test_from_position.py +0 -0
  216. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/subscriptions/test_handling_uncommitted_messages_from_other_clients.py +0 -0
  217. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/subscriptions/test_in_transaction_subscription.py +0 -0
  218. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/subscriptions/test_subscriptions.py +0 -0
  219. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/subscriptions/test_to_category.py +0 -0
  220. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/subscriptions/test_to_event_types.py +0 -0
  221. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_category.py +0 -0
  222. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_concurrency_control.py +0 -0
  223. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_multitenancy.py +0 -0
  224. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_named_streams.py +0 -0
  225. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_no_outbox.py +0 -0
  226. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_no_versioning.py +0 -0
  227. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_removing_stream.py +0 -0
  228. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_snapshots.py +0 -0
  229. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_stream_id.py +0 -0
  230. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/factories.py +0 -0
  231. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/mark.py +0 -0
  232. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/read_model/__init__.py +0 -0
  233. {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/read_model/test_projector.py +0 -0
@@ -41,6 +41,12 @@ jobs:
41
41
  - 1113:1113
42
42
  - 2113:2113
43
43
 
44
+ rabbitmq:
45
+ image: rabbitmq:3.11.2
46
+ ports:
47
+ - '5672:5672'
48
+ - '15672:15672'
49
+
44
50
  steps:
45
51
  - uses: actions/checkout@v2
46
52
  - name: Install uv
@@ -48,11 +54,15 @@ jobs:
48
54
  with:
49
55
  python-version: ${{ matrix.python-version }}
50
56
  enable-cache: true
57
+ - name: Install poe
58
+ run: uv tool install poethepoet
51
59
  - name: Install deps
52
60
  run: uv sync --all-extras
53
61
  - name: Run linters
54
- run: make lint
62
+ run: poe lint-fix
55
63
  - name: Build docs
56
64
  run: uv run --all-extras zensical build --clean
65
+ - name: Test docs code snippets
66
+ run: poe docs-test
57
67
  - name: Test with pytest
58
- run: make test
68
+ run: poe test
@@ -21,9 +21,10 @@ uv sync --all-extras # install all deps (dev group is included by default)
21
21
 
22
22
  ```bash
23
23
  docker compose up -d # required before tests (postgres, kurrentdb, rabbitmq)
24
- make test # pytest with coverage (fail_under = 100)
25
- make lint # ruff format + ruff check + mypy (must pass clean)
26
- make docs-serve # docs live preview
24
+ poe test # pytest with coverage (fail_under = 100)
25
+ poe lint-fix # ruff format + ruff check + mypy (must pass clean)
26
+ poe docs-test # pytest docs/code/ — snippets embedded in docs must pass
27
+ poe docs-serve # docs live preview
27
28
  ```
28
29
 
29
30
  Run single tools via `uv run --all-extras <tool>`, e.g. `uv run --all-extras pytest tests/event_store -k django`.
@@ -34,12 +35,18 @@ Run single tools via `uv run --all-extras <tool>`, e.g. `uv run --all-extras pyt
34
35
  - New backend functionality goes through the shared test suite in `tests/`; use `not_implemented` / `skip_backend` markers for unsupported features.
35
36
  - Version bumps: `uv version --bump alpha` (CI autopublishes prereleases on push to main).
36
37
 
38
+ ## Tests
39
+
40
+ See [`tests/AGENTS.md`](tests/AGENTS.md) for required TDD workflow and test style.
41
+
37
42
  ## Docs
38
43
 
39
- Changing public API or behavior may require updating `docs/` — pages embed runnable snippets from `docs/code/` (via `--8<--` includes). After touching docs code:
44
+ Changing public API or behavior **requires** updating `docs/` and running docs tests —
45
+ pages embed runnable snippets from `docs/code/` (via `--8<--` includes), and broken
46
+ snippets go unnoticed without them. Docs tests need no docker services. After touching docs code:
40
47
 
41
48
  ```bash
42
- uv run --all-extras pytest docs/code # snippets are real tests, must pass
43
- make lint-fix-docs # lint docs/code
49
+ poe docs-test # snippets are real tests, must pass (enforced in CI)
50
+ poe docs-lint-fix # lint docs/code
44
51
  uv run --all-extras zensical build --clean # verify docs build
45
52
  ```
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0
4
+ ### Added
5
+ - Typed, independently owned event context components with lazy access through `Context.get()`.
6
+ - Context enrichers configured through `Backend.with_context_enrichers()`.
7
+ - Optional `event_sourcery_otel` package for OpenTelemetry context propagation and parent-context activation.
8
+
9
+ ### Changed
10
+ - **Breaking:** New `Context` values reject arbitrary root fields; add new context data with typed components.
11
+
3
12
  ## 0.5.2
4
13
  ### Changed
5
14
  - **Breaking:** `Repository.aggregate()` yields `WrappedAggregate` instead of `Aggregate` directly
@@ -0,0 +1,54 @@
1
+ # Basics
2
+
3
+ ## Prerequisites
4
+
5
+ - docker + docker compose / any compatible replacement
6
+ - uv
7
+ - [poe](https://poethepoet.natn.io/)
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ uv sync --all-extras
13
+ ```
14
+
15
+ ## Running tests
16
+
17
+ ```bash
18
+ docker compose up
19
+ poe test
20
+ ```
21
+
22
+ ## Running linters
23
+
24
+ ```bash
25
+ poe lint-fix
26
+ ```
27
+
28
+ # Documentation
29
+
30
+ First, `cd docs/documentation`
31
+
32
+ ## Testing docs code snippets
33
+
34
+ Snippets embedded in docs pages (`docs/documentation/`) are real tests living in
35
+ `docs/code/`. They must pass — otherwise the docs silently show broken code.
36
+ No docker services required (they run on in-memory/SQLite backends):
37
+
38
+ ```bash
39
+ poe docs-test # or: uv run --all-extras pytest docs/code/
40
+ poe docs-lint-fix # ruff format + check for docs/code/
41
+ ```
42
+
43
+ Run them whenever you change public API, behavior, or anything in `docs/code/`.
44
+
45
+ ## Serve docs with live preview
46
+
47
+ ```bash
48
+ poe docs-serve
49
+ ```
50
+
51
+ ## Deploy docs to github pages
52
+
53
+ Docs get auto deployed from main after each change.
54
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-event-sourcery
3
- Version: 0.5.3a4
3
+ Version: 0.6.0
4
4
  Summary: A library for event-based systems in Python.
5
5
  Author-email: Sebastian Buczyński <nnplaya@gmail.com>, Łukasz Żukowski <lukasz.zukowski@gmail.com>
6
6
  License-Expression: MIT
@@ -13,6 +13,8 @@ Provides-Extra: django
13
13
  Requires-Dist: django<6,>=4; extra == 'django'
14
14
  Provides-Extra: kurrentdb
15
15
  Requires-Dist: kurrentdbclient>=1.0.6; extra == 'kurrentdb'
16
+ Provides-Extra: otel
17
+ Requires-Dist: opentelemetry-api<2,>=1; extra == 'otel'
16
18
  Provides-Extra: sqlalchemy
17
19
  Requires-Dist: sqlalchemy>=2.0; extra == 'sqlalchemy'
18
20
  Description-Content-Type: text/markdown
@@ -0,0 +1,204 @@
1
+ # Typed Event Context Components and Enrichers
2
+
3
+ Date: 2026-09-26
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ Discussion: [GitHub issue #115](https://github.com/python-event-sourcery/python-event-sourcery/issues/115)
10
+
11
+ ## Problem Statement
12
+
13
+ `Context` currently stores `correlation_id`, `causation_id`, and arbitrary extra
14
+ fields. Applications can validate the complete context as their own `Context`
15
+ subclass, but optional integrations cannot add strongly typed data without knowing
16
+ that complete application-specific type.
17
+
18
+ OpenTelemetry is the initial use case. Its integration needs to persist a typed
19
+ propagation carrier alongside existing application context. The same mechanism may
20
+ support request, tenant, authentication, and other integration-owned context data.
21
+
22
+ Python cannot express an accumulated type such as `ApplicationContext &
23
+ OpenTelemetryContext & RequestContext`. Each integration therefore needs to own and
24
+ type only its part of the event context.
25
+
26
+ ## Decision
27
+
28
+ ### Context components
29
+
30
+ Introduce a common Pydantic base for independently typed context components. Every
31
+ component declares an explicit, stable wire name:
32
+
33
+ ```python
34
+ class ContextComponent(BaseModel):
35
+ __context_name__: ClassVar[str]
36
+
37
+
38
+ class OpenTelemetryCarrier(ContextComponent):
39
+ __context_name__ = "opentelemetry"
40
+
41
+ values: dict[str, str]
42
+ ```
43
+
44
+ `Context` stores extension payloads in a dedicated `components` mapping. Its keys
45
+ are component wire names and its values are JSON-compatible component payloads:
46
+
47
+ ```json
48
+ {
49
+ "correlation_id": "...",
50
+ "causation_id": "...",
51
+ "components": {
52
+ "opentelemetry": {
53
+ "values": {
54
+ "traceparent": "00-...",
55
+ "tracestate": "..."
56
+ }
57
+ },
58
+ "application.request": {
59
+ "user_id": "123",
60
+ "request_id": "..."
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ The mapping prevents duplicate components by construction and keeps new
67
+ integration-owned data separate from root context fields.
68
+
69
+ ### Typed access and serialization
70
+
71
+ `Context` exposes lazy typed access:
72
+
73
+ ```python
74
+ otel = context.get(OpenTelemetryCarrier)
75
+ # OpenTelemetryCarrier | None
76
+ ```
77
+
78
+ `get()` derives the wire name from the requested component type, finds that
79
+ payload in `components`, and validates it with `component_type.model_validate()`.
80
+ `Context.with_component(component)` returns a new context containing the component
81
+ payload; it does not mutate the existing context.
82
+
83
+ `Serde` persists and restores `components` as raw JSON-compatible data. It does
84
+ not eagerly materialize component classes. Consequently, this change does not add
85
+ `ContextRegistry` or a generic registry abstraction: the caller supplies the
86
+ component class when it requests a typed value. An event written with an optional
87
+ integration can therefore be read and round-tripped by a process that does not
88
+ install that integration.
89
+
90
+ ### Context enrichers
91
+
92
+ An enricher declares the component type it owns and produces that component for an
93
+ event when applicable:
94
+
95
+ ```python
96
+ class EventContextEnricher(Protocol):
97
+ component_type: type[ContextComponent]
98
+
99
+ def enrich(
100
+ self,
101
+ context: Context,
102
+ *,
103
+ event: WrappedEvent,
104
+ stream_id: StreamId,
105
+ ) -> ContextComponent | None:
106
+ ...
107
+ ```
108
+
109
+ Enrichers are configured through `Backend.with_context_enrichers()` and run in
110
+ `EventStore._append()` after a `WrappedEvent` and its `Context` already exist, but
111
+ before serialization:
112
+
113
+ ```text
114
+ existing Context
115
+ -> context enrichers
116
+ -> Serde
117
+ -> StorageStrategy
118
+ ```
119
+
120
+ An enricher returns only the component it owns. It does not replace or mutate the
121
+ complete context. Enrichment happens once when a new event is written; it does not
122
+ run during loading, replay, subscription processing, or outbox retries.
123
+
124
+ A component already present on the incoming context is explicit and takes
125
+ precedence over enricher output. Two enrichers declaring the same component are
126
+ rejected by `with_context_enrichers()` before events can be appended.
127
+
128
+ ## Backward compatibility
129
+
130
+ ### Root correlation fields
131
+
132
+ The existing `correlation_id` and `causation_id` fields remain at the context root.
133
+ They are exposed through the component API as a special built-in
134
+ `EventCorrelation` component:
135
+
136
+ ```python
137
+ correlation = context.get(EventCorrelation)
138
+ ```
139
+
140
+ `EventCorrelation` is a typed view over the existing root fields.
141
+ `Context.with_component(EventCorrelation(...))` updates those root fields rather
142
+ than adding a nested component key.
143
+
144
+ ### Existing API and stored events
145
+
146
+ New `Context` values accept the root correlation fields and typed components. New
147
+ integration data is added with `Context.with_component()` rather than arbitrary
148
+ root fields:
149
+
150
+ ```python
151
+ context = Context(correlation_id=...)
152
+ context = context.with_component(RequestContext(user_id="123"))
153
+ ```
154
+
155
+ Existing stored events remain readable without data migration. Historical root
156
+ fields are retained when an event is deserialized, and existing whole-context
157
+ validation remains available:
158
+
159
+ ```python
160
+ wrapped_event.get_context(ApplicationContext)
161
+ ```
162
+
163
+ If `ApplicationContext` declares `components`, it receives their raw payloads;
164
+ otherwise `components` are excluded for compatibility with models using
165
+ `extra="forbid"`. Unknown keys within persisted `components` remain opaque raw
166
+ payloads, allowing processes without an optional integration to read and
167
+ round-trip their events.
168
+
169
+ This gives two compatible views:
170
+
171
+ ```text
172
+ Legacy: validate the complete historical context as an application model
173
+ Typed: retrieve an independent component by its class
174
+ ```
175
+
176
+ ## OpenTelemetry application
177
+
178
+ The optional `event_sourcery_otel` package defines an `OpenTelemetryCarrier` and
179
+ an enricher without adding an OpenTelemetry dependency to the core package:
180
+
181
+ ```text
182
+ current OpenTelemetry Context
183
+ -> propagator.inject()
184
+ -> OpenTelemetryCarrier
185
+ -> persisted event Context
186
+ ```
187
+
188
+ The enricher only snapshots the active propagation context. Consumers can
189
+ explicitly obtain or activate a persisted parent context through
190
+ `get_parent_context()` and `use_parent_context()`. Creating spans and deciding
191
+ their lifecycle remain application concerns.
192
+
193
+
194
+ ## Consequences
195
+
196
+ - Integrations can own strongly typed context data independently.
197
+ - Application-defined context and integration-defined components can coexist.
198
+ - Existing root fields, custom context models, and stored events remain compatible.
199
+ - Component validation happens lazily when code requests a component class.
200
+ - `EventStore` gains a context enrichment step before serialization.
201
+ - Stable component names become part of the persisted event format and must not be
202
+ changed without an explicit migration strategy.
203
+ - New integration data cannot collide with legacy root fields because it is stored
204
+ under `components`.
@@ -0,0 +1,40 @@
1
+ from typing import ClassVar
2
+
3
+ from event_sourcery import StreamId
4
+ from event_sourcery.backend import InMemoryBackend
5
+ from event_sourcery.event import Context, ContextComponent, Event, WrappedEvent
6
+
7
+
8
+ def test_context_enrichers() -> None:
9
+ # --8<-- [start:context_enrichers_01]
10
+ class RequestContext(ContextComponent):
11
+ __context_name__: ClassVar[str] = "application.request"
12
+
13
+ request_id: str
14
+
15
+ class RequestContextEnricher:
16
+ component_type: type[ContextComponent] = RequestContext
17
+
18
+ def enrich(
19
+ self,
20
+ context: Context,
21
+ *,
22
+ event: WrappedEvent,
23
+ stream_id: StreamId,
24
+ ) -> RequestContext:
25
+ return RequestContext(request_id="request-123")
26
+
27
+ class InvoicePaid(Event):
28
+ invoice_number: str
29
+
30
+ backend = InMemoryBackend().with_context_enrichers(RequestContextEnricher())
31
+ event_store = backend.event_store
32
+ stream_id = StreamId(name="invoices/1003")
33
+
34
+ event_store.append(InvoicePaid(invoice_number="1003"), stream_id=stream_id)
35
+
36
+ recorded = event_store.load_stream(stream_id)[0]
37
+ assert recorded.context.get(RequestContext) == RequestContext(
38
+ request_id="request-123"
39
+ )
40
+ # --8<-- [end:context_enrichers_01]
@@ -14,10 +14,12 @@ def test_get_started():
14
14
  InvoicePaid(invoice_number="1003"),
15
15
  stream_id=StreamId(name="invoices/1003"),
16
16
  )
17
-
18
- for recorded in event_store.load_stream(StreamId(name="invoices/1003")):
19
- print(recorded.event, recorded.version)
20
17
  # --8<-- [end:get_started_01]
18
+ # --8<-- [start:get_started_02]
19
+ recorded_events = event_store.load_stream(StreamId(name="invoices/1003"))
20
+ for recorded in recorded_events:
21
+ print(recorded.event, recorded.version)
22
+ # --8<-- [end:get_started_02]
21
23
 
22
24
  events = event_store.load_stream(StreamId(name="invoices/1003"))
23
25
  assert len(events) == 1
@@ -0,0 +1,38 @@
1
+ from opentelemetry import trace
2
+
3
+ from event_sourcery import StreamId
4
+ from event_sourcery.backend import InMemoryBackend
5
+ from event_sourcery.event import Event
6
+ from event_sourcery_otel import OpenTelemetryContextEnricher, use_parent_context
7
+
8
+
9
+ def test_otel_propagation() -> None:
10
+ # --8<-- [start:otel_01]
11
+ class InvoicePaid(Event):
12
+ invoice_number: str
13
+
14
+ backend = InMemoryBackend().with_context_enrichers(OpenTelemetryContextEnricher())
15
+ event_store = backend.event_store
16
+ stream_id = StreamId(name="invoices/1003")
17
+
18
+ span_context = trace.SpanContext(
19
+ trace_id=0x1234567890ABCDEF1234567890ABCDEF,
20
+ span_id=0x1234567890ABCDEF,
21
+ is_remote=False,
22
+ trace_flags=trace.TraceFlags(trace.TraceFlags.SAMPLED),
23
+ trace_state=trace.TraceState(),
24
+ )
25
+ with trace.use_span(trace.NonRecordingSpan(span_context), end_on_exit=False):
26
+ event_store.append(InvoicePaid(invoice_number="1003"), stream_id=stream_id)
27
+
28
+ recorded = event_store.load_stream(stream_id)[0]
29
+ # --8<-- [end:otel_01]
30
+
31
+ # --8<-- [start:otel_02]
32
+ with use_parent_context(recorded):
33
+ consume(recorded.event)
34
+ # --8<-- [end:otel_02]
35
+
36
+
37
+ def consume(event: Event) -> None:
38
+ assert isinstance(event, Event)
@@ -16,7 +16,7 @@ pip install "python-event-sourcery[sqlalchemy]"
16
16
 
17
17
  The example below uses the in-memory backend, so you can run it right away, without setting up a database.
18
18
 
19
- Define an event by inheriting from the [Event] base class, then append it to a stream and read it back:
19
+ Define an event by inheriting from the [Event] base class, then append it to a stream:
20
20
 
21
21
  ```python
22
22
  --8<--
@@ -24,6 +24,16 @@ docs/code/test_get_started.py:get_started_01
24
24
  --8<--
25
25
  ```
26
26
 
27
+ ## Reading events
28
+
29
+ Events from a given stream can be read back at once:
30
+
31
+ ```python
32
+ --8<--
33
+ docs/code/test_get_started.py:get_started_02
34
+ --8<--
35
+ ```
36
+
27
37
  Output:
28
38
 
29
39
  ```
@@ -1,3 +1,7 @@
1
1
  # Python Event Sourcery
2
2
 
3
3
  A library for event-based systems in Python. For event sourcing, CQRS, and event-driven architectures.
4
+
5
+ ## What's first?
6
+
7
+ - **[Get started guide](get_started.md)** - try out the library in 5 minutes
@@ -0,0 +1,17 @@
1
+ # Enrich event context
2
+
3
+ A context enricher adds a typed component immediately before an event is
4
+ serialized. Configure enrichers on the backend before accessing its event store.
5
+ The original event context is not mutated.
6
+
7
+ ```python
8
+ --8<--
9
+ docs/code/test_context_enrichers.py:context_enrichers_01
10
+ --8<--
11
+ ```
12
+
13
+ Pass multiple enrichers to `with_context_enrichers()` in their execution order.
14
+ Each enricher may return `None` when it has no component to add for an event.
15
+ If two enrichers declare the same component, `with_context_enrichers()` raises
16
+ `ConflictingContextComponent`. A component explicitly supplied with the event
17
+ takes precedence over enricher output.
@@ -61,6 +61,7 @@ docs/code/test_recipes.py:integrate_sql_02
61
61
 
62
62
  You can now use [EventStore] to load events from a stream or append new events.
63
63
 
64
+
64
65
  [Backend]: ../reference/event_store/backend/Backend.md
65
66
  [EventStore]: ../reference/event_store/EventStore.md
66
67
  [SQLAlchemy]: ../reference/backends/sqlalchemy.md
@@ -0,0 +1,25 @@
1
+ # OpenTelemetry context propagation
2
+
3
+ Install the optional integration:
4
+
5
+ ```bash
6
+ pip install "python-event-sourcery[otel]"
7
+ ```
8
+
9
+ Configure its enricher before accessing the event store. It persists the active
10
+ OpenTelemetry propagation context; it does not create or end spans.
11
+
12
+ ```python
13
+ --8<--
14
+ docs/code/test_otel.py:otel_01
15
+ --8<--
16
+ ```
17
+
18
+ When consuming a recorded event, explicitly activate its propagated parent
19
+ context before starting application work or a consumer span:
20
+
21
+ ```python
22
+ --8<--
23
+ docs/code/test_otel.py:otel_02
24
+ --8<--
25
+ ```
@@ -0,0 +1 @@
1
+ ::: event_sourcery.event.ContextComponent
@@ -0,0 +1 @@
1
+ ::: event_sourcery.event.EventContextEnricher
@@ -0,0 +1 @@
1
+ ::: event_sourcery.event.EventCorrelation
@@ -0,0 +1 @@
1
+ ::: event_sourcery_otel.OpenTelemetryCarrier
@@ -0,0 +1 @@
1
+ ::: event_sourcery_otel.OpenTelemetryContextEnricher
@@ -0,0 +1 @@
1
+ ::: event_sourcery_otel.get_parent_context
@@ -0,0 +1 @@
1
+ ::: event_sourcery_otel.use_parent_context
@@ -14,6 +14,7 @@ from event_sourcery._event_store.event.encryption import (
14
14
  from event_sourcery._event_store.event.registry import EventRegistry
15
15
  from event_sourcery._event_store.event.serde import Serde
16
16
  from event_sourcery._event_store.event_store import (
17
+ EventContextEnricher,
17
18
  EventStore,
18
19
  StorageStrategy,
19
20
  )
@@ -36,7 +37,7 @@ from event_sourcery._event_store.subscription.interfaces import (
36
37
  SubscriptionStrategy,
37
38
  )
38
39
  from event_sourcery._event_store.tenant_id import DEFAULT_TENANT, TenantId
39
- from event_sourcery.exceptions import NoProviderConfigured
40
+ from event_sourcery.exceptions import ConflictingContextComponent, NoProviderConfigured
40
41
 
41
42
  T = TypeVar("T")
42
43
  T_co = TypeVar("T_co", covariant=True)
@@ -107,6 +108,10 @@ class _Container:
107
108
  return new
108
109
 
109
110
 
111
+ class ContextEnrichers(tuple[EventContextEnricher, ...]):
112
+ pass
113
+
114
+
110
115
  class Backend(_Container):
111
116
  """
112
117
  Dependency Injection container for Event Sourcery components.
@@ -140,9 +145,11 @@ class Backend(_Container):
140
145
  self[StorageStrategy] = not_configured(
141
146
  "Use one of pyES backends: SQLAlchemy, Django or KurrentDB",
142
147
  )
148
+ self[ContextEnrichers] = ContextEnrichers()
143
149
  self[EventStore] = lambda c: EventStore(
144
150
  storage_strategy=c[StorageStrategy],
145
151
  serde=c[Serde],
152
+ context_enrichers=c[ContextEnrichers],
146
153
  )
147
154
  self[Outbox] = lambda c: Outbox(
148
155
  strategy=c[OutboxStorageStrategy],
@@ -206,6 +213,16 @@ class Backend(_Container):
206
213
  )
207
214
  return self
208
215
 
216
+ def with_context_enrichers(self, *enrichers: EventContextEnricher) -> Self:
217
+ names = set[str]()
218
+ for enricher in enrichers:
219
+ name = enricher.component_type.__context_name__
220
+ if name in names:
221
+ raise ConflictingContextComponent(name)
222
+ names.add(name)
223
+ self[ContextEnrichers] = ContextEnrichers(enrichers)
224
+ return self
225
+
209
226
 
210
227
  class TransactionalBackend(Backend):
211
228
  """