python-event-sourcery 0.4.0a3__tar.gz → 0.4.0a4__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.4.0a3 → python_event_sourcery-0.4.0a4}/PKG-INFO +1 -1
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/aggregate/repository.py +1 -1
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/event/dto.py +21 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/event/registry.py +7 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/event_store.py +81 -14
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/factory.py +2 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/in_memory.py +2 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/pyproject.toml +2 -1
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/LICENSE +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/__init__.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/aggregate/__init__.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/aggregate/aggregate.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/__init__.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/dispatcher.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/event/__init__.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/event/serde.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/exceptions.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/interfaces.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/outbox.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/stream_id.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/subscription.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/versioning.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/py.typed +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/read_model/__init__.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/read_model/cursors_dao.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/read_model/projector.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/__init__.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/dto.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/event_store.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/outbox.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/py.typed +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/stream.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/subscription.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/__init__.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/cursors_dao.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/dto.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/event_store.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/guid.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/jsonb.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/models.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/outbox.py +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/py.typed +0 -0
- {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/subscription.py +0 -0
|
@@ -27,6 +27,14 @@ class RecordedRaw(BaseModel):
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class Event(BaseModel, extra="forbid"):
|
|
30
|
+
"""Base class for all events.
|
|
31
|
+
|
|
32
|
+
Example usage:
|
|
33
|
+
```
|
|
34
|
+
class OrderCancelled(Event):
|
|
35
|
+
order_id: OrderId
|
|
36
|
+
```
|
|
37
|
+
"""
|
|
30
38
|
__registry__: ClassVar = EventRegistry()
|
|
31
39
|
|
|
32
40
|
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
@@ -42,6 +50,19 @@ class Context(BaseModel, extra="allow"):
|
|
|
42
50
|
|
|
43
51
|
|
|
44
52
|
class Metadata(BaseModel, Generic[TEvent], extra="forbid"):
|
|
53
|
+
"""Wrapper for events with all relevant metadata.
|
|
54
|
+
|
|
55
|
+
Returned from EventStore when loading events from a stream.
|
|
56
|
+
|
|
57
|
+
Example usage:
|
|
58
|
+
```
|
|
59
|
+
class OrderCancelled(Event):
|
|
60
|
+
order_id: OrderId
|
|
61
|
+
|
|
62
|
+
event = OrderCancelled(order_id=OrderId("#123"))
|
|
63
|
+
metadata = Metadata.wrap(event, version=1)
|
|
64
|
+
```
|
|
65
|
+
"""
|
|
45
66
|
event: TEvent
|
|
46
67
|
version: int | None
|
|
47
68
|
uuid: UUID = Field(default_factory=uuid4)
|
|
@@ -18,11 +18,18 @@ def event_name(cls: type) -> str:
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class EventRegistry:
|
|
21
|
+
"""Keeps mappings between event types and their names.
|
|
22
|
+
|
|
23
|
+
Normally, there is no need to use it directly. If one needs to have multiple
|
|
24
|
+
registries or wants more granular control, they can pass an instance
|
|
25
|
+
of EventRegistry to BackendFactory."""
|
|
26
|
+
|
|
21
27
|
def __init__(self) -> None:
|
|
22
28
|
self._types_to_names: dict[type[Event], str] = {}
|
|
23
29
|
self._names_to_types: dict[str, type[Event]] = {}
|
|
24
30
|
|
|
25
31
|
def add(self, event: type["Event"]) -> type["Event"]:
|
|
32
|
+
"""Add event subclass to the registry."""
|
|
26
33
|
if event in self._types_to_names:
|
|
27
34
|
raise DuplicatedEvent(f"Duplicated Event detected! {event}")
|
|
28
35
|
|
|
@@ -13,6 +13,8 @@ from event_sourcery.event_store.versioning import (
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class EventStore:
|
|
16
|
+
"""API for working with events."""
|
|
17
|
+
|
|
16
18
|
def __init__(self, storage_strategy: StorageStrategy, serde: Serde) -> None:
|
|
17
19
|
self._storage_strategy = storage_strategy
|
|
18
20
|
self._serde = serde
|
|
@@ -23,6 +25,24 @@ class EventStore:
|
|
|
23
25
|
start: int | None = None,
|
|
24
26
|
stop: int | None = None,
|
|
25
27
|
) -> Sequence[Metadata]:
|
|
28
|
+
"""Loads events from a given stream.
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
>>> load_stream(stream_id=StreamId(name="not_existing_stream"))
|
|
32
|
+
[]
|
|
33
|
+
>>> load_stream(stream_id=StreamId(name="existing_stream"))
|
|
34
|
+
[Metadata(..., version=1), Metadata(...), Metadata(..., version=3)]
|
|
35
|
+
>>> load_stream(stream_id=StreamId(name="existing_stream"), start=2, stop=3)
|
|
36
|
+
[Metadata(..., version=2)]
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
stream_id: The stream identifier to load events from.
|
|
40
|
+
start: The stream version to start loading from (including).
|
|
41
|
+
stop: The stream version to stop loading at (excluding).
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
A sequence of events or empty list if the stream doesn't exist.
|
|
45
|
+
"""
|
|
26
46
|
events = self._storage_strategy.fetch_events(stream_id, start=start, stop=stop)
|
|
27
47
|
return self._deserialize_events(events)
|
|
28
48
|
|
|
@@ -34,6 +54,26 @@ class EventStore:
|
|
|
34
54
|
stream_id: StreamId,
|
|
35
55
|
expected_version: int | Versioning = 0,
|
|
36
56
|
) -> None:
|
|
57
|
+
"""Appends events to a stream with a given ID.
|
|
58
|
+
|
|
59
|
+
Implements optimistic locking to ensure stream wasn't modified since last read.
|
|
60
|
+
To use it, pass the expected version of the stream.
|
|
61
|
+
|
|
62
|
+
Examples:
|
|
63
|
+
>>> append(Metadata(...), stream_id=StreamId())
|
|
64
|
+
None
|
|
65
|
+
>>> append(Metadata(...), stream_id=StreamId(), expected_version=1)
|
|
66
|
+
None
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
first: The first event to append (Metadata or Event).
|
|
70
|
+
*events: The rest of the events to append (same type as first argument).
|
|
71
|
+
stream_id: The stream identifier to append events to.
|
|
72
|
+
expected_version: The expected version of the stream
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
None
|
|
76
|
+
"""
|
|
37
77
|
self._append(
|
|
38
78
|
stream_id=stream_id,
|
|
39
79
|
events=(first, *events),
|
|
@@ -71,20 +111,6 @@ class EventStore:
|
|
|
71
111
|
) -> Sequence[Metadata]:
|
|
72
112
|
return [Metadata.wrap(event=event, version=None) for event in events]
|
|
73
113
|
|
|
74
|
-
@singledispatchmethod
|
|
75
|
-
def publish(
|
|
76
|
-
self,
|
|
77
|
-
first: Metadata,
|
|
78
|
-
*events: Metadata,
|
|
79
|
-
stream_id: StreamId,
|
|
80
|
-
expected_version: int | Versioning = 0,
|
|
81
|
-
) -> None:
|
|
82
|
-
self._append(
|
|
83
|
-
stream_id=stream_id,
|
|
84
|
-
events=(first, *events),
|
|
85
|
-
expected_version=expected_version,
|
|
86
|
-
)
|
|
87
|
-
|
|
88
114
|
def _append(
|
|
89
115
|
self,
|
|
90
116
|
stream_id: StreamId,
|
|
@@ -108,9 +134,41 @@ class EventStore:
|
|
|
108
134
|
)
|
|
109
135
|
|
|
110
136
|
def delete_stream(self, stream_id: StreamId) -> None:
|
|
137
|
+
"""Deletes a stream with a given ID.
|
|
138
|
+
|
|
139
|
+
If a stream does not exist, this method does nothing.
|
|
140
|
+
|
|
141
|
+
Examples:
|
|
142
|
+
>>> delete_stream(StreamId())
|
|
143
|
+
None
|
|
144
|
+
>>> delete_stream(StreamId(name="not_existing_stream"))
|
|
145
|
+
None
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
stream_id: The stream identifier to delete.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
None
|
|
152
|
+
"""
|
|
111
153
|
self._storage_strategy.delete_stream(stream_id)
|
|
112
154
|
|
|
113
155
|
def save_snapshot(self, stream_id: StreamId, snapshot: Metadata) -> None:
|
|
156
|
+
"""Saves a snapshot of the stream.
|
|
157
|
+
|
|
158
|
+
Examples:
|
|
159
|
+
>>> save_snapshot(StreamId(), Metadata(...))
|
|
160
|
+
None
|
|
161
|
+
>>> save_snapshot(StreamId(name="not_existing_stream"), Metadata(...))
|
|
162
|
+
None
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
stream_id: The stream identifier to save the snapshot.
|
|
166
|
+
snapshot: The snapshot to save.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
None
|
|
170
|
+
"""
|
|
171
|
+
|
|
114
172
|
serialized = self._serde.serialize(event=snapshot, stream_id=stream_id)
|
|
115
173
|
self._storage_strategy.save_snapshot(serialized)
|
|
116
174
|
|
|
@@ -126,4 +184,13 @@ class EventStore:
|
|
|
126
184
|
|
|
127
185
|
@property
|
|
128
186
|
def position(self) -> Position | None:
|
|
187
|
+
"""Returns the current position of the event store.
|
|
188
|
+
|
|
189
|
+
Examples:
|
|
190
|
+
>>> position
|
|
191
|
+
None # nothing was saved yet
|
|
192
|
+
>>> position
|
|
193
|
+
Position(15) # Some events were saved
|
|
194
|
+
|
|
195
|
+
"""
|
|
129
196
|
return self._storage_strategy.current_position
|
|
@@ -298,6 +298,8 @@ class Config(BaseModel):
|
|
|
298
298
|
|
|
299
299
|
@dataclass(repr=False)
|
|
300
300
|
class InMemoryBackendFactory(BackendFactory):
|
|
301
|
+
"""Lightweight in-memory backend factory for testing and development."""
|
|
302
|
+
|
|
301
303
|
serde = Serde(Event.__registry__)
|
|
302
304
|
|
|
303
305
|
_config: Config = field(default_factory=Config)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "python-event-sourcery"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.0a4"
|
|
4
4
|
description = ""
|
|
5
5
|
authors = ["Your Name <you@example.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -26,6 +26,7 @@ pytest-cov = "*"
|
|
|
26
26
|
pytest-django = {version = "*"}
|
|
27
27
|
mkdocs-material = "^8.3.9"
|
|
28
28
|
psycopg2-binary = "^2.9.3"
|
|
29
|
+
mkdocstrings = {version = "*", extras = ["python"]}
|
|
29
30
|
|
|
30
31
|
[tool.poetry.extras]
|
|
31
32
|
sqlalchemy = ["SQLAlchemy"]
|
|
File without changes
|
|
File without changes
|
{python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/aggregate/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/outbox.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/event_store.py
RENAMED
|
File without changes
|
{python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/outbox.py
RENAMED
|
File without changes
|
{python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/py.typed
RENAMED
|
File without changes
|
{python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/stream.py
RENAMED
|
File without changes
|
{python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/subscription.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/dto.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/guid.py
RENAMED
|
File without changes
|
{python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/jsonb.py
RENAMED
|
File without changes
|
{python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/models.py
RENAMED
|
File without changes
|
{python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/outbox.py
RENAMED
|
File without changes
|
{python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/py.typed
RENAMED
|
File without changes
|
|
File without changes
|