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["Student"]
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["OuterClass.Student"]
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["OuterClass.Student"]
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({
"students": [
{"name": "foo", "age": 18},
{"name": "bar", "age": 19},
]
})
print(obj.json(indent=4))
Output:
{
"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
}
]
}