【问题标题】:Python dataclass validation: an easy way?Python 数据类验证:一种简单的方法?
【发布时间】:2022-10-04 19:56:32
【问题描述】:

我试图了解如何直接实现 python 数据类验证。我正在使用 marshmallow validate 尝试执行此操作,但不了解如何在数据类中实际运行验证,或者它是否只是作为一个元数据字段而您必须相当笨拙地运行。

我可以使用__post_init__(建议herehere)直接对每个字段执行验证,但我觉得应该有一种更简单、与验证器无关的方法来根据@987654325 验证所有字段@ 元数据,在 __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 可以处理此问题。

【问题讨论】:

    标签: python validation python-dataclasses marshmallow


    【解决方案1】:

    使用__post_init__ 是使用标准python 数据类时的正确方法。

    你可以使用这样的东西

    from dataclasses import dataclass, field
    
    
    def null_validate(value):
        """Validation fn for dataclass"""
        if value is None:
            pass
        else:
            raise ValueError(f"{value} should be a string for this dataclass field!")
    
    
    def plus_minus_one(value):
        if value in [1, -1]:
            return value
        raise ValueError()
    
    
    @dataclass
    class Testing:
        plus_minus_one: int = field(default=None, metadata=dict(required=False))
        max_one: int = field(default=None, metadata=dict(required=False))
        null_field: str = field(
            default=None, metadata=dict(required=False, validate=null_validate)
        )
    
        def __post_init__(self):
            # use separate method/function for validation
            self.plus_minus_one = plus_minus_one(self.plus_minus_one)
            # inline validation
            self.max_one = self.max_one if self.max_one >= 1 else 0
            self.null_field = null_validate(self.null_field)
    
    
    print("this passes")
    it = Testing(1, 1, None)
    print("this should fail")
    it = Testing(1, 10, 10)
    

    【讨论】:

      【解决方案2】:

      事实证明,您可以通过使用棉花糖数据类及其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.']}
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-08-27
        • 2021-06-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多