如何在类下定义一个嵌套的Pydantic模型

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

How to define a Pydantic model nested under a class

问题

以下是您提供的代码的翻译部分:

我有两个Pydantic模型

from typing import List, Union
from pydantic import BaseModel

class Students:
    class Student(BaseModel):
        StudentName: str
        StudentAge: int

    class StudentRequest(BaseModel):
        Class: int
        UUID: str
        Students: Union[List[Student], None]

对于上面的代码中的Students: Union[List[Student], None]部分,我收到错误消息Unresolved reference 'Student'。我们不能在一个类下定义模型并在其中使用吗?

下面的代码可以工作,但我想了解上面的BaseModel嵌套在一个类下是否能工作:

class Student(BaseModel):
    StudentName: str
    StudentAge: int

class StudentRequest(BaseModel):
    Class: int
    UUID: str
    Students: Union[List[Student], None]

希望这对您有所帮助。

<details>
<summary>英文:</summary>

I have two Pydantic models:

```python
from typing import List, Union
from pydantic import BaseModel

class Students:
    class Student(BaseModel):
        StudentName: str
        StudentAge: int

    class StudentRequest(BaseModel):
        Class: int
        UUID: str
        Students: Union[List[Student], None]

For the above class at Students: Union[List[Student], None], I get the error Unresolved reference &#39;Student&#39;. Can we not define a model under a class and use it for segregating them?

The code below works, but I want to get an understanding whether the above BaseModel nested under a class will work or not:

class Student(BaseModel):
    StudentName: str
    StudentAge: int

class StudentRequest(BaseModel):
    Class: int
    UUID: str
    Students: Union[List[Student], None]

答案1

得分: 4

你需要理解,只要外部类没有完全构建(当您仍然在其命名空间内设置东西时),您将不可避免地需要处理前向引用

所以在执行此操作时,有两个强制性的事情(和一个可选的事情)你需要记住。

1) 使用限定的类名 OuterClass.InnerClass

Python 解释器本身对在注释中引用另一个内部类的前向引用不会有任何问题。这仅仅是因为它默认情况下实际上不会对这些注释执行任何操作。所以你可以像这样做:

from pydantic import BaseModel

class OuterClass:
    class Student(BaseModel):
        name: str
        age: int

    class StudentRequest(BaseModel):
        ...
        students: list["Student"]

但是这在 Pydantic 模型中会失败,因为它们实际上使用这些注释来构建基于它们的对象。正如您将在下一部分中看到的,Pydantic 在某个时候将不得不实际解析对 Student 的引用,以在运行时获取实际的基础类。而且由于这将不可避免地发生在OuterClass的作用域之外,如果没有限定名称,它将遇到NameError

因此,您必须像这样执行:

...

class OuterClass:
    class Student(BaseModel):
        ...

    class StudentRequest(BaseModel):
        ...
        students: list["OuterClass.Student"]

2) 在外部类构建完成后更新前向引用

如上所示的注释在内部存储为ForwardRef对象。

如上所述,Pydantic 最终将不得不解析这些前向引用,以便您能够实际使用这些模型。

但是它不总能够自动解析。引用文档中的一段话:

> 在某些情况下,模型创建过程中无法解析ForwardRef。[...] 当发生这种情况时,您需要在模型创建后调用update_forward_refs,然后才能使用它。

但是在像您的设置中,当模型嵌套在外部类的命名空间中时,您不能只是在模型创建后这样做。您必须在外部类创建后才能执行。

因此,在这种设置下,您必须执行以下操作:

from pydantic import BaseModel

class OuterClass:
    class Student(BaseModel):
        name: str
        age: int

    class StudentRequest(BaseModel):
        ...
        students: list["OuterClass.Student"]

OuterClass.StudentRequest.update_forward_refs()

请注意,这个调用是在创建OuterClass之后外部发生的。

3) 启用注释的延迟评估(可选)

自从PEP 563以来,您可以在模块顶部执行from __future__ import annotations,然后省略前向引用中的引号。这只是提高了可读性,使编码变得更加简单。

因此,总体而言,您的代码应该像这样:

from __future__ import annotations
from pydantic import BaseModel

class OuterClass:
    class Student(BaseModel):
        name: str
        age: int

    class StudentRequest(BaseModel):
        ...
        students: list[OuterClass.Student]

OuterClass.StudentRequest.update_forward_refs()

演示

print(OuterClass.StudentRequest.schema_json(indent=4))
obj = OuterClass.StudentRequest.parse_obj({
    "students": [
        {"name": "foo", "age": 18},
        {"name": "bar", "age": 19},
    ]
})
print(obj.json(indent=4))

输出:

{
    "title": "StudentRequest",
    "type": "object",
    "properties": {
        "students": {
            "title": "Students",
            "type": "array",
            "items": {
                "$ref": "#/definitions/Student"
            }
        }
    },
    "required": [
        "students"
    ],
    "definitions": {
        "Student": {
            "title": "Student",
            "type": "object",
            "properties": {
                "name": {
                    "title": "Name",
                    "type": "string"
                },
                "age": {
                    "title": "Age",
                    "type": "integer"
                }
            },
            "required": [
                "name",
                "age"
            ]
        }
    }
}
{
    "students": [
        {
            "name": "foo",
            "age": 18
        },
        {
            "name": "bar",
            "age": 19
        }
    ]
}
英文:

You need to understand that as long as the outer class is not fully constructed (when you are still setting up things inside its namespace), you will inevitably have to deal with forward references.

So there are two mandatory things (and one optional) you need to remember, when doing this.

1) Use the qualified class name OuterClass.InnerClass

The Python interpreter itself will have no trouble with a forward reference to another inner class in an annotation. That is simply because it does not actually do anything with those annotations by default. So you could just do this:

from pydantic import BaseModel


class OuterClass:
    class Student(BaseModel):
        name: str
        age: int

    class StudentRequest(BaseModel):
        ...
        students: list[&quot;Student&quot;]

But this will fall apart with Pydantic models because those actually use those annotations to construct objects based off of them. As you will see in the next section, at some point Pydantic will have to actually resolve the refernce to Student so get the actual underlying class at runtime. And since that will inevitably happen outside the scope of the OuterClass, without the qualified name, it will run into a NameError.

So you have to do it like this:

...

class OuterClass:
    class Student(BaseModel):
        ...

    class StudentRequest(BaseModel):
        ...
        students: list[&quot;OuterClass.Student&quot;]

2) Update forward references after the outer class is constructed

An annotation as shown above is internally stored as a ForwardRef object.

As mentioned above, Pydantic will have to resolve those forward references eventually, for you to be able to actually use those models.

However it is not always able to do so automatically. To quote the documentation:

> In some cases, a ForwardRef won't be able to be resolved during model creation. [...] When this happens, you'll need to call update_forward_refs after the model has been created before it can be used.

But with a setup like yours, when the model is nested in the namespace of an outer class, you cannot just do so after the model is created. You must do that after the outer class is created.

So with that setup, you will have to do this:

from pydantic import BaseModel


class OuterClass:
    class Student(BaseModel):
        name: str
        age: int

    class StudentRequest(BaseModel):
        ...
        students: list[&quot;OuterClass.Student&quot;]


OuterClass.StudentRequest.update_forward_refs()

Notice that the call happens outside of OuterClass after it is created.

3) Enable postponed evaluation of annotations (optional)

Since PEP 563 you can do from __future__ import annotations at the top of your module and then omit the quotes from your forward references. This just improves readability and makes things generally easier to code.

So in total, your code should look like this:

from __future__ import annotations
from pydantic import BaseModel


class OuterClass:
    class Student(BaseModel):
        name: str
        age: int

    class StudentRequest(BaseModel):
        ...
        students: list[OuterClass.Student]


OuterClass.StudentRequest.update_forward_refs()

Demo:

print(OuterClass.StudentRequest.schema_json(indent=4))
obj = OuterClass.StudentRequest.parse_obj({
    &quot;students&quot;: [
        {&quot;name&quot;: &quot;foo&quot;, &quot;age&quot;: 18},
        {&quot;name&quot;: &quot;bar&quot;, &quot;age&quot;: 19},
    ]
})
print(obj.json(indent=4))

Output:

{
    &quot;title&quot;: &quot;StudentRequest&quot;,
    &quot;type&quot;: &quot;object&quot;,
    &quot;properties&quot;: {
        &quot;students&quot;: {
            &quot;title&quot;: &quot;Students&quot;,
            &quot;type&quot;: &quot;array&quot;,
            &quot;items&quot;: {
                &quot;$ref&quot;: &quot;#/definitions/Student&quot;
            }
        }
    },
    &quot;required&quot;: [
        &quot;students&quot;
    ],
    &quot;definitions&quot;: {
        &quot;Student&quot;: {
            &quot;title&quot;: &quot;Student&quot;,
            &quot;type&quot;: &quot;object&quot;,
            &quot;properties&quot;: {
                &quot;name&quot;: {
                    &quot;title&quot;: &quot;Name&quot;,
                    &quot;type&quot;: &quot;string&quot;
                },
                &quot;age&quot;: {
                    &quot;title&quot;: &quot;Age&quot;,
                    &quot;type&quot;: &quot;integer&quot;
                }
            },
            &quot;required&quot;: [
                &quot;name&quot;,
                &quot;age&quot;
            ]
        }
    }
}
{
    &quot;students&quot;: [
        {
            &quot;name&quot;: &quot;foo&quot;,
            &quot;age&quot;: 18
        },
        {
            &quot;name&quot;: &quot;bar&quot;,
            &quot;age&quot;: 19
        }
    ]
}

huangapple
  • 本文由 发表于 2023年5月25日 17:26:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/76330754.html
匿名

发表评论

匿名网友

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

确定