python-event-sourcery 0.4.0a1__tar.gz → 0.4.0a3__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 (43) hide show
  1. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/PKG-INFO +1 -1
  2. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/aggregate/aggregate.py +2 -1
  3. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/aggregate/repository.py +2 -1
  4. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/event_store/dispatcher.py +4 -4
  5. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/event_store/event/dto.py +2 -2
  6. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/event_store/event/registry.py +7 -7
  7. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/event_store/event/serde.py +2 -1
  8. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/event_store/event_store.py +4 -3
  9. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/event_store/factory.py +5 -2
  10. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/event_store/in_memory.py +7 -5
  11. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/event_store/interfaces.py +7 -4
  12. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/event_store/outbox.py +1 -1
  13. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/event_store/subscription.py +10 -12
  14. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/read_model/projector.py +2 -1
  15. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery_esdb/__init__.py +4 -2
  16. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery_esdb/event_store.py +5 -6
  17. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery_esdb/outbox.py +8 -7
  18. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery_esdb/stream.py +2 -2
  19. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery_esdb/subscription.py +1 -1
  20. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery_sqlalchemy/__init__.py +3 -3
  21. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery_sqlalchemy/event_store.py +6 -4
  22. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery_sqlalchemy/guid.py +7 -8
  23. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery_sqlalchemy/jsonb.py +3 -3
  24. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery_sqlalchemy/models.py +2 -2
  25. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery_sqlalchemy/outbox.py +6 -3
  26. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery_sqlalchemy/subscription.py +3 -3
  27. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/pyproject.toml +40 -11
  28. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/LICENSE +0 -0
  29. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/__init__.py +0 -0
  30. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/aggregate/__init__.py +0 -0
  31. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/event_store/__init__.py +0 -0
  32. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/event_store/event/__init__.py +0 -0
  33. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/event_store/exceptions.py +0 -0
  34. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/event_store/stream_id.py +0 -0
  35. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/event_store/versioning.py +0 -0
  36. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/py.typed +0 -0
  37. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/read_model/__init__.py +0 -0
  38. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery/read_model/cursors_dao.py +0 -0
  39. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery_esdb/dto.py +0 -0
  40. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery_esdb/py.typed +0 -0
  41. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery_sqlalchemy/cursors_dao.py +0 -0
  42. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery_sqlalchemy/dto.py +0 -0
  43. {python_event_sourcery-0.4.0a1 → python_event_sourcery-0.4.0a3}/event_sourcery_sqlalchemy/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-event-sourcery
3
- Version: 0.4.0a1
3
+ Version: 0.4.0a3
4
4
  Summary:
5
5
  License: MIT
6
6
  Author: Your Name
@@ -1,5 +1,6 @@
1
+ from collections.abc import Iterator
1
2
  from contextlib import contextmanager
2
- from typing import ClassVar, Iterator
3
+ from typing import ClassVar
3
4
 
4
5
  from event_sourcery.event_store import Event
5
6
 
@@ -1,5 +1,6 @@
1
+ from collections.abc import Iterator
1
2
  from contextlib import contextmanager
2
- from typing import Generic, Iterator, TypeVar, cast
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
@@ -1,5 +1,5 @@
1
1
  from collections import defaultdict
2
- from typing import Callable, Type
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[Type[Event], set[Listener]] = defaultdict(set)
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: Type[Event]) -> None:
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: Type[Event]) -> None:
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, Optional, TypeAlias, TypeVar
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
@@ -43,7 +43,7 @@ class Context(BaseModel, extra="allow"):
43
43
 
44
44
  class Metadata(BaseModel, Generic[TEvent], extra="forbid"):
45
45
  event: TEvent
46
- version: Optional[int]
46
+ version: int | None
47
47
  uuid: UUID = Field(default_factory=uuid4)
48
48
  created_at: datetime = Field(default_factory=datetime.utcnow)
49
49
  context: Context = Field(default_factory=Context)
@@ -1,5 +1,5 @@
1
1
  import inspect
2
- from typing import TYPE_CHECKING, Type
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: Type) -> str:
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
@@ -19,10 +19,10 @@ def event_name(cls: Type) -> str:
19
19
 
20
20
  class EventRegistry:
21
21
  def __init__(self) -> None:
22
- self._types_to_names: dict[Type["Event"], str] = {}
23
- self._names_to_types: dict[str, Type["Event"]] = {}
22
+ self._types_to_names: dict[type[Event], str] = {}
23
+ self._names_to_types: dict[str, type[Event]] = {}
24
24
 
25
- def add(self, event: Type["Event"]) -> Type["Event"]:
25
+ def add(self, event: type["Event"]) -> type["Event"]:
26
26
  if event in self._types_to_names:
27
27
  raise DuplicatedEvent(f"Duplicated Event detected! {event}")
28
28
 
@@ -34,8 +34,8 @@ class EventRegistry:
34
34
  self._names_to_types[name] = event
35
35
  return event # for use as a decorator
36
36
 
37
- def type_for_name(self, name: str) -> Type["Event"]:
37
+ def type_for_name(self, name: str) -> type["Event"]:
38
38
  return self._names_to_types[name]
39
39
 
40
- def name_for_type(self, event: Type["Event"]) -> str:
40
+ def name_for_type(self, event: type["Event"]) -> str:
41
41
  return self._types_to_names[event]
@@ -1,5 +1,6 @@
1
+ from collections.abc import Mapping
1
2
  from dataclasses import dataclass
2
- from typing import Mapping, cast
3
+ from typing import cast
3
4
 
4
5
  from event_sourcery.event_store.event.dto import (
5
6
  Event,
@@ -1,5 +1,6 @@
1
+ from collections.abc import Sequence
1
2
  from functools import singledispatchmethod
2
- from typing import Sequence, cast
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
@@ -35,7 +36,7 @@ class EventStore:
35
36
  ) -> None:
36
37
  self._append(
37
38
  stream_id=stream_id,
38
- events=(first,) + events,
39
+ events=(first, *events),
39
40
  expected_version=expected_version,
40
41
  )
41
42
 
@@ -80,7 +81,7 @@ class EventStore:
80
81
  ) -> None:
81
82
  self._append(
82
83
  stream_id=stream_id,
83
- events=(first,) + events,
84
+ events=(first, *events),
84
85
  expected_version=expected_version,
85
86
  )
86
87
 
@@ -1,5 +1,6 @@
1
1
  import abc
2
- from typing import ContextManager, Iterator
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(self, limit: int) -> Iterator[ContextManager[RecordedRaw]]:
23
+ def outbox_entries(
24
+ self, limit: int
25
+ ) -> Iterator[AbstractContextManager[RecordedRaw]]:
23
26
  return iter([])
24
27
 
25
28
 
@@ -1,10 +1,10 @@
1
1
  import time
2
- from contextlib import contextmanager
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(self, limit: int) -> Iterator[ContextManager[RecordedRaw]]:
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: Dict[str | None, str] = {}
229
+ self._names: dict[str | None, str] = {}
228
230
  self._storage = storage
229
231
  self._dispatcher = dispatcher
230
232
  self._outbox = outbox_strategy
@@ -298,7 +300,7 @@ class Config(BaseModel):
298
300
  class InMemoryBackendFactory(BackendFactory):
299
301
  serde = Serde(Event.__registry__)
300
302
 
301
- _config: Config = Config()
303
+ _config: Config = field(default_factory=Config)
302
304
  _storage: Storage = field(default_factory=Storage)
303
305
  _outbox_strategy: InMemoryOutboxStorageStrategy | None = None
304
306
  _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 ContextManager, Iterator, Protocol
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(self, limit: int) -> Iterator[ContextManager[RecordedRaw]]:
18
+ def outbox_entries(
19
+ self, limit: int
20
+ ) -> Iterator[AbstractContextManager[RecordedRaw]]:
18
21
  pass
19
22
 
20
23
 
@@ -1,4 +1,4 @@
1
- from typing import Callable
1
+ from collections.abc import Callable
2
2
 
3
3
  from event_sourcery.event_store.event import Recorded, Serde
4
4
  from event_sourcery.event_store.interfaces import OutboxStorageStrategy
@@ -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 Callable, Iterator, Type, TypeAlias
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(self, timelimit: Seconds | timedelta) -> Iterator[Recorded | None]:
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[Type[Event]]) -> BuildPhase:
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[Type[Event]]) -> BuildPhase:
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,
@@ -1,4 +1,5 @@
1
- from typing import Callable, TypeAlias
1
+ from collections.abc import Callable
2
+ from typing import TypeAlias
2
3
 
3
4
  from event_sourcery.event_store import Event, EventStore, Metadata, StreamId
4
5
  from event_sourcery.read_model.cursors_dao import CursorsDao
@@ -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 = NoOutboxStorageStrategy()
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()
@@ -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] + events
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
- self._ensure_stream(stream_id=stream_id, versioning=versioning)
67
- for stream_id in {e.stream_id for e in events}:
68
- stream_name = stream.Name(stream_id)
69
- stream_events = [e for e in events if e.stream_id == 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,
@@ -1,8 +1,8 @@
1
1
  import logging
2
- from contextlib import contextmanager
3
- from dataclasses import dataclass
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 | None = None
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._active_subscription = None
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(self, limit: int) -> Iterator[ContextManager[RecordedRaw]]:
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,
@@ -1,6 +1,6 @@
1
1
  import sys
2
2
  from collections import UserString
3
- from typing import Tuple, cast
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
- ) -> Tuple[Position | None, int]:
60
+ ) -> tuple[Position | None, int]:
61
61
  start = cast(
62
62
  Position | None, start_version and Position.from_version(start_version)
63
63
  )
@@ -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 Sequence, Union, cast
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[Union[EventModel, SnapshotModel]]
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] + newer_events # type: ignore
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) for raw, db in zip(events, entries)
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)
@@ -1,5 +1,5 @@
1
1
  import uuid
2
- from typing import Any, Optional
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) -> Optional[str]:
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
- if not isinstance(value, uuid.UUID): # pragma: no cover
32
- return "%.32x" % uuid.UUID(value).int
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) -> Optional[uuid.UUID]:
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:
@@ -1,5 +1,5 @@
1
1
  import json
2
- from typing import Any, Optional
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) -> Optional[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) -> Optional[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:
@@ -1,5 +1,5 @@
1
1
  from datetime import datetime
2
- from typing import Any, Type, cast
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: Type) -> None:
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
 
@@ -1,8 +1,9 @@
1
1
  import logging
2
- from contextlib import contextmanager
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 ContextManager, Generator, Iterator, cast
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(self, limit: int) -> Iterator[ContextManager[RecordedRaw]]:
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 Iterator, Protocol, cast
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.0a1"
3
+ version = "0.4.0a3"
4
4
  description = ""
5
5
  authors = ["Your Name <you@example.com>"]
6
6
  license = "MIT"
@@ -19,17 +19,13 @@ esdbclient = {version = ">=1.0b3", optional = true}
19
19
  django = {version = ">=4,<6", optional = true}
20
20
 
21
21
  [tool.poetry.group.dev.dependencies]
22
- black = "*"
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
- pytest-django = {version = "*"}
32
- flake8-pyproject = "*"
33
29
 
34
30
  [tool.poetry.extras]
35
31
  sqlalchemy = ["SQLAlchemy"]
@@ -82,11 +78,44 @@ warn_return_any = true
82
78
  warn_unused_ignores = true
83
79
  show_error_codes = true
84
80
 
85
- [tool.flake8]
86
- max-line-length = 88
81
+ [tool.ruff]
82
+ line-length = 88
83
+ target-version = "py310"
84
+ show-fixes = true
85
+ exclude = ["**/migrations"]
86
+
87
+ [tool.ruff.lint]
88
+ select = [
89
+ "A", # flake8-builtins
90
+ "B", # flake8-bugbear
91
+ "C", # Convention
92
+ "E", # Error
93
+ "F", # Pyflakes
94
+ "G", # flake8-logging-format
95
+ "I", # isort
96
+ "N", # pep8-naming
97
+ "Q", # flake8-quotes
98
+ "S", # flake8-bandit
99
+ "W", # Warning
100
+ "PL", # Pylint
101
+ "UP", # pyupgrade
102
+ "RUF", # Ruff-specific rules
103
+ ]
104
+ ignore = [
105
+ "N818", # error-suffix-on-exception-name
106
+ "PLR2004", # magic-value-comparison
107
+ ]
108
+
109
+ [tool.ruff.lint.per-file-ignores]
110
+ "tests/*" = [
111
+ "S101", # Use of assert detected
112
+ ]
113
+ "event_sourcery_django/models.py" = [
114
+ "RUF012", # Mutable class attributes should be annotated with typing.ClassVar
115
+ ]
87
116
 
88
- [tool.isort]
89
- profile = "black"
117
+ [tool.ruff.lint.pylint]
118
+ max-args = 10
90
119
 
91
120
  [build-system]
92
121
  requires = ["poetry-core>=1.0.0"]