最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

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

programmeradmin4浏览0评论

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

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

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

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

Share Improve this question asked Feb 14 at 3:55 kviLLkviLL 3554 silver badges16 bronze badges 3
  • Why are you enforcing a type check if you want a duck type like handling? A simple int(a) in (0, 90, 180, 270) would do it. – Klaus D. Commented Feb 14 at 4:54
  • You could use BeforeValidator. But since this is clearly not literal, I don't know if you should. Perhaps using IntEnum is a more pydantic solution. – ken Commented Feb 14 at 7:13
  • @KlausD. My particular case is a bit more complicated than this, but generally speaking, I just prefer using @validate_call to check all the parameter types, and lenient duck typing is an added bonus that works on all the other parameters. Normally it's less code as well, though admittedly not here. I suppose it's just personal preference – kviLL Commented Feb 15 at 16:51
Add a comment  | 

1 Answer 1

Reset to default 2

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.

发布评论

评论列表(0)

  1. 暂无评论