python-event-sourcery 0.4.0a18__tar.gz → 0.4.0a19__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 (56) hide show
  1. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/PKG-INFO +1 -1
  2. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_sqlalchemy/__init__.py +60 -7
  3. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_sqlalchemy/cursors_dao.py +22 -19
  4. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_sqlalchemy/dto.py +2 -2
  5. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_sqlalchemy/event_store.py +45 -40
  6. python_event_sourcery-0.4.0a19/event_sourcery_sqlalchemy/models/__init__.py +53 -0
  7. python_event_sourcery-0.4.0a18/event_sourcery_sqlalchemy/models.py → python_event_sourcery-0.4.0a19/event_sourcery_sqlalchemy/models/base.py +102 -60
  8. python_event_sourcery-0.4.0a19/event_sourcery_sqlalchemy/models/default.py +27 -0
  9. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_sqlalchemy/outbox.py +7 -6
  10. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_sqlalchemy/subscription.py +74 -29
  11. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/pyproject.toml +1 -1
  12. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/LICENSE +0 -0
  13. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/__init__.py +0 -0
  14. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_sourcing/__init__.py +0 -0
  15. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_sourcing/aggregate.py +0 -0
  16. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_sourcing/repository.py +0 -0
  17. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_store/__init__.py +0 -0
  18. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_store/backend.py +0 -0
  19. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_store/dispatcher.py +0 -0
  20. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_store/event/__init__.py +0 -0
  21. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_store/event/_dto.py +0 -0
  22. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_store/event/_encryption.py +0 -0
  23. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_store/event/_registry.py +0 -0
  24. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_store/event/_serde.py +0 -0
  25. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_store/event_store.py +0 -0
  26. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_store/exceptions.py +0 -0
  27. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_store/in_memory.py +0 -0
  28. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_store/interfaces.py +0 -0
  29. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_store/outbox.py +0 -0
  30. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_store/stream_id.py +0 -0
  31. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_store/subscription.py +0 -0
  32. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_store/tenant_id.py +0 -0
  33. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/event_store/versioning.py +0 -0
  34. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/py.typed +0 -0
  35. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/read_model/__init__.py +0 -0
  36. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/read_model/cursors_dao.py +0 -0
  37. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery/read_model/projector.py +0 -0
  38. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_django/__init__.py +0 -0
  39. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_django/dto.py +0 -0
  40. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_django/event_store.py +0 -0
  41. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_django/migrations/0001_initial.py +0 -0
  42. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_django/migrations/__init__.py +0 -0
  43. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_django/models.py +0 -0
  44. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_django/outbox.py +0 -0
  45. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_django/py.typed +0 -0
  46. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_django/subscription.py +0 -0
  47. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_kurrentdb/__init__.py +0 -0
  48. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_kurrentdb/dto.py +0 -0
  49. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_kurrentdb/event_store.py +0 -0
  50. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_kurrentdb/outbox.py +0 -0
  51. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_kurrentdb/py.typed +0 -0
  52. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_kurrentdb/stream.py +0 -0
  53. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_kurrentdb/subscription.py +0 -0
  54. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_sqlalchemy/guid.py +0 -0
  55. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_sqlalchemy/jsonb.py +0 -0
  56. {python_event_sourcery-0.4.0a18 → python_event_sourcery-0.4.0a19}/event_sourcery_sqlalchemy/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: python-event-sourcery
3
- Version: 0.4.0a18
3
+ Version: 0.4.0a19
4
4
  Summary:
5
5
  License: MIT
6
6
  Author: Your Name
@@ -1,11 +1,18 @@
1
1
  __all__ = [
2
+ "BaseEvent",
3
+ "BaseOutboxEntry",
4
+ "BaseProjectorCursor",
5
+ "BaseSnapshot",
6
+ "BaseStream",
2
7
  "Config",
8
+ "Models",
3
9
  "SQLAlchemyBackend",
4
10
  "SqlAlchemyStorageStrategy",
5
11
  "configure_models",
6
12
  "models",
7
13
  ]
8
14
 
15
+ from dataclasses import dataclass
9
16
  from datetime import timedelta
10
17
 
11
18
  from pydantic import BaseModel, ConfigDict, PositiveInt
@@ -23,10 +30,31 @@ from event_sourcery.event_store.interfaces import (
23
30
  from event_sourcery_sqlalchemy import models
24
31
  from event_sourcery_sqlalchemy.event_store import SqlAlchemyStorageStrategy
25
32
  from event_sourcery_sqlalchemy.models import configure_models
33
+ from event_sourcery_sqlalchemy.models.base import (
34
+ BaseEvent,
35
+ BaseOutboxEntry,
36
+ BaseProjectorCursor,
37
+ BaseSnapshot,
38
+ BaseStream,
39
+ )
40
+ from event_sourcery_sqlalchemy.models.default import (
41
+ DefaultEvent,
42
+ DefaultOutboxEntry,
43
+ DefaultSnapshot,
44
+ DefaultStream,
45
+ )
26
46
  from event_sourcery_sqlalchemy.outbox import SqlAlchemyOutboxStorageStrategy
27
47
  from event_sourcery_sqlalchemy.subscription import SqlAlchemySubscriptionStrategy
28
48
 
29
49
 
50
+ @dataclass(frozen=True)
51
+ class Models:
52
+ event_model: type[BaseEvent]
53
+ stream_model: type[BaseStream]
54
+ snapshot_model: type[BaseSnapshot]
55
+ outbox_entry_model: type[BaseOutboxEntry] = DefaultOutboxEntry
56
+
57
+
30
58
  class Config(BaseModel):
31
59
  model_config = ConfigDict(extra="forbid", frozen=True)
32
60
 
@@ -35,27 +63,51 @@ class Config(BaseModel):
35
63
 
36
64
 
37
65
  class SQLAlchemyBackend(TransactionalBackend):
66
+ UNCONFIGURED_MESSAGE = "Configure backend with `.configure(session, config)`"
67
+
38
68
  def __init__(self) -> None:
39
69
  super().__init__()
40
- self[Session] = not_configured(
41
- "Configure backend with `.configure(session, config)`",
42
- )
43
- self[Config] = not_configured(
44
- "Configure backend with `.configure(session, config)`",
45
- )
70
+ self[BaseEvent] = not_configured(self.UNCONFIGURED_MESSAGE)
71
+ self[BaseStream] = not_configured(self.UNCONFIGURED_MESSAGE)
72
+ self[BaseSnapshot] = not_configured(self.UNCONFIGURED_MESSAGE)
73
+ self[BaseOutboxEntry] = not_configured(self.UNCONFIGURED_MESSAGE)
74
+ self[Session] = not_configured(self.UNCONFIGURED_MESSAGE)
75
+ self[Config] = not_configured(self.UNCONFIGURED_MESSAGE)
46
76
  self[StorageStrategy] = lambda c: SqlAlchemyStorageStrategy(
47
77
  c[Session],
48
78
  c[Dispatcher],
49
79
  c.get(SqlAlchemyOutboxStorageStrategy, None),
80
+ c[BaseEvent],
81
+ c[BaseSnapshot],
82
+ c[BaseStream],
50
83
  ).scoped_for_tenant(c.tenant_id)
51
84
  self[SubscriptionStrategy] = lambda c: SqlAlchemySubscriptionStrategy(
52
85
  c[Session],
53
86
  c[Config].gap_retry_interval,
87
+ c[BaseEvent],
88
+ c[BaseStream],
54
89
  )
55
90
 
56
- def configure(self, session: Session, config: Config | None = None) -> Self:
91
+ def configure(
92
+ self,
93
+ session: Session,
94
+ config: Config | None = None,
95
+ custom_models: Models | None = None,
96
+ ) -> Self:
97
+ if custom_models is None:
98
+ custom_models = Models(
99
+ event_model=DefaultEvent,
100
+ stream_model=DefaultStream,
101
+ snapshot_model=DefaultSnapshot,
102
+ outbox_entry_model=DefaultOutboxEntry,
103
+ )
104
+
57
105
  self[Session] = session
58
106
  self[Config] = config or Config()
107
+ self[BaseEvent] = lambda _: custom_models.event_model
108
+ self[BaseStream] = lambda _: custom_models.stream_model
109
+ self[BaseSnapshot] = lambda _: custom_models.snapshot_model
110
+ self[BaseOutboxEntry] = lambda _: custom_models.outbox_entry_model
59
111
  return self
60
112
 
61
113
  def with_outbox(self, filterer: OutboxFiltererStrategy = no_filter) -> Self:
@@ -65,6 +117,7 @@ class SQLAlchemyBackend(TransactionalBackend):
65
117
  c[Session],
66
118
  c[OutboxFiltererStrategy],
67
119
  c[Config].outbox_attempts,
120
+ c[BaseOutboxEntry],
68
121
  )
69
122
  )
70
123
  self[OutboxStorageStrategy] = lambda c: c[SqlAlchemyOutboxStorageStrategy]
@@ -5,12 +5,15 @@ from sqlalchemy.orm import Session
5
5
 
6
6
  from event_sourcery.event_store import StreamId
7
7
  from event_sourcery.read_model import CursorsDao
8
- from event_sourcery_sqlalchemy.models import ProjectorCursor
8
+ from event_sourcery_sqlalchemy.models.base import BaseProjectorCursor
9
9
 
10
10
 
11
11
  class SqlAlchemyCursorsDao(CursorsDao):
12
- def __init__(self, session: Session) -> None:
12
+ def __init__(
13
+ self, session: Session, projector_cursor_model: type[BaseProjectorCursor]
14
+ ) -> None:
13
15
  self._session = session
16
+ self._projector_cursor_model = projector_cursor_model
14
17
 
15
18
  def increment(self, name: str, stream_id: StreamId, version: int) -> None:
16
19
  if version == 1:
@@ -18,7 +21,7 @@ class SqlAlchemyCursorsDao(CursorsDao):
18
21
  if current_version is not None:
19
22
  raise self.AheadOfStream(current_version=current_version)
20
23
 
21
- stmt = insert(ProjectorCursor).values(
24
+ stmt = insert(self._projector_cursor_model).values(
22
25
  name=name,
23
26
  stream_id=stream_id,
24
27
  category=stream_id.category,
@@ -28,14 +31,14 @@ class SqlAlchemyCursorsDao(CursorsDao):
28
31
  return
29
32
 
30
33
  update_stmt = (
31
- update(ProjectorCursor)
34
+ update(self._projector_cursor_model)
32
35
  .where(
33
- ProjectorCursor.name == name,
34
- ProjectorCursor.stream_id == stream_id,
35
- ProjectorCursor.category == stream_id.category,
36
- ProjectorCursor.version == version - 1,
36
+ self._projector_cursor_model.name == name,
37
+ self._projector_cursor_model.stream_id == stream_id,
38
+ self._projector_cursor_model.category == stream_id.category,
39
+ self._projector_cursor_model.version == version - 1,
37
40
  )
38
- .values({ProjectorCursor.version: version})
41
+ .values({self._projector_cursor_model.version: version})
39
42
  )
40
43
  result = self._session.execute(update_stmt)
41
44
  if result.rowcount == 1:
@@ -51,15 +54,15 @@ class SqlAlchemyCursorsDao(CursorsDao):
51
54
  raise self.AheadOfStream(current_version=current_version)
52
55
 
53
56
  def _current_version(self, name: str, stream_id: StreamId) -> int | None:
54
- stmt = select(ProjectorCursor.version).filter(
55
- ProjectorCursor.name == name,
56
- ProjectorCursor.stream_id == stream_id,
57
- ProjectorCursor.category == stream_id.category,
57
+ stmt = select(self._projector_cursor_model.version).filter(
58
+ self._projector_cursor_model.name == name,
59
+ self._projector_cursor_model.stream_id == stream_id,
60
+ self._projector_cursor_model.category == stream_id.category,
58
61
  )
59
62
  return cast(int | None, self._session.execute(stmt).scalar())
60
63
 
61
64
  def put_at(self, name: str, stream_id: StreamId, version: int) -> None:
62
- stmt = insert(ProjectorCursor).values(
65
+ stmt = insert(self._projector_cursor_model).values(
63
66
  name=name,
64
67
  stream_id=stream_id,
65
68
  category=stream_id.category,
@@ -69,12 +72,12 @@ class SqlAlchemyCursorsDao(CursorsDao):
69
72
 
70
73
  def move_to(self, name: str, stream_id: StreamId, version: int) -> None:
71
74
  stmt = (
72
- update(ProjectorCursor)
75
+ update(self._projector_cursor_model)
73
76
  .where(
74
- ProjectorCursor.name == name,
75
- ProjectorCursor.stream_id == stream_id,
76
- ProjectorCursor.category == stream_id.category,
77
+ self._projector_cursor_model.name == name,
78
+ self._projector_cursor_model.stream_id == stream_id,
79
+ self._projector_cursor_model.category == stream_id.category,
77
80
  )
78
- .values({ProjectorCursor.version: version})
81
+ .values({self._projector_cursor_model.version: version})
79
82
  )
80
83
  self._session.execute(stmt)
@@ -1,8 +1,8 @@
1
1
  from event_sourcery.event_store import RawEvent, StreamId
2
- from event_sourcery_sqlalchemy.models import Event, Stream
2
+ from event_sourcery_sqlalchemy.models.base import BaseEvent, BaseStream
3
3
 
4
4
 
5
- def raw_event(from_entry: Event, in_stream: Stream) -> RawEvent:
5
+ def raw_event(from_entry: BaseEvent, in_stream: BaseStream) -> RawEvent:
6
6
  return RawEvent(
7
7
  uuid=from_entry.uuid,
8
8
  stream_id=StreamId(
@@ -24,9 +24,7 @@ from event_sourcery.event_store.exceptions import (
24
24
  )
25
25
  from event_sourcery.event_store.interfaces import StorageStrategy
26
26
  from event_sourcery.event_store.tenant_id import DEFAULT_TENANT, TenantId
27
- from event_sourcery_sqlalchemy.models import Event as EventModel
28
- from event_sourcery_sqlalchemy.models import Snapshot as SnapshotModel
29
- from event_sourcery_sqlalchemy.models import Stream as StreamModel
27
+ from event_sourcery_sqlalchemy.models.base import BaseEvent, BaseSnapshot, BaseStream
30
28
  from event_sourcery_sqlalchemy.outbox import SqlAlchemyOutboxStorageStrategy
31
29
 
32
30
 
@@ -34,7 +32,10 @@ from event_sourcery_sqlalchemy.outbox import SqlAlchemyOutboxStorageStrategy
34
32
  class SqlAlchemyStorageStrategy(StorageStrategy):
35
33
  _session: Session
36
34
  _dispatcher: Dispatcher
37
- _outbox: SqlAlchemyOutboxStorageStrategy | None = None
35
+ _outbox: SqlAlchemyOutboxStorageStrategy
36
+ _event_model: type[BaseEvent]
37
+ _snapshot_model: type[BaseSnapshot]
38
+ _stream_model: type[BaseStream]
38
39
  _tenant_id: TenantId = DEFAULT_TENANT
39
40
 
40
41
  def fetch_events(
@@ -44,41 +45,45 @@ class SqlAlchemyStorageStrategy(StorageStrategy):
44
45
  stop: int | None = None,
45
46
  ) -> list[RawEvent]:
46
47
  events_stmt = (
47
- select(EventModel)
48
+ select(self._event_model)
48
49
  .filter_by(stream_id=stream_id, tenant_id=self._tenant_id)
49
- .order_by(EventModel.version)
50
+ .order_by(self._event_model.version)
50
51
  )
51
52
 
52
53
  if start is not None:
53
- events_stmt = events_stmt.filter(EventModel.version >= start)
54
+ events_stmt = events_stmt.filter(self._event_model.version >= start)
54
55
 
55
56
  if stop is not None:
56
- events_stmt = events_stmt.filter(EventModel.version < stop)
57
+ events_stmt = events_stmt.filter(self._event_model.version < stop)
57
58
 
58
- events: Sequence[EventModel | SnapshotModel]
59
+ events: Sequence[BaseEvent | BaseSnapshot]
59
60
  try:
60
61
  snapshot_stmt = (
61
- select(SnapshotModel)
62
- .join(StreamModel)
62
+ select(self._snapshot_model)
63
+ .join(self._stream_model)
63
64
  .filter(
64
- StreamModel.stream_id == stream_id,
65
- StreamModel.tenant_id == self._tenant_id,
65
+ self._stream_model.stream_id == stream_id,
66
+ self._stream_model.tenant_id == self._tenant_id,
66
67
  )
67
- .order_by(SnapshotModel.created_at.desc())
68
+ .order_by(self._snapshot_model.created_at.desc())
68
69
  .limit(1)
69
70
  )
70
71
  if start is not None:
71
- snapshot_stmt = snapshot_stmt.filter(SnapshotModel.version >= start)
72
+ snapshot_stmt = snapshot_stmt.filter(
73
+ self._snapshot_model.version >= start
74
+ )
72
75
 
73
76
  if stop is not None:
74
- snapshot_stmt = snapshot_stmt.filter(SnapshotModel.version < stop)
77
+ snapshot_stmt = snapshot_stmt.filter(
78
+ self._snapshot_model.version < stop
79
+ )
75
80
 
76
81
  latest_snapshot = self._session.execute(snapshot_stmt).scalars().one()
77
82
  except NoResultFound:
78
83
  events = self._session.execute(events_stmt).scalars().all()
79
84
  else:
80
85
  events_stmt = events_stmt.filter(
81
- EventModel.version > latest_snapshot.version
86
+ self._event_model.version > latest_snapshot.version
82
87
  )
83
88
  newer_events = list(self._session.execute(events_stmt).scalars().all())
84
89
  events = [latest_snapshot, *newer_events]
@@ -104,21 +109,21 @@ class SqlAlchemyStorageStrategy(StorageStrategy):
104
109
  initial_version = versioning.initial_version
105
110
 
106
111
  condition = (
107
- (StreamModel.uuid == stream_id)
108
- & (StreamModel.category == (stream_id.category or ""))
109
- & (StreamModel.tenant_id == self._tenant_id)
112
+ (self._stream_model.uuid == stream_id)
113
+ & (self._stream_model.category == (stream_id.category or ""))
114
+ & (self._stream_model.tenant_id == self._tenant_id)
110
115
  )
111
116
  if stream_id.name:
112
117
  condition = condition | (
113
- (StreamModel.name == stream_id.name)
114
- & (StreamModel.category == (stream_id.category or ""))
115
- & (StreamModel.tenant_id == self._tenant_id)
118
+ (self._stream_model.name == stream_id.name)
119
+ & (self._stream_model.category == (stream_id.category or ""))
120
+ & (self._stream_model.tenant_id == self._tenant_id)
116
121
  )
117
- matching_streams_stmt = select(StreamModel).where(condition)
122
+ matching_streams_stmt = select(self._stream_model).where(condition)
118
123
  matching_streams = self._session.execute(matching_streams_stmt).scalars().all()
119
124
  if not matching_streams:
120
125
  ensure_stream_stmt = (
121
- postgresql_insert(StreamModel)
126
+ postgresql_insert(self._stream_model)
122
127
  .values(
123
128
  uuid=stream_id,
124
129
  name=stream_id.name,
@@ -134,7 +139,7 @@ class SqlAlchemyStorageStrategy(StorageStrategy):
134
139
  )
135
140
 
136
141
  if stream_id.name is not None:
137
- matching_stream_with_same_name: StreamModel = [
142
+ matching_stream_with_same_name: BaseStream = [
138
143
  stream
139
144
  for stream in matching_streams
140
145
  if stream.name == stream_id.name
@@ -144,7 +149,7 @@ class SqlAlchemyStorageStrategy(StorageStrategy):
144
149
  raise AnotherStreamWithThisNameButOtherIdExists()
145
150
 
146
151
  stream = cast(
147
- StreamModel,
152
+ BaseStream,
148
153
  first_true(
149
154
  matching_streams, pred=lambda stream: stream.stream_id == stream_id
150
155
  ),
@@ -156,10 +161,10 @@ class SqlAlchemyStorageStrategy(StorageStrategy):
156
161
 
157
162
  if versioning.expected_version and versioning is not NO_VERSIONING:
158
163
  bump_version_stmt = (
159
- update(StreamModel)
164
+ update(self._stream_model)
160
165
  .where(
161
- StreamModel.stream_id == stream_id,
162
- StreamModel.version == versioning.expected_version,
166
+ self._stream_model.stream_id == stream_id,
167
+ self._stream_model.version == versioning.expected_version,
163
168
  )
164
169
  .values(version=versioning.initial_version)
165
170
  )
@@ -175,17 +180,17 @@ class SqlAlchemyStorageStrategy(StorageStrategy):
175
180
  self._ensure_stream(stream_id=stream_id, versioning=versioning)
176
181
 
177
182
  stream = cast(
178
- StreamModel,
183
+ BaseStream,
179
184
  first_true(
180
185
  self._session.info["strong_set"],
181
- pred=lambda model: isinstance(model, StreamModel)
186
+ pred=lambda model: isinstance(model, self._stream_model)
182
187
  and model.stream_id == stream_id
183
188
  and model.tenant_id == self._tenant_id,
184
189
  ),
185
190
  )
186
191
 
187
192
  entries = [
188
- EventModel(
193
+ self._event_model(
189
194
  uuid=event.uuid,
190
195
  created_at=event.created_at,
191
196
  name=event.name,
@@ -207,7 +212,7 @@ class SqlAlchemyStorageStrategy(StorageStrategy):
207
212
  self._dispatcher.dispatch(*records)
208
213
 
209
214
  def save_snapshot(self, snapshot: RawEvent) -> None:
210
- entry = SnapshotModel(
215
+ entry = self._snapshot_model(
211
216
  uuid=snapshot.uuid,
212
217
  created_at=snapshot.created_at,
213
218
  version=snapshot.version,
@@ -216,7 +221,7 @@ class SqlAlchemyStorageStrategy(StorageStrategy):
216
221
  event_context=snapshot.context,
217
222
  )
218
223
  stream = (
219
- self._session.query(StreamModel)
224
+ self._session.query(self._stream_model)
220
225
  .filter_by(stream_id=snapshot.stream_id)
221
226
  .one()
222
227
  )
@@ -224,18 +229,18 @@ class SqlAlchemyStorageStrategy(StorageStrategy):
224
229
  self._session.flush()
225
230
 
226
231
  def delete_stream(self, stream_id: StreamId) -> None:
227
- delete_events_stmt = delete(EventModel).where(
228
- EventModel.stream_id == stream_id,
232
+ delete_events_stmt = delete(self._event_model).where(
233
+ self._event_model.stream_id == stream_id,
229
234
  )
230
235
  self._session.execute(delete_events_stmt)
231
- delete_stream_stmt = delete(StreamModel).where(
232
- StreamModel.stream_id == stream_id,
236
+ delete_stream_stmt = delete(self._stream_model).where(
237
+ self._stream_model.stream_id == stream_id,
233
238
  )
234
239
  self._session.execute(delete_stream_stmt)
235
240
 
236
241
  @property
237
242
  def current_position(self) -> Position | None:
238
- stmt = select(func.max(EventModel.id))
243
+ stmt = select(func.max(self._event_model.id))
239
244
  last_event = self._session.scalar(stmt)
240
245
  return last_event or Position(0)
241
246
 
@@ -0,0 +1,53 @@
1
+ from typing import Protocol
2
+
3
+ from sqlalchemy import MetaData
4
+ from sqlalchemy.orm import registry
5
+ from sqlalchemy.orm.clsregistry import ClsRegistryToken
6
+
7
+ from event_sourcery_sqlalchemy.models.base import (
8
+ BaseEvent,
9
+ BaseOutboxEntry,
10
+ BaseProjectorCursor,
11
+ BaseSnapshot,
12
+ BaseStream,
13
+ )
14
+ from event_sourcery_sqlalchemy.models.default import (
15
+ DefaultEvent,
16
+ DefaultOutboxEntry,
17
+ DefaultProjectorCursor,
18
+ DefaultSnapshot,
19
+ DefaultStream,
20
+ )
21
+
22
+
23
+ class BaseProto(Protocol):
24
+ metadata: MetaData
25
+
26
+
27
+ _class_registry: dict[str, type | ClsRegistryToken] = {}
28
+
29
+
30
+ def configure_models(
31
+ base: type[BaseProto],
32
+ event_model: type[BaseEvent] = DefaultEvent,
33
+ stream_model: type[BaseStream] = DefaultStream,
34
+ snapshot_model: type[BaseSnapshot] = DefaultSnapshot,
35
+ outbox_entry_model: type[BaseOutboxEntry] = DefaultOutboxEntry,
36
+ projector_cursor_model: type[BaseProjectorCursor] = DefaultProjectorCursor,
37
+ ) -> None:
38
+ event_model.__set_mapping_information__(stream_model)
39
+ snapshot_model.__set_mapping_information__(stream_model)
40
+ stream_model.__set_mapping_information__(event_model, snapshot_model)
41
+
42
+ mapping_registry = registry(metadata=base.metadata, class_registry=_class_registry)
43
+ for model_cls in (
44
+ stream_model,
45
+ event_model,
46
+ snapshot_model,
47
+ outbox_entry_model,
48
+ projector_cursor_model,
49
+ ):
50
+ if model_cls in _class_registry.values():
51
+ continue
52
+
53
+ mapping_registry.map_declaratively(model_cls)
@@ -20,8 +20,9 @@ from sqlalchemy.orm import (
20
20
  Composite,
21
21
  InstrumentedAttribute,
22
22
  Mapped,
23
+ MappedColumn,
24
+ declared_attr,
23
25
  mapped_column,
24
- registry,
25
26
  relationship,
26
27
  )
27
28
 
@@ -31,11 +32,6 @@ from event_sourcery_sqlalchemy.guid import GUID
31
32
  from event_sourcery_sqlalchemy.jsonb import JSONB
32
33
 
33
34
 
34
- def configure_models(base: type[Any]) -> None:
35
- for model_cls in (Stream, Event, Snapshot, OutboxEntry, ProjectorCursor):
36
- registry(metadata=base.metadata, class_registry={}).map_declaratively(model_cls)
37
-
38
-
39
35
  class StreamIdComparator(Comparator[StreamId]):
40
36
  def __init__(
41
37
  self,
@@ -55,12 +51,17 @@ class StreamIdComparator(Comparator[StreamId]):
55
51
  return and_(same_uuid, same_name, same_category) # type: ignore
56
52
 
57
53
 
58
- class Stream:
59
- __tablename__ = "event_sourcery_streams"
60
- __table_args__ = (
61
- UniqueConstraint("uuid", "category", "tenant_id"),
62
- UniqueConstraint("name", "category", "tenant_id"),
63
- )
54
+ class BaseStream:
55
+ __event_model__: type["BaseEvent"]
56
+ __snapshot_model__: type["BaseSnapshot"]
57
+
58
+ @declared_attr # type: ignore[arg-type]
59
+ @classmethod
60
+ def __table_args__(cls) -> tuple[Index | UniqueConstraint, ...]:
61
+ return (
62
+ UniqueConstraint("uuid", "category", "tenant_id"),
63
+ UniqueConstraint("name", "category", "tenant_id"),
64
+ )
64
65
 
65
66
  id = mapped_column(BigInteger().with_variant(Integer(), "sqlite"), primary_key=True)
66
67
  uuid = mapped_column(GUID(), nullable=False)
@@ -69,8 +70,22 @@ class Stream:
69
70
  tenant_id = mapped_column(String(255), nullable=False)
70
71
  version = mapped_column(BigInteger(), nullable=True)
71
72
 
72
- events: Mapped[list["Event"]]
73
- snapshots: Mapped[list["Snapshot"]]
73
+ @classmethod
74
+ def __set_mapping_information__(
75
+ cls, event_model: type["BaseEvent"], snapshot_model: type["BaseSnapshot"]
76
+ ) -> None:
77
+ cls.__event_model__ = event_model
78
+ cls.__snapshot_model__ = snapshot_model
79
+
80
+ @declared_attr
81
+ @classmethod
82
+ def events(cls) -> Mapped[list["BaseEvent"]]:
83
+ return relationship(cls.__event_model__, back_populates="stream")
84
+
85
+ @declared_attr
86
+ @classmethod
87
+ def snapshots(cls) -> Mapped[list["BaseSnapshot"]]:
88
+ return relationship(cls.__snapshot_model__, back_populates="stream")
74
89
 
75
90
  @hybrid_property
76
91
  def stream_id(self) -> StreamId:
@@ -82,16 +97,21 @@ class Stream:
82
97
  return StreamIdComparator(cls.uuid, cls.name, cls.category)
83
98
 
84
99
 
85
- class Event:
86
- __tablename__ = "event_sourcery_events"
87
- __table_args__ = (
88
- Index(
89
- "ix_events_stream_id_version",
90
- "db_stream_id",
91
- "version",
92
- unique=True,
93
- ),
94
- )
100
+ class BaseEvent:
101
+ __stream_model__: type["BaseStream"]
102
+ __tablename__: str
103
+
104
+ @declared_attr # type: ignore[arg-type]
105
+ @classmethod
106
+ def __table_args__(cls) -> tuple[Index | UniqueConstraint, ...]:
107
+ return (
108
+ Index(
109
+ f"ix_events_stream_id_version_{cls.__tablename__}",
110
+ "db_stream_id",
111
+ "version",
112
+ unique=True,
113
+ ),
114
+ )
95
115
 
96
116
  def __init__(
97
117
  self,
@@ -109,17 +129,29 @@ class Event:
109
129
  self.event_context = event_context
110
130
  self.version = version
111
131
 
132
+ @classmethod
133
+ def __set_mapping_information__(cls, stream_model: type[BaseStream]) -> None:
134
+ cls.__stream_model__ = stream_model
135
+
136
+ @declared_attr
137
+ @classmethod
138
+ def _db_stream_id(cls) -> MappedColumn[Any]:
139
+ return mapped_column(
140
+ "db_stream_id",
141
+ BigInteger().with_variant(Integer(), "sqlite"),
142
+ ForeignKey(cls.__stream_model__.id),
143
+ nullable=False,
144
+ index=True,
145
+ )
146
+
147
+ @declared_attr
148
+ @classmethod
149
+ def stream(cls) -> Mapped[BaseStream]:
150
+ return relationship(cls.__stream_model__, back_populates="events")
151
+
112
152
  id = mapped_column(BigInteger().with_variant(Integer(), "sqlite"), primary_key=True)
113
153
  version = mapped_column(Integer(), nullable=True)
114
154
  uuid = mapped_column(GUID(), index=True, unique=True)
115
- _db_stream_id = mapped_column(
116
- "db_stream_id",
117
- BigInteger().with_variant(Integer(), "sqlite"),
118
- ForeignKey(Stream.id),
119
- nullable=False,
120
- index=True,
121
- )
122
- stream: Mapped[Stream] = relationship(Stream, back_populates="events")
123
155
  stream_id: AssociationProxy[StreamId] = association_proxy("stream", "stream_id")
124
156
  tenant_id: AssociationProxy[TenantId] = association_proxy("stream", "tenant_id")
125
157
  name = mapped_column(String(200), nullable=False)
@@ -128,8 +160,8 @@ class Event:
128
160
  created_at = mapped_column(DateTime(), nullable=False, index=True)
129
161
 
130
162
 
131
- class Snapshot:
132
- __tablename__ = "event_sourcery_snapshots"
163
+ class BaseSnapshot:
164
+ __stream_model__: type["BaseStream"]
133
165
 
134
166
  def __init__(
135
167
  self,
@@ -147,16 +179,28 @@ class Snapshot:
147
179
  self.event_context = event_context
148
180
  self.version = version
149
181
 
182
+ @classmethod
183
+ def __set_mapping_information__(cls, stream_model: type[BaseStream]) -> None:
184
+ cls.__stream_model__ = stream_model
185
+
186
+ @declared_attr
187
+ @classmethod
188
+ def _db_stream_id(cls) -> MappedColumn[Any]:
189
+ return mapped_column(
190
+ "db_stream_id",
191
+ BigInteger().with_variant(Integer(), "sqlite"),
192
+ ForeignKey(cls.__stream_model__.id),
193
+ nullable=False,
194
+ index=True,
195
+ )
196
+
197
+ @declared_attr
198
+ @classmethod
199
+ def stream(cls) -> Mapped[BaseStream]:
200
+ return relationship(cls.__stream_model__, back_populates="snapshots")
201
+
150
202
  uuid = mapped_column(GUID, primary_key=True)
151
203
  version = mapped_column(Integer(), nullable=False)
152
- _db_stream_id = mapped_column(
153
- "db_stream_id",
154
- BigInteger().with_variant(Integer(), "sqlite"),
155
- ForeignKey(Stream.id),
156
- nullable=False,
157
- index=True,
158
- )
159
- stream = relationship(Stream, back_populates="snapshots")
160
204
  stream_id: AssociationProxy[StreamId] = association_proxy("stream", "stream_id")
161
205
  name = mapped_column(String(50), nullable=False)
162
206
  data = mapped_column(JSONB(), nullable=False)
@@ -164,9 +208,7 @@ class Snapshot:
164
208
  created_at = mapped_column(DateTime(), nullable=False)
165
209
 
166
210
 
167
- class OutboxEntry:
168
- __tablename__ = "event_sourcery_outbox_entries"
169
-
211
+ class BaseOutboxEntry:
170
212
  id = mapped_column(BigInteger().with_variant(Integer(), "sqlite"), primary_key=True)
171
213
  created_at = mapped_column(DateTime(), nullable=False, index=True)
172
214
  data = mapped_column(JSONB(), nullable=False)
@@ -175,24 +217,24 @@ class OutboxEntry:
175
217
  tries_left = mapped_column(Integer(), nullable=False)
176
218
 
177
219
 
178
- class ProjectorCursor:
179
- __tablename__ = "event_sourcery_projector_cursors"
180
- __table_args__ = (
181
- Index(
182
- "ix_name_stream_id",
183
- "name",
184
- "stream_id",
185
- "category",
186
- unique=True,
187
- ),
188
- )
220
+ class BaseProjectorCursor:
221
+ __tablename__: str
222
+
223
+ @declared_attr # type: ignore[arg-type]
224
+ @classmethod
225
+ def __table_args__(cls) -> tuple[Index | UniqueConstraint, ...]:
226
+ return (
227
+ Index(
228
+ f"ix_name_stream_id_{cls.__tablename__}",
229
+ "name",
230
+ "stream_id",
231
+ "category",
232
+ unique=True,
233
+ ),
234
+ )
189
235
 
190
236
  id = mapped_column(BigInteger().with_variant(Integer(), "sqlite"), primary_key=True)
191
237
  name = mapped_column(String(255), nullable=False)
192
238
  stream_id = mapped_column(GUID(), nullable=False, index=True)
193
239
  category = mapped_column(String(255), nullable=True)
194
240
  version = mapped_column(BigInteger(), nullable=False)
195
-
196
-
197
- Stream.events = relationship(Event, back_populates="stream")
198
- Stream.snapshots = relationship(Snapshot, back_populates="stream")
@@ -0,0 +1,27 @@
1
+ from event_sourcery_sqlalchemy.models.base import (
2
+ BaseEvent,
3
+ BaseOutboxEntry,
4
+ BaseProjectorCursor,
5
+ BaseSnapshot,
6
+ BaseStream,
7
+ )
8
+
9
+
10
+ class DefaultStream(BaseStream):
11
+ __tablename__ = "event_sourcery_streams"
12
+
13
+
14
+ class DefaultEvent(BaseEvent):
15
+ __tablename__ = "event_sourcery_events"
16
+
17
+
18
+ class DefaultSnapshot(BaseSnapshot):
19
+ __tablename__ = "event_sourcery_snapshots"
20
+
21
+
22
+ class DefaultOutboxEntry(BaseOutboxEntry):
23
+ __tablename__ = "event_sourcery_outbox_entries"
24
+
25
+
26
+ class DefaultProjectorCursor(BaseProjectorCursor):
27
+ __tablename__ = "event_sourcery_projector_cursors"
@@ -15,7 +15,7 @@ from event_sourcery.event_store.interfaces import (
15
15
  OutboxFiltererStrategy,
16
16
  OutboxStorageStrategy,
17
17
  )
18
- from event_sourcery_sqlalchemy.models import OutboxEntry
18
+ from event_sourcery_sqlalchemy.models.base import BaseOutboxEntry
19
19
 
20
20
  logger = logging.getLogger(__name__)
21
21
 
@@ -25,6 +25,7 @@ class SqlAlchemyOutboxStorageStrategy(OutboxStorageStrategy):
25
25
  _session: Session
26
26
  _filterer: OutboxFiltererStrategy
27
27
  _max_publish_attempts: int
28
+ _outbox_entry_model: type[BaseOutboxEntry]
28
29
 
29
30
  def put_into_outbox(self, records: list[RecordedRaw]) -> None:
30
31
  rows = []
@@ -53,15 +54,15 @@ class SqlAlchemyOutboxStorageStrategy(OutboxStorageStrategy):
53
54
  if len(rows) == 0:
54
55
  return
55
56
 
56
- self._session.execute(insert(OutboxEntry), rows)
57
+ self._session.execute(insert(self._outbox_entry_model), rows)
57
58
 
58
59
  def outbox_entries(
59
60
  self, limit: int
60
61
  ) -> Iterator[AbstractContextManager[RecordedRaw]]:
61
62
  stmt = (
62
- select(OutboxEntry)
63
- .filter(OutboxEntry.tries_left > 0)
64
- .order_by(OutboxEntry.id)
63
+ select(self._outbox_entry_model)
64
+ .filter(self._outbox_entry_model.tries_left > 0)
65
+ .order_by(self._outbox_entry_model.id)
65
66
  .limit(limit)
66
67
  .with_for_update(skip_locked=True)
67
68
  )
@@ -71,7 +72,7 @@ class SqlAlchemyOutboxStorageStrategy(OutboxStorageStrategy):
71
72
 
72
73
  @contextmanager
73
74
  def _publish_context(
74
- self, entry: OutboxEntry
75
+ self, entry: BaseOutboxEntry
75
76
  ) -> Generator[RecordedRaw, None, None]:
76
77
  raw = RawEvent(
77
78
  uuid=UUID(entry.data["uuid"]),
@@ -9,13 +9,22 @@ from sqlalchemy.orm import Session
9
9
 
10
10
  from event_sourcery.event_store import Position, RecordedRaw
11
11
  from event_sourcery.event_store.interfaces import SubscriptionStrategy
12
- from event_sourcery_sqlalchemy import dto, models
12
+ from event_sourcery_sqlalchemy import dto
13
+ from event_sourcery_sqlalchemy.models.base import BaseEvent, BaseStream
13
14
 
14
15
 
15
16
  class SqlAlchemySubscriptionStrategy(SubscriptionStrategy):
16
- def __init__(self, session: Session, gap_retry_interval: timedelta) -> None:
17
+ def __init__(
18
+ self,
19
+ session: Session,
20
+ gap_retry_interval: timedelta,
21
+ event_model: type[BaseEvent],
22
+ stream_model: type[BaseStream],
23
+ ) -> None:
17
24
  self._session = session
18
25
  self._gap_retry_interval = gap_retry_interval
26
+ self._event_model = event_model
27
+ self._stream_model = stream_model
19
28
 
20
29
  def subscribe_to_all(
21
30
  self,
@@ -24,7 +33,9 @@ class SqlAlchemySubscriptionStrategy(SubscriptionStrategy):
24
33
  timelimit: timedelta,
25
34
  ) -> Iterator[list[RecordedRaw]]:
26
35
  return GapDetectingIterator(
27
- get_batch=GetBatchToAll(self._session, batch_size),
36
+ get_batch=GetBatchToAll(
37
+ self._session, batch_size, self._event_model, self._stream_model
38
+ ),
28
39
  gap_retry_interval=self._gap_retry_interval,
29
40
  start_from=start_from,
30
41
  batch_size=batch_size,
@@ -39,7 +50,13 @@ class SqlAlchemySubscriptionStrategy(SubscriptionStrategy):
39
50
  category: str,
40
51
  ) -> Iterator[list[RecordedRaw]]:
41
52
  return GapDetectingIterator(
42
- get_batch=GetBatchToCategory(self._session, batch_size, category),
53
+ get_batch=GetBatchToCategory(
54
+ self._session,
55
+ batch_size,
56
+ category,
57
+ self._event_model,
58
+ self._stream_model,
59
+ ),
43
60
  gap_retry_interval=self._gap_retry_interval,
44
61
  start_from=start_from,
45
62
  batch_size=batch_size,
@@ -54,7 +71,9 @@ class SqlAlchemySubscriptionStrategy(SubscriptionStrategy):
54
71
  events: list[str],
55
72
  ) -> Iterator[list[RecordedRaw]]:
56
73
  return GapDetectingIterator(
57
- get_batch=GetBatchToEvents(self._session, batch_size, events),
74
+ get_batch=GetBatchToEvents(
75
+ self._session, batch_size, events, self._event_model, self._stream_model
76
+ ),
58
77
  gap_retry_interval=self._gap_retry_interval,
59
78
  start_from=start_from,
60
79
  batch_size=batch_size,
@@ -63,20 +82,28 @@ class SqlAlchemySubscriptionStrategy(SubscriptionStrategy):
63
82
 
64
83
 
65
84
  class GetBatch(Protocol):
66
- def __call__(self, position: Position) -> list[models.Event]: ...
85
+ def __call__(self, position: Position) -> list[BaseEvent]: ...
67
86
 
68
87
 
69
88
  class GetBatchToAll(GetBatch):
70
- def __init__(self, session: Session, batch_size: int) -> None:
89
+ def __init__(
90
+ self,
91
+ session: Session,
92
+ batch_size: int,
93
+ event_model: type[BaseEvent],
94
+ stream_model: type[BaseStream],
95
+ ) -> None:
71
96
  self._session = session
72
97
  self._batch_size = batch_size
98
+ self._event_model = event_model
99
+ self._stream_model = stream_model
73
100
 
74
- def __call__(self, position: Position) -> list[models.Event]:
101
+ def __call__(self, position: Position) -> list[BaseEvent]:
75
102
  stmt = (
76
- select(models.Event)
77
- .join(models.Stream)
78
- .where(models.Event.id > position)
79
- .order_by(models.Event.id)
103
+ select(self._event_model)
104
+ .join(self._stream_model)
105
+ .where(self._event_model.id > position)
106
+ .order_by(self._event_model.id)
80
107
  .limit(self._batch_size)
81
108
  )
82
109
 
@@ -84,18 +111,27 @@ class GetBatchToAll(GetBatch):
84
111
 
85
112
 
86
113
  class GetBatchToCategory(GetBatch):
87
- def __init__(self, session: Session, batch_size: int, category: str) -> None:
114
+ def __init__(
115
+ self,
116
+ session: Session,
117
+ batch_size: int,
118
+ category: str,
119
+ event_model: type[BaseEvent],
120
+ stream_model: type[BaseStream],
121
+ ) -> None:
88
122
  self._session = session
89
123
  self._batch_size = batch_size
90
124
  self._category = category
125
+ self._event_model = event_model
126
+ self._stream_model = stream_model
91
127
 
92
- def __call__(self, position: Position) -> list[models.Event]:
128
+ def __call__(self, position: Position) -> list[BaseEvent]:
93
129
  stmt = (
94
- select(models.Event)
95
- .join(models.Stream)
96
- .where(models.Stream.category == self._category)
97
- .where(models.Event.id > position)
98
- .order_by(models.Event.id)
130
+ select(self._event_model)
131
+ .join(self._stream_model)
132
+ .where(self._stream_model.category == self._category)
133
+ .where(self._event_model.id > position)
134
+ .order_by(self._event_model.id)
99
135
  .limit(self._batch_size)
100
136
  )
101
137
 
@@ -103,18 +139,27 @@ class GetBatchToCategory(GetBatch):
103
139
 
104
140
 
105
141
  class GetBatchToEvents(GetBatch):
106
- def __init__(self, session: Session, batch_size: int, events: list[str]) -> None:
142
+ def __init__(
143
+ self,
144
+ session: Session,
145
+ batch_size: int,
146
+ events: list[str],
147
+ event_model: type[BaseEvent],
148
+ stream_model: type[BaseStream],
149
+ ) -> None:
107
150
  self._session = session
108
151
  self._batch_size = batch_size
109
152
  self._events = events
153
+ self._event_model = event_model
154
+ self._stream_model = stream_model
110
155
 
111
- def __call__(self, position: Position) -> list[models.Event]:
156
+ def __call__(self, position: Position) -> list[BaseEvent]:
112
157
  stmt = (
113
- select(models.Event)
114
- .join(models.Stream)
115
- .where(models.Event.name.in_(self._events))
116
- .where(models.Event.id > position)
117
- .order_by(models.Event.id)
158
+ select(self._event_model)
159
+ .join(self._stream_model)
160
+ .where(self._event_model.name.in_(self._events))
161
+ .where(self._event_model.id > position)
162
+ .order_by(self._event_model.id)
118
163
  .limit(self._batch_size)
119
164
  )
120
165
 
@@ -125,7 +170,7 @@ class GetBatchToEvents(GetBatch):
125
170
  class Cursor:
126
171
  position: Position
127
172
 
128
- def advance(self, batch: list[models.Event]) -> None:
173
+ def advance(self, batch: list[BaseEvent]) -> None:
129
174
  if len(batch) > 0:
130
175
  self.position = batch[-1].id
131
176
 
@@ -159,14 +204,14 @@ class GapDetectingIterator(Iterator[list[RecordedRaw]]):
159
204
  time.sleep(self._gap_retry_interval.total_seconds())
160
205
 
161
206
  @staticmethod
162
- def _is_continuous(batch: list[models.Event]) -> bool:
207
+ def _is_continuous(batch: list[BaseEvent]) -> bool:
163
208
  if len(batch) < 2:
164
209
  return False
165
210
 
166
211
  return cast(bool, batch[-1].id - batch[0].id + 1 == len(batch))
167
212
 
168
213
  @staticmethod
169
- def _batch_to_recorded_raw(batch: list[models.Event]) -> list[RecordedRaw]:
214
+ def _batch_to_recorded_raw(batch: list[BaseEvent]) -> list[RecordedRaw]:
170
215
  return [
171
216
  RecordedRaw(
172
217
  entry=dto.raw_event(event, event.stream),
@@ -3,7 +3,7 @@ name = "python-event-sourcery"
3
3
  requires-python = ">=3.10"
4
4
 
5
5
  [tool.poetry]
6
- version = "0.4.0a18"
6
+ version = "0.4.0a19"
7
7
  description = ""
8
8
  authors = ["Your Name <you@example.com>"]
9
9
  license = "MIT"