eventsourcing 9.2.21__tar.gz → 9.3.0a1__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 (156) hide show
  1. {eventsourcing-9.2.21/eventsourcing.egg-info → eventsourcing-9.3.0a1}/PKG-INFO +30 -13
  2. eventsourcing-9.3.0a1/eventsourcing/__init__.py +1 -0
  3. {eventsourcing-9.2.21 → eventsourcing-9.3.0a1}/eventsourcing/application.py +127 -132
  4. {eventsourcing-9.2.21 → eventsourcing-9.3.0a1}/eventsourcing/cipher.py +17 -12
  5. {eventsourcing-9.2.21 → eventsourcing-9.3.0a1}/eventsourcing/compressor.py +2 -0
  6. eventsourcing-9.3.0a1/eventsourcing/dispatch.py +38 -0
  7. {eventsourcing-9.2.21 → eventsourcing-9.3.0a1}/eventsourcing/domain.py +139 -144
  8. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate1/application.py +27 -0
  9. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate1/domainmodel.py +16 -0
  10. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate1/test_application.py +37 -0
  11. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate2/__init__.py +0 -0
  12. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate2/application.py +27 -0
  13. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate2/domainmodel.py +22 -0
  14. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate2/test_application.py +37 -0
  15. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate3/__init__.py +0 -0
  16. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate3/application.py +27 -0
  17. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate3/domainmodel.py +38 -0
  18. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate3/test_application.py +37 -0
  19. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate4/__init__.py +0 -0
  20. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate4/application.py +27 -0
  21. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate4/domainmodel.py +128 -0
  22. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate4/test_application.py +38 -0
  23. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate5/__init__.py +0 -0
  24. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate5/application.py +27 -0
  25. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate5/domainmodel.py +131 -0
  26. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate5/test_application.py +38 -0
  27. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate6/__init__.py +0 -0
  28. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate6/application.py +30 -0
  29. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate6/domainmodel.py +123 -0
  30. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate6/test_application.py +38 -0
  31. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate6a/__init__.py +0 -0
  32. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate6a/application.py +40 -0
  33. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate6a/domainmodel.py +149 -0
  34. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate6a/test_application.py +45 -0
  35. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate7/__init__.py +0 -0
  36. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate7/application.py +48 -0
  37. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate7/domainmodel.py +144 -0
  38. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate7/persistence.py +57 -0
  39. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate7/test_application.py +38 -0
  40. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate7/test_compression_and_encryption.py +45 -0
  41. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate7/test_snapshotting_intervals.py +67 -0
  42. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate7a/__init__.py +0 -0
  43. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate7a/application.py +56 -0
  44. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate7a/domainmodel.py +170 -0
  45. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate7a/test_application.py +46 -0
  46. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate7a/test_compression_and_encryption.py +45 -0
  47. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate8/__init__.py +0 -0
  48. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate8/application.py +47 -0
  49. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate8/domainmodel.py +65 -0
  50. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate8/persistence.py +57 -0
  51. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate8/test_application.py +37 -0
  52. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate8/test_compression_and_encryption.py +44 -0
  53. eventsourcing-9.3.0a1/eventsourcing/examples/aggregate8/test_snapshotting_intervals.py +38 -0
  54. eventsourcing-9.3.0a1/eventsourcing/examples/bankaccounts/__init__.py +0 -0
  55. eventsourcing-9.3.0a1/eventsourcing/examples/bankaccounts/application.py +70 -0
  56. eventsourcing-9.3.0a1/eventsourcing/examples/bankaccounts/domainmodel.py +56 -0
  57. eventsourcing-9.3.0a1/eventsourcing/examples/bankaccounts/test.py +173 -0
  58. eventsourcing-9.3.0a1/eventsourcing/examples/cargoshipping/__init__.py +0 -0
  59. eventsourcing-9.3.0a1/eventsourcing/examples/cargoshipping/application.py +126 -0
  60. eventsourcing-9.3.0a1/eventsourcing/examples/cargoshipping/domainmodel.py +330 -0
  61. eventsourcing-9.3.0a1/eventsourcing/examples/cargoshipping/interface.py +143 -0
  62. eventsourcing-9.3.0a1/eventsourcing/examples/cargoshipping/test.py +231 -0
  63. eventsourcing-9.3.0a1/eventsourcing/examples/contentmanagement/__init__.py +0 -0
  64. eventsourcing-9.3.0a1/eventsourcing/examples/contentmanagement/application.py +118 -0
  65. eventsourcing-9.3.0a1/eventsourcing/examples/contentmanagement/domainmodel.py +69 -0
  66. eventsourcing-9.3.0a1/eventsourcing/examples/contentmanagement/test.py +180 -0
  67. eventsourcing-9.3.0a1/eventsourcing/examples/contentmanagement/utils.py +26 -0
  68. eventsourcing-9.3.0a1/eventsourcing/examples/contentmanagementsystem/__init__.py +0 -0
  69. eventsourcing-9.3.0a1/eventsourcing/examples/contentmanagementsystem/application.py +54 -0
  70. eventsourcing-9.3.0a1/eventsourcing/examples/contentmanagementsystem/postgres.py +17 -0
  71. eventsourcing-9.3.0a1/eventsourcing/examples/contentmanagementsystem/sqlite.py +17 -0
  72. eventsourcing-9.3.0a1/eventsourcing/examples/contentmanagementsystem/system.py +14 -0
  73. eventsourcing-9.3.0a1/eventsourcing/examples/contentmanagementsystem/test_system.py +174 -0
  74. eventsourcing-9.3.0a1/eventsourcing/examples/searchablecontent/__init__.py +0 -0
  75. eventsourcing-9.3.0a1/eventsourcing/examples/searchablecontent/application.py +45 -0
  76. eventsourcing-9.3.0a1/eventsourcing/examples/searchablecontent/persistence.py +23 -0
  77. eventsourcing-9.3.0a1/eventsourcing/examples/searchablecontent/postgres.py +118 -0
  78. eventsourcing-9.3.0a1/eventsourcing/examples/searchablecontent/sqlite.py +136 -0
  79. eventsourcing-9.3.0a1/eventsourcing/examples/searchablecontent/test_application.py +111 -0
  80. eventsourcing-9.3.0a1/eventsourcing/examples/searchablecontent/test_recorder.py +69 -0
  81. eventsourcing-9.3.0a1/eventsourcing/examples/searchabletimestamps/__init__.py +0 -0
  82. eventsourcing-9.3.0a1/eventsourcing/examples/searchabletimestamps/application.py +32 -0
  83. eventsourcing-9.3.0a1/eventsourcing/examples/searchabletimestamps/persistence.py +20 -0
  84. eventsourcing-9.3.0a1/eventsourcing/examples/searchabletimestamps/postgres.py +110 -0
  85. eventsourcing-9.3.0a1/eventsourcing/examples/searchabletimestamps/sqlite.py +99 -0
  86. eventsourcing-9.3.0a1/eventsourcing/examples/searchabletimestamps/test_searchabletimestamps.py +91 -0
  87. eventsourcing-9.3.0a1/eventsourcing/examples/test_invoice.py +176 -0
  88. eventsourcing-9.3.0a1/eventsourcing/examples/test_parking_lot.py +206 -0
  89. {eventsourcing-9.2.21 → eventsourcing-9.3.0a1}/eventsourcing/interface.py +4 -2
  90. {eventsourcing-9.2.21 → eventsourcing-9.3.0a1}/eventsourcing/persistence.py +88 -82
  91. {eventsourcing-9.2.21 → eventsourcing-9.3.0a1}/eventsourcing/popo.py +32 -31
  92. eventsourcing-9.3.0a1/eventsourcing/postgres.py +794 -0
  93. eventsourcing-9.3.0a1/eventsourcing/py.typed +0 -0
  94. {eventsourcing-9.2.21 → eventsourcing-9.3.0a1}/eventsourcing/sqlite.py +100 -102
  95. {eventsourcing-9.2.21 → eventsourcing-9.3.0a1}/eventsourcing/system.py +56 -71
  96. eventsourcing-9.3.0a1/eventsourcing/tests/__init__.py +0 -0
  97. {eventsourcing-9.2.21 → eventsourcing-9.3.0a1}/eventsourcing/tests/application.py +20 -32
  98. eventsourcing-9.3.0a1/eventsourcing/tests/application_tests/__init__.py +0 -0
  99. eventsourcing-9.3.0a1/eventsourcing/tests/application_tests/test_application_with_automatic_snapshotting.py +55 -0
  100. eventsourcing-9.3.0a1/eventsourcing/tests/application_tests/test_application_with_popo.py +22 -0
  101. eventsourcing-9.3.0a1/eventsourcing/tests/application_tests/test_application_with_postgres.py +75 -0
  102. eventsourcing-9.3.0a1/eventsourcing/tests/application_tests/test_application_with_sqlite.py +72 -0
  103. eventsourcing-9.3.0a1/eventsourcing/tests/application_tests/test_cache.py +134 -0
  104. eventsourcing-9.3.0a1/eventsourcing/tests/application_tests/test_event_sourced_log.py +162 -0
  105. eventsourcing-9.3.0a1/eventsourcing/tests/application_tests/test_notificationlog.py +232 -0
  106. eventsourcing-9.3.0a1/eventsourcing/tests/application_tests/test_notificationlogreader.py +126 -0
  107. eventsourcing-9.3.0a1/eventsourcing/tests/application_tests/test_processapplication.py +110 -0
  108. eventsourcing-9.3.0a1/eventsourcing/tests/application_tests/test_processingpolicy.py +109 -0
  109. eventsourcing-9.3.0a1/eventsourcing/tests/application_tests/test_repository.py +504 -0
  110. eventsourcing-9.3.0a1/eventsourcing/tests/application_tests/test_snapshotting.py +68 -0
  111. eventsourcing-9.3.0a1/eventsourcing/tests/application_tests/test_upcasting.py +459 -0
  112. eventsourcing-9.3.0a1/eventsourcing/tests/docs_tests/__init__.py +0 -0
  113. eventsourcing-9.3.0a1/eventsourcing/tests/docs_tests/test_docs.py +293 -0
  114. {eventsourcing-9.2.21 → eventsourcing-9.3.0a1}/eventsourcing/tests/domain.py +1 -1
  115. eventsourcing-9.3.0a1/eventsourcing/tests/domain_tests/__init__.py +0 -0
  116. eventsourcing-9.3.0a1/eventsourcing/tests/domain_tests/test_aggregate.py +1159 -0
  117. eventsourcing-9.3.0a1/eventsourcing/tests/domain_tests/test_aggregate_decorators.py +1604 -0
  118. eventsourcing-9.3.0a1/eventsourcing/tests/domain_tests/test_domainevent.py +80 -0
  119. eventsourcing-9.3.0a1/eventsourcing/tests/interface_tests/__init__.py +0 -0
  120. eventsourcing-9.3.0a1/eventsourcing/tests/interface_tests/test_remotenotificationlog.py +258 -0
  121. {eventsourcing-9.2.21 → eventsourcing-9.3.0a1}/eventsourcing/tests/persistence.py +49 -50
  122. eventsourcing-9.3.0a1/eventsourcing/tests/persistence_tests/__init__.py +0 -0
  123. eventsourcing-9.3.0a1/eventsourcing/tests/persistence_tests/test_aes.py +93 -0
  124. eventsourcing-9.3.0a1/eventsourcing/tests/persistence_tests/test_connection_pool.py +722 -0
  125. eventsourcing-9.3.0a1/eventsourcing/tests/persistence_tests/test_eventstore.py +72 -0
  126. eventsourcing-9.3.0a1/eventsourcing/tests/persistence_tests/test_infrastructure_factory.py +21 -0
  127. eventsourcing-9.3.0a1/eventsourcing/tests/persistence_tests/test_mapper.py +113 -0
  128. eventsourcing-9.3.0a1/eventsourcing/tests/persistence_tests/test_noninterleaving_notification_ids.py +69 -0
  129. eventsourcing-9.3.0a1/eventsourcing/tests/persistence_tests/test_popo.py +124 -0
  130. eventsourcing-9.3.0a1/eventsourcing/tests/persistence_tests/test_postgres.py +1121 -0
  131. eventsourcing-9.3.0a1/eventsourcing/tests/persistence_tests/test_sqlite.py +348 -0
  132. eventsourcing-9.3.0a1/eventsourcing/tests/persistence_tests/test_transcoder.py +44 -0
  133. {eventsourcing-9.2.21 → eventsourcing-9.3.0a1}/eventsourcing/tests/postgres_utils.py +7 -7
  134. eventsourcing-9.3.0a1/eventsourcing/tests/system_tests/__init__.py +0 -0
  135. eventsourcing-9.3.0a1/eventsourcing/tests/system_tests/test_runner.py +935 -0
  136. eventsourcing-9.3.0a1/eventsourcing/tests/system_tests/test_system.py +287 -0
  137. eventsourcing-9.3.0a1/eventsourcing/tests/utils_tests/__init__.py +0 -0
  138. eventsourcing-9.3.0a1/eventsourcing/tests/utils_tests/test_utils.py +226 -0
  139. {eventsourcing-9.2.21 → eventsourcing-9.3.0a1}/eventsourcing/utils.py +49 -50
  140. eventsourcing-9.3.0a1/pyproject.toml +266 -0
  141. eventsourcing-9.2.21/AUTHORS +0 -10
  142. eventsourcing-9.2.21/PKG-INFO +0 -227
  143. eventsourcing-9.2.21/eventsourcing/__init__.py +0 -1
  144. eventsourcing-9.2.21/eventsourcing/dispatch.py +0 -64
  145. eventsourcing-9.2.21/eventsourcing/postgres.py +0 -1005
  146. eventsourcing-9.2.21/eventsourcing.egg-info/SOURCES.txt +0 -30
  147. eventsourcing-9.2.21/eventsourcing.egg-info/dependency_links.txt +0 -1
  148. eventsourcing-9.2.21/eventsourcing.egg-info/not-zip-safe +0 -1
  149. eventsourcing-9.2.21/eventsourcing.egg-info/requires.txt +0 -36
  150. eventsourcing-9.2.21/eventsourcing.egg-info/top_level.txt +0 -1
  151. eventsourcing-9.2.21/setup.cfg +0 -41
  152. eventsourcing-9.2.21/setup.py +0 -94
  153. {eventsourcing-9.2.21 → eventsourcing-9.3.0a1}/LICENSE +0 -0
  154. {eventsourcing-9.2.21 → eventsourcing-9.3.0a1}/README.md +0 -0
  155. {eventsourcing-9.2.21/eventsourcing/tests → eventsourcing-9.3.0a1/eventsourcing/examples}/__init__.py +0 -0
  156. /eventsourcing-9.2.21/eventsourcing/py.typed → /eventsourcing-9.3.0a1/eventsourcing/examples/aggregate1/__init__.py +0 -0
@@ -1,35 +1,51 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: eventsourcing
3
- Version: 9.2.21
3
+ Version: 9.3.0a1
4
4
  Summary: Event sourcing in Python
5
5
  Home-page: https://github.com/pyeventsourcing/eventsourcing
6
+ License: BSD 3-Clause
7
+ Keywords: event sourcing,event store,domain driven design,domain-driven design,ddd,cqrs,cqs
6
8
  Author: John Bywater
7
9
  Author-email: john.bywater@appropriatesoftware.net
8
- License: BSD-3-Clause
9
- Keywords: event sourcing,event store,domain driven design,domain-driven design,ddd,cqrs,cqs
10
- Classifier: Development Status :: 5 - Production/Stable
10
+ Requires-Python: >=3.8,<4.0
11
+ Classifier: Development Status :: 3 - Alpha
11
12
  Classifier: Intended Audience :: Developers
12
13
  Classifier: Intended Audience :: Education
13
14
  Classifier: Intended Audience :: Science/Research
14
15
  Classifier: License :: OSI Approved :: BSD License
16
+ Classifier: License :: Other/Proprietary License
15
17
  Classifier: Operating System :: OS Independent
16
18
  Classifier: Programming Language :: Python
17
- Classifier: Programming Language :: Python :: 3.7
19
+ Classifier: Programming Language :: Python :: 3
18
20
  Classifier: Programming Language :: Python :: 3.8
19
21
  Classifier: Programming Language :: Python :: 3.9
20
22
  Classifier: Programming Language :: Python :: 3.10
21
23
  Classifier: Programming Language :: Python :: 3.11
22
- Classifier: Programming Language :: Python :: Implementation :: CPython
24
+ Classifier: Programming Language :: Python :: 3.12
23
25
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
- Requires-Python: >=3.7
25
- Description-Content-Type: text/markdown
26
- Provides-Extra: postgres
27
- Provides-Extra: postgres_dev
28
26
  Provides-Extra: crypto
29
27
  Provides-Extra: docs
30
- Provides-Extra: dev
31
- License-File: LICENSE
32
- License-File: AUTHORS
28
+ Provides-Extra: postgres
29
+ Requires-Dist: Sphinx ; extra == "docs"
30
+ Requires-Dist: backports.zoneinfo ; python_version < "3.9"
31
+ Requires-Dist: orjson ; extra == "docs"
32
+ Requires-Dist: psycopg[c,pool] (<=3.9.99999) ; extra == "postgres"
33
+ Requires-Dist: pycryptodome (<=3.20.99999) ; extra == "crypto"
34
+ Requires-Dist: pydantic ; extra == "docs"
35
+ Requires-Dist: sphinx_rtd_theme ; extra == "docs"
36
+ Requires-Dist: typing_extensions ; python_version < "3.8"
37
+ Project-URL: Repository, https://github.com/pyeventsourcing/eventsourcing
38
+ Description-Content-Type: text/markdown
39
+
40
+ [![Build Status](https://github.com/pyeventsourcing/eventsourcing/actions/workflows/runtests.yaml/badge.svg?branch=main)](https://github.com/pyeventsourcing/eventsourcing/tree/main)
41
+ [![Coverage Status](https://coveralls.io/repos/github/pyeventsourcing/eventsourcing/badge.svg?branch=main)](https://coveralls.io/github/pyeventsourcing/eventsourcing?branch=main)
42
+ [![Documentation Status](https://readthedocs.org/projects/eventsourcing/badge/?version=stable)](https://eventsourcing.readthedocs.io/en/stable/)
43
+ [![Latest Release](https://badge.fury.io/py/eventsourcing.svg)](https://pypi.org/project/eventsourcing/)
44
+ [![Downloads](https://static.pepy.tech/personalized-badge/eventsourcing?period=total&units=international_system&left_color=grey&right_color=brightgreen&left_text=downloads)](https://pypistats.org/packages/eventsourcing)
45
+ [![Code Style: Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
46
+
47
+
48
+ # Event Sourcing in Python
33
49
 
34
50
  A library for event sourcing in Python.
35
51
 
@@ -225,3 +241,4 @@ for this project, which you are [welcome to join](https://join.slack.com/t/event
225
241
 
226
242
  Please refer to the [documentation](https://eventsourcing.readthedocs.io/) for installation and usage guides.
227
243
 
244
+
@@ -0,0 +1 @@
1
+ __version__ = "9.3.0dev0"
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  from abc import ABC, abstractmethod
3
5
  from copy import deepcopy
@@ -5,8 +7,10 @@ from dataclasses import dataclass
5
7
  from itertools import chain
6
8
  from threading import Event, Lock
7
9
  from typing import (
10
+ TYPE_CHECKING,
8
11
  Any,
9
12
  Callable,
13
+ ClassVar,
10
14
  Dict,
11
15
  Generic,
12
16
  Iterable,
@@ -17,16 +21,10 @@ from typing import (
17
21
  Tuple,
18
22
  Type,
19
23
  TypeVar,
20
- Union,
21
24
  cast,
22
25
  )
23
- from uuid import UUID
24
26
  from warnings import warn
25
27
 
26
- # For backwards compatibility of import statements...
27
- from eventsourcing.domain import LogEvent # noqa: F401
28
- from eventsourcing.domain import TLogEvent # noqa: F401
29
- from eventsourcing.domain import create_utc_datetime_now # noqa: F401
30
28
  from eventsourcing.domain import (
31
29
  Aggregate,
32
30
  CanMutateProtocol,
@@ -39,6 +37,7 @@ from eventsourcing.domain import (
39
37
  SnapshotProtocol,
40
38
  TDomainEvent,
41
39
  TMutableOrImmutableAggregate,
40
+ create_utc_datetime_now,
42
41
  )
43
42
  from eventsourcing.persistence import (
44
43
  ApplicationRecorder,
@@ -55,6 +54,9 @@ from eventsourcing.persistence import (
55
54
  )
56
55
  from eventsourcing.utils import Environment, EnvType, strtobool
57
56
 
57
+ if TYPE_CHECKING: # pragma: nocover
58
+ from uuid import UUID
59
+
58
60
  ProjectorFunction = Callable[
59
61
  [Optional[TMutableOrImmutableAggregate], Iterable[TDomainEvent]],
60
62
  Optional[TMutableOrImmutableAggregate],
@@ -67,9 +69,9 @@ MutatorFunction = Callable[
67
69
 
68
70
 
69
71
  def project_aggregate(
70
- aggregate: Optional[TMutableOrImmutableAggregate],
72
+ aggregate: TMutableOrImmutableAggregate | None,
71
73
  domain_events: Iterable[DomainEventProtocol],
72
- ) -> Optional[TMutableOrImmutableAggregate]:
74
+ ) -> TMutableOrImmutableAggregate | None:
73
75
  """
74
76
  Projector function for aggregate projections, which works
75
77
  by successively calling aggregate mutator function mutate()
@@ -89,13 +91,12 @@ class Cache(Generic[S, T]):
89
91
  def __init__(self) -> None:
90
92
  self.cache: Dict[S, Any] = {}
91
93
 
92
- def get(self, key: S, evict: bool = False) -> T:
94
+ def get(self, key: S, *, evict: bool = False) -> T:
93
95
  if evict:
94
96
  return self.cache.pop(key)
95
- else:
96
- return self.cache[key]
97
+ return self.cache[key]
97
98
 
98
- def put(self, key: S, value: T) -> Optional[T]:
99
+ def put(self, key: S, value: T) -> T | None:
99
100
  if value is not None:
100
101
  self.cache[key] = value
101
102
  return None
@@ -130,7 +131,7 @@ class LRUCache(Cache[S, T]):
130
131
  None,
131
132
  ] # initialize by pointing to self
132
133
 
133
- def get(self, key: S, evict: bool = False) -> T:
134
+ def get(self, key: S, *, evict: bool = False) -> T:
134
135
  with self.lock:
135
136
  link = self.cache.get(key)
136
137
  if link is not None:
@@ -151,10 +152,9 @@ class LRUCache(Cache[S, T]):
151
152
  self.full = self.cache.__len__() >= self.maxsize
152
153
 
153
154
  return result
154
- else:
155
- raise KeyError
155
+ raise KeyError
156
156
 
157
- def put(self, key: S, value: T) -> Optional[Any]:
157
+ def put(self, key: S, value: T) -> Any | None:
158
158
  evicted_key = None
159
159
  evicted_value = None
160
160
  with self.lock:
@@ -213,8 +213,9 @@ class Repository:
213
213
  def __init__(
214
214
  self,
215
215
  event_store: EventStore,
216
- snapshot_store: Optional[EventStore] = None,
217
- cache_maxsize: Optional[int] = None,
216
+ *,
217
+ snapshot_store: EventStore | None = None,
218
+ cache_maxsize: int | None = None,
218
219
  fastforward: bool = True,
219
220
  fastforward_skipping: bool = False,
220
221
  deepcopy_from_cache: bool = True,
@@ -231,7 +232,7 @@ class Repository:
231
232
  self.snapshot_store = snapshot_store
232
233
 
233
234
  if cache_maxsize is None:
234
- self.cache: Optional[Cache[UUID, MutableOrImmutableAggregate]] = None
235
+ self.cache: Cache[UUID, MutableOrImmutableAggregate] | None = None
235
236
  elif cache_maxsize <= 0:
236
237
  self.cache = Cache()
237
238
  else:
@@ -250,7 +251,8 @@ class Repository:
250
251
  def get(
251
252
  self,
252
253
  aggregate_id: UUID,
253
- version: Optional[int] = None,
254
+ *,
255
+ version: int | None = None,
254
256
  projector_func: ProjectorFunction[
255
257
  TMutableOrImmutableAggregate, TDomainEvent
256
258
  ] = project_aggregate,
@@ -289,9 +291,8 @@ class Repository:
289
291
  aggregate, cast(Iterable[TDomainEvent], new_events)
290
292
  )
291
293
  if _aggregate is None:
292
- raise AggregateNotFound(aggregate_id)
293
- else:
294
- aggregate = _aggregate
294
+ raise AggregateNotFoundError(aggregate_id)
295
+ aggregate = _aggregate
295
296
  finally:
296
297
  fastforward_lock.release()
297
298
  finally:
@@ -310,10 +311,10 @@ class Repository:
310
311
  def _reconstruct_aggregate(
311
312
  self,
312
313
  aggregate_id: UUID,
313
- version: Optional[int],
314
+ version: int | None,
314
315
  projector_func: ProjectorFunction[TMutableOrImmutableAggregate, TDomainEvent],
315
316
  ) -> TMutableOrImmutableAggregate:
316
- gt: Optional[int] = None
317
+ gt: int | None = None
317
318
 
318
319
  if self.snapshot_store is not None:
319
320
  # Try to get a snapshot.
@@ -338,7 +339,7 @@ class Repository:
338
339
  )
339
340
 
340
341
  # Reconstruct the aggregate from its events.
341
- initial: Optional[TMutableOrImmutableAggregate] = None
342
+ initial: TMutableOrImmutableAggregate | None = None
342
343
  aggregate = projector_func(
343
344
  initial,
344
345
  chain(
@@ -349,10 +350,9 @@ class Repository:
349
350
 
350
351
  # Raise exception if "not found".
351
352
  if aggregate is None:
352
- raise AggregateNotFound((aggregate_id, version))
353
- else:
354
- # Return the aggregate.
355
- return aggregate
353
+ raise AggregateNotFoundError((aggregate_id, version))
354
+ # Return the aggregate.
355
+ return aggregate
356
356
 
357
357
  def _use_fastforward_lock(self, aggregate_id: UUID) -> Lock:
358
358
  with self._fastforward_locks_lock:
@@ -386,7 +386,7 @@ class Repository:
386
386
  """
387
387
  try:
388
388
  self.get(aggregate_id=item)
389
- except AggregateNotFound:
389
+ except AggregateNotFoundError:
390
390
  return False
391
391
  else:
392
392
  return True
@@ -412,9 +412,9 @@ class Section:
412
412
  :param Optional[str] next_id: section ID of the following section
413
413
  """
414
414
 
415
- id: Optional[str]
415
+ id: str | None
416
416
  items: List[Notification]
417
- next_id: Optional[str]
417
+ next_id: str | None
418
418
 
419
419
 
420
420
  class NotificationLog(ABC):
@@ -435,7 +435,7 @@ class NotificationLog(ABC):
435
435
  self,
436
436
  start: int,
437
437
  limit: int,
438
- stop: Optional[int] = None,
438
+ stop: int | None = None,
439
439
  topics: Sequence[str] = (),
440
440
  ) -> List[Notification]:
441
441
  """
@@ -495,8 +495,8 @@ class LocalNotificationLog(NotificationLog):
495
495
  notifications = self.select(start, limit)
496
496
 
497
497
  # Get next section ID.
498
- actual_section_id: Optional[str]
499
- next_id: Optional[str]
498
+ actual_section_id: str | None
499
+ next_id: str | None
500
500
  if len(notifications):
501
501
  last_notification_id = notifications[-1].id
502
502
  actual_section_id = self.format_section_id(
@@ -523,7 +523,7 @@ class LocalNotificationLog(NotificationLog):
523
523
  self,
524
524
  start: int,
525
525
  limit: int,
526
- stop: Optional[int] = None,
526
+ stop: int | None = None,
527
527
  topics: Sequence[str] = (),
528
528
  ) -> List[Notification]:
529
529
  """
@@ -532,16 +532,17 @@ class LocalNotificationLog(NotificationLog):
532
532
  from the notification log.
533
533
  """
534
534
  if limit > self.section_size:
535
- raise ValueError(
535
+ msg = (
536
536
  f"Requested limit {limit} greater than section size {self.section_size}"
537
537
  )
538
+ raise ValueError(msg)
538
539
  return self.recorder.select_notifications(
539
540
  start=start, limit=limit, stop=stop, topics=topics
540
541
  )
541
542
 
542
543
  @staticmethod
543
544
  def format_section_id(first_id: int, last_id: int) -> str:
544
- return "{},{}".format(first_id, last_id)
545
+ return f"{first_id},{last_id}"
545
546
 
546
547
 
547
548
  class ProcessingEvent:
@@ -552,7 +553,7 @@ class ProcessingEvent:
552
553
  new domain events that result from processing that notification.
553
554
  """
554
555
 
555
- def __init__(self, tracking: Optional[Tracking] = None):
556
+ def __init__(self, tracking: Tracking | None = None):
556
557
  """
557
558
  Initialises the process event with the given tracking object.
558
559
  """
@@ -563,7 +564,7 @@ class ProcessingEvent:
563
564
 
564
565
  def collect_events(
565
566
  self,
566
- *objs: Optional[Union[MutableOrImmutableAggregate, DomainEventProtocol]],
567
+ *objs: MutableOrImmutableAggregate | DomainEventProtocol | None,
567
568
  **kwargs: Any,
568
569
  ) -> None:
569
570
  """
@@ -572,7 +573,7 @@ class ProcessingEvent:
572
573
  for obj in objs:
573
574
  if obj is None:
574
575
  continue
575
- elif isinstance(obj, DomainEventProtocol):
576
+ if isinstance(obj, DomainEventProtocol):
576
577
  self.events.append(obj)
577
578
  else:
578
579
  if isinstance(obj, CollectEventsProtocol):
@@ -584,7 +585,7 @@ class ProcessingEvent:
584
585
 
585
586
  def save(
586
587
  self,
587
- *aggregates: Optional[Union[MutableOrImmutableAggregate, DomainEventProtocol]],
588
+ *aggregates: MutableOrImmutableAggregate | DomainEventProtocol | None,
588
589
  **kwargs: Any,
589
590
  ) -> None:
590
591
  warn(
@@ -596,31 +597,12 @@ class ProcessingEvent:
596
597
  self.collect_events(*aggregates, **kwargs)
597
598
 
598
599
 
599
- class ProcessEvent(ProcessingEvent):
600
- """Deprecated, use :class:`ProcessingEvent` instead.
601
-
602
- Keeps together a :class:`~eventsourcing.persistence.Tracking`
603
- object, which represents the position of a domain event notification
604
- in the notification log of a particular application, and the
605
- new domain events that result from processing that notification.
606
- """
607
-
608
- def __init__(self, tracking: Optional[Tracking] = None):
609
- warn(
610
- "'ProcessEvent' is deprecated, use 'ProcessingEvent' instead",
611
- DeprecationWarning,
612
- stacklevel=2,
613
- )
614
-
615
- super().__init__(tracking)
616
-
617
-
618
600
  class RecordingEvent:
619
601
  def __init__(
620
602
  self,
621
603
  application_name: str,
622
604
  recordings: List[Recording],
623
- previous_max_notification_id: Optional[int],
605
+ previous_max_notification_id: int | None,
624
606
  ):
625
607
  self.application_name = application_name
626
608
  self.recordings = recordings
@@ -633,13 +615,13 @@ class Application:
633
615
  """
634
616
 
635
617
  name = "Application"
636
- env: EnvType = {}
618
+ env: ClassVar[Dict[str, str]] = {}
637
619
  is_snapshotting_enabled: bool = False
638
- snapshotting_intervals: Optional[
639
- Dict[Type[MutableOrImmutableAggregate], int]
620
+ snapshotting_intervals: ClassVar[
621
+ Dict[Type[MutableOrImmutableAggregate], int] | None
640
622
  ] = None
641
- snapshotting_projectors: Optional[
642
- Dict[Type[MutableOrImmutableAggregate], ProjectorFunction[Any, Any]]
623
+ snapshotting_projectors: ClassVar[
624
+ Dict[Type[MutableOrImmutableAggregate], ProjectorFunction[Any, Any]] | None
643
625
  ] = None
644
626
  snapshot_class: Type[SnapshotProtocol] = Snapshot
645
627
  log_section_size = 10
@@ -654,7 +636,7 @@ class Application:
654
636
  if "name" not in cls.__dict__:
655
637
  cls.name = cls.__name__
656
638
 
657
- def __init__(self, env: Optional[EnvType] = None) -> None:
639
+ def __init__(self, env: EnvType | None = None) -> None:
658
640
  """
659
641
  Initialises an application with an
660
642
  :class:`~eventsourcing.persistence.InfrastructureFactory`,
@@ -664,20 +646,36 @@ class Application:
664
646
  a :class:`~eventsourcing.application.Repository`, and
665
647
  a :class:`~eventsourcing.application.LocalNotificationLog`.
666
648
  """
667
- self.env = self.construct_env(self.name, env)
649
+ self.env = self.construct_env(self.name, env) # type: ignore[misc]
668
650
  self.factory = self.construct_factory(self.env)
669
651
  self.mapper = self.construct_mapper()
670
652
  self.recorder = self.construct_recorder()
671
653
  self.events = self.construct_event_store()
672
- self.snapshots: Optional[EventStore] = None
654
+ self.snapshots: EventStore | None = None
673
655
  if self.factory.is_snapshotting_enabled():
674
656
  self.snapshots = self.construct_snapshot_store()
675
- self.repository = self.construct_repository()
676
- self.notification_log = self.construct_notification_log()
657
+ self._repository = self.construct_repository()
658
+ self._notification_log = self.construct_notification_log()
677
659
  self.closing = Event()
678
- self.previous_max_notification_id: Optional[
679
- int
680
- ] = self.recorder.max_notification_id()
660
+ self.previous_max_notification_id: int | None = (
661
+ self.recorder.max_notification_id()
662
+ )
663
+
664
+ @property
665
+ def repository(self) -> Repository:
666
+ """
667
+ An application's repository reconstructs aggregates from stored events.
668
+ """
669
+ return self._repository
670
+
671
+ @property
672
+ def notification_log(self) -> LocalNotificationLog:
673
+ """
674
+ An application's notification log presents all the aggregate events
675
+ of an application in the order they were recorded as a sequence of event
676
+ notifications.
677
+ """
678
+ return self._notification_log
681
679
 
682
680
  @property
683
681
  def log(self) -> LocalNotificationLog:
@@ -686,9 +684,9 @@ class Application:
686
684
  DeprecationWarning,
687
685
  stacklevel=2,
688
686
  )
689
- return self.notification_log
687
+ return self._notification_log
690
688
 
691
- def construct_env(self, name: str, env: Optional[EnvType] = None) -> Environment:
689
+ def construct_env(self, name: str, env: EnvType | None = None) -> Environment:
692
690
  """
693
691
  Constructs environment from which application will be configured.
694
692
  """
@@ -767,10 +765,7 @@ class Application:
767
765
  Constructs a :class:`Repository` for use by the application.
768
766
  """
769
767
  cache_maxsize_envvar = self.env.get(self.AGGREGATE_CACHE_MAXSIZE)
770
- if cache_maxsize_envvar:
771
- cache_maxsize = int(cache_maxsize_envvar)
772
- else:
773
- cache_maxsize = None
768
+ cache_maxsize = int(cache_maxsize_envvar) if cache_maxsize_envvar else None
774
769
  return Repository(
775
770
  event_store=self.events,
776
771
  snapshot_store=self.snapshots,
@@ -792,7 +787,7 @@ class Application:
792
787
 
793
788
  def save(
794
789
  self,
795
- *objs: Optional[Union[MutableOrImmutableAggregate, DomainEventProtocol]],
790
+ *objs: MutableOrImmutableAggregate | DomainEventProtocol | None,
796
791
  **kwargs: Any,
797
792
  ) -> List[Recording]:
798
793
  """
@@ -830,40 +825,39 @@ class Application:
830
825
  except KeyError:
831
826
  continue
832
827
  interval = self.snapshotting_intervals.get(type(aggregate))
833
- if interval is not None:
834
- if event.originator_version % interval == 0:
835
- projector_func: ProjectorFunction[
836
- MutableOrImmutableAggregate, DomainEventProtocol
837
- ]
838
- if (
839
- self.snapshotting_projectors
840
- and type(aggregate) in self.snapshotting_projectors
841
- ):
842
- projector_func = self.snapshotting_projectors[
843
- type(aggregate)
844
- ]
845
- else:
846
- projector_func = project_aggregate
847
- if (
848
- not isinstance(event, CanMutateProtocol)
849
- and projector_func is project_aggregate
850
- ):
851
- raise ProgrammingError(
852
- (
853
- "Aggregate projector function not found. Please set "
854
- "snapshotting_projectors on application class."
855
- )
856
- )
857
- self.take_snapshot(
858
- aggregate_id=event.originator_id,
859
- version=event.originator_version,
860
- projector_func=projector_func,
828
+ if interval is not None and event.originator_version % interval == 0:
829
+ if (
830
+ self.snapshotting_projectors
831
+ and type(aggregate) in self.snapshotting_projectors
832
+ ):
833
+ projector_func = self.snapshotting_projectors[type(aggregate)]
834
+ else:
835
+ projector_func = project_aggregate
836
+ if projector_func is project_aggregate and not isinstance(
837
+ event, CanMutateProtocol
838
+ ):
839
+ msg = (
840
+ f"Cannot take snapshot for {type(aggregate)} with "
841
+ "default project_aggregate() function, because its "
842
+ f"domain event {type(event)} does not implement "
843
+ "the 'can mutate' protocol (see CanMutateProtocol)."
844
+ f" Please define application class {type(self)}"
845
+ " with class variable 'snapshotting_projectors', "
846
+ f"to be a dict that has {type(aggregate)} as a key "
847
+ "with the aggregate projector function for "
848
+ f"{type(aggregate)} as the value for that key."
861
849
  )
850
+ raise ProgrammingError(msg)
851
+ self.take_snapshot(
852
+ aggregate_id=event.originator_id,
853
+ version=event.originator_version,
854
+ projector_func=projector_func,
855
+ )
862
856
 
863
857
  def take_snapshot(
864
858
  self,
865
859
  aggregate_id: UUID,
866
- version: Optional[int] = None,
860
+ version: int | None = None,
867
861
  projector_func: ProjectorFunction[
868
862
  TMutableOrImmutableAggregate, TDomainEvent
869
863
  ] = project_aggregate,
@@ -873,19 +867,20 @@ class Application:
873
867
  and puts the snapshot in the snapshot store.
874
868
  """
875
869
  if self.snapshots is None:
876
- raise AssertionError(
870
+ msg = (
877
871
  "Can't take snapshot without snapshots store. Please "
878
872
  "set environment variable IS_SNAPSHOTTING_ENABLED to "
879
873
  "a true value (e.g. 'y'), or set 'is_snapshotting_enabled' "
880
874
  "on application class, or set 'snapshotting_intervals' on "
881
875
  "application class."
882
876
  )
883
- else:
884
- aggregate = self.repository.get(
885
- aggregate_id, version=version, projector_func=projector_func
886
- )
887
- snapshot = type(self).snapshot_class.take(aggregate)
888
- self.snapshots.put([snapshot])
877
+ raise AssertionError(msg)
878
+ aggregate = self.repository.get(
879
+ aggregate_id, version=version, projector_func=projector_func
880
+ )
881
+ snapshot_class = getattr(type(aggregate), "Snapshot", type(self).snapshot_class)
882
+ snapshot = snapshot_class.take(aggregate)
883
+ self.snapshots.put([snapshot])
889
884
 
890
885
  def notify(self, new_events: List[DomainEventProtocol]) -> None:
891
886
  """
@@ -913,7 +908,7 @@ class Application:
913
908
  TApplication = TypeVar("TApplication", bound=Application)
914
909
 
915
910
 
916
- class AggregateNotFound(EventSourcingError):
911
+ class AggregateNotFoundError(EventSourcingError):
917
912
  """
918
913
  Raised when an :class:`~eventsourcing.domain.Aggregate`
919
914
  object is not found in a :class:`Repository`.
@@ -939,15 +934,15 @@ class EventSourcedLog(Generic[TDomainEvent]):
939
934
  self,
940
935
  events: EventStore,
941
936
  originator_id: UUID,
942
- logged_cls: Type[TDomainEvent], # Todo: Rename to 'event_class' in v10.
937
+ logged_cls: Type[TDomainEvent], # TODO: Rename to 'event_class' in v10.
943
938
  ):
944
939
  self.events = events
945
940
  self.originator_id = originator_id
946
- self.logged_cls = logged_cls # Todo: Rename to 'event_class' in v10.
941
+ self.logged_cls = logged_cls # TODO: Rename to 'event_class' in v10.
947
942
 
948
943
  def trigger_event(
949
944
  self,
950
- next_originator_version: Optional[int] = None,
945
+ next_originator_version: int | None = None,
951
946
  **kwargs: Any,
952
947
  ) -> TDomainEvent:
953
948
  """
@@ -961,8 +956,8 @@ class EventSourcedLog(Generic[TDomainEvent]):
961
956
 
962
957
  def _trigger_event(
963
958
  self,
964
- logged_cls: Optional[Type[T]],
965
- next_originator_version: Optional[int] = None,
959
+ logged_cls: Type[T] | None,
960
+ next_originator_version: int | None = None,
966
961
  **kwargs: Any,
967
962
  ) -> T:
968
963
  """
@@ -975,15 +970,14 @@ class EventSourcedLog(Generic[TDomainEvent]):
975
970
  else:
976
971
  next_originator_version = last_logged.originator_version + 1
977
972
 
978
- logged_event = logged_cls( # type: ignore
973
+ return logged_cls( # type: ignore
979
974
  originator_id=self.originator_id,
980
975
  originator_version=next_originator_version,
981
976
  timestamp=create_utc_datetime_now(),
982
977
  **kwargs,
983
978
  )
984
- return logged_event
985
979
 
986
- def get_first(self) -> Optional[TDomainEvent]:
980
+ def get_first(self) -> TDomainEvent | None:
987
981
  """
988
982
  Selects the first logged event.
989
983
  """
@@ -992,7 +986,7 @@ class EventSourcedLog(Generic[TDomainEvent]):
992
986
  except StopIteration:
993
987
  return None
994
988
 
995
- def get_last(self) -> Optional[TDomainEvent]:
989
+ def get_last(self) -> TDomainEvent | None:
996
990
  """
997
991
  Selects the last logged event.
998
992
  """
@@ -1003,10 +997,11 @@ class EventSourcedLog(Generic[TDomainEvent]):
1003
997
 
1004
998
  def get(
1005
999
  self,
1006
- gt: Optional[int] = None,
1007
- lte: Optional[int] = None,
1000
+ *,
1001
+ gt: int | None = None,
1002
+ lte: int | None = None,
1008
1003
  desc: bool = False,
1009
- limit: Optional[int] = None,
1004
+ limit: int | None = None,
1010
1005
  ) -> Iterator[TDomainEvent]:
1011
1006
  """
1012
1007
  Selects a range of logged events with limit,