Python: retrieving info from Pydantic models
How to extract info from a Pydantic model like the base type, if it is a list, an optional value, the default value…
Pydantic is a great way to validate and serialize data, but what about using the Pydantic model to build a user interface?
The idea was already covered in a previous article:
Now it’s time to provide some source code if you had like to do the same kind of things ;)
Retrieving info from the Pydantic model
Accessing the field informations
To define a Pydantic model you need to inherit from the pydantic.BaseModel class. This gives you access to some interesting class attributes like :
- model_fields, to have info about attributes,
- model_computed_fields, to have info about computed properties (properties whose values are given by a method).
Both those attributes will return you a dictionary, keys being name of the attributes/computed fields and values being pydantic.fields.FieldInfo objects (for model_fields) or pydantic.fields.ComputedFieldInfo (for model_computed_fields)

Illustration by the author
What we would like to extract:
For our needs, what we would like to know, is:
- the ‘base’ type (str, float, int?),
- wether we are working with a list or not (multiple values?),
- if the value is optional,
- and finally if there is a default value.
We will start with basic cases (single non-optional value) and then see how we can handle more complexe cases.
Extracting the information from FieldInfo
Easy case: single non optional value
To start, all you need to do is get the origin of the field_info annotation, it will directly give you access to the type declaration.
from typing import get_origin
field = TestModel.model_fields["string"]
origin_type = get_origin(string_field.annotation)
print(f"{field.annotation=} {field is str}")
# field.annotation=<class 'str'> True
More complex case, what about list values?
Lets see what gives our previous code works for the string_list field
from typing import get_origin
string_list_field = TestModel.model_fields["string_list"]
origin_type = get_origin(string_list_field.annotation)
print(f"{string_list_field.annotation=} {string_list_field.annotation is str}")
# string_list_field.annotation=typing.List[str] False
We can see it does not work. Let’s fix it using the typing get_args function. This time we will first detect we are handling a list, and if it is the case get the first argument of the field annotation.
from typing import get_origin, get_args
field = TestModel.model_fields["optional_int"]
origin_type = get_origin(field.annotation)
is_list = origin_type is list
if is_list:
annotation_args = get_args(field.annotation)
print(f"{field.annotation=} list: True, {annotation_args[0] is str}")
else:
print(f"{field.annotation=} list: False, {origin_type is str}")
# field.annotation=typing.Optional[int] list: False, False
It works!
Detecting optional values
We will start by checking what we got for the optional_int field.
Using Optional[int] annotation is the same as writing int | None, we will have to check if we are working with an union type whose last args is None
import types
from typing import get_origin, get_args, Union
field = TestModel.model_fields["optional_int"]
origin_type = get_origin(field.annotation)
is_list = origin_type is list
is_union = origin_type is Union
if is_union:
annotation_args = get_args(field.annotation)
is_optional = annotation_args[-1] is types.NoneType
print(f"{field.annotation=} Optional: {is_optional}, {annotation_args[0] is int}")
elif is_list:
annotation_args = get_args(field.annotation)
print(f"{field.annotation=} list: True, {annotation_args[0] is str}")
else:
print(f"{field.annotation=} list: False, {origin_type is str}")
# field.annotation=typing.Optional[int] Optional: True, True
It also works!
Getting the default value
This one is quite easy, you only have to check the field_info default attribute. Please notice that if a default wasn’t provided you won’t have None but PydanticUndefined.
from pydantic_core import PydanticUndefined
default_value = field_info.default
default_value_provided = default_value is not PydanticUndefined
For computed fields
For computed fields the work is nearly the same, except you do not start from the field_info.annotation value, but from the computed_field_info.return_type value and then follow up the same steps.
The final word
You would have to handle also detecting an optional list, this would imply first detecting you have an optional value, and then for it’s annotation argument if it is a list.
That’s all folks!