python-event-sourcery 0.6.1__tar.gz → 0.7.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.6.1 → python_event_sourcery-0.7.0}/AGENTS.md +1 -1
- python_event_sourcery-0.7.0/CHANGELOG.md +73 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/PKG-INFO +4 -2
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/README.md +1 -1
- python_event_sourcery-0.7.0/docs/adr/20260827-async-api.md +133 -0
- python_event_sourcery-0.7.0/docs/code/recipes/integrate/kurrent_async_01.py +15 -0
- python_event_sourcery-0.7.0/docs/code/test_get_started.py +67 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/code/test_recipes.py +336 -4
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/get_started.md +32 -10
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/recipes/data_privacy.md +35 -17
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/recipes/event_sourcing.md +30 -12
- python_event_sourcery-0.7.0/docs/recipes/integrate.md +98 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/recipes/multitenancy.md +17 -7
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/recipes/outbox.md +58 -24
- python_event_sourcery-0.7.0/docs/recipes/saving_events.md +129 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/recipes/snapshots.md +41 -16
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/recipes/subscriptions.md +58 -26
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/backends/in_memory.md +3 -0
- python_event_sourcery-0.7.0/docs/reference/backends/kurrentdb.md +17 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/backends/sqlalchemy.md +15 -0
- python_event_sourcery-0.7.0/docs/reference/event_sourcing/AsyncRepository.md +2 -0
- python_event_sourcery-0.7.0/docs/reference/event_store/AsyncEventStore.md +1 -0
- python_event_sourcery-0.7.0/docs/reference/event_store/backend/AsyncBackend.md +1 -0
- python_event_sourcery-0.7.0/docs/reference/event_store/backend/AsyncTransactionalBackend.md +1 -0
- python_event_sourcery-0.7.0/docs/reference/event_store/encryption/AsyncEncryption.md +2 -0
- python_event_sourcery-0.7.0/docs/reference/event_store/encryption/AsyncNoKeyStorageStrategy.md +2 -0
- python_event_sourcery-0.7.0/docs/reference/event_store/interfaces/AsyncEncryptionKeyStorageStrategy.md +2 -0
- python_event_sourcery-0.7.0/docs/reference/event_store/interfaces/AsyncOutboxStorageStrategy.md +1 -0
- python_event_sourcery-0.7.0/docs/reference/event_store/interfaces/AsyncStorageStrategy.md +1 -0
- python_event_sourcery-0.7.0/docs/reference/event_store/interfaces/AsyncSubscriptionStrategy.md +1 -0
- python_event_sourcery-0.7.0/docs/reference/event_store/outbox/AsyncNoOutboxStorageStrategy.md +1 -0
- python_event_sourcery-0.7.0/docs/reference/event_store/outbox/AsyncOutbox.md +1 -0
- python_event_sourcery-0.7.0/docs/reference/event_store/subscription/AsyncBuildPhase.md +1 -0
- python_event_sourcery-0.7.0/docs/reference/event_store/subscription/AsyncFilterPhase.md +1 -0
- python_event_sourcery-0.7.0/docs/reference/event_store/subscription/AsyncPositionPhase.md +1 -0
- python_event_sourcery-0.7.0/docs/reference/event_store/subscription/AsyncSubscriptionBuilder.md +1 -0
- python_event_sourcery-0.7.0/docs/reference/read_model/AsyncCursorsDao.md +2 -0
- python_event_sourcery-0.7.0/docs/reference/read_model/AsyncProjector.md +2 -0
- python_event_sourcery-0.7.0/docs/reference/read_model/AsyncReadModel.md +2 -0
- python_event_sourcery-0.7.0/docs/reference/read_model/CursorsDao.md +2 -0
- python_event_sourcery-0.7.0/docs/reference/read_model/Projector.md +2 -0
- python_event_sourcery-0.7.0/docs/reference/read_model/ReadModel.md +2 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/__init__.py +10 -0
- python_event_sourcery-0.7.0/event_sourcery/_event_store/_async/backend.py +154 -0
- python_event_sourcery-0.7.0/event_sourcery/_event_store/_async/dispatcher.py +39 -0
- python_event_sourcery-0.7.0/event_sourcery/_event_store/_async/encryption.py +222 -0
- python_event_sourcery-0.7.0/event_sourcery/_event_store/_async/event_store.py +268 -0
- python_event_sourcery-0.7.0/event_sourcery/_event_store/_async/in_memory.py +347 -0
- python_event_sourcery-0.7.0/event_sourcery/_event_store/_async/outbox.py +99 -0
- python_event_sourcery-0.7.0/event_sourcery/_event_store/_async/serde.py +70 -0
- python_event_sourcery-0.7.0/event_sourcery/_event_store/_async/subscription.py +202 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/_event_store/backend.py +28 -23
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/_event_store/event/encryption.py +56 -14
- python_event_sourcery-0.7.0/event_sourcery/_event_store/event/serde.py +116 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/_event_store/event_store.py +23 -16
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/_event_store/in_memory.py +56 -33
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/_event_store/subscription/builder.py +3 -16
- python_event_sourcery-0.7.0/event_sourcery/_event_store/subscription/helpers.py +15 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/_event_store/subscription/in_transaction.py +29 -4
- python_event_sourcery-0.7.0/event_sourcery/async_/__init__.py +13 -0
- python_event_sourcery-0.7.0/event_sourcery/async_/backend.py +20 -0
- python_event_sourcery-0.7.0/event_sourcery/async_/encryption.py +9 -0
- python_event_sourcery-0.7.0/event_sourcery/async_/interfaces.py +13 -0
- python_event_sourcery-0.7.0/event_sourcery/async_/outbox.py +11 -0
- python_event_sourcery-0.7.0/event_sourcery/async_/subscription.py +13 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/encryption.py +4 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/event_sourcing/__init__.py +2 -0
- python_event_sourcery-0.7.0/event_sourcery/event_sourcing/repository_async.py +86 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/read_model/__init__.py +8 -0
- python_event_sourcery-0.7.0/event_sourcery/read_model/projector_async.py +94 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_django/dto.py +2 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_django/event_store.py +13 -1
- python_event_sourcery-0.7.0/event_sourcery_django/migrations/0002_remove_event_ix_events_stream_id_version_and_more.py +67 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_django/models.py +1 -5
- python_event_sourcery-0.7.0/event_sourcery_kurrentdb/async_/__init__.py +96 -0
- python_event_sourcery-0.7.0/event_sourcery_kurrentdb/async_/event_store.py +133 -0
- python_event_sourcery-0.7.0/event_sourcery_kurrentdb/async_/outbox.py +141 -0
- python_event_sourcery-0.7.0/event_sourcery_kurrentdb/async_/subscription.py +97 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_kurrentdb/event_store.py +28 -38
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_kurrentdb/outbox.py +2 -0
- python_event_sourcery-0.7.0/event_sourcery_sqlalchemy/async_/__init__.py +125 -0
- python_event_sourcery-0.7.0/event_sourcery_sqlalchemy/async_/cursors_dao.py +94 -0
- python_event_sourcery-0.7.0/event_sourcery_sqlalchemy/async_/event_store.py +297 -0
- python_event_sourcery-0.7.0/event_sourcery_sqlalchemy/async_/outbox.py +106 -0
- python_event_sourcery-0.7.0/event_sourcery_sqlalchemy/async_/subscription.py +243 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_sqlalchemy/event_store.py +26 -3
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_sqlalchemy/models/__init__.py +2 -3
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_sqlalchemy/outbox.py +2 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/pyproject.toml +6 -2
- python_event_sourcery-0.7.0/tests/adapter.py +345 -0
- python_event_sourcery-0.7.0/tests/backend/in_memory_async.py +14 -0
- python_event_sourcery-0.7.0/tests/backend/kurrentdb_async.py +40 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/backend/sqlalchemy.py +3 -1
- python_event_sourcery-0.7.0/tests/backend/sqlalchemy_async.py +185 -0
- python_event_sourcery-0.7.0/tests/backend/test_django_migrations.py +71 -0
- python_event_sourcery-0.7.0/tests/backend/test_kurrentdb.py +42 -0
- python_event_sourcery-0.7.0/tests/backend/test_kurrentdb_async.py +97 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/backend/test_sqlalchemy.py +60 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/bdd.py +12 -15
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/conftest.py +8 -8
- python_event_sourcery-0.7.0/tests/event_sourcing/conftest.py +34 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_sourcing/test_aggregate.py +4 -3
- python_event_sourcery-0.7.0/tests/event_store/async_only/test_encryption_async.py +146 -0
- python_event_sourcery-0.7.0/tests/event_store/async_only/test_in_memory.py +196 -0
- python_event_sourcery-0.7.0/tests/event_store/async_only/test_kurrentdb.py +221 -0
- python_event_sourcery-0.7.0/tests/event_store/async_only/test_sqlalchemy.py +328 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/conftest.py +19 -3
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/event/test_privacy.py +14 -4
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/event/test_registry.py +4 -3
- python_event_sourcery-0.7.0/tests/event_store/outbox/conftest.py +145 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/outbox/test_filters.py +4 -4
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/outbox/test_multitenancy.py +4 -3
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/outbox/test_outbox.py +31 -7
- python_event_sourcery-0.7.0/tests/event_store/subscriptions/__init__.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/subscriptions/other_client.py +15 -4
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/subscriptions/test_from_position.py +5 -5
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/subscriptions/test_handling_uncommitted_messages_from_other_clients.py +47 -7
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/subscriptions/test_in_transaction_subscription.py +33 -7
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/subscriptions/test_subscriptions.py +3 -2
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/subscriptions/test_to_category.py +3 -2
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/subscriptions/test_to_event_types.py +3 -2
- python_event_sourcery-0.7.0/tests/event_store/test_concurrency_control.py +79 -0
- python_event_sourcery-0.7.0/tests/event_store/test_multitenancy.py +100 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/test_named_streams.py +13 -4
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/test_no_outbox.py +3 -2
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/test_no_versioning.py +2 -2
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/test_save_retrieval.py +4 -3
- python_event_sourcery-0.7.0/tests/protocols.py +86 -0
- python_event_sourcery-0.7.0/tests/read_model/__init__.py +0 -0
- python_event_sourcery-0.7.0/tests/read_model/conftest.py +105 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/read_model/test_projector.py +17 -12
- python_event_sourcery-0.7.0/tests/read_model/test_projector_async.py +231 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/uv.lock +225 -7
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/zensical.toml +25 -0
- python_event_sourcery-0.6.1/CHANGELOG.md +0 -38
- python_event_sourcery-0.6.1/docs/code/test_get_started.py +0 -27
- python_event_sourcery-0.6.1/docs/recipes/integrate.md +0 -70
- python_event_sourcery-0.6.1/docs/recipes/saving_events.md +0 -77
- python_event_sourcery-0.6.1/docs/reference/backends/kurrentdb.md +0 -5
- python_event_sourcery-0.6.1/event_sourcery/_event_store/event/serde.py +0 -85
- python_event_sourcery-0.6.1/tests/event_sourcing/conftest.py +0 -10
- python_event_sourcery-0.6.1/tests/event_store/outbox/conftest.py +0 -83
- python_event_sourcery-0.6.1/tests/event_store/test_concurrency_control.py +0 -42
- python_event_sourcery-0.6.1/tests/event_store/test_multitenancy.py +0 -44
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/.adr-dir +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/.github/workflows/bump-version-and-publish.yml +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/.github/workflows/deploy-docs.yml +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/.github/workflows/main.yml +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/.github/workflows/publish-release.yml +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/.gitignore +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/CONTRIBUTING.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/LICENSE +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/compose.yml +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/django_manage.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/adr/20220730-record-architecture-decisions.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/adr/20230903-explicit-init-arguments-for-sqlalchemy-s-models.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/adr/20231027_1-packaging-of-the-project.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/adr/20231027_2-leave-implementation-of-synchronous-dispatcher-to-users-of-the-library.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/adr/20231029-in-memory-event-store.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/adr/20231230-in_transaction_subscription.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/adr/20240726-dependencies-and-distribution.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/adr/20241007-multitenancy.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/adr/20250411-packaging-of-the-project-revised.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/adr/20250729-privacy-data-encryption-and-shredding.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/adr/20251004-packaging-of-the-event_store-package.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/adr/20260314-aggregate-metadata.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/adr/20260926-typed-event-context-components-and-enrichers.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/code/__init__.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/code/recipes/__init__.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/code/recipes/integrate/__init__.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/code/recipes/integrate/kurrent_01.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/code/test_context_enrichers.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/code/test_otel.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/concepts.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/index.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/recipes/context_enrichment.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/recipes/defining_events.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/recipes/opentelemetry.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/backends/django.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_sourcing/Aggregate.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_sourcing/Repository.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_sourcing/WrappedAggregate.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/DEFAULT_TENANT.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/EventStore.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/NO_VERSIONING.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/StreamCategory.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/StreamId.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/StreamUUID.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/TenantId.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/backend/Backend.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/backend/TransactionalBackend.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/backend/not_configured.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/backend/singleton.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/encryption/DataSubject.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/encryption/Encrypted.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/encryption/Encryption.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/encryption/NoEncryptionStrategy.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/encryption/NoKeyStorageStrategy.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/event/Context.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/event/ContextComponent.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/event/Entry.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/event/Event.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/event/EventContextEnricher.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/event/EventCorrelation.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/event/Position.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/event/RawEvent.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/event/Recorded.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/event/RecordedRaw.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/event/WrappedEvent.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/exceptions.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/in_transaction/Dispatcher.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/in_transaction/Listener.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/in_transaction/Listeners.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/interfaces/EncryptionKeyStorageStrategy.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/interfaces/EncryptionStrategy.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/interfaces/OutboxFiltererStrategy.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/interfaces/OutboxStorageStrategy.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/interfaces/StorageStrategy.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/interfaces/SubscriptionStrategy.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/outbox/NoOutboxStorageStrategy.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/outbox/Outbox.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/outbox/no_filter.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/subscription/BuildPhase.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/subscription/FilterPhase.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/subscription/PositionPhase.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/event_store/subscription/SubscriptionBuilder.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/otel/OpenTelemetryCarrier.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/otel/OpenTelemetryContextEnricher.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/otel/get_parent_context.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/reference/otel/use_parent_context.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/res/eda_example.plantuml +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/docs/res/eda_example.png +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/_event_store/__init__.py +0 -0
- {python_event_sourcery-0.6.1/event_sourcery/_event_store/event → python_event_sourcery-0.7.0/event_sourcery/_event_store/_async}/__init__.py +0 -0
- {python_event_sourcery-0.6.1/event_sourcery/_event_store/subscription → python_event_sourcery-0.7.0/event_sourcery/_event_store/event}/__init__.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/_event_store/event/dto.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/_event_store/event/registry.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/_event_store/outbox.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/_event_store/stream_id.py +0 -0
- {python_event_sourcery-0.6.1/event_sourcery_django/migrations → python_event_sourcery-0.7.0/event_sourcery/_event_store/subscription}/__init__.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/_event_store/subscription/interfaces.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/_event_store/tenant_id.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/_event_store/versioning.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/backend.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/event.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/event_sourcing/aggregate.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/event_sourcing/repository.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/exceptions.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/in_transaction.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/interfaces.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/outbox.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/py.typed +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/read_model/cursors_dao.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/read_model/projector.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery/subscription.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_django/__init__.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_django/migrations/0001_initial.py +0 -0
- {python_event_sourcery-0.6.1/tests → python_event_sourcery-0.7.0/event_sourcery_django/migrations}/__init__.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_django/outbox.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_django/py.typed +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_django/subscription.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_kurrentdb/__init__.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_kurrentdb/dto.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_kurrentdb/py.typed +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_kurrentdb/stream.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_kurrentdb/subscription.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_otel/__init__.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_otel/_carrier.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_otel/_context.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_otel/_enricher.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_otel/py.typed +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_sqlalchemy/__init__.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_sqlalchemy/cursors_dao.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_sqlalchemy/dto.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_sqlalchemy/guid.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_sqlalchemy/jsonb.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_sqlalchemy/models/base.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_sqlalchemy/models/default.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_sqlalchemy/py.typed +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/event_sourcery_sqlalchemy/subscription.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/AGENTS.md +0 -0
- {python_event_sourcery-0.6.1/tests/backend → python_event_sourcery-0.7.0/tests}/__init__.py +0 -0
- {python_event_sourcery-0.6.1/tests/event_sourcing → python_event_sourcery-0.7.0/tests/backend}/__init__.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/backend/django.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/backend/in_memory.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/backend/kurrentdb.py +0 -0
- {python_event_sourcery-0.6.1/tests/event_store → python_event_sourcery-0.7.0/tests/event_sourcing}/__init__.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_sourcing/light_switch.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_sourcing/test_wrapped_aggregate.py +0 -0
- {python_event_sourcery-0.6.1/tests/event_store/event → python_event_sourcery-0.7.0/tests/event_store}/__init__.py +0 -0
- {python_event_sourcery-0.6.1/tests/event_store/outbox → python_event_sourcery-0.7.0/tests/event_store/async_only}/__init__.py +0 -0
- {python_event_sourcery-0.6.1/tests/event_store/subscriptions → python_event_sourcery-0.7.0/tests/event_store/event}/__init__.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/event/test_dto.py +0 -0
- {python_event_sourcery-0.6.1/tests/read_model → python_event_sourcery-0.7.0/tests/event_store/outbox}/__init__.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/subscriptions/test_batch.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/test_category.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/test_context_enrichment.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/test_removing_stream.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/test_snapshots.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/event_store/test_stream_id.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/factories.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/mark.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/matchers.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/otel/AGENTS.md +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/otel/bdd.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/otel/conftest.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/otel/test_context_propagation.py +0 -0
- {python_event_sourcery-0.6.1 → python_event_sourcery-0.7.0}/tests/otel/test_parent_context.py +0 -0
|
@@ -5,7 +5,7 @@ Event sourcing library for Python with pluggable backends.
|
|
|
5
5
|
## Layout
|
|
6
6
|
|
|
7
7
|
- `event_sourcery/` — core library
|
|
8
|
-
- `event_sourcery_sqlalchemy/`, `event_sourcery_django/`, `event_sourcery_kurrentdb/` — backends (optional extras: `sqlalchemy`, `django`, `kurrentdb`)
|
|
8
|
+
- `event_sourcery_sqlalchemy/`, `event_sourcery_django/`, `event_sourcery_kurrentdb/` — backends (optional extras: `sqlalchemy`, `sqlalchemy-async`, `django`, `kurrentdb`)
|
|
9
9
|
- `tests/` — shared test suite run against every backend
|
|
10
10
|
- `docs/` — documentation (zensical)
|
|
11
11
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.7.0
|
|
4
|
+
### Added
|
|
5
|
+
- Async API mirroring the sync one: `AsyncEventStore`, `AsyncBackend`, `AsyncOutbox`,
|
|
6
|
+
`AsyncRepository`, `AsyncProjector`, `AsyncEncryption` and friends.
|
|
7
|
+
- Async backends: in-memory, `AsyncSQLAlchemyBackend` (new `sqlalchemy-async` extra)
|
|
8
|
+
and `AsyncKurrentDBBackend`.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **Breaking:** `expected_version` is enforced on every append. The default `0` now
|
|
12
|
+
means "stream must not exist", so appending to an existing stream requires its
|
|
13
|
+
current version (or `NO_VERSIONING`); a non-zero version on a new stream is rejected.
|
|
14
|
+
Previously both cases were silently accepted.
|
|
15
|
+
- **Breaking:** KurrentDB checks `expected_version` server-side, closing the race
|
|
16
|
+
between version check and append.
|
|
17
|
+
- **Breaking (Django):** migration `0002` adds a unique constraint on
|
|
18
|
+
`(stream, version)`. Duplicates, possible under the previous lenient versioning,
|
|
19
|
+
make it stop with a list of affected streams; nothing is changed until they are
|
|
20
|
+
resolved. Find them with:
|
|
21
|
+
`SELECT stream_id, version FROM event_sourcery_django_event
|
|
22
|
+
GROUP BY stream_id, version HAVING COUNT(*) > 1;`
|
|
23
|
+
Resolve per stream as your domain requires: renumber the events (and the stream's
|
|
24
|
+
`version`) in insertion order, or delete the duplicates. Building the index blocks
|
|
25
|
+
writes to the events table for its duration.
|
|
26
|
+
|
|
27
|
+
### Fixes
|
|
28
|
+
- Tenant isolation: in-memory streams are scoped per tenant; SQLAlchemy
|
|
29
|
+
`delete_stream`, `save_snapshot` and versioned appends no longer touch other
|
|
30
|
+
tenants' streams with the same ID.
|
|
31
|
+
- Outbox preserves stream category (SQLAlchemy, Django).
|
|
32
|
+
- KurrentDB outbox acknowledges events rejected by the filter instead of leaving
|
|
33
|
+
them to be redelivered.
|
|
34
|
+
- In-memory backend no longer persists events whose deserialization fails;
|
|
35
|
+
in-transaction dispatcher deserializes only events with matching listeners.
|
|
36
|
+
- `event_sourcery_sqlalchemy` imports on SQLAlchemy 2.1.
|
|
37
|
+
|
|
38
|
+
## 0.6.1
|
|
39
|
+
### Fixes
|
|
40
|
+
- Include the `py.typed` marker in the `event_sourcery_otel` package.
|
|
41
|
+
|
|
42
|
+
## 0.6.0
|
|
43
|
+
### Added
|
|
44
|
+
- Typed, independently owned event context components with lazy access through `Context.get()`.
|
|
45
|
+
- Context enrichers configured through `Backend.with_context_enrichers()`.
|
|
46
|
+
- Optional `event_sourcery_otel` package for OpenTelemetry context propagation and parent-context activation.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
- **Breaking:** New `Context` values reject arbitrary root fields; add new context data with typed components.
|
|
50
|
+
|
|
51
|
+
## 0.5.2
|
|
52
|
+
### Changed
|
|
53
|
+
- **Breaking:** `Repository.aggregate()` yields `WrappedAggregate` instead of `Aggregate` directly
|
|
54
|
+
|
|
55
|
+
## 0.5.1
|
|
56
|
+
### Added
|
|
57
|
+
- In transaction subscription to category
|
|
58
|
+
- Custom context support in `Repository.aggregate()` via optional `context` parameter
|
|
59
|
+
- `WrappedEvent.get_context()` method for typed context retrieval
|
|
60
|
+
### Fixes
|
|
61
|
+
- event_sourcery_sqlalchemy.configure_models annotation for sqlalchemy.orm.DeclarativeBase
|
|
62
|
+
|
|
63
|
+
## 0.5.0
|
|
64
|
+
### Added
|
|
65
|
+
- Event definitions via the `Event` base class (Pydantic) with built-in metadata and tracing IDs.
|
|
66
|
+
- Event Store API for appending/loading streams with optimistic concurrency and optional `NO_VERSIONING`.
|
|
67
|
+
- Stream snapshots to optimize long event streams.
|
|
68
|
+
- Outbox pattern with retries and optional filters, plus synchronous in-process subscriptions.
|
|
69
|
+
- Subscription builder with single-event and batch iterators for asynchronous processing.
|
|
70
|
+
- Multi-tenancy with tenant-scoped stores and explicit visibility rules.
|
|
71
|
+
- Crypto-shredding and field-level encryption via `Encrypted` and `DataSubject` markers.
|
|
72
|
+
- Event sourcing primitives: `Aggregate` and `Repository` base classes.
|
|
73
|
+
- Backend integrations for SQLAlchemy, Django, KurrentDB (EventStoreDB), and in-memory storage.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-event-sourcery
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.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
|
|
@@ -17,6 +17,8 @@ Provides-Extra: otel
|
|
|
17
17
|
Requires-Dist: opentelemetry-api<2,>=1; extra == 'otel'
|
|
18
18
|
Provides-Extra: sqlalchemy
|
|
19
19
|
Requires-Dist: sqlalchemy>=2.0; extra == 'sqlalchemy'
|
|
20
|
+
Provides-Extra: sqlalchemy-async
|
|
21
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0; extra == 'sqlalchemy-async'
|
|
20
22
|
Description-Content-Type: text/markdown
|
|
21
23
|
|
|
22
24
|
|
|
@@ -91,7 +93,7 @@ from event_sourcery import (
|
|
|
91
93
|
|
|
92
94
|
# Set up your DB and application as you would normally do
|
|
93
95
|
engine = create_engine(
|
|
94
|
-
"postgresql://event_sourcery:event_sourcery@localhost:5432/event_sourcery"
|
|
96
|
+
"postgresql+psycopg://event_sourcery:event_sourcery@localhost:5432/event_sourcery"
|
|
95
97
|
)
|
|
96
98
|
|
|
97
99
|
|
|
@@ -70,7 +70,7 @@ from event_sourcery import (
|
|
|
70
70
|
|
|
71
71
|
# Set up your DB and application as you would normally do
|
|
72
72
|
engine = create_engine(
|
|
73
|
-
"postgresql://event_sourcery:event_sourcery@localhost:5432/event_sourcery"
|
|
73
|
+
"postgresql+psycopg://event_sourcery:event_sourcery@localhost:5432/event_sourcery"
|
|
74
74
|
)
|
|
75
75
|
|
|
76
76
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Async API
|
|
2
|
+
|
|
3
|
+
Date: 2026-08-27
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
Accepted
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
Applications built on async frameworks (FastAPI, aiohttp, async workers) need
|
|
12
|
+
an event store that does not block the event loop. We want to add async support
|
|
13
|
+
without breaking or degrading the existing synchronous API, which remains fully
|
|
14
|
+
supported.
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
|
|
18
|
+
**Parallel async classes.** Every component gets an async counterpart with the
|
|
19
|
+
`Async` prefix: `AsyncEventStore`, `AsyncBackend`, `AsyncOutbox`,
|
|
20
|
+
`AsyncSubscriptionBuilder`, `AsyncStorageStrategy`, etc. Synchronous classes are
|
|
21
|
+
untouched. We deliberately rejected `a`-prefixed methods on shared classes to
|
|
22
|
+
keep contracts explicit and make accidental sync/async mixing impossible.
|
|
23
|
+
|
|
24
|
+
**Public layout mirrors the sync one.** Implementation lives in the private
|
|
25
|
+
`event_sourcery._event_store._async` subpackage; public proxy imports live in
|
|
26
|
+
`event_sourcery.async_` (following the packaging ADR). Root `event_sourcery`
|
|
27
|
+
exports the four main symbols, same as sync: `AsyncEventStore`, `AsyncBackend`,
|
|
28
|
+
`AsyncOutbox`, `AsyncTransactionalBackend`.
|
|
29
|
+
|
|
30
|
+
**Asyncio-only.** The public async contract targets asyncio. No trio/anyio
|
|
31
|
+
support is promised.
|
|
32
|
+
|
|
33
|
+
**Deliberate API deviations from sync:**
|
|
34
|
+
|
|
35
|
+
- `position` is a coroutine method — `await store.position()` — because
|
|
36
|
+
fetching the position performs I/O in real backends. Same applies to
|
|
37
|
+
`AsyncStorageStrategy.current_position`.
|
|
38
|
+
- Subscriptions return `AsyncIterator`; outbox entries are async context
|
|
39
|
+
managers; outbox publishers are async callables.
|
|
40
|
+
- **In-transaction listeners remain synchronous** even on async backends. They
|
|
41
|
+
are dispatched inside the append transaction, where bridging to async
|
|
42
|
+
callables would either block the loop or break ordering guarantees.
|
|
43
|
+
- KurrentDB outbox creates its persistent subscription lazily (sync variant
|
|
44
|
+
creates it eagerly in `with_outbox()`, which cannot await). Because the
|
|
45
|
+
subscription is anchored at creation time (`from_end=True`), the async
|
|
46
|
+
backend ensures creation before the first append that uses the outbox —
|
|
47
|
+
creating it only on the first outbox run would permanently skip events
|
|
48
|
+
appended earlier.
|
|
49
|
+
- SQLAlchemy async strategies avoid ORM relationship arms
|
|
50
|
+
(`stream.events.extend(...)`, `stream.snapshots.append(...)`) and lazy
|
|
51
|
+
attribute access: both trigger lazy loads, which are synchronous I/O and
|
|
52
|
+
raise `MissingGreenlet` under asyncio. Instead, foreign keys are assigned
|
|
53
|
+
directly (`entry._db_stream_id = stream.id`, producing identical INSERT
|
|
54
|
+
statements) and query results eager-load streams with `selectinload`.
|
|
55
|
+
For the same reason, the stream row found by `_ensure_stream` is passed to
|
|
56
|
+
`insert_events` directly instead of being looked up again in
|
|
57
|
+
`session.info["strong_set"]` — after a commit, the session expires ORM
|
|
58
|
+
instances and reading attributes of an expired instance would refresh
|
|
59
|
+
them lazily.
|
|
60
|
+
- A single `AsyncSession` cannot interleave concurrent operations. Async
|
|
61
|
+
SQLAlchemy usage follows the same rule as the sync one: one session (and
|
|
62
|
+
thus one configured backend) per concurrent unit of work.
|
|
63
|
+
- The async SQLAlchemy backend ships as a new `sqlalchemy-async` extra
|
|
64
|
+
(`SQLAlchemy[asyncio]`). Async drivers (`asyncpg`, `aiosqlite`) are not
|
|
65
|
+
installed by the library — the same policy as the sync variant, which
|
|
66
|
+
installs no database drivers either.
|
|
67
|
+
|
|
68
|
+
**Testing strategy.** The shared test suite is not duplicated: async backends
|
|
69
|
+
are added as fixtures next to their sync counterparts, and a sync facade
|
|
70
|
+
(`tests/adapter.py`) drains coroutines on a dedicated event loop, so every
|
|
71
|
+
existing BDD-style test exercises both APIs. A small set of true-async tests
|
|
72
|
+
verifies concurrency semantics directly. Coverage gate stays at 100%.
|
|
73
|
+
|
|
74
|
+
## Consequences
|
|
75
|
+
|
|
76
|
+
- Sync and async APIs evolve in parallel; every new feature must land in both.
|
|
77
|
+
- Docs show sync and async snippets side by side (tabs); docs tests use the
|
|
78
|
+
in-memory backend only, so no docker services are needed.
|
|
79
|
+
- Async variants of SQLAlchemy and KurrentDB backends follow in subsequent
|
|
80
|
+
milestones; Django async is currently out of scope.
|
|
81
|
+
|
|
82
|
+
## Amendment 2026-08-27: SQLAlchemy backend milestone
|
|
83
|
+
|
|
84
|
+
The SQLAlchemy milestone validated the decisions above against a real
|
|
85
|
+
relational backend. Consequences specific to it:
|
|
86
|
+
|
|
87
|
+
- The mirrored test suite runs against `sqlalchemy_async_sqlite` and
|
|
88
|
+
`sqlalchemy_async_postgres` fixtures through the sync facade. Test
|
|
89
|
+
transactions are never committed, so fixture teardown closes all tracked
|
|
90
|
+
sessions before dropping tables (mirroring the sync suite's
|
|
91
|
+
`close_all_sessions()`) — otherwise `DROP TABLE` deadlocks against locks
|
|
92
|
+
held by open transactions.
|
|
93
|
+
- In-transaction listeners fired inside the append transaction work unchanged:
|
|
94
|
+
the async dispatcher awaits deserialization before invoking synchronous listeners.
|
|
95
|
+
|
|
96
|
+
## Amendment 2026-08-28: parity wrap-up
|
|
97
|
+
|
|
98
|
+
Milestone 4 closed the remaining gaps, with two notable designs:
|
|
99
|
+
|
|
100
|
+
- **Async encryption pipeline.** `AsyncEncryption` /
|
|
101
|
+
`AsyncEncryptionKeyStorageStrategy` mirror their sync counterparts;
|
|
102
|
+
`AsyncSerde` exclusively awaits an `AsyncEncryption`.
|
|
103
|
+
`AsyncBackend.with_encryption` accepts only async key storage. The async
|
|
104
|
+
in-transaction dispatcher awaits deserialization, then invokes synchronous
|
|
105
|
+
listeners. Sync backends retain their separate synchronous pipeline.
|
|
106
|
+
- **In-transaction dispatcher only deserializes on matching listeners.**
|
|
107
|
+
`Dispatcher` resolves event types lazily via `EventRegistry`, which keeps
|
|
108
|
+
the (possibly stubbed) serde untouched when no listener is registered.
|
|
109
|
+
- `AsyncRepository`, `AsyncProjector`, `AsyncCursorsDao`,
|
|
110
|
+
`AsyncReadModel`, `AsyncSqlAlchemyCursorsDao` complete the feature parity.
|
|
111
|
+
|
|
112
|
+
### Parity audit (sync → async)
|
|
113
|
+
|
|
114
|
+
| Sync | Async (or documented exclusion) |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `Backend` / `TransactionalBackend` | `AsyncBackend` / `AsyncTransactionalBackend` |
|
|
117
|
+
| `EventStore` | `AsyncEventStore` |
|
|
118
|
+
| `Outbox` (`run`) | `AsyncOutbox` (`run`) |
|
|
119
|
+
| `StorageStrategy` | `AsyncStorageStrategy` |
|
|
120
|
+
| `SubscriptionStrategy` | `AsyncSubscriptionStrategy` |
|
|
121
|
+
| `OutboxStorageStrategy` | `AsyncOutboxStorageStrategy` |
|
|
122
|
+
| `PositionPhase` / `FilterPhase` / `BuildPhase` | async counterparts |
|
|
123
|
+
| `SubscriptionBuilder` | `AsyncSubscriptionBuilder` |
|
|
124
|
+
| `NoKeyStorageStrategy` | `AsyncNoKeyStorageStrategy` |
|
|
125
|
+
| `Encryption` | `AsyncEncryption` (shred via `ashred`) |
|
|
126
|
+
| `EncryptionKeyStorageStrategy` | `AsyncEncryptionKeyStorageStrategy` |
|
|
127
|
+
| `Repository` | `AsyncRepository` |
|
|
128
|
+
| `CursorsDao` | `AsyncCursorsDao` |
|
|
129
|
+
| `Projector` | `AsyncProjector` |
|
|
130
|
+
| `ReadModel` (callable) | `AsyncReadModel` (async callable) |
|
|
131
|
+
| `SqlAlchemyCursorsDao` | `AsyncSqlAlchemyCursorsDao` |
|
|
132
|
+
| `InMemoryBackend` / `InMemoryKeyStorage` | `Async*` counterparts |
|
|
133
|
+
| `In-transaction listeners` | remain sync (ADR §2.4), dispatched under lazy resolution |
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from kurrentdbclient import AsyncKurrentDBClient
|
|
4
|
+
|
|
5
|
+
from event_sourcery import StreamId
|
|
6
|
+
from event_sourcery_kurrentdb.async_ import AsyncKurrentDBBackend
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def main() -> None:
|
|
10
|
+
client = AsyncKurrentDBClient(uri="kurrentdb://localhost:2113?Tls=false")
|
|
11
|
+
backend = AsyncKurrentDBBackend().configure(client)
|
|
12
|
+
await backend.event_store.load_stream(StreamId()) # test if connection works
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
def test_get_started():
|
|
2
|
+
# --8<-- [start:get_started_01]
|
|
3
|
+
from event_sourcery import StreamId
|
|
4
|
+
from event_sourcery.backend import InMemoryBackend
|
|
5
|
+
from event_sourcery.event import Event
|
|
6
|
+
|
|
7
|
+
class InvoicePaid(Event):
|
|
8
|
+
invoice_number: str
|
|
9
|
+
|
|
10
|
+
backend = InMemoryBackend()
|
|
11
|
+
event_store = backend.event_store
|
|
12
|
+
|
|
13
|
+
event_store.append(
|
|
14
|
+
InvoicePaid(invoice_number="1003"),
|
|
15
|
+
stream_id=StreamId(name="invoices/1003"),
|
|
16
|
+
)
|
|
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]
|
|
23
|
+
|
|
24
|
+
events = event_store.load_stream(StreamId(name="invoices/1003"))
|
|
25
|
+
assert len(events) == 1
|
|
26
|
+
assert events[0].event.invoice_number == "1003"
|
|
27
|
+
assert events[0].version == 1
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_get_started_async():
|
|
31
|
+
# --8<-- [start:get_started_async_01]
|
|
32
|
+
import asyncio
|
|
33
|
+
|
|
34
|
+
from event_sourcery import StreamId
|
|
35
|
+
from event_sourcery.async_.backend import AsyncInMemoryBackend
|
|
36
|
+
from event_sourcery.event import Event
|
|
37
|
+
|
|
38
|
+
class InvoicePaid(Event):
|
|
39
|
+
invoice_number: str
|
|
40
|
+
|
|
41
|
+
backend = AsyncInMemoryBackend()
|
|
42
|
+
event_store = backend.event_store
|
|
43
|
+
|
|
44
|
+
async def main() -> None:
|
|
45
|
+
await event_store.append(
|
|
46
|
+
InvoicePaid(invoice_number="1003"),
|
|
47
|
+
stream_id=StreamId(name="invoices/1003"),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
asyncio.run(main())
|
|
51
|
+
|
|
52
|
+
# --8<-- [end:get_started_async_01]
|
|
53
|
+
# --8<-- [start:get_started_async_02]
|
|
54
|
+
async def read() -> None:
|
|
55
|
+
recorded_events = await event_store.load_stream(
|
|
56
|
+
StreamId(name="invoices/1003"),
|
|
57
|
+
)
|
|
58
|
+
for recorded in recorded_events:
|
|
59
|
+
print(recorded.event, recorded.version)
|
|
60
|
+
|
|
61
|
+
asyncio.run(read())
|
|
62
|
+
# --8<-- [end:get_started_async_02]
|
|
63
|
+
|
|
64
|
+
events = asyncio.run(event_store.load_stream(StreamId(name="invoices/1003")))
|
|
65
|
+
assert len(events) == 1
|
|
66
|
+
assert events[0].event.invoice_number == "1003"
|
|
67
|
+
assert events[0].version == 1
|