I'm validating inputs to a function using Pydantic's @validate_call
as follows:
from typing import Literal
from pydantic import validate_call
@validate_call
def foo(a: Literal[0, 90, 180, 270]) -> None:
print(a, type(a))
I want Pydantic to perform its default type coercion like it does with the int
type:
foo(90) # Works as expected
foo('90') # Doesn't work, but I want it to
If I use the annotation a: int
, it will coerce strings like '180'
, but then I have to manually validate which integers are given.
How do I make Pydantic perform type coercion on Literals?
Note: I'll accept a solution that requires a
to be a string type instead of an integer, as long as it still allows both integer and string input.
I don't want to add every literal case. Literal[0, 90, 180, 270, '0', '90', '180', '270']
is bad because it doesn't allow the strings '-0'
or '180.0'
.
I could do Annotated[int, Field(ge=0, le=0)] | Annotated[int, Field(ge=90, le=90)] | ...
, but that's stupidly verbose.
I don't want to define some separate function or model. At that point, it's easier to just accept a: int
and validate the particular value inside the method.
You can combine the BeforeValidator
and the Literal
like this:
from typing import Annotated, Literal
from pydantic import validate_call, BeforeValidator, ValidationError
# First try has the following validator:
# BeforeValidator(int)
@validate_call
def foo(a: Annotated[Literal[0, 90, 180, 270], BeforeValidator(float)]) -> None:
print(a, type(a))
if __name__ == "__main__":
foo("90")
foo("180.0")
foo("180.0")
try:
foo(0.1)
except ValidationError as err:
print(err)
try:
foo("70")
except ValidationError as err:
print(err)
try:
foo("can't convert to int")
except ValueError as err:
print(err)
The BeforeValidator
function will be called before checks and thus the literal validation will be done against an integer.
Edit: better manage string with decimal number.