Reputation: 288
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.
Upvotes: 3
Views: 7707
Reputation: 18388
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.
Upvotes: 3
Reputation: 2447
If you want to convert a Pydantic object/type to another Pydantic object/type.
from pydantic import BaseModel, Field
# Some hypothetical Pydantics types.
class PyDanticTypeA(BaseModel):
attribute_a: str
attribute_b: str
class PyDanticTypeB(PyDanticTypeA):
attribute_c: str
class PyDanticTypeC(PyDanticTypeA):
attribute_d: str = Field("d")
# Converting (parsing) one Pydantic type to another.
pydantic_type_b = PyDanticTypeB(attribute_a="a", attribute_b="b", attribute_c="c")
pydantic_type_c = PyDanticTypeC.parse_obj(pydantic_type_b)
# Testing the converted (parsed) Pydantic type.
print(pydantic_type_c.attribute_d)
pydantic_type_c.attribute_d = "e"
print(pydantic_type_c.attribute_d)
(my-test) [who-i-am@who-i-am-pc my-test]$ python my-test.py
d
e
Thanks!
āļøš
Upvotes: 0