Source code for eventbus.domain.decorators
# -*- coding: utf-8 -*-
from typing import Dict, Type
from eventbus.domain.events import DomainEvent
[docs]def subclassevents(cls: type) -> type: # noqa
"""
Decorator that avoids "boilerplate" subclassing of domain events.
For example, with this decorator you can do this:
.. code::
@subclassevents
class Example(AggregateRoot):
class SomethingHappened(DomainEvent): pass
rather than this:
.. code::
class Example(AggregateRoot):
class Event(AggregateRoot.Event): pass
class Created(Event, AggregateRoot.Created): pass
class AttributeChanged(Event, AggregateRoot.AttributeChanged): pass
class Discarded(Event, AggregateRoot.Discarded): pass
class SomethingHappened(Event): pass
You can apply this to a tree of domain event classes by defining
the base class with attribute 'subclassevents = True'.
"""
bases_event_attrs = []
super_event_class_names = set()
for base_cls in cls.__bases__:
base_event_attrs: Dict[str, Type[DomainEvent]] = {}
bases_event_attrs.append(base_event_attrs)
for base_attr_name in dir(base_cls):
base_attr = getattr(base_cls, base_attr_name)
if isinstance(base_attr, type) and issubclass(base_attr, DomainEvent):
base_event_attrs[base_attr_name] = base_attr
if base_attr_name != "Event":
super_event_class_names.add(base_attr_name)
# Define base Event subclass including super Event classes.
if "Event" in cls.__dict__:
event_event_subclass = cls.__dict__["Event"]
else:
base_event_classes = []
for base_event_attrs in bases_event_attrs:
try:
event_cls = base_event_attrs["Event"]
except KeyError:
pass
else:
base_event_classes.append(event_cls)
event_event_subclass = type(
"Event", tuple(base_event_classes or [DomainEvent]), {"__qualname__": cls.__name__ + ".Event"},
)
event_event_subclass.__module__ = cls.__module__
setattr(cls, "Event", event_event_subclass) # noqa
# Define subclasses for super event classes, including Event subclass as base.
for super_event_class_name in super_event_class_names:
base_event_classes = [event_event_subclass]
if super_event_class_name in cls.__dict__:
continue
for base_event_attrs in bases_event_attrs:
try:
event_cls = base_event_attrs[super_event_class_name]
except KeyError:
pass
else:
base_event_classes.append(event_cls)
event_subclass = type(
super_event_class_name,
tuple(base_event_classes),
{"__qualname__": cls.__name__ + "." + super_event_class_name},
)
event_subclass.__module__ = cls.__module__
setattr(cls, super_event_class_name, event_subclass)
# Redefine event classes in cls.__dict__ that are not subclasses of Event.
for cls_attr_name in cls.__dict__.keys():
cls_attr = getattr(cls, cls_attr_name)
if isinstance(cls_attr, type) and issubclass(cls_attr, DomainEvent):
if not issubclass(cls_attr, event_event_subclass):
try:
event_subclass = type(
cls_attr_name,
(cls_attr, event_event_subclass),
{
"__qualname__": cls_attr.__qualname__,
"__module__": cls_attr.__module__,
"__doc__": cls_attr.__doc__,
},
)
except TypeError:
raise Exception(cls_attr_name, cls_attr, event_event_subclass)
setattr(cls, cls_attr_name, event_subclass)
return cls