Search code examples
pythonpython-typingpydantic-v2

Which part of a Union type does a value satisfy?


I'm using the Typing module and Pydantic to define a custom type which is a union. How can I tell which part of the Union is satisfied by a particular value after initializing the class?

For example, in:

from datetime import datetime
from typing import Union, Literal, Tuple, List

from pydantic import BaseModel, UUID4, ValidationError


class ExportDataRequest(BaseModel):
    customers: Union[Literal["all"], List[UUID4], UUID4, List[int], int]
    daterange: Union[Tuple[datetime, datetime], int]


data = {
    "customers": "all",
    'daterange': ("2024-01-01", "2024-03-01")
}

try:
    model = ExportDataRequest(**data)
    
    print(type(model.customers))
    # str
except ValidationError as e:
    print(e.errors())

The type from the Union satisfied by the input to customers above is the Literal["all"] piece.

If I ask for the type of model.customers as in the above snippet, python will respond with str.

Is there a way to determine that it is the Literal["all"] from my class definition?


Solution

  • For anyone who sees this, I solved it with Pydantic's TypeAdapter: https://docs.pydantic.dev/latest/concepts/type_adapter/

    Adding the below to the code from the question above:

    from pydantic import TypeAdapter
    
    def handle_type(model):
        types_to_check = [
            (Literal["all"], "all"),
            (List[UUID4], "uuid_list"),
            (List[int], "int_list"),
            (int, "int"),
            (UUID4, "uuid"),
        ]
    
        for type_to_check, result in types_to_check:
            try:
                TypeAdapter(type_to_check).validate_python(model.customers)
                return result
            except ValidationError:
                pass
    
        return None