python-event-sourcery 0.4.1a1__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. python_event_sourcery-0.5.0/PKG-INFO +152 -0
  2. python_event_sourcery-0.5.0/README.md +126 -0
  3. python_event_sourcery-0.5.0/event_sourcery/__init__.py +21 -0
  4. {python_event_sourcery-0.4.1a1/event_sourcery/event_store → python_event_sourcery-0.5.0/event_sourcery/_event_store}/backend.py +89 -35
  5. python_event_sourcery-0.4.1a1/event_sourcery/event_store/event/_dto.py → python_event_sourcery-0.5.0/event_sourcery/_event_store/event/dto.py +64 -3
  6. python_event_sourcery-0.4.1a1/event_sourcery/event_store/event/_encryption.py → python_event_sourcery-0.5.0/event_sourcery/_event_store/event/encryption.py +125 -8
  7. python_event_sourcery-0.4.1a1/event_sourcery/event_store/event/_registry.py → python_event_sourcery-0.5.0/event_sourcery/_event_store/event/registry.py +8 -7
  8. python_event_sourcery-0.4.1a1/event_sourcery/event_store/event/_serde.py → python_event_sourcery-0.5.0/event_sourcery/_event_store/event/serde.py +4 -4
  9. {python_event_sourcery-0.4.1a1/event_sourcery/event_store → python_event_sourcery-0.5.0/event_sourcery/_event_store}/event_store.py +89 -5
  10. {python_event_sourcery-0.4.1a1/event_sourcery/event_store → python_event_sourcery-0.5.0/event_sourcery/_event_store}/in_memory.py +60 -16
  11. python_event_sourcery-0.5.0/event_sourcery/_event_store/outbox.py +106 -0
  12. {python_event_sourcery-0.4.1a1/event_sourcery/event_store → python_event_sourcery-0.5.0/event_sourcery/_event_store}/stream_id.py +31 -3
  13. python_event_sourcery-0.4.1a1/event_sourcery/event_store/subscription.py → python_event_sourcery-0.5.0/event_sourcery/_event_store/subscription/builder.py +11 -33
  14. python_event_sourcery-0.5.0/event_sourcery/_event_store/subscription/in_transaction.py +132 -0
  15. python_event_sourcery-0.5.0/event_sourcery/_event_store/subscription/interfaces.py +107 -0
  16. python_event_sourcery-0.5.0/event_sourcery/_event_store/versioning.py +108 -0
  17. python_event_sourcery-0.5.0/event_sourcery/backend.py +21 -0
  18. python_event_sourcery-0.5.0/event_sourcery/encryption.py +17 -0
  19. python_event_sourcery-0.5.0/event_sourcery/event.py +26 -0
  20. python_event_sourcery-0.5.0/event_sourcery/event_sourcing/aggregate.py +66 -0
  21. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery/event_sourcing/repository.py +24 -7
  22. {python_event_sourcery-0.4.1a1/event_sourcery/event_store → python_event_sourcery-0.5.0/event_sourcery}/exceptions.py +4 -0
  23. python_event_sourcery-0.5.0/event_sourcery/in_transaction.py +11 -0
  24. python_event_sourcery-0.5.0/event_sourcery/interfaces.py +21 -0
  25. python_event_sourcery-0.5.0/event_sourcery/outbox.py +11 -0
  26. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery/read_model/cursors_dao.py +1 -1
  27. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery/read_model/projector.py +2 -1
  28. python_event_sourcery-0.5.0/event_sourcery/subscription.py +15 -0
  29. python_event_sourcery-0.5.0/event_sourcery_django/__init__.py +97 -0
  30. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_django/dto.py +2 -1
  31. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_django/event_store.py +5 -13
  32. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_django/models.py +1 -1
  33. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_django/outbox.py +2 -2
  34. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_django/subscription.py +2 -2
  35. python_event_sourcery-0.5.0/event_sourcery_kurrentdb/__init__.py +107 -0
  36. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_kurrentdb/dto.py +1 -1
  37. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_kurrentdb/event_store.py +4 -11
  38. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_kurrentdb/outbox.py +2 -2
  39. python_event_sourcery-0.5.0/event_sourcery_kurrentdb/py.typed +0 -0
  40. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_kurrentdb/stream.py +2 -2
  41. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_kurrentdb/subscription.py +2 -2
  42. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_sqlalchemy/__init__.py +65 -10
  43. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_sqlalchemy/cursors_dao.py +1 -1
  44. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_sqlalchemy/dto.py +2 -1
  45. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_sqlalchemy/event_store.py +5 -12
  46. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_sqlalchemy/models/__init__.py +25 -0
  47. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_sqlalchemy/models/base.py +1 -2
  48. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_sqlalchemy/outbox.py +3 -2
  49. python_event_sourcery-0.5.0/event_sourcery_sqlalchemy/py.typed +0 -0
  50. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_sqlalchemy/subscription.py +2 -2
  51. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/pyproject.toml +7 -3
  52. python_event_sourcery-0.4.1a1/PKG-INFO +0 -23
  53. python_event_sourcery-0.4.1a1/event_sourcery/event_sourcing/aggregate.py +0 -24
  54. python_event_sourcery-0.4.1a1/event_sourcery/event_store/__init__.py +0 -52
  55. python_event_sourcery-0.4.1a1/event_sourcery/event_store/dispatcher.py +0 -64
  56. python_event_sourcery-0.4.1a1/event_sourcery/event_store/event/__init__.py +0 -38
  57. python_event_sourcery-0.4.1a1/event_sourcery/event_store/interfaces.py +0 -101
  58. python_event_sourcery-0.4.1a1/event_sourcery/event_store/outbox.py +0 -27
  59. python_event_sourcery-0.4.1a1/event_sourcery/event_store/versioning.py +0 -57
  60. python_event_sourcery-0.4.1a1/event_sourcery_django/__init__.py +0 -61
  61. python_event_sourcery-0.4.1a1/event_sourcery_kurrentdb/__init__.py +0 -69
  62. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/LICENSE +0 -0
  63. {python_event_sourcery-0.4.1a1/event_sourcery → python_event_sourcery-0.5.0/event_sourcery/_event_store}/__init__.py +0 -0
  64. {python_event_sourcery-0.4.1a1/event_sourcery_django/migrations → python_event_sourcery-0.5.0/event_sourcery/_event_store/event}/__init__.py +0 -0
  65. /python_event_sourcery-0.4.1a1/event_sourcery/py.typed → /python_event_sourcery-0.5.0/event_sourcery/_event_store/subscription/__init__.py +0 -0
  66. {python_event_sourcery-0.4.1a1/event_sourcery/event_store → python_event_sourcery-0.5.0/event_sourcery/_event_store}/tenant_id.py +0 -0
  67. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery/event_sourcing/__init__.py +0 -0
  68. {python_event_sourcery-0.4.1a1/event_sourcery_django → python_event_sourcery-0.5.0/event_sourcery}/py.typed +0 -0
  69. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery/read_model/__init__.py +0 -0
  70. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_django/migrations/0001_initial.py +0 -0
  71. /python_event_sourcery-0.4.1a1/event_sourcery_kurrentdb/py.typed → /python_event_sourcery-0.5.0/event_sourcery_django/migrations/__init__.py +0 -0
  72. {python_event_sourcery-0.4.1a1/event_sourcery_sqlalchemy → python_event_sourcery-0.5.0/event_sourcery_django}/py.typed +0 -0
  73. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_sqlalchemy/guid.py +0 -0
  74. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_sqlalchemy/jsonb.py +0 -0
  75. {python_event_sourcery-0.4.1a1 → python_event_sourcery-0.5.0}/event_sourcery_sqlalchemy/models/default.py +0 -0
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.3
2
+ Name: python-event-sourcery
3
+ Version: 0.5.0
4
+ Summary: A library for event-based systems in Python.
5
+ License: MIT
6
+ Author: Sebastian Buczyński
7
+ Author-email: nnplaya@gmail.com
8
+ Requires-Python: >=3.10
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Provides-Extra: django
16
+ Provides-Extra: kurrentdb
17
+ Provides-Extra: sqlalchemy
18
+ Requires-Dist: SQLAlchemy (>=2.0) ; extra == "sqlalchemy"
19
+ Requires-Dist: django (>=4,<6) ; extra == "django"
20
+ Requires-Dist: kurrentdbclient (>=1.0.6) ; extra == "kurrentdb"
21
+ Requires-Dist: more-itertools
22
+ Requires-Dist: psycopg2-binary (>2,<3)
23
+ Requires-Dist: pydantic (>=2.0,<3)
24
+ Description-Content-Type: text/markdown
25
+
26
+
27
+ # Python Event Sourcery
28
+
29
+ A library for event-based systems in Python. For event sourcing, CQRS, and event-driven architectures.
30
+
31
+ ```python
32
+ event = EventSourceryIsBorn()
33
+
34
+ event_store.append(event, stream_id=stream_id)
35
+ ```
36
+
37
+ _Under heavy development_
38
+
39
+ ---
40
+
41
+ Documentation: [python-event-sourcery.github.io/python-event-sourcery/](https://python-event-sourcery.github.io/python-event-sourcery/)
42
+
43
+ ---
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ pip install python-event-sourcery
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Easy to setup
54
+ Just configure your `EventStore` instance (or a factory) and call `configure_models` once. Examples below.
55
+
56
+ ## Versatile
57
+ Event Sourcery makes no assumptions about your configuration or session management. It's designed to be plugged in into what you already have, without a need to adjust anything. It can be integrated smoothly with thread-scoped SQLAlchemy sessions as well as dependency injection.
58
+
59
+ ## NOT opinionated
60
+ Although one can easily start with a library, the latter is very customizable and DOES NOT enforce a particular style of using. Also, libraries integrated, i.e. SQLAlchemy and Pydantic are not hardcoded dependencies. They can be replaced with a finite amount of effort.
61
+
62
+ ---
63
+
64
+ ## Use cases & features
65
+ Until full documentation is available, you can follow tests
66
+
67
+ - Event Sourcing [tests](https://github.com/Enforcer/python-event-sourcery/blob/main/tests/repository/test_aggregate_context_manager.py)
68
+ - Snapshots (for Event Sourcing) [tests](https://github.com/Enforcer/python-event-sourcery/blob/main/tests/event_store/test_snapshots.py)
69
+ - Event Store - storage for events [tests](https://github.com/Enforcer/python-event-sourcery/blob/main/tests/event_store/test_save_retrieval.py)
70
+ - Outbox pattern [tests](https://github.com/Enforcer/python-event-sourcery/blob/main/tests/outbox/test_outbox.py)
71
+ - Concurrency control with optimistic locking [tests](https://github.com/Enforcer/python-event-sourcery/blob/main/tests/event_store/test_concurrency_control.py)
72
+
73
+ ---
74
+
75
+ ## Quick start
76
+
77
+ The script is complete, it should run as-is provided FastAPI and library with dependencies are installed.
78
+ See examples/0-fastapi-integration
79
+
80
+ ```python
81
+ from typing import Iterator, Literal
82
+ from uuid import uuid4
83
+
84
+ from fastapi import FastAPI, Depends
85
+ from sqlalchemy import create_engine
86
+ from sqlalchemy.orm import Session, as_declarative
87
+
88
+ # Import a couple of things from event_sourcery
89
+ from event_sourcery import EventStore # for type annotations
90
+ from event_sourcery import (
91
+ configure_models, # set-up function for library's models
92
+ get_event_store, # factory function to start quickly
93
+ Event, # base class for events
94
+ )
95
+
96
+ # Set up your DB and application as you would normally do
97
+ engine = create_engine(
98
+ "postgresql://event_sourcery:event_sourcery@localhost:5432/event_sourcery"
99
+ )
100
+
101
+
102
+ def db_session() -> Iterator[Session]:
103
+ session = Session(bind=engine)
104
+ try:
105
+ yield session
106
+ finally:
107
+ session.close()
108
+
109
+
110
+ @as_declarative()
111
+ class Base:
112
+ pass
113
+
114
+
115
+ app = FastAPI(on_startup=[lambda: Base.metadata.create_all(bind=engine)])
116
+
117
+ # initialize Event Sourcery models, so they can be handled by SQLAlchemy and e.g. alembic
118
+ configure_models(Base)
119
+
120
+
121
+ # Set up factory for event store to be injected into views
122
+ def event_store(session: Session = Depends(db_session)) -> EventStore:
123
+ return get_event_store(session)
124
+
125
+
126
+ # Define your event(s) using base-class provided
127
+ class CustomerSubscribed(Event):
128
+ plan_id: int
129
+ model: Literal["monthly", "yearly"]
130
+
131
+
132
+ @app.post("/subscription")
133
+ def subscribe(
134
+ event_store: EventStore = Depends(event_store),
135
+ session: Session = Depends(db_session),
136
+ ):
137
+ # Create an event
138
+ event = CustomerSubscribed(plan_id=1, model="yearly")
139
+ # Put it in the event store!
140
+ event_store.append(event, stream_id=uuid4())
141
+ session.commit()
142
+
143
+ ```
144
+
145
+ ## Feature requests / feedback
146
+
147
+ Anything missing? Create an issue in this repository. Let others upvote
148
+
149
+ ## Inspirations
150
+ - [Rails Event Store](https://railseventstore.org/)
151
+ - [Marten](https://martendb.io/)
152
+
@@ -0,0 +1,126 @@
1
+
2
+ # Python Event Sourcery
3
+
4
+ A library for event-based systems in Python. For event sourcing, CQRS, and event-driven architectures.
5
+
6
+ ```python
7
+ event = EventSourceryIsBorn()
8
+
9
+ event_store.append(event, stream_id=stream_id)
10
+ ```
11
+
12
+ _Under heavy development_
13
+
14
+ ---
15
+
16
+ Documentation: [python-event-sourcery.github.io/python-event-sourcery/](https://python-event-sourcery.github.io/python-event-sourcery/)
17
+
18
+ ---
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ pip install python-event-sourcery
24
+ ```
25
+
26
+ ---
27
+
28
+ ## Easy to setup
29
+ Just configure your `EventStore` instance (or a factory) and call `configure_models` once. Examples below.
30
+
31
+ ## Versatile
32
+ Event Sourcery makes no assumptions about your configuration or session management. It's designed to be plugged in into what you already have, without a need to adjust anything. It can be integrated smoothly with thread-scoped SQLAlchemy sessions as well as dependency injection.
33
+
34
+ ## NOT opinionated
35
+ Although one can easily start with a library, the latter is very customizable and DOES NOT enforce a particular style of using. Also, libraries integrated, i.e. SQLAlchemy and Pydantic are not hardcoded dependencies. They can be replaced with a finite amount of effort.
36
+
37
+ ---
38
+
39
+ ## Use cases & features
40
+ Until full documentation is available, you can follow tests
41
+
42
+ - Event Sourcing [tests](https://github.com/Enforcer/python-event-sourcery/blob/main/tests/repository/test_aggregate_context_manager.py)
43
+ - Snapshots (for Event Sourcing) [tests](https://github.com/Enforcer/python-event-sourcery/blob/main/tests/event_store/test_snapshots.py)
44
+ - Event Store - storage for events [tests](https://github.com/Enforcer/python-event-sourcery/blob/main/tests/event_store/test_save_retrieval.py)
45
+ - Outbox pattern [tests](https://github.com/Enforcer/python-event-sourcery/blob/main/tests/outbox/test_outbox.py)
46
+ - Concurrency control with optimistic locking [tests](https://github.com/Enforcer/python-event-sourcery/blob/main/tests/event_store/test_concurrency_control.py)
47
+
48
+ ---
49
+
50
+ ## Quick start
51
+
52
+ The script is complete, it should run as-is provided FastAPI and library with dependencies are installed.
53
+ See examples/0-fastapi-integration
54
+
55
+ ```python
56
+ from typing import Iterator, Literal
57
+ from uuid import uuid4
58
+
59
+ from fastapi import FastAPI, Depends
60
+ from sqlalchemy import create_engine
61
+ from sqlalchemy.orm import Session, as_declarative
62
+
63
+ # Import a couple of things from event_sourcery
64
+ from event_sourcery import EventStore # for type annotations
65
+ from event_sourcery import (
66
+ configure_models, # set-up function for library's models
67
+ get_event_store, # factory function to start quickly
68
+ Event, # base class for events
69
+ )
70
+
71
+ # Set up your DB and application as you would normally do
72
+ engine = create_engine(
73
+ "postgresql://event_sourcery:event_sourcery@localhost:5432/event_sourcery"
74
+ )
75
+
76
+
77
+ def db_session() -> Iterator[Session]:
78
+ session = Session(bind=engine)
79
+ try:
80
+ yield session
81
+ finally:
82
+ session.close()
83
+
84
+
85
+ @as_declarative()
86
+ class Base:
87
+ pass
88
+
89
+
90
+ app = FastAPI(on_startup=[lambda: Base.metadata.create_all(bind=engine)])
91
+
92
+ # initialize Event Sourcery models, so they can be handled by SQLAlchemy and e.g. alembic
93
+ configure_models(Base)
94
+
95
+
96
+ # Set up factory for event store to be injected into views
97
+ def event_store(session: Session = Depends(db_session)) -> EventStore:
98
+ return get_event_store(session)
99
+
100
+
101
+ # Define your event(s) using base-class provided
102
+ class CustomerSubscribed(Event):
103
+ plan_id: int
104
+ model: Literal["monthly", "yearly"]
105
+
106
+
107
+ @app.post("/subscription")
108
+ def subscribe(
109
+ event_store: EventStore = Depends(event_store),
110
+ session: Session = Depends(db_session),
111
+ ):
112
+ # Create an event
113
+ event = CustomerSubscribed(plan_id=1, model="yearly")
114
+ # Put it in the event store!
115
+ event_store.append(event, stream_id=uuid4())
116
+ session.commit()
117
+
118
+ ```
119
+
120
+ ## Feature requests / feedback
121
+
122
+ Anything missing? Create an issue in this repository. Let others upvote
123
+
124
+ ## Inspirations
125
+ - [Rails Event Store](https://railseventstore.org/)
126
+ - [Marten](https://martendb.io/)
@@ -0,0 +1,21 @@
1
+ __all__ = [
2
+ "DEFAULT_TENANT",
3
+ "NO_VERSIONING",
4
+ "Backend",
5
+ "Event",
6
+ "EventStore",
7
+ "Outbox",
8
+ "StreamCategory",
9
+ "StreamId",
10
+ "StreamUUID",
11
+ "TenantId",
12
+ "TransactionalBackend",
13
+ ]
14
+
15
+ from event_sourcery._event_store.backend import Backend, TransactionalBackend
16
+ from event_sourcery._event_store.event.dto import Event
17
+ from event_sourcery._event_store.event_store import EventStore
18
+ from event_sourcery._event_store.outbox import Outbox
19
+ from event_sourcery._event_store.stream_id import StreamCategory, StreamId, StreamUUID
20
+ from event_sourcery._event_store.tenant_id import DEFAULT_TENANT, TenantId
21
+ from event_sourcery._event_store.versioning import NO_VERSIONING
@@ -1,48 +1,42 @@
1
- from collections.abc import Callable, Iterator
2
- from contextlib import AbstractContextManager
1
+ from collections.abc import Callable
3
2
  from functools import wraps
4
3
  from typing import NoReturn, TypeVar, cast
5
4
 
6
5
  from typing_extensions import Self
7
6
 
8
- from event_sourcery.event_store import subscription
9
- from event_sourcery.event_store.dispatcher import Dispatcher, Listeners
10
- from event_sourcery.event_store.event import (
7
+ from event_sourcery._event_store.event.encryption import (
11
8
  Encryption,
12
- EventRegistry,
9
+ EncryptionKeyStorageStrategy,
10
+ EncryptionStrategy,
13
11
  NoEncryptionStrategy,
14
12
  NoKeyStorageStrategy,
15
- RawEvent,
16
- RecordedRaw,
17
- Serde,
18
13
  )
19
- from event_sourcery.event_store.event_store import EventStore
20
- from event_sourcery.event_store.interfaces import (
21
- EncryptionKeyStorageStrategy,
22
- EncryptionStrategy,
14
+ from event_sourcery._event_store.event.registry import EventRegistry
15
+ from event_sourcery._event_store.event.serde import Serde
16
+ from event_sourcery._event_store.event_store import (
17
+ EventStore,
18
+ StorageStrategy,
19
+ )
20
+ from event_sourcery._event_store.outbox import (
21
+ NoOutboxStorageStrategy,
22
+ Outbox,
23
23
  OutboxFiltererStrategy,
24
24
  OutboxStorageStrategy,
25
- StorageStrategy,
25
+ no_filter,
26
+ )
27
+ from event_sourcery._event_store.subscription.builder import (
28
+ SubscriptionBuilder,
29
+ )
30
+ from event_sourcery._event_store.subscription.in_transaction import (
31
+ Dispatcher,
32
+ Listeners,
33
+ )
34
+ from event_sourcery._event_store.subscription.interfaces import (
35
+ PositionPhase,
26
36
  SubscriptionStrategy,
27
37
  )
28
- from event_sourcery.event_store.outbox import Outbox
29
- from event_sourcery.event_store.tenant_id import DEFAULT_TENANT, TenantId
30
-
31
-
32
- class NoProviderConfigured(Exception):
33
- pass
34
-
35
-
36
- def no_filter(entry: RawEvent) -> bool:
37
- return True
38
-
39
-
40
- class NoOutboxStorageStrategy(OutboxStorageStrategy):
41
- def outbox_entries(
42
- self, limit: int
43
- ) -> Iterator[AbstractContextManager[RecordedRaw]]:
44
- return iter([])
45
-
38
+ from event_sourcery._event_store.tenant_id import DEFAULT_TENANT, TenantId
39
+ from event_sourcery.exceptions import NoProviderConfigured
46
40
 
47
41
  T = TypeVar("T")
48
42
  T_co = TypeVar("T_co", covariant=True)
@@ -51,6 +45,16 @@ _Provider = Callable[["_Container"], T]
51
45
 
52
46
 
53
47
  def singleton(provider: _Provider[T]) -> _Provider[T]:
48
+ """
49
+ Decorator that ensures the provider returns the same instance (singleton)
50
+ for every call within the container's lifecycle.
51
+
52
+ Args:
53
+ provider (_Provider[T]): The provider function to wrap.
54
+
55
+ Returns:
56
+ _Provider[T]: A provider that always returns the same instance.
57
+ """
54
58
  result: T | None = None
55
59
 
56
60
  @wraps(provider)
@@ -65,6 +69,13 @@ def singleton(provider: _Provider[T]) -> _Provider[T]:
65
69
 
66
70
 
67
71
  def not_configured(error_message: str) -> _Provider[T]:
72
+ """
73
+ Used as a placeholder for providers that require configuration.
74
+
75
+ Args:
76
+ error_message (str): str. The error message to raise with the exception.
77
+ """
78
+
68
79
  def _raise(container: "_Container") -> NoReturn:
69
80
  raise NoProviderConfigured(error_message) # pragma: no cover
70
81
 
@@ -97,6 +108,18 @@ class _Container:
97
108
 
98
109
 
99
110
  class Backend(_Container):
111
+ """
112
+ Dependency Injection container for Event Sourcery components.
113
+
114
+ Dict-like object resolving object instance during access by type.
115
+ Providers are passed setting the type as key and provider as a value.
116
+
117
+ By default, basic implementations are registered or an exception is raised
118
+ if backend configuration (e.g., storage strategy) is required.
119
+
120
+ Core components are exposed by properties.
121
+ """
122
+
100
123
  def __init__(self) -> None:
101
124
  super().__init__()
102
125
  self[TenantId] = DEFAULT_TENANT
@@ -129,29 +152,44 @@ class Backend(_Container):
129
152
  self[SubscriptionStrategy] = not_configured(
130
153
  "Use one of pyES backends: SQLAlchemy, Django or KurrentDB",
131
154
  )
132
- self[subscription.PositionPhase] = lambda c: subscription.SubscriptionBuilder(
155
+ self[PositionPhase] = lambda c: SubscriptionBuilder(
133
156
  c[Serde],
134
157
  c[SubscriptionStrategy],
135
158
  )
136
159
 
137
160
  @property
138
161
  def event_store(self) -> EventStore:
162
+ """
163
+ Returns the current instance of `EventStore`.
164
+ """
139
165
  return self[EventStore]
140
166
 
141
167
  @property
142
168
  def outbox(self) -> Outbox:
169
+ """
170
+ Returns the current instance of `Outbox`.
171
+ """
143
172
  return self[Outbox]
144
173
 
145
174
  @property
146
- def subscriber(self) -> subscription.PositionPhase:
147
- return self[subscription.PositionPhase]
175
+ def subscriber(self) -> PositionPhase:
176
+ """
177
+ Returns the current instance of `SubscriptionBuilder` (as `PositionPhase`).
178
+ """
179
+ return self[PositionPhase]
148
180
 
149
181
  def in_tenant_mode(self, tenant_id: TenantId) -> Self:
182
+ """
183
+ Returns a copy of the backend with the specified tenant ID set.
184
+ """
150
185
  in_tenant_mode = self.copy()
151
186
  in_tenant_mode[TenantId] = tenant_id
152
187
  return in_tenant_mode
153
188
 
154
189
  def with_outbox(self, filterer: OutboxFiltererStrategy = no_filter) -> Self:
190
+ """
191
+ Configure the outbox with a custom filter.
192
+ """
155
193
  raise NotImplementedError()
156
194
 
157
195
  def with_encryption(
@@ -159,6 +197,9 @@ class Backend(_Container):
159
197
  strategy: EncryptionStrategy,
160
198
  key_storage: EncryptionKeyStorageStrategy,
161
199
  ) -> Self:
200
+ """
201
+ Configures event encryption with the provided strategy and key storage.
202
+ """
162
203
  self[EncryptionStrategy] = strategy
163
204
  self[EncryptionKeyStorageStrategy] = lambda c: key_storage.scoped_for_tenant(
164
205
  c[TenantId],
@@ -167,6 +208,16 @@ class Backend(_Container):
167
208
 
168
209
 
169
210
  class TransactionalBackend(Backend):
211
+ """
212
+ Backend variant that provides transactional event handling support.
213
+
214
+ Registers additional services for managing listeners and dispatchers
215
+ to enable in-transaction event processing.
216
+
217
+ Properties:
218
+ in_transaction (Listeners): The current state of in transaction Listeners.
219
+ """
220
+
170
221
  def __init__(self) -> None:
171
222
  super().__init__()
172
223
  self[Listeners] = singleton(lambda _: Listeners())
@@ -174,4 +225,7 @@ class TransactionalBackend(Backend):
174
225
 
175
226
  @property
176
227
  def in_transaction(self) -> Listeners:
228
+ """
229
+ Returns the current instance of `Listeners` for transactional event handling.
230
+ """
177
231
  return cast(Listeners, self[Listeners])
@@ -6,14 +6,30 @@ from uuid import UUID, uuid4
6
6
 
7
7
  from pydantic import BaseModel
8
8
 
9
- from event_sourcery.event_store.stream_id import StreamId
10
- from event_sourcery.event_store.tenant_id import DEFAULT_TENANT, TenantId
9
+ from event_sourcery._event_store.stream_id import StreamId
10
+ from event_sourcery._event_store.tenant_id import DEFAULT_TENANT, TenantId
11
11
 
12
12
  DataSubject = object()
13
13
 
14
14
 
15
15
  @dataclasses.dataclass(frozen=True)
16
16
  class RawEvent(BaseModel):
17
+ """
18
+ Data Transfer Object representing a raw event that will be stored.
19
+
20
+ Contains all event metadata and payload in a flat structure.
21
+ Used for low-level event store operations, serialization, and transport.
22
+
23
+ Attributes:
24
+ uuid (UUID): Unique identifier of the event.
25
+ stream_id (StreamId): Identifier of the stream to which the event belongs.
26
+ created_at (datetime): Timestamp when the event was created.
27
+ name (str): Name/type of the event.
28
+ data (dict[str, Any]): Event payload (fields and values).
29
+ context (dict[str, Any]): Event custom metadata.
30
+ version (int | None): Version of the event in the stream (if applicable).
31
+ """
32
+
17
33
  uuid: UUID
18
34
  stream_id: StreamId
19
35
  created_at: datetime
@@ -28,6 +44,18 @@ Position: TypeAlias = int
28
44
 
29
45
  @dataclasses.dataclass(frozen=True)
30
46
  class RecordedRaw(BaseModel):
47
+ """
48
+ DTO representing a raw event received from the event store.
49
+
50
+ Used for low-level event store operations.
51
+ Wraps a RawEvent with additional metadata (position and tenant id).
52
+
53
+ Attributes:
54
+ entry (RawEvent): The raw event data and metadata.
55
+ position (Position): Position of the event in the event store.
56
+ tenant_id (TenantId): Tenant identifier (for multi-tenant scenarios).
57
+ """
58
+
31
59
  entry: RawEvent
32
60
  position: Position
33
61
  tenant_id: TenantId = DEFAULT_TENANT
@@ -48,16 +76,38 @@ TEvent = TypeVar("TEvent", bound=Event)
48
76
 
49
77
 
50
78
  class Context(BaseModel, extra="allow"):
79
+ """
80
+ Extensible context object for event metadata.
81
+
82
+ Designed to carry correlation and causation identifiers, and can be extended
83
+ with additional metadata fields as needed. This allows for flexible propagation
84
+ of context information throughout event processing and handling pipelines.
85
+
86
+ Attributes:
87
+ correlation_id (UUID | None): Identifier for correlating related events.
88
+ causation_id (UUID | None): Identifier for the cause of the event.
89
+
90
+ Additional fields can be added dynamically due to `extra="allow"`.
91
+ """
92
+
51
93
  correlation_id: UUID | None = None
52
94
  causation_id: UUID | None = None
53
95
 
54
96
 
55
97
  @dataclasses.dataclass()
56
98
  class WrappedEvent(Generic[TEvent]):
57
- """Wrapper for events with all relevant metadata.
99
+ """
100
+ Wrapper for events with all relevant metadata.
58
101
 
59
102
  Returned from EventStore when loading events from a stream.
60
103
 
104
+ Attributes:
105
+ event (TEvent): The event instance.
106
+ version (int | None): Version of the event in the stream (if applicable).
107
+ uuid (UUID): Unique identifier of the event instance.
108
+ created_at (datetime): Timestamp when the event was created.
109
+ context (Context): Context object containing additional metadata.
110
+
61
111
  Example usage:
62
112
  ```
63
113
  class OrderCancelled(Event):
@@ -83,6 +133,17 @@ class WrappedEvent(Generic[TEvent]):
83
133
 
84
134
  @dataclasses.dataclass(frozen=True)
85
135
  class Entry:
136
+ """
137
+ Container for a single event and its stream identifier.
138
+
139
+ Used as a base DTO for representing an event (with all metadata) and the stream
140
+ it belongs to. Serves as a base for more specialized event store DTOs (e.g. Recorded).
141
+
142
+ Attributes:
143
+ wrapped_event (WrappedEvent[Event]): The event instance with metadata.
144
+ stream_id (StreamId): The identifier of the stream to which the event belongs.
145
+ """
146
+
86
147
  wrapped_event: WrappedEvent[Event]
87
148
  stream_id: StreamId
88
149