-
BackgroundA few days ago, Samuel posted a code example along these lines in my example Pydantic v2 code base. I've simplified it to a reproducible example below: # pip install pydantic
from pydantic import TypeAdapter, field_validator
from typing_extensions import NotRequired, TypedDict
class Item(TypedDict):
id: int
price: float
description: NotRequired[str]
@field_validator("price", mode="before")
def check_non_zero(cls, v):
if v <= 0:
raise ValueError("price cannot be zero")
else:
return v
if __name__ == "__main__":
ItemsTypeAdapter: TypeAdapter[list[Item]] = TypeAdapter(list[Item])
sample_data = [
{"id": 1, "price": 10.0, "description": "A nice item"},
{"id": 2, "price": 20.0, "description": "Another nice item"},
{"id": 3, "price": 0.0, "description": "A bad item"},
]
try:
items = ItemsTypeAdapter.validate_python(sample_data)
from pprint import pprint
pprint(items, sort_dicts=False)
except ValueError as e:
print(e) This produces the expected output (there will be a validation error as per the field validator constraints)
The rationale in not using
Confusion with type checkersWhen using VS Code, the Pylance language server (which it uses for linting) complains that this is incorrect usage.
However, the code runs and there's clearly no syntax error. I've filed an issue in the Pylance repo regarding this, and the folks there are of the opinion they're not sure if it's "correct" to subclass TypedDict and add custom methods to it, and that subclassing BaseModel is the right way to do it when field validators are involved. I realize it's still early days and that the Pydantic v2 docs are still catching up, but is there any documentation on how the inheritance from TypedDict works exactly? I'd love some tips in understanding how/why Pydantic is able to handle the specification of custom methods under TypedDict objects even though the object was originally intended to just house struct-like fields?
|
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 3 replies
-
I would suggest creating a new instance of |
Beta Was this translation helpful? Give feedback.
-
The short answer is that what you are using works at runtime but type checkers don't like it. Because it wasn't much extra work on our end / at runtime we thought it better to make it work even if users have to add a I'll point out that there is an alternative for that particular example that:
import math
from typing import Annotated, TypeVar, Union
from annotated_types import Gt
from typing_extensions import NotRequired, TypedDict
from pydantic import TypeAdapter
from pydantic.functional_validators import AfterValidator
Number = TypeVar('Number', bound=Union[float, int])
def check_finite(v: Number) -> Number:
if not math.isfinite(v):
raise ValueError('Expected a finite number')
else:
return v
Positive = Annotated[Number, Gt(0)]
Finite = Annotated[Number, AfterValidator(check_finite)]
class Item(TypedDict):
id: int
price: Positive[Finite[float]]
description: NotRequired[str]
if __name__ == '__main__':
ItemsTypeAdapter: TypeAdapter[list[Item]] = TypeAdapter(list[Item])
sample_data = [
{'id': 1, 'price': 10.0, 'description': 'A nice item'},
{'id': 2, 'price': 20.0, 'description': 'Another nice item'},
{'id': 3, 'price': 0.0, 'description': 'A bad item'},
{'id': 3, 'price': math.nan, 'description': 'Another bad one'},
{'id': 3, 'price': math.inf, 'description': 'A big bad one'},
]
try:
items = ItemsTypeAdapter.validate_python(sample_data)
from pprint import pprint
pprint(items, sort_dicts=False)
except ValueError as e:
print(e)
|
Beta Was this translation helpful? Give feedback.
The short answer is that what you are using works at runtime but type checkers don't like it. Because it wasn't much extra work on our end / at runtime we thought it better to make it work even if users have to add a
# type: ignore
and figure out down the road if we can convince type checkers to allow it or look for alternatives.I'll point out that there is an alternative for that particular example that: