Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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)
@bofm

This comment has been minimized.

Copy link
Owner Author

@bofm bofm commented Jan 13, 2021

workaround for samuelcolvin/pydantic#760

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment