eventsourcing 9.4.0a7__tar.gz → 9.4.0b1__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.

Potentially problematic release.


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

Files changed (27) hide show
  1. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/PKG-INFO +2 -2
  2. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/application.py +22 -30
  3. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/cipher.py +3 -1
  4. eventsourcing-9.4.0b1/eventsourcing/dispatch.py +79 -0
  5. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/domain.py +373 -360
  6. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/interface.py +1 -1
  7. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/persistence.py +26 -28
  8. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/popo.py +5 -1
  9. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/postgres.py +174 -127
  10. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/projection.py +82 -26
  11. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/sqlite.py +5 -1
  12. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/system.py +14 -9
  13. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/tests/application.py +57 -49
  14. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/tests/domain.py +8 -6
  15. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/tests/persistence.py +170 -143
  16. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/tests/postgres_utils.py +12 -9
  17. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/utils.py +27 -17
  18. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/pyproject.toml +13 -3
  19. eventsourcing-9.4.0a7/eventsourcing/dispatch.py +0 -38
  20. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/AUTHORS +0 -0
  21. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/LICENSE +0 -0
  22. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/README.md +0 -0
  23. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/__init__.py +0 -0
  24. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/compressor.py +0 -0
  25. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/cryptography.py +0 -0
  26. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/py.typed +0 -0
  27. {eventsourcing-9.4.0a7 → eventsourcing-9.4.0b1}/eventsourcing/tests/__init__.py +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: eventsourcing
3
- Version: 9.4.0a7
3
+ Version: 9.4.0b1
4
4
  Summary: Event sourcing in Python
5
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
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 :: 3 - Alpha
10
+ Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: Intended Audience :: Education
13
13
  Classifier: Intended Audience :: Science/Research
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import contextlib
3
4
  import os
4
5
  from abc import ABC, abstractmethod
5
6
  from collections.abc import Iterable, Iterator, Sequence
@@ -26,6 +27,7 @@ from eventsourcing.domain import (
26
27
  DomainEventProtocol,
27
28
  EventSourcingError,
28
29
  MutableOrImmutableAggregate,
30
+ SDomainEvent,
29
31
  Snapshot,
30
32
  SnapshotProtocol,
31
33
  TDomainEvent,
@@ -43,7 +45,6 @@ from eventsourcing.persistence import (
43
45
  Notification,
44
46
  Recording,
45
47
  Tracking,
46
- TrackingRecorder,
47
48
  Transcoder,
48
49
  UUIDAsHex,
49
50
  )
@@ -95,10 +96,9 @@ class Cache(Generic[S, T]):
95
96
  return self.cache.pop(key)
96
97
  return self.cache[key]
97
98
 
98
- def put(self, key: S, value: T) -> T | None:
99
+ def put(self, key: S, value: T | None) -> None:
99
100
  if value is not None:
100
101
  self.cache[key] = value
101
- return None
102
102
 
103
103
 
104
104
  class LRUCache(Cache[S, T]):
@@ -153,7 +153,7 @@ class LRUCache(Cache[S, T]):
153
153
  return result
154
154
  raise KeyError
155
155
 
156
- def put(self, key: S, value: T) -> Any | None:
156
+ def put(self, key: S, value: T | None) -> Any | None:
157
157
  evicted_key = None
158
158
  evicted_value = None
159
159
  with self.lock:
@@ -354,19 +354,18 @@ class Repository:
354
354
  return aggregate
355
355
 
356
356
  def _use_fastforward_lock(self, aggregate_id: UUID) -> Lock:
357
+ lock: Lock | None = None
357
358
  with self._fastforward_locks_lock:
358
- try:
359
+ num_users = 0
360
+ with contextlib.suppress(KeyError):
359
361
  lock, num_users = self._fastforward_locks_inuse[aggregate_id]
360
- except KeyError:
361
- try:
362
+ if lock is None:
363
+ with contextlib.suppress(KeyError):
362
364
  lock = self._fastforward_locks_cache.get(aggregate_id, evict=True)
363
- except KeyError:
364
- lock = Lock()
365
- finally:
366
- num_users = 0
367
- finally:
368
- num_users += 1
369
- self._fastforward_locks_inuse[aggregate_id] = (lock, num_users)
365
+ if lock is None:
366
+ lock = Lock()
367
+ num_users += 1
368
+ self._fastforward_locks_inuse[aggregate_id] = (lock, num_users)
370
369
  return lock
371
370
 
372
371
  def _disuse_fastforward_lock(self, aggregate_id: UUID) -> None:
@@ -612,12 +611,10 @@ class Application:
612
611
  name = "Application"
613
612
  env: ClassVar[dict[str, str]] = {}
614
613
  is_snapshotting_enabled: bool = False
615
- snapshotting_intervals: ClassVar[
616
- dict[type[MutableOrImmutableAggregate], int] | None
617
- ] = None
614
+ snapshotting_intervals: ClassVar[dict[type[MutableOrImmutableAggregate], int]] = {}
618
615
  snapshotting_projectors: ClassVar[
619
- dict[type[MutableOrImmutableAggregate], ProjectorFunction[Any, Any]] | None
620
- ] = None
616
+ dict[type[MutableOrImmutableAggregate], ProjectorFunction[Any, Any]]
617
+ ] = {}
621
618
  snapshot_class: type[SnapshotProtocol] = Snapshot
622
619
  log_section_size = 10
623
620
  notify_topics: Sequence[str] = []
@@ -690,9 +687,7 @@ class Application:
690
687
  _env.update(env)
691
688
  return Environment(name, _env)
692
689
 
693
- def construct_factory(
694
- self, env: Environment
695
- ) -> InfrastructureFactory[TrackingRecorder]:
690
+ def construct_factory(self, env: Environment) -> InfrastructureFactory:
696
691
  """
697
692
  Constructs an :class:`~eventsourcing.persistence.InfrastructureFactory`
698
693
  for use by the application.
@@ -821,12 +816,9 @@ class Application:
821
816
  continue
822
817
  interval = self.snapshotting_intervals.get(type(aggregate))
823
818
  if interval is not None and event.originator_version % interval == 0:
824
- if (
825
- self.snapshotting_projectors
826
- and type(aggregate) in self.snapshotting_projectors
827
- ):
819
+ try:
828
820
  projector_func = self.snapshotting_projectors[type(aggregate)]
829
- else:
821
+ except KeyError:
830
822
  projector_func = project_aggregate
831
823
  if projector_func is project_aggregate and not isinstance(
832
824
  event, CanMutateProtocol
@@ -951,10 +943,10 @@ class EventSourcedLog(Generic[TDomainEvent]):
951
943
 
952
944
  def _trigger_event(
953
945
  self,
954
- logged_cls: type[T] | None,
946
+ logged_cls: type[SDomainEvent],
955
947
  next_originator_version: int | None = None,
956
948
  **kwargs: Any,
957
- ) -> T:
949
+ ) -> SDomainEvent:
958
950
  """
959
951
  Constructs and returns a new log event.
960
952
  """
@@ -965,7 +957,7 @@ class EventSourcedLog(Generic[TDomainEvent]):
965
957
  else:
966
958
  next_originator_version = last_logged.originator_version + 1
967
959
 
968
- return logged_cls( # type: ignore
960
+ return logged_cls(
969
961
  originator_id=self.originator_id,
970
962
  originator_version=next_originator_version,
971
963
  timestamp=datetime_now_with_tzinfo(),
@@ -5,7 +5,9 @@ from base64 import b64decode, b64encode
5
5
  from typing import TYPE_CHECKING
6
6
 
7
7
  from Crypto.Cipher import AES
8
- from Crypto.Cipher._mode_gcm import GcmMode
8
+ from Crypto.Cipher._mode_gcm import (
9
+ GcmMode, # pyright: ignore [reportPrivateImportUsage]
10
+ )
9
11
  from Crypto.Cipher.AES import key_size
10
12
 
11
13
  from eventsourcing.persistence import Cipher
@@ -0,0 +1,79 @@
1
+ from __future__ import annotations
2
+
3
+ import functools
4
+ from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar, cast, overload
5
+
6
+ _T = TypeVar("_T")
7
+ _S = TypeVar("_S")
8
+
9
+ if TYPE_CHECKING:
10
+
11
+ class _singledispatchmethod(functools.singledispatchmethod[_T]): # noqa: N801
12
+ pass
13
+
14
+ else:
15
+
16
+ class _singledispatchmethod( # noqa: N801
17
+ functools.singledispatchmethod, Generic[_T]
18
+ ):
19
+ pass
20
+
21
+
22
+ class singledispatchmethod(_singledispatchmethod[_T]): # noqa: N801
23
+ def __init__(self, func: Callable[..., _T]) -> None:
24
+ super().__init__(func)
25
+ self.deferred_registrations: list[
26
+ tuple[type[Any] | Callable[..., _T], Callable[..., _T] | None]
27
+ ] = []
28
+
29
+ @overload
30
+ def register(
31
+ self, cls: type[Any], method: None = None
32
+ ) -> Callable[[Callable[..., _T]], Callable[..., _T]]: ... # pragma: no cover
33
+ @overload
34
+ def register(
35
+ self, cls: Callable[..., _T], method: None = None
36
+ ) -> Callable[..., _T]: ... # pragma: no cover
37
+
38
+ @overload
39
+ def register(
40
+ self, cls: type[Any], method: Callable[..., _T]
41
+ ) -> Callable[..., _T]: ... # pragma: no cover
42
+
43
+ def register(
44
+ self,
45
+ cls: type[Any] | Callable[..., _T],
46
+ method: Callable[..., _T] | None = None,
47
+ ) -> Callable[[Callable[..., _T]], Callable[..., _T]] | Callable[..., _T]:
48
+ """generic_method.register(cls, func) -> func
49
+
50
+ Registers a new implementation for the given *cls* on a *generic_method*.
51
+ """
52
+ if isinstance(cls, (classmethod, staticmethod)):
53
+ first_annotation = {}
54
+ for k, v in cls.__func__.__annotations__.items():
55
+ first_annotation[k] = v
56
+ break
57
+ cls.__annotations__ = first_annotation
58
+
59
+ # for globals in typing.get_type_hints() in Python 3.8 and 3.9
60
+ if not hasattr(cls, "__wrapped__"):
61
+ cls.__dict__["__wrapped__"] = cls.__func__
62
+ # cls.__wrapped__ = cls.__func__
63
+
64
+ try:
65
+ return self.dispatcher.register(cast(type[Any], cls), func=method)
66
+ except NameError:
67
+ self.deferred_registrations.append(
68
+ (cls, method) # pyright: ignore [reportArgumentType]
69
+ )
70
+ # TODO: Fix this....
71
+ return method or cls # pyright: ignore [reportReturnType]
72
+
73
+ def __get__(self, obj: _S, cls: type[_S] | None = None) -> Callable[..., _T]:
74
+ for registered_cls, registered_method in self.deferred_registrations:
75
+ self.dispatcher.register(
76
+ cast(type[Any], registered_cls), func=registered_method
77
+ )
78
+ self.deferred_registrations = []
79
+ return super().__get__(obj, cls=cls)