Passing only one TypeVar (of two) when using defaults

Hi there! I think I found a minor issue when using the default values with TypeVars.

I have a class that uses 2 typevars, and I’d like users of the class to be able to pass 0, 1 or all of the variables, unfortunately when using this code:

from __future__ import annotations

from typing import Any, Generic, Tuple, Type, Union
from typing_extensions import TypeVar

ContextType = TypeVar("ContextType", default=Any)
RootValueType = TypeVar("RootValueType", default=Any)


class Info(Generic[ContextType, RootValueType]):
    ...

print(Info)
print(Info[int])
print(Info[int, str])

I get the following error:

<class '__main__.Info'>
Traceback (most recent call last):
  File "/Users/patrick/github/strawberry-graphql/strawberry/typevardemo.py", line 20, in <module>
    print(Info[int])
          ~~~~^^^^^
  File "/Users/patrick/.local/share/rtx/installs/python/3.12.0/lib/python3.12/typing.py", line 377, in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
  File "/Users/patrick/.local/share/rtx/installs/python/3.12.0/lib/python3.12/typing.py", line 1059, in _generic_class_getitem
    _check_generic(cls, params, len(cls.__parameters__))
  File "/Users/patrick/.local/share/rtx/installs/python/3.12.0/lib/python3.12/typing.py", line 297, in _check_generic
    raise TypeError(f"Too {'many' if alen > elen else 'few'} arguments for {cls};"
TypeError: Too few arguments for <class '__main__.Info'>; actual 1, expected 2

I’ve solved this by adding a custom __class_getitem__, like this:

from __future__ import annotations

from typing import Any, Generic, Tuple, Type, Union
from typing_extensions import TypeVar

ContextType = TypeVar("ContextType", default=Any)
RootValueType = TypeVar("RootValueType", default=Any)


class Info(Generic[ContextType, RootValueType]):
    def __class_getitem__(cls, types: Union[type, Tuple[type, ...]]) -> Type[Info]:
        if not isinstance(types, tuple):
            types = (types, Any)

        return super().__class_getitem__(types)


print(Info)
print(Info[int])
print(Info[int, str])

Seems a bit of a hack, but it works :blush: Do you see anything wrong with it?

If that’s correct, should we create a new Generic class inside typing extensions that does something like this? Should we do this in the next version of python itself (I guess in that case we’d change the check generic function)?

I’d be happy to contribute this if it makes sense :blush:

Worth mentioning that my solution only works for my specific use case, but we could make it generic.

But thinking more about this, I wonder if we need to add additional checks to defaults, what if I did something like this:

from __future__ import annotations

from typing import Any, Generic
from typing_extensions import TypeVar

ContextType = TypeVar("ContextType", default=Any)
RootValueType = TypeVar("RootValueType")

# ContextType has a default, but RootValueType does not
class Info(Generic[ContextType, RootValueType]):
    pass

Would this be valid/useful? I don’t think so, but I might be missing something :blush:

This work is already being done in typing_extension, see Fix runtime behaviour of PEP 696 by NCPlayz · Pull Request #293 · python/typing_extensions · GitHub. It might take a bit till we get a release, but you should already be able to test it out with pip install git+https://github.com/python/typing_extensions.

1 Like

@MegaIng oh! Nice, I’ll implement my workaround for now and then I’ll remove it once that’s released.

Sorry I missed it!