0

Schema当传递一个对象列表进行验证时,我很难理解如何处理未知字段。我到目前为止:

class MySchema(Schema):
    # fields ...

    @marshmallow_decorators.validates_schema(pass_original=True)
    def check_unknown_fields(self, data, original_data):
        if isinstance(original_data, list):
            for dct in original_data:
                self._assert_no_unknown_field(dct)
        else:
            self._assert_no_unknown_field(original_data)

    def _assert_no_unknown_field(self, dct):
        unknown = set(dct.keys()) - set(self.fields)
        if unknown:
            raise MarshmallowValidationError('Unknown field', unknown)

但这显然不起作用,因为每次都会为列表中的所有项目运行验证器。因此将捕获第一个错误,并在所有项目上返回:

items = [
    {'a': 1, 'b': 2, 'unknown1': 3},
    {'a': 4, 'b': 5, 'unknown2': 6},
]
errors = MySchema(many=True).validate(items)
# {0: {'unknown1': ['Unknown field']}, 1: {'unknown1': ['Unknown field']}}

我试图想出一种方法来仅从original_data对应于data参数的单个项目中获取并仅验证该项目,但我不能真正做到这一点,因为项目没有 id 或使它们可搜索的字段......

我错过了什么吗?有针对这个的解决方法吗?

4

2 回答 2

1

在 marshmallow 3.0+ 中有unknownMeta 中的字段,即:

def test_validate(self):
        class ModelSchema(Schema):
            class Meta:
                unknown = RAISE
            name = fields.String()

        schema = ModelSchema()
        data = dict(name='jfaleiro', xyz=2)
        schema.validate(data) # passes
        schema.load(data) # fails (as intended)

虽然它通过validate和失败的原因有点矛盾load

于 2019-05-24T14:15:17.780 回答
1

这是我想出的解决方法......我希望它更简单,但它是:

from marshmallow import Schema, ValidationError as MarshmallowValidationError, fields

UNKNOWN_MESSAGE = 'unknown field'


class _RejectUnknownMixin(object):

    def _collect_unknown_fields_errors(self, schema, data):
        """
        Checks `data` against `schema` and returns a dictionary `{<field>: <error>}`
        if unknown fields detected, or `{0: {<field>: <error>}, ... N: <field>: <error>}`
        if `data` is a list.
        """
        if isinstance(data, list):
            validation_errors = {}
            for i, datum in enumerate(data):
                datum_validation_errors = self._collect_unknown_fields_errors(schema, datum)
                if datum_validation_errors:
                    validation_errors[i] = datum_validation_errors
            return validation_errors

        else:
            unknown = set(data.keys()) - set(schema.fields)
            return {name: [UNKNOWN_MESSAGE] for name in unknown}


class NestedRejectUnknown(fields.Nested, _RejectUnknownMixin):
    """
    Nested field that returns validation errors if unknown fields are detected.
    """

    def _deserialize(self, value, attr, data):
        validation_errors = {}
        try:
            result = super(NestedRejectUnknown, self)._deserialize(value, attr, data)
        except MarshmallowValidationError as err:
            validation_errors = err.normalized_messages()

        # Merge with unknown field errors
        validation_errors = _merge_dicts(
            self._collect_unknown_fields_errors(self.schema, value), validation_errors)
        if validation_errors:
            raise MarshmallowValidationError(validation_errors)

        return result


class SchemaRejectUnknown(Schema, _RejectUnknownMixin):
    """
    Schema that return validation errors if unknown fields are detected
    """

    def validate(self, data, **kwargs):
        validation_errors = super(SchemaRejectUnknown, self).validate(data, **kwargs)
        return _merge_dicts(
            self._collect_unknown_fields_errors(self, data), validation_errors)


def _merge_dicts(a, b, path=None):
    """
    Ref : https://stackoverflow.com/questions/7204805/dictionaries-of-dictionaries-merge
    merges b into a
    """
    if path is None:
        path = []
    for key in b:
        if key in a:
            if isinstance(a[key], dict) and isinstance(b[key], dict):
                _merge_dicts(a[key], b[key], path + [str(key)])
            elif a[key] == b[key]:
                # same leaf value
                pass
            else:
                raise Exception('Conflict at %s' % '.'.join(path + [str(key)]))
        else:
            a[key] = b[key]
    return a
于 2017-06-09T12:41:56.867 回答