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
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)
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
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)
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
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
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)