eventsourcing 9.4.0b4__py3-none-any.whl → 9.4.2__py3-none-any.whl

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.

Potentially problematic release.


This version of eventsourcing might be problematic. Click here for more details.

eventsourcing/domain.py CHANGED
@@ -990,7 +990,7 @@ class MetaAggregate(EventsourcingType, Generic[TAggregate], type):
990
990
  class BaseAggregate(metaclass=MetaAggregate):
991
991
  """Base class for aggregates."""
992
992
 
993
- INITIAL_VERSION = 1
993
+ INITIAL_VERSION: int = 1
994
994
 
995
995
  @staticmethod
996
996
  def create_id(*_: Any, **__: Any) -> UUID:
eventsourcing/postgres.py CHANGED
@@ -766,6 +766,10 @@ class PostgresFactory(InfrastructureFactory[PostgresTrackingRecorder]):
766
766
  "in environment with key "
767
767
  f"'{self.POSTGRES_DBNAME}'"
768
768
  )
769
+ # TODO: Indicate both keys here, also for other environment variables.
770
+ # ) + " or ".join(
771
+ # [f"'{key}'" for key in self.env.create_keys(self.POSTGRES_DBNAME)]
772
+ # )
769
773
  raise OSError(msg)
770
774
 
771
775
  host = self.env.get(self.POSTGRES_HOST)
@@ -2,19 +2,22 @@ from __future__ import annotations
2
2
 
3
3
  import contextlib
4
4
  import os
5
+ import threading
5
6
  import weakref
6
7
  from abc import ABC, abstractmethod
7
8
  from collections.abc import Iterator, Sequence
8
9
  from threading import Event, Thread
9
10
  from traceback import format_exc
10
- from typing import TYPE_CHECKING, Any, Generic, TypeVar
11
+ from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar
11
12
  from warnings import warn
12
13
 
13
- from eventsourcing.application import Application
14
+ from eventsourcing.application import Application, ProcessingEvent
14
15
  from eventsourcing.dispatch import singledispatchmethod
15
16
  from eventsourcing.domain import DomainEventProtocol
16
17
  from eventsourcing.persistence import (
17
18
  InfrastructureFactory,
19
+ IntegrityError,
20
+ ProcessRecorder,
18
21
  Tracking,
19
22
  TrackingRecorder,
20
23
  TTrackingRecorder,
@@ -43,7 +46,7 @@ class ApplicationSubscription(Iterator[tuple[DomainEventProtocol, Tracking]]):
43
46
  topics: Sequence[str] = (),
44
47
  ):
45
48
  """
46
- Starts subscription to application's stored events using application's recorder.
49
+ Starts a subscription to application's recorder.
47
50
  """
48
51
  self.name = app.name
49
52
  self.recorder = app.recorder
@@ -51,7 +54,7 @@ class ApplicationSubscription(Iterator[tuple[DomainEventProtocol, Tracking]]):
51
54
  self.subscription = self.recorder.subscribe(gt=gt, topics=topics)
52
55
 
53
56
  def stop(self) -> None:
54
- """Stops the stored event subscription."""
57
+ """Stops the subscription to the application's recorder."""
55
58
  self.subscription.stop()
56
59
 
57
60
  def __enter__(self) -> Self:
@@ -67,10 +70,11 @@ class ApplicationSubscription(Iterator[tuple[DomainEventProtocol, Tracking]]):
67
70
  return self
68
71
 
69
72
  def __next__(self) -> tuple[DomainEventProtocol, Tracking]:
70
- """Returns the next stored event from the stored event subscription.
71
- Constructs a tracking object that identifies the position of
72
- the event in the application sequence, and reconstructs a domain
73
- event object from the stored event object.
73
+ """Returns the next stored event from subscription to the application's
74
+ recorder. Constructs a tracking object that identifies the position of
75
+ the event in the application sequence. Constructs a domain event object
76
+ from the stored event object using the application's mapper. Returns a
77
+ tuple of the domain event object and the tracking object.
74
78
  """
75
79
  notification = next(self.subscription)
76
80
  tracking = Tracking(self.name, notification.id)
@@ -91,14 +95,18 @@ class Projection(ABC, Generic[TTrackingRecorder]):
91
95
  """
92
96
  topics: tuple[str, ...] = ()
93
97
  """
94
- Filter events in database when subscribing to an application.
98
+ Event topics, used to filter events in database when subscribing to an application.
95
99
  """
96
100
 
101
+ def __init_subclass__(cls, **kwargs: Any) -> None:
102
+ if "name" not in cls.__dict__:
103
+ cls.name = cls.__name__
104
+
97
105
  def __init__(
98
106
  self,
99
107
  view: TTrackingRecorder,
100
108
  ):
101
- """Initialises a projection instance."""
109
+ """Initialises the view property with the given view argument."""
102
110
  self._view = view
103
111
 
104
112
  @property
@@ -114,63 +122,130 @@ class Projection(ABC, Generic[TTrackingRecorder]):
114
122
  """Process a domain event and track it."""
115
123
 
116
124
 
125
+ class EventSourcedProjection(Application, ABC):
126
+ """Extends the :py:class:`~eventsourcing.application.Application` class
127
+ by using a process recorder as its application recorder, and by
128
+ processing domain events through its :py:func:`policy` method.
129
+ """
130
+
131
+ topics: ClassVar[Sequence[str]] = ()
132
+
133
+ def __init__(self, env: EnvType | None = None) -> None:
134
+ super().__init__(env)
135
+ self.recorder: ProcessRecorder
136
+ self.processing_lock = threading.Lock()
137
+
138
+ def construct_recorder(self) -> ProcessRecorder:
139
+ """Constructs and returns a :class:`~eventsourcing.persistence.ProcessRecorder`
140
+ for the application to use as its application recorder.
141
+ """
142
+ return self.factory.process_recorder()
143
+
144
+ def process_event(
145
+ self, domain_event: DomainEventProtocol, tracking: Tracking
146
+ ) -> None:
147
+ """Calls :func:`~eventsourcing.system.Follower.policy` method with
148
+ the given :class:`~eventsourcing.domain.AggregateEvent` and a
149
+ new :class:`~eventsourcing.application.ProcessingEvent` created from
150
+ the given :class:`~eventsourcing.persistence.Tracking` object.
151
+
152
+ The policy will collect any new aggregate events on the process
153
+ event object.
154
+
155
+ After the policy method returns, the process event object will
156
+ then be recorded by calling
157
+ :func:`~eventsourcing.application.Application.record`, which
158
+ will return new notifications.
159
+
160
+ After calling
161
+ :func:`~eventsourcing.application.Application.take_snapshots`,
162
+ the new notifications are passed to the
163
+ :func:`~eventsourcing.application.Application.notify` method.
164
+ """
165
+ processing_event = ProcessingEvent(tracking=tracking)
166
+ self.policy(domain_event, processing_event)
167
+ try:
168
+ recordings = self._record(processing_event)
169
+ except IntegrityError:
170
+ if self.recorder.has_tracking_id(
171
+ tracking.application_name,
172
+ tracking.notification_id,
173
+ ):
174
+ pass
175
+ else:
176
+ raise
177
+ else:
178
+ self._take_snapshots(processing_event)
179
+ self.notify(processing_event.events)
180
+ self._notify(recordings)
181
+
182
+ @singledispatchmethod
183
+ def policy(
184
+ self,
185
+ domain_event: DomainEventProtocol,
186
+ processing_event: ProcessingEvent,
187
+ ) -> None:
188
+ """Abstract domain event processing policy method. Must be
189
+ implemented by event processing applications. When
190
+ processing the given domain event, event processing
191
+ applications must use the :func:`~ProcessingEvent.collect_events`
192
+ method of the given :py:class:`~ProcessingEvent` object (not
193
+ the application's :func:`~eventsourcing.application.Application.save`
194
+ method) so that the new domain events will be recorded atomically
195
+ and uniquely with tracking information about the position of the processed
196
+ event in its application sequence.
197
+ """
198
+
199
+
117
200
  TApplication = TypeVar("TApplication", bound=Application)
201
+ TEventSourcedProjection = TypeVar(
202
+ "TEventSourcedProjection", bound=EventSourcedProjection
203
+ )
118
204
 
119
205
 
120
- class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
206
+ class BaseProjectionRunner(Generic[TApplication]):
121
207
  def __init__(
122
208
  self,
123
209
  *,
210
+ projection: EventSourcedProjection | Projection[Any],
124
211
  application_class: type[TApplication],
125
- projection_class: type[Projection[TTrackingRecorder]],
126
- view_class: type[TTrackingRecorder],
212
+ tracking_recorder: TrackingRecorder,
213
+ topics: Sequence[str],
127
214
  env: EnvType | None = None,
128
- ):
129
- """Constructs application from given application class with given environment.
130
- Also constructs a materialised view from given class using an infrastructure
131
- factory constructed with an environment named after the projection. Also
132
- constructs a projection with the constructed materialised view object.
133
- Starts a subscription to application and, in a separate event-processing
134
- thread, calls projection's process_event() method for each event and tracking
135
- object pair received from the subscription.
136
- """
215
+ ) -> None:
216
+ self._projection = projection
137
217
  self._is_interrupted = Event()
138
218
  self._has_called_stop = False
139
219
 
220
+ # Construct the application.
140
221
  self.app: TApplication = application_class(env)
141
222
 
142
- self.view = (
143
- InfrastructureFactory[TTrackingRecorder]
144
- .construct(
145
- env=self._construct_env(
146
- name=projection_class.name or projection_class.__name__, env=env
147
- )
148
- )
149
- .tracking_recorder(view_class)
150
- )
223
+ self._tracking_recorder = tracking_recorder
151
224
 
152
- self.projection = projection_class(
153
- view=self.view,
154
- )
155
- self.subscription = ApplicationSubscription(
225
+ # Subscribe to the application.
226
+ self._subscription = ApplicationSubscription(
156
227
  app=self.app,
157
- gt=self.view.max_tracking_id(self.app.name),
158
- topics=self.projection.topics,
228
+ gt=self._tracking_recorder.max_tracking_id(self.app.name),
229
+ topics=topics,
159
230
  )
231
+
232
+ # Start a thread to stop the subscription when the runner is interrupted.
160
233
  self._thread_error: BaseException | None = None
161
234
  self._stop_thread = Thread(
162
235
  target=self._stop_subscription_when_stopping,
163
236
  kwargs={
164
- "subscription": self.subscription,
237
+ "subscription": self._subscription,
165
238
  "is_stopping": self._is_interrupted,
166
239
  },
167
240
  )
168
241
  self._stop_thread.start()
242
+
243
+ # Start a thread to iterate over the subscription.
169
244
  self._processing_thread = Thread(
170
245
  target=self._process_events_loop,
171
246
  kwargs={
172
- "subscription": self.subscription,
173
- "projection": self.projection,
247
+ "subscription": self._subscription,
248
+ "projection": self._projection,
174
249
  "is_stopping": self._is_interrupted,
175
250
  "runner": weakref.ref(self),
176
251
  },
@@ -181,7 +256,8 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
181
256
  def is_interrupted(self) -> Event:
182
257
  return self._is_interrupted
183
258
 
184
- def _construct_env(self, name: str, env: EnvType | None = None) -> Environment:
259
+ @staticmethod
260
+ def _construct_env(name: str, env: EnvType | None = None) -> Environment:
185
261
  """Constructs environment from which projection will be configured."""
186
262
  _env: dict[str, str] = {}
187
263
  _env.update(os.environ)
@@ -211,10 +287,11 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
211
287
  @staticmethod
212
288
  def _process_events_loop(
213
289
  subscription: ApplicationSubscription,
214
- projection: Projection[TrackingRecorder],
290
+ projection: EventSourcedProjection | Projection[Any],
215
291
  is_stopping: Event,
216
292
  runner: weakref.ReferenceType[ProjectionRunner[Application, TrackingRecorder]],
217
293
  ) -> None:
294
+ """Iterates over the subscription and calls process_event()."""
218
295
  try:
219
296
  with subscription:
220
297
  for domain_event, tracking in subscription:
@@ -251,8 +328,8 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
251
328
  object that is greater than or equal to the given notification ID.
252
329
  """
253
330
  try:
254
- self.projection.view.wait(
255
- application_name=self.subscription.name,
331
+ self._tracking_recorder.wait(
332
+ application_name=self.app.name,
256
333
  notification_id=notification_id,
257
334
  timeout=timeout,
258
335
  interrupt=self._is_interrupted,
@@ -288,3 +365,64 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
288
365
  """Calls stop()."""
289
366
  with contextlib.suppress(AttributeError):
290
367
  self.stop()
368
+
369
+
370
+ class ProjectionRunner(
371
+ BaseProjectionRunner[TApplication], Generic[TApplication, TTrackingRecorder]
372
+ ):
373
+ def __init__(
374
+ self,
375
+ *,
376
+ application_class: type[TApplication],
377
+ projection_class: type[Projection[TTrackingRecorder]],
378
+ view_class: type[TTrackingRecorder],
379
+ env: EnvType | None = None,
380
+ ):
381
+ """Constructs application from given application class with given environment.
382
+ Also constructs a materialised view from given class using an infrastructure
383
+ factory constructed with an environment named after the projection. Also
384
+ constructs a projection with the constructed materialised view object.
385
+ Starts a subscription to application and, in a separate event-processing
386
+ thread, calls projection's process_event() method for each event and tracking
387
+ object pair received from the subscription.
388
+ """
389
+ # Construct the materialised view using an infrastructure factory.
390
+ self.view = (
391
+ InfrastructureFactory[TTrackingRecorder]
392
+ .construct(env=self._construct_env(name=projection_class.name, env=env))
393
+ .tracking_recorder(view_class)
394
+ )
395
+
396
+ # Construct the projection using the materialised view.
397
+ self.projection = projection_class(view=self.view)
398
+
399
+ super().__init__(
400
+ projection=self.projection,
401
+ application_class=application_class,
402
+ tracking_recorder=self.view,
403
+ topics=self.projection.topics,
404
+ env=env,
405
+ )
406
+
407
+
408
+ class EventSourcedProjectionRunner(
409
+ BaseProjectionRunner[TApplication], Generic[TApplication, TEventSourcedProjection]
410
+ ):
411
+ def __init__(
412
+ self,
413
+ *,
414
+ application_class: type[TApplication],
415
+ projection_class: type[TEventSourcedProjection],
416
+ env: EnvType | None = None,
417
+ ):
418
+ self.projection: TEventSourcedProjection = projection_class(
419
+ env=self._construct_env(name=projection_class.name, env=env)
420
+ )
421
+
422
+ super().__init__(
423
+ projection=self.projection,
424
+ application_class=application_class,
425
+ tracking_recorder=self.projection.recorder,
426
+ topics=self.projection.topics,
427
+ env=env,
428
+ )
eventsourcing/sqlite.py CHANGED
@@ -472,6 +472,7 @@ class SQLiteApplicationRecorder(
472
472
  def subscribe(
473
473
  self, gt: int | None = None, topics: Sequence[str] = ()
474
474
  ) -> Subscription[ApplicationRecorder]:
475
+ """This method is not implemented on this class."""
475
476
  msg = f"The {type(self).__qualname__} recorder does not support subscriptions"
476
477
  raise NotImplementedError(msg)
477
478
 
eventsourcing/system.py CHANGED
@@ -6,26 +6,25 @@ import traceback
6
6
  from abc import ABC, abstractmethod
7
7
  from collections import defaultdict
8
8
  from queue import Full, Queue
9
+ from types import FrameType, ModuleType
9
10
  from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union, cast
10
11
 
12
+ from eventsourcing.projection import EventSourcedProjection
13
+
11
14
  if TYPE_CHECKING:
12
15
  from collections.abc import Iterable, Iterator, Sequence
13
- from types import FrameType, ModuleType
14
16
 
15
17
  from typing_extensions import Self
16
18
 
17
19
  from eventsourcing.application import (
18
20
  Application,
19
21
  NotificationLog,
20
- ProcessingEvent,
21
22
  ProgrammingError,
22
23
  Section,
23
24
  TApplication,
24
25
  )
25
- from eventsourcing.dispatch import singledispatchmethod
26
26
  from eventsourcing.domain import DomainEventProtocol, MutableOrImmutableAggregate
27
27
  from eventsourcing.persistence import (
28
- IntegrityError,
29
28
  Mapper,
30
29
  Notification,
31
30
  ProcessRecorder,
@@ -52,29 +51,25 @@ class RecordingEvent:
52
51
  ConvertingJob = Optional[Union[RecordingEvent, list[Notification]]]
53
52
 
54
53
 
55
- class Follower(Application):
56
- """Extends the :class:`~eventsourcing.application.Application` class
57
- by using a process recorder as its application recorder, by keeping
58
- track of the applications it is following, and pulling and processing
59
- new domain event notifications through its :func:`policy` method.
54
+ class Follower(EventSourcedProjection):
55
+ """Extends the :class:`~eventsourcing.projection.EventSourcedProjection` class
56
+ by pulling notification objects from its notification log readers, by converting
57
+ the notification objects to domain events and tracking objects and by processing
58
+ the reconstructed domain event objects.
60
59
  """
61
60
 
62
- follow_topics: ClassVar[Sequence[str]] = []
63
61
  pull_section_size = 10
64
62
 
63
+ def __init_subclass__(cls, **kwargs: Any) -> None:
64
+ super().__init_subclass__(**kwargs)
65
+ # for backwards compatibility, set "topics" if has "follow_topics".
66
+ cls.topics = getattr(cls, "follow_topics", cls.topics)
67
+
65
68
  def __init__(self, env: EnvType | None = None) -> None:
66
69
  super().__init__(env)
67
70
  self.readers: dict[str, NotificationLogReader] = {}
68
71
  self.mappers: dict[str, Mapper] = {}
69
- self.recorder: ProcessRecorder
70
72
  self.is_threading_enabled = False
71
- self.processing_lock = threading.Lock()
72
-
73
- def construct_recorder(self) -> ProcessRecorder:
74
- """Constructs and returns a :class:`~eventsourcing.persistence.ProcessRecorder`
75
- for the application to use as its application recorder.
76
- """
77
- return self.factory.process_recorder()
78
73
 
79
74
  def follow(self, name: str, log: NotificationLog) -> None:
80
75
  """Constructs a notification log reader and a mapper for
@@ -107,6 +102,12 @@ class Follower(Application):
107
102
  ):
108
103
  self.process_event(domain_event, tracking)
109
104
 
105
+ def process_event(
106
+ self, domain_event: DomainEventProtocol, tracking: Tracking
107
+ ) -> None:
108
+ with self.processing_lock:
109
+ super().process_event(domain_event, tracking)
110
+
110
111
  def pull_notifications(
111
112
  self,
112
113
  leader_name: str,
@@ -121,15 +122,15 @@ class Follower(Application):
121
122
  return self.readers[leader_name].select(
122
123
  start=start,
123
124
  stop=stop,
124
- topics=self.follow_topics,
125
+ topics=self.topics,
125
126
  inclusive_of_start=inclusive_of_start,
126
127
  )
127
128
 
128
129
  def filter_received_notifications(
129
130
  self, notifications: list[Notification]
130
131
  ) -> list[Notification]:
131
- if self.follow_topics:
132
- return [n for n in notifications if n.topic in self.follow_topics]
132
+ if self.topics:
133
+ return [n for n in notifications if n.topic in self.topics]
133
134
  return notifications
134
135
 
135
136
  def convert_notifications(
@@ -151,64 +152,6 @@ class Follower(Application):
151
152
  processing_jobs.append((domain_event, tracking))
152
153
  return processing_jobs
153
154
 
154
- # @retry(IntegrityError, max_attempts=50000, wait=0.01)
155
- def process_event(
156
- self, domain_event: DomainEventProtocol, tracking: Tracking
157
- ) -> None:
158
- """Calls :func:`~eventsourcing.system.Follower.policy` method with
159
- the given :class:`~eventsourcing.domain.AggregateEvent` and a
160
- new :class:`~eventsourcing.application.ProcessingEvent` created from
161
- the given :class:`~eventsourcing.persistence.Tracking` object.
162
-
163
- The policy will collect any new aggregate events on the process
164
- event object.
165
-
166
- After the policy method returns, the process event object will
167
- then be recorded by calling
168
- :func:`~eventsourcing.application.Application.record`, which
169
- will return new notifications.
170
-
171
- After calling
172
- :func:`~eventsourcing.application.Application.take_snapshots`,
173
- the new notifications are passed to the
174
- :func:`~eventsourcing.application.Application.notify` method.
175
- """
176
- processing_event = ProcessingEvent(tracking=tracking)
177
- with self.processing_lock:
178
- self.policy(domain_event, processing_event)
179
- try:
180
- recordings = self._record(processing_event)
181
- except IntegrityError:
182
- if self.recorder.has_tracking_id(
183
- tracking.application_name,
184
- tracking.notification_id,
185
- ):
186
- pass
187
- else:
188
- raise
189
- else:
190
- self._take_snapshots(processing_event)
191
- self.notify(processing_event.events)
192
- self._notify(recordings)
193
-
194
- @singledispatchmethod
195
- def policy(
196
- self,
197
- domain_event: DomainEventProtocol,
198
- processing_event: ProcessingEvent,
199
- ) -> None:
200
- """Abstract domain event processing policy method. Must be
201
- implemented by event processing applications. When
202
- processing the given domain event, event processing
203
- applications must use the :func:`~ProcessingEvent.collect_events`
204
- method of the given process event object (instead of
205
- the application's :func:`~eventsourcing.application.Application.save`
206
- method) to collect pending events from changed aggregates,
207
- so that the new domain events will be recorded atomically
208
- with tracking information about the position of the given
209
- domain event's notification.
210
- """
211
-
212
155
 
213
156
  class RecordingEventReceiver(ABC):
214
157
  """Abstract base class for objects that may receive recording events."""
@@ -279,8 +222,8 @@ class System:
279
222
  pipes: Iterable[Iterable[type[Application]]],
280
223
  ):
281
224
  # Remember the caller frame's module, so that we might identify a topic.
282
- caller_frame = cast("FrameType", inspect.currentframe()).f_back
283
- module = cast("ModuleType", inspect.getmodule(caller_frame))
225
+ caller_frame = cast(FrameType, inspect.currentframe()).f_back
226
+ module = cast(ModuleType, inspect.getmodule(caller_frame))
284
227
  type(self).__caller_modules[id(self)] = module # noqa: SLF001
285
228
 
286
229
  # Build nodes and edges.
@@ -629,9 +572,9 @@ class NewSingleThreadedRunner(Runner, RecordingEventReceiver):
629
572
  follower = self.apps[follower_name]
630
573
  assert isinstance(follower, Follower)
631
574
  if (
632
- follower.follow_topics
575
+ follower.topics
633
576
  and recording.notification.topic
634
- not in follower.follow_topics
577
+ not in follower.topics
635
578
  ):
636
579
  continue
637
580
  follower.process_event(
@@ -1076,9 +1019,8 @@ class ConvertingThread(threading.Thread):
1076
1019
  recording_event = recording_event_or_notifications
1077
1020
  for recording in recording_event.recordings:
1078
1021
  if (
1079
- self.follower.follow_topics
1080
- and recording.notification.topic
1081
- not in self.follower.follow_topics
1022
+ self.follower.topics
1023
+ and recording.notification.topic not in self.follower.topics
1082
1024
  ):
1083
1025
  continue
1084
1026
  tracking = Tracking(
@@ -450,8 +450,8 @@ class ApplicationRecorderTestCase(TestCase, ABC, Generic[_TApplicationRecorder])
450
450
  num_writers = 10
451
451
  num_writes_per_writer = 100
452
452
  num_events_per_write = 100
453
- reader_sleep = 0.0
454
- writer_sleep = 0.0
453
+ reader_sleep = 0.0001
454
+ writer_sleep = 0.0001
455
455
 
456
456
  def insert_events() -> None:
457
457
  thread_id = get_ident()
@@ -1,33 +1,32 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: eventsourcing
3
- Version: 9.4.0b4
3
+ Version: 9.4.2
4
4
  Summary: Event sourcing in Python
5
- License: BSD 3-Clause
5
+ License: BSD-3-Clause
6
6
  Keywords: event sourcing,event store,domain driven design,domain-driven design,ddd,cqrs,cqs
7
7
  Author: John Bywater
8
8
  Author-email: john.bywater@appropriatesoftware.net
9
- Requires-Python: >=3.9, !=2.7.*, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*, !=3.8.*
10
- Classifier: Development Status :: 4 - Beta
9
+ Requires-Python: >=3.9.2
10
+ Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: Intended Audience :: Education
13
13
  Classifier: Intended Audience :: Science/Research
14
14
  Classifier: License :: OSI Approved :: BSD License
15
- Classifier: License :: Other/Proprietary License
16
15
  Classifier: Operating System :: OS Independent
17
- Classifier: Programming Language :: Python
18
16
  Classifier: Programming Language :: Python :: 3
19
17
  Classifier: Programming Language :: Python :: 3.10
20
18
  Classifier: Programming Language :: Python :: 3.11
21
19
  Classifier: Programming Language :: Python :: 3.12
22
20
  Classifier: Programming Language :: Python :: 3.13
23
21
  Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python
24
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
24
  Provides-Extra: crypto
26
25
  Provides-Extra: cryptography
27
26
  Provides-Extra: postgres
28
- Requires-Dist: cryptography (>=44.0,<44.1) ; extra == "cryptography"
29
- Requires-Dist: psycopg[pool] (<=3.2.99999) ; extra == "postgres"
30
- Requires-Dist: pycryptodome (>=3.22,<3.23) ; extra == "crypto"
27
+ Requires-Dist: cryptography (>=44.0,<45.0) ; extra == "cryptography"
28
+ Requires-Dist: psycopg[pool] (>=3.2,<3.3) ; extra == "postgres"
29
+ Requires-Dist: pycryptodome (>=3.22,<4.0) ; extra == "crypto"
31
30
  Requires-Dist: typing_extensions
32
31
  Project-URL: Homepage, https://github.com/pyeventsourcing/eventsourcing
33
32
  Project-URL: Repository, https://github.com/pyeventsourcing/eventsourcing
@@ -43,7 +42,10 @@ Description-Content-Type: text/markdown
43
42
 
44
43
  # Event Sourcing in Python
45
44
 
46
- A library for event sourcing in Python.
45
+ This project is a comprehensive Python library for implementing event sourcing, a design pattern where all
46
+ changes to application state are stored as a sequence of events. This library provides a solid foundation
47
+ for building event-sourced applications in Python, with a focus on reliability, performance, and developer
48
+ experience. Please [read the docs](https://eventsourcing.readthedocs.io/). See also [extension projects](https://github.com/pyeventsourcing).
47
49
 
48
50
  *"totally amazing and a pleasure to use"*
49
51
 
@@ -51,8 +53,6 @@ A library for event sourcing in Python.
51
53
 
52
54
  *"a huge help and time saver"*
53
55
 
54
- Please [read the docs](https://eventsourcing.readthedocs.io/). See also [extension projects](https://github.com/pyeventsourcing).
55
-
56
56
 
57
57
  ## Installation
58
58
 
@@ -4,23 +4,23 @@ eventsourcing/cipher.py,sha256=ulTBtX5K9ejRAkdUaUbdIaj4H7anYwDOi7JxOolj2uo,3295
4
4
  eventsourcing/compressor.py,sha256=qEYWvsUXFLyhKgfuv-HGNJ6VF4sRw4z0IxbNW9ukOfc,385
5
5
  eventsourcing/cryptography.py,sha256=aFZLlJxxSb5seVbh94-T8FA_RIGOe-VFu5SJrbOnwUU,2969
6
6
  eventsourcing/dispatch.py,sha256=j03cIVPziq6LFEgJxvQMMIPlixuZ4bB8ynXXdd_Tj8Q,2740
7
- eventsourcing/domain.py,sha256=bIELG5g8O4KhBBJMa9A0YeUe9EIuHul4LvCvGBuUhls,62962
7
+ eventsourcing/domain.py,sha256=2c33FfhVIBcUzhJa6TMhGPDwOma-wGiPHUL8RC8ZokQ,62967
8
8
  eventsourcing/interface.py,sha256=-VLoqcd9a0PXpD_Bv0LjCiG21xLREG6tXK6phgtShOw,5035
9
9
  eventsourcing/persistence.py,sha256=y_1o3LNi9tkOTqkvjgsGF4un4XPXEgxzt0Iwhk7UzEI,46340
10
10
  eventsourcing/popo.py,sha256=xZD6mig7bVwAoHe-UdraXvuu2iL5a8b2b41cEcBHlBU,9642
11
- eventsourcing/postgres.py,sha256=lcbLvHCiUCYyHeIn4Fh7RHEjrbdmIrZC9XwfTOH4t4Q,37544
12
- eventsourcing/projection.py,sha256=-iTNb8nFUKeKEle8lih-n8vp_G5F98W08XgJKFDMk8Y,9829
11
+ eventsourcing/postgres.py,sha256=LGEsfAN_cUOL6xX0NT4gxVcu2Btma95WLqtYcEzdPSA,37757
12
+ eventsourcing/projection.py,sha256=2qkagy2o1sts22LqndYZzsDV7AJK28oGkPrFH1ECnYM,15069
13
13
  eventsourcing/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- eventsourcing/sqlite.py,sha256=_9ENoxEY7hqHGhOS4HBgElibBXyHI2vJ5nUVAQuffD0,22016
15
- eventsourcing/system.py,sha256=3mbxQRFLg8wQ8ulThd6y0CKBjtmo6lR3gZf9ZFITNCI,46888
14
+ eventsourcing/sqlite.py,sha256=1DRQgDE1S7lz7Mnz9QH6WZ4luMjI9tof1YpH5qrK9u8,22076
15
+ eventsourcing/system.py,sha256=WCfuSc45A9A1fFO7zpDum_ddh4pU7x-vEcpVZ_ycAyE,44358
16
16
  eventsourcing/tests/__init__.py,sha256=FtOyuj-L-oSisYeByTIrnUw-XzsctSbq76XmjPy5fMc,102
17
17
  eventsourcing/tests/application.py,sha256=pAn9Cugp_1rjOtj_nruGhh7PxdrQWibDlrnOAlOKXwo,20614
18
18
  eventsourcing/tests/domain.py,sha256=yN-F6gMRumeX6nIXIcZGxAR3RrUslzmEMM8JksnkI8Q,3227
19
- eventsourcing/tests/persistence.py,sha256=DVSKySk86iRW-QKGgs-4ca1C956N1LoGhYEXXesBF6k,58648
19
+ eventsourcing/tests/persistence.py,sha256=jy_aMQwRGKQKohw8Ji7oBf1aFIdcHLHiSrHxH6yHvzw,58654
20
20
  eventsourcing/tests/postgres_utils.py,sha256=0ywklGp6cXZ5PmV8ANVkwSHsZZCl5zTmOk7iG-RmrCE,1548
21
21
  eventsourcing/utils.py,sha256=pOnczXzaE5q7UbQbPmgcpWaP660fsmfiDJs6Gmo8QCM,8558
22
- eventsourcing-9.4.0b4.dist-info/AUTHORS,sha256=8aHOM4UbNZcKlD-cHpFRcM6RWyCqtwtxRev6DeUgVRs,137
23
- eventsourcing-9.4.0b4.dist-info/LICENSE,sha256=CQEQzcZO8AWXL5i3hIo4yVKrYjh2FBz6hCM7kpXWpw4,1512
24
- eventsourcing-9.4.0b4.dist-info/METADATA,sha256=ue7kdW9BT-cgWYdlTkULnlBwCKxr1_xzPGyoVcrb7-I,9796
25
- eventsourcing-9.4.0b4.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
26
- eventsourcing-9.4.0b4.dist-info/RECORD,,
22
+ eventsourcing-9.4.2.dist-info/AUTHORS,sha256=8aHOM4UbNZcKlD-cHpFRcM6RWyCqtwtxRev6DeUgVRs,137
23
+ eventsourcing-9.4.2.dist-info/LICENSE,sha256=CQEQzcZO8AWXL5i3hIo4yVKrYjh2FBz6hCM7kpXWpw4,1512
24
+ eventsourcing-9.4.2.dist-info/METADATA,sha256=agR-sOEFNlfqTmHsaQ4rIyxFoIROU_K6B4fh0Lo5ZL8,9959
25
+ eventsourcing-9.4.2.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
26
+ eventsourcing-9.4.2.dist-info/RECORD,,