pythontype-conversionpydanticpydantic-v2

How to make Pydantic's non-strict, coercive mode apply to integer literals?


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.


Bad Solutions


Solution

  • 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.