Skip to content

Commit 5612f6f

Browse files
committed
Merge remote-tracking branch 'upstream/main' into get_type_hints
2 parents 54b8eb0 + 82d512a commit 5612f6f

File tree

5 files changed

+134
-32
lines changed

5 files changed

+134
-32
lines changed

.github/workflows/third_party.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,7 @@ jobs:
236236
strategy:
237237
fail-fast: false
238238
matrix:
239-
# 3.13 support: https://github.com/swansonk14/typed-argument-parser/issues/150
240-
python-version: ["3.9", "3.10", "3.11", "3.12"]
239+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
241240
runs-on: ubuntu-latest
242241
timeout-minutes: 60
243242
steps:

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
subscripted objects) had wrong parameters if they were directly
1717
subscripted with an `Unpack` object.
1818
Patch by [Daraan](https://github.com/Daraan).
19+
- Backport to Python 3.10 the ability to substitute `...` in generic `Callable`
20+
aliases that have a `Concatenate` special form as their argument.
21+
Patch by [Daraan](https://github.com/Daraan).
22+
- Extended the `Concatenate` backport for Python 3.8-3.10 to now accept
23+
`Ellipsis` as an argument. Patch by [Daraan](https://github.com/Daraan).
1924
- Fix backport of `get_type_hints` to reflect Python 3.11+ behavior which does not add
2025
`Union[..., NoneType]` to annotations that have a `None` default value anymore.
2126
This fixes wrapping of `Annotated` in an unwanted `Optional` in such cases.

doc/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ Special typing primitives
178178
See :py:data:`typing.Concatenate` and :pep:`612`. In ``typing`` since 3.10.
179179

180180
The backport does not support certain operations involving ``...`` as
181-
a parameter; see :issue:`48` and :issue:`110` for details.
181+
a parameter; see :issue:`48` and :pr:`481` for details.
182182

183183
.. data:: Final
184184

src/test_typing_extensions.py

Lines changed: 69 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1809,12 +1809,14 @@ class C(Generic[T]): pass
18091809
# In 3.9 and lower we use typing_extensions's hacky implementation
18101810
# of ParamSpec, which gets incorrectly wrapped in a list
18111811
self.assertIn(get_args(Callable[P, int]), [(P, int), ([P], int)])
1812-
self.assertEqual(get_args(Callable[Concatenate[int, P], int]),
1813-
(Concatenate[int, P], int))
18141812
self.assertEqual(get_args(Required[int]), (int,))
18151813
self.assertEqual(get_args(NotRequired[int]), (int,))
18161814
self.assertEqual(get_args(Unpack[Ts]), (Ts,))
18171815
self.assertEqual(get_args(Unpack), ())
1816+
self.assertEqual(get_args(Callable[Concatenate[int, P], int]),
1817+
(Concatenate[int, P], int))
1818+
self.assertEqual(get_args(Callable[Concatenate[int, ...], int]),
1819+
(Concatenate[int, ...], int))
18181820

18191821

18201822
class CollectionsAbcTests(BaseTestCase):
@@ -5356,6 +5358,10 @@ class Y(Protocol[T, P]):
53565358
self.assertEqual(G2.__args__, (int, Concatenate[int, P_2]))
53575359
self.assertEqual(G2.__parameters__, (P_2,))
53585360

5361+
G3 = klass[int, Concatenate[int, ...]]
5362+
self.assertEqual(G3.__args__, (int, Concatenate[int, ...]))
5363+
self.assertEqual(G3.__parameters__, ())
5364+
53595365
# The following are some valid uses cases in PEP 612 that don't work:
53605366
# These do not work in 3.9, _type_check blocks the list and ellipsis.
53615367
# G3 = X[int, [int, bool]]
@@ -5451,21 +5457,28 @@ class MyClass: ...
54515457
c = Concatenate[MyClass, P]
54525458
self.assertNotEqual(c, Concatenate)
54535459

5460+
# Test Ellipsis Concatenation
5461+
d = Concatenate[MyClass, ...]
5462+
self.assertNotEqual(d, c)
5463+
self.assertNotEqual(d, Concatenate)
5464+
54545465
def test_valid_uses(self):
54555466
P = ParamSpec('P')
54565467
T = TypeVar('T')
5468+
for callable_variant in (Callable, collections.abc.Callable):
5469+
with self.subTest(callable_variant=callable_variant):
5470+
if not TYPING_3_9_0 and callable_variant is collections.abc.Callable:
5471+
self.skipTest("Needs PEP 585")
54575472

5458-
C1 = Callable[Concatenate[int, P], int]
5459-
C2 = Callable[Concatenate[int, T, P], T]
5460-
self.assertEqual(C1.__origin__, C2.__origin__)
5461-
self.assertNotEqual(C1, C2)
5473+
C1 = callable_variant[Concatenate[int, P], int]
5474+
C2 = callable_variant[Concatenate[int, T, P], T]
5475+
self.assertEqual(C1.__origin__, C2.__origin__)
5476+
self.assertNotEqual(C1, C2)
54625477

5463-
# Test collections.abc.Callable too.
5464-
if sys.version_info[:2] >= (3, 9):
5465-
C3 = collections.abc.Callable[Concatenate[int, P], int]
5466-
C4 = collections.abc.Callable[Concatenate[int, T, P], T]
5467-
self.assertEqual(C3.__origin__, C4.__origin__)
5468-
self.assertNotEqual(C3, C4)
5478+
C3 = callable_variant[Concatenate[int, ...], int]
5479+
C4 = callable_variant[Concatenate[int, T, ...], T]
5480+
self.assertEqual(C3.__origin__, C4.__origin__)
5481+
self.assertNotEqual(C3, C4)
54695482

54705483
def test_invalid_uses(self):
54715484
P = ParamSpec('P')
@@ -5479,25 +5492,54 @@ def test_invalid_uses(self):
54795492

54805493
with self.assertRaisesRegex(
54815494
TypeError,
5482-
'The last parameter to Concatenate should be a ParamSpec variable',
5495+
'The last parameter to Concatenate should be a ParamSpec variable or ellipsis',
54835496
):
54845497
Concatenate[P, T]
54855498

5486-
if not TYPING_3_11_0:
5487-
with self.assertRaisesRegex(
5488-
TypeError,
5489-
'each arg must be a type',
5490-
):
5491-
Concatenate[1, P]
5499+
# Test with tuple argument
5500+
with self.assertRaisesRegex(
5501+
TypeError,
5502+
"The last parameter to Concatenate should be a ParamSpec variable or ellipsis.",
5503+
):
5504+
Concatenate[(P, T)]
5505+
5506+
with self.assertRaisesRegex(
5507+
TypeError,
5508+
'is not a generic class',
5509+
):
5510+
Callable[Concatenate[int, ...], Any][Any]
5511+
5512+
# Assure that `_type_check` is called.
5513+
P = ParamSpec('P')
5514+
with self.assertRaisesRegex(
5515+
TypeError,
5516+
"each arg must be a type",
5517+
):
5518+
Concatenate[(str,), P]
5519+
5520+
@skipUnless(TYPING_3_10_0, "Missing backport to <=3.9. See issue #48")
5521+
def test_alias_subscription_with_ellipsis(self):
5522+
P = ParamSpec('P')
5523+
X = Callable[Concatenate[int, P], Any]
5524+
5525+
C1 = X[...]
5526+
self.assertEqual(C1.__parameters__, ())
5527+
self.assertEqual(get_args(C1), (Concatenate[int, ...], Any))
54925528

54935529
def test_basic_introspection(self):
54945530
P = ParamSpec('P')
54955531
C1 = Concatenate[int, P]
54965532
C2 = Concatenate[int, T, P]
5533+
C3 = Concatenate[int, ...]
5534+
C4 = Concatenate[int, T, ...]
54975535
self.assertEqual(C1.__origin__, Concatenate)
54985536
self.assertEqual(C1.__args__, (int, P))
54995537
self.assertEqual(C2.__origin__, Concatenate)
55005538
self.assertEqual(C2.__args__, (int, T, P))
5539+
self.assertEqual(C3.__origin__, Concatenate)
5540+
self.assertEqual(C3.__args__, (int, Ellipsis))
5541+
self.assertEqual(C4.__origin__, Concatenate)
5542+
self.assertEqual(C4.__args__, (int, T, Ellipsis))
55015543

55025544
def test_eq(self):
55035545
P = ParamSpec('P')
@@ -5508,6 +5550,13 @@ def test_eq(self):
55085550
self.assertEqual(hash(C1), hash(C2))
55095551
self.assertNotEqual(C1, C3)
55105552

5553+
C4 = Concatenate[int, ...]
5554+
C5 = Concatenate[int, ...]
5555+
C6 = Concatenate[int, T, ...]
5556+
self.assertEqual(C4, C5)
5557+
self.assertEqual(hash(C4), hash(C5))
5558+
self.assertNotEqual(C4, C6)
5559+
55115560

55125561
class TypeGuardTests(BaseTestCase):
55135562
def test_basics(self):
@@ -6219,7 +6268,7 @@ def test_typing_extensions_defers_when_possible(self):
62196268
if sys.version_info < (3, 10, 1):
62206269
exclude |= {"Literal"}
62216270
if sys.version_info < (3, 11):
6222-
exclude |= {'final', 'Any', 'NewType', 'overload'}
6271+
exclude |= {'final', 'Any', 'NewType', 'overload', 'Concatenate'}
62236272
if sys.version_info < (3, 12):
62246273
exclude |= {
62256274
'SupportsAbs', 'SupportsBytes',

src/typing_extensions.py

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1875,28 +1875,77 @@ def __parameters__(self):
18751875
return tuple(
18761876
tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec))
18771877
)
1878+
# 3.10+
1879+
else:
1880+
_ConcatenateGenericAlias = typing._ConcatenateGenericAlias
18781881

1882+
# 3.10
1883+
if sys.version_info < (3, 11):
1884+
_typing_ConcatenateGenericAlias = _ConcatenateGenericAlias
1885+
1886+
class _ConcatenateGenericAlias(_typing_ConcatenateGenericAlias, _root=True):
1887+
# needed for checks in collections.abc.Callable to accept this class
1888+
__module__ = "typing"
1889+
1890+
def copy_with(self, params):
1891+
if isinstance(params[-1], (list, tuple)):
1892+
return (*params[:-1], *params[-1])
1893+
if isinstance(params[-1], _ConcatenateGenericAlias):
1894+
params = (*params[:-1], *params[-1].__args__)
1895+
elif not (params[-1] is ... or isinstance(params[-1], ParamSpec)):
1896+
raise TypeError("The last parameter to Concatenate should be a "
1897+
"ParamSpec variable or ellipsis.")
1898+
return super(_typing_ConcatenateGenericAlias, self).copy_with(params)
1899+
1900+
1901+
# 3.8-3.9.2
1902+
class _EllipsisDummy: ...
1903+
1904+
1905+
# 3.8-3.10
1906+
def _create_concatenate_alias(origin, parameters):
1907+
if parameters[-1] is ... and sys.version_info < (3, 9, 2):
1908+
# Hack: Arguments must be types, replace it with one.
1909+
parameters = (*parameters[:-1], _EllipsisDummy)
1910+
if sys.version_info >= (3, 10, 2):
1911+
concatenate = _ConcatenateGenericAlias(origin, parameters,
1912+
_typevar_types=(TypeVar, ParamSpec),
1913+
_paramspec_tvars=True)
1914+
else:
1915+
concatenate = _ConcatenateGenericAlias(origin, parameters)
1916+
if parameters[-1] is not _EllipsisDummy:
1917+
return concatenate
1918+
# Remove dummy again
1919+
concatenate.__args__ = tuple(p if p is not _EllipsisDummy else ...
1920+
for p in concatenate.__args__)
1921+
if sys.version_info < (3, 10):
1922+
# backport needs __args__ adjustment only
1923+
return concatenate
1924+
concatenate.__parameters__ = tuple(p for p in concatenate.__parameters__
1925+
if p is not _EllipsisDummy)
1926+
return concatenate
18791927

1880-
# 3.8-3.9
1928+
1929+
# 3.8-3.10
18811930
@typing._tp_cache
18821931
def _concatenate_getitem(self, parameters):
18831932
if parameters == ():
18841933
raise TypeError("Cannot take a Concatenate of no types.")
18851934
if not isinstance(parameters, tuple):
18861935
parameters = (parameters,)
1887-
if not isinstance(parameters[-1], ParamSpec):
1936+
if not (parameters[-1] is ... or isinstance(parameters[-1], ParamSpec)):
18881937
raise TypeError("The last parameter to Concatenate should be a "
1889-
"ParamSpec variable.")
1938+
"ParamSpec variable or ellipsis.")
18901939
msg = "Concatenate[arg, ...]: each arg must be a type."
1891-
parameters = tuple(typing._type_check(p, msg) for p in parameters)
1892-
return _ConcatenateGenericAlias(self, parameters)
1940+
parameters = (*(typing._type_check(p, msg) for p in parameters[:-1]),
1941+
parameters[-1])
1942+
return _create_concatenate_alias(self, parameters)
18931943

18941944

1895-
# 3.10+
1896-
if hasattr(typing, 'Concatenate'):
1945+
# 3.11+; Concatenate does not accept ellipsis in 3.10
1946+
if sys.version_info >= (3, 11):
18971947
Concatenate = typing.Concatenate
1898-
_ConcatenateGenericAlias = typing._ConcatenateGenericAlias
1899-
# 3.9
1948+
# 3.9-3.10
19001949
elif sys.version_info[:2] >= (3, 9):
19011950
@_ExtensionsSpecialForm
19021951
def Concatenate(self, parameters):

0 commit comments

Comments
 (0)