Ejemplo n.º 1
0
def entity_factory(element_cls, **kwargs):
    element_cls = derive_element_class(element_cls, BaseEntity)

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

    element_cls.meta_.provider = (kwargs.pop("provider", None)
                                  or (hasattr(element_cls, "meta_")
                                      and element_cls.meta_.provider)
                                  or "default")
    element_cls.meta_.model = (kwargs.pop("model", None)
                               or (hasattr(element_cls, "meta_")
                                   and element_cls.meta_.model) or None)
    element_cls.meta_.aggregate_cls = (kwargs.pop("aggregate_cls", None)
                                       or (hasattr(element_cls, "meta_")
                                           and element_cls.meta_.aggregate_cls)
                                       or None)

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

    return element_cls
Ejemplo n.º 2
0
    def __init__(self, *template, owner=None, raise_errors=True, **kwargs):
        """
        Initialise the container.

        During initialization, set value on fields if validation passes.

        This initialization technique supports keyword arguments as well as dictionaries. You
            can even use a template for initial data.
        """

        if self.meta_.abstract is True:
            raise NotSupportedError(
                f'{self.__class__.__name__} class has been marked abstract'
                f' and cannot be instantiated')

        self.errors = defaultdict(list)
        self.raise_errors = raise_errors

        # Entity/Aggregate to which this Command is connected to
        self.owner = owner

        # Load the attributes based on the template
        loaded_fields = []
        for dictionary in template:
            if not isinstance(dictionary, dict):
                raise AssertionError(
                    f'Positional argument "{dictionary}" passed must be a dict.'
                    f'This argument serves as a template for loading common '
                    f'values.', )
            for field_name, val in dictionary.items():
                loaded_fields.append(field_name)
                setattr(self, field_name, val)

        # Now load against the keyword arguments
        for field_name, val in kwargs.items():
            loaded_fields.append(field_name)
            setattr(self, field_name, val)

        # Now load the remaining fields with a None value, which will fail
        # for required fields
        for field_name, field_obj in self.meta_.declared_fields.items():
            if field_name not in loaded_fields:
                setattr(self, field_name, None)

        self.defaults()

        # `clean()` will return a `defaultdict(list)` if errors are to be raised
        custom_errors = self.clean() or {}
        for field in custom_errors:
            self.errors[field].extend(custom_errors[field])

        # Raise any errors found during load
        if self.errors and self.raise_errors:
            logger.error(self.errors)
            raise ValidationError(self.errors)
Ejemplo n.º 3
0
    def _validate_entity_class(cls, element_cls):
        if not issubclass(element_cls, BaseEntity):
            raise AssertionError(
                f'Element {element_cls.__name__} must be subclass of `BaseEntity`'
            )

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

        return True
Ejemplo n.º 4
0
    def register(self, element_cls, **kwargs):
        """Register an element already subclassed with the correct Hierarchy"""
        if getattr(element_cls, 'element_type',
                   None) not in [element for element in DomainObjects]:
            raise NotImplementedError

        if (hasattr(element_cls, 'meta_')
                and hasattr(element_cls.meta_, 'abstract')
                and element_cls.meta_.abstract is True):
            raise NotSupportedError(
                f'{element_cls.__name__} class has been marked abstract'
                ' and cannot be instantiated')

        return self._register_element(element_cls.element_type, element_cls,
                                      **kwargs)
Ejemplo n.º 5
0
def aggregate_factory(element_cls, **kwargs):
    element_cls = derive_element_class(element_cls, BaseAggregate)

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

    element_cls.meta_.provider = (kwargs.pop("provider", None)
                                  or (hasattr(element_cls, "meta_")
                                      and element_cls.meta_.provider)
                                  or "default")
    element_cls.meta_.model = (kwargs.pop("model", None)
                               or (hasattr(element_cls, "meta_")
                                   and element_cls.meta_.model) or None)

    return element_cls
Ejemplo n.º 6
0
    def __new__(mcs, name, bases, attrs, **kwargs):  # noqa: C901
        """Initialize Serializer MetaClass and load attributes"""
        def _declared_base_class_fields(bases, attrs):
            """If this class is subclassing another Serializer, add that Serializer's
            fields.  Note that we loop over the bases in *reverse*.
            This is necessary in order to maintain the correct order of fields.
            """
            declared_fields = {}

            for base in reversed(bases):
                if hasattr(base, 'meta_') and \
                        hasattr(base.meta_, 'declared_fields'):
                    base_class_fields = {
                        field_name: field_obj
                        for (field_name,
                             field_obj) in base.meta_.declared_fields.items()
                        if field_name not in attrs and not field_obj.identifier
                    }
                    declared_fields.update(base_class_fields)

            return declared_fields

        def _declared_fields(attrs):
            """Load field items into Class"""
            declared_fields = {}

            for attr_name, attr_obj in attrs.items():
                if isinstance(attr_obj, Field):
                    declared_fields[attr_name] = attr_obj

            return declared_fields

        # Ensure initialization is only performed for subclasses of Serializer
        # (excluding Serializer class itself).
        parents = [b for b in bases if isinstance(b, _SerializerMetaclass)]
        if not parents:
            return super().__new__(mcs, name, bases, attrs)

        # Load declared fields
        declared_fields = _declared_fields(attrs)

        # Load declared fields from Base class, in case this Entity is subclassing another
        base_class_fields = _declared_base_class_fields(bases, attrs)

        all_fields = {**declared_fields, **base_class_fields}

        schema_fields = {}
        for field_name, field_obj in all_fields.items():
            if isinstance(field_obj, Boolean):
                schema_fields[field_name] = fields.Boolean()
            elif isinstance(field_obj, Date):
                schema_fields[field_name] = fields.Date()
            elif isinstance(field_obj, DateTime):
                schema_fields[field_name] = fields.DateTime()
            elif isinstance(field_obj, Identifier):
                schema_fields[field_name] = fields.String()
            elif isinstance(field_obj, String):
                schema_fields[field_name] = fields.String()
            elif isinstance(field_obj, Text):
                schema_fields[field_name] = fields.String()
            elif isinstance(field_obj, Integer):
                schema_fields[field_name] = fields.Integer()
            elif isinstance(field_obj, Float):
                schema_fields[field_name] = fields.Float()
            elif isinstance(field_obj, Method):
                schema_fields[field_name] = fields.Method(
                    field_obj.method_name)
            elif isinstance(field_obj, List):
                schema_fields[field_name] = fields.List(
                    fields.String())  # FIXME Accept type param in List field
            elif isinstance(field_obj,
                            Dict):  # FIXME Accept type param in Dict field
                schema_fields[field_name] = fields.Dict(
                    keys=fields.Str(), values=fields.Boolean())
            elif isinstance(field_obj, Nested):
                schema_fields[field_name] = fields.Nested(
                    field_obj.schema_name, many=field_obj.many)
            else:
                raise NotSupportedError("{} Field not supported".format(
                    type(field_obj)))

        # Remove Protean fields from Serializer class
        for field_name in schema_fields:
            attrs.pop(field_name, None)

        # Update `attrs` with new marshmallow fields
        attrs.update(schema_fields)

        # Remove `abstract` in base classes if defined
        for base in bases:
            if hasattr(base, 'Meta') and hasattr(base.Meta, 'abstract'):
                delattr(base.Meta, 'abstract')

        # Explicit redefinition element_type  necessary because `attrs`
        #   are reset when a serializer class is initialized.
        attrs['element_type'] = DomainObjects.SERIALIZER

        new_class = type(name, (Schema, ), attrs)

        # Gather `Meta` class/object if defined
        attr_meta = attrs.pop('Meta', None)
        meta = attr_meta or getattr(new_class, 'Meta', None)
        setattr(new_class, 'meta_', SerializerMeta(meta, declared_fields))

        return new_class
Ejemplo n.º 7
0
    def __init__(self, *template, raise_errors=True, **kwargs):  # noqa: C901
        """
        Initialise the entity object.

        During initialization, set value on fields if validation passes.

        This initialization technique supports keyword arguments as well as dictionaries. The objects initialized
        in the following example have the same structure::

            user1 = User({'first_name': 'John', 'last_name': 'Doe'})

            user2 = User(first_name='John', last_name='Doe')

        You can also specify a template for initial data and override specific attributes::

            base_user = User({'age': 15})

            user = User(base_user.to_dict(), first_name='John', last_name='Doe')
        """

        if self.meta_.abstract is True:
            raise NotSupportedError(
                f'{self.__class__.__name__} class has been marked abstract'
                f' and cannot be instantiated')

        self.errors = defaultdict(list)
        self.raise_errors = raise_errors

        # Set up the storage for instance state
        self.state_ = _EntityState()

        # Load the attributes based on the template
        loaded_fields = []
        for dictionary in template:
            if not isinstance(dictionary, dict):
                raise AssertionError(
                    f'Positional argument "{dictionary}" passed must be a dict.'
                    f'This argument serves as a template for loading common '
                    f'values.', )
            for field_name, val in dictionary.items():
                if field_name not in kwargs:
                    kwargs[field_name] = val

        # Now load against the keyword arguments
        for field_name, val in kwargs.items():
            loaded_fields.append(field_name)
            try:
                setattr(self, field_name, val)
            except ValidationError as err:
                for field_name in err.messages:
                    self.errors[field_name].extend(err.messages[field_name])

        # Load Value Objects from associated fields
        #   This block will dynamically construct value objects from field values
        #   and associated the vo with the entity
        # If the value object was already provided, it will not be overridden.
        for field_name, field_obj in self.meta_.declared_fields.items():
            if isinstance(
                    field_obj,
                (ValueObjectField)) and not getattr(self, field_name):
                attributes = [
                    (embedded_field.field_name, embedded_field.attribute_name)
                    for embedded_field in field_obj.embedded_fields.values()
                ]
                values = {name: kwargs.get(attr) for name, attr in attributes}
                try:
                    value_object = field_obj.value_object_cls.build(**values)
                    # Set VO value only if the value object is not None/Empty
                    if value_object:
                        setattr(self, field_name, value_object)
                        loaded_fields.append(field_name)
                except ValidationError as err:
                    for sub_field_name in err.messages:
                        self.errors['{}_{}'.format(
                            field_name, sub_field_name)].extend(
                                err.messages[sub_field_name])

        # Load Identities
        if not getattr(self, self.meta_.id_field.field_name,
                       None) and type(self.meta_.id_field) is Auto:
            setattr(self, self.meta_.id_field.field_name,
                    self.generate_identity())
            loaded_fields.append(self.meta_.id_field.field_name)

        # Load Associations
        for field_name, field_obj in self.meta_.declared_fields.items():
            if isinstance(field_obj, Association):
                getattr(
                    self,
                    field_name)  # This refreshes the values in associations

        # Now load the remaining fields with a None value, which will fail
        # for required fields
        for field_name, field_obj in self.meta_.declared_fields.items():
            if field_name not in loaded_fields:
                if not isinstance(field_obj,
                                  (Reference, _ReferenceField, Association)):
                    try:
                        setattr(self, field_name, None)

                        # If field is a VO, set underlying attributes to None as well
                        if isinstance(field_obj, ValueObjectField):
                            for embedded_field in field_obj.embedded_fields.values(
                            ):
                                setattr(self, embedded_field.attribute_name,
                                        None)
                    except ValidationError as err:
                        for field_name in err.messages:
                            self.errors[field_name].extend(
                                err.messages[field_name])

        for field_name, field_obj in self.meta_.attributes.items():
            if field_name not in loaded_fields and not hasattr(
                    self, field_name):
                setattr(self, field_name, None)

        self.defaults()

        # `clean()` will return a `defaultdict(list)` if errors are to be raised
        custom_errors = self.clean() or {}
        for field in custom_errors:
            self.errors[field].extend(custom_errors[field])

        # Raise any errors found during load
        if self.errors and self.raise_errors:
            logger.error(self.errors)
            raise ValidationError(self.errors)