python pydantic typed dict validation
from functools import wraps | |
from typing import TypedDict | |
from pydantic import BaseModel, validate_arguments | |
def replace_typed_dict_annotations(annotations): | |
new_annotations = {} | |
for k, T in annotations.items(): | |
if type(T).__name__ == '_TypedDictMeta': | |
class M(TDict): | |
__annotations__ = replace_typed_dict_annotations(T.__annotations__) | |
M.__name__ = T.__name__ | |
new_annotations[k] = M | |
else: | |
new_annotations[k] = T | |
return new_annotations | |
def validate_args(fn): | |
def f(*args, **kwargs): | |
pass | |
@wraps(fn) | |
def wrapper(*args, **kwargs): | |
f(*args, **kwargs) | |
return fn(*args, **kwargs) | |
wrapper.__annotations__ = replace_typed_dict_annotations(fn.__annotations__) | |
return validate_arguments(wrapper) | |
class TDict(BaseModel): | |
class Config: | |
extra = 'forbid' | |
arbitrary_types_allowed = True | |
@classmethod | |
def __get_validators__(cls): | |
yield from super().__get_validators__() | |
yield cls.validate | |
@classmethod | |
def validate(cls, v): | |
if not isinstance(v, dict): | |
raise TypeError('dict type expected') | |
return cls(**v).dict() | |
class D(TypedDict): | |
z: int | |
class A(TypedDict): | |
x: int | |
y: str | |
d: D | |
@validate_args | |
def foo(a: A) -> int: | |
print(type(a)) | |
return a | |
print(foo({'x': 1, 'y': '2', 'd': {'z': 3}})) | |
print(foo({'x': 1, 'y': [], 'z': 22, 'd': []})) | |
# OUT: | |
# <class 'dict'> | |
# {'x': 1, 'y': '2', 'd': {'z': 3}} | |
# ... | |
# pydantic.error_wrappers.ValidationError: 3 validation errors for Foo | |
# a -> y | |
# str type expected (type=type_error.str) | |
# a -> d | |
# dict type expected (type=type_error) | |
# a -> z | |
# extra fields not permitted (type=value_error.extra) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
workaround for samuelcolvin/pydantic#760