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.
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/.github/workflows/main.yml +12 -2
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/AGENTS.md +13 -6
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/CHANGELOG.md +9 -0
- python_event_sourcery-0.6.0/CONTRIBUTING.md +54 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/PKG-INFO +3 -1
- python_event_sourcery-0.6.0/docs/adr/20260926-typed-event-context-components-and-enrichers.md +204 -0
- python_event_sourcery-0.6.0/docs/code/test_context_enrichers.py +40 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/code/test_get_started.py +5 -3
- python_event_sourcery-0.6.0/docs/code/test_otel.py +38 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/get_started.md +11 -1
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/index.md +4 -0
- python_event_sourcery-0.6.0/docs/recipes/context_enrichment.md +17 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/recipes/integrate.md +1 -0
- python_event_sourcery-0.6.0/docs/recipes/opentelemetry.md +25 -0
- python_event_sourcery-0.6.0/docs/reference/event_store/event/ContextComponent.md +1 -0
- python_event_sourcery-0.6.0/docs/reference/event_store/event/EventContextEnricher.md +1 -0
- python_event_sourcery-0.6.0/docs/reference/event_store/event/EventCorrelation.md +1 -0
- python_event_sourcery-0.6.0/docs/reference/otel/OpenTelemetryCarrier.md +1 -0
- python_event_sourcery-0.6.0/docs/reference/otel/OpenTelemetryContextEnricher.md +1 -0
- python_event_sourcery-0.6.0/docs/reference/otel/get_parent_context.md +1 -0
- python_event_sourcery-0.6.0/docs/reference/otel/use_parent_context.md +1 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/backend.py +18 -1
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/event/dto.py +59 -13
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/event/serde.py +22 -12
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/event_store.py +44 -4
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/event.py +6 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/exceptions.py +5 -0
- python_event_sourcery-0.6.0/event_sourcery_otel/__init__.py +12 -0
- python_event_sourcery-0.6.0/event_sourcery_otel/_carrier.py +9 -0
- python_event_sourcery-0.6.0/event_sourcery_otel/_context.py +38 -0
- python_event_sourcery-0.6.0/event_sourcery_otel/_enricher.py +27 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/pyproject.toml +85 -1
- python_event_sourcery-0.6.0/tests/AGENTS.md +95 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/bdd.py +9 -1
- python_event_sourcery-0.6.0/tests/event_store/event/test_dto.py +100 -0
- python_event_sourcery-0.6.0/tests/event_store/test_context_enrichment.py +174 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_save_retrieval.py +28 -14
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/matchers.py +28 -3
- python_event_sourcery-0.6.0/tests/otel/AGENTS.md +23 -0
- python_event_sourcery-0.6.0/tests/otel/bdd.py +67 -0
- python_event_sourcery-0.6.0/tests/otel/conftest.py +15 -0
- python_event_sourcery-0.6.0/tests/otel/test_context_propagation.py +64 -0
- python_event_sourcery-0.6.0/tests/otel/test_parent_context.py +73 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/uv.lock +18 -2
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/zensical.toml +13 -3
- python_event_sourcery-0.5.3a4/CONTRIBUTING.md +0 -40
- python_event_sourcery-0.5.3a4/Makefile +0 -20
- python_event_sourcery-0.5.3a4/tests/event_store/event/test_dto.py +0 -30
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/.adr-dir +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/.github/workflows/bump-version-and-publish.yml +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/.github/workflows/deploy-docs.yml +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/.github/workflows/publish-release.yml +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/.gitignore +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/LICENSE +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/README.md +0 -0
- /python_event_sourcery-0.5.3a4/docker-compose.yml → /python_event_sourcery-0.6.0/compose.yml +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/django_manage.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20220730-record-architecture-decisions.md +0 -0
- {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
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20231027_1-packaging-of-the-project.md +0 -0
- {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
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20231029-in-memory-event-store.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20231230-in_transaction_subscription.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20240726-dependencies-and-distribution.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20241007-multitenancy.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20250411-packaging-of-the-project-revised.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20250729-privacy-data-encryption-and-shredding.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20251004-packaging-of-the-event_store-package.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/adr/20260314-aggregate-metadata.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/code/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/code/recipes/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/code/recipes/integrate/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/code/recipes/integrate/kurrent_01.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/code/test_recipes.py +0 -0
- /python_event_sourcery-0.5.3a4/docs/concepts/basics.md → /python_event_sourcery-0.6.0/docs/concepts.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/recipes/data_privacy.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/recipes/defining_events.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/recipes/event_sourcing.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/recipes/multitenancy.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/recipes/outbox.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/recipes/saving_events.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/recipes/snapshots.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/recipes/subscriptions.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/backends/django.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/backends/in_memory.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/backends/kurrentdb.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/backends/sqlalchemy.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_sourcing/Aggregate.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_sourcing/Repository.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_sourcing/WrappedAggregate.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/DEFAULT_TENANT.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/EventStore.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/NO_VERSIONING.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/StreamCategory.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/StreamId.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/StreamUUID.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/TenantId.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/backend/Backend.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/backend/TransactionalBackend.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/backend/not_configured.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/backend/singleton.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/encryption/DataSubject.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/encryption/Encrypted.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/encryption/Encryption.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/encryption/NoEncryptionStrategy.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/encryption/NoKeyStorageStrategy.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/event/Context.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/event/Entry.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/event/Event.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/event/Position.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/event/RawEvent.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/event/Recorded.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/event/RecordedRaw.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/event/WrappedEvent.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/exceptions.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/in_transaction/Dispatcher.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/in_transaction/Listener.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/in_transaction/Listeners.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/interfaces/EncryptionKeyStorageStrategy.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/interfaces/EncryptionStrategy.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/interfaces/OutboxFiltererStrategy.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/interfaces/OutboxStorageStrategy.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/interfaces/StorageStrategy.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/interfaces/SubscriptionStrategy.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/outbox/NoOutboxStorageStrategy.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/outbox/Outbox.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/outbox/no_filter.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/subscription/BuildPhase.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/subscription/FilterPhase.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/subscription/PositionPhase.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/reference/event_store/subscription/SubscriptionBuilder.md +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/res/eda_example.plantuml +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/docs/res/eda_example.png +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/event/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/event/encryption.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/event/registry.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/in_memory.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/outbox.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/stream_id.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/subscription/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/subscription/builder.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/subscription/in_transaction.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/subscription/interfaces.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/tenant_id.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/versioning.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/backend.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/encryption.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/event_sourcing/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/event_sourcing/aggregate.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/event_sourcing/repository.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/in_transaction.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/interfaces.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/outbox.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/py.typed +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/read_model/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/read_model/cursors_dao.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/read_model/projector.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/subscription.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_django/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_django/dto.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_django/event_store.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_django/migrations/0001_initial.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_django/migrations/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_django/models.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_django/outbox.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_django/py.typed +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_django/subscription.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_kurrentdb/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_kurrentdb/dto.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_kurrentdb/event_store.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_kurrentdb/outbox.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_kurrentdb/py.typed +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_kurrentdb/stream.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_kurrentdb/subscription.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/cursors_dao.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/dto.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/event_store.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/guid.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/jsonb.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/models/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/models/base.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/models/default.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/outbox.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/py.typed +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery_sqlalchemy/subscription.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/backend/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/backend/django.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/backend/in_memory.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/backend/kurrentdb.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/backend/sqlalchemy.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/backend/test_sqlalchemy.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/conftest.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_sourcing/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_sourcing/conftest.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_sourcing/light_switch.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_sourcing/test_aggregate.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_sourcing/test_wrapped_aggregate.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/conftest.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/event/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/event/test_privacy.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/event/test_registry.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/outbox/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/outbox/conftest.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/outbox/test_filters.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/outbox/test_multitenancy.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/outbox/test_outbox.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/subscriptions/__init__.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/subscriptions/other_client.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/subscriptions/test_batch.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/subscriptions/test_from_position.py +0 -0
- {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
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/subscriptions/test_in_transaction_subscription.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/subscriptions/test_subscriptions.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/subscriptions/test_to_category.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/subscriptions/test_to_event_types.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_category.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_concurrency_control.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_multitenancy.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_named_streams.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_no_outbox.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_no_versioning.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_removing_stream.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_snapshots.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/event_store/test_stream_id.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/factories.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/mark.py +0 -0
- {python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/tests/read_model/__init__.py +0 -0
- {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:
|
|
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:
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
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
|
-
|
|
43
|
-
|
|
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.
|
|
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
|
|
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
|
```
|
|
@@ -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
|
{python_event_sourcery-0.5.3a4 → python_event_sourcery-0.6.0}/event_sourcery/_event_store/backend.py
RENAMED
|
@@ -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
|
"""
|