python-event-sourcery 0.4.0a3__tar.gz → 0.4.0a5__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 (44) hide show
  1. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/PKG-INFO +1 -1
  2. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/aggregate/repository.py +9 -3
  3. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/event_store/__init__.py +2 -2
  4. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/event_store/dispatcher.py +4 -4
  5. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/event_store/event/__init__.py +2 -2
  6. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/event_store/event/dto.py +27 -4
  7. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/event_store/event/registry.py +7 -0
  8. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/event_store/event/serde.py +5 -5
  9. python_event_sourcery-0.4.0a5/event_sourcery/event_store/event_store.py +202 -0
  10. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/event_store/factory.py +2 -0
  11. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/event_store/in_memory.py +2 -0
  12. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/event_store/outbox.py +1 -1
  13. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/read_model/projector.py +12 -12
  14. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery_sqlalchemy/cursors_dao.py +1 -1
  15. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery_sqlalchemy/event_store.py +1 -1
  16. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/pyproject.toml +2 -1
  17. python_event_sourcery-0.4.0a3/event_sourcery/event_store/event_store.py +0 -129
  18. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/LICENSE +0 -0
  19. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/__init__.py +0 -0
  20. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/aggregate/__init__.py +0 -0
  21. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/aggregate/aggregate.py +0 -0
  22. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/event_store/exceptions.py +0 -0
  23. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/event_store/interfaces.py +0 -0
  24. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/event_store/stream_id.py +0 -0
  25. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/event_store/subscription.py +0 -0
  26. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/event_store/versioning.py +0 -0
  27. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/py.typed +0 -0
  28. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/read_model/__init__.py +0 -0
  29. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery/read_model/cursors_dao.py +0 -0
  30. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery_esdb/__init__.py +0 -0
  31. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery_esdb/dto.py +0 -0
  32. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery_esdb/event_store.py +0 -0
  33. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery_esdb/outbox.py +0 -0
  34. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery_esdb/py.typed +0 -0
  35. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery_esdb/stream.py +0 -0
  36. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery_esdb/subscription.py +0 -0
  37. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery_sqlalchemy/__init__.py +0 -0
  38. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery_sqlalchemy/dto.py +0 -0
  39. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery_sqlalchemy/guid.py +0 -0
  40. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery_sqlalchemy/jsonb.py +0 -0
  41. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery_sqlalchemy/models.py +0 -0
  42. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery_sqlalchemy/outbox.py +0 -0
  43. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery_sqlalchemy/py.typed +0 -0
  44. {python_event_sourcery-0.4.0a3 → python_event_sourcery-0.4.0a5}/event_sourcery_sqlalchemy/subscription.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-event-sourcery
3
- Version: 0.4.0a3
3
+ Version: 0.4.0a5
4
4
  Summary:
5
5
  License: MIT
6
6
  Author: Your Name
@@ -3,7 +3,13 @@ from contextlib import contextmanager
3
3
  from typing import Generic, TypeVar, cast
4
4
 
5
5
  from event_sourcery.aggregate import Aggregate
6
- from event_sourcery.event_store import Event, EventStore, Metadata, StreamId, StreamUUID
6
+ from event_sourcery.event_store import (
7
+ Event,
8
+ EventStore,
9
+ StreamId,
10
+ StreamUUID,
11
+ WrappedEvent,
12
+ )
7
13
 
8
14
  TAggregate = TypeVar("TAggregate", bound=Aggregate)
9
15
  TEvent = TypeVar("TEvent", bound=Event)
@@ -42,14 +48,14 @@ class Repository(Generic[TAggregate]):
42
48
  with aggregate.__persisting_changes__() as pending:
43
49
  start_from = old_version + 1
44
50
  events = [
45
- Metadata.wrap(event, version)
51
+ WrappedEvent.wrap(event, version)
46
52
  for version, event in enumerate(pending, start=start_from)
47
53
  ]
48
54
 
49
55
  if not events:
50
56
  return
51
57
 
52
- self._event_store.publish(
58
+ self._event_store.append(
53
59
  *events,
54
60
  stream_id=stream_id,
55
61
  expected_version=old_version,
@@ -9,7 +9,7 @@ __all__ = [
9
9
  "ExplicitVersioning",
10
10
  "InMemoryBackendFactory",
11
11
  "Listener",
12
- "Metadata",
12
+ "WrappedEvent",
13
13
  "NO_VERSIONING",
14
14
  "Position",
15
15
  "RawEvent",
@@ -31,11 +31,11 @@ from event_sourcery.event_store.event import (
31
31
  Entry,
32
32
  Event,
33
33
  EventRegistry,
34
- Metadata,
35
34
  Position,
36
35
  RawEvent,
37
36
  Recorded,
38
37
  RecordedRaw,
38
+ WrappedEvent,
39
39
  )
40
40
  from event_sourcery.event_store.event_store import EventStore
41
41
  from event_sourcery.event_store.factory import (
@@ -3,14 +3,14 @@ from collections.abc import Callable
3
3
 
4
4
  from event_sourcery.event_store.event import (
5
5
  Event,
6
- Metadata,
7
6
  Position,
8
7
  RecordedRaw,
9
8
  Serde,
9
+ WrappedEvent,
10
10
  )
11
11
  from event_sourcery.event_store.stream_id import StreamId
12
12
 
13
- Listener = Callable[[Metadata, StreamId, Position | None], None]
13
+ Listener = Callable[[WrappedEvent, StreamId, Position | None], None]
14
14
 
15
15
 
16
16
  class Dispatcher:
@@ -22,10 +22,10 @@ class Dispatcher:
22
22
  for raw in raws:
23
23
  record = self._serde.deserialize_record(raw)
24
24
  for event_type, listeners in self._listeners.items():
25
- if not isinstance(record.metadata.event, event_type):
25
+ if not isinstance(record.wrapped_event.event, event_type):
26
26
  continue
27
27
  for listener in listeners:
28
- listener(record.metadata, record.stream_id, record.position)
28
+ listener(record.wrapped_event, record.stream_id, record.position)
29
29
 
30
30
  def register(self, listener: Listener, to: type[Event]) -> None:
31
31
  self._listeners[to].add(listener)
@@ -3,7 +3,7 @@ __all__ = [
3
3
  "Entry",
4
4
  "Event",
5
5
  "EventRegistry",
6
- "Metadata",
6
+ "WrappedEvent",
7
7
  "Position",
8
8
  "RawEvent",
9
9
  "Recorded",
@@ -16,11 +16,11 @@ from event_sourcery.event_store.event.dto import (
16
16
  Context,
17
17
  Entry,
18
18
  Event,
19
- Metadata,
20
19
  Position,
21
20
  RawEvent,
22
21
  Recorded,
23
22
  RecordedRaw,
23
+ WrappedEvent,
24
24
  )
25
25
  from event_sourcery.event_store.event.registry import EventRegistry
26
26
  from event_sourcery.event_store.event.serde import Serde
@@ -27,6 +27,15 @@ 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
+ """
38
+
30
39
  __registry__: ClassVar = EventRegistry()
31
40
 
32
41
  def __init_subclass__(cls, **kwargs: Any) -> None:
@@ -41,7 +50,21 @@ class Context(BaseModel, extra="allow"):
41
50
  causation_id: UUID | None = None
42
51
 
43
52
 
44
- class Metadata(BaseModel, Generic[TEvent], extra="forbid"):
53
+ class WrappedEvent(BaseModel, Generic[TEvent], extra="forbid"):
54
+ """Wrapper for events with all relevant metadata.
55
+
56
+ Returned from EventStore when loading events from a stream.
57
+
58
+ Example usage:
59
+ ```
60
+ class OrderCancelled(Event):
61
+ order_id: OrderId
62
+
63
+ event = OrderCancelled(order_id=OrderId("#123"))
64
+ wrapped_event = WrappedEvent.wrap(event, version=1)
65
+ ```
66
+ """
67
+
45
68
  event: TEvent
46
69
  version: int | None
47
70
  uuid: UUID = Field(default_factory=uuid4)
@@ -49,12 +72,12 @@ class Metadata(BaseModel, Generic[TEvent], extra="forbid"):
49
72
  context: Context = Field(default_factory=Context)
50
73
 
51
74
  @classmethod
52
- def wrap(cls, event: TEvent, version: int | None) -> "Metadata[TEvent]":
53
- return Metadata[TEvent](event=event, version=version)
75
+ def wrap(cls, event: TEvent, version: int | None) -> "WrappedEvent[TEvent]":
76
+ return WrappedEvent[TEvent](event=event, version=version)
54
77
 
55
78
 
56
79
  class Entry(BaseModel):
57
- metadata: Metadata
80
+ wrapped_event: WrappedEvent
58
81
  stream_id: StreamId
59
82
 
60
83
 
@@ -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
 
@@ -4,10 +4,10 @@ from typing import cast
4
4
 
5
5
  from event_sourcery.event_store.event.dto import (
6
6
  Event,
7
- Metadata,
8
7
  RawEvent,
9
8
  Recorded,
10
9
  RecordedRaw,
10
+ WrappedEvent,
11
11
  )
12
12
  from event_sourcery.event_store.event.registry import EventRegistry
13
13
  from event_sourcery.event_store.stream_id import StreamId
@@ -17,27 +17,27 @@ from event_sourcery.event_store.stream_id import StreamId
17
17
  class Serde:
18
18
  registry: EventRegistry
19
19
 
20
- def deserialize(self, event: RawEvent) -> Metadata:
20
+ def deserialize(self, event: RawEvent) -> WrappedEvent:
21
21
  event_as_dict = dict(event)
22
22
  del event_as_dict["stream_id"]
23
23
  del event_as_dict["name"]
24
24
  data = cast(Mapping, event_as_dict.pop("data"))
25
25
  event_type = self.registry.type_for_name(event.name)
26
- return Metadata[event_type]( # type: ignore
26
+ return WrappedEvent[event_type]( # type: ignore
27
27
  **event_as_dict,
28
28
  event=event_type(**data),
29
29
  )
30
30
 
31
31
  def deserialize_record(self, record: RecordedRaw) -> Recorded:
32
32
  return Recorded(
33
- metadata=self.deserialize(record.entry),
33
+ wrapped_event=self.deserialize(record.entry),
34
34
  stream_id=record.entry.stream_id,
35
35
  position=record.position,
36
36
  )
37
37
 
38
38
  def serialize(
39
39
  self,
40
- event: Metadata,
40
+ event: WrappedEvent,
41
41
  stream_id: StreamId,
42
42
  ) -> RawEvent:
43
43
  model = cast(Event, event.event)
@@ -0,0 +1,202 @@
1
+ from collections.abc import Sequence
2
+ from functools import singledispatchmethod
3
+ from typing import cast
4
+
5
+ from event_sourcery.event_store.event import (
6
+ Event,
7
+ Position,
8
+ RawEvent,
9
+ Serde,
10
+ WrappedEvent,
11
+ )
12
+ from event_sourcery.event_store.interfaces import StorageStrategy
13
+ from event_sourcery.event_store.stream_id import StreamId
14
+ from event_sourcery.event_store.versioning import (
15
+ NO_VERSIONING,
16
+ ExplicitVersioning,
17
+ Versioning,
18
+ )
19
+
20
+
21
+ class EventStore:
22
+ """API for working with events."""
23
+
24
+ def __init__(self, storage_strategy: StorageStrategy, serde: Serde) -> None:
25
+ self._storage_strategy = storage_strategy
26
+ self._serde = serde
27
+
28
+ def load_stream(
29
+ self,
30
+ stream_id: StreamId,
31
+ start: int | None = None,
32
+ stop: int | None = None,
33
+ ) -> Sequence[WrappedEvent]:
34
+ """Loads events from a given stream.
35
+
36
+ Examples:
37
+ >>> load_stream(stream_id=StreamId(name="not_existing_stream"))
38
+ []
39
+ >>> load_stream(stream_id=StreamId(name="existing_stream"))
40
+ [WrappedEvent(..., version=1), ..., WrappedEvent(..., version=3)]
41
+ >>> load_stream(stream_id=StreamId(name="existing_stream"), start=2, stop=3)
42
+ [WrappedEvent(..., version=2)]
43
+
44
+ Args:
45
+ stream_id: The stream identifier to load events from.
46
+ start: The stream version to start loading from (including).
47
+ stop: The stream version to stop loading at (excluding).
48
+
49
+ Returns:
50
+ A sequence of events or empty list if the stream doesn't exist.
51
+ """
52
+ events = self._storage_strategy.fetch_events(stream_id, start=start, stop=stop)
53
+ return self._deserialize_events(events)
54
+
55
+ @singledispatchmethod
56
+ def append(
57
+ self,
58
+ first: WrappedEvent,
59
+ *events: WrappedEvent,
60
+ stream_id: StreamId,
61
+ expected_version: int | Versioning = 0,
62
+ ) -> None:
63
+ """Appends events to a stream with a given ID.
64
+
65
+ Implements optimistic locking to ensure stream wasn't modified since last read.
66
+ To use it, pass the expected version of the stream.
67
+
68
+ Examples:
69
+ >>> append(WrappedEvent(...), stream_id=StreamId())
70
+ None
71
+ >>> append(WrappedEvent(...), stream_id=StreamId(), expected_version=1)
72
+ None
73
+
74
+ Args:
75
+ first: The first event to append (WrappedEvent or Event).
76
+ *events: The rest of the events to append (same type as first argument).
77
+ stream_id: The stream identifier to append events to.
78
+ expected_version: The expected version of the stream
79
+
80
+ Returns:
81
+ None
82
+ """
83
+ self._append(
84
+ stream_id=stream_id,
85
+ events=(first, *events),
86
+ expected_version=expected_version,
87
+ )
88
+
89
+ @append.register
90
+ def _append_events(
91
+ self,
92
+ *events: Event,
93
+ stream_id: StreamId,
94
+ expected_version: int | Versioning = 0,
95
+ ) -> None:
96
+ wrapped_events = self._wrap_events(expected_version, events)
97
+ self.append(
98
+ *wrapped_events,
99
+ stream_id=stream_id,
100
+ expected_version=expected_version,
101
+ )
102
+
103
+ @singledispatchmethod
104
+ def _wrap_events(
105
+ self,
106
+ expected_version: int,
107
+ events: Sequence[Event],
108
+ ) -> Sequence[WrappedEvent]:
109
+ return [
110
+ WrappedEvent.wrap(event=event, version=version)
111
+ for version, event in enumerate(events, start=expected_version + 1)
112
+ ]
113
+
114
+ @_wrap_events.register
115
+ def _wrap_events_versioning(
116
+ self, expected_version: Versioning, events: Sequence[Event]
117
+ ) -> Sequence[WrappedEvent]:
118
+ return [WrappedEvent.wrap(event=event, version=None) for event in events]
119
+
120
+ def _append(
121
+ self,
122
+ stream_id: StreamId,
123
+ events: Sequence[WrappedEvent],
124
+ expected_version: int | Versioning,
125
+ ) -> None:
126
+ new_version = events[-1].version
127
+ versioning: Versioning
128
+ if expected_version is not NO_VERSIONING:
129
+ versioning = ExplicitVersioning(
130
+ expected_version=cast(int, expected_version),
131
+ initial_version=cast(int, new_version),
132
+ )
133
+ else:
134
+ versioning = NO_VERSIONING
135
+
136
+ self._storage_strategy.insert_events(
137
+ stream_id=stream_id,
138
+ versioning=versioning,
139
+ events=self._serialize_events(events, stream_id),
140
+ )
141
+
142
+ def delete_stream(self, stream_id: StreamId) -> None:
143
+ """Deletes a stream with a given ID.
144
+
145
+ If a stream does not exist, this method does nothing.
146
+
147
+ Examples:
148
+ >>> delete_stream(StreamId())
149
+ None
150
+ >>> delete_stream(StreamId(name="not_existing_stream"))
151
+ None
152
+
153
+ Args:
154
+ stream_id: The stream identifier to delete.
155
+
156
+ Returns:
157
+ None
158
+ """
159
+ self._storage_strategy.delete_stream(stream_id)
160
+
161
+ def save_snapshot(self, stream_id: StreamId, snapshot: WrappedEvent) -> None:
162
+ """Saves a snapshot of the stream.
163
+
164
+ Examples:
165
+ >>> save_snapshot(StreamId(), WrappedEvent(...))
166
+ None
167
+ >>> save_snapshot(StreamId(name="not_existing_stream"), WrappedEvent(...))
168
+ None
169
+
170
+ Args:
171
+ stream_id: The stream identifier to save the snapshot.
172
+ snapshot: The snapshot to save.
173
+
174
+ Returns:
175
+ None
176
+ """
177
+
178
+ serialized = self._serde.serialize(event=snapshot, stream_id=stream_id)
179
+ self._storage_strategy.save_snapshot(serialized)
180
+
181
+ def _deserialize_events(self, events: list[RawEvent]) -> list[WrappedEvent]:
182
+ return [self._serde.deserialize(e) for e in events]
183
+
184
+ def _serialize_events(
185
+ self,
186
+ events: Sequence[WrappedEvent],
187
+ stream_id: StreamId,
188
+ ) -> list[RawEvent]:
189
+ return [self._serde.serialize(event=e, stream_id=stream_id) for e in events]
190
+
191
+ @property
192
+ def position(self) -> Position | None:
193
+ """Returns the current position of the event store.
194
+
195
+ Examples:
196
+ >>> position
197
+ None # nothing was saved yet
198
+ >>> position
199
+ Position(15) # Some events were saved
200
+
201
+ """
202
+ return self._storage_strategy.current_position
@@ -38,6 +38,8 @@ class TransactionalBackend(Backend):
38
38
 
39
39
 
40
40
  class BackendFactory(abc.ABC):
41
+ """Abstract base class to configure EventStore."""
42
+
41
43
  @abc.abstractmethod
42
44
  def build(self) -> Backend:
43
45
  pass
@@ -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)
@@ -19,7 +19,7 @@ class Outbox:
19
19
  with entry as raw_record:
20
20
  event = self._serde.deserialize(raw_record.entry)
21
21
  record = Recorded(
22
- metadata=event,
22
+ wrapped_event=event,
23
23
  stream_id=raw_record.entry.stream_id,
24
24
  position=raw_record.position,
25
25
  )
@@ -1,10 +1,10 @@
1
1
  from collections.abc import Callable
2
2
  from typing import TypeAlias
3
3
 
4
- from event_sourcery.event_store import Event, EventStore, Metadata, StreamId
4
+ from event_sourcery.event_store import Event, EventStore, StreamId, WrappedEvent
5
5
  from event_sourcery.read_model.cursors_dao import CursorsDao
6
6
 
7
- ReadModel: TypeAlias = Callable[[Metadata, StreamId], None]
7
+ ReadModel: TypeAlias = Callable[[WrappedEvent, StreamId], None]
8
8
 
9
9
 
10
10
  class Projector:
@@ -23,38 +23,38 @@ class Projector:
23
23
  self._cursors_dao = cursors_dao
24
24
  self._read_model = read_model
25
25
 
26
- def project(self, metadata: Metadata[Event], stream_id: StreamId) -> None:
27
- if metadata.version is None:
26
+ def project(self, wrapped_event: WrappedEvent[Event], stream_id: StreamId) -> None:
27
+ if wrapped_event.version is None:
28
28
  raise self.CantProjectUnversionedEvent
29
29
 
30
30
  try:
31
31
  self._cursors_dao.increment(
32
- name=self._name, stream_id=stream_id, version=metadata.version
32
+ name=self._name, stream_id=stream_id, version=wrapped_event.version
33
33
  )
34
- self._read_model(metadata, stream_id)
34
+ self._read_model(wrapped_event, stream_id)
35
35
  except self._cursors_dao.StreamNotTracked:
36
36
  self._cursors_dao.put_at(
37
- name=self._name, stream_id=stream_id, version=metadata.version
37
+ name=self._name, stream_id=stream_id, version=wrapped_event.version
38
38
  )
39
39
  missed_events = self._event_store.load_stream(
40
- stream_id=stream_id, stop=metadata.version
40
+ stream_id=stream_id, stop=wrapped_event.version
41
41
  )
42
42
  for event in missed_events:
43
43
  self._read_model(event, stream_id)
44
44
 
45
- self._read_model(metadata, stream_id)
45
+ self._read_model(wrapped_event, stream_id)
46
46
  except self._cursors_dao.BehindStream as exc:
47
47
  self._cursors_dao.move_to(
48
- name=self._name, stream_id=stream_id, version=metadata.version
48
+ name=self._name, stream_id=stream_id, version=wrapped_event.version
49
49
  )
50
50
  missed_events = self._event_store.load_stream(
51
51
  stream_id=stream_id,
52
52
  start=exc.current_version + 1,
53
- stop=metadata.version,
53
+ stop=wrapped_event.version,
54
54
  )
55
55
  for event in missed_events:
56
56
  self._read_model(event, stream_id)
57
57
 
58
- self._read_model(metadata, stream_id)
58
+ self._read_model(wrapped_event, stream_id)
59
59
  except self._cursors_dao.AheadOfStream:
60
60
  return
@@ -38,7 +38,7 @@ class SqlAlchemyCursorsDao(CursorsDao):
38
38
  .values({ProjectorCursor.version: version})
39
39
  )
40
40
  result = self._session.execute(update_stmt)
41
- if result.rowcount == 1: # type: ignore
41
+ if result.rowcount == 1:
42
42
  return
43
43
  else:
44
44
  current_version = self._current_version(name, stream_id)
@@ -155,7 +155,7 @@ class SqlAlchemyStorageStrategy(StorageStrategy):
155
155
  )
156
156
  result = self._session.execute(bump_version_stmt)
157
157
 
158
- if result.rowcount != 1: # type: ignore
158
+ if result.rowcount != 1:
159
159
  # optimistic lock failed
160
160
  raise ConcurrentStreamWriteError
161
161
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-event-sourcery"
3
- version = "0.4.0a3"
3
+ version = "0.4.0a5"
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"]
@@ -1,129 +0,0 @@
1
- from collections.abc import Sequence
2
- from functools import singledispatchmethod
3
- from typing import cast
4
-
5
- from event_sourcery.event_store.event import Event, Metadata, Position, RawEvent, Serde
6
- from event_sourcery.event_store.interfaces import StorageStrategy
7
- from event_sourcery.event_store.stream_id import StreamId
8
- from event_sourcery.event_store.versioning import (
9
- NO_VERSIONING,
10
- ExplicitVersioning,
11
- Versioning,
12
- )
13
-
14
-
15
- class EventStore:
16
- def __init__(self, storage_strategy: StorageStrategy, serde: Serde) -> None:
17
- self._storage_strategy = storage_strategy
18
- self._serde = serde
19
-
20
- def load_stream(
21
- self,
22
- stream_id: StreamId,
23
- start: int | None = None,
24
- stop: int | None = None,
25
- ) -> Sequence[Metadata]:
26
- events = self._storage_strategy.fetch_events(stream_id, start=start, stop=stop)
27
- return self._deserialize_events(events)
28
-
29
- @singledispatchmethod
30
- def append(
31
- self,
32
- first: Metadata,
33
- *events: Metadata,
34
- stream_id: StreamId,
35
- expected_version: int | Versioning = 0,
36
- ) -> None:
37
- self._append(
38
- stream_id=stream_id,
39
- events=(first, *events),
40
- expected_version=expected_version,
41
- )
42
-
43
- @append.register
44
- def _append_events(
45
- self,
46
- *events: Event,
47
- stream_id: StreamId,
48
- expected_version: int | Versioning = 0,
49
- ) -> None:
50
- wrapped_events = self._wrap_events(expected_version, events)
51
- self.append(
52
- *wrapped_events,
53
- stream_id=stream_id,
54
- expected_version=expected_version,
55
- )
56
-
57
- @singledispatchmethod
58
- def _wrap_events(
59
- self,
60
- expected_version: int,
61
- events: Sequence[Event],
62
- ) -> Sequence[Metadata]:
63
- return [
64
- Metadata.wrap(event=event, version=version)
65
- for version, event in enumerate(events, start=expected_version + 1)
66
- ]
67
-
68
- @_wrap_events.register
69
- def _wrap_events_versioning(
70
- self, expected_version: Versioning, events: Sequence[Event]
71
- ) -> Sequence[Metadata]:
72
- return [Metadata.wrap(event=event, version=None) for event in events]
73
-
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
- def _append(
89
- self,
90
- stream_id: StreamId,
91
- events: Sequence[Metadata],
92
- expected_version: int | Versioning,
93
- ) -> None:
94
- new_version = events[-1].version
95
- versioning: Versioning
96
- if expected_version is not NO_VERSIONING:
97
- versioning = ExplicitVersioning(
98
- expected_version=cast(int, expected_version),
99
- initial_version=cast(int, new_version),
100
- )
101
- else:
102
- versioning = NO_VERSIONING
103
-
104
- self._storage_strategy.insert_events(
105
- stream_id=stream_id,
106
- versioning=versioning,
107
- events=self._serialize_events(events, stream_id),
108
- )
109
-
110
- def delete_stream(self, stream_id: StreamId) -> None:
111
- self._storage_strategy.delete_stream(stream_id)
112
-
113
- def save_snapshot(self, stream_id: StreamId, snapshot: Metadata) -> None:
114
- serialized = self._serde.serialize(event=snapshot, stream_id=stream_id)
115
- self._storage_strategy.save_snapshot(serialized)
116
-
117
- def _deserialize_events(self, events: list[RawEvent]) -> list[Metadata]:
118
- return [self._serde.deserialize(e) for e in events]
119
-
120
- def _serialize_events(
121
- self,
122
- events: Sequence[Metadata],
123
- stream_id: StreamId,
124
- ) -> list[RawEvent]:
125
- return [self._serde.serialize(event=e, stream_id=stream_id) for e in events]
126
-
127
- @property
128
- def position(self) -> Position | None:
129
- return self._storage_strategy.current_position