将Pydantic基础模型属性转换为列表。

huangapple go评论59阅读模式
英文:

casting a pydantic base model attribute to a list

问题

我有一个继承自基本模型的类,如下所定义:

class MessageInfo(CustomBaseModel):
    completion_code: int = None
    code: str = None
    message: str = None
    severity: str = None
class OrderResponseMessageInfo(CustomBaseModel):
    message_info: Union[List[MessageInfo], MessageInfo] = None

希望message_info能够接受列表或字典,但在将其序列化为字典或JSON时始终将其生成为列表。

由于这是API响应的解析器,而API响应不一致,有时消息信息块是数组,如下所示:

{
    "MessageInfo": [
        {
            "CompletionCode": 517,
            "Code": "1B61",
            "Message": "some message",
            "Severity": "Warning"
        },
        {
            "CompletionCode": 9886,
            "Code": "1B61",
            "Message": "Another message",
            "Severity": "Warning"
        }
    ]
}

而有时它是一个字典,如下所示:

{
    "MessageInfo": {
        "CompletionCode": 517,
        "Code": "1B61",
        "Message": "some message",
        "Severity": "Warning"
    }
}

当消息信息是字典时,我希望能够将其类型转换为列表,这样当我执行OrderResponseMessageInfo.dict()时,我应该获得以下输出:

{
    "OrderLineMessageInfo": {
        "MessageInfo": [
            {
                "CompletionCode": 517,
                "Code": "1B61",
                "Message": "Routing completed. Confirmation required.",
                "Severity": "Warning"
            }
        ]
    }
}

是否有实现这一目标的方法?

英文:

I have a class that inherits from base model, and is defined as below:

class MessageInfo(CustomBaseModel):
    completion_code: int = None
    code: str = None
    message: str = None
    severity: str = None

class OrderResponseMessageInfo(CustomBaseModel):
    message_info: Union[List[MessageInfo], MessageInfo] = None

Want this message_info to be able to consume either a list or a dict, but consistently produce it as a list when i serialise it to a dict or a json.

Since this is a parser for an API response, and the API response is not consistent, i.e sometimes the message info block is an array like so :

{
    "MessageInfo": [
        {
            "CompletionCode": 517,
            "Code": "1B61",
            "Message": "some message",
            "Severity": "Warning"
        },
        {
            "CompletionCode": 9886,
            "Code": "1B61",
            "Message": "Another message",
            "Severity": "Warning"
        }
    ]
}

And sometimes its a dict like so

{
    "MessageInfo": {
        "CompletionCode": 517,
        "Code": "1B61",
        "Message": "some message",
        "Severity": "Warning"
    }
}

When message info is a dict, and I do OrderResponseMessageInfo.dict(), i get

{
    "OrderLineMessageInfo": {
        "MessageInfo": {
                "CompletionCode": 517,
                "Code": "1B61",
                "Message": "Routing completed. Confirmation required.",
                "Severity": "Warning"
        }
    }
}

What i want is that if message info is a dict, then i should be able to type cast it to a list, so that when I do OrderResponseMessageInfo.dict(), i should get the output as:

{
    "OrderLineMessageInfo": {
        "MessageInfo": [
            {
                "CompletionCode": 517,
                "Code": "1B61",
                "Message": "Routing completed. Confirmation required.",
                "Severity": "Warning"
            }
        ]
    }
}

Is there a way to achieve this?

答案1

得分: 1

以下是翻译好的内容:

"作为一般规则,您应该根据您实际想要的模式来定义您的模型,而不是根据您可能会得到的内容。

如果您希望某个字段是列表类型,那么请将其定义为列表。如果您希望在解析/初始化过程中允许将单个元素转换为一个元素的列表作为特殊情况,您可以定义一个自定义 pre=True 字段验证器 来执行此操作。

无论哪种方式,在解析和验证过程的最后,您应该得到您期望的模式,而不是需要在某个阶段再次进行处理的内容。

示例:

from collections.abc import Mapping

from pydantic import BaseModel, validator

class Foo(BaseModel):
    x: int
    y: str

class Bar(BaseModel):
    foos: list[Foo]

    @validator("foos", pre=True)
    def single_to_list(cls, v: object) -> object:
        if isinstance(v, (Mapping, Foo)):
            return [Foo.parse_obj(v)]
        return v

演示:

b1 = Bar.parse_raw('{"foos": [{"x": 1, "y": "spam"}, {"x": -1, "y": "eggs"}]}')
b2 = Bar.parse_raw('{"foos": {"x": 42, "y": "ham"}}')
f = Foo(x=69, y="beans")
b3 = Bar.parse_obj({"foos": f})
b4 = Bar.parse_obj({"foos": [f, f]})
print(b1.json(indent=4))
print(b2.json(indent=4))
print(b3.json(indent=4))
print(b4.json(indent=4))

输出:

{
    "foos": [
        {
            "x": 1,
            "y": "spam"
        },
        {
            "x": -1,
            "y": "eggs"
        }
    ]
}
{
    "foos": [
        {
            "x": 42,
            "y": "ham"
        }
    ]
}
{
    "foos": [
        {
            "x": 69,
            "y": "beans"
        }
    ]
}
{
    "foos": [
        {
            "x": 69,
            "y": "beans"
        },
        {
            "x": 69,
            "y": "beans"
        }
    ]
}

如果您希望message_info是一个列表,请将其定义为list[MessageInfo],并编写一个自定义验证器来尝试将任何映射或MessageInfo实例强制转换为包含一个MessageInfo实例的列表。

当然,您也可以覆盖转储/序列化方法(如.dict.json),但我不建议这样做。1)这将导致更多的代码,2)效率会更低,3)模型模式仍然会具有误导性,因为该字段将始终被转储为列表,但模式将暗示可能出现单个实例。"

英文:

As a general rule, you should define your models in terms of the schema you actually want, not in terms of what you might get.

If you want a field to be of a list type, then define it as such. If you then want to allow singular elements to be turned into one-item-lists as a special case during parsing/initialization, you can define a custom pre=True field validator to do that.

Either way, at the end of the parsing and validation process you should end up with your desired schema, not something you need to mangle again at some stage.

Example:

from collections.abc import Mapping

from pydantic import BaseModel, validator


class Foo(BaseModel):
    x: int
    y: str


class Bar(BaseModel):
    foos: list[Foo]

    @validator("foos", pre=True)
    def single_to_list(cls, v: object) -> object:
        if isinstance(v, (Mapping, Foo)):
            return [Foo.parse_obj(v)]
        return v

Demo:

b1 = Bar.parse_raw('{"foos": [{"x": 1, "y": "spam"}, {"x": -1, "y": "eggs"}]}')
b2 = Bar.parse_raw('{"foos": {"x": 42, "y": "ham"}}')
f = Foo(x=69, y="beans")
b3 = Bar.parse_obj({"foos": f})
b4 = Bar.parse_obj({"foos": [f, f]})
print(b1.json(indent=4))
print(b2.json(indent=4))
print(b3.json(indent=4))
print(b4.json(indent=4))

Output:

{
    "foos": [
        {
            "x": 1,
            "y": "spam"
        },
        {
            "x": -1,
            "y": "eggs"
        }
    ]
}
{
    "foos": [
        {
            "x": 42,
            "y": "ham"
        }
    ]
}
{
    "foos": [
        {
            "x": 69,
            "y": "beans"
        }
    ]
}
{
    "foos": [
        {
            "x": 69,
            "y": "beans"
        },
        {
            "x": 69,
            "y": "beans"
        }
    ]
}

If you want message_info to be a list, then define it as list[MessageInfo] and write a custom validator to try and coerce any mapping or MessageInfo instance to a list containing one MessageInfo instance.

Of course you could instead override the dumping/serialization methods (like .dict and .json), but I would not recommend it. 1) That would result in more code, 2) it would be less efficient, 3) the model schema would still be misleading because you would always dump that field as a list, but the schema would imply a single instance might appear.

huangapple
  • 本文由 发表于 2023年4月11日 03:06:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/75979955.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定