Ejemplo n.º 1
0
def repository_factory(element_cls, **opts):
    element_cls = derive_element_class(element_cls, BaseRepository, **opts)

    if not element_cls.meta_.aggregate_cls:
        raise IncorrectUsageError({
            "_entity": [
                f"Repository `{element_cls.__name__}` should be associated with an Aggregate"
            ]
        })

    # FIXME Uncomment
    # if not issubclass(element_cls.meta_.aggregate_cls, BaseAggregate):
    #     raise IncorrectUsageError(
    #         {"_entity": [f"Repository `{element_cls.__name__}` can only be associated with an Aggregate"]}
    #     )

    # Ensure the value of `database` is among known databases
    if element_cls.meta_.database != "ALL" and element_cls.meta_.database not in [
            database.value for database in Database
    ]:
        raise IncorrectUsageError({
            "_entity": [
                f"Repository `{element_cls.__name__}` should be associated with a valid Database"
            ]
        })

    return element_cls
Ejemplo n.º 2
0
    def to_command_message(cls, command: BaseCommand) -> Message:
        # FIXME Should one of `aggregate_cls` or `stream_name` be mandatory?
        if not (command.meta_.aggregate_cls or command.meta_.stream_name):
            raise IncorrectUsageError({
                "_entity": [
                    f"Command `{command.__class__.__name__}` needs to be associated with an aggregate or a stream"
                ]
            })

        # Use the value of an identifier field if specified, or generate a new uuid
        if has_id_field(command):
            identifier = getattr(command, id_field(command).field_name)
        else:
            identifier = str(uuid4())

        # Use explicit stream name if provided, or fallback on Aggregate's stream name
        stream_name = (command.meta_.stream_name
                       or command.meta_.aggregate_cls.meta_.stream_name)

        return cls(
            stream_name=f"{stream_name}:command-{identifier}",
            type=fully_qualified_name(command.__class__),
            data=command.to_dict(),
            metadata=MessageMetadata(
                kind=MessageType.COMMAND.value,
                owner=current_domain.domain_name,
                **cls.derived_metadata(MessageType.COMMAND.value),
            )
            # schema_version=command.meta_.version,  # FIXME Maintain version for command
        )
Ejemplo n.º 3
0
    def to_event_message(cls, event: BaseEvent) -> Message:
        # FIXME Should one of `aggregate_cls` or `stream_name` be mandatory?
        if not (event.meta_.aggregate_cls or event.meta_.stream_name):
            raise IncorrectUsageError({
                "_entity": [
                    f"Event `{event.__class__.__name__}` needs to be associated with an aggregate or a stream"
                ]
            })

        if has_id_field(event):
            identifier = getattr(event, id_field(event).field_name)
        else:
            identifier = str(uuid4())

        # Use explicit stream name if provided, or fallback on Aggregate's stream name
        stream_name = (event.meta_.stream_name
                       or event.meta_.aggregate_cls.meta_.stream_name)

        return cls(
            stream_name=f"{stream_name}-{identifier}",
            type=fully_qualified_name(event.__class__),
            data=event.to_dict(),
            metadata=MessageMetadata(
                kind=MessageType.EVENT.value,
                owner=current_domain.domain_name,
                **cls.derived_metadata(MessageType.EVENT.value),
            )
            # schema_version=command.meta_.version,  # FIXME Maintain version for event
        )
Ejemplo n.º 4
0
    def __set_id_field(new_class):
        """Lookup the id field for this entity and assign"""
        # FIXME What does it mean when there are no declared fields?
        #   Does it translate to an abstract entity?
        try:
            id_field = next(
                field for _, field in declared_fields(new_class).items()
                if isinstance(field, (Field, Reference)) and field.identifier)

            setattr(new_class, _ID_FIELD_NAME, id_field.field_name)

            # If the aggregate/entity has been marked abstract,
            #   and contains an identifier field, raise exception
            if new_class.meta_.abstract and id_field:
                raise IncorrectUsageError({
                    "_entity": [
                        f"Abstract Aggregate `{new_class.__name__}` marked as abstract cannot have"
                        " identity fields"
                    ]
                })
        except StopIteration:
            # If no id field is declared then create one
            #   If the aggregate/entity is marked abstract,
            #   avoid creating an identifier field.
            if not new_class.meta_.abstract:
                new_class.__create_id_field()
Ejemplo n.º 5
0
def event_sourced_aggregate_factory(element_cls, **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

            # Associate Event with the aggregate class
            if inspect.isclass(method._event_cls) and issubclass(
                    method._event_cls, BaseEvent):
                # An Event can only be associated with one aggregate class, but multiple event handlers
                #   can consume it.
                if (method._event_cls.meta_.aggregate_cls and
                        method._event_cls.meta_.aggregate_cls != element_cls):
                    raise IncorrectUsageError({
                        "_entity": [
                            f"{method._event_cls.__name__} Event cannot be associated with"
                            f" {element_cls.__name__} because it is already associated with"
                            f" {method._event_cls.meta_.aggregate_cls.__name__}"
                        ]
                    })

                method._event_cls.meta_.aggregate_cls = element_cls

    return element_cls
Ejemplo n.º 6
0
def model_factory(element_cls, **kwargs):
    element_cls.element_type = DomainObjects.MODEL

    if hasattr(element_cls, "Meta"):
        element_cls.meta_ = ModelMeta(element_cls.Meta)
    else:
        element_cls.meta_ = ModelMeta()

    if not (hasattr(element_cls.meta_, "entity_cls")
            and element_cls.meta_.entity_cls):
        element_cls.meta_.entity_cls = kwargs.pop("entity_cls", None)

    if not (hasattr(element_cls.meta_, "schema") and element_cls.meta_.schema):
        element_cls.meta_.schema = kwargs.pop("schema", None)

    if not (hasattr(element_cls.meta_, "database")
            and element_cls.meta_.database):
        element_cls.meta_.database = kwargs.pop("database", None)

    if not element_cls.meta_.entity_cls:
        raise IncorrectUsageError({
            "_entity": [
                f"Model `{element_cls.__name__}` should be associated with an Entity or Aggregate"
            ]
        })

    return element_cls
Ejemplo n.º 7
0
 def __validate_for_basic_field_types(subclass):
     for field_name, field_obj in fields(subclass).items():
         if isinstance(field_obj, (Reference, Association, ValueObject)):
             raise IncorrectUsageError({
                 "_entity": [
                     f"Views can only contain basic field types. "
                     f"Remove {field_name} ({field_obj.__class__.__name__}) from class {subclass.__name__}"
                 ]
             })
Ejemplo n.º 8
0
 def __init__(self, event_cls: "BaseEvent") -> None:
     # Will throw error if the `apply` method is defined without event class
     # E.g.
     # @apply
     # def mark_published(self, event: Published):
     #     ...
     if not inspect.isclass(event_cls):
         raise IncorrectUsageError(
             {"_entity": ["Apply method is missing Event class argument"]})
     self._event_cls = event_cls
Ejemplo n.º 9
0
def event_sourced_repository_factory(element_cls, **opts):
    element_cls = derive_element_class(element_cls, BaseEventSourcedRepository,
                                       **opts)

    if not element_cls.meta_.aggregate_cls:
        raise IncorrectUsageError({
            "_entity": [
                f"Repository `{element_cls.__name__}` should be associated with an Aggregate"
            ]
        })

    if not issubclass(element_cls.meta_.aggregate_cls,
                      BaseEventSourcedAggregate):
        raise IncorrectUsageError({
            "_entity": [
                f"Repository `{element_cls.__name__}` can only be associated with an Aggregate"
            ]
        })

    return element_cls
Ejemplo n.º 10
0
def subscriber_factory(element_cls, **kwargs):
    element_cls = derive_element_class(element_cls, BaseSubscriber, **kwargs)

    if not element_cls.meta_.event:
        raise IncorrectUsageError(
            {
                "_entity": [
                    f"Subscriber `{element_cls.__name__}` needs to be associated with an Event"
                ]
            }
        )

    if not element_cls.meta_.broker:
        raise IncorrectUsageError(
            {
                "_entity": [
                    f"Subscriber `{element_cls.__name__}` needs to be associated with a Broker"
                ]
            }
        )

    return element_cls
Ejemplo n.º 11
0
    def factory_for(self, domain_object_type):
        from protean.core.aggregate import aggregate_factory
        from protean.core.application_service import application_service_factory
        from protean.core.command import command_factory
        from protean.core.command_handler import command_handler_factory
        from protean.core.domain_service import domain_service_factory
        from protean.core.email import email_factory
        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.model import model_factory
        from protean.core.repository import repository_factory
        from protean.core.serializer import serializer_factory
        from protean.core.subscriber import subscriber_factory
        from protean.core.value_object import value_object_factory
        from protean.core.view import view_factory

        factories = {
            DomainObjects.AGGREGATE.value: aggregate_factory,
            DomainObjects.APPLICATION_SERVICE.value:
            application_service_factory,
            DomainObjects.COMMAND.value: command_factory,
            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.DOMAIN_SERVICE.value: domain_service_factory,
            DomainObjects.EMAIL.value: email_factory,
            DomainObjects.ENTITY.value: entity_factory,
            DomainObjects.MODEL.value: model_factory,
            DomainObjects.REPOSITORY.value: repository_factory,
            DomainObjects.SUBSCRIBER.value: subscriber_factory,
            DomainObjects.SERIALIZER.value: serializer_factory,
            DomainObjects.VALUE_OBJECT.value: value_object_factory,
            DomainObjects.VIEW.value: view_factory,
        }

        if domain_object_type.value not in factories:
            raise IncorrectUsageError({
                "_entity":
                [f"Unknown Element Type `{domain_object_type.value}`"]
            })

        return factories[domain_object_type.value]
Ejemplo n.º 12
0
    def __validate_id_field(subclass):
        """Lookup the id field for this view and assign"""
        # FIXME What does it mean when there are no declared fields?
        #   Does it translate to an abstract view?
        if has_fields(subclass):
            try:
                id_field = next(
                    field for _, field in declared_fields(subclass).items()
                    if isinstance(field, (Field)) and field.identifier)

                setattr(subclass, _ID_FIELD_NAME, id_field.field_name)

            except StopIteration:
                raise IncorrectUsageError({
                    "_entity": [
                        f"Event Sourced Aggregate `{subclass.__name__}` needs to have at least one identifier"
                    ]
                })
Ejemplo n.º 13
0
def entity_factory(element_cls, **kwargs):
    element_cls = derive_element_class(element_cls, BaseEntity, **kwargs)

    if element_cls.meta_.abstract is True:
        raise NotSupportedError({
            "_entity": [
                f"`{element_cls.__name__}` class has been marked abstract"
                f" and cannot be instantiated"
            ]
        })

    if not element_cls.meta_.aggregate_cls:
        raise IncorrectUsageError({
            "_entity": [
                f"Entity `{element_cls.__name__}` needs to be associated with an Aggregate"
            ]
        })

    return element_cls
Ejemplo n.º 14
0
def command_handler_factory(element_cls, **kwargs):
    element_cls = derive_element_class(element_cls, BaseCommandHandler,
                                       **kwargs)

    if not element_cls.meta_.aggregate_cls:
        raise IncorrectUsageError({
            "_entity": [
                f"Command Handler `{element_cls.__name__}` needs to be associated with an Aggregate"
            ]
        })

    # Iterate through methods marked as `@handle` and construct a handler map
    if not element_cls._handlers:  # Protect against re-registration
        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, "_target_cls"):
                # Do not allow multiple handlers per command
                if (fully_qualified_name(
                        method._target_cls) in element_cls._handlers
                        and len(element_cls._handlers[fully_qualified_name(
                            method._target_cls)]) != 0):
                    raise NotSupportedError(
                        f"Command {method._target_cls.__name__} cannot be handled by multiple handlers"
                    )

                # `_handlers` maps the command to its handler method
                element_cls._handlers[fully_qualified_name(
                    method._target_cls)].add(method)

                # Associate Command with the handler's stream
                if inspect.isclass(method._target_cls) and issubclass(
                        method._target_cls, BaseCommand):
                    # Order of preference:
                    #   1. Stream name defined in command
                    #   2. Stream name derived from aggregate associated with command handler
                    method._target_cls.meta_.stream_name = (
                        method._target_cls.meta_.stream_name
                        or element_cls.meta_.aggregate_cls.meta_.stream_name)

    return element_cls
Ejemplo n.º 15
0
def event_handler_factory(element_cls, **opts):
    element_cls = derive_element_class(element_cls, BaseEventHandler, **opts)

    if not (element_cls.meta_.aggregate_cls or element_cls.meta_.stream_name):
        raise IncorrectUsageError({
            "_entity": [
                f"Event Handler `{element_cls.__name__}` needs to be associated with an aggregate or a stream"
            ]
        })

    # Iterate through methods marked as `@handle` and construct a handler map
    #
    # Also, if `_target_cls` is an event, associate it with the event handler's
    #   aggregate or stream
    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, "_target_cls"):
            # `_handlers` is a dictionary mapping the event to the handler method.
            if method._target_cls == "$any":
                # This replaces any existing `$any` handler, by design. An Event Handler
                # can have only one `$any` handler method.
                element_cls._handlers["$any"] = {method}
            else:
                element_cls._handlers[fully_qualified_name(
                    method._target_cls)].add(method)

            # Associate Event with the handler's stream
            if inspect.isclass(method._target_cls) and issubclass(
                    method._target_cls, BaseEvent):
                # Order of preference:
                #   1. Stream name defined in event
                #   2. Stream name defined for the event handler
                #   3. Stream name derived from aggregate
                stream_name = element_cls.meta_.stream_name or (
                    element_cls.meta_.aggregate_cls.meta_.stream_name
                    if element_cls.meta_.aggregate_cls else None)
                method._target_cls.meta_.stream_name = (
                    method._target_cls.meta_.stream_name or stream_name)

    return element_cls