python-event-sourcery 0.4.0a0__tar.gz → 0.4.0a2__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 (45) hide show
  1. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/PKG-INFO +6 -7
  2. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery_sqlalchemy/__init__.py +5 -1
  3. python_event_sourcery-0.4.0a2/event_sourcery_sqlalchemy/dto.py +18 -0
  4. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery_sqlalchemy/event_store.py +4 -2
  5. python_event_sourcery-0.4.0a2/event_sourcery_sqlalchemy/subscription.py +176 -0
  6. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/pyproject.toml +22 -9
  7. python_event_sourcery-0.4.0a0/event_sourcery_kombu/__init__.py +0 -155
  8. python_event_sourcery-0.4.0a0/event_sourcery_sqlalchemy/subscription.py +0 -33
  9. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/LICENSE +0 -0
  10. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/__init__.py +0 -0
  11. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/aggregate/__init__.py +0 -0
  12. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/aggregate/aggregate.py +0 -0
  13. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/aggregate/repository.py +0 -0
  14. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/event_store/__init__.py +0 -0
  15. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/event_store/dispatcher.py +0 -0
  16. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/event_store/event/__init__.py +0 -0
  17. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/event_store/event/dto.py +0 -0
  18. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/event_store/event/registry.py +0 -0
  19. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/event_store/event/serde.py +0 -0
  20. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/event_store/event_store.py +0 -0
  21. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/event_store/exceptions.py +0 -0
  22. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/event_store/factory.py +0 -0
  23. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/event_store/in_memory.py +0 -0
  24. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/event_store/interfaces.py +0 -0
  25. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/event_store/outbox.py +0 -0
  26. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/event_store/stream_id.py +0 -0
  27. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/event_store/subscription.py +0 -0
  28. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/event_store/versioning.py +0 -0
  29. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/py.typed +0 -0
  30. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/read_model/__init__.py +0 -0
  31. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/read_model/cursors_dao.py +0 -0
  32. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery/read_model/projector.py +0 -0
  33. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery_esdb/__init__.py +0 -0
  34. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery_esdb/dto.py +0 -0
  35. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery_esdb/event_store.py +0 -0
  36. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery_esdb/outbox.py +0 -0
  37. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery_esdb/py.typed +0 -0
  38. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery_esdb/stream.py +0 -0
  39. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery_esdb/subscription.py +0 -0
  40. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery_sqlalchemy/cursors_dao.py +0 -0
  41. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery_sqlalchemy/guid.py +0 -0
  42. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery_sqlalchemy/jsonb.py +0 -0
  43. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery_sqlalchemy/models.py +0 -0
  44. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/event_sourcery_sqlalchemy/outbox.py +0 -0
  45. {python_event_sourcery-0.4.0a0 → python_event_sourcery-0.4.0a2}/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.0a0
3
+ Version: 0.4.0a2
4
4
  Summary:
5
5
  License: MIT
6
6
  Author: Your Name
@@ -10,13 +10,12 @@ Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
- Provides-Extra: cloudevents
13
+ Classifier: Programming Language :: Python :: 3.12
14
14
  Provides-Extra: django
15
- Provides-Extra: kombu
16
- Requires-Dist: SQLAlchemy (>=2.0)
17
- Requires-Dist: cloudevents ; extra == "cloudevents"
15
+ Provides-Extra: esdb
16
+ Provides-Extra: sqlalchemy
17
+ Requires-Dist: SQLAlchemy (>=2.0) ; extra == "sqlalchemy"
18
18
  Requires-Dist: django (>=4,<6) ; extra == "django"
19
- Requires-Dist: esdbclient (>=1.0b3)
20
- Requires-Dist: kombu ; extra == "kombu"
19
+ Requires-Dist: esdbclient (>=1.0b3) ; extra == "esdb"
21
20
  Requires-Dist: more-itertools
22
21
  Requires-Dist: pydantic (>=2.0,<3)
@@ -7,6 +7,7 @@ __all__ = [
7
7
  ]
8
8
 
9
9
  from dataclasses import dataclass
10
+ from datetime import timedelta
10
11
 
11
12
  from pydantic import BaseModel, ConfigDict, PositiveInt
12
13
  from sqlalchemy.orm import Session
@@ -36,6 +37,7 @@ class Config(BaseModel):
36
37
  model_config = ConfigDict(extra="forbid", frozen=True)
37
38
 
38
39
  outbox_attempts: PositiveInt = 3
40
+ gap_retry_interval: timedelta = timedelta(seconds=0.5)
39
41
 
40
42
 
41
43
  @dataclass(repr=False)
@@ -63,7 +65,9 @@ class SQLAlchemyBackendFactory(BackendFactory):
63
65
  )
64
66
  backend.subscriber = es.subscription.SubscriptionBuilder(
65
67
  _serde=backend.serde,
66
- _strategy=SqlAlchemySubscriptionStrategy(),
68
+ _strategy=SqlAlchemySubscriptionStrategy(
69
+ self._session, self._config.gap_retry_interval
70
+ ),
67
71
  )
68
72
  return backend
69
73
 
@@ -0,0 +1,18 @@
1
+ from event_sourcery.event_store import RawEvent, StreamId
2
+ from event_sourcery_sqlalchemy.models import Event, Stream
3
+
4
+
5
+ def raw_event(from_entry: Event, in_stream: Stream) -> RawEvent:
6
+ return RawEvent(
7
+ uuid=from_entry.uuid,
8
+ stream_id=StreamId(
9
+ uuid=in_stream.uuid,
10
+ name=in_stream.name,
11
+ category=None if in_stream.category == "" else in_stream.category,
12
+ ),
13
+ created_at=from_entry.created_at,
14
+ version=from_entry.version,
15
+ name=from_entry.name,
16
+ data=from_entry.data,
17
+ context=from_entry.event_context,
18
+ )
@@ -2,7 +2,7 @@ from dataclasses import dataclass
2
2
  from typing import Sequence, Union, cast
3
3
 
4
4
  from more_itertools import first_true
5
- from sqlalchemy import delete, select, update
5
+ from sqlalchemy import delete, func, select, update
6
6
  from sqlalchemy.dialects.postgresql import insert as postgresql_insert
7
7
  from sqlalchemy.exc import NoResultFound
8
8
  from sqlalchemy.orm import Session
@@ -221,4 +221,6 @@ class SqlAlchemyStorageStrategy(StorageStrategy):
221
221
 
222
222
  @property
223
223
  def current_position(self) -> Position | None:
224
- raise NotImplementedError
224
+ stmt = select(func.max(EventModel.id))
225
+ last_event = self._session.scalar(stmt)
226
+ return last_event or Position(0)
@@ -0,0 +1,176 @@
1
+ import time
2
+ from dataclasses import dataclass
3
+ from datetime import timedelta
4
+ from typing import Iterator, Protocol, cast
5
+
6
+ from sqlalchemy import select
7
+ from sqlalchemy.orm import Session
8
+
9
+ from event_sourcery.event_store import Position, RecordedRaw
10
+ from event_sourcery.event_store.interfaces import SubscriptionStrategy
11
+ from event_sourcery_sqlalchemy import dto, models
12
+
13
+
14
+ class SqlAlchemySubscriptionStrategy(SubscriptionStrategy):
15
+ def __init__(self, session: Session, gap_retry_interval: timedelta) -> None:
16
+ self._session = session
17
+ self._gap_retry_interval = gap_retry_interval
18
+
19
+ def subscribe_to_all(
20
+ self,
21
+ start_from: Position,
22
+ batch_size: int,
23
+ timelimit: timedelta,
24
+ ) -> Iterator[list[RecordedRaw]]:
25
+ return GapDetectingIterator(
26
+ get_batch=GetBatchToAll(self._session, batch_size),
27
+ gap_retry_interval=self._gap_retry_interval,
28
+ start_from=start_from,
29
+ batch_size=batch_size,
30
+ timelimit=timelimit,
31
+ )
32
+
33
+ def subscribe_to_category(
34
+ self,
35
+ start_from: Position,
36
+ batch_size: int,
37
+ timelimit: timedelta,
38
+ category: str,
39
+ ) -> Iterator[list[RecordedRaw]]:
40
+ return GapDetectingIterator(
41
+ get_batch=GetBatchToCategory(self._session, batch_size, category),
42
+ gap_retry_interval=self._gap_retry_interval,
43
+ start_from=start_from,
44
+ batch_size=batch_size,
45
+ timelimit=timelimit,
46
+ )
47
+
48
+ def subscribe_to_events(
49
+ self,
50
+ start_from: Position,
51
+ batch_size: int,
52
+ timelimit: timedelta,
53
+ events: list[str],
54
+ ) -> Iterator[list[RecordedRaw]]:
55
+ return GapDetectingIterator(
56
+ get_batch=GetBatchToEvents(self._session, batch_size, events),
57
+ gap_retry_interval=self._gap_retry_interval,
58
+ start_from=start_from,
59
+ batch_size=batch_size,
60
+ timelimit=timelimit,
61
+ )
62
+
63
+
64
+ class GetBatch(Protocol):
65
+ def __call__(self, position: Position) -> list[models.Event]:
66
+ ...
67
+
68
+
69
+ class GetBatchToAll(GetBatch):
70
+ def __init__(self, session: Session, batch_size: int) -> None:
71
+ self._session = session
72
+ self._batch_size = batch_size
73
+
74
+ def __call__(self, position: Position) -> list[models.Event]:
75
+ stmt = (
76
+ select(models.Event)
77
+ .join(models.Stream)
78
+ .where(models.Event.id > position)
79
+ .order_by(models.Event.id)
80
+ .limit(self._batch_size)
81
+ )
82
+
83
+ return list(self._session.scalars(stmt).all())
84
+
85
+
86
+ class GetBatchToCategory(GetBatch):
87
+ def __init__(self, session: Session, batch_size: int, category: str) -> None:
88
+ self._session = session
89
+ self._batch_size = batch_size
90
+ self._category = category
91
+
92
+ def __call__(self, position: Position) -> list[models.Event]:
93
+ 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)
99
+ .limit(self._batch_size)
100
+ )
101
+
102
+ return list(self._session.scalars(stmt).all())
103
+
104
+
105
+ class GetBatchToEvents(GetBatch):
106
+ def __init__(self, session: Session, batch_size: int, events: list[str]) -> None:
107
+ self._session = session
108
+ self._batch_size = batch_size
109
+ self._events = events
110
+
111
+ def __call__(self, position: Position) -> list[models.Event]:
112
+ 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)
118
+ .limit(self._batch_size)
119
+ )
120
+
121
+ return list(self._session.scalars(stmt).all())
122
+
123
+
124
+ @dataclass
125
+ class Cursor:
126
+ position: Position
127
+
128
+ def advance(self, batch: list[models.Event]) -> None:
129
+ if len(batch) > 0:
130
+ self.position = batch[-1].id
131
+
132
+
133
+ class GapDetectingIterator(Iterator[list[RecordedRaw]]):
134
+ def __init__(
135
+ self,
136
+ get_batch: GetBatch,
137
+ gap_retry_interval: timedelta,
138
+ start_from: Position,
139
+ batch_size: int,
140
+ timelimit: timedelta,
141
+ ) -> None:
142
+ self._get_batch = get_batch
143
+ self._gap_retry_interval = gap_retry_interval
144
+ self._cursor = Cursor(position=start_from)
145
+ self._batch_size = batch_size
146
+ self._timelimit = timelimit
147
+
148
+ def __next__(self) -> list[RecordedRaw]:
149
+ start = time.monotonic()
150
+ while True:
151
+ batch = self._get_batch(self._cursor.position)
152
+ if self._is_continuous(batch) and len(batch) == self._batch_size:
153
+ self._cursor.advance(batch)
154
+ return self._batch_to_recorded_raw(batch)
155
+ elif time.monotonic() - start > self._timelimit.total_seconds():
156
+ self._cursor.advance(batch)
157
+ return self._batch_to_recorded_raw(batch)
158
+ else:
159
+ time.sleep(self._gap_retry_interval.total_seconds())
160
+
161
+ @staticmethod
162
+ def _is_continuous(batch: list[models.Event]) -> bool:
163
+ if len(batch) < 2:
164
+ return False
165
+
166
+ return cast(bool, batch[-1].id - batch[0].id + 1 == len(batch))
167
+
168
+ @staticmethod
169
+ def _batch_to_recorded_raw(batch: list[models.Event]) -> list[RecordedRaw]:
170
+ return [
171
+ RecordedRaw(
172
+ entry=dto.raw_event(event, event.stream),
173
+ position=event.id,
174
+ )
175
+ for event in batch
176
+ ]
@@ -1,24 +1,21 @@
1
1
  [tool.poetry]
2
2
  name = "python-event-sourcery"
3
- version = "0.4.0a0"
3
+ version = "0.4.0a2"
4
4
  description = ""
5
5
  authors = ["Your Name <you@example.com>"]
6
6
  license = "MIT"
7
7
  packages = [
8
8
  { include = "event_sourcery" },
9
9
  { include = "event_sourcery_esdb" },
10
- { include = "event_sourcery_kombu" },
11
10
  { include = "event_sourcery_sqlalchemy" },
12
11
  ]
13
12
 
14
13
  [tool.poetry.dependencies]
15
14
  python = ">=3.10,<4.0"
16
15
  pydantic = ">=2.0,<3"
17
- SQLAlchemy = ">=2.0"
18
- esdbclient = ">=1.0b3"
19
- kombu = {version = "*", optional = true}
20
- cloudevents = {version = "*", optional = true}
21
16
  more-itertools = "*"
17
+ SQLAlchemy = {version = ">=2.0", optional = true}
18
+ esdbclient = {version = ">=1.0b3", optional = true}
22
19
  django = {version = ">=4,<6", optional = true}
23
20
 
24
21
  [tool.poetry.group.dev.dependencies]
@@ -32,10 +29,11 @@ pytest-cov = "*"
32
29
  mkdocs-material = "^8.3.9"
33
30
  psycopg2-binary = "^2.9.3"
34
31
  pytest-django = {version = "*"}
32
+ flake8-pyproject = "*"
35
33
 
36
34
  [tool.poetry.extras]
37
- kombu = ["kombu"]
38
- cloudevents = ["cloudevents"]
35
+ sqlalchemy = ["SQLAlchemy"]
36
+ esdb = ["esdbclient"]
39
37
  django = ["django"]
40
38
 
41
39
  [tool.pytest]
@@ -57,7 +55,6 @@ source = [
57
55
  "event_sourcery_sqlalchemy",
58
56
  ]
59
57
  omit = [
60
- "event_sourcery_kombu/*",
61
58
  "tests/*",
62
59
  ]
63
60
 
@@ -75,6 +72,22 @@ exclude_lines = [
75
72
  "TYPE_CHECKING",
76
73
  ]
77
74
 
75
+ [tool.mypy]
76
+ python_version = "3.10"
77
+ ignore_missing_imports = true
78
+ disallow_untyped_defs = true
79
+ no_implicit_optional = true
80
+ check_untyped_defs = true
81
+ warn_return_any = true
82
+ warn_unused_ignores = true
83
+ show_error_codes = true
84
+
85
+ [tool.flake8]
86
+ max-line-length = 88
87
+
88
+ [tool.isort]
89
+ profile = "black"
90
+
78
91
  [build-system]
79
92
  requires = ["poetry-core>=1.0.0"]
80
93
  build-backend = "poetry.core.masonry.api"
@@ -1,155 +0,0 @@
1
- from dataclasses import dataclass, field
2
- from typing import Callable, Type
3
-
4
- from kombu import Connection, Exchange, Message, Queue
5
- from kombu.connection import ConnectionPool
6
- from kombu.pools import connections
7
- from pydantic import AmqpDsn, Field
8
- from pydantic_settings import BaseSettings
9
-
10
- __all__ = ["publish_event", "declare_exchange"]
11
-
12
- from event_sourcery.event_store import Event, Metadata, StreamId
13
- from event_sourcery.event_store.event.registry import event_name
14
-
15
-
16
- class BrokerSettings(BaseSettings):
17
- BROKER_URL: AmqpDsn = Field(default="amqp://guest:guest@rabbitmq//")
18
-
19
-
20
- class PoolFactory:
21
- _pool: ConnectionPool | None = None
22
- _config: BrokerSettings | None = None
23
-
24
- @classmethod
25
- def configure(cls, broker_url: str) -> None:
26
- if cls._pool is not None:
27
- cls._pool.force_close_all()
28
- cls._pool = None
29
-
30
- cls._config = BrokerSettings(BROKER_URL=broker_url)
31
-
32
- @classmethod
33
- def get(cls) -> ConnectionPool:
34
- if cls._config is None:
35
- cls._config = BrokerSettings()
36
-
37
- if cls._pool is None:
38
- connection = Connection(
39
- cls._config.BROKER_URL, transport_options={"confirm_publish": True}
40
- )
41
- cls._pool = connections[connection]
42
- return cls._pool
43
-
44
-
45
- EVENT_SOURCERY_EXCHANGE = "event_sourcery.events"
46
-
47
-
48
- def publish_event(
49
- metadata: Metadata, stream_name: str | None, stream_id: StreamId
50
- ) -> None:
51
- name = event_name(type(metadata.event))
52
- stream_category = stream_name.split(".", maxsplit=1)[0] if stream_name else None
53
- _publish(
54
- routing_key=name,
55
- headers={
56
- "event": name,
57
- "stream_name": stream_name,
58
- "stream_category": stream_category,
59
- "stream_id": str(stream_id),
60
- },
61
- message=metadata.json(),
62
- exchange=EVENT_SOURCERY_EXCHANGE,
63
- )
64
-
65
-
66
- def declare_exchange() -> None:
67
- _declare(Exchange(name=EVENT_SOURCERY_EXCHANGE, type="headers"))
68
-
69
-
70
- def _publish(
71
- routing_key: str,
72
- message: str,
73
- exchange: str,
74
- headers: dict,
75
- ) -> None:
76
- with PoolFactory.get().acquire(block=True) as conn:
77
- producer = conn.Producer()
78
- producer.publish(
79
- message,
80
- exchange=exchange,
81
- routing_key=routing_key,
82
- headers=headers,
83
- content_type="application/json",
84
- )
85
-
86
-
87
- def _declare(queue_or_exchange: Exchange | Queue) -> None:
88
- with PoolFactory.get().acquire(block=True) as conn:
89
- queue_or_exchange(conn).declare()
90
-
91
-
92
- class InvalidSubscription(Exception):
93
- pass
94
-
95
-
96
- @dataclass(frozen=True)
97
- class Subscription:
98
- event_types: list[Type[Event]] = field(default_factory=list)
99
- stream_categories: list[str] = field(default_factory=list)
100
-
101
- def __post_init__(self) -> None:
102
- if len(self.event_types) == 0 and len(self.stream_categories) == 0:
103
- raise InvalidSubscription(
104
- "At least one event type or stream category needs to be specified"
105
- )
106
-
107
-
108
- def consume(
109
- subscription: Subscription,
110
- listener: Callable[[Metadata, StreamId], None],
111
- ) -> None:
112
- queue = Queue(f"event_sourcery.{listener.__name__}", durable=True)
113
- _declare(queue)
114
-
115
- def callback(body: dict, message: Message) -> None:
116
- event_name = message.headers["event"]
117
- event_type = Event.__registry__.type_for_name(event_name)
118
-
119
- stream_id = StreamId(
120
- from_hex=message.headers["stream_id"],
121
- name=message.headers["stream_name"],
122
- )
123
- metadata = Metadata[event_type](**body) # type: ignore
124
-
125
- try:
126
- listener(metadata, stream_id)
127
- except Exception: # TODO: DLQ, retries, error handling etc
128
- message.reject()
129
- raise
130
- else:
131
- message.ack()
132
-
133
- with PoolFactory.get().acquire(block=True) as conn:
134
- for event_type in subscription.event_types:
135
- queue(conn).bind_to(
136
- exchange=EVENT_SOURCERY_EXCHANGE,
137
- arguments={
138
- "event": event_name(event_type),
139
- },
140
- )
141
-
142
- for stream_category in subscription.stream_categories:
143
- queue(conn).bind_to(
144
- exchange=EVENT_SOURCERY_EXCHANGE,
145
- arguments={
146
- "stream_category": stream_category,
147
- },
148
- )
149
-
150
- with conn.Consumer([queue], callbacks=[callback]):
151
- while True:
152
- try:
153
- conn.drain_events()
154
- except KeyboardInterrupt:
155
- return
@@ -1,33 +0,0 @@
1
- from datetime import timedelta
2
- from typing import Iterator
3
-
4
- from event_sourcery.event_store import Position, RecordedRaw
5
- from event_sourcery.event_store.interfaces import SubscriptionStrategy
6
-
7
-
8
- class SqlAlchemySubscriptionStrategy(SubscriptionStrategy):
9
- def subscribe_to_all(
10
- self,
11
- start_from: Position,
12
- batch_size: int,
13
- timelimit: timedelta,
14
- ) -> Iterator[list[RecordedRaw]]:
15
- raise NotImplementedError
16
-
17
- def subscribe_to_category(
18
- self,
19
- start_from: Position,
20
- batch_size: int,
21
- timelimit: timedelta,
22
- category: str,
23
- ) -> Iterator[list[RecordedRaw]]:
24
- raise NotImplementedError
25
-
26
- def subscribe_to_events(
27
- self,
28
- start_from: Position,
29
- batch_size: int,
30
- timelimit: timedelta,
31
- events: list[str],
32
- ) -> Iterator[list[RecordedRaw]]:
33
- raise NotImplementedError