Source code for eventbus.domain.events

# -*- coding: utf-8 -*-
from datetime import datetime, timezone
from uuid import UUID
from typing import Any, Generic, Optional

from eventbus.domain.whitehead import ActualOccasion, TEntity
from eventbus.util.hashing import hash_object
from eventbus.util.topic import get_topic
from eventbus.util.transcoding import ObjectJSONEncoder


[docs]class DomainEvent(ActualOccasion, Generic[TEntity]): """ Base class for domain model events. Implements methods to make instances read-only, comparable for equality in Python, and have recognisable representations.Custom To make domain events hashable, this class also implements a method to create a cryptographic hash of the state of the event. """ __json_encoder__ = ObjectJSONEncoder(sort_keys=True) __notifiable__ = True def __init__(self, **kwargs: Any): """ Initialises event attribute values directly from constructor kwargs. """ super().__init__() self.__dict__.update(kwargs) def __repr__(self) -> str: """ Creates a string representing the type and attribute values of the event. :rtype: str """ sorted_items = tuple(sorted(self.__dict__.items())) args_strings = ("{0}={1!r}".format(*item) for item in sorted_items) args_string = ", ".join(args_strings) return "{0}({1})".format(self.__class__.__qualname__, args_string) def __mutate__(self, obj: Optional[TEntity]) -> Optional[TEntity]: """ Updates 'obj' with values from 'self'. Calls the 'mutate()' method. Can be extended, but subclasses must call super and return an object to their caller. :param obj: object (normally a domain entity) to be mutated :return: mutated object """ if obj is not None: self.mutate(obj) return obj
[docs] def mutate(self, obj: TEntity) -> None: """ Updates ("mutates") given 'obj'. Intended to be overridden by subclasses, as the most concise way of coding a default projection of the event (for example into the state of a domain entity). The advantage of implementing a default projection using this method rather than __mutate__ is that you don't need to call super or return a value. :param obj: domain entity to be mutated """
def __setattr__(self, key: Any, value: Any) -> None: """ Inhibits event attributes from being updated by assignment. """ raise AttributeError("DomainEvent attributes are read-only") def __eq__(self, other: object) -> bool: """ Tests for equality of two event objects. :rtype: bool """ return isinstance(other, DomainEvent) and self.__hash__() == other.__hash__() def __ne__(self, other: object) -> bool: """ Negates the equality test. :rtype: bool """ return not (self == other) def __hash__(self) -> int: """ Computes a Python integer hash for an event. Supports Python equality and inequality comparisons. :return: Python integer hash :rtype: int """ attrs = self.__dict__.copy() # Involve the topic in the hash, so that different types # with same attribute values have different hash values. attrs["__event_topic__"] = get_topic(type(self)) # Calculate the cryptographic hash of the event. sha256_hash = self.__hash_object__(attrs) # Return the Python hash of the cryptographic hash. return hash(sha256_hash) @classmethod def __hash_object__(cls, obj: dict) -> str: """ Calculates SHA-256 hash of JSON encoded 'obj'. :param obj: Object to be hashed. :return: SHA-256 as hexadecimal string. :rtype: str """ return hash_object(cls.__json_encoder__, obj)
[docs]class EventWithOriginatorID(DomainEvent[TEntity]): """ For events that have an originator ID. """ def __init__(self, originator_id: UUID, **kwargs: Any): super(EventWithOriginatorID, self).__init__(originator_id=originator_id, **kwargs) @property def originator_id(self) -> UUID: """ Originator ID is the identity of the object that originated this event. :return: A UUID representing the identity of the originator. :rtype: UUID """ return self.__dict__["originator_id"]
[docs]class EventWithTimestamp(DomainEvent[TEntity]): """ For events that have a timestamp value. """ def __init__(self, timestamp: Optional[datetime] = None, **kwargs: Any): kwargs["timestamp"] = timestamp or datetime.now(tz=timezone.utc) super(EventWithTimestamp, self).__init__(**kwargs) @property def timestamp(self) -> datetime: """ A UNIX timestamp as a datetime object. """ return self.__dict__["timestamp"]
[docs]class EventWithOriginatorVersion(DomainEvent[TEntity]): """ For events that have an originator version number. """ def __init__(self, originator_version: int, **kwargs: Any): if not isinstance(originator_version, int): raise TypeError("Version must be an integer: {0}".format(originator_version)) super(EventWithOriginatorVersion, self).__init__(originator_version=originator_version, **kwargs) @property def originator_version(self) -> int: """ Originator version is the version of the object that originated this event. :return: A integer representing the version of the originator. """ return self.__dict__["originator_version"]
[docs]class CreatedEvent(DomainEvent[TEntity]): """ Happens when something is created. """
[docs]class DiscardedEvent(DomainEvent[TEntity]): """ Happens when something is discarded. """
[docs]class AttributeChangedEvent(DomainEvent[TEntity]): """ Happens when the value of an attribute changes. """ @property def name(self) -> str: return self.__dict__["name"] @property def value(self) -> Any: return self.__dict__["value"]