Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

validate_call trying to parse an enum.StrEnum as an int #9437

Open
1 task done
hb2638 opened this issue May 14, 2024 · 2 comments
Open
1 task done

validate_call trying to parse an enum.StrEnum as an int #9437

hb2638 opened this issue May 14, 2024 · 2 comments
Labels
bug V2 Bug related to Pydantic V2

Comments

@hb2638
Copy link

hb2638 commented May 14, 2024

Initial Checks

  • I confirm that I'm using Pydantic V2

Description

Unable to pass a set with a StrEnum, int and str to an argument with a type hint of str | PropertyName | int | typing.Sequence[PropertyName | str | int] | typing.Iterable[int] | None

Example Code

import enum
import typing

import pydantic


class PropertyName(enum.StrEnum):
    PERIOD = "Period"
    VALUE_CODE = "Value Code"

@pydantic.validate_call
def get_item_property_values(
        ids: int | str | typing.Sequence[int | str] | typing.Iterable[int],
        *,
        properties: str | PropertyName | int | typing.Sequence[PropertyName | str | int] | typing.Iterable[int] | None = None,
) -> list:
    return [list(ids), list(properties)]

resp = get_item_property_values(
    {1, 2, "3", "4", 5, "6", 7},
    properties={"A", 1, PropertyName.PERIOD, "B", 2, PropertyName.VALUE_CODE},
)
print(f"{resp=}")

Python, Pydantic & OS Version

pydantic version: 2.7.1                                                                           
        pydantic-core version: 2.18.2                                                                          
          pydantic-core build: profile=release pgo=true                                                        
                 install path: C:\XXX\XXX\XXX\venv\Lib\site-packages\pydantic          
               python version: 3.12.1 (tags/v3.12.1:2305ca5, Dec  7 2023, 22:03:25) [MSC v.1937 64 bit (AMD64)]
                     platform: Windows-10-10.0.19044-SP0                                                       
             related packages: typing_extensions-4.11.0                                                        
                       commit: unknown
@hb2638 hb2638 added bug V2 Bug related to Pydantic V2 pending Awaiting a response / confirmation labels May 14, 2024
@hb2638
Copy link
Author

hb2638 commented May 14, 2024

I tried using typing.Collection instead of typing.Sequence and it failed with

pydantic.errors.PydanticSchemaGenerationError:Unable to generate pydantic-core schema for typing.Collection[int]. Set `arbitrary_types_allowed=True` in the model_config to ignore this error or implement `__get_pydantic_core_schema__` on your type to fully support it.

so I ended creating my own Collection type hint using the below code and that seems to work.

import typing
import pydantic
import pydantic_core
import typing_extensions

if typing.TYPE_CHECKING:
    Collection = typing.Collection
else:
    class Collection(typing.Collection):
        @classmethod
        def __get_pydantic_core_schema__(cls, source_type, handler):
            origin = typing_extensions.get_origin(source_type)
            if origin is None:
                validator_type = list|set|tuple
            else:
                item_tp = typing_extensions.get_args(source_type)[0]
                validator_type = list[item_tp]|set[item_tp]|tuple[item_tp, ...]
            validator = pydantic.TypeAdapter(validator_type)
            def validate(v, info):
                return validator.validate_python(v, strict=True)
            return pydantic_core.core_schema.with_info_plain_validator_function(validate)

Thanks to whoever took the time to write the Handling custom generic classes documentation. I couldn't have done this without it

working example

import enum
import typing

import pydantic
import pydantic_core
import typing_extensions


class PropertyName(enum.StrEnum):
    PERIOD = "Period"
    VALUE_CODE = "Value Code"


if typing.TYPE_CHECKING:
    Collection = typing.Collection
else:
    class Collection(typing.Collection):
        @classmethod
        def __get_pydantic_core_schema__(cls, source_type, handler):
            origin = typing_extensions.get_origin(source_type)
            if origin is None:
                validator_type = list|set|tuple
            else:
                item_tp = typing_extensions.get_args(source_type)[0]
                validator_type = list[item_tp]|set[item_tp]|tuple[item_tp, ...]
            validator = pydantic.TypeAdapter(validator_type)
            def validate(v, info):
                return validator.validate_python(v, strict=True)
            return pydantic_core.core_schema.with_info_plain_validator_function(validate)


@pydantic.validate_call
def get_item_property_values(
        ids: int | str | set[int | str] | Collection[int],
        *,
        properties: Collection[PropertyName|str|int]|str|PropertyName|int|Collection[int]|None = None,
) -> list:
    if isinstance(properties, str|int|PropertyName):
        properties = [properties]
    return [ids, properties]

resp = get_item_property_values(
    [1, 2, "3", "4", 5, "6", 7],
    properties=["A", 1, PropertyName.PERIOD,  "B", 2, PropertyName.VALUE_CODE],
)
print(f"{resp=}")

resp = get_item_property_values(
    {1, 2, "3", "4", 5, "6", 7},
    properties=["A", 1, PropertyName.PERIOD, "B", 2, PropertyName.VALUE_CODE],
)
print(f"{resp=}")

resp = get_item_property_values(
    [1, 2, "3", "4", 5, "6", 7],
    properties={"A", 1, PropertyName.PERIOD, "B", 2, PropertyName.VALUE_CODE},
)
print(f"{resp=}")

resp = get_item_property_values(
    {1, 2, "3", "4", 5, "6", 7},
    properties={"A", 1, PropertyName.PERIOD, "B", 2, PropertyName.VALUE_CODE},
)

resp = get_item_property_values(
    {1, 2, "3", "4", 5, "6", 7},
    properties=tuple({"A", 1, PropertyName.PERIOD, "B", 2, PropertyName.VALUE_CODE}),
)
print(f"{resp=}")

@sydney-runkle
Copy link
Member

@hb2638,

Thanks for the bug report. At a first glance, definitely looks like a problem, maybe relating to our new core enum parsing logic. Here's a more minimal example:

import enum
import typing

import pydantic


class PropertyName(str, enum.Enum):
    PERIOD = "Period"
    VALUE_CODE = "Value Code"

@pydantic.validate_call
def get_item_property_values(
        properties: str | PropertyName | int | typing.Sequence[PropertyName | str | int] | typing.Iterable[int] | None = None,
) -> list:
    return [
        list(properties)
    ]

resp = get_item_property_values(
    properties={"A", 1, PropertyName.PERIOD, "B", 2, PropertyName.VALUE_CODE},
)
print(f"{resp=}")

Looks like the same issue doesn't arise in the case of a TypeAdapter

@sydney-runkle sydney-runkle removed the pending Awaiting a response / confirmation label May 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug V2 Bug related to Pydantic V2
Projects
None yet
Development

No branches or pull requests

2 participants