Skip to content

Instantly share code, notes, and snippets.

@bofm
Last active January 13, 2021 21:00
Show Gist options
  • Save bofm/db2195086da290a55468b6976e630f49 to your computer and use it in GitHub Desktop.
Save bofm/db2195086da290a55468b6976e630f49 to your computer and use it in GitHub Desktop.
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
Copy link
Author

bofm commented Jan 13, 2021

workaround for pydantic/pydantic#760

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