diff --git a/src/protean/__init__.py b/src/protean/__init__.py index e44a6720..185a038d 100644 --- a/src/protean/__init__.py +++ b/src/protean/__init__.py @@ -1,6 +1,6 @@ __version__ = "0.12.1" -from .core.aggregate import BaseAggregate, atomic_change +from .core.aggregate import BaseAggregate, apply, atomic_change from .core.application_service import BaseApplicationService from .core.command import BaseCommand from .core.command_handler import BaseCommandHandler @@ -9,7 +9,6 @@ from .core.entity import BaseEntity, invariant from .core.event import BaseEvent from .core.event_handler import BaseEventHandler -from .core.event_sourced_aggregate import BaseEventSourcedAggregate, apply from .core.model import BaseModel from .core.queryset import Q, QuerySet from .core.repository import BaseRepository @@ -33,7 +32,6 @@ "BaseEntity", "BaseEvent", "BaseEventHandler", - "BaseEventSourcedAggregate", "BaseModel", "BaseRepository", "BaseSerializer", diff --git a/src/protean/container.py b/src/protean/container.py index 92c8b932..5954a2c2 100644 --- a/src/protean/container.py +++ b/src/protean/container.py @@ -2,19 +2,16 @@ import copy import inspect -import json import logging from collections import defaultdict from typing import Any, Type, Union from protean.exceptions import ( - ConfigurationError, InvalidDataError, NotSupportedError, ValidationError, ) from protean.fields import Auto, Field, FieldBase, ValueObject -from protean.reflection import id_field from protean.utils import generate_identity from .reflection import ( @@ -366,77 +363,6 @@ def _default_options(cls): return [] -class EventedMixin: - def __init__(self, *args, **kwargs) -> None: - """Initialize an instance-level variable named `_events` to track events - raised in the aggregate cluster. - - This method cannot have a super invocation, because we don't want it to - invoke BaseContainer's `__init__` method. But there is a conflict regarding - this between BaseAggregate and BaseEventSourcedAggregate. So this Mixin's - functionality has been replicated temporarily in BaseAggregate class. - - Other mixins that are inherited by BaseEntity and BaseEventSourcedAggregate - work with `__init_subclass__`, and do not have this issue. - """ - super().__init__(*args, **kwargs) - self._events = [] - - def raise_(self, event, fact_event=False) -> None: - """Raise an event in the aggregate cluster. - - The version of the aggregate is incremented with every event raised, which is true - in the case of Event Sourced Aggregates. - - Event is immutable, so we clone a new event object from the event raised, - and add the enhanced metadata to it. - """ - # Verify that event is indeed associated with this aggregate - if event.meta_.part_of != self.__class__: - raise ConfigurationError( - f"Event `{event.__class__.__name__}` is not associated with " - f"aggregate `{self.__class__.__name__}`" - ) - - if not fact_event: - self._version += 1 - - identifier = getattr(self, id_field(self).field_name) - - # Set Fact Event stream to be `-fact` - if event.__class__.__name__.endswith("FactEvent"): - stream = f"{self.meta_.stream_category}-fact-{identifier}" - else: - stream = f"{self.meta_.stream_category}-{identifier}" - - event_with_metadata = event.__class__( - event.to_dict(), - _expected_version=self._event_position, - _metadata={ - "id": (f"{stream}-{self._version}"), - "type": event._metadata.type, - "fqn": event._metadata.fqn, - "kind": event._metadata.kind, - "stream": stream, - "origin_stream": event._metadata.origin_stream, - "timestamp": event._metadata.timestamp, - "version": event._metadata.version, - "sequence_id": self._version, - "payload_hash": hash( - json.dumps( - event.payload, - sort_keys=True, - ) - ), - }, - ) - - # Increment the event position after generating event - self._event_position = self._event_position + 1 - - self._events.append(event_with_metadata) - - class IdentityMixin: def __init_subclass__(subclass) -> None: super().__init_subclass__() diff --git a/src/protean/core/aggregate.py b/src/protean/core/aggregate.py index 65f83c81..387345bb 100644 --- a/src/protean/core/aggregate.py +++ b/src/protean/core/aggregate.py @@ -1,15 +1,20 @@ """Aggregate Functionality and Classes""" +import functools import inspect import logging +import typing +from collections import defaultdict +from typing import List from protean.core.entity import BaseEntity from protean.core.event import BaseEvent from protean.core.value_object import BaseValueObject -from protean.exceptions import NotSupportedError -from protean.fields import HasMany, HasOne, Integer, List, Reference, ValueObject +from protean.exceptions import IncorrectUsageError, NotSupportedError +from protean.fields import HasMany, HasOne, Integer, Reference, ValueObject +from protean.fields import List as ProteanList from protean.reflection import fields -from protean.utils import DomainObjects, derive_element_class, inflection +from protean.utils import DomainObjects, derive_element_class, fqn, inflection logger = logging.getLogger(__name__) @@ -56,6 +61,23 @@ def __new__(cls, *args, **kwargs): # a single aggregate update could have triggered multiple events. _event_position = -1 + def __init_subclass__(subclass) -> None: + super().__init_subclass__() + + # Event-Sourcing Functionality + # + # Associate a `_projections` map with subclasses. + # It needs to be initialized here because if it + # were initialized in __init__, the same collection object + # would be made available across all subclasses, + # defeating its purpose. + setattr(subclass, "_projections", defaultdict(set)) + + # Event-Sourcing Functionality + # + # Store associated events + setattr(subclass, "_events_cls_map", {}) + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -73,12 +95,51 @@ def _default_options(cls): ("aggregate_cluster", None), ("auto_add_id_field", True), ("fact_events", False), + ("is_event_sourced", False), ("model", None), ("provider", "default"), ("schema_name", inflection.underscore(cls.__name__)), ("stream_category", inflection.underscore(cls.__name__)), ] + def _apply(self, event: BaseEvent) -> None: + """Event-Sourcing Functionality + + Apply the event onto the aggregate by calling the appropriate projection. + + Args: + event (BaseEvent): Event object to apply + """ + # FIXME Handle case of missing projection + event_name = fqn(event.__class__) + + # FIXME Handle case of missing projection method + if event_name not in self._projections: + raise NotImplementedError( + f"No handler registered for event `{event_name}` in `{self.__class__.__name__}`" + ) + + for fn in self._projections[event_name]: + # Call event handler method + fn(self, event) + self._version += 1 + + @classmethod + def from_events(cls, events: List[BaseEvent]) -> "BaseAggregate": + """Event-Sourcing Functionality + + Reconstruct an aggregate from a list of events. + """ + # Initialize the aggregate with the first event's payload and apply it + aggregate = cls(**events[0].payload) + aggregate._apply(events[0]) + + # Apply the rest of the events + for event in events[1:]: + aggregate._apply(event) + + return aggregate + def element_to_fact_event(element_cls): """Convert an Element to a Fact Event. @@ -94,9 +155,7 @@ def element_to_fact_event(element_cls): The target class of associations is constructed as the Value Object. """ # Gather all fields defined in the element, except References. - # We ignore references in event payloads. We also ignore - # the `_next_version` field because it is a temporary in-flight - # field used to track the next version of the aggregate. + # We ignore references in event payloads. attrs = { key: value for key, value in fields(element_cls).items() @@ -108,7 +167,7 @@ def element_to_fact_event(element_cls): if isinstance(value, HasOne): attrs[key] = element_to_fact_event(value.to_cls) elif isinstance(value, HasMany): - attrs[key] = List(content_type=element_to_fact_event(value.to_cls)) + attrs[key] = ProteanList(content_type=element_to_fact_event(value.to_cls)) # If we are dealing with an Entity, we convert it to a Value Object # and return it. @@ -160,6 +219,16 @@ def aggregate_factory(element_cls, domain, **opts): f"{domain.normalized_name}::{element_cls.meta_.stream_category}" ) + # Event-Sourcing Functionality + # Iterate through methods marked as `@apply` and construct a projections map + methods = inspect.getmembers(element_cls, predicate=inspect.isroutine) + for method_name, method in methods: + if not ( + method_name.startswith("__") and method_name.endswith("__") + ) and hasattr(method, "_event_cls"): + element_cls._projections[fqn(method._event_cls)].add(method) + element_cls._events_cls_map[fqn(method._event_cls)] = method._event_cls + return element_cls @@ -178,3 +247,46 @@ def __exit__(self, *args): # Validate on exit to trigger invariant checks self.aggregate._disable_invariant_checks = False self.aggregate._postcheck() + + +def apply(fn): + """Event-Sourcing Functionality + + Decorator to mark methods in EventHandler classes. + """ + + if len(typing.get_type_hints(fn)) > 2: + raise IncorrectUsageError( + { + "_entity": [ + f"Handler method `{fn.__name__}` has incorrect number of arguments" + ] + } + ) + + try: + _event_cls = next( + iter( + { + value + for value in typing.get_type_hints(fn).values() + if inspect.isclass(value) and issubclass(value, BaseEvent) + } + ) + ) + except StopIteration: + raise IncorrectUsageError( + { + "_entity": [ + f"Apply method `{fn.__name__}` should accept an argument annotated with the Event class" + ] + } + ) + + @functools.wraps(fn) + def wrapper(*args): + fn(*args) + + setattr(wrapper, "_event_cls", _event_cls) + + return wrapper diff --git a/src/protean/core/entity.py b/src/protean/core/entity.py index 5964000c..a101f616 100644 --- a/src/protean/core/entity.py +++ b/src/protean/core/entity.py @@ -440,21 +440,6 @@ def raise_(self, event) -> None: f" aggregate `{self._root.__class__.__name__}`" ) - # Events are sometimes raised from within the aggregate, well-before persistence. - # In that case, the aggregate's next version has to be considered in events, - # because we want to associate the event with the version that will be persisted. - # - # Other times, an event is generated after persistence, like in the case of - # fact events. In this case, the aggregate's current version and next version - # will be the same. - # - # So we simply take the latest version, among `_version` and `_next_version`. - aggregate_version = max(self._root._version, self._root._next_version) - - # This is just a counter to uniquely gather all events generated - # in the same edit session - event_number = len(self._root._events) + 1 - identifier = getattr(self._root, id_field(self._root).field_name) # Set Fact Event stream to be `-fact` @@ -463,11 +448,42 @@ def raise_(self, event) -> None: else: stream = f"{self._root.meta_.stream_category}-{identifier}" + if self._root.meta_.is_event_sourced: + # The version of the aggregate is incremented with every event raised, which is true + # in the case of Event Sourced Aggregates. + # + # Except for Fact Events. Fact Events are raised after the aggregate has been persisted, + if not event.__class__.__name__.endswith("FactEvent"): + self._version += 1 + + event_identity = f"{stream}-{self._version}" + sequence_id = f"{self._version}" + else: + # Events are sometimes raised from within the aggregate, well-before persistence. + # In that case, the aggregate's next version has to be considered in events, + # because we want to associate the event with the version that will be persisted. + # + # Other times, an event is generated after persistence, like in the case of + # fact events. In this case, the aggregate's current version and next version + # will be the same. + # + # So we simply take the latest version, among `_version` and `_next_version`. + aggregate_version = max(self._root._version, self._root._next_version) + + # This is just a counter to uniquely gather all events generated + # in the same edit session + event_number = len(self._root._events) + 1 + + event_identity = f"{stream}-{aggregate_version}.{event_number}" + sequence_id = f"{aggregate_version}.{event_number}" + + # Event is immutable, so we clone a new event object from the event raised, + # and add the enhanced metadata to it. event_with_metadata = event.__class__( event.to_dict(), _expected_version=self._root._event_position, _metadata={ - "id": (f"{stream}-{aggregate_version}.{event_number}"), + "id": event_identity, "type": event._metadata.type, "fqn": event._metadata.fqn, "kind": event._metadata.kind, @@ -475,7 +491,7 @@ def raise_(self, event) -> None: "origin_stream": event._metadata.origin_stream, "timestamp": event._metadata.timestamp, "version": event._metadata.version, - "sequence_id": f"{aggregate_version}.{event_number}", + "sequence_id": sequence_id, "payload_hash": hash( json.dumps( event.payload, diff --git a/src/protean/core/event_sourced_aggregate.py b/src/protean/core/event_sourced_aggregate.py deleted file mode 100644 index 37ab28e2..00000000 --- a/src/protean/core/event_sourced_aggregate.py +++ /dev/null @@ -1,183 +0,0 @@ -import functools -import inspect -import logging -import typing -from collections import defaultdict -from typing import List - -from protean.container import BaseContainer, EventedMixin, IdentityMixin, OptionsMixin -from protean.core.event import BaseEvent -from protean.exceptions import IncorrectUsageError, NotSupportedError -from protean.fields import Integer -from protean.reflection import id_field -from protean.utils import ( - DomainObjects, - derive_element_class, - fully_qualified_name, - inflection, -) - -logger = logging.getLogger(__name__) - - -class BaseEventSourcedAggregate( - OptionsMixin, IdentityMixin, EventedMixin, BaseContainer -): - """Base Event Sourced Aggregate class that all EventSourced Aggregates should inherit from. - - The order of inheritance is important. We want BaseContainer to be initialised first followed by - OptionsMixin (so that `meta_` is in place) before inheriting other mixins.""" - - element_type = DomainObjects.EVENT_SOURCED_AGGREGATE - - # Track current version of Aggregate - _version = Integer(default=-1) - - # Temporary variable to track version of events of Aggregate - # This can be different from the version of the Aggregate itself because - # a single aggregate update could have triggered multiple events. - _event_position = -1 - - def __new__(cls, *args, **kwargs): - if cls is BaseEventSourcedAggregate: - raise NotSupportedError("BaseEventSourcedAggregate cannot be instantiated") - return super().__new__(cls) - - @classmethod - def _default_options(cls): - return [ - ("aggregate_cluster", None), - ("auto_add_id_field", True), - ("fact_events", False), - ("stream_category", inflection.underscore(cls.__name__)), - ] - - def __init_subclass__(subclass) -> None: - super().__init_subclass__() - - # Associate a `_projections` map with subclasses. - # It needs to be initialized here because if it - # were initialized in __init__, the same collection object - # would be made available across all subclasses, - # defeating its purpose. - setattr(subclass, "_projections", defaultdict(set)) - - # Store associated events - setattr(subclass, "_events_cls_map", {}) - - def __eq__(self, other): - """Equivalence check to be based only on Identity""" - - # FIXME Enhanced Equality Checks - # * Ensure IDs have values and both of them are not null - # * Ensure that the ID is of the right type - # * Ensure that Objects belong to the same `type` - # * Check Reference equality - - # FIXME Check if `==` and `in` operator work with __eq__ - - if type(other) is type(self): - self_id = getattr(self, id_field(self).field_name) - other_id = getattr(other, id_field(other).field_name) - - return self_id == other_id - - return False - - def __hash__(self): - """Overrides the default implementation and bases hashing on identity""" - - # FIXME Add Object Class Type to hash - return hash(getattr(self, id_field(self).field_name)) - - def _apply(self, event: BaseEvent) -> None: - """Apply the event onto the aggregate by calling the appropriate projection. - - Args: - event (BaseEvent): Event object to apply - """ - # FIXME Handle case of missing projection - event_name = fully_qualified_name(event.__class__) - - # FIXME Handle case of missing projection method - if event_name not in self._projections: - raise NotImplementedError( - f"No handler registered for event `{event_name}` in `{self.__class__.__name__}`" - ) - - for fn in self._projections[event_name]: - # Call event handler method - fn(self, event) - self._version += 1 - - @classmethod - def from_events(cls, events: List[BaseEvent]) -> "BaseEventSourcedAggregate": - """Reconstruct an aggregate from a list of events.""" - # Initialize the aggregate with the first event's payload and apply it - aggregate = cls(**events[0].payload) - aggregate._apply(events[0]) - - # Apply the rest of the events - for event in events[1:]: - aggregate._apply(event) - - return aggregate - - -def apply(fn): - """Decorator to mark methods in EventHandler classes.""" - - if len(typing.get_type_hints(fn)) > 2: - raise IncorrectUsageError( - { - "_entity": [ - f"Handler method `{fn.__name__}` has incorrect number of arguments" - ] - } - ) - - try: - _event_cls = next( - iter( - { - value - for value in typing.get_type_hints(fn).values() - if inspect.isclass(value) and issubclass(value, BaseEvent) - } - ) - ) - except StopIteration: - raise IncorrectUsageError( - { - "_entity": [ - f"Apply method `{fn.__name__}` should accept an argument annotated with the Event class" - ] - } - ) - - @functools.wraps(fn) - def wrapper(*args): - fn(*args) - - setattr(wrapper, "_event_cls", _event_cls) - - return wrapper - - -def event_sourced_aggregate_factory(element_cls, domain, **opts): - element_cls = derive_element_class(element_cls, BaseEventSourcedAggregate, **opts) - - # Iterate through methods marked as `@apply` and construct a projections map - methods = inspect.getmembers(element_cls, predicate=inspect.isroutine) - for method_name, method in methods: - if not ( - method_name.startswith("__") and method_name.endswith("__") - ) and hasattr(method, "_event_cls"): - element_cls._projections[fully_qualified_name(method._event_cls)].add( - method - ) - element_cls._events_cls_map[fully_qualified_name(method._event_cls)] = ( - method._event_cls - ) - - return element_cls diff --git a/src/protean/core/event_sourced_repository.py b/src/protean/core/event_sourced_repository.py index 949109dc..f605504d 100644 --- a/src/protean/core/event_sourced_repository.py +++ b/src/protean/core/event_sourced_repository.py @@ -1,6 +1,6 @@ import logging -from protean import BaseEventSourcedAggregate, UnitOfWork +from protean import BaseAggregate, UnitOfWork from protean.container import Element, OptionsMixin from protean.exceptions import ( IncorrectUsageError, @@ -30,7 +30,7 @@ def __new__(cls, *args, **kwargs): def __init__(self, domain) -> None: self._domain = domain - def add(self, aggregate: BaseEventSourcedAggregate) -> None: + def add(self, aggregate: BaseAggregate) -> None: if aggregate is None: raise IncorrectUsageError( {"_entity": ["Aggregate object to persist is invalid"]} @@ -55,7 +55,7 @@ def add(self, aggregate: BaseEventSourcedAggregate) -> None: # Construct and raise the Fact Event fact_event_obj = aggregate._fact_event_cls(**payload) - aggregate.raise_(fact_event_obj, fact_event=True) + aggregate.raise_(fact_event_obj) uow._add_to_identity_map(aggregate) @@ -63,7 +63,7 @@ def add(self, aggregate: BaseEventSourcedAggregate) -> None: if own_current_uow: own_current_uow.commit() - def get(self, identifier: Identifier) -> BaseEventSourcedAggregate: + def get(self, identifier: Identifier) -> BaseAggregate: """Retrieve a fully-formed Aggregate from a stream of Events. If the aggregate was already loaded in the current UnitOfWork, @@ -113,7 +113,7 @@ def event_sourced_repository_factory(element_cls, domain, **opts): } ) - if not issubclass(element_cls.meta_.part_of, BaseEventSourcedAggregate): + if not element_cls.meta_.part_of.meta_.is_event_sourced: raise IncorrectUsageError( { "_entity": [ diff --git a/src/protean/domain/__init__.py b/src/protean/domain/__init__.py index 56fb406c..a0c2545f 100644 --- a/src/protean/domain/__init__.py +++ b/src/protean/domain/__init__.py @@ -445,7 +445,6 @@ def factory_for(self, domain_object_type): from protean.core.entity import entity_factory from protean.core.event import domain_event_factory from protean.core.event_handler import event_handler_factory - from protean.core.event_sourced_aggregate import event_sourced_aggregate_factory from protean.core.event_sourced_repository import ( event_sourced_repository_factory, ) @@ -463,7 +462,6 @@ def factory_for(self, domain_object_type): DomainObjects.COMMAND_HANDLER.value: command_handler_factory, DomainObjects.EVENT.value: domain_event_factory, DomainObjects.EVENT_HANDLER.value: event_handler_factory, - DomainObjects.EVENT_SOURCED_AGGREGATE.value: event_sourced_aggregate_factory, DomainObjects.EVENT_SOURCED_REPOSITORY.value: event_sourced_repository_factory, DomainObjects.DOMAIN_SERVICE.value: domain_service_factory, DomainObjects.EMAIL.value: email_factory, @@ -568,7 +566,6 @@ def _resolve_references(self): field_obj.to_cls, ( DomainObjects.AGGREGATE, - DomainObjects.EVENT_SOURCED_AGGREGATE, DomainObjects.ENTITY, ), ) @@ -584,10 +581,7 @@ def _resolve_references(self): cls = params to_cls = self.fetch_element_cls_from_registry( cls.meta_.part_of, - ( - DomainObjects.AGGREGATE, - DomainObjects.EVENT_SOURCED_AGGREGATE, - ), + (DomainObjects.AGGREGATE,), ) cls.meta_.part_of = to_cls case _: @@ -763,9 +757,13 @@ def _validate_domain(self): # Check that no two event sourced aggregates have the same event class in their # `_events_cls_map`. - event_sourced_aggregates = self.registry._elements[ - DomainObjects.EVENT_SOURCED_AGGREGATE.value - ] + event_sourced_aggregates = { + name: record + for (name, record) in self.registry._elements[ + DomainObjects.AGGREGATE.value + ].items() + if record.cls.meta_.is_event_sourced is True + } # Collect all event class names from `_events_cls_map` of all event sourced aggregates event_class_names = list() for event_sourced_aggregate in event_sourced_aggregates.values(): @@ -806,12 +804,10 @@ def _validate_domain(self): def _assign_aggregate_clusters(self): """Assign Aggregate Clusters to all relevant elements""" from protean.core.aggregate import BaseAggregate - from protean.core.event_sourced_aggregate import BaseEventSourcedAggregate # Assign Aggregates and EventSourcedAggregates to their own cluster for element_type in [ DomainObjects.AGGREGATE, - DomainObjects.EVENT_SOURCED_AGGREGATE, ]: for _, element in self.registry._elements[element_type.value].items(): element.cls.meta_.aggregate_cluster = element.cls @@ -826,9 +822,7 @@ def _assign_aggregate_clusters(self): part_of = element.cls.meta_.part_of if part_of: # Traverse up the graph tree to find the root aggregate - while not issubclass( - part_of, (BaseAggregate, BaseEventSourcedAggregate) - ): + while not issubclass(part_of, BaseAggregate): part_of = part_of.meta_.part_of element.cls.meta_.aggregate_cluster = part_of @@ -974,14 +968,12 @@ def _setup_event_handlers(self): def _generate_fact_event_classes(self): """Generate FactEvent classes for all aggregates with `fact_events` enabled""" - for element_type in [ - DomainObjects.AGGREGATE, - DomainObjects.EVENT_SOURCED_AGGREGATE, - ]: - for _, element in self.registry._elements[element_type.value].items(): - if element.cls.meta_.fact_events: - event_cls = element_to_fact_event(element.cls) - self.register(event_cls, part_of=element.cls) + for _, element in self.registry._elements[ + DomainObjects.AGGREGATE.value + ].items(): + if element.cls.meta_.fact_events: + event_cls = element_to_fact_event(element.cls) + self.register(event_cls, part_of=element.cls) ###################### # Element Decorators # @@ -1025,13 +1017,6 @@ def event_handler(self, _cls=None, **kwargs): **kwargs, ) - def event_sourced_aggregate(self, _cls=None, **kwargs): - return self._domain_element( - DomainObjects.EVENT_SOURCED_AGGREGATE, - _cls=_cls, - **kwargs, - ) - def domain_service(self, _cls=None, **kwargs): return self._domain_element( DomainObjects.DOMAIN_SERVICE, @@ -1211,20 +1196,25 @@ def handlers_for(self, event: BaseEvent) -> List[BaseEventHandler]: ############################ # FIXME Optimize calls to this method with cache, but also with support for Multitenancy - def repository_for(self, part_of) -> BaseRepository: - if isinstance(part_of, str): + def repository_for(self, element_cls) -> BaseRepository: + if isinstance(element_cls, str): raise IncorrectUsageError( { "element": [ - f"Element {part_of} is not registered in domain {self.name}" + f"Element {element_cls} is not registered in domain {self.name}" ] } ) - if part_of.element_type == DomainObjects.EVENT_SOURCED_AGGREGATE: - return self.event_store.repository_for(part_of) + if ( + element_cls.element_type == DomainObjects.AGGREGATE + and element_cls.meta_.is_event_sourced + ): + # Return an Event Sourced repository + return self.event_store.repository_for(element_cls) else: - return self.providers.repository_for(part_of) + # This is a regular aggregate or a view + return self.providers.repository_for(element_cls) ####################### # Cache Functionality # diff --git a/src/protean/fields/association.py b/src/protean/fields/association.py index e7884244..83c3a78c 100644 --- a/src/protean/fields/association.py +++ b/src/protean/fields/association.py @@ -377,6 +377,9 @@ def __set__(self, instance, value): The `temp_cache` we set up here is eventually used by the `Repository` to determine the changes to be persisted. """ + # Accept dictionary values and convert them to Entity objects + if isinstance(value, dict): + value = self.to_cls(**value) super().__set__(instance, value) @@ -481,10 +484,20 @@ def __set__(self, instance, value): """This supports direct assignment of values to HasMany fields, like: `order.items = [item1, item2, item3]` """ - super().__set__(instance, value) + value = value if isinstance(value, list) else [value] + + # Accept dictionary values and convert them to Entity objects + values = [] + for item in value: + if isinstance(item, dict): + values.append(self.to_cls(**item)) + else: + values.append(item) + + super().__set__(instance, values) if value is not None: - self.add(instance, value) + self.add(instance, values) def add(self, instance, items) -> None: """ diff --git a/src/protean/port/event_store.py b/src/protean/port/event_store.py index 586dcc1d..2d1e0b1c 100644 --- a/src/protean/port/event_store.py +++ b/src/protean/port/event_store.py @@ -2,7 +2,7 @@ from collections import deque from typing import Any, Dict, List, Optional, Type, Union -from protean import BaseCommand, BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseCommand, BaseEvent from protean.fields import Identifier from protean.utils.mixins import Message @@ -104,8 +104,8 @@ def append(self, object: Union[BaseEvent, BaseCommand]) -> int: return position def load_aggregate( - self, part_of: Type[BaseEventSourcedAggregate], identifier: Identifier - ) -> Optional[BaseEventSourcedAggregate]: + self, part_of: Type[BaseAggregate], identifier: Identifier + ) -> Optional[BaseAggregate]: """Load an aggregate from underlying events. The first event is used to initialize the aggregate, after which each event is diff --git a/src/protean/utils/__init__.py b/src/protean/utils/__init__.py index b2b29712..7fed2290 100644 --- a/src/protean/utils/__init__.py +++ b/src/protean/utils/__init__.py @@ -4,7 +4,6 @@ to the maximum extent possible. """ -import functools import importlib import logging from datetime import UTC, datetime @@ -73,14 +72,6 @@ def get_version(): return importlib.metadata.version("protean") -def import_from_full_path(domain, path): - spec = importlib.util.spec_from_file_location(domain, path) - mod = importlib.util.module_from_spec(spec) - spec.loader.exec_module(mod) - - return getattr(mod, domain) - - def fully_qualified_name(cls): """Return Fully Qualified name along with module""" return ".".join([cls.__module__, cls.__qualname__]) @@ -89,19 +80,6 @@ def fully_qualified_name(cls): fqn = fully_qualified_name -def singleton(cls): - """Make a class a Singleton class (only one instance)""" - - @functools.wraps(cls) - def wrapper_singleton(*args, **kwargs): - if not wrapper_singleton.instance: - wrapper_singleton.instance = cls(*args, **kwargs) - return wrapper_singleton.instance - - wrapper_singleton.instance = None - return wrapper_singleton - - def convert_str_values_to_list(value): if not value: return [] @@ -118,7 +96,6 @@ class DomainObjects(Enum): COMMAND_HANDLER = "COMMAND_HANDLER" EVENT = "EVENT" EVENT_HANDLER = "EVENT_HANDLER" - EVENT_SOURCED_AGGREGATE = "EVENT_SOURCED_AGGREGATE" EVENT_SOURCED_REPOSITORY = "EVENT_SOURCED_REPOSITORY" DOMAIN_SERVICE = "DOMAIN_SERVICE" EMAIL = "EMAIL" @@ -185,7 +162,7 @@ def generate_identity( elif id_type == IdentityType.UUID.value: id_value = uuid4() else: - raise ConfigurationError(f"Unknown Identity Type {id_type}") + raise ConfigurationError(f"Unknown Identity Type '{id_type}'") # Function Strategy elif id_strategy == IdentityStrategy.FUNCTION.value: @@ -215,7 +192,5 @@ def generate_identity( "fully_qualified_name", "generate_identity", "get_version", - "import_from_full_path", - "singleton", "utcnow_func", ] diff --git a/tests/aggregate/events/test_event_association_with_aggregate.py b/tests/aggregate/events/test_event_association_with_aggregate.py index a77d0144..dcd0d6c5 100644 --- a/tests/aggregate/events/test_event_association_with_aggregate.py +++ b/tests/aggregate/events/test_event_association_with_aggregate.py @@ -1,6 +1,6 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseEvent from protean.exceptions import ConfigurationError from protean.fields import Identifier, String @@ -24,7 +24,7 @@ class UserArchived(BaseEvent): user_id = Identifier(required=True) -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): user_id = Identifier(identifier=True) name = String(max_length=50, required=True) email = String(required=True) @@ -53,7 +53,7 @@ class UserUnknownEvent(BaseEvent): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(UserRegistered, part_of=User) test_domain.register(UserActivated, part_of=User) test_domain.register(UserRenamed, part_of=User) @@ -69,7 +69,7 @@ def test_an_unassociated_event_throws_error(test_domain): def test_that_event_associated_with_another_aggregate_throws_error(test_domain): - test_domain.register(User2) + test_domain.register(User2, is_event_sourced=True) test_domain.register(UserUnknownEvent, part_of=User2) test_domain.init(traverse=False) diff --git a/tests/command/test_command_basics.py b/tests/command/test_command_basics.py index 3b346fa4..086efdbb 100644 --- a/tests/command/test_command_basics.py +++ b/tests/command/test_command_basics.py @@ -1,8 +1,8 @@ -from protean import BaseCommand, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseCommand from protean.fields import Identifier, String -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): id = Identifier(identifier=True) email = String() name = String() @@ -15,6 +15,7 @@ class Register(BaseCommand): def test_domain_stores_command_type_for_easy_retrieval(test_domain): + test_domain.register(User, is_event_sourced=True) test_domain.register(Register, part_of=User) test_domain.init(traverse=False) diff --git a/tests/command/test_command_meta.py b/tests/command/test_command_meta.py index b252ff46..70cb15f1 100644 --- a/tests/command/test_command_meta.py +++ b/tests/command/test_command_meta.py @@ -2,13 +2,13 @@ import pytest -from protean import BaseCommand, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseCommand from protean.exceptions import IncorrectUsageError from protean.fields import String from protean.fields.basic import Identifier -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): id = Identifier(identifier=True) email = String() name = String() @@ -21,7 +21,7 @@ class Register(BaseCommand): def test_command_definition_without_aggregate_or_stream(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) with pytest.raises(IncorrectUsageError) as exc: test_domain.register(Register) @@ -47,7 +47,7 @@ class AbstractCommand(BaseCommand): @pytest.mark.eventstore def test_command_associated_with_aggregate(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Register, part_of=User) test_domain.init(traverse=False) @@ -60,15 +60,15 @@ def test_command_associated_with_aggregate(test_domain): ) ) - messages = test_domain.event_store.store.read("user:command") + messages = test_domain.event_store.store.read("test::user:command") assert len(messages) == 1 - messages[0].stream_name == f"user:command-{identifier}" + messages[0].stream_name == f"test::user:command-{identifier}" @pytest.mark.eventstore def test_command_associated_with_aggregate_with_custom_stream_name(test_domain): - test_domain.register(User, stream_category="foo") + test_domain.register(User, is_event_sourced=True, stream_category="foo") test_domain.register(Register, part_of=User) test_domain.init(traverse=False) @@ -81,14 +81,14 @@ def test_command_associated_with_aggregate_with_custom_stream_name(test_domain): ) ) - messages = test_domain.event_store.store.read("foo:command") + messages = test_domain.event_store.store.read("test::foo:command") assert len(messages) == 1 - messages[0].stream_name == f"foo:command-{identifier}" + messages[0].stream_name == f"test::foo:command-{identifier}" def test_aggregate_cluster_of_event(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Register, part_of=User) test_domain.init(traverse=False) diff --git a/tests/command_handler/test_retrieving_handlers_by_command.py b/tests/command_handler/test_retrieving_handlers_by_command.py index c08e9013..e57c88b7 100644 --- a/tests/command_handler/test_retrieving_handlers_by_command.py +++ b/tests/command_handler/test_retrieving_handlers_by_command.py @@ -1,6 +1,6 @@ import pytest -from protean import BaseCommand, BaseEventSourcedAggregate, handle +from protean import BaseAggregate, BaseCommand, handle from protean.core.command_handler import BaseCommandHandler from protean.exceptions import ConfigurationError, NotSupportedError from protean.fields import Identifier, String, Text @@ -10,7 +10,7 @@ class UnknownCommand(BaseCommand): foo = String() -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): user_id = Identifier(identifier=True) # FIXME Auto-associate ID email = String() name = String() @@ -38,7 +38,7 @@ def register(self, _: Register) -> None: pass -class Post(BaseEventSourcedAggregate): +class Post(BaseAggregate): topic = String() content = Text() @@ -56,11 +56,11 @@ def create_new_post(self, _: Create): def test_retrieving_handler_by_command(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Register, part_of=User) test_domain.register(ChangeAddress, part_of=User) test_domain.register(UserCommandHandlers, part_of=User) - test_domain.register(Post) + test_domain.register(Post, is_event_sourced=True) test_domain.register(Create, part_of=Post) test_domain.register(PostCommandHandler, part_of=Post) test_domain.init(traverse=False) @@ -72,7 +72,7 @@ def test_retrieving_handler_by_command(test_domain): def test_for_no_errors_when_no_handler_method_has_not_been_defined_for_a_command( test_domain, ): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Register, part_of=User) test_domain.register(ChangeAddress, part_of=User) test_domain.register(UserCommandHandlers, part_of=User) @@ -92,7 +92,7 @@ def test_retrieving_handlers_for_unknown_command(test_domain): def test_error_on_defining_multiple_handlers_for_a_command(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(UserCommandHandlers, part_of=User) test_domain.register(AdminUserCommandHandlers, part_of=User) test_domain.init(traverse=False) diff --git a/tests/domain/test_aggregate_cluster_assignment.py b/tests/domain/test_aggregate_cluster_assignment.py index a224fd92..6e6f3250 100644 --- a/tests/domain/test_aggregate_cluster_assignment.py +++ b/tests/domain/test_aggregate_cluster_assignment.py @@ -5,7 +5,6 @@ BaseCommand, BaseEntity, BaseEvent, - BaseEventSourcedAggregate, ) from protean.fields import HasMany, HasOne, Identifier, Integer, String @@ -55,7 +54,7 @@ def test_aggregate_cluster_assignment(self): assert DepartmentClosed.meta_.aggregate_cluster == University -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): id = Identifier(identifier=True) email = String() name = String() @@ -77,7 +76,7 @@ class Registered(BaseEvent): class TestEventSourcedAggregateClusterAssignment: @pytest.fixture(autouse=True) def register_elements(self, test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Register, part_of=User) test_domain.register(Registered, part_of=User) test_domain.init(traverse=False) diff --git a/tests/event/test_event_meta.py b/tests/event/test_event_meta.py index 81457de5..363a6d39 100644 --- a/tests/event/test_event_meta.py +++ b/tests/event/test_event_meta.py @@ -1,12 +1,12 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseEvent from protean.exceptions import IncorrectUsageError from protean.fields import String from protean.fields.basic import Identifier -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): id = Identifier(identifier=True) email = String() name = String() @@ -18,7 +18,7 @@ class UserLoggedIn(BaseEvent): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(UserLoggedIn, part_of=User) test_domain.init(traverse=False) diff --git a/tests/event/test_event_metadata.py b/tests/event/test_event_metadata.py index 6699eeda..8e132afc 100644 --- a/tests/event/test_event_metadata.py +++ b/tests/event/test_event_metadata.py @@ -3,14 +3,14 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseEvent from protean.fields import String, ValueObject from protean.fields.basic import Identifier from protean.reflection import fields from protean.utils import fqn -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): id = Identifier(identifier=True) email = String() name = String() @@ -25,7 +25,7 @@ class UserLoggedIn(BaseEvent): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(UserLoggedIn, part_of=User) test_domain.init(traverse=False) @@ -133,15 +133,15 @@ def test_event_metadata(): assert event._metadata is not None assert isinstance(event._metadata.timestamp, datetime) - assert event._metadata.id == f"user-{user.id}-0" + assert event._metadata.id == f"test::user-{user.id}-0" assert event.to_dict() == { "_metadata": { - "id": f"user-{user.id}-0", + "id": f"test::user-{user.id}-0", "type": "Test.UserLoggedIn.v1", "fqn": fqn(UserLoggedIn), "kind": "EVENT", - "stream": f"user-{user.id}", + "stream": f"test::user-{user.id}", "origin_stream": None, "timestamp": str(event._metadata.timestamp), "version": "v1", diff --git a/tests/event/test_event_part_of_resolution.py b/tests/event/test_event_part_of_resolution.py index 7d30a076..5f21ff51 100644 --- a/tests/event/test_event_part_of_resolution.py +++ b/tests/event/test_event_part_of_resolution.py @@ -1,11 +1,11 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseEvent from protean.fields import String from protean.fields.basic import Identifier -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): id = Identifier(identifier=True) email = String() name = String() @@ -17,7 +17,7 @@ class UserLoggedIn(BaseEvent): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(UserLoggedIn, part_of="User") @@ -29,4 +29,4 @@ def test_event_has_stream_category_after_domain_init(test_domain): test_domain.init(traverse=False) assert UserLoggedIn.meta_.part_of == User - assert UserLoggedIn.meta_.part_of.meta_.stream_category == "user" + assert UserLoggedIn.meta_.part_of.meta_.stream_category == "test::user" diff --git a/tests/event/test_event_payload.py b/tests/event/test_event_payload.py index 79082fb2..552d9ecd 100644 --- a/tests/event/test_event_payload.py +++ b/tests/event/test_event_payload.py @@ -2,13 +2,13 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseEvent from protean.fields import String from protean.fields.basic import Identifier from protean.utils import fqn -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): id = Identifier(identifier=True) email = String() name = String() @@ -23,7 +23,7 @@ class UserLoggedIn(BaseEvent): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(UserLoggedIn, part_of=User) test_domain.init(traverse=False) @@ -37,11 +37,11 @@ def test_event_payload(): assert event.to_dict() == { "_metadata": { - "id": f"user-{user_id}-0", + "id": f"test::user-{user_id}-0", "type": "Test.UserLoggedIn.v1", "fqn": fqn(UserLoggedIn), "kind": "EVENT", - "stream": f"user-{user_id}", + "stream": f"test::user-{user_id}", "origin_stream": None, "timestamp": str(event._metadata.timestamp), "version": "v1", diff --git a/tests/event/test_event_properties.py b/tests/event/test_event_properties.py index f72b826b..f97afde2 100644 --- a/tests/event/test_event_properties.py +++ b/tests/event/test_event_properties.py @@ -2,13 +2,13 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseEvent from protean.exceptions import IncorrectUsageError from protean.fields import Identifier, String from protean.reflection import id_field -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): user_id = Identifier(identifier=True) email = String() name = String() @@ -22,7 +22,7 @@ class Registered(BaseEvent): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.init(traverse=False) diff --git a/tests/event/test_raising_events.py b/tests/event/test_raising_events.py index c2b5f895..ff5dab55 100644 --- a/tests/event/test_raising_events.py +++ b/tests/event/test_raising_events.py @@ -2,12 +2,12 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseEvent from protean.fields import String from protean.fields.basic import Identifier -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): id = Identifier(identifier=True) email = String() name = String() @@ -19,7 +19,7 @@ class UserLoggedIn(BaseEvent): @pytest.mark.eventstore def test_raising_event(test_domain): - test_domain.register(User, stream_category="authentication") + test_domain.register(User, is_event_sourced=True, stream_category="authentication") test_domain.register(UserLoggedIn, part_of=User) test_domain.init(traverse=False) @@ -29,7 +29,7 @@ def test_raising_event(test_domain): test_domain.repository_for(User).add(user) - messages = test_domain.event_store.store.read("authentication") + messages = test_domain.event_store.store.read("test::authentication") assert len(messages) == 1 - assert messages[0].stream_name == f"authentication-{identifier}" + assert messages[0].stream_name == f"test::authentication-{identifier}" diff --git a/tests/event_handler/test_retrieving_handlers_by_event.py b/tests/event_handler/test_retrieving_handlers_by_event.py index 49ac95b4..50b68a57 100644 --- a/tests/event_handler/test_retrieving_handlers_by_event.py +++ b/tests/event_handler/test_retrieving_handlers_by_event.py @@ -2,17 +2,17 @@ import pytest -from protean import BaseEvent, BaseEventHandler, BaseEventSourcedAggregate, handle +from protean import BaseAggregate, BaseEvent, BaseEventHandler, handle from protean.fields import DateTime, Identifier, String -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String() password_hash = String() -class Email(BaseEventSourcedAggregate): +class Email(BaseAggregate): email = String() sent_at = DateTime() @@ -73,13 +73,13 @@ def universal_handler(self, _: BaseEvent) -> None: @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.register(Activated, part_of=User) test_domain.register(Renamed, part_of=User) test_domain.register(UserEventHandler, part_of=User) test_domain.register(UserMetrics, part_of=User) - test_domain.register(Email) + test_domain.register(Email, is_event_sourced=True) test_domain.register(Sent, part_of=Email) test_domain.register(EmailEventHandler, part_of=Email) diff --git a/tests/event_sourced_aggregates/events/test_event_es_metadata_id_and_sequence.py b/tests/event_sourced_aggregates/events/test_event_es_metadata_id_and_sequence.py index 9bad33bc..07731c4f 100644 --- a/tests/event_sourced_aggregates/events/test_event_es_metadata_id_and_sequence.py +++ b/tests/event_sourced_aggregates/events/test_event_es_metadata_id_and_sequence.py @@ -2,12 +2,12 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseEvent from protean.fields import String from protean.fields.basic import Identifier -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): id = Identifier(identifier=True) email = String() name = String() @@ -22,7 +22,7 @@ class UserLoggedIn(BaseEvent): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(UserLoggedIn, part_of=User) test_domain.init(traverse=False) @@ -33,5 +33,5 @@ def test_event_is_generated_with_unique_id(): user.login() event = user._events[0] - assert event._metadata.id == f"user-{identifier}-0" + assert event._metadata.id == f"test::user-{identifier}-0" assert event._metadata.sequence_id == "0" diff --git a/tests/event_sourced_aggregates/events/test_fact_event_generation.py b/tests/event_sourced_aggregates/events/test_fact_event_generation.py index 62b3db58..6125ba32 100644 --- a/tests/event_sourced_aggregates/events/test_fact_event_generation.py +++ b/tests/event_sourced_aggregates/events/test_fact_event_generation.py @@ -1,6 +1,6 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate, apply +from protean import BaseAggregate, BaseEvent, apply from protean.fields import Identifier, String from protean.utils.mixins import Message @@ -16,7 +16,7 @@ class Renamed(BaseEvent): name = String() -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String() @@ -32,7 +32,7 @@ def renamed(self, event: Renamed) -> None: @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User, fact_events=True) + test_domain.register(User, is_event_sourced=True, fact_events=True) test_domain.register(Registered, part_of=User) test_domain.register(Renamed, part_of=User) test_domain.init(traverse=False) @@ -49,11 +49,13 @@ def test_generation_of_first_fact_event_on_persistence(test_domain): test_domain.repository_for(User).add(user) # Read event from event store - event_messages = test_domain.event_store.store.read(f"user-{user.id}") + event_messages = test_domain.event_store.store.read(f"test::user-{user.id}") assert len(event_messages) == 1 # Read fact events from event store - fact_event_messages = test_domain.event_store.store.read(f"user-fact-{user.id}") + fact_event_messages = test_domain.event_store.store.read( + f"test::user-fact-{user.id}" + ) assert len(fact_event_messages) == 1 # Deserialize event @@ -88,11 +90,13 @@ def test_generation_of_subsequent_fact_events_after_fetch(test_domain): test_domain.repository_for(User).add(refreshed_user) # Read event from event store - event_messages = test_domain.event_store.store.read(f"user-{user.id}") + event_messages = test_domain.event_store.store.read(f"test::user-{user.id}") assert len(event_messages) == 2 # Read fact events from event store - fact_event_messages = test_domain.event_store.store.read(f"user-fact-{user.id}") + fact_event_messages = test_domain.event_store.store.read( + f"test::user-fact-{user.id}" + ) assert len(fact_event_messages) == 2 # Deserialize 1st event and verify diff --git a/tests/event_sourced_aggregates/test_apply.py b/tests/event_sourced_aggregates/test_apply.py index 26a98b27..e9f23e6d 100644 --- a/tests/event_sourced_aggregates/test_apply.py +++ b/tests/event_sourced_aggregates/test_apply.py @@ -3,7 +3,7 @@ import pytest -from protean import BaseCommand, BaseEvent, BaseEventSourcedAggregate, apply +from protean import BaseAggregate, BaseCommand, BaseEvent, apply from protean.exceptions import IncorrectUsageError from protean.fields import Identifier, String from protean.utils import fqn @@ -30,7 +30,7 @@ class UserRenamed(BaseEvent): name = String(required=True, max_length=50) -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): user_id = Identifier(identifier=True) name = String(max_length=50, required=True) email = String(required=True) @@ -63,7 +63,7 @@ def renamed(self, event: UserRenamed): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(UserRegistered, part_of=User) test_domain.register(UserActivated, part_of=User) test_domain.register(UserRenamed, part_of=User) @@ -89,7 +89,7 @@ def test_apply_decorator_method_should_have_exactly_one_argument(): class Sent(BaseEvent): email_id = Identifier() - class _(BaseEventSourcedAggregate): + class _(BaseAggregate): email_id = Identifier(identifier=True) @apply @@ -108,7 +108,7 @@ class Send(BaseCommand): # Argument should be an event class with pytest.raises(IncorrectUsageError) as exc: - class _(BaseEventSourcedAggregate): + class _(BaseAggregate): email_id = Identifier(identifier=True) @apply @@ -124,7 +124,7 @@ def sent(self, _: Send) -> None: # Argument should be annotated with pytest.raises(IncorrectUsageError) as exc: - class _(BaseEventSourcedAggregate): + class _(BaseAggregate): email_id = Identifier(identifier=True) @apply @@ -140,7 +140,7 @@ def sent(self, _) -> None: # Argument should be supplied with pytest.raises(IncorrectUsageError) as exc: - class _(BaseEventSourcedAggregate): + class _(BaseAggregate): email_id = Identifier(identifier=True) @apply diff --git a/tests/event_sourced_aggregates/test_applying_events.py b/tests/event_sourced_aggregates/test_applying_events.py index ed909f05..0bd893e0 100644 --- a/tests/event_sourced_aggregates/test_applying_events.py +++ b/tests/event_sourced_aggregates/test_applying_events.py @@ -3,7 +3,7 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate, apply +from protean import BaseAggregate, BaseEvent, apply from protean.fields import Identifier, String @@ -28,7 +28,7 @@ class UserRenamed(BaseEvent): name = String(required=True, max_length=50) -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): user_id = Identifier(identifier=True) name = String(max_length=50, required=True) email = String(required=True) @@ -61,7 +61,7 @@ def renamed(self, event: UserRenamed): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(UserRegistered, part_of=User) test_domain.register(UserActivated, part_of=User) test_domain.register(UserRenamed, part_of=User) diff --git a/tests/event_sourced_aggregates/test_automatic_id_field.py b/tests/event_sourced_aggregates/test_automatic_id_field.py index 65ab95fb..b27f4c6e 100644 --- a/tests/event_sourced_aggregates/test_automatic_id_field.py +++ b/tests/event_sourced_aggregates/test_automatic_id_field.py @@ -1,15 +1,15 @@ -from protean import BaseEventSourcedAggregate +from protean import BaseAggregate from protean.fields import Auto, DateTime, Identifier, Integer, String from protean.reflection import declared_fields, fields, id_field from protean.utils import utcnow_func -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): name = String() age = Integer() -class Order(BaseEventSourcedAggregate): +class Order(BaseAggregate): order_id = Identifier(identifier=True) placed_at = DateTime() @@ -31,7 +31,7 @@ def test_no_auto_id_field_generation_when_an_identifier_is_provided(): def test_that_an_aggregate_can_opt_to_have_no_id_field_by_default(test_domain): - @test_domain.event_sourced_aggregate(auto_add_id_field=False) + @test_domain.aggregate(is_event_sourced=True, auto_add_id_field=False) class TimeStamped: created_at = DateTime(default=utcnow_func) updated_at = DateTime(default=utcnow_func) diff --git a/tests/event_sourced_aggregates/test_event_association_with_aggregate.py b/tests/event_sourced_aggregates/test_event_association_with_aggregate.py index cca03b21..85c49fc0 100644 --- a/tests/event_sourced_aggregates/test_event_association_with_aggregate.py +++ b/tests/event_sourced_aggregates/test_event_association_with_aggregate.py @@ -2,7 +2,7 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate, apply +from protean import BaseAggregate, BaseEvent, apply from protean.exceptions import ConfigurationError, IncorrectUsageError from protean.fields import Identifier, String @@ -32,7 +32,7 @@ class UserArchived(BaseEvent): user_id = Identifier(required=True) -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): user_id = Identifier(identifier=True) name = String(max_length=50, required=True) email = String(required=True) @@ -63,7 +63,7 @@ def renamed(self, event: UserRenamed): self.name = event.name -class Email(BaseEventSourcedAggregate): +class Email(BaseAggregate): email_id = Identifier(identifier=True) @apply @@ -73,11 +73,11 @@ def registered(self, _: UserRegistered): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(UserRegistered, part_of=User) test_domain.register(UserActivated, part_of=User) test_domain.register(UserRenamed, part_of=User) - test_domain.register(Email) + test_domain.register(Email, is_event_sourced=True) @pytest.mark.eventstore @@ -91,7 +91,7 @@ def test_that_event_is_associated_with_aggregate(): def test_that_trying_to_associate_an_event_with_multiple_aggregates_throws_an_error( test_domain, ): - test_domain.register(Email) + test_domain.register(Email, is_event_sourced=True) with pytest.raises(IncorrectUsageError) as exc: test_domain.init(traverse=False) diff --git a/tests/event_sourced_aggregates/test_event_sourced_aggregate_options.py b/tests/event_sourced_aggregates/test_event_sourced_aggregate_options.py index 260291cf..1c0c3f90 100644 --- a/tests/event_sourced_aggregates/test_event_sourced_aggregate_options.py +++ b/tests/event_sourced_aggregates/test_event_sourced_aggregate_options.py @@ -1,53 +1,53 @@ import pytest -from protean import BaseEventSourcedAggregate +from protean import BaseAggregate from protean.fields import Integer, String -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): name = String() age = Integer() -class AdminUser(BaseEventSourcedAggregate): +class AdminUser(BaseAggregate): name = String() -class Person(BaseEventSourcedAggregate): +class Person(BaseAggregate): name = String() age = Integer() @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) - test_domain.register(AdminUser) - test_domain.register(Person, stream_category="people") + test_domain.register(User, is_event_sourced=True) + test_domain.register(AdminUser, is_event_sourced=True) + test_domain.register(Person, is_event_sourced=True, stream_category="people") def test_stream_category_option_of_an_event_sourced_aggregate(): - assert User.meta_.stream_category == "user" + assert User.meta_.stream_category == "test::user" # Verify snake-casing the Aggregate name - assert AdminUser.meta_.stream_category == "admin_user" + assert AdminUser.meta_.stream_category == "test::admin_user" # Verify manually set stream_category - assert Person.meta_.stream_category == "people" + assert Person.meta_.stream_category == "test::people" def test_stream_category_option_of_an_event_sourced_aggregate_defined_via_annotation( test_domain, ): - @test_domain.event_sourced_aggregate - class Adult(BaseEventSourcedAggregate): + @test_domain.aggregate(is_event_sourced=True) + class Adult(BaseAggregate): name = String() age = Integer() - assert Adult.meta_.stream_category == "adult" + assert Adult.meta_.stream_category == "test::adult" - @test_domain.event_sourced_aggregate(stream_category="children") - class Child(BaseEventSourcedAggregate): + @test_domain.aggregate(is_event_sourced=True, stream_category="children") + class Child(BaseAggregate): name = String() age = Integer() - assert Child.meta_.stream_category == "children" + assert Child.meta_.stream_category == "test::children" diff --git a/tests/event_sourced_aggregates/test_event_sourced_aggregate_properties.py b/tests/event_sourced_aggregates/test_event_sourced_aggregate_properties.py index 46eb9547..e7ebf38a 100644 --- a/tests/event_sourced_aggregates/test_event_sourced_aggregate_properties.py +++ b/tests/event_sourced_aggregates/test_event_sourced_aggregate_properties.py @@ -2,27 +2,19 @@ import pytest -from protean import BaseEventSourcedAggregate -from protean.exceptions import NotSupportedError +from protean import BaseAggregate from protean.fields import Integer, String -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): name = String() age = Integer() -def test_event_sourced_aggregate_cannot_be_initialized(): - with pytest.raises(NotSupportedError) as exc: - BaseEventSourcedAggregate() - - assert str(exc.value) == "BaseEventSourcedAggregate cannot be instantiated" - - class TestEventSourcedAggregateEquivalence: @pytest.fixture(autouse=True) def register_elements(self, test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.init(traverse=False) def test_event_sourced_aggregate_are_not_equivalent_based_on_data(test_domain): @@ -50,7 +42,7 @@ class Person(User): def test_event_sourced_aggregate_hash(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) user1 = User(name="John Doe", age=25) assert hash(user1) == hash(user1.id) diff --git a/tests/event_sourced_aggregates/test_event_sourced_aggregate_registration.py b/tests/event_sourced_aggregates/test_event_sourced_aggregate_registration.py index 5f67e375..4ec906a6 100644 --- a/tests/event_sourced_aggregates/test_event_sourced_aggregate_registration.py +++ b/tests/event_sourced_aggregates/test_event_sourced_aggregate_registration.py @@ -1,28 +1,28 @@ import pytest -from protean import BaseEventSourcedAggregate +from protean import BaseAggregate from protean.fields import Integer, String from protean.utils import fully_qualified_name -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): name = String() age = Integer() def test_registering_an_event_sourced_aggregate_manually(test_domain): try: - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) except Exception: pytest.fail("Failed to register an Event Sourced Aggregate") - assert fully_qualified_name(User) in test_domain.registry.event_sourced_aggregates + assert fully_qualified_name(User) in test_domain.registry.aggregates def test_registering_an_event_sourced_aggregate_via_annotation(test_domain): try: - @test_domain.event_sourced_aggregate + @test_domain.aggregate(is_event_sourced=True) class Person: name = String() age = Integer() @@ -30,4 +30,4 @@ class Person: except Exception: pytest.fail("Failed to register an Event Sourced Aggregate via annotation") - assert fully_qualified_name(Person) in test_domain.registry.event_sourced_aggregates + assert fully_qualified_name(Person) in test_domain.registry.aggregates diff --git a/tests/event_sourced_aggregates/test_expected_version_error.py b/tests/event_sourced_aggregates/test_expected_version_error.py index 180c5789..3912ead9 100644 --- a/tests/event_sourced_aggregates/test_expected_version_error.py +++ b/tests/event_sourced_aggregates/test_expected_version_error.py @@ -3,7 +3,7 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate, UnitOfWork, apply +from protean import BaseAggregate, BaseEvent, UnitOfWork, apply from protean.exceptions import ExpectedVersionError from protean.fields import Identifier, String @@ -29,7 +29,7 @@ class UserRenamed(BaseEvent): name = String(required=True, max_length=50) -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): user_id = Identifier(identifier=True) name = String(max_length=50, required=True) email = String(required=True) @@ -62,7 +62,7 @@ def renamed(self, event: UserRenamed): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(UserRegistered, part_of=User) test_domain.register(UserActivated, part_of=User) test_domain.register(UserRenamed, part_of=User) @@ -94,5 +94,5 @@ def test_expected_version_error(test_domain): assert ( exc.value.args[0] - == f"Wrong expected version: 0 (Stream: user-{identifier}, Stream Version: 1)" + == f"Wrong expected version: 0 (Stream: test::user-{identifier}, Stream Version: 1)" ) diff --git a/tests/event_sourced_aggregates/test_generated_event_version.py b/tests/event_sourced_aggregates/test_generated_event_version.py index afc90683..00b28043 100644 --- a/tests/event_sourced_aggregates/test_generated_event_version.py +++ b/tests/event_sourced_aggregates/test_generated_event_version.py @@ -2,7 +2,7 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate, apply +from protean import BaseAggregate, BaseEvent, apply from protean.fields import Identifier, String from protean.utils.mixins import Message @@ -28,7 +28,7 @@ class UserRenamed(BaseEvent): name = String(required=True, max_length=50) -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): user_id = Identifier(identifier=True) name = String(max_length=50, required=True) email = String(required=True) @@ -61,7 +61,7 @@ def renamed(self, event: UserRenamed): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(UserRegistered, part_of=User) test_domain.register(UserActivated, part_of=User) test_domain.register(UserRenamed, part_of=User) @@ -79,7 +79,7 @@ def test_aggregate_and_event_version_after_first_persistence(test_domain): user = User.register(user_id="1", name="John Doe", email="john.doe@example.com") test_domain.repository_for(User).add(user) - event_messages = test_domain.event_store.store.read(f"user-{user.user_id}") + event_messages = test_domain.event_store.store.read(f"test::user-{user.user_id}") assert len(event_messages) == 1 refreshed_user = test_domain.repository_for(User).get(user.user_id) @@ -103,7 +103,7 @@ def test_aggregate_and_event_version_after_first_persistence_after_multiple_pers refreshed_user.change_name(f"John Doe {i}") test_domain.repository_for(User).add(refreshed_user) - event_messages = test_domain.event_store.store.read(f"user-{user.user_id}") + event_messages = test_domain.event_store.store.read(f"test::user-{user.user_id}") assert len(event_messages) == 11 refreshed_user = test_domain.repository_for(User).get(user.user_id) @@ -137,7 +137,7 @@ def test_aggregate_and_event_version_after_multiple_event_generation_in_one_upda assert refreshed_user._version == 1 - event_messages = test_domain.event_store.store.read(f"user-{user.user_id}") + event_messages = test_domain.event_store.store.read(f"test::user-{user.user_id}") assert len(event_messages) == 2 event1 = Message.to_object(event_messages[0]) diff --git a/tests/event_sourced_aggregates/test_initialization_from_events.py b/tests/event_sourced_aggregates/test_initialization_from_events.py index 532eefcd..745d4f9b 100644 --- a/tests/event_sourced_aggregates/test_initialization_from_events.py +++ b/tests/event_sourced_aggregates/test_initialization_from_events.py @@ -2,7 +2,7 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate, apply +from protean import BaseAggregate, BaseEvent, apply from protean.fields import Identifier, String @@ -21,7 +21,7 @@ class UserRenamed(BaseEvent): name = String(required=True, max_length=50) -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): user_id = Identifier(identifier=True) name = String(max_length=50, required=True) email = String(required=True) @@ -56,7 +56,7 @@ def renamed(self, event: UserRenamed): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(UserRegistered, part_of=User) test_domain.register(UserActivated, part_of=User) test_domain.register(UserRenamed, part_of=User) diff --git a/tests/event_sourced_aggregates/test_multi_level_aggregate_persistence.py b/tests/event_sourced_aggregates/test_multi_level_aggregate_persistence.py new file mode 100644 index 00000000..39c655c8 --- /dev/null +++ b/tests/event_sourced_aggregates/test_multi_level_aggregate_persistence.py @@ -0,0 +1,164 @@ +import pytest + +from protean import BaseAggregate, BaseEntity, BaseEvent, BaseValueObject, apply +from protean.fields import ( + HasMany, + HasOne, + Identifier, + Integer, + List, + String, + ValueObject, +) + + +class Department(BaseEntity): + name = String(max_length=50) + dean = HasOne("Dean") + + +class Dean(BaseEntity): + name = String(max_length=50) + age = Integer(min_value=21) + office = HasOne("Office") + + +class Office(BaseEntity): + building = String(max_length=25) + room = Integer(min_value=1) + + +class OfficeVO(BaseValueObject): + id = Identifier() + building = String(max_length=25) + room = Integer(min_value=1) + + +class DeanVO(BaseValueObject): + id = Identifier() + name = String(max_length=50) + age = Integer(min_value=21) + office = ValueObject(OfficeVO) + + +class DepartmentVO(BaseValueObject): + id = Identifier() + name = String(max_length=50) + dean = ValueObject(DeanVO) + + +class UniversityCreated(BaseEvent): + id = Identifier(identifier=True) + _version = Integer() + name = String(max_length=50) + departments = List(content_type=ValueObject(DepartmentVO)) + + +class NameChanged(BaseEvent): + id = Identifier(identifier=True) + name = String(max_length=50) + + +class University(BaseAggregate): + name = String(max_length=50) + departments = HasMany(Department) + + def raise_event(self): + self.raise_(UniversityCreated(**self.to_dict())) + + def change_name(self, name): + self.name = name + self.raise_(NameChanged(id=self.id, name=name)) + + @apply + def on_university_created(self, event: UniversityCreated): + # We are not doing anything here, because Protean applies + # the first event automatically, with from_events + pass + + @apply + def on_name_changed(self, event: NameChanged): + self.name = event.name + + +@pytest.fixture(autouse=True) +def register_elements(test_domain): + test_domain.register(University, is_event_sourced=True) + test_domain.register(Department, part_of=University) + test_domain.register(Dean, part_of=Department) + test_domain.register(Office, part_of=Dean) + test_domain.register(UniversityCreated, part_of=University) + test_domain.register(NameChanged, part_of=University) + test_domain.init(traverse=False) + + +@pytest.fixture +def university(test_domain): + university = University( + name="MIT", + departments=[ + Department( + name="Computer Science", + dean=Dean( + name="John Doe", age=45, office=Office(building="NE43", room=123) + ), + ), + Department( + name="Electrical Engineering", + dean=Dean( + name="Jane Smith", age=50, office=Office(building="NE43", room=124) + ), + ), + ], + ) + university.raise_event() + test_domain.repository_for(University).add(university) + + return university + + +def test_aggregate_persistence(test_domain, university): + refreshed_university = test_domain.repository_for(University).get(university.id) + + assert refreshed_university.id == university.id + assert refreshed_university.name == university.name + assert len(refreshed_university.departments) == 2 + assert refreshed_university.departments[0].name == university.departments[0].name + assert refreshed_university.departments[1].name == university.departments[1].name + assert ( + refreshed_university.departments[0].dean.name + == university.departments[0].dean.name + ) + assert ( + refreshed_university.departments[1].dean.name + == university.departments[1].dean.name + ) + assert ( + refreshed_university.departments[0].dean.office.building + == university.departments[0].dean.office.building + ) + assert ( + refreshed_university.departments[1].dean.office.building + == university.departments[1].dean.office.building + ) + assert ( + refreshed_university.departments[0].dean.office.room + == university.departments[0].dean.office.room + ) + assert ( + refreshed_university.departments[1].dean.office.room + == university.departments[1].dean.office.room + ) + + +def test_aggregate_persistence_after_update(test_domain, university): + refreshed_university = test_domain.repository_for(University).get(university.id) + + refreshed_university.change_name("Harvard") + test_domain.repository_for(University).add(refreshed_university) + + refreshed_university = test_domain.repository_for(University).get( + refreshed_university.id + ) + + assert refreshed_university.name == "Harvard" diff --git a/tests/event_sourced_aggregates/test_raising_events_from_within_aggregates.py b/tests/event_sourced_aggregates/test_raising_events_from_within_aggregates.py index 72864a13..76a39588 100644 --- a/tests/event_sourced_aggregates/test_raising_events_from_within_aggregates.py +++ b/tests/event_sourced_aggregates/test_raising_events_from_within_aggregates.py @@ -4,9 +4,8 @@ import pytest -from protean import BaseCommandHandler, BaseEvent, BaseEventSourcedAggregate, handle +from protean import BaseAggregate, BaseCommandHandler, BaseEvent, apply, handle from protean.core.command import BaseCommand -from protean.core.event_sourced_aggregate import apply from protean.fields import Identifier, String from protean.globals import current_domain @@ -25,7 +24,7 @@ class Registered(BaseEvent): password_hash = String() -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String() password_hash = String() @@ -64,7 +63,7 @@ def register_user(self, command: Register) -> None: @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Register, part_of=User) test_domain.register(Registered, part_of=User) test_domain.register(UserCommandHandler, part_of=User) @@ -83,14 +82,14 @@ def test_that_events_can_be_raised_from_within_aggregates(test_domain): ) ) - messages = test_domain.event_store.store._read("user") + messages = test_domain.event_store.store._read("test::user") assert len(messages) == 1 - assert messages[0]["stream_name"] == f"user-{identifier}" + assert messages[0]["stream_name"] == f"test::user-{identifier}" assert messages[0]["type"] == Registered.__type__ - messages = test_domain.event_store.store._read("user:command") + messages = test_domain.event_store.store._read("test::user:command") assert len(messages) == 1 - assert messages[0]["stream_name"] == f"user:command-{identifier}" + assert messages[0]["stream_name"] == f"test::user:command-{identifier}" assert messages[0]["type"] == Register.__type__ diff --git a/tests/event_sourced_aggregates/test_raising_multiple_events_for_one_aggregate_in_a_uow.py b/tests/event_sourced_aggregates/test_raising_multiple_events_for_one_aggregate_in_a_uow.py index c3548e80..62dee530 100644 --- a/tests/event_sourced_aggregates/test_raising_multiple_events_for_one_aggregate_in_a_uow.py +++ b/tests/event_sourced_aggregates/test_raising_multiple_events_for_one_aggregate_in_a_uow.py @@ -5,10 +5,10 @@ from uuid import uuid4 from protean import ( + BaseAggregate, BaseCommand, BaseCommandHandler, BaseEvent, - BaseEventSourcedAggregate, apply, handle, ) @@ -35,7 +35,7 @@ class Renamed(BaseEvent): name = String() -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): name = String() email = String() @@ -74,7 +74,7 @@ def rename_user(self, command: RenameNameTwice) -> None: def test_that_multiple_events_are_raised_per_aggregate_in_the_same_uow(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Register, part_of=User) test_domain.register(RenameNameTwice, part_of=User) test_domain.register(UserCommandHandler, part_of=User) diff --git a/tests/event_sourced_aggregates/test_validations.py b/tests/event_sourced_aggregates/test_validations.py index 3b8141e7..6e358894 100644 --- a/tests/event_sourced_aggregates/test_validations.py +++ b/tests/event_sourced_aggregates/test_validations.py @@ -7,7 +7,7 @@ def test_exception_on_multiple_identifiers(test_domain): with pytest.raises(NotSupportedError) as exc: - @test_domain.event_sourced_aggregate + @test_domain.aggregate(is_event_sourced=True) class Person: email = String(identifier=True) username = String(identifier=True) diff --git a/tests/event_sourced_repository/test_add.py b/tests/event_sourced_repository/test_add.py index c4f31332..f90c93ef 100644 --- a/tests/event_sourced_repository/test_add.py +++ b/tests/event_sourced_repository/test_add.py @@ -2,7 +2,7 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseEvent from protean.exceptions import IncorrectUsageError from protean.fields import Identifier, String @@ -13,7 +13,7 @@ class UserRegistered(BaseEvent): email = String(required=True) -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): id = Identifier(identifier=True) email = String() name = String() @@ -27,7 +27,7 @@ def register(cls, id, name, email): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(UserRegistered, part_of=User) test_domain.init(traverse=False) @@ -48,7 +48,7 @@ def test_successful_persistence_of_aggregate(test_domain): test_domain.repository_for(User).add(user) assert len(user._events) == 0 - event_messages = test_domain.event_store.store.read(f"user-{user.id}") + event_messages = test_domain.event_store.store.read(f"test::user-{user.id}") assert len(event_messages) == 1 @@ -57,5 +57,5 @@ def test_aggregate_with_no_changes_is_not_acted_on(test_domain): assert len(user._events) == 0 test_domain.repository_for(User).add(user) - event_messages = test_domain.event_store.store.read(f"user-{user.id}") + event_messages = test_domain.event_store.store.read(f"test::user-{user.id}") assert len(event_messages) == 0 diff --git a/tests/event_sourced_repository/test_add_uow.py b/tests/event_sourced_repository/test_add_uow.py index 4d47b45f..72f8b5c6 100644 --- a/tests/event_sourced_repository/test_add_uow.py +++ b/tests/event_sourced_repository/test_add_uow.py @@ -1,11 +1,11 @@ import mock import pytest -from protean import BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseEvent from protean.fields import Identifier, String -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): id = Identifier(identifier=True) email = String() name = String() @@ -19,7 +19,7 @@ class Registered(BaseEvent): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.init(traverse=False) diff --git a/tests/event_sourced_repository/test_loading_aggregates.py b/tests/event_sourced_repository/test_loading_aggregates.py index c2c7f151..69037e17 100644 --- a/tests/event_sourced_repository/test_loading_aggregates.py +++ b/tests/event_sourced_repository/test_loading_aggregates.py @@ -5,9 +5,9 @@ import pytest from protean import ( + BaseAggregate, BaseCommandHandler, BaseEvent, - BaseEventSourcedAggregate, apply, handle, ) @@ -42,7 +42,7 @@ class AddressChanged(BaseEvent): address = String() -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): user_id = Identifier(identifier=True) email = String() name = String() @@ -102,7 +102,7 @@ def change_address(self, command: ChangeAddress) -> None: @pytest.fixture(autouse=True) def register(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Register, part_of=User) test_domain.register(Registered, part_of=User) test_domain.register(ChangeAddress, part_of=User) diff --git a/tests/event_sourced_repository/test_retrieving_event_sourced_repository.py b/tests/event_sourced_repository/test_retrieving_event_sourced_repository.py index 1eb53561..43e7503c 100644 --- a/tests/event_sourced_repository/test_retrieving_event_sourced_repository.py +++ b/tests/event_sourced_repository/test_retrieving_event_sourced_repository.py @@ -1,13 +1,13 @@ import pytest -from protean import BaseAggregate, BaseEventSourcedAggregate +from protean import BaseAggregate from protean.core.event_sourced_repository import BaseEventSourcedRepository from protean.exceptions import IncorrectUsageError from protean.fields import Integer, String from protean.utils import DomainObjects -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): name = String() age = Integer() @@ -15,7 +15,7 @@ class User(BaseEventSourcedAggregate): def test_that_event_sourced_repository_is_returned_for_event_sourced_aggregate( test_domain, ): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) assert ( test_domain.repository_for(User).element_type @@ -49,6 +49,7 @@ class CustomRepository(BaseEventSourcedRepository): pass with pytest.raises(IncorrectUsageError) as exc: + test_domain.register(CustomAggregate) test_domain.register(CustomRepository, part_of=CustomAggregate) assert exc.value.messages == { diff --git a/tests/event_store/test_appending_aggregate_events.py b/tests/event_store/test_appending_aggregate_events.py index 0f5053ea..62e54a30 100644 --- a/tests/event_store/test_appending_aggregate_events.py +++ b/tests/event_store/test_appending_aggregate_events.py @@ -4,8 +4,7 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate -from protean.core.event_sourced_aggregate import apply +from protean import BaseAggregate, BaseEvent, apply from protean.fields import String from protean.fields.basic import Identifier @@ -25,7 +24,7 @@ class Renamed(BaseEvent): name = String() -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String() status = String(default="INACTIVE") @@ -59,7 +58,7 @@ def renamed(self, event: Renamed) -> None: @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.register(Activated, part_of=User) test_domain.register(Renamed, part_of=User) @@ -72,7 +71,7 @@ def test_appending_messages_to_aggregate(test_domain): user = User.register(id=identifier, email="john.doe@example.com", name="John Doe") test_domain.event_store.store.append(user._events[0]) - messages = test_domain.event_store.store._read("user") + messages = test_domain.event_store.store._read("test::user") assert len(messages) == 1 @@ -83,17 +82,17 @@ def test_version_increment_on_new_event(test_domain): user = User.register(id=identifier, email="john.doe@example.com", name="John Doe") test_domain.event_store.store.append(user._events[0]) - events = test_domain.event_store.store._read(f"user-{identifier}") + events = test_domain.event_store.store._read(f"test::user-{identifier}") assert events[0]["position"] == 0 user.activate() test_domain.event_store.store.append(user._events[1]) - events = test_domain.event_store.store._read(f"user-{identifier}") + events = test_domain.event_store.store._read(f"test::user-{identifier}") assert events[-1]["position"] == 1 user.rename(name="John Doe 2") test_domain.event_store.store.append(user._events[2]) - events = test_domain.event_store.store._read(f"user-{identifier}") + events = test_domain.event_store.store._read(f"test::user-{identifier}") assert events[-1]["position"] == 2 diff --git a/tests/event_store/test_appending_commands.py b/tests/event_store/test_appending_commands.py index c31092a9..1e1c4026 100644 --- a/tests/event_store/test_appending_commands.py +++ b/tests/event_store/test_appending_commands.py @@ -2,13 +2,13 @@ import pytest -from protean import BaseCommand, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseCommand from protean.exceptions import IncorrectUsageError from protean.fields import String from protean.fields.basic import Identifier -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): id = Identifier(identifier=True) email = String() name = String() @@ -21,7 +21,7 @@ class Register(BaseCommand): def test_command_submission_without_aggregate(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.init(traverse=False) with pytest.raises(IncorrectUsageError) as exc: @@ -36,7 +36,7 @@ def test_command_submission_without_aggregate(test_domain): @pytest.mark.eventstore def test_command_submission(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Register, part_of=User) test_domain.init(traverse=False) @@ -49,7 +49,7 @@ def test_command_submission(test_domain): ) ) - messages = test_domain.event_store.store.read("user:command") + messages = test_domain.event_store.store.read("test::user:command") assert len(messages) == 1 - messages[0].stream_name == f"user:command-{identifier}" + messages[0].stream_name == f"test::user:command-{identifier}" diff --git a/tests/event_store/test_appending_events.py b/tests/event_store/test_appending_events.py index d606fb8d..af23215a 100644 --- a/tests/event_store/test_appending_events.py +++ b/tests/event_store/test_appending_events.py @@ -4,12 +4,12 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseEvent from protean.fields.basic import Identifier from protean.utils.mixins import Message -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): user_id = Identifier(identifier=True) @@ -19,7 +19,7 @@ class UserLoggedIn(BaseEvent): @pytest.mark.eventstore def test_appending_raw_events(test_domain): - test_domain.register(User, stream_category="authentication") + test_domain.register(User, is_event_sourced=True, stream_category="authentication") test_domain.register(UserLoggedIn, part_of=User) test_domain.init(traverse=False) @@ -29,14 +29,14 @@ def test_appending_raw_events(test_domain): event = user._events[0] # Remember event for later comparison test_domain.repository_for(User).add(user) - messages = test_domain.event_store.store.read("authentication") + messages = test_domain.event_store.store.read("test::authentication") assert len(messages) == 1 message = messages[0] assert isinstance(message, Message) - assert message.stream_name == f"authentication-{identifier}" + assert message.stream_name == f"test::authentication-{identifier}" assert message.metadata.kind == "EVENT" assert message.data == event.payload assert message.metadata == event._metadata diff --git a/tests/event_store/test_inline_event_processing_on_publish.py b/tests/event_store/test_inline_event_processing_on_publish.py index 5d577fda..6fd22e7f 100644 --- a/tests/event_store/test_inline_event_processing_on_publish.py +++ b/tests/event_store/test_inline_event_processing_on_publish.py @@ -9,7 +9,7 @@ import pytest -from protean import BaseEvent, BaseEventHandler, BaseEventSourcedAggregate, handle +from protean import BaseAggregate, BaseEvent, BaseEventHandler, handle from protean.fields import Identifier, String from protean.globals import current_domain @@ -21,7 +21,7 @@ def count_up(): counter += 1 -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): user_id = Identifier(identifier=True) email = String() name = String() @@ -43,9 +43,9 @@ def registered(self, _: Registered) -> None: @pytest.mark.eventstore def test_inline_event_processing_on_publish_in_sync_mode(test_domain): - test_domain.register(User, stream_category="user") + test_domain.register(User, is_event_sourced=True, stream_category="user") test_domain.register(Registered, part_of=User) - test_domain.register(UserEventHandler, stream_category="user") + test_domain.register(UserEventHandler, stream_category="test::user") test_domain.init(traverse=False) user = User( diff --git a/tests/event_store/test_reading_all_streams.py b/tests/event_store/test_reading_all_streams.py index 58ec623a..5277b932 100644 --- a/tests/event_store/test_reading_all_streams.py +++ b/tests/event_store/test_reading_all_streams.py @@ -4,12 +4,12 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseEvent from protean.fields import DateTime, Identifier, String, Text from protean.utils import utcnow_func -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String(max_length=50) @@ -43,7 +43,7 @@ class Renamed(BaseEvent): name = String(required=True, max_length=50) -class Post(BaseEventSourcedAggregate): +class Post(BaseAggregate): topic = String() content = Text() @@ -71,12 +71,12 @@ class Published(BaseEvent): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.register(Activated, part_of=User) test_domain.register(Renamed, part_of=User) - test_domain.register(Post) + test_domain.register(Post, is_event_sourced=True) test_domain.register(Created, part_of=Post) test_domain.register(Published, part_of=Post) diff --git a/tests/event_store/test_reading_events_of_type.py b/tests/event_store/test_reading_events_of_type.py index 5185debc..31753a7d 100644 --- a/tests/event_store/test_reading_events_of_type.py +++ b/tests/event_store/test_reading_events_of_type.py @@ -4,12 +4,12 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseEvent from protean.fields import String from protean.fields.basic import Identifier -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String(max_length=50) @@ -45,7 +45,7 @@ class Renamed(BaseEvent): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.register(Activated, part_of=User) test_domain.register(Renamed, part_of=User) @@ -98,7 +98,7 @@ def test_reading_events_of_type_with_multiple_events(self, test_domain): @pytest.mark.eventstore def test_reading_events_of_type_with_multiple_events_in_stream(self, test_domain): - events = test_domain.event_store.events_of_type(Renamed, "user") + events = test_domain.event_store.events_of_type(Renamed, "test::user") assert len(events) == 10 assert events[-1].name == "John Doe 9" @@ -106,5 +106,5 @@ def test_reading_events_of_type_with_multiple_events_in_stream(self, test_domain def test_reading_events_of_type_with_multiple_events_in_different_stream( self, test_domain ): - events = test_domain.event_store.events_of_type(Renamed, "group") + events = test_domain.event_store.events_of_type(Renamed, "test::group") assert len(events) == 0 diff --git a/tests/event_store/test_reading_last_event_of_type.py b/tests/event_store/test_reading_last_event_of_type.py index 40de226a..c6fda9f4 100644 --- a/tests/event_store/test_reading_last_event_of_type.py +++ b/tests/event_store/test_reading_last_event_of_type.py @@ -4,12 +4,12 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseEvent from protean.fields import String from protean.fields.basic import Identifier -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String(max_length=50) @@ -45,7 +45,7 @@ class Renamed(BaseEvent): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.register(Activated, part_of=User) test_domain.register(Renamed, part_of=User) @@ -112,7 +112,7 @@ def test_reading_the_last_event_of_type_with_multiple_events_in_stream( registered_user.rename(name=f"John Doe {i}") test_domain.event_store.store.append(registered_user._events[-1]) - event = test_domain.event_store.last_event_of_type(Renamed, "user") + event = test_domain.event_store.last_event_of_type(Renamed, "test::user") assert event.name == "John Doe 9" @pytest.mark.eventstore diff --git a/tests/event_store/test_reading_messages.py b/tests/event_store/test_reading_messages.py index 2d190742..01ee680d 100644 --- a/tests/event_store/test_reading_messages.py +++ b/tests/event_store/test_reading_messages.py @@ -4,13 +4,13 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseEvent from protean.fields import String from protean.fields.basic import Identifier from protean.utils.mixins import Message -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String(max_length=50) @@ -31,7 +31,7 @@ class Renamed(BaseEvent): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.register(Activated, part_of=User) test_domain.register(Renamed, part_of=User) @@ -67,13 +67,13 @@ def renamed_user(test_domain, activated_user): @pytest.mark.eventstore def test_reading_a_message(test_domain, registered_user): - messages = test_domain.event_store.store.read("user") + messages = test_domain.event_store.store.read("test::user") assert len(messages) == 1 message = messages[0] assert isinstance(message, Message) - assert message.stream_name == f"user-{registered_user.id}" + assert message.stream_name == f"test::user-{registered_user.id}" assert message.metadata.kind == "EVENT" assert message.data == registered_user._events[-1].payload assert message.metadata == registered_user._events[-1]._metadata @@ -81,11 +81,11 @@ def test_reading_a_message(test_domain, registered_user): @pytest.mark.eventstore def test_reading_many_messages(test_domain, activated_user): - messages = test_domain.event_store.store.read(f"user-{activated_user.id}") + messages = test_domain.event_store.store.read(f"test::user-{activated_user.id}") assert len(messages) == 2 - assert messages[0].stream_name == f"user-{activated_user.id}" + assert messages[0].stream_name == f"test::user-{activated_user.id}" assert messages[0].metadata.kind == "EVENT" assert messages[0].data == activated_user._events[0].payload assert messages[0].metadata == activated_user._events[0]._metadata @@ -95,18 +95,20 @@ def test_reading_many_messages(test_domain, activated_user): @pytest.mark.eventstore def test_limiting_no_of_messages(test_domain, renamed_user): - messages = test_domain.event_store.store.read(f"user-{renamed_user.id}") + messages = test_domain.event_store.store.read(f"test::user-{renamed_user.id}") assert len(messages) == 12 messages = test_domain.event_store.store.read( - f"user-{renamed_user.id}", no_of_messages=5 + f"test::user-{renamed_user.id}", no_of_messages=5 ) assert len(messages) == 5 @pytest.mark.eventstore def test_reading_messages_from_position(test_domain, renamed_user): - messages = test_domain.event_store.store.read(f"user-{renamed_user.id}", position=5) + messages = test_domain.event_store.store.read( + f"test::user-{renamed_user.id}", position=5 + ) assert len(messages) == 7 # Read until end, 1000 messages by default assert messages[0].data["name"] == "John Doe 3" @@ -115,7 +117,7 @@ def test_reading_messages_from_position(test_domain, renamed_user): @pytest.mark.eventstore def test_reading_messages_from_position_with_limit(test_domain, renamed_user): messages = test_domain.event_store.store.read( - f"user-{renamed_user.id}", position=5, no_of_messages=2 + f"test::user-{renamed_user.id}", position=5, no_of_messages=2 ) assert len(messages) == 2 @@ -124,11 +126,11 @@ def test_reading_messages_from_position_with_limit(test_domain, renamed_user): @pytest.mark.eventstore def test_reading_messages_by_category(test_domain, activated_user): - messages = test_domain.event_store.store.read("user") + messages = test_domain.event_store.store.read("test::user") assert len(messages) == 2 - assert messages[0].stream_name == f"user-{activated_user.id}" + assert messages[0].stream_name == f"test::user-{activated_user.id}" assert messages[0].metadata.kind == "EVENT" assert messages[0].data == activated_user._events[0].payload assert messages[0].metadata == activated_user._events[0]._metadata @@ -139,6 +141,8 @@ def test_reading_messages_by_category(test_domain, activated_user): @pytest.mark.eventstore def test_reading_last_message(test_domain, renamed_user): # Reading by stream - message = test_domain.event_store.store.read_last_message(f"user-{renamed_user.id}") + message = test_domain.event_store.store.read_last_message( + f"test::user-{renamed_user.id}" + ) assert message.type == Renamed.__type__ assert message.data["name"] == "John Doe 9" diff --git a/tests/event_store/test_snapshotting.py b/tests/event_store/test_snapshotting.py index ff517e70..44fcdd40 100644 --- a/tests/event_store/test_snapshotting.py +++ b/tests/event_store/test_snapshotting.py @@ -3,7 +3,7 @@ import pytest -from protean import BaseEvent, BaseEventSourcedAggregate, UnitOfWork, apply +from protean import BaseAggregate, BaseEvent, UnitOfWork, apply from protean.fields import Identifier, String @@ -28,7 +28,7 @@ class UserRenamed(BaseEvent): name = String(required=True, max_length=50) -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): user_id = Identifier(identifier=True) name = String(max_length=50, required=True) email = String(required=True) @@ -61,7 +61,7 @@ def renamed(self, event: UserRenamed): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(UserRegistered, part_of=User) test_domain.register(UserActivated, part_of=User) test_domain.register(UserRenamed, part_of=User) @@ -109,7 +109,7 @@ def test_that_snapshot_is_constructed_after_threshold(test_domain): repo.add(user) snapshot = test_domain.event_store.store._read_last_message( - f"user:snapshot-{identifier}" + f"test::user:snapshot-{identifier}" ) assert snapshot is not None assert User(**snapshot["data"]) == user @@ -137,7 +137,7 @@ def test_that_a_stream_can_have_multiple_snapshots_but_latest_is_considered( repo.add(user) snapshot = test_domain.event_store.store._read_last_message( - f"user:snapshot-{identifier}" + f"test::user:snapshot-{identifier}" ) assert snapshot is not None assert User(**snapshot["data"]) == user @@ -165,7 +165,7 @@ def test_that_a_stream_with_a_snapshop_and_no_further_events_is_reconstructed_co repo.add(user) snapshot = test_domain.event_store.store._read_last_message( - f"user:snapshot-{identifier}" + f"test::user:snapshot-{identifier}" ) assert snapshot is not None assert User(**snapshot["data"]) == user diff --git a/tests/event_store/test_streams_initialization.py b/tests/event_store/test_streams_initialization.py index 3511e504..b10fa9ff 100644 --- a/tests/event_store/test_streams_initialization.py +++ b/tests/event_store/test_streams_initialization.py @@ -2,17 +2,17 @@ import pytest -from protean import BaseEvent, BaseEventHandler, BaseEventSourcedAggregate, handle +from protean import BaseAggregate, BaseEvent, BaseEventHandler, handle from protean.fields import DateTime, Identifier, String -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String() password_hash = String() -class Email(BaseEventSourcedAggregate): +class Email(BaseAggregate): email = String() sent_at = DateTime() @@ -56,10 +56,10 @@ def record_sent_email(self, event: Sent) -> None: @pytest.fixture(autouse=True) def register(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.register(Activated, part_of=User) - test_domain.register(Email) + test_domain.register(Email, is_event_sourced=True) test_domain.register(Sent, part_of=Email) test_domain.register(UserEventHandler, part_of=User) test_domain.register(EmailEventHandler, part_of=Email) @@ -70,8 +70,8 @@ def test_streams_initialization(test_domain): assert len(test_domain.event_store._event_streams) == 2 assert all( stream_category in test_domain.event_store._event_streams - for stream_category in ["user", "email"] + for stream_category in ["test::user", "test::email"] ) - assert test_domain.event_store._event_streams["user"] == {UserEventHandler} - assert test_domain.event_store._event_streams["email"] == {EmailEventHandler} + assert test_domain.event_store._event_streams["test::user"] == {UserEventHandler} + assert test_domain.event_store._event_streams["test::email"] == {EmailEventHandler} diff --git a/tests/message/test_message_to_object.py b/tests/message/test_message_to_object.py index 8b650aa6..11046476 100644 --- a/tests/message/test_message_to_object.py +++ b/tests/message/test_message_to_object.py @@ -2,14 +2,14 @@ import pytest -from protean import BaseCommand, BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseCommand, BaseEvent from protean.core.event import Metadata from protean.exceptions import InvalidDataError from protean.fields import Identifier, String from protean.utils.mixins import Message -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String() @@ -30,7 +30,7 @@ class Registered(BaseEvent): name = String() -class SendEmail(BaseEventSourcedAggregate): +class SendEmail(BaseAggregate): to = String() subject = String() content = String() @@ -44,10 +44,10 @@ class SendEmailCommand(BaseCommand): @pytest.fixture(autouse=True) def register(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Register, part_of=User) test_domain.register(Registered, part_of=User) - test_domain.register(SendEmail) + test_domain.register(SendEmail, is_event_sourced=True) test_domain.register(SendEmailCommand, part_of=SendEmail) test_domain.init(traverse=False) diff --git a/tests/message/test_object_to_message.py b/tests/message/test_object_to_message.py index 6c566256..80a84d59 100644 --- a/tests/message/test_object_to_message.py +++ b/tests/message/test_object_to_message.py @@ -2,13 +2,13 @@ import pytest -from protean import BaseCommand, BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseCommand, BaseEvent from protean.exceptions import ConfigurationError from protean.fields import Identifier, String from protean.utils.mixins import Message -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String() @@ -29,7 +29,7 @@ class Registered(BaseEvent): name = String() -class SendEmail(BaseEventSourcedAggregate): +class SendEmail(BaseAggregate): to = String() subject = String() content = String() @@ -43,10 +43,10 @@ class SendEmailCommand(BaseCommand): @pytest.fixture(autouse=True) def register(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Register, part_of=User) test_domain.register(Registered, part_of=User) - test_domain.register(SendEmail) + test_domain.register(SendEmail, is_event_sourced=True) test_domain.register(SendEmailCommand, part_of=SendEmail) test_domain.init(traverse=False) diff --git a/tests/message/test_origin_stream_name_in_metadata.py b/tests/message/test_origin_stream_name_in_metadata.py index 49b5fc2e..314598d7 100644 --- a/tests/message/test_origin_stream_name_in_metadata.py +++ b/tests/message/test_origin_stream_name_in_metadata.py @@ -2,7 +2,7 @@ import pytest -from protean import BaseCommand, BaseEvent, BaseEventSourcedAggregate +from protean import BaseAggregate, BaseCommand, BaseEvent from protean.core.event import Metadata from protean.fields import String from protean.fields.basic import Identifier @@ -10,7 +10,7 @@ from protean.utils.mixins import Message -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): id = Identifier(identifier=True) email = String() name = String() @@ -30,7 +30,7 @@ class Registered(BaseEvent): @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Register, part_of=User) test_domain.register(Registered, part_of=User) test_domain.init(traverse=False) @@ -167,4 +167,4 @@ def test_origin_stream_in_command_from_event( enriched_command = test_domain._enrich_command(command) command_message = Message.to_message(enriched_command) - assert command_message.metadata.origin_stream == f"user-{user_id}" + assert command_message.metadata.origin_stream == f"test::user-{user_id}" diff --git a/tests/server/test_any_event_handler.py b/tests/server/test_any_event_handler.py index 8333b223..57dc75e7 100644 --- a/tests/server/test_any_event_handler.py +++ b/tests/server/test_any_event_handler.py @@ -2,7 +2,7 @@ import pytest -from protean import BaseEvent, BaseEventHandler, BaseEventSourcedAggregate, handle +from protean import BaseAggregate, BaseEvent, BaseEventHandler, handle from protean.fields import Identifier, String from protean.server import Engine from protean.utils.mixins import Message @@ -15,7 +15,7 @@ def count_up(): counter += 1 -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String() password_hash = String() @@ -36,7 +36,7 @@ def increment(self, event: BaseEventHandler) -> None: @pytest.mark.asyncio async def test_that_an_event_handler_can_be_associated_with_an_all_stream(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.register(UserEventHandler, part_of=User) test_domain.init(traverse=False) diff --git a/tests/server/test_command_handler_subscription.py b/tests/server/test_command_handler_subscription.py index 8cec2be3..fe6238c7 100644 --- a/tests/server/test_command_handler_subscription.py +++ b/tests/server/test_command_handler_subscription.py @@ -1,12 +1,12 @@ import pytest -from protean import BaseCommand, BaseCommandHandler, BaseEventSourcedAggregate, handle +from protean import BaseAggregate, BaseCommand, BaseCommandHandler, handle from protean.fields import Identifier, String from protean.server import Engine from protean.utils import fully_qualified_name -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): id = Identifier(identifier=True) email = String() name = String() @@ -37,10 +37,11 @@ def activate(self, command: Activate) -> None: @pytest.fixture(autouse=True) def register(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Register, part_of=User) test_domain.register(Activate, part_of=User) test_domain.register(UserCommandHandler, part_of=User) + test_domain.init(traverse=False) @pytest.fixture @@ -54,5 +55,5 @@ def test_command_handler_subscriptions(engine): assert fully_qualified_name(UserCommandHandler) in engine._subscriptions assert ( engine._subscriptions[fully_qualified_name(UserCommandHandler)].stream_category - == "user:command" + == "test::user:command" ) diff --git a/tests/server/test_command_handling.py b/tests/server/test_command_handling.py index 94ccc758..208e288f 100644 --- a/tests/server/test_command_handling.py +++ b/tests/server/test_command_handling.py @@ -2,7 +2,7 @@ import pytest -from protean import BaseCommand, BaseCommandHandler, BaseEventSourcedAggregate, handle +from protean import BaseAggregate, BaseCommand, BaseCommandHandler, handle from protean.fields import Identifier, String from protean.server import Engine from protean.utils.mixins import Message @@ -10,7 +10,7 @@ counter = 0 -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): id = Identifier(identifier=True) email = String() name = String() @@ -42,7 +42,7 @@ def activate(self, command: Activate) -> None: @pytest.mark.asyncio async def test_handler_invocation(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Register, part_of=User) test_domain.register(Activate, part_of=User) test_domain.register(UserCommandHandler, part_of=User) diff --git a/tests/server/test_error_handling.py b/tests/server/test_error_handling.py index 9552968d..b58dac29 100644 --- a/tests/server/test_error_handling.py +++ b/tests/server/test_error_handling.py @@ -3,13 +3,13 @@ import pytest -from protean import BaseEvent, BaseEventHandler, BaseEventSourcedAggregate, handle +from protean import BaseAggregate, BaseEvent, BaseEventHandler, handle from protean.fields import Identifier, String from protean.server import Engine from protean.utils.mixins import Message -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String() password_hash = String() @@ -48,7 +48,7 @@ def auto_set_and_close_loop(): @pytest.mark.asyncio async def test_that_exception_is_raised(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.register(UserEventHandler, part_of=User) test_domain.init(traverse=False) @@ -78,7 +78,7 @@ async def test_that_exception_is_raised(test_domain): def test_exceptions_stop_processing(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.register(UserEventHandler, part_of=User) diff --git a/tests/server/test_event_handler_subscription.py b/tests/server/test_event_handler_subscription.py index be70cc97..6a31d448 100644 --- a/tests/server/test_event_handler_subscription.py +++ b/tests/server/test_event_handler_subscription.py @@ -4,7 +4,7 @@ import pytest -from protean import BaseEvent, BaseEventHandler, BaseEventSourcedAggregate, handle +from protean import BaseAggregate, BaseEvent, BaseEventHandler, handle from protean.fields import DateTime, Identifier, String from protean.server import Engine from protean.utils import fqn @@ -27,7 +27,7 @@ class Sent(BaseEvent): sent_on = DateTime() -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String() password_hash = String() @@ -72,41 +72,49 @@ def setup_event_loop(): def test_event_subscriptions(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.register(Activated, part_of=User) test_domain.register(UserEventHandler, part_of=User) + test_domain.init(traverse=False) engine = Engine(test_domain, test_mode=True) assert len(engine._subscriptions) == 1 assert fqn(UserEventHandler) in engine._subscriptions - assert engine._subscriptions[fqn(UserEventHandler)].stream_category == "user" + assert engine._subscriptions[fqn(UserEventHandler)].stream_category == "test::user" def test_origin_stream_category_in_subscription(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Sent, part_of=User) - test_domain.register(EmailEventHandler, part_of=User, source_stream="email") + test_domain.register(EmailEventHandler, part_of=User, source_stream="test::email") + test_domain.init(traverse=False) engine = Engine(test_domain, test_mode=True) assert len(engine._subscriptions) == 1 - assert engine._subscriptions[fqn(EmailEventHandler)].stream_category == "user" - assert engine._subscriptions[fqn(EmailEventHandler)].origin_stream == "email" + assert engine._subscriptions[fqn(EmailEventHandler)].stream_category == "test::user" + assert engine._subscriptions[fqn(EmailEventHandler)].origin_stream == "test::email" def test_that_stream_name_overrides_the_derived_stream_name_from_owning_aggregate( test_domain, ): + test_domain.register(User, is_event_sourced=True) + test_domain.register(Sent, part_of=User) test_domain.register( EmailEventHandler, part_of=User, - stream_category="identity", - source_stream="email", + stream_category="test::identity", + source_stream="test::email", ) + test_domain.init(traverse=False) engine = Engine(test_domain, test_mode=True) assert len(engine._subscriptions) == 1 - assert engine._subscriptions[fqn(EmailEventHandler)].stream_category == "identity" - assert engine._subscriptions[fqn(EmailEventHandler)].origin_stream == "email" + assert ( + engine._subscriptions[fqn(EmailEventHandler)].stream_category + == "test::identity" + ) + assert engine._subscriptions[fqn(EmailEventHandler)].origin_stream == "test::email" diff --git a/tests/server/test_event_handling.py b/tests/server/test_event_handling.py index c42718a2..67c944f8 100644 --- a/tests/server/test_event_handling.py +++ b/tests/server/test_event_handling.py @@ -4,7 +4,7 @@ import pytest -from protean import BaseEvent, BaseEventHandler, BaseEventSourcedAggregate, handle +from protean import BaseAggregate, BaseEvent, BaseEventHandler, handle from protean.fields import Identifier, String from protean.server import Engine from protean.utils.mixins import Message @@ -12,7 +12,7 @@ counter = 0 -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String() password_hash = String() @@ -38,7 +38,7 @@ def send_notification(self, event: Registered) -> None: @pytest.mark.asyncio async def test_handler_invocation(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.register(UserEventHandler, part_of=User) test_domain.init(traverse=False) diff --git a/tests/server/test_handling_all_events.py b/tests/server/test_handling_all_events.py index 9dca5254..4dedb9c5 100644 --- a/tests/server/test_handling_all_events.py +++ b/tests/server/test_handling_all_events.py @@ -2,7 +2,7 @@ import pytest -from protean import BaseEvent, BaseEventHandler, BaseEventSourcedAggregate, handle +from protean import BaseAggregate, BaseEvent, BaseEventHandler, handle from protean.fields import Identifier, String, Text from protean.server import Engine from protean.utils.mixins import Message @@ -15,7 +15,7 @@ def count_up(): counter += 1 -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String() password_hash = String() @@ -28,7 +28,7 @@ class Registered(BaseEvent): password_hash = String() -class Post(BaseEventSourcedAggregate): +class Post(BaseAggregate): topic = String() content = Text() @@ -48,9 +48,9 @@ def increment(self, event: BaseEventHandler) -> None: @pytest.mark.asyncio @pytest.mark.eventstore async def test_that_any_message_can_be_handled_with_any_handler(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) - test_domain.register(Post) + test_domain.register(Post, is_event_sourced=True) test_domain.register(Created, part_of=Post) test_domain.register(SystemMetrics, stream_category="$all") test_domain.init(traverse=False) diff --git a/tests/subscription/test_message_filtering_with_origin_stream.py b/tests/subscription/test_message_filtering_with_origin_stream.py index 0a606d16..79d257dc 100644 --- a/tests/subscription/test_message_filtering_with_origin_stream.py +++ b/tests/subscription/test_message_filtering_with_origin_stream.py @@ -6,7 +6,7 @@ import mock import pytest -from protean import BaseEvent, BaseEventHandler, BaseEventSourcedAggregate, handle +from protean import BaseAggregate, BaseEvent, BaseEventHandler, handle from protean.core.event import Metadata from protean.fields import DateTime, Identifier, String from protean.server import Engine @@ -14,13 +14,13 @@ from protean.utils.mixins import Message -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String() password_hash = String() -class Email(BaseEventSourcedAggregate): +class Email(BaseAggregate): email = String() sent_at = DateTime() @@ -68,12 +68,12 @@ def record_sent_email(self, event: Sent) -> None: @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.register(Activated, part_of=User) test_domain.register(UserEventHandler, part_of=User) - test_domain.register(Email) + test_domain.register(Email, is_event_sourced=True) test_domain.register(Sent, part_of=Email) test_domain.register( EmailEventHandler, stream_category="email", source_stream="user" diff --git a/tests/subscription/test_message_handover_to_engine.py b/tests/subscription/test_message_handover_to_engine.py index 062fa384..c56dc3d6 100644 --- a/tests/subscription/test_message_handover_to_engine.py +++ b/tests/subscription/test_message_handover_to_engine.py @@ -5,7 +5,7 @@ import mock import pytest -from protean import BaseEvent, BaseEventHandler, BaseEventSourcedAggregate, handle +from protean import BaseAggregate, BaseEvent, BaseEventHandler, handle from protean.fields import Identifier, String from protean.server import Engine from protean.server.subscription import Subscription @@ -22,7 +22,7 @@ class Registered(BaseEvent): password_hash = String() -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String() password_hash = String() @@ -54,7 +54,7 @@ def send_notification(self, event: Registered) -> None: async def test_that_subscription_invokes_engine_handler_on_message( mock_handle_message, test_domain ): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.register(UserEventHandler, part_of=User) test_domain.init(traverse=False) @@ -70,7 +70,7 @@ async def test_that_subscription_invokes_engine_handler_on_message( engine = Engine(test_domain, test_mode=True) subscription = Subscription( - engine, fully_qualified_name(UserEventHandler), "user", UserEventHandler + engine, fully_qualified_name(UserEventHandler), "test::user", UserEventHandler ) await subscription.poll() diff --git a/tests/subscription/test_no_message_filtering.py b/tests/subscription/test_no_message_filtering.py index b85f74c2..1afb4d15 100644 --- a/tests/subscription/test_no_message_filtering.py +++ b/tests/subscription/test_no_message_filtering.py @@ -6,7 +6,7 @@ import mock import pytest -from protean import BaseEvent, BaseEventHandler, BaseEventSourcedAggregate, handle +from protean import BaseAggregate, BaseEvent, BaseEventHandler, handle from protean.core.event import Metadata from protean.fields import DateTime, Identifier, String from protean.server import Engine @@ -14,13 +14,13 @@ from protean.utils.mixins import Message -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String() password_hash = String() -class Email(BaseEventSourcedAggregate): +class Email(BaseAggregate): email = String() sent_at = DateTime() @@ -68,11 +68,11 @@ def record_sent_email(self, event: Sent) -> None: @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.register(Activated, part_of=User) test_domain.register(UserEventHandler, part_of=User) - test_domain.register(Email) + test_domain.register(Email, is_event_sourced=True) test_domain.register(Sent, part_of=Email) test_domain.register(EmailEventHandler, stream_category="email") test_domain.init(traverse=False) diff --git a/tests/subscription/test_read_position_updates.py b/tests/subscription/test_read_position_updates.py index 8fe7c578..0c372bc3 100644 --- a/tests/subscription/test_read_position_updates.py +++ b/tests/subscription/test_read_position_updates.py @@ -5,19 +5,19 @@ import pytest -from protean import BaseEvent, BaseEventHandler, BaseEventSourcedAggregate, handle +from protean import BaseAggregate, BaseEvent, BaseEventHandler, handle from protean.fields import DateTime, Identifier, String from protean.server import Engine from protean.utils import fqn -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String() password_hash = String() -class Email(BaseEventSourcedAggregate): +class Email(BaseAggregate): email = String() sent_at = DateTime() @@ -65,12 +65,12 @@ def record_sent_email(self, event: Sent) -> None: @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) - test_domain.register(Email) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.register(Activated, part_of=User) - test_domain.register(Sent, part_of=Email) test_domain.register(UserEventHandler, part_of=User) + test_domain.register(Email, is_event_sourced=True) + test_domain.register(Sent, part_of=Email) test_domain.register(EmailEventHandler, stream_category="email") test_domain.init(traverse=False) diff --git a/tests/test_containers.py b/tests/test_containers.py index 4a5bd44b..40eebb53 100644 --- a/tests/test_containers.py +++ b/tests/test_containers.py @@ -1,7 +1,7 @@ import pytest from protean.container import BaseContainer, OptionsMixin -from protean.exceptions import InvalidDataError +from protean.exceptions import InvalidDataError, NotSupportedError from protean.fields import Integer, String from protean.reflection import declared_fields @@ -18,6 +18,11 @@ class CustomContainer(CustomContainerMeta, OptionsMixin): bar = String() +def test_that_base_container_class_cannot_be_instantiated(): + with pytest.raises(NotSupportedError): + BaseContainer() + + class TestContainerInitialization: def test_that_base_container_class_cannot_be_instantiated(self): with pytest.raises(TypeError): diff --git a/tests/test_registry.py b/tests/test_registry.py index 804e4982..31382ba5 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -96,7 +96,6 @@ def test_properties_method_returns_a_dictionary_of_all_protean_elements(): "emails": DomainObjects.EMAIL.value, "entities": DomainObjects.ENTITY.value, "event_handlers": DomainObjects.EVENT_HANDLER.value, - "event_sourced_aggregates": DomainObjects.EVENT_SOURCED_AGGREGATE.value, "event_sourced_repositories": DomainObjects.EVENT_SOURCED_REPOSITORY.value, "events": DomainObjects.EVENT.value, "models": DomainObjects.MODEL.value, diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 00000000..0eece996 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,56 @@ +import pytest + +from protean.exceptions import ConfigurationError +from protean.utils import convert_str_values_to_list, generate_identity + + +def test_convert_str_values_to_list(): + # Test when value is None + assert convert_str_values_to_list(None) == [] + + # Test when value is an empty string + assert convert_str_values_to_list("") == [] + + # Test when value is a non-empty string + assert convert_str_values_to_list("test") == ["test"] + + # Test when value is a list of strings + assert convert_str_values_to_list(["a", "b"]) == ["a", "b"] + + # Test when value is a list of integers + assert convert_str_values_to_list([1, 2, 3]) == [1, 2, 3] + + # Test when value is a tuple + assert convert_str_values_to_list((1, 2, 3)) == [1, 2, 3] + + # Test when value is a set + assert convert_str_values_to_list({1, 2, 3}) == [1, 2, 3] + + # Test when value is a dictionary + assert convert_str_values_to_list({"a": 1, "b": 2}) == ["a", "b"] + + # Test when value is an integer (not iterable) + with pytest.raises(TypeError): + convert_str_values_to_list(10) + + # Test when value is a float (not iterable) + with pytest.raises(TypeError): + convert_str_values_to_list(10.5) + + # Test when value is a boolean + with pytest.raises(TypeError): + convert_str_values_to_list(True) + + # Test when value is an object + class TestObject: + pass + + with pytest.raises(TypeError): + convert_str_values_to_list(TestObject()) + + +def test_unknown_identity_type_raises_exception(): + with pytest.raises(ConfigurationError) as exc: + generate_identity(identity_type="foo") + + assert str(exc.value) == "Unknown Identity Type 'foo'" diff --git a/tests/unit_of_work/test_inline_event_processing.py b/tests/unit_of_work/test_inline_event_processing.py index b8fb0a8c..43db627c 100644 --- a/tests/unit_of_work/test_inline_event_processing.py +++ b/tests/unit_of_work/test_inline_event_processing.py @@ -5,11 +5,11 @@ import pytest from protean import ( + BaseAggregate, BaseCommand, BaseCommandHandler, BaseEvent, BaseEventHandler, - BaseEventSourcedAggregate, apply, handle, ) @@ -38,7 +38,7 @@ class Registered(BaseEvent): password_hash = String() -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): user_id = Identifier(identifier=True) email = String() name = String() @@ -92,7 +92,7 @@ def count_registrations(self, _: BaseEventHandler) -> None: @pytest.mark.eventstore def test_inline_event_processing_in_sync_mode(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Register, part_of=User) test_domain.register(Registered, part_of=User) test_domain.register(UserCommandHandler, part_of=User) diff --git a/tests/unit_of_work/test_nested_inline_event_processing.py b/tests/unit_of_work/test_nested_inline_event_processing.py index cdf0dc5a..7ffbb1f2 100644 --- a/tests/unit_of_work/test_nested_inline_event_processing.py +++ b/tests/unit_of_work/test_nested_inline_event_processing.py @@ -5,11 +5,11 @@ import pytest from protean import ( + BaseAggregate, BaseCommand, BaseCommandHandler, BaseEvent, BaseEventHandler, - BaseEventSourcedAggregate, apply, handle, ) @@ -38,7 +38,7 @@ class Published(BaseEvent): published_time = DateTime(default=utcnow_func) -class Post(BaseEventSourcedAggregate): +class Post(BaseAggregate): topic = String() content = Text() is_published = Boolean(default=False) @@ -93,12 +93,12 @@ def record_publishing(self, _: Published) -> None: @pytest.mark.eventstore def test_nested_uow_processing(test_domain): - test_domain.register(Post) + test_domain.register(Post, is_event_sourced=True) test_domain.register(Create, part_of=Post) test_domain.register(Created, part_of=Post) test_domain.register(Published, part_of=Post) test_domain.register(PostEventHandler, part_of=Post) - test_domain.register(Metrics, stream_category="post") + test_domain.register(Metrics, stream_category="test::post") test_domain.init(traverse=False) identifier = str(uuid4()) diff --git a/tests/unit_of_work/test_storing_events_on_commit.py b/tests/unit_of_work/test_storing_events_on_commit.py index a9cf1f78..003e1dc6 100644 --- a/tests/unit_of_work/test_storing_events_on_commit.py +++ b/tests/unit_of_work/test_storing_events_on_commit.py @@ -4,7 +4,7 @@ import pytest -from protean import BaseCommandHandler, BaseEvent, BaseEventSourcedAggregate, handle +from protean import BaseAggregate, BaseCommandHandler, BaseEvent, handle from protean.core.command import BaseCommand from protean.fields import String from protean.fields.basic import Identifier @@ -18,7 +18,7 @@ class Register(BaseCommand): password_hash = String() -class User(BaseEventSourcedAggregate): +class User(BaseAggregate): email = String() name = String() password_hash = String() @@ -60,7 +60,7 @@ def register_user(self, command: Register) -> None: @pytest.fixture(autouse=True) def register_elements(test_domain): - test_domain.register(User) + test_domain.register(User, is_event_sourced=True) test_domain.register(Registered, part_of=User) test_domain.register(Register, part_of=User) test_domain.register(UserCommandHandler, part_of=User) @@ -79,6 +79,6 @@ def test_persisting_events_on_commit(test_domain): ) ) - events = test_domain.event_store.store._read(f"user-{identifier}") + events = test_domain.event_store.store._read(f"test::user-{identifier}") assert len(events) == 1