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



我试图理解如何直接实现python数据类验证。我使用棉花糖validate来尝试做到这一点,但不理解如何验证实际上可以在数据类中运行,或者它是否只是作为一个元数据字段,您必须相当笨拙地运行。

我可以做一个__post_init__(如这里和这里所建议的)直接对每个字段执行验证,但我觉得应该有一个更简单的,验证器不确定的方式来验证所有字段根据他们的validate元数据,无论是在__init__或其他。

下面是一个示例脚本:

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)

我像下面这样运行,但是没有得到任何ValidationError,所以我知道验证不会在数据类中神奇地发生:

% python testing.py
this passes
this should fail

所以我能做的是添加一个__post_init__方法,像这样的数据类:

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

这样,验证或多或少地在参数的基础上工作:

% 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.

但是这看起来相当笨拙,而且似乎很难实现比这更复杂的验证。似乎我应该能够验证"预先"。当参数传入时,不更改任何内容。

是解决方案移动到一个完整的marshmallow-dataclass ?可能作为Schema处理这个问题。

事实证明,通过使用棉花糖数据类及其Schema()方法可以很容易地做到这一点。

下面的代码显示了没有__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})

当它运行时,我得到了期望的结果:

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.']}

相关内容

  • 没有找到相关文章

最新更新