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

Fix tests on Python 3.11 #1139

Merged
merged 5 commits into from
Apr 16, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 39 additions & 22 deletions typing_extensions/src/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
# version of the typing module.
TYPING_3_8_0 = sys.version_info[:3] >= (3, 8, 0)
TYPING_3_10_0 = sys.version_info[:3] >= (3, 10, 0)

# 3.11 makes runtime type checks (_type_check) more lenient.
TYPING_3_11_0 = sys.version_info[:3] >= (3, 11, 0)


Expand Down Expand Up @@ -154,8 +156,9 @@ def test_exception(self):
class ClassVarTests(BaseTestCase):

def test_basics(self):
with self.assertRaises(TypeError):
ClassVar[1]
if not TYPING_3_11_0:
with self.assertRaises(TypeError):
ClassVar[1]
with self.assertRaises(TypeError):
ClassVar[int, str]
with self.assertRaises(TypeError):
Expand Down Expand Up @@ -198,8 +201,9 @@ def test_no_isinstance(self):
class FinalTests(BaseTestCase):

def test_basics(self):
with self.assertRaises(TypeError):
Final[1]
if not TYPING_3_11_0:
with self.assertRaises(TypeError):
Final[1]
with self.assertRaises(TypeError):
Final[int, str]
with self.assertRaises(TypeError):
Expand Down Expand Up @@ -242,8 +246,9 @@ def test_no_isinstance(self):
class RequiredTests(BaseTestCase):

def test_basics(self):
with self.assertRaises(TypeError):
Required[1]
if not TYPING_3_11_0:
with self.assertRaises(TypeError):
Required[1]
with self.assertRaises(TypeError):
Required[int, str]
with self.assertRaises(TypeError):
Expand Down Expand Up @@ -286,8 +291,9 @@ def test_no_isinstance(self):
class NotRequiredTests(BaseTestCase):

def test_basics(self):
with self.assertRaises(TypeError):
NotRequired[1]
if not TYPING_3_11_0:
with self.assertRaises(TypeError):
NotRequired[1]
with self.assertRaises(TypeError):
NotRequired[int, str]
with self.assertRaises(TypeError):
Expand Down Expand Up @@ -666,7 +672,10 @@ class C(Generic[T]): pass
self.assertEqual(get_args(Union[int, Callable[[Tuple[T, ...]], str]]),
(int, Callable[[Tuple[T, ...]], str]))
self.assertEqual(get_args(Tuple[int, ...]), (int, ...))
self.assertEqual(get_args(Tuple[()]), ((),))
if TYPING_3_11_0:
self.assertEqual(get_args(Tuple[()]), ())
else:
self.assertEqual(get_args(Tuple[()]), ((),))
self.assertEqual(get_args(Annotated[T, 'one', 2, ['three']]), (T, 'one', 2, ['three']))
self.assertEqual(get_args(List), ())
self.assertEqual(get_args(Tuple), ())
Expand Down Expand Up @@ -1659,10 +1668,12 @@ def test_typeddict_errors(self):
isinstance(jim, Emp)
with self.assertRaises(TypeError):
issubclass(dict, Emp)
with self.assertRaises(TypeError):
TypedDict('Hi', x=1)
with self.assertRaises(TypeError):
TypedDict('Hi', [('x', int), ('y', 1)])

if not TYPING_3_11_0:
with self.assertRaises(TypeError):
TypedDict('Hi', x=1)
with self.assertRaises(TypeError):
TypedDict('Hi', [('x', int), ('y', 1)])
with self.assertRaises(TypeError):
TypedDict('Hi', [('x', int)], y=int)

Expand Down Expand Up @@ -2241,11 +2252,12 @@ def test_invalid_uses(self):
):
Concatenate[P, T]

with self.assertRaisesRegex(
TypeError,
'each arg must be a type',
):
Concatenate[1, P]
if not TYPING_3_11_0:
with self.assertRaisesRegex(
TypeError,
'each arg must be a type',
):
Concatenate[1, P]

def test_basic_introspection(self):
P = ParamSpec('P')
Expand Down Expand Up @@ -2425,7 +2437,10 @@ def test_basic_plain(self):

def test_repr(self):
Ts = TypeVarTuple('Ts')
self.assertEqual(repr(Unpack[Ts]), 'typing_extensions.Unpack[Ts]')
if TYPING_3_11_0:
self.assertEqual(repr(Unpack[Ts]), '*Ts')
else:
self.assertEqual(repr(Unpack[Ts]), 'typing_extensions.Unpack[Ts]')
Comment on lines +2512 to +2515
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Which I forgot to save.)

Wouldn't it make more sense to make the typing_extensions version match the one from Python 3.11?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was thinking about that too. On the other hand, *Ts is a syntax error before 3.11, so it may not be very helpful as repr() output.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mrahtz what do you think? Should we use *Ts in the repr() for Unpack even before 3.11?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I agree it should be Unpack[Ts] before 3.11 - isn't the repr() suppose to be as close as possible to something you can directly eval()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, then let's stick with the current behavior.


def test_cannot_subclass_vars(self):
with self.assertRaises(TypeError):
Expand Down Expand Up @@ -2500,8 +2515,10 @@ class C(Generic[T1, T2, Unpack[Ts]]): pass
self.assertEqual(C[int, str].__args__, (int, str))
self.assertEqual(C[int, str, float].__args__, (int, str, float))
self.assertEqual(C[int, str, float, bool].__args__, (int, str, float, bool))
with self.assertRaises(TypeError):
C[int]
# TODO This should probably also fail on 3.11, pending changes to CPython.
if not TYPING_3_11_0:
with self.assertRaises(TypeError):
C[int]


class TypeVarTupleTests(BaseTestCase):
Expand Down Expand Up @@ -2545,7 +2562,7 @@ def test_args_and_parameters(self):
Ts = TypeVarTuple('Ts')

t = Tuple[tuple(Ts)]
self.assertEqual(t.__args__, (Ts.__unpacked__,))
self.assertEqual(t.__args__, (Unpack[Ts],))
self.assertEqual(t.__parameters__, (Ts,))


Expand Down
119 changes: 62 additions & 57 deletions typing_extensions/src/typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1603,7 +1603,9 @@ class Movie(TypedDict):
""")


if sys.version_info[:2] >= (3, 9):
if hasattr(typing, "Unpack"): # 3.11+
Unpack = typing.Unpack
elif sys.version_info[:2] >= (3, 9):
class _UnpackSpecialForm(typing._SpecialForm, _root=True):
def __repr__(self):
return 'typing_extensions.' + self._name
Expand Down Expand Up @@ -1659,84 +1661,87 @@ def _is_unpack(obj):
return isinstance(obj, _UnpackAlias)


class TypeVarTuple:
"""Type variable tuple.
if hasattr(typing, "TypeVarTuple"): # 3.11+
TypeVarTuple = typing.TypeVarTuple
else:
class TypeVarTuple:
"""Type variable tuple.

Usage::
Usage::

Ts = TypeVarTuple('Ts')
Ts = TypeVarTuple('Ts')

In the same way that a normal type variable is a stand-in for a single
type such as ``int``, a type variable *tuple* is a stand-in for a *tuple* type such as
``Tuple[int, str]``.
In the same way that a normal type variable is a stand-in for a single
type such as ``int``, a type variable *tuple* is a stand-in for a *tuple*
type such as ``Tuple[int, str]``.

Type variable tuples can be used in ``Generic`` declarations.
Consider the following example::
Type variable tuples can be used in ``Generic`` declarations.
Consider the following example::

class Array(Generic[*Ts]): ...
class Array(Generic[*Ts]): ...

The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``,
where ``T1`` and ``T2`` are type variables. To use these type variables
as type parameters of ``Array``, we must *unpack* the type variable tuple using
the star operator: ``*Ts``. The signature of ``Array`` then behaves
as if we had simply written ``class Array(Generic[T1, T2]): ...``.
In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows
us to parameterise the class with an *arbitrary* number of type parameters.
The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``,
where ``T1`` and ``T2`` are type variables. To use these type variables
as type parameters of ``Array``, we must *unpack* the type variable tuple using
the star operator: ``*Ts``. The signature of ``Array`` then behaves
as if we had simply written ``class Array(Generic[T1, T2]): ...``.
In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows
us to parameterise the class with an *arbitrary* number of type parameters.

Type variable tuples can be used anywhere a normal ``TypeVar`` can.
This includes class definitions, as shown above, as well as function
signatures and variable annotations::
Type variable tuples can be used anywhere a normal ``TypeVar`` can.
This includes class definitions, as shown above, as well as function
signatures and variable annotations::

class Array(Generic[*Ts]):
class Array(Generic[*Ts]):

def __init__(self, shape: Tuple[*Ts]):
self._shape: Tuple[*Ts] = shape
def __init__(self, shape: Tuple[*Ts]):
self._shape: Tuple[*Ts] = shape

def get_shape(self) -> Tuple[*Ts]:
return self._shape
def get_shape(self) -> Tuple[*Ts]:
return self._shape

shape = (Height(480), Width(640))
x: Array[Height, Width] = Array(shape)
y = abs(x) # Inferred type is Array[Height, Width]
z = x + x # ... is Array[Height, Width]
x.get_shape() # ... is tuple[Height, Width]
shape = (Height(480), Width(640))
x: Array[Height, Width] = Array(shape)
y = abs(x) # Inferred type is Array[Height, Width]
z = x + x # ... is Array[Height, Width]
x.get_shape() # ... is tuple[Height, Width]

"""
"""

# Trick Generic __parameters__.
__class__ = typing.TypeVar
# Trick Generic __parameters__.
__class__ = typing.TypeVar

def __iter__(self):
yield self.__unpacked__
def __iter__(self):
yield self.__unpacked__

def __init__(self, name):
self.__name__ = name
def __init__(self, name):
self.__name__ = name

# for pickling:
try:
def_mod = sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
def_mod = None
if def_mod != 'typing_extensions':
self.__module__ = def_mod
# for pickling:
try:
def_mod = sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
def_mod = None
if def_mod != 'typing_extensions':
self.__module__ = def_mod

self.__unpacked__ = Unpack[self]
self.__unpacked__ = Unpack[self]

def __repr__(self):
return self.__name__
def __repr__(self):
return self.__name__

def __hash__(self):
return object.__hash__(self)
def __hash__(self):
return object.__hash__(self)

def __eq__(self, other):
return self is other
def __eq__(self, other):
return self is other

def __reduce__(self):
return self.__name__
def __reduce__(self):
return self.__name__

def __init_subclass__(self, *args, **kwds):
if '_root' not in kwds:
raise TypeError("Cannot subclass special typing classes")
def __init_subclass__(self, *args, **kwds):
if '_root' not in kwds:
raise TypeError("Cannot subclass special typing classes")


if hasattr(typing, "reveal_type"):
Expand Down