How to `isinstance` a typing type so that it works with static type checkers?

My code defines lots of typing types, and I need to isinstance lots of objects against them at runtime. Something like this:

Type1 = list[int]
Type2 = list[str]
# etc.
    
# get data from the outside, e.g. read from a file
data = get_data()

# NOT WORKING
if isinstance(data, Type1):
    "do something"
elif isinstance(data, Type2):
    "do something else"
else:
    "etc."

I browsed stackoverflow and found this answer which suggested using pydantic. This is what I’ve been using (I modified the answer to be compatible with pydantic 2.0):

from pydantic import TypeAdapter, ValidationError

# drop-in replacement for isinstance
def pydantic_isinstance(obj, pydantic_type):
    try:
        TypeAdapter(pydantic_type).validate_python(obj)
        return True
    except ValidationError:
        return False

I’m not entirely happy with this solution because unlike the built-in isinstance, this does not integrate with static type checkers (I use pyright). For instance, inside the block if pydantic_isinstance(data, list[int]): ..., pyright does not recognize that data is of type list[int].


In short, I need a runtime type validator that also works with static type checkers. Does it exist? How would I implement one? I prefer staying with pyright, but if a solution only exists with another type checker, I will consider migrating.

I am not 100% sure if it covers your usecase, but I think typeguards can do what you want: PEP 647 – User-Defined Type Guards | peps.python.org

This signature appears to work:

T = TypeVar('T')

def pydantic_isinstance(obj: Any, pydantic_type: type[T]) -> TypeGuard[T]:
   ...

Thank you. That’s a nice feature, it’s simpler than I thought.

To cover all of my use cases, it it possible to use it with Union and Annotated types? Like,

# NOT WORKING
pydantic_isinstance(2, int | str)

from pydantic import Field
custom_string = Annotated[str, Field(pattern=r"\d+")]
# NOT WORKING
pydantic_isinstance("2", custom_string)

See Is `Annotated` compatible with `type[T]`?

Thanks for the link. I learned that pyright recently changed its behavior with regard to this kind of things, so I rolled back to v1.1.345 and everything works.