How to make the following work with pydantic?
from typing import Type
import pydantic
class InputField(pydantic.BaseModel):
name: str
type: Type
InputField.parse_raw('{"name": "myfancyfield", "type": "str"}')
It fails with
pydantic.error_wrappers.ValidationError: 1 validation error for InputField
type
a class is expected (type=type_error.class)
But I need to parse this from json, so I don't have the option to directly pass the Type object to the __init__
method.
A custom BeforeValidator
will allow you to attempt to find a class with the provided name. Here is a working example first trying to grab a built-in and failing that assuming the class is in global namespace:
from typing import Annotated, Any
from pydantic import BaseModel, BeforeValidator
def ensure_type_instance(v: Any) -> type:
name = str(v)
try:
obj = getattr(__builtins__, name)
except AttributeError:
try:
obj = globals()[name]
except KeyError:
raise ValueError(f"{v} is not a valid name")
if not isinstance(obj, type):
raise ValueError(f"{obj} is not a class")
return obj
class InputField(BaseModel):
name: str
type_: Annotated[type, BeforeValidator(ensure_type_instance)]
class Foo:
pass
print(InputField.model_validate_json('{"name": "a", "type_": "str"}'))
print(InputField.model_validate_json('{"name": "a", "type_": "Foo"}'))
Output:
name='a' type_=<class 'str'>
name='b' type_=<class '__main__.Foo'>
A custom validator with pre=True
will allow you to attempt to find a class with the provided name. Here is a working example first trying to grab a built-in and failing that assuming the class is in global namespace:
from pydantic import BaseModel, validator
class InputField(BaseModel):
name: str
type_: type
@validator("type_", pre=True)
def parse_cls(cls, value: object) -> type:
name = str(value)
try:
obj = getattr(__builtins__, name)
except AttributeError:
try:
obj = globals()[name]
except KeyError:
raise ValueError(f"{value} is not a valid name")
if not isinstance(obj, type):
raise TypeError(f"{value} is not a class")
return obj
class Foo:
pass
if __name__ == "__main__":
print(InputField.parse_raw('{"name": "a", "type_": "str"}'))
print(InputField.parse_raw('{"name": "b", "type_": "Foo"}'))
Output:
name='a' type_=<class 'str'>
name='b' type_=<class '__main__.Foo'>
If you want to support dynamic imports as well, that is possible too. See here or here for pointers.