def from_representation(self, representation): """Convert given representation dict into internal object. Internal object is simply a dictionary of values with respect to field sources. This does not check if all required fields exist or values are valid in terms of value validation (see: :meth:`BaseField.validate()`) but still requires all of passed representation values to be well formed representation (success call to ``field.from_representation``). In case of malformed representation it will run additional validation only to provide a full detailed exception about all that might be wrong with provided representation. Args: representation (dict): dictionary with field representation values Raises: DeserializationError: when at least one representation field is not formed as expected by field object. Information about additional forbidden/missing/invalid fields is provided as well. """ object_dict = {} failed = {} for name, field in self.fields.items(): if name not in representation: continue try: # if field has explicitely specified source then use it # else fallback to field name. # Note: field does not know its name object_dict[field.source or name] = field.from_representation( representation[name]) except ValueError as err: failed[name] = str(err) if failed: # if failed to parse we eagerly perform validation so full # information about what is wrong will be returned try: self.validate(object_dict) # note: this exception can be reached with partial==True # since do not support partial updates yet this has 'no cover' raise DeserializationError() # pragma: no cover except DeserializationError as err: err.failed = failed raise return object_dict
def validate(self, object_dict, partial=False): """ Validate given internal object agains missing/forbidden/invalid fields values using fields definitions defined in serializer. Args: object_dict (dict): internal object dictionart to perform to validate partial (bool): if set to True then incomplete object_dict is accepter and will not raise any exceptions when one of fields is missing Raises: DeserializationError: """ # we are working on object_dict not an representation so there # is a need to annotate sources differently sources = { # TODO: handling of '*' sources here is a bit terryfying # TODO: maybe this needs more care in future releases field.source or name if field.source != "*" else name: field for name, field in self.fields.items() } # note: we are checking for all mising and invalid fields so we can # return exception with all fields that are missing and should # exist instead of single one missing = [ name for name, field in sources.items() if all((not partial, name not in object_dict, not field.read_only)) ] forbidden = [ name for name in object_dict if any((name not in sources, sources[name].read_only)) ] invalid = {} for name, value in object_dict.items(): try: sources[name].validate(value) except ValidationError as err: invalid[name] = str(err) if any([missing, forbidden, invalid]): raise DeserializationError(missing, forbidden, invalid)
def validate(self, object_dict, partial=False): """Validate given internal object returned by ``to_representation()``. Internal object is validated against missing/forbidden/invalid fields values using fields definitions defined in serializer. Args: object_dict (dict): internal object dictionart to perform to validate partial (bool): if set to True then incomplete object_dict is accepter and will not raise any exceptions when one of fields is missing Raises: DeserializationError: """ # we are working on object_dict not an representation so there # is a need to annotate sources differently sources = { _source(name, field): field for name, field in self.fields.items() } # note: we are checking for all mising and invalid fields so we can # return exception with all fields that are missing and should # exist instead of single one missing = [ name for name, field in sources.items() if all((not partial, name not in object_dict, not field.read_only)) ] forbidden = [ name for name in object_dict if any((name not in sources, sources[name].read_only)) ] invalid = {} for name, value in object_dict.items(): try: field = sources[name] if field.many: for single_value in value: field.validate(single_value) else: field.validate(value) except ValueError as err: invalid[name] = str(err) if any([missing, forbidden, invalid]): # note: We have validated internal object instance but need to # inform the user about problems with his representation. # This is why we have to do this dirty transformation. # note: This will be removed in 1.0.0 where we change how # validation works and where we remove star-like fields. # refs: #42 (https://github.com/swistakm/graceful/issues/42) sources_to_field_names = { _source(name, field): name for name, field in self.fields.items() } def _(names): if isinstance(names, list): return [ sources_to_field_names.get(name, name) for name in names ] elif isinstance(names, dict): return { sources_to_field_names.get(name, name): value for name, value in names.items() } else: return names # pragma: nocover raise DeserializationError(_(missing), _(forbidden), _(invalid))
def from_representation(self, representation): """Convert given representation dict into internal object. Internal object is simply a dictionary of values with respect to field sources. This does not check if all required fields exist or values are valid in terms of value validation (see: :meth:`BaseField.validate()`) but still requires all of passed representation values to be well formed representation (success call to ``field.from_representation``). In case of malformed representation it will run additional validation only to provide a full detailed exception about all that might be wrong with provided representation. Args: representation (dict): dictionary with field representation values Raises: DeserializationError: when at least one representation field is not formed as expected by field object. Information about additional forbidden/missing/invalid fields is provided as well. """ object_dict = {} failed = {} for name, field in self.fields.items(): if name not in representation: continue try: if ( # note: we cannot check for any sequence or iterable # because of strings and nested dicts. not isinstance(representation[name], (list, tuple)) and field.many): raise ValueError("field should be sequence") source = _source(name, field) value = representation[name] if field.many: if not field.allow_null: object_dict[source] = [ field.from_representation(single_value) for single_value in value ] else: object_dict[source] = [ field.from_representation(single_value) if single_value is not None else None for single_value in value ] else: if not field.allow_null: object_dict[source] = field.from_representation(value) else: object_dict[source] = field.from_representation( value) if value else None except ValueError as err: failed[name] = str(err) if failed: # if failed to parse we eagerly perform validation so full # information about what is wrong will be returned try: self.validate(object_dict) # note: this exception can be reached with partial==True # since do not support partial updates yet this has 'no cover' raise DeserializationError() # pragma: no cover except DeserializationError as err: err.failed = failed raise return object_dict