Python Dataclass验证:一种简单的方法?

发布于 2025-01-21 15:12:07 字数 2752 浏览 1 评论 0原文

我试图了解如何直接实现Python Dataclass验证。我正在使用棉花糖validate尝试执行此操作,但不了解验证方式实际上可以在数据级别内运行,或者是否只是作为元数据字段而glomp在dataclast中,您必须尴尬地运行。

我可以做一个__ post_init __(如建议在这里在这里)直接执行验证在每个字段上,但我觉得应该有一种更容易的,验证的方法,可以根据其validate元数据来验证所有字段,要么是__ Init __ Init __ 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

所以我可以做的是添加A __ 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.

但这似乎相当笨拙,似乎很难实现比这更复杂的验证。似乎我应该能够在参数传递时可以验证“前期”,而无需更改任何内容。

是否可以移动到完整的棉花糖dataclass?可能将其视为架构可以处理这一点。

I'm trying to understand how python dataclass validation can be implemented straightforwardly. 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.

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.

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:

% python testing.py
this passes
this should fail

So what I can do is add a __post_init__ method like this to the dataclass:

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? Possibly treating as a Schema could handle this.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

不即不离 2025-01-28 15:12:07

事实证明,您可以使用棉花糖dataclasses及其schema()方法很容易地执行此操作。

以下代码显示了没有__ POST_INIT __的所需行为,尽管我显然需要在棉花糖上进行更多阅读:

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

It turns out that you can do this quite easily by using marshmallow dataclasses and their Schema() method.

The below code shows the desired behavior without the __post_init__, though I clearly need to read up more on 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.']}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文