英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论