python-event-sourcery 0.4.0a2__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.0a2 → python_event_sourcery-0.4.0a4}/PKG-INFO +1 -1
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/aggregate/aggregate.py +2 -1
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/aggregate/repository.py +3 -2
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/dispatcher.py +4 -4
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/event/dto.py +23 -2
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/event/registry.py +14 -7
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/event/serde.py +2 -1
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/event_store.py +84 -16
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/factory.py +7 -2
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/in_memory.py +9 -5
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/interfaces.py +7 -4
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/outbox.py +1 -1
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/subscription.py +10 -12
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/read_model/projector.py +2 -1
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/__init__.py +4 -2
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/event_store.py +5 -6
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/outbox.py +8 -7
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/stream.py +2 -2
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/subscription.py +1 -1
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/__init__.py +3 -3
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/event_store.py +6 -4
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/guid.py +7 -8
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/jsonb.py +3 -3
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/models.py +2 -2
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/outbox.py +6 -3
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/subscription.py +3 -3
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/pyproject.toml +41 -11
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/LICENSE +0 -0
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/__init__.py +0 -0
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/aggregate/__init__.py +0 -0
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/__init__.py +0 -0
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/event/__init__.py +0 -0
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/exceptions.py +0 -0
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/stream_id.py +0 -0
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/event_store/versioning.py +0 -0
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/py.typed +0 -0
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/read_model/__init__.py +0 -0
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery/read_model/cursors_dao.py +0 -0
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/dto.py +0 -0
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/py.typed +0 -0
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/cursors_dao.py +0 -0
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/dto.py +0 -0
- {python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/py.typed +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
from collections.abc import Iterator
|
|
1
2
|
from contextlib import contextmanager
|
|
2
|
-
from typing import Generic,
|
|
3
|
+
from typing import Generic, TypeVar, cast
|
|
3
4
|
|
|
4
5
|
from event_sourcery.aggregate import Aggregate
|
|
5
6
|
from event_sourcery.event_store import Event, EventStore, Metadata, StreamId, StreamUUID
|
|
@@ -48,7 +49,7 @@ class Repository(Generic[TAggregate]):
|
|
|
48
49
|
if not events:
|
|
49
50
|
return
|
|
50
51
|
|
|
51
|
-
self._event_store.
|
|
52
|
+
self._event_store.append(
|
|
52
53
|
*events,
|
|
53
54
|
stream_id=stream_id,
|
|
54
55
|
expected_version=old_version,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
|
-
from
|
|
2
|
+
from collections.abc import Callable
|
|
3
3
|
|
|
4
4
|
from event_sourcery.event_store.event import (
|
|
5
5
|
Event,
|
|
@@ -15,7 +15,7 @@ Listener = Callable[[Metadata, StreamId, Position | None], None]
|
|
|
15
15
|
|
|
16
16
|
class Dispatcher:
|
|
17
17
|
def __init__(self, serde: Serde) -> None:
|
|
18
|
-
self._listeners: dict[
|
|
18
|
+
self._listeners: dict[type[Event], set[Listener]] = defaultdict(set)
|
|
19
19
|
self._serde = serde
|
|
20
20
|
|
|
21
21
|
def dispatch(self, *raws: RecordedRaw) -> None:
|
|
@@ -27,9 +27,9 @@ class Dispatcher:
|
|
|
27
27
|
for listener in listeners:
|
|
28
28
|
listener(record.metadata, record.stream_id, record.position)
|
|
29
29
|
|
|
30
|
-
def register(self, listener: Listener, to:
|
|
30
|
+
def register(self, listener: Listener, to: type[Event]) -> None:
|
|
31
31
|
self._listeners[to].add(listener)
|
|
32
32
|
|
|
33
|
-
def remove(self, listener: Listener, to:
|
|
33
|
+
def remove(self, listener: Listener, to: type[Event]) -> None:
|
|
34
34
|
if listener in self._listeners[to]:
|
|
35
35
|
self._listeners[to].remove(listener)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
from typing import Any, ClassVar, Generic,
|
|
2
|
+
from typing import Any, ClassVar, Generic, TypeAlias, TypeVar
|
|
3
3
|
from uuid import UUID, uuid4
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel, Field
|
|
@@ -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,8 +50,21 @@ 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
|
-
version:
|
|
67
|
+
version: int | None
|
|
47
68
|
uuid: UUID = Field(default_factory=uuid4)
|
|
48
69
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
49
70
|
context: Context = Field(default_factory=Context)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import inspect
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
3
|
|
|
4
4
|
from event_sourcery.event_store.exceptions import (
|
|
5
5
|
ClassModuleUnavailable,
|
|
@@ -10,7 +10,7 @@ if TYPE_CHECKING:
|
|
|
10
10
|
from event_sourcery.event_store.event.dto import Event
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def event_name(cls:
|
|
13
|
+
def event_name(cls: type) -> str:
|
|
14
14
|
event_module = inspect.getmodule(cls)
|
|
15
15
|
if event_module is None: # pragma: no cover
|
|
16
16
|
raise ClassModuleUnavailable
|
|
@@ -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
|
-
self._types_to_names: dict[
|
|
23
|
-
self._names_to_types: dict[str,
|
|
28
|
+
self._types_to_names: dict[type[Event], str] = {}
|
|
29
|
+
self._names_to_types: dict[str, type[Event]] = {}
|
|
24
30
|
|
|
25
|
-
def add(self, event:
|
|
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
|
|
|
@@ -34,8 +41,8 @@ class EventRegistry:
|
|
|
34
41
|
self._names_to_types[name] = event
|
|
35
42
|
return event # for use as a decorator
|
|
36
43
|
|
|
37
|
-
def type_for_name(self, name: str) ->
|
|
44
|
+
def type_for_name(self, name: str) -> type["Event"]:
|
|
38
45
|
return self._names_to_types[name]
|
|
39
46
|
|
|
40
|
-
def name_for_type(self, event:
|
|
47
|
+
def name_for_type(self, event: type["Event"]) -> str:
|
|
41
48
|
return self._types_to_names[event]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
1
2
|
from functools import singledispatchmethod
|
|
2
|
-
from typing import
|
|
3
|
+
from typing import cast
|
|
3
4
|
|
|
4
5
|
from event_sourcery.event_store.event import Event, Metadata, Position, RawEvent, Serde
|
|
5
6
|
from event_sourcery.event_store.interfaces import StorageStrategy
|
|
@@ -12,6 +13,8 @@ from event_sourcery.event_store.versioning import (
|
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class EventStore:
|
|
16
|
+
"""API for working with events."""
|
|
17
|
+
|
|
15
18
|
def __init__(self, storage_strategy: StorageStrategy, serde: Serde) -> None:
|
|
16
19
|
self._storage_strategy = storage_strategy
|
|
17
20
|
self._serde = serde
|
|
@@ -22,6 +25,24 @@ class EventStore:
|
|
|
22
25
|
start: int | None = None,
|
|
23
26
|
stop: int | None = None,
|
|
24
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
|
+
"""
|
|
25
46
|
events = self._storage_strategy.fetch_events(stream_id, start=start, stop=stop)
|
|
26
47
|
return self._deserialize_events(events)
|
|
27
48
|
|
|
@@ -33,9 +54,29 @@ class EventStore:
|
|
|
33
54
|
stream_id: StreamId,
|
|
34
55
|
expected_version: int | Versioning = 0,
|
|
35
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
|
+
"""
|
|
36
77
|
self._append(
|
|
37
78
|
stream_id=stream_id,
|
|
38
|
-
events=(first,
|
|
79
|
+
events=(first, *events),
|
|
39
80
|
expected_version=expected_version,
|
|
40
81
|
)
|
|
41
82
|
|
|
@@ -70,20 +111,6 @@ class EventStore:
|
|
|
70
111
|
) -> Sequence[Metadata]:
|
|
71
112
|
return [Metadata.wrap(event=event, version=None) for event in events]
|
|
72
113
|
|
|
73
|
-
@singledispatchmethod
|
|
74
|
-
def publish(
|
|
75
|
-
self,
|
|
76
|
-
first: Metadata,
|
|
77
|
-
*events: Metadata,
|
|
78
|
-
stream_id: StreamId,
|
|
79
|
-
expected_version: int | Versioning = 0,
|
|
80
|
-
) -> None:
|
|
81
|
-
self._append(
|
|
82
|
-
stream_id=stream_id,
|
|
83
|
-
events=(first,) + events,
|
|
84
|
-
expected_version=expected_version,
|
|
85
|
-
)
|
|
86
|
-
|
|
87
114
|
def _append(
|
|
88
115
|
self,
|
|
89
116
|
stream_id: StreamId,
|
|
@@ -107,9 +134,41 @@ class EventStore:
|
|
|
107
134
|
)
|
|
108
135
|
|
|
109
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
|
+
"""
|
|
110
153
|
self._storage_strategy.delete_stream(stream_id)
|
|
111
154
|
|
|
112
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
|
+
|
|
113
172
|
serialized = self._serde.serialize(event=snapshot, stream_id=stream_id)
|
|
114
173
|
self._storage_strategy.save_snapshot(serialized)
|
|
115
174
|
|
|
@@ -125,4 +184,13 @@ class EventStore:
|
|
|
125
184
|
|
|
126
185
|
@property
|
|
127
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
|
+
"""
|
|
128
196
|
return self._storage_strategy.current_position
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import abc
|
|
2
|
-
from
|
|
2
|
+
from collections.abc import Iterator
|
|
3
|
+
from contextlib import AbstractContextManager
|
|
3
4
|
|
|
4
5
|
from typing_extensions import Self
|
|
5
6
|
|
|
@@ -19,7 +20,9 @@ def no_filter(entry: RawEvent) -> bool:
|
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class NoOutboxStorageStrategy(OutboxStorageStrategy):
|
|
22
|
-
def outbox_entries(
|
|
23
|
+
def outbox_entries(
|
|
24
|
+
self, limit: int
|
|
25
|
+
) -> Iterator[AbstractContextManager[RecordedRaw]]:
|
|
23
26
|
return iter([])
|
|
24
27
|
|
|
25
28
|
|
|
@@ -35,6 +38,8 @@ class TransactionalBackend(Backend):
|
|
|
35
38
|
|
|
36
39
|
|
|
37
40
|
class BackendFactory(abc.ABC):
|
|
41
|
+
"""Abstract base class to configure EventStore."""
|
|
42
|
+
|
|
38
43
|
@abc.abstractmethod
|
|
39
44
|
def build(self) -> Backend:
|
|
40
45
|
pass
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import time
|
|
2
|
-
from
|
|
2
|
+
from collections.abc import Generator, Iterator
|
|
3
|
+
from contextlib import AbstractContextManager, contextmanager
|
|
3
4
|
from copy import copy
|
|
4
5
|
from dataclasses import dataclass, field
|
|
5
6
|
from datetime import timedelta
|
|
6
7
|
from operator import getitem
|
|
7
|
-
from typing import ContextManager, Dict, Generator, Iterator
|
|
8
8
|
|
|
9
9
|
from pydantic import BaseModel, ConfigDict, PositiveInt
|
|
10
10
|
from typing_extensions import Self
|
|
@@ -148,7 +148,9 @@ class InMemoryOutboxStorageStrategy(OutboxStorageStrategy):
|
|
|
148
148
|
def put_into_outbox(self, records: list[RecordedRaw]) -> None:
|
|
149
149
|
self._outbox.extend([(e, 0) for e in records if self._filterer(e.entry)])
|
|
150
150
|
|
|
151
|
-
def outbox_entries(
|
|
151
|
+
def outbox_entries(
|
|
152
|
+
self, limit: int
|
|
153
|
+
) -> Iterator[AbstractContextManager[RecordedRaw]]:
|
|
152
154
|
for record in self._outbox[:limit]:
|
|
153
155
|
yield self._publish_context(*record)
|
|
154
156
|
|
|
@@ -224,7 +226,7 @@ class InMemoryStorageStrategy(StorageStrategy):
|
|
|
224
226
|
dispatcher: Dispatcher,
|
|
225
227
|
outbox_strategy: InMemoryOutboxStorageStrategy | None,
|
|
226
228
|
) -> None:
|
|
227
|
-
self._names:
|
|
229
|
+
self._names: dict[str | None, str] = {}
|
|
228
230
|
self._storage = storage
|
|
229
231
|
self._dispatcher = dispatcher
|
|
230
232
|
self._outbox = outbox_strategy
|
|
@@ -296,9 +298,11 @@ class Config(BaseModel):
|
|
|
296
298
|
|
|
297
299
|
@dataclass(repr=False)
|
|
298
300
|
class InMemoryBackendFactory(BackendFactory):
|
|
301
|
+
"""Lightweight in-memory backend factory for testing and development."""
|
|
302
|
+
|
|
299
303
|
serde = Serde(Event.__registry__)
|
|
300
304
|
|
|
301
|
-
_config: Config = Config
|
|
305
|
+
_config: Config = field(default_factory=Config)
|
|
302
306
|
_storage: Storage = field(default_factory=Storage)
|
|
303
307
|
_outbox_strategy: InMemoryOutboxStorageStrategy | None = None
|
|
304
308
|
_subscription_strategy: InMemorySubscriptionStrategy = field(init=False)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import abc
|
|
2
|
+
from collections.abc import Iterator
|
|
3
|
+
from contextlib import AbstractContextManager
|
|
2
4
|
from datetime import timedelta
|
|
3
|
-
from typing import
|
|
5
|
+
from typing import Protocol
|
|
4
6
|
|
|
5
7
|
from event_sourcery.event_store.event import Position, RawEvent, RecordedRaw
|
|
6
8
|
from event_sourcery.event_store.stream_id import StreamId
|
|
@@ -8,13 +10,14 @@ from event_sourcery.event_store.versioning import Versioning
|
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class OutboxFiltererStrategy(Protocol):
|
|
11
|
-
def __call__(self, entry: RawEvent) -> bool:
|
|
12
|
-
...
|
|
13
|
+
def __call__(self, entry: RawEvent) -> bool: ...
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class OutboxStorageStrategy(abc.ABC):
|
|
16
17
|
@abc.abstractmethod
|
|
17
|
-
def outbox_entries(
|
|
18
|
+
def outbox_entries(
|
|
19
|
+
self, limit: int
|
|
20
|
+
) -> Iterator[AbstractContextManager[RecordedRaw]]:
|
|
18
21
|
pass
|
|
19
22
|
|
|
20
23
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
import sys
|
|
3
|
+
from collections.abc import Callable, Iterator
|
|
3
4
|
from dataclasses import dataclass, field
|
|
4
5
|
from datetime import timedelta
|
|
5
6
|
from functools import partial
|
|
6
|
-
from typing import
|
|
7
|
+
from typing import TypeAlias
|
|
7
8
|
|
|
8
9
|
from event_sourcery.event_store.event import (
|
|
9
10
|
Event,
|
|
@@ -20,32 +21,29 @@ Seconds: TypeAlias = int | float
|
|
|
20
21
|
|
|
21
22
|
class BuildPhase(abc.ABC):
|
|
22
23
|
@abc.abstractmethod
|
|
23
|
-
def build_iter(
|
|
24
|
-
|
|
24
|
+
def build_iter(
|
|
25
|
+
self, timelimit: Seconds | timedelta
|
|
26
|
+
) -> Iterator[Recorded | None]: ...
|
|
25
27
|
|
|
26
28
|
@abc.abstractmethod
|
|
27
29
|
def build_batch(
|
|
28
30
|
self,
|
|
29
31
|
size: int,
|
|
30
32
|
timelimit: Seconds | timedelta,
|
|
31
|
-
) -> Iterator[list[Recorded]]:
|
|
32
|
-
...
|
|
33
|
+
) -> Iterator[list[Recorded]]: ...
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
class FilterPhase(BuildPhase):
|
|
36
37
|
@abc.abstractmethod
|
|
37
|
-
def to_category(self, category: Category) -> BuildPhase:
|
|
38
|
-
...
|
|
38
|
+
def to_category(self, category: Category) -> BuildPhase: ...
|
|
39
39
|
|
|
40
40
|
@abc.abstractmethod
|
|
41
|
-
def to_events(self, events: list[
|
|
42
|
-
...
|
|
41
|
+
def to_events(self, events: list[type[Event]]) -> BuildPhase: ...
|
|
43
42
|
|
|
44
43
|
|
|
45
44
|
class PositionPhase(abc.ABC):
|
|
46
45
|
@abc.abstractmethod
|
|
47
|
-
def start_from(self, position: Position) -> FilterPhase:
|
|
48
|
-
...
|
|
46
|
+
def start_from(self, position: Position) -> FilterPhase: ...
|
|
49
47
|
|
|
50
48
|
|
|
51
49
|
@dataclass(repr=False)
|
|
@@ -71,7 +69,7 @@ class SubscriptionBuilder(PositionPhase, FilterPhase, BuildPhase):
|
|
|
71
69
|
)
|
|
72
70
|
return self
|
|
73
71
|
|
|
74
|
-
def to_events(self, events: list[
|
|
72
|
+
def to_events(self, events: list[type[Event]]) -> BuildPhase:
|
|
75
73
|
self._build = partial(
|
|
76
74
|
self._strategy.subscribe_to_events,
|
|
77
75
|
start_from=self._position,
|
{python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/__init__.py
RENAMED
|
@@ -45,8 +45,10 @@ class Config(BaseModel):
|
|
|
45
45
|
class ESDBBackendFactory(BackendFactory):
|
|
46
46
|
esdb_client: EventStoreDBClient
|
|
47
47
|
config: Config = field(default_factory=Config)
|
|
48
|
-
_serde: Serde = Serde(Event.__registry__)
|
|
49
|
-
_outbox_strategy: OutboxStorageStrategy =
|
|
48
|
+
_serde: Serde = field(default_factory=lambda: Serde(Event.__registry__))
|
|
49
|
+
_outbox_strategy: OutboxStorageStrategy = field(
|
|
50
|
+
default_factory=NoOutboxStorageStrategy
|
|
51
|
+
)
|
|
50
52
|
|
|
51
53
|
def build(self) -> Backend:
|
|
52
54
|
backend = Backend()
|
{python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/event_store.py
RENAMED
|
@@ -42,7 +42,7 @@ class ESDBStorageStrategy(StorageStrategy):
|
|
|
42
42
|
try:
|
|
43
43
|
events = [dto.raw_event(entry) for entry in entries]
|
|
44
44
|
if snapshot:
|
|
45
|
-
return [snapshot
|
|
45
|
+
return [snapshot, *events]
|
|
46
46
|
return events
|
|
47
47
|
except NotFound:
|
|
48
48
|
return []
|
|
@@ -63,14 +63,13 @@ class ESDBStorageStrategy(StorageStrategy):
|
|
|
63
63
|
def insert_events(
|
|
64
64
|
self, stream_id: StreamId, versioning: Versioning, events: list[RawEvent]
|
|
65
65
|
) -> None:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
stream_name = stream.Name(
|
|
69
|
-
stream_events = [e for e in events if e.stream_id ==
|
|
66
|
+
for sid in {e.stream_id for e in events}:
|
|
67
|
+
self._ensure_stream(stream_id=sid, versioning=versioning)
|
|
68
|
+
stream_name = stream.Name(sid)
|
|
69
|
+
stream_events = [e for e in events if e.stream_id == sid]
|
|
70
70
|
self._append_events(stream_name, events=stream_events)
|
|
71
71
|
|
|
72
72
|
def _append_events(self, name: stream.Name, events: list[RawEvent]) -> int:
|
|
73
|
-
assert all(e.stream_id == name.uuid for e in events)
|
|
74
73
|
return self._client.append_events(
|
|
75
74
|
str(name),
|
|
76
75
|
current_version=StreamState.ANY,
|
{python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/outbox.py
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from
|
|
3
|
-
from
|
|
2
|
+
from collections.abc import Generator, Iterator
|
|
3
|
+
from contextlib import AbstractContextManager, contextmanager
|
|
4
|
+
from dataclasses import dataclass, field
|
|
4
5
|
from itertools import islice
|
|
5
|
-
from typing import ContextManager, Generator, Iterator
|
|
6
6
|
|
|
7
7
|
from esdbclient import EventStoreDBClient, RecordedEvent
|
|
8
8
|
from esdbclient.exceptions import DeadlineExceeded, NotFound
|
|
@@ -25,7 +25,7 @@ class ESDBOutboxStorageStrategy(OutboxStorageStrategy):
|
|
|
25
25
|
_outbox_name: str
|
|
26
26
|
_max_publish_attempts: int
|
|
27
27
|
_timeout: float | None
|
|
28
|
-
_active_subscription: PersistentSubscription
|
|
28
|
+
_active_subscription: PersistentSubscription = field(init=False)
|
|
29
29
|
|
|
30
30
|
def create_subscription(self) -> None:
|
|
31
31
|
try:
|
|
@@ -48,14 +48,15 @@ class ESDBOutboxStorageStrategy(OutboxStorageStrategy):
|
|
|
48
48
|
)
|
|
49
49
|
yield islice(self._active_subscription, limit or 100)
|
|
50
50
|
self._active_subscription.stop()
|
|
51
|
-
self
|
|
51
|
+
delattr(self, "_active_subscription")
|
|
52
52
|
|
|
53
53
|
@property
|
|
54
54
|
def active_subscription(self) -> PersistentSubscription:
|
|
55
|
-
assert self._active_subscription is not None
|
|
56
55
|
return self._active_subscription
|
|
57
56
|
|
|
58
|
-
def outbox_entries(
|
|
57
|
+
def outbox_entries(
|
|
58
|
+
self, limit: int
|
|
59
|
+
) -> Iterator[AbstractContextManager[RecordedRaw]]:
|
|
59
60
|
info = self._client.get_subscription_info(
|
|
60
61
|
self._outbox_name,
|
|
61
62
|
timeout=self._timeout,
|
{python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/stream.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
from collections import UserString
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import cast
|
|
4
4
|
|
|
5
5
|
from event_sourcery.event_store import StreamId
|
|
6
6
|
from event_sourcery.event_store.exceptions import IllegalCategoryName
|
|
@@ -57,7 +57,7 @@ MAX_POSITION = Position(sys.maxsize)
|
|
|
57
57
|
def scope(
|
|
58
58
|
start_version: int | None,
|
|
59
59
|
stop_version: int | None,
|
|
60
|
-
) ->
|
|
60
|
+
) -> tuple[Position | None, int]:
|
|
61
61
|
start = cast(
|
|
62
62
|
Position | None, start_version and Position.from_version(start_version)
|
|
63
63
|
)
|
{python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/subscription.py
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
from collections.abc import Callable, Iterator
|
|
1
2
|
from dataclasses import dataclass
|
|
2
3
|
from datetime import timedelta
|
|
3
4
|
from functools import partial
|
|
4
|
-
from typing import Callable, Iterator
|
|
5
5
|
|
|
6
6
|
import esdbclient.exceptions
|
|
7
7
|
from esdbclient import EventStoreDBClient, RecordedEvent
|
|
@@ -6,7 +6,7 @@ __all__ = [
|
|
|
6
6
|
"SQLAlchemyBackendFactory",
|
|
7
7
|
]
|
|
8
8
|
|
|
9
|
-
from dataclasses import dataclass
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
10
|
from datetime import timedelta
|
|
11
11
|
|
|
12
12
|
from pydantic import BaseModel, ConfigDict, PositiveInt
|
|
@@ -43,8 +43,8 @@ class Config(BaseModel):
|
|
|
43
43
|
@dataclass(repr=False)
|
|
44
44
|
class SQLAlchemyBackendFactory(BackendFactory):
|
|
45
45
|
_session: Session
|
|
46
|
-
_config: Config = Config
|
|
47
|
-
_serde: Serde = Serde(Event.__registry__)
|
|
46
|
+
_config: Config = field(default_factory=Config)
|
|
47
|
+
_serde: Serde = field(default_factory=lambda: Serde(Event.__registry__))
|
|
48
48
|
_outbox_strategy: SqlAlchemyOutboxStorageStrategy | None = None
|
|
49
49
|
|
|
50
50
|
def build(self) -> TransactionalBackend:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
1
2
|
from dataclasses import dataclass
|
|
2
|
-
from typing import
|
|
3
|
+
from typing import cast
|
|
3
4
|
|
|
4
5
|
from more_itertools import first_true
|
|
5
6
|
from sqlalchemy import delete, func, select, update
|
|
@@ -51,7 +52,7 @@ class SqlAlchemyStorageStrategy(StorageStrategy):
|
|
|
51
52
|
if stop is not None:
|
|
52
53
|
events_stmt = events_stmt.filter(EventModel.version < stop)
|
|
53
54
|
|
|
54
|
-
events: Sequence[
|
|
55
|
+
events: Sequence[EventModel | SnapshotModel]
|
|
55
56
|
try:
|
|
56
57
|
snapshot_stmt = (
|
|
57
58
|
select(SnapshotModel)
|
|
@@ -74,7 +75,7 @@ class SqlAlchemyStorageStrategy(StorageStrategy):
|
|
|
74
75
|
EventModel.version > latest_snapshot.version
|
|
75
76
|
)
|
|
76
77
|
newer_events = list(self._session.execute(events_stmt).scalars().all())
|
|
77
|
-
events = [latest_snapshot
|
|
78
|
+
events = [latest_snapshot, *newer_events]
|
|
78
79
|
|
|
79
80
|
if not events:
|
|
80
81
|
return []
|
|
@@ -185,7 +186,8 @@ class SqlAlchemyStorageStrategy(StorageStrategy):
|
|
|
185
186
|
stream.events.extend(entries)
|
|
186
187
|
self._session.flush()
|
|
187
188
|
records = [
|
|
188
|
-
RecordedRaw(entry=raw, position=db.id)
|
|
189
|
+
RecordedRaw(entry=raw, position=db.id)
|
|
190
|
+
for raw, db in zip(events, entries, strict=False)
|
|
189
191
|
]
|
|
190
192
|
if self._outbox:
|
|
191
193
|
self._outbox.put_into_outbox(records)
|
{python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/guid.py
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import uuid
|
|
2
|
-
from typing import Any
|
|
2
|
+
from typing import Any
|
|
3
3
|
|
|
4
4
|
from sqlalchemy.dialects.postgresql import UUID
|
|
5
5
|
from sqlalchemy.types import CHAR, TypeDecorator
|
|
@@ -24,17 +24,16 @@ class GUID(TypeDecorator):
|
|
|
24
24
|
else:
|
|
25
25
|
return dialect.type_descriptor(CHAR(32))
|
|
26
26
|
|
|
27
|
-
def process_bind_param(self, value: Any, dialect: Any) ->
|
|
27
|
+
def process_bind_param(self, value: Any, dialect: Any) -> str | None:
|
|
28
28
|
if dialect.name == "postgresql":
|
|
29
29
|
return str(value)
|
|
30
|
+
elif not isinstance(value, uuid.UUID): # pragma: no cover
|
|
31
|
+
return uuid.UUID(value).hex
|
|
30
32
|
else:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
else:
|
|
34
|
-
# hexstring
|
|
35
|
-
return "%.32x" % value.int
|
|
33
|
+
# hexstring
|
|
34
|
+
return value.hex
|
|
36
35
|
|
|
37
|
-
def process_result_value(self, value: Any, dialect: Any) ->
|
|
36
|
+
def process_result_value(self, value: Any, dialect: Any) -> uuid.UUID | None:
|
|
38
37
|
if isinstance(value, uuid.UUID):
|
|
39
38
|
return value
|
|
40
39
|
else:
|
{python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/jsonb.py
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from typing import Any
|
|
2
|
+
from typing import Any
|
|
3
3
|
|
|
4
4
|
from sqlalchemy.dialects.postgresql import JSONB as POSTGRES_JSONB
|
|
5
5
|
from sqlalchemy.types import Text, TypeDecorator
|
|
@@ -14,13 +14,13 @@ class JSONB(TypeDecorator):
|
|
|
14
14
|
else:
|
|
15
15
|
return dialect.type_descriptor(Text())
|
|
16
16
|
|
|
17
|
-
def process_bind_param(self, value: Any, dialect: Any) ->
|
|
17
|
+
def process_bind_param(self, value: Any, dialect: Any) -> Any | None:
|
|
18
18
|
if dialect.name == "postgresql":
|
|
19
19
|
return value
|
|
20
20
|
else:
|
|
21
21
|
return json.dumps(value)
|
|
22
22
|
|
|
23
|
-
def process_result_value(self, value: Any, dialect: Any) ->
|
|
23
|
+
def process_result_value(self, value: Any, dialect: Any) -> Any | None:
|
|
24
24
|
if dialect.name == "postgresql":
|
|
25
25
|
return value
|
|
26
26
|
else:
|
{python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/models.py
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
from typing import Any,
|
|
2
|
+
from typing import Any, cast
|
|
3
3
|
from uuid import UUID
|
|
4
4
|
|
|
5
5
|
from sqlalchemy import (
|
|
@@ -30,7 +30,7 @@ from event_sourcery_sqlalchemy.guid import GUID
|
|
|
30
30
|
from event_sourcery_sqlalchemy.jsonb import JSONB
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
def configure_models(base:
|
|
33
|
+
def configure_models(base: type[Any]) -> None:
|
|
34
34
|
for model_cls in (Stream, Event, Snapshot, OutboxEntry, ProjectorCursor):
|
|
35
35
|
registry(metadata=base.metadata, class_registry={}).map_declaratively(model_cls)
|
|
36
36
|
|
{python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/outbox.py
RENAMED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from
|
|
2
|
+
from collections.abc import Generator, Iterator
|
|
3
|
+
from contextlib import AbstractContextManager, contextmanager
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from datetime import datetime
|
|
5
|
-
from typing import
|
|
6
|
+
from typing import cast
|
|
6
7
|
from uuid import UUID
|
|
7
8
|
|
|
8
9
|
from sqlalchemy import insert, select
|
|
@@ -50,7 +51,9 @@ class SqlAlchemyOutboxStorageStrategy(OutboxStorageStrategy):
|
|
|
50
51
|
|
|
51
52
|
self._session.execute(insert(OutboxEntry), rows)
|
|
52
53
|
|
|
53
|
-
def outbox_entries(
|
|
54
|
+
def outbox_entries(
|
|
55
|
+
self, limit: int
|
|
56
|
+
) -> Iterator[AbstractContextManager[RecordedRaw]]:
|
|
54
57
|
stmt = (
|
|
55
58
|
select(OutboxEntry)
|
|
56
59
|
.filter(OutboxEntry.tries_left > 0)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import time
|
|
2
|
+
from collections.abc import Iterator
|
|
2
3
|
from dataclasses import dataclass
|
|
3
4
|
from datetime import timedelta
|
|
4
|
-
from typing import
|
|
5
|
+
from typing import Protocol, cast
|
|
5
6
|
|
|
6
7
|
from sqlalchemy import select
|
|
7
8
|
from sqlalchemy.orm import Session
|
|
@@ -62,8 +63,7 @@ class SqlAlchemySubscriptionStrategy(SubscriptionStrategy):
|
|
|
62
63
|
|
|
63
64
|
|
|
64
65
|
class GetBatch(Protocol):
|
|
65
|
-
def __call__(self, position: Position) -> list[models.Event]:
|
|
66
|
-
...
|
|
66
|
+
def __call__(self, position: Position) -> list[models.Event]: ...
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
class GetBatchToAll(GetBatch):
|
|
@@ -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"
|
|
@@ -19,17 +19,14 @@ esdbclient = {version = ">=1.0b3", optional = true}
|
|
|
19
19
|
django = {version = ">=4,<6", optional = true}
|
|
20
20
|
|
|
21
21
|
[tool.poetry.group.dev.dependencies]
|
|
22
|
-
|
|
23
|
-
isort = "*"
|
|
24
|
-
flake8 = "*"
|
|
25
|
-
bandit = "*"
|
|
22
|
+
ruff = "*"
|
|
26
23
|
mypy = "*"
|
|
27
24
|
pytest = "*"
|
|
28
25
|
pytest-cov = "*"
|
|
26
|
+
pytest-django = {version = "*"}
|
|
29
27
|
mkdocs-material = "^8.3.9"
|
|
30
28
|
psycopg2-binary = "^2.9.3"
|
|
31
|
-
|
|
32
|
-
flake8-pyproject = "*"
|
|
29
|
+
mkdocstrings = {version = "*", extras = ["python"]}
|
|
33
30
|
|
|
34
31
|
[tool.poetry.extras]
|
|
35
32
|
sqlalchemy = ["SQLAlchemy"]
|
|
@@ -82,11 +79,44 @@ warn_return_any = true
|
|
|
82
79
|
warn_unused_ignores = true
|
|
83
80
|
show_error_codes = true
|
|
84
81
|
|
|
85
|
-
[tool.
|
|
86
|
-
|
|
82
|
+
[tool.ruff]
|
|
83
|
+
line-length = 88
|
|
84
|
+
target-version = "py310"
|
|
85
|
+
show-fixes = true
|
|
86
|
+
exclude = ["**/migrations"]
|
|
87
|
+
|
|
88
|
+
[tool.ruff.lint]
|
|
89
|
+
select = [
|
|
90
|
+
"A", # flake8-builtins
|
|
91
|
+
"B", # flake8-bugbear
|
|
92
|
+
"C", # Convention
|
|
93
|
+
"E", # Error
|
|
94
|
+
"F", # Pyflakes
|
|
95
|
+
"G", # flake8-logging-format
|
|
96
|
+
"I", # isort
|
|
97
|
+
"N", # pep8-naming
|
|
98
|
+
"Q", # flake8-quotes
|
|
99
|
+
"S", # flake8-bandit
|
|
100
|
+
"W", # Warning
|
|
101
|
+
"PL", # Pylint
|
|
102
|
+
"UP", # pyupgrade
|
|
103
|
+
"RUF", # Ruff-specific rules
|
|
104
|
+
]
|
|
105
|
+
ignore = [
|
|
106
|
+
"N818", # error-suffix-on-exception-name
|
|
107
|
+
"PLR2004", # magic-value-comparison
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
[tool.ruff.lint.per-file-ignores]
|
|
111
|
+
"tests/*" = [
|
|
112
|
+
"S101", # Use of assert detected
|
|
113
|
+
]
|
|
114
|
+
"event_sourcery_django/models.py" = [
|
|
115
|
+
"RUF012", # Mutable class attributes should be annotated with typing.ClassVar
|
|
116
|
+
]
|
|
87
117
|
|
|
88
|
-
[tool.
|
|
89
|
-
|
|
118
|
+
[tool.ruff.lint.pylint]
|
|
119
|
+
max-args = 10
|
|
90
120
|
|
|
91
121
|
[build-system]
|
|
92
122
|
requires = ["poetry-core>=1.0.0"]
|
|
File without changes
|
|
File without changes
|
{python_event_sourcery-0.4.0a2 → 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
|
|
File without changes
|
|
File without changes
|
{python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_esdb/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
{python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/dto.py
RENAMED
|
File without changes
|
{python_event_sourcery-0.4.0a2 → python_event_sourcery-0.4.0a4}/event_sourcery_sqlalchemy/py.typed
RENAMED
|
File without changes
|