Beispiel #1
0
    def _deserialize(
        self,
        value: typing.Any,
        attr: typing.Optional[str],
        data: typing.Optional[typing.Mapping[str, typing.Any]],
        **kwargs,
    ):
        error_store = ErrorStore()
        if self.many:
            result = []
            if utils.is_collection(value):
                for collection_entry in value:
                    result.append(self._check_schemas(collection_entry))
                return result

            raise self.make_error("type",
                                  input=value,
                                  type=value.__class__.__name__)

        for schema in self.nested:
            try:
                return common.resolve_schema_instance(schema).load(
                    value, unknown=self.unknown)
            except ValidationError as exc:
                error_store.store_error(exc.messages,
                                        field_name=exc.field_name)

        raise ValidationError(error_store.errors, data=value)
Beispiel #2
0
    def _load_schemas(self, scalar: Result, partial=None) -> Result:
        rv = {}
        error_store = ErrorStore()
        value = dict(scalar)
        for schema in self._nested_schemas():
            schema_inst = common.resolve_schema_instance(schema)
            try:
                loaded = schema_inst.load(
                    value,
                    unknown=self.unknown,
                    partial=partial,
                )
                if not self.merged:
                    return loaded

                for key in schema_inst.declared_fields:
                    if key in value:
                        del value[key]
            except ValidationError as exc:
                for key in schema_inst.declared_fields:
                    if key in value:
                        del value[key]
                error_store.store_error({exc.field_name: exc.messages})
                continue

            if self.merged:
                rv.update(loaded)

        if error_store.errors:
            raise ValidationError(error_store.errors)
        return rv
Beispiel #3
0
    def _serialize(
        self,
        value: typing.Any,
        attr: str,
        obj: typing.Any,
        **kwargs,
    ) -> typing.Union[Result, list[Result]]:
        result: typing.Any
        error_store = ErrorStore()

        result = []
        if self.many:
            if utils.is_collection(value):
                for entry in value:
                    result.extend(self._dump_schemas(entry))
            else:
                error_store.store_error(self._make_type_error(value))
        else:
            result.extend(self._dump_schemas(value))

        if error_store.errors:
            raise ValidationError(error_store.errors, data=value)

        if self.merged and not self.many:
            rv = {}
            for entry in result:
                for key, _value in entry.items():
                    if key in rv:
                        raise ValidationError({key: "Can't collate result. Key occurs twice."})
                    rv[key] = _value
            return rv

        return result
Beispiel #4
0
    def _dump_schemas(self, scalar: Result) -> list[Result]:
        rv = []
        error_store = ErrorStore()
        value = dict(scalar)
        for schema in self._nested_schemas():
            schema_inst = common.resolve_schema_instance(schema)
            try:
                dumped = schema_inst.dump(value, many=False)
                if not self.merged:
                    return dumped
                loaded = schema_inst.load(dumped)
                # We check what could actually pass through the load() call, because some
                # schemas validate keys without having them defined in their _declared_fields.
                for key in loaded.keys():
                    if key in value:
                        del value[key]
            except ValidationError as exc:
                # When we encounter an error, we can't do anything besides remove the keys, which
                # we know about.
                for key in schema_inst.declared_fields:
                    if key in value:
                        del value[key]
                error_store.store_error({exc.field_name: exc.messages})
                continue

            if not isinstance(schema_inst, MultiNested.ValidateOnDump):
                rv.append(dumped)

        if error_store.errors:
            raise ValidationError(error_store.errors)

        return rv
Beispiel #5
0
    def _check_schemas(self, scalar, partial=None):
        error_store = ErrorStore()
        for schema in self.nested:
            try:
                return common.resolve_schema_instance(schema).load(
                    scalar,
                    unknown=self.unknown,
                    partial=partial,
                )
            except ValidationError as exc:
                error_store.store_error(exc.messages,
                                        field_name=exc.field_name)

        raise ValidationError(error_store.errors, data=scalar)
Beispiel #6
0
    def _serialize(
        self,
        value: typing.Any,
        attr: str,
        obj: typing.Any,
        **kwargs,
    ):
        if value is None:
            return None

        error_store = ErrorStore()
        for schema in self.nested:
            try:
                return common.resolve_schema_instance(schema).dump(
                    value, many=self.many)
            except ValidationError as exc:
                error_store.store_error(exc.messages,
                                        field_name=exc.field_name)

        raise ValidationError(error_store.errors, data=value)
Beispiel #7
0
    def _deserialize(
            self,
            data: typing.Union[
                typing.Mapping[str, typing.Any],
                typing.Iterable[typing.Mapping[str, typing.Any]],
            ],
            *,
            error_store: ErrorStore,
            many: bool = False,
            partial=False,
            unknown=RAISE,
            index=None
    ) -> typing.Union[_T, typing.List[_T]]:
        """Deserialize ``data``.

        :param dict data: The data to deserialize.
        :param ErrorStore error_store: Structure to store errors.
        :param bool many: `True` if ``data`` should be deserialized as a collection.
        :param bool|tuple partial: Whether to ignore missing fields and not require
            any fields declared. Propagates down to ``Nested`` fields as well. If
            its value is an iterable, only missing fields listed in that iterable
            will be ignored. Use dot delimiters to specify nested fields.
        :param unknown: Whether to exclude, include, or raise an error for unknown
            fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`.
        :param int index: Index of the item being serialized (for storing errors) if
            serializing a collection, otherwise `None`.
        :return: A dictionary of the deserialized data.
        """
        index_errors = self.opts.index_errors
        index = index if index_errors else None
        if many:
            if not is_collection(data):
                error_store.store_error([self.error_messages["type"]], index=index)
                ret = []  # type: typing.List[_T]
            else:
                ret = [
                    typing.cast(
                        _T,
                        self._deserialize(
                            typing.cast(typing.Mapping[str, typing.Any], d),
                            error_store=error_store,
                            many=False,
                            partial=partial,
                            unknown=unknown,
                            index=idx,
                        ),
                    )
                    for idx, d in enumerate(data)
                ]
            return ret
        ret = self.dict_class()
        # Check data is a dict
        if not isinstance(data, Mapping):
            error_store.store_error([self.error_messages["type"]], index=index)
        else:
            partial_is_collection = is_collection(partial)
            for attr_name, field_obj in self.load_fields.items():
                field_name = (
                    field_obj.data_key if field_obj.data_key is not None else attr_name
                )
                raw_value = data.get(field_name, missing)
                if raw_value is missing:
                    # Ignore missing field if we're allowed to.
                    if partial is True or (
                            partial_is_collection and attr_name in partial
                    ):
                        continue
                d_kwargs = {}
                # Allow partial loading of nested schemas.
                if partial_is_collection:
                    prefix = field_name + "."
                    len_prefix = len(prefix)
                    sub_partial = [
                        f[len_prefix:] for f in partial if f.startswith(prefix)
                    ]
                    d_kwargs["partial"] = sub_partial
                else:
                    d_kwargs["partial"] = partial
                getter = lambda val: field_obj.deserialize(
                    val, field_name, data, **d_kwargs
                )
                value = self._call_and_store(
                    getter_func=getter,
                    data=raw_value,
                    field_name=field_name,
                    error_store=error_store,
                    index=index,
                )
                if value is not missing:
                    key = field_obj.attribute or attr_name
                    set_value(typing.cast(typing.Dict, ret), key, value)
            if unknown != EXCLUDE:
                fields = {
                    field_obj.data_key if field_obj.data_key is not None else field_name
                    for field_name, field_obj in self.load_fields.items()
                }
                for key in set(data) - fields:
                    value = data[key]
                    if unknown == INCLUDE:
                        set_value(typing.cast(typing.Dict, ret), key, value)
                    elif unknown == RAISE:
                        error_store.store_error(
                            [self.error_messages["unknown"]],
                            key,
                            (index if index_errors else None),
                        )
        return ret
Beispiel #8
0
    def _deserialize(
        self,
        data: typing.Union[typing.Mapping[str, typing.Any],
                           typing.Iterable[typing.Mapping[str, typing.Any]], ],
        *args,
        error_store: ErrorStore,
        many: bool = False,
        partial=False,
        unknown=RAISE,
        index=None,
    ) -> typing.Union[_T, typing.List[_T]]:
        index_errors = self.opts.index_errors
        index = index if index_errors else None

        if self.flattened and is_collection(data) and not self._all_objects:
            new_data = []
            self._all_objects = {}

            for d in data:
                self._all_objects[d["@id"]] = d

                if self._compare_ids(d["@type"], self.opts.rdf_type):
                    new_data.append(d)
            data = new_data

            if len(data) == 1:
                data = data[0]

        if many:
            if not is_collection(data):
                error_store.store_error([self.error_messages["type"]],
                                        index=index)
                ret = []  # type: typing.List[_T]
            else:
                ret = [
                    typing.cast(
                        _T,
                        self._deserialize(
                            typing.cast(typing.Mapping[str, typing.Any], d),
                            error_store=error_store,
                            many=False,
                            partial=partial,
                            unknown=unknown,
                            index=idx,
                        ),
                    ) for idx, d in enumerate(data)
                ]
            return ret
        ret = self.dict_class()
        # Check data is a dict
        if not isinstance(data, Mapping):
            error_store.store_error([self.error_messages["type"]], index=index)
        else:
            if data.get("@context", None):
                # we got compacted jsonld, expand it
                data = jsonld.expand(data)

            partial_is_collection = is_collection(partial)
            for attr_name, field_obj in self.load_fields.items():
                field_name = field_obj.data_key if field_obj.data_key is not None else attr_name

                if getattr(field_obj, "reverse", False):
                    raw_value = data.get("@reverse", missing)
                    if raw_value is not missing:
                        raw_value = raw_value.get(field_name, missing)
                    elif self.flattened:
                        # find an object that refers to this one with the same property
                        raw_value = self.get_reverse_links(data, field_name)

                        if not raw_value:
                            raw_value = missing
                else:
                    raw_value = data.get(field_name, missing)

                if raw_value is missing:
                    # Ignore missing field if we're allowed to.
                    if partial is True or (partial_is_collection
                                           and attr_name in partial):
                        continue

                d_kwargs = {}
                # Allow partial loading of nested schemes.
                if partial_is_collection:
                    prefix = field_name + "."
                    len_prefix = len(prefix)
                    sub_partial = [
                        f[len_prefix:] for f in partial if f.startswith(prefix)
                    ]
                    d_kwargs["partial"] = sub_partial
                else:
                    d_kwargs["partial"] = partial

                d_kwargs["_all_objects"] = self._all_objects
                d_kwargs["flattened"] = self.flattened
                getter = lambda val: field_obj.deserialize(
                    val, field_name, data, **d_kwargs)
                value = self._call_and_store(
                    getter_func=getter,
                    data=raw_value,
                    field_name=field_name,
                    error_store=error_store,
                    index=index,
                )
                if value is not missing:
                    key = field_obj.attribute or attr_name
                    set_value(typing.cast(typing.Dict, ret), key, value)
            if unknown != EXCLUDE:
                fields = {
                    field_obj.data_key
                    if field_obj.data_key is not None else field_name
                    for field_name, field_obj in self.load_fields.items()
                }
                for key in set(data) - fields:
                    if key in ["@type", "@reverse"]:
                        # ignore JsonLD meta fields
                        continue

                    value = data[key]
                    if unknown == INCLUDE:
                        set_value(typing.cast(typing.Dict, ret), key, value)
                    elif unknown == RAISE:
                        error_store.store_error(
                            [self.error_messages["unknown"]],
                            key,
                            (index if index_errors else None),
                        )

        return ret