eventsourcing 9.4.5__tar.gz → 9.4.6__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.
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/PKG-INFO +1 -1
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/application.py +1 -1
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/domain.py +58 -33
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/persistence.py +40 -20
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/pyproject.toml +1 -1
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/AUTHORS +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/LICENSE +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/README.md +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/__init__.py +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/cipher.py +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/compressor.py +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/cryptography.py +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/dispatch.py +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/interface.py +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/popo.py +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/postgres.py +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/projection.py +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/py.typed +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/sqlite.py +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/system.py +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/tests/__init__.py +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/tests/application.py +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/tests/domain.py +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/tests/persistence.py +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/tests/postgres_utils.py +0 -0
- {eventsourcing-9.4.5 → eventsourcing-9.4.6}/eventsourcing/utils.py +0 -0
|
@@ -840,7 +840,7 @@ class Application(Generic[TAggregateID]):
|
|
|
840
840
|
"application class."
|
|
841
841
|
)
|
|
842
842
|
raise AssertionError(msg)
|
|
843
|
-
aggregate: BaseAggregate[
|
|
843
|
+
aggregate: BaseAggregate[TAggregateID] = self.repository.get(
|
|
844
844
|
aggregate_id, version=version, projector_func=projector_func
|
|
845
845
|
)
|
|
846
846
|
snapshot_class = getattr(type(aggregate), "Snapshot", type(self).snapshot_class)
|
|
@@ -17,6 +17,7 @@ from typing import (
|
|
|
17
17
|
Callable,
|
|
18
18
|
ClassVar,
|
|
19
19
|
Generic,
|
|
20
|
+
Optional,
|
|
20
21
|
Protocol,
|
|
21
22
|
TypeVar,
|
|
22
23
|
Union,
|
|
@@ -237,35 +238,41 @@ class CanCreateTimestamp:
|
|
|
237
238
|
TAggregate = TypeVar("TAggregate", bound="BaseAggregate[Any]")
|
|
238
239
|
|
|
239
240
|
|
|
240
|
-
class HasOriginatorIDVersion(Generic[
|
|
241
|
+
class HasOriginatorIDVersion(Generic[TAggregateID]):
|
|
241
242
|
"""Declares ``originator_id`` and ``originator_version`` attributes."""
|
|
242
243
|
|
|
243
|
-
originator_id:
|
|
244
|
+
originator_id: TAggregateID
|
|
244
245
|
"""UUID identifying an aggregate to which the event belongs."""
|
|
245
246
|
originator_version: int
|
|
246
247
|
"""Integer identifying the version of the aggregate when the event occurred."""
|
|
247
248
|
|
|
248
|
-
|
|
249
|
+
originator_id_type: ClassVar[Optional[type[Union[UUID, str]]]] = None # noqa: UP007
|
|
249
250
|
|
|
250
251
|
def __init_subclass__(cls) -> None:
|
|
251
252
|
cls.find_originator_id_type(HasOriginatorIDVersion)
|
|
253
|
+
super().__init_subclass__()
|
|
252
254
|
|
|
253
255
|
@classmethod
|
|
254
256
|
def find_originator_id_type(cls: type, generic_cls: type) -> None:
|
|
255
|
-
"""Store the type argument of
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
257
|
+
"""Store the type argument of TAggregateID on the subclass."""
|
|
258
|
+
if "originator_id_type" not in cls.__dict__:
|
|
259
|
+
for orig_base in cls.__orig_bases__: # type: ignore[attr-defined]
|
|
260
|
+
if "originator_id_type" in orig_base.__dict__:
|
|
261
|
+
cls.originator_id_type = orig_base.__dict__["originator_id_type"] # type: ignore[attr-defined]
|
|
262
|
+
elif get_origin(orig_base) is generic_cls:
|
|
263
|
+
originator_id_type = get_args(orig_base)[0]
|
|
264
|
+
if originator_id_type in (UUID, str):
|
|
265
|
+
cls.originator_id_type = originator_id_type # type: ignore[attr-defined]
|
|
266
|
+
break
|
|
267
|
+
if originator_id_type is Any:
|
|
268
|
+
continue
|
|
269
|
+
if isinstance(originator_id_type, TypeVar):
|
|
270
|
+
continue
|
|
271
|
+
msg = f"Aggregate ID type arg cannot be {originator_id_type}"
|
|
272
|
+
raise TypeError(msg)
|
|
266
273
|
|
|
267
274
|
|
|
268
|
-
class CanMutateAggregate(HasOriginatorIDVersion[
|
|
275
|
+
class CanMutateAggregate(HasOriginatorIDVersion[TAggregateID], CanCreateTimestamp):
|
|
269
276
|
"""Implements a :py:func:`~eventsourcing.domain.CanMutateAggregate.mutate`
|
|
270
277
|
method that evolves the state of an aggregate.
|
|
271
278
|
"""
|
|
@@ -276,6 +283,7 @@ class CanMutateAggregate(HasOriginatorIDVersion[TAggregateID_co], CanCreateTimes
|
|
|
276
283
|
|
|
277
284
|
def __init_subclass__(cls) -> None:
|
|
278
285
|
cls.find_originator_id_type(CanMutateAggregate)
|
|
286
|
+
super().__init_subclass__()
|
|
279
287
|
|
|
280
288
|
def mutate(self, aggregate: TAggregate | None) -> TAggregate | None:
|
|
281
289
|
"""Validates and adjusts the attributes of the given ``aggregate``
|
|
@@ -333,7 +341,7 @@ class CanMutateAggregate(HasOriginatorIDVersion[TAggregateID_co], CanCreateTimes
|
|
|
333
341
|
return self.__dict__
|
|
334
342
|
|
|
335
343
|
|
|
336
|
-
class CanInitAggregate(CanMutateAggregate[
|
|
344
|
+
class CanInitAggregate(CanMutateAggregate[TAggregateID]):
|
|
337
345
|
"""Implements a :func:`~eventsourcing.domain.CanMutateAggregate.mutate`
|
|
338
346
|
method that constructs the initial state of an aggregate.
|
|
339
347
|
"""
|
|
@@ -343,6 +351,7 @@ class CanInitAggregate(CanMutateAggregate[TAggregateID_co]):
|
|
|
343
351
|
|
|
344
352
|
def __init_subclass__(cls) -> None:
|
|
345
353
|
cls.find_originator_id_type(CanInitAggregate)
|
|
354
|
+
super().__init_subclass__()
|
|
346
355
|
|
|
347
356
|
def mutate(self, aggregate: TAggregate | None) -> TAggregate | None:
|
|
348
357
|
"""Constructs an aggregate instance according to the attributes of an event.
|
|
@@ -1075,7 +1084,7 @@ class BaseAggregate(Generic[TAggregateID], metaclass=MetaAggregate):
|
|
|
1075
1084
|
cls: type[Self],
|
|
1076
1085
|
event_class: type[CanInitAggregate[TAggregateID]],
|
|
1077
1086
|
*,
|
|
1078
|
-
id:
|
|
1087
|
+
id: TAggregateID | None = None, # noqa: A002
|
|
1079
1088
|
**kwargs: Any,
|
|
1080
1089
|
) -> Self:
|
|
1081
1090
|
"""Constructs a new aggregate object instance."""
|
|
@@ -1253,7 +1262,10 @@ class BaseAggregate(Generic[TAggregateID], metaclass=MetaAggregate):
|
|
|
1253
1262
|
cls.__name__ in _module.__dict__
|
|
1254
1263
|
and ENVVAR_DISABLE_REDEFINITION_CHECK not in os.environ
|
|
1255
1264
|
):
|
|
1256
|
-
msg =
|
|
1265
|
+
msg = (
|
|
1266
|
+
f"Name '{cls.__name__}' of {cls} already defined in "
|
|
1267
|
+
f"'{cls.__module__}' module: {_module.__dict__[cls.__name__]}"
|
|
1268
|
+
)
|
|
1257
1269
|
raise ProgrammingError(msg)
|
|
1258
1270
|
|
|
1259
1271
|
# Get the class annotations.
|
|
@@ -1343,24 +1355,41 @@ class BaseAggregate(Generic[TAggregateID], metaclass=MetaAggregate):
|
|
|
1343
1355
|
if name.lower() == name:
|
|
1344
1356
|
continue
|
|
1345
1357
|
|
|
1346
|
-
#
|
|
1358
|
+
# Don't subclass if not "CanMutateAggregate".
|
|
1347
1359
|
if not isinstance(value, type) or not issubclass(value, CanMutateAggregate):
|
|
1348
1360
|
continue
|
|
1349
1361
|
|
|
1362
|
+
# # Don't subclass generic classes (we don't have a type argument).
|
|
1363
|
+
# # TODO: Maybe also prohibit triggering such things?
|
|
1364
|
+
# if value.__dict__.get("__parameters__", ()):
|
|
1365
|
+
# continue
|
|
1366
|
+
|
|
1350
1367
|
# Check we have a base event class.
|
|
1351
1368
|
if base_event_cls is None:
|
|
1352
1369
|
raise base_event_class_not_defined_error
|
|
1353
1370
|
|
|
1354
1371
|
# Redefine events that aren't already subclass of the base event class.
|
|
1355
1372
|
if not issubclass(value, base_event_cls):
|
|
1373
|
+
# Identify base classes that were redefined, to preserve hierarchy.
|
|
1374
|
+
redefined_bases = []
|
|
1375
|
+
for base in value.__bases__:
|
|
1376
|
+
if base in redefined_event_classes:
|
|
1377
|
+
redefined_bases.append(redefined_event_classes[base])
|
|
1378
|
+
elif "__pydantic_generic_metadata__" in base.__dict__:
|
|
1379
|
+
pydantic_metadata = base.__dict__[
|
|
1380
|
+
"__pydantic_generic_metadata__"
|
|
1381
|
+
]
|
|
1382
|
+
for i, key in enumerate(pydantic_metadata):
|
|
1383
|
+
if key == "origin":
|
|
1384
|
+
origin = base.__bases__[i]
|
|
1385
|
+
if origin in redefined_event_classes:
|
|
1386
|
+
redefined_bases.append(
|
|
1387
|
+
redefined_event_classes[origin]
|
|
1388
|
+
)
|
|
1389
|
+
|
|
1356
1390
|
# Decide base classes of redefined event class: it must be
|
|
1357
1391
|
# a subclass of the original class, all redefined classes that
|
|
1358
1392
|
# were in its bases, and the aggregate's base event class.
|
|
1359
|
-
redefined_bases = [
|
|
1360
|
-
redefined_event_classes[b]
|
|
1361
|
-
for b in value.__bases__
|
|
1362
|
-
if b in redefined_event_classes
|
|
1363
|
-
]
|
|
1364
1393
|
event_class_bases = (
|
|
1365
1394
|
value,
|
|
1366
1395
|
*redefined_bases,
|
|
@@ -1735,17 +1764,13 @@ class SnapshotProtocol(DomainEventProtocol[TAggregateID_co], Protocol):
|
|
|
1735
1764
|
"""Snapshots have a 'take()' class method."""
|
|
1736
1765
|
|
|
1737
1766
|
|
|
1738
|
-
|
|
1739
|
-
"TCanSnapshotAggregate", bound="CanSnapshotAggregate[Any]"
|
|
1740
|
-
)
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
class CanSnapshotAggregate(HasOriginatorIDVersion[TAggregateID_co], CanCreateTimestamp):
|
|
1767
|
+
class CanSnapshotAggregate(HasOriginatorIDVersion[TAggregateID], CanCreateTimestamp):
|
|
1744
1768
|
topic: str
|
|
1745
1769
|
state: Any
|
|
1746
1770
|
|
|
1747
1771
|
def __init_subclass__(cls) -> None:
|
|
1748
1772
|
cls.find_originator_id_type(CanSnapshotAggregate)
|
|
1773
|
+
super().__init_subclass__()
|
|
1749
1774
|
|
|
1750
1775
|
# def __init__(
|
|
1751
1776
|
# self,
|
|
@@ -1760,7 +1785,7 @@ class CanSnapshotAggregate(HasOriginatorIDVersion[TAggregateID_co], CanCreateTim
|
|
|
1760
1785
|
@classmethod
|
|
1761
1786
|
def take(
|
|
1762
1787
|
cls,
|
|
1763
|
-
aggregate: MutableOrImmutableAggregate[
|
|
1788
|
+
aggregate: MutableOrImmutableAggregate[TAggregateID],
|
|
1764
1789
|
) -> Self:
|
|
1765
1790
|
"""Creates a snapshot of the given :class:`Aggregate` object."""
|
|
1766
1791
|
aggregate_state = dict(aggregate.__dict__)
|
|
@@ -1779,9 +1804,9 @@ class CanSnapshotAggregate(HasOriginatorIDVersion[TAggregateID_co], CanCreateTim
|
|
|
1779
1804
|
state=aggregate_state, # pyright: ignore[reportCallIssue]
|
|
1780
1805
|
)
|
|
1781
1806
|
|
|
1782
|
-
def mutate(self, _: None) -> BaseAggregate[
|
|
1807
|
+
def mutate(self, _: None) -> BaseAggregate[TAggregateID]:
|
|
1783
1808
|
"""Reconstructs the snapshotted :class:`Aggregate` object."""
|
|
1784
|
-
cls = cast(type[BaseAggregate[
|
|
1809
|
+
cls = cast(type[BaseAggregate[TAggregateID]], resolve_topic(self.topic))
|
|
1785
1810
|
aggregate_state = dict(self.state)
|
|
1786
1811
|
from_version = aggregate_state.pop("class_version", 1)
|
|
1787
1812
|
class_version = getattr(cls, "class_version", 1)
|
|
@@ -291,7 +291,7 @@ class Mapper(Generic[TAggregateID]):
|
|
|
291
291
|
)
|
|
292
292
|
raise MapperDeserialisationError(msg) from e
|
|
293
293
|
|
|
294
|
-
id_convertor =
|
|
294
|
+
id_convertor = find_id_convertor(
|
|
295
295
|
cls, cast(Hashable, type(stored_event.originator_id))
|
|
296
296
|
)
|
|
297
297
|
# print("ID of convertor:", id(convertor))
|
|
@@ -309,33 +309,53 @@ class Mapper(Generic[TAggregateID]):
|
|
|
309
309
|
|
|
310
310
|
|
|
311
311
|
@lru_cache
|
|
312
|
-
def
|
|
312
|
+
def find_id_convertor(
|
|
313
313
|
domain_event_cls: type[object], originator_id_cls: type[UUID | str]
|
|
314
314
|
) -> Callable[[UUID | str], UUID | str]:
|
|
315
315
|
# Try to find the originator_id type.
|
|
316
|
-
type_originator_id: type[UUID | str] = UUID
|
|
317
316
|
if issubclass(domain_event_cls, HasOriginatorIDVersion):
|
|
318
|
-
|
|
317
|
+
# For classes that inherit CanMutateAggregate, and don't use a different
|
|
318
|
+
# mapper, then assume they aren't overriding __init_subclass__ is a way
|
|
319
|
+
# that prevents 'originator_id_type' being found from type arguments and
|
|
320
|
+
# set on the class.
|
|
321
|
+
# TODO: Write a test where a custom class does override __init_subclass__
|
|
322
|
+
# so that the next line will cause an AssertionError. Then fix this code.
|
|
323
|
+
if domain_event_cls.originator_id_type is None:
|
|
324
|
+
msg = "originator_id_type cannot be None"
|
|
325
|
+
raise TypeError(msg)
|
|
326
|
+
originator_id_type = domain_event_cls.originator_id_type
|
|
319
327
|
else:
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
328
|
+
# Otherwise look for annotations.
|
|
329
|
+
for cls in domain_event_cls.__mro__:
|
|
330
|
+
try:
|
|
331
|
+
annotation = cls.__annotations__["originator_id"]
|
|
332
|
+
except (KeyError, AttributeError): # noqa: PERF203
|
|
333
|
+
continue
|
|
334
|
+
else:
|
|
335
|
+
valid_annotations = {
|
|
336
|
+
str: str,
|
|
337
|
+
UUID: UUID,
|
|
338
|
+
"str": str,
|
|
339
|
+
"UUID": UUID,
|
|
340
|
+
"uuid.UUID": UUID,
|
|
341
|
+
}
|
|
342
|
+
if annotation not in valid_annotations:
|
|
343
|
+
msg = f"originator_id annotation on {cls} is not either UUID or str"
|
|
344
|
+
raise TypeError(msg)
|
|
345
|
+
assert annotation in valid_annotations, annotation
|
|
346
|
+
originator_id_type = valid_annotations[annotation]
|
|
347
|
+
break
|
|
348
|
+
else:
|
|
349
|
+
msg = (
|
|
350
|
+
f"Neither event class {domain_event_cls}"
|
|
351
|
+
f"nor its bases have an originator_id annotation"
|
|
352
|
+
)
|
|
353
|
+
raise TypeError(msg)
|
|
354
|
+
|
|
355
|
+
if originator_id_cls is str and originator_id_type is UUID:
|
|
331
356
|
convertor = str_to_uuid_convertor
|
|
332
357
|
else:
|
|
333
358
|
convertor = pass_through_convertor
|
|
334
|
-
# print(
|
|
335
|
-
# f"Decided {convertor.__name__} "
|
|
336
|
-
# f"for {domain_event_cls.__name__} "
|
|
337
|
-
# f"and {originator_id_cls.__name__}."
|
|
338
|
-
# )
|
|
339
359
|
return convertor
|
|
340
360
|
|
|
341
361
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|