使用棉花糖验证具有列表长度约束的模式列表

时间:2019-12-05 15:31:54

标签: python marshmallow

例如,我要检查数据是否包含格式正确的字典列表,并且此列表的长度在1到10之间。

from marshmallow import Schema, fields

class Record(Schema): 
    id = fields.Integer(required=True)
    # more fields here, let's omit them

schema = Record(many=True)
# somehow define that we have constraint on list length
# list length should be between 1 and 10 inclusive

# validation should fail
errors = schema.validate([])
assert errors  # length < 1
errors = schema.validate([{"id": i} for i in range(100)])
assert errors  # length > 10

# validation should succeed
errors = schema.validate([{"id": i} for i in range(5)])
assert not errors

是否可以使用棉花糖定义此类约束?


我需要这样的东西,但我想避免在数据中嵌套更多级别:

from marshmallow.validate import Length

class BatchOfRecords(Schema):
    records = fields.Nested(
        Record,
        required=True,
        many=True,
        validate=Length(1, 10)
    )

UPD:

因此,为了澄清这个问题,我想验证一列字典:

[
    {"id": 1},
    {"id": 2},
    ...
]

不是字典,其键包含字典列表:

# it works but it introduces extra level of nesting,
# I want to avoid it
{
    "records": [
        {"id": 1},
        {"id": 2},
        ...
    ]
}

2 个答案:

答案 0 :(得分:1)

编辑

因此可以仅使用棉花糖来验证集合。您可以将pass_many kwarg与pre_loadpost_load方法一起使用。我在pre_load上没有获得成功,但是开始从事邮政工作。 pass_many kwarg会将输入视为集合,因此您可以在加载后检查集合的长度。我使用many kwarg,以便仅在传递记录集而不是单个记录时才检查长度

from marshmallow import Schema, fields, ValidationError, post_load


class Record(Schema):
    id = fields.Integer(required=True)
    name = fields.String(required=True)
    status = fields.String(required=True)

    @post_load(pass_many=True)
    def check_length(self, data, many, **kwargs):
        if many:
            if len(data) < 1 or len(data) > 10:
                raise ValidationError(message=['Record length should be greater than 1 and less than 10.'],
                                      field_name='record')

编辑测试案例

from unittest import TestCase

from marshmallow import ValidationError

from stack_marshmallow import Record


class TestStackSchemasNonNested(TestCase):

    def test_empty_dict(self):
        with self.assertRaises(ValidationError) as exc:
            Record(many=True).load([])
        self.assertEqual(exc.exception.messages['record'], ['Record length should be greater than 1 and less than 10.'])

    def test_happy_path(self):
        user_data = [{"id": "1", "name": "apple", "status": "OK"}, {"id": "2", "name": "apple", "status": 'OK'}]
        data = Record(many=True).load(user_data)
        self.assertEqual(len(data), 2)

    def test_invalid_values_with_valid_values(self):
        user_data = [{"id": "1", "name": "apple", "status": 'OK'}, {"id": "2"}]
        with self.assertRaises(ValidationError) as exc:
            Record(many=True).load(user_data)
        self.assertEqual(exc.exception.messages[1]['name'], ['Missing data for required field.'])
        self.assertEqual(exc.exception.messages[1]['status'], ['Missing data for required field.'])

    def test_too_many(self):
        user_data = [{"id": "1", "name": "apple", "status": "OK"},
                     {"id": "2", "name": "apple", "status": 'OK'},
                     {"id": "3", "name": "apple", "status": 'OK'},
                     {"id": "4", "name": "apple", "status": 'OK'},
                     {"id": "5", "name": "apple", "status": 'OK'},
                     {"id": "6", "name": "apple", "status": 'OK'},
                     {"id": "7", "name": "apple", "status": 'OK'},
                     {"id": "8", "name": "apple", "status": 'OK'},
                     {"id": "9", "name": "apple", "status": 'OK'},
                     {"id": "10", "name": "apple", "status": 'OK'},
                     {"id": "11", "name": "apple", "status": 'OK'},
                     ]
        with self.assertRaises(ValidationError) as exc:
            Record(many=True).load(user_data)
        self.assertEqual(exc.exception.messages['record'], ['Record length should be greater than 1 and less than 10.'])
  

编辑源https://marshmallow.readthedocs.io/en/stable/extending.html

您非常亲密。我增加了一些记录的复杂性,因为我认为您不会只拥有一个字段,否则我只会使用整数列表。我还添加了一些单元测试,以便您了解如何进行测试。

from marshmallow import Schema, fields, validate


class Record(Schema):
    id = fields.Integer(required=True)
    name = fields.String(required=True)
    status = fields.String(required=True)


class Records(Schema):
    records = fields.List(
        fields.Nested(Record),
        required=True,
        validate=validate.Length(min=1,max=10)
    )

测试案例

from unittest import TestCase

from marshmallow import ValidationError

from stack_marshmallow import Records


class TestStackSchemas(TestCase):

    def setUp(self):
        self.schema = Records()

    def test_empty_dict(self):
        with self.assertRaises(ValidationError) as exc:
            self.schema.load({})
        self.assertEqual(exc.exception.messages['records'], ['Missing data for required field.'])

    def test_empty_empty_list_in_dict(self):
        with self.assertRaises(ValidationError) as exc:
            self.schema.load({"records": []})
        self.assertEqual(exc.exception.messages['records'], ['Length must be between 1 and 10.'])

    def test_missing_fields_in_single_record(self):
        with self.assertRaises(ValidationError) as exc:
            self.schema.load({"records": [{"id": 1}]})
        self.assertEqual(exc.exception.messages['records'][0]['name'], ['Missing data for required field.'])
        self.assertEqual(exc.exception.messages['records'][0]['status'], ['Missing data for required field.'])

    def test_list_too_long_and_invalid_records(self):
        with self.assertRaises(ValidationError) as exc:
            self.schema.load({"records":
                                  [{"id": 1, "name": "stack", "status": "overflow"},
                                   {"id": 2, "name": "stack", "status": "overflow"},
                                   {"id": 3, "name": "stack", "status": "overflow"},
                                   {"id": 4, "name": "stack", "status": "overflow"},
                                   {"id": 5, "name": "stack", "status": "overflow"},
                                   {"id": 6, "name": "stack", "status": "overflow"},
                                   {"id": 7, "name": "stack", "status": "overflow"},
                                   {"id": 8, "name": "stack", "status": "overflow"},
                                   {"id": 9, "name": "stack", "status": "overflow"},
                                   {"id": 10, "name": "stack", "status": "overflow"},
                                   {"id": 11, "name": "stack", "status": "overflow"}]})
        self.assertEqual(exc.exception.messages['records'], ['Length must be between 1 and 10.'])
  

来源:https://marshmallow.readthedocs.io/en/stable/nesting.html和   https://marshmallow.readthedocs.io/en/stable/examples.html

答案 1 :(得分:0)

我想做的事可以使用这个小库:https://github.com/and-semakin/marshmallow-toplevel

pip install marshmallow-toplevel

用法:

from marshmallow.validate import Length
from marshmallow_toplevel import TopLevelSchema

class BatchOfRecords(TopLevelSchema):
    _toplevel = fields.Nested(
        Record,
        required=True,
        many=True,
        validate=Length(1, 10)
    )