简体   繁体   English

Python 数据类验证:一种简单的方法?

[英]Python dataclass validation: an easy way?

I'm trying to understand how python dataclass validation can be implemented straightforwardly.我试图了解如何直接实现 python 数据类验证。 I'm using marshmallow validate to try to do this but not understanding how the validation can actually be run within the dataclass, or whether it is just glommed on as a metadata field that you have to rather awkwardly run.我正在使用 marshmallow validate来尝试执行此操作,但不了解验证实际上是如何在数据类中运行的,或者它是否只是作为元数据字段出现,您必须相当笨拙地运行。

I could do a __post_init__ (as suggested here and here ) to directly perform the validation on each field but I feel like there should be an easier, validator-agnostic way to validate all the fields according to their validate metadata, either at __init__ or otherwise.我可以做一个__post_init__ (如这里这里的建议)直接对每个字段执行验证,但我觉得应该有一种更简单的、与验证器无关的方法来根据它们的validate元数据验证所有字段,无论是在__init__还是其他地方.

Here is an example script below:下面是一个示例脚本:

from dataclasses import dataclass, field
from marshmallow import validate


def null_validate(value):
    """Validation fn for dataclass"""
    if value is None:
        pass
    else:
        raise ValidationError("{value} should be a string for this dataclass field!")


@dataclass
class Testing:
    plus_minus_one: int = field(
        default=None,
        metadata=dict(
            required=False,
            validate=validate.OneOf([1, -1])
        )
    )
    max_one: int = field(
        default=None,
        metadata=dict(
            required=False,
            validate=validate.Range(max=1)
        )
    )
    null_field: str = field(
        default=None,
        metadata=dict(
            required=False,
            validate=null_validate
        )
    )

print("this passes")
it = Testing(1, 1, None)
print("this should fail")
it = Testing(10, 10, 10)

I run this as follows but don't get any ValidationError , so I know that the validation doesn't somehow happen magically inside the dataclass:我按如下方式运行但没有得到任何ValidationError ,所以我知道验证不会以某种方式神奇地发生在数据类中:

% python testing.py
this passes
this should fail

So what I can do is add a __post_init__ method like this to the dataclass:所以我能做的是将这样的__post_init__方法添加到数据类:

def __post_init__(self):
    for data_field in self.__dataclass_fields__:
        self.__dataclass_fields__[data_field].metadata["validate"](
            self.__dict__[data_field]
        )

With this, the validation more or less works on an argument-wise basis:有了这个,验证或多或少地在参数方面起作用:

% python testing.py
this passes
this should fail
Traceback (most recent call last):
  File "testing.py", line 47, in <module>
    it = Testing(10, 10, 10)
  File "<string>", line 6, in __init__
  File "testing.py", line 41, in __post_init__
    self.__dataclass_fields__[data_field].metadata["validate"](self.__dict__[data_field])
  File "/Users/max.press/miniconda3/envs/test_env/lib/python3.7/site-packages/marshmallow/validate.py", line 569, in __call__
    raise ValidationError(self._format_error(value))
marshmallow.exceptions.ValidationError: Must be one of: 1, -1.

But this seems rather clunky, and it seems hard to implement more complex validations than this.但这看起来相当笨拙,而且似乎很难实现比这更复杂的验证。 It seems like I should be able to validate "up-front" when the argument is passed in, without changing anything.似乎我应该能够在传入参数时“预先”验证,而无需更改任何内容。

Is the solution to move to a full marshmallow-dataclass ?是转移到完整的marshmallow-dataclass的解决方案吗? Possibly treating as a Schema could handle this.可能将其视为Schema可以解决这个问题。

It turns out that you can do this quite easily by using marshmallow dataclasses and their Schema() method.事实证明,通过使用棉花糖数据类及其Schema()方法,您可以很容易地做到这一点。

The below code shows the desired behavior without the __post_init__ , though I clearly need to read up more on marshmallow:下面的代码显示了没有__post_init__的期望行为,尽管我显然需要阅读更多关于 marshmallow 的内容:

from dataclasses import dataclass, field
from marshmallow import validate, Schema
from marshmallow_dataclass import dataclass



def null_validate(value):
    """Validation fn for dataclass"""
    if value is None:
        pass
    else:
        raise ValidationError("{value} should be a string for this dataclass field!")


@dataclass
class Testing:
    plus_minus_one: int = field(
        default=None,
        metadata=dict(
            required=False,
            validate=validate.OneOf([1, -1])
        )
    )
    max_one: int = field(
        default=None,
        metadata=dict(
            required=False,
            validate=validate.Range(max=1)
        )
    )
    null_field: NoneType = field(
        default=None,
        metadata=dict(
            required=False,
            validate=null_validate
        )
    )

print("this passes")
it = Testing.Schema().load({"plus_minus_one": 1, "max_one": 1, "null_field": None})
print("this should fail")
it = Testing.Schema().load({"plus_minus_one": 10, "max_one": 10, "null_field": 10})

When it's run, I get the desired result:当它运行时,我得到了想要的结果:

this passes
this should fail
[...]
marshmallow.exceptions.ValidationError: {'null_field': ['Not a valid string.'], 'plus_minus_one': ['Must be one of: 1, -1.'], 'max_one': ['Must be less than or equal to 1.']}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM