Parse_obj在pydantic中带有异质元组的田地?

发布于 2025-02-07 03:53:08 字数 789 浏览 2 评论 0原文

在pydantic中,

class Foo(BaseModel):
      bar : str
      baz : int

可以从元组中导入[“ AAA”,3]通过做类似的操作:

[**{key: tr[i] for i, key in enumerate(fields.__TraceItem__.keys())})]

也可以从{bar:“ AAA”,BAZ:3} <{bar:bar:3} < /代码>使用parse_obj。但是,如何导入将两者结合在一起的东西呢?换句话说,给定类别

class Bar(BaseModel):
      f1: str
      f2: float
      f3: Boolean

class Qux(BaseModel):
      field1: str
      field2: float
      field3: list[Bar]

如何将以下JSON转换为上面的qux对象?

{
    field1: "bar",
    field2: 3.14,
    field3: [
        ["aaa", 2.71, True],
        ["bbb", -1, False]
    ]
}

In Pydantic the class

class Foo(BaseModel):
      bar : str
      baz : int

Can be imported from a tuple ["aaa", 3] by doing something like:

[**{key: tr[i] for i, key in enumerate(fields.__TraceItem__.keys())})]

It can also be converted from {bar: "aaa", baz: 3} using parse_obj. But how do you import something that combines the two? In other words, given the classes

class Bar(BaseModel):
      f1: str
      f2: float
      f3: Boolean

class Qux(BaseModel):
      field1: str
      field2: float
      field3: list[Bar]

How do I convert the following JSON into the Qux object above?

{
    field1: "bar",
    field2: 3.14,
    field3: [
        ["aaa", 2.71, True],
        ["bbb", -1, False]
    ]
}

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

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

发布评论

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

评论(2

请恋爱 2025-02-14 03:53:08

您可以使用 验证器

,“ 总会在Pydantic接受数据之前可能需要转换,这对于与模型不完全匹配的JSON数据尤其如此。

[["aaa", 2.71, True], ["bbb", -1, False]]

是原始值的列表,它绝对与

[
  {'f1': 'aaa', 'f2': 2.71, 'f3': True}, 
  {'f1': 'bbb', 'f2': -1.0, 'f3': False}
]

与实际bar对象列表相似的类型绝对不一样。

对于这样的情况,您可能需要自己实施解析。

选项1

bar具有验证器,将列表列表解析为bar的列表。

class Bar(BaseModel):
    f1: str
    f2: float
    f3: bool

class Qux(BaseModel):
    field1: str
    field2: float
    field3: list[Bar]

    @validator("field3", pre=True)
    def parse_field3_as_bar(cls, value):
        # If value is already a list[Bar], then return as-is
        if isinstance(value, list) and isinstance(value[0], Bar):
            return value
        # If not, try coercing into a list[Bar]
        # Expect value to be ex. [["aaa", 2.71, True], ["bbb", -1, False]]
        try:
            bar_fields = Bar.__fields__.keys()
            return [
                Bar(**dict(zip(bar_fields, triplet))) 
                for triplet in value
            ]
        except Exception:
            raise ValueError(f"Cannot convert to list[Bar]: {value!r}")
>>> from main import Bar, Qux
>>> d = {"field1": "bar", "field2": 3.14, "field3": [["aaa", 2.71, True], ['bbb', -1, False]]}
>>> q = Qux(**d)
>>> q
Qux(field1='bar', field2=3.14, field3=[Bar(f1='aaa', f2=2.71, f3=True), Bar(f1='bbb', f2=-1.0, f3=False)])

如果传递的value是错误的数据格式:

>>> dx = {"field1": "bar", "field2": 3.14, "field3": [["not", "a"], ["list", "of", "Bar"], "values"]}
>>> q = Qux(**dx)
  ...
  File "pydantic/main.py", line 341, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for Qux
field3
  Cannot convert to list[Bar]: [['not', 'a'], ['list', 'of', 'Bar'], 'values'] (type=value_error)

您在标题中提到了parse_obj,我假设您的意思是

>>> d = {"field1": "bar", "field2": 3.14, "field3": [["aaa", 2.71, True], ['bbb', -1, False]]}
>>> Qux.parse_obj(d)
Qux(field1='bar', field2=3.14, field3=[Bar(f1='aaa', f2=2.71, f3=True), Bar(f1='bbb', f2=-1.0, f3=False)])

选项2

bar具有验证器 option 1 相同,但使用 enter_item = eyter_item = eyter_item = true ,以便您可以一次解析一个列表项目(一个三重态一次)bar对象:

class Bar(BaseModel):
    f1: str
    f2: float
    f3: bool

class Qux(BaseModel):
    field1: str
    field2: float
    field3: list[Bar]

    @validator("field3", pre=True, each_item=True)
    def parse_field3_as_bar(cls, value):
        # If value is already a Bar, then return as-is
        if isinstance(value, Bar):
            return value
        # Try coercing value into a Bar
        # Expect value to be ex. ['aaa', 2.71, True]
        try:
            bar_fields = Bar.__fields__.keys()
            return Bar(**dict(zip(bar_fields, value)))
        except Exception:
            raise ValueError("Cannot convert to Bar:", value)
>>> from main import Bar, Qux
>>> d = {"field1": "bar", "field2": 3.14, "field3": [["aaa", 2.71, True], ['bbb', -1, False]]}
>>> q = Qux(**d)
>>> q
Qux(field1='bar', field2=3.14, field3=[Bar(f1='aaa', f2=2.71, f3=True), Bar(f1='bbb', f2=-1.0, f3=False)])

如果传递value是错误的数据格式:

>>> from main import Bar, Qux
>>> dx = {"field1": "bar", "field2": 3.14, "field3": [["aaa", 2.71, True], 9999]}
>>> q = Qux(**dx)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pydantic/main.py", line 341, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for Qux
field3 -> 1
  Cannot convert to Bar: 9999 (type=value_error)

您在标题中提到parse_obj,我认为您的意思是 parse_obj 助手功能。
它也与此相同:

>>> d = {"field1": "bar", "field2": 3.14, "field3": [["aaa", 2.71, True], ['bbb', -1, False]]}
>>> Qux.parse_obj(d)
Qux(field1='bar', field2=3.14, field3=[Bar(f1='aaa', f2=2.71, f3=True), Bar(f1='bbb', f2=-1.0, f3=False)])

作为附带说明,我已经在评论中提到了它,您提到“ tuple

可以从元组中导入[“ AAA”,3]

但这不是 ,我也不会在您的任何示例代码和输入/输出中看到任何元组数据。我不确定您是否知道,但是在python中,元组是指特定数据类型: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences

您所拥有的基本上是列表的列表,每个子列表只是其他数据类型的列表。 (这就是为什么在我的回答中,我将其称为triplet)。

You can use a validator.

As pointed out in a comment, "there will be always cases where a transformation might be necessary before pydantic can accept the data", which is especially true for your JSON data that does not exactly match the model.

This

[["aaa", 2.71, True], ["bbb", -1, False]]

is a list of primitive values and it is definitely not the same type as

[
  {'f1': 'aaa', 'f2': 2.71, 'f3': True}, 
  {'f1': 'bbb', 'f2': -1.0, 'f3': False}
]

which more closely resembles a list of actual Bar objects.

For cases like that, you might need to implement the parsing yourself.

Option 1

Let Bar have a validator that parses a list of lists into a list of Bar's.

class Bar(BaseModel):
    f1: str
    f2: float
    f3: bool

class Qux(BaseModel):
    field1: str
    field2: float
    field3: list[Bar]

    @validator("field3", pre=True)
    def parse_field3_as_bar(cls, value):
        # If value is already a list[Bar], then return as-is
        if isinstance(value, list) and isinstance(value[0], Bar):
            return value
        # If not, try coercing into a list[Bar]
        # Expect value to be ex. [["aaa", 2.71, True], ["bbb", -1, False]]
        try:
            bar_fields = Bar.__fields__.keys()
            return [
                Bar(**dict(zip(bar_fields, triplet))) 
                for triplet in value
            ]
        except Exception:
            raise ValueError(f"Cannot convert to list[Bar]: {value!r}")
>>> from main import Bar, Qux
>>> d = {"field1": "bar", "field2": 3.14, "field3": [["aaa", 2.71, True], ['bbb', -1, False]]}
>>> q = Qux(**d)
>>> q
Qux(field1='bar', field2=3.14, field3=[Bar(f1='aaa', f2=2.71, f3=True), Bar(f1='bbb', f2=-1.0, f3=False)])

If the passed value is of the wrong data format:

>>> dx = {"field1": "bar", "field2": 3.14, "field3": [["not", "a"], ["list", "of", "Bar"], "values"]}
>>> q = Qux(**dx)
  ...
  File "pydantic/main.py", line 341, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for Qux
field3
  Cannot convert to list[Bar]: [['not', 'a'], ['list', 'of', 'Bar'], 'values'] (type=value_error)

You mentioned parse_obj in your title, and I assume you mean the parse_obj helper function.
It works just as well with that:

>>> d = {"field1": "bar", "field2": 3.14, "field3": [["aaa", 2.71, True], ['bbb', -1, False]]}
>>> Qux.parse_obj(d)
Qux(field1='bar', field2=3.14, field3=[Bar(f1='aaa', f2=2.71, f3=True), Bar(f1='bbb', f2=-1.0, f3=False)])

Option 2

Let Bar have a validator same as Option 1, but with each_item=True so that you can parse one list item at a time (one triplet at a time) into a Bar object:

class Bar(BaseModel):
    f1: str
    f2: float
    f3: bool

class Qux(BaseModel):
    field1: str
    field2: float
    field3: list[Bar]

    @validator("field3", pre=True, each_item=True)
    def parse_field3_as_bar(cls, value):
        # If value is already a Bar, then return as-is
        if isinstance(value, Bar):
            return value
        # Try coercing value into a Bar
        # Expect value to be ex. ['aaa', 2.71, True]
        try:
            bar_fields = Bar.__fields__.keys()
            return Bar(**dict(zip(bar_fields, value)))
        except Exception:
            raise ValueError("Cannot convert to Bar:", value)
>>> from main import Bar, Qux
>>> d = {"field1": "bar", "field2": 3.14, "field3": [["aaa", 2.71, True], ['bbb', -1, False]]}
>>> q = Qux(**d)
>>> q
Qux(field1='bar', field2=3.14, field3=[Bar(f1='aaa', f2=2.71, f3=True), Bar(f1='bbb', f2=-1.0, f3=False)])

If the passed value is of the wrong data format:

>>> from main import Bar, Qux
>>> dx = {"field1": "bar", "field2": 3.14, "field3": [["aaa", 2.71, True], 9999]}
>>> q = Qux(**dx)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pydantic/main.py", line 341, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for Qux
field3 -> 1
  Cannot convert to Bar: 9999 (type=value_error)

You mentioned parse_obj in your title, and I assume you mean the parse_obj helper function.
It works just as well with that:

>>> d = {"field1": "bar", "field2": 3.14, "field3": [["aaa", 2.71, True], ['bbb', -1, False]]}
>>> Qux.parse_obj(d)
Qux(field1='bar', field2=3.14, field3=[Bar(f1='aaa', f2=2.71, f3=True), Bar(f1='bbb', f2=-1.0, f3=False)])

As a side note, I mentioned it already in a comment, you mention "tuple" in your question

can be imported from a tuple ["aaa", 3]

But that is not a tuple, nor do I see any tuples in any of your example code and input/output data. I'm not sure if you are aware, but in Python, a Tuple refers to a specific data type: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences.

What you have is basically a list of lists, and each sub-list is just list of other data types. (Which is why in my answer I refer to it as a triplet).

风向决定发型 2025-02-14 03:53:08

我会采用一个更简单的方法,基于 gino的答案 - bar bar添加类方法要允许从数组实例化,然后从qux>从验证器调用该方法:

from typing import List, Any
from pydantic import BaseModel, validator


class Bar(BaseModel):
    f1: str
    f2: float
    f3: bool

    @classmethod
    def from_array(cls, values: List[Any]):
        """
        Returns instance of class from an array of values.

        NOTE: This method assumes all fields are present

        ALSO: A hackier implementation of this method:
            return cls(
                f1=values[0],
                f2=values[1],
                f3=values[2]
            )
        """
        model_data = {
            key: values[i]
            for i, key in enumerate(cls.__fields__.keys())
        }

        return cls.parse_obj(model_data)


class Qux(BaseModel):
    field1: str
    field2: float
    field3: List[Bar]

    @validator('field3', each_item=True, pre=True)
    def field3_from_array(cls, v: List[Any]):
        """
        Validator for field3 which can be represented as
        a list containing:
            - `Bar` objects
            - arrays (of Bar values)
            - dicts (of Bar objects)
        """
        if isinstance(v, Bar):
            return v
        elif isinstance(v, list):
            return Bar.from_array(v)
        elif isinstance(v, dict):
            return Bar.parse_obj(v)

这是在行动中看起来:

>>> data = {
...     "field1": "bar",
...     "field2": 3.14,
...     "field3": [
...         ["aaa", 2.71, True],
...         ["bbb", -1, False],
...         {"f1": "ccc", "f2": 3.0, "f3": True}
...     ]
... }

>>> Qux.parse_obj(data)
Qux(field1='bar',field2=3.14, field3=[Bar(f1='aaa', f2=2.71, f3=True), Bar(f1='bbb', f2=-1.0, f3=False), Bar(f1='ccc', f2=3.0, f3=True)])

I would take a simpler approach, based off of Gino's answer – add a class method on Bar to allow instantiating from an array, then call that method from a validator from Qux:

from typing import List, Any
from pydantic import BaseModel, validator


class Bar(BaseModel):
    f1: str
    f2: float
    f3: bool

    @classmethod
    def from_array(cls, values: List[Any]):
        """
        Returns instance of class from an array of values.

        NOTE: This method assumes all fields are present

        ALSO: A hackier implementation of this method:
            return cls(
                f1=values[0],
                f2=values[1],
                f3=values[2]
            )
        """
        model_data = {
            key: values[i]
            for i, key in enumerate(cls.__fields__.keys())
        }

        return cls.parse_obj(model_data)


class Qux(BaseModel):
    field1: str
    field2: float
    field3: List[Bar]

    @validator('field3', each_item=True, pre=True)
    def field3_from_array(cls, v: List[Any]):
        """
        Validator for field3 which can be represented as
        a list containing:
            - `Bar` objects
            - arrays (of Bar values)
            - dicts (of Bar objects)
        """
        if isinstance(v, Bar):
            return v
        elif isinstance(v, list):
            return Bar.from_array(v)
        elif isinstance(v, dict):
            return Bar.parse_obj(v)

Here's what it looks in action:

>>> data = {
...     "field1": "bar",
...     "field2": 3.14,
...     "field3": [
...         ["aaa", 2.71, True],
...         ["bbb", -1, False],
...         {"f1": "ccc", "f2": 3.0, "f3": True}
...     ]
... }

>>> Qux.parse_obj(data)
Qux(field1='bar',field2=3.14, field3=[Bar(f1='aaa', f2=2.71, f3=True), Bar(f1='bbb', f2=-1.0, f3=False), Bar(f1='ccc', f2=3.0, f3=True)])
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文