Content-Length: 758022 | pFad | http://togithub.com/python/typing_extensions/commit/92d508d262d84d503b11c3fa63a076e571fbdedd

4AB Add get_overloads() (#1140) · python/typing_extensions@92d508d · GitHub
Skip to content

Commit 92d508d

Browse files
Add get_overloads() (#1140)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
1 parent d00b345 commit 92d508d

File tree

4 files changed

+152
-3
lines changed

4 files changed

+152
-3
lines changed

CHANGELOG

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Unreleased
22

3-
- Add `typing.assert_type`. Backport from bpo-46480.
3+
- Add `typing_extensions.get_overloads` and
4+
`typing_extensions.clear_overloads`, and add registry support to
5+
`typing_extensions.overload`. Backport from python/cpython#89263.
6+
- Add `typing_extensions.assert_type`. Backport from bpo-46480.
47
- Drop support for Python 3.6. Original patch by Adam Turner (@AA-Turner).
58

69
# Release 4.1.1 (February 13, 2022)

README.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ This module currently contains the following:
4747

4848
- ``assert_never``
4949
- ``assert_type``
50+
- ``clear_overloads``
51+
- ``get_overloads``
5052
- ``LiteralString`` (see PEP 675)
5153
- ``Never``
5254
- ``NotRequired`` (see PEP 655)
@@ -122,6 +124,10 @@ Certain objects were changed after they were added to ``typing``, and
122124
Python 3.8 and lack support for ``ParamSpecArgs`` and ``ParamSpecKwargs``
123125
in 3.9.
124126
- ``@final`` was changed in Python 3.11 to set the ``.__final__`` attribute.
127+
- ``@overload`` was changed in Python 3.11 to make function overloads
128+
introspectable at runtime. In order to access overloads with
129+
``typing_extensions.get_overloads()``, you must use
130+
``@typing_extensions.overload``.
125131

126132
There are a few types whose interface was modified between different
127133
versions of typing. For example, ``typing.Sequence`` was modified to

src/test_typing_extensions.py

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
import abc
44
import contextlib
55
import collections
6+
from collections import defaultdict
67
import collections.abc
78
from functools import lru_cache
89
import inspect
910
import pickle
1011
import subprocess
1112
import types
1213
from unittest import TestCase, main, skipUnless, skipIf
14+
from unittest.mock import patch
1315
from test import ann_module, ann_module2, ann_module3
1416
import typing
1517
from typing import TypeVar, Optional, Union, Any, AnyStr
@@ -21,9 +23,10 @@
2123
from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict, Self
2224
from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard
2325
from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired
24-
from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict
26+
from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, final, is_typeddict
2527
from typing_extensions import TypeVarTuple, Unpack, dataclass_transform, reveal_type, Never, assert_never, LiteralString
2628
from typing_extensions import assert_type, get_type_hints, get_origen, get_args
29+
from typing_extensions import clear_overloads, get_overloads, overload
2730

2831
# Flags used to mark tests that only apply after a specific
2932
# version of the typing module.
@@ -403,6 +406,20 @@ def test_no_multiple_subscripts(self):
403406
Literal[1][1]
404407

405408

409+
class MethodHolder:
410+
@classmethod
411+
def clsmethod(cls): ...
412+
@staticmethod
413+
def stmethod(): ...
414+
def method(self): ...
415+
416+
417+
if TYPING_3_11_0:
418+
registry_holder = typing
419+
else:
420+
registry_holder = typing_extensions
421+
422+
406423
class OverloadTests(BaseTestCase):
407424

408425
def test_overload_fails(self):
@@ -424,6 +441,61 @@ def blah():
424441

425442
blah()
426443

444+
def set_up_overloads(self):
445+
def blah():
446+
pass
447+
448+
overload1 = blah
449+
overload(blah)
450+
451+
def blah():
452+
pass
453+
454+
overload2 = blah
455+
overload(blah)
456+
457+
def blah():
458+
pass
459+
460+
return blah, [overload1, overload2]
461+
462+
# Make sure we don't clear the global overload registry
463+
@patch(
464+
f"{registry_holder.__name__}._overload_registry",
465+
defaultdict(lambda: defaultdict(dict))
466+
)
467+
def test_overload_registry(self):
468+
registry = registry_holder._overload_registry
469+
# The registry starts out empty
470+
self.assertEqual(registry, {})
471+
472+
impl, overloads = self.set_up_overloads()
473+
self.assertNotEqual(registry, {})
474+
self.assertEqual(list(get_overloads(impl)), overloads)
475+
476+
def some_other_func(): pass
477+
overload(some_other_func)
478+
other_overload = some_other_func
479+
def some_other_func(): pass
480+
self.assertEqual(list(get_overloads(some_other_func)), [other_overload])
481+
482+
# Make sure that after we clear all overloads, the registry is
483+
# completely empty.
484+
clear_overloads()
485+
self.assertEqual(registry, {})
486+
self.assertEqual(get_overloads(impl), [])
487+
488+
# Querying a function with no overloads shouldn't change the registry.
489+
def the_only_one(): pass
490+
self.assertEqual(get_overloads(the_only_one), [])
491+
self.assertEqual(registry, {})
492+
493+
def test_overload_registry_repeated(self):
494+
for _ in range(2):
495+
impl, overloads = self.set_up_overloads()
496+
497+
self.assertEqual(list(get_overloads(impl)), overloads)
498+
427499

428500
class AssertTypeTests(BaseTestCase):
429501

src/typing_extensions.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import abc
22
import collections
33
import collections.abc
4+
import functools
45
import operator
56
import sys
67
import types as _types
@@ -46,7 +47,9 @@
4647
'Annotated',
4748
'assert_never',
4849
'assert_type',
50+
'clear_overloads',
4951
'dataclass_transform',
52+
'get_overloads',
5053
'final',
5154
'get_args',
5255
'get_origen',
@@ -249,7 +252,72 @@ def __getitem__(self, parameters):
249252

250253

251254
_overload_dummy = typing._overload_dummy # noqa
252-
overload = typing.overload
255+
256+
257+
if hasattr(typing, "get_overloads"): # 3.11+
258+
overload = typing.overload
259+
get_overloads = typing.get_overloads
260+
clear_overloads = typing.clear_overloads
261+
else:
262+
# {module: {qualname: {firstlineno: func}}}
263+
_overload_registry = collections.defaultdict(
264+
functools.partial(collections.defaultdict, dict)
265+
)
266+
267+
def overload(func):
268+
"""Decorator for overloaded functions/methods.
269+
270+
In a stub file, place two or more stub definitions for the same
271+
function in a row, each decorated with @overload. For example:
272+
273+
@overload
274+
def utf8(value: None) -> None: ...
275+
@overload
276+
def utf8(value: bytes) -> bytes: ...
277+
@overload
278+
def utf8(value: str) -> bytes: ...
279+
280+
In a non-stub file (i.e. a regular .py file), do the same but
281+
follow it with an implementation. The implementation should *not*
282+
be decorated with @overload. For example:
283+
284+
@overload
285+
def utf8(value: None) -> None: ...
286+
@overload
287+
def utf8(value: bytes) -> bytes: ...
288+
@overload
289+
def utf8(value: str) -> bytes: ...
290+
def utf8(value):
291+
# implementation goes here
292+
293+
The overloads for a function can be retrieved at runtime using the
294+
get_overloads() function.
295+
"""
296+
# classmethod and staticmethod
297+
f = getattr(func, "__func__", func)
298+
try:
299+
_overload_registry[f.__module__][f.__qualname__][
300+
f.__code__.co_firstlineno
301+
] = func
302+
except AttributeError:
303+
# Not a normal function; ignore.
304+
pass
305+
return _overload_dummy
306+
307+
def get_overloads(func):
308+
"""Return all defined overloads for *func* as a sequence."""
309+
# classmethod and staticmethod
310+
f = getattr(func, "__func__", func)
311+
if f.__module__ not in _overload_registry:
312+
return []
313+
mod_dict = _overload_registry[f.__module__]
314+
if f.__qualname__ not in mod_dict:
315+
return []
316+
return list(mod_dict[f.__qualname__].values())
317+
318+
def clear_overloads():
319+
"""Clear all overloads in the registry."""
320+
_overload_registry.clear()
253321

254322

255323
# This is not a real generic class. Don't use outside annotations.

0 commit comments

Comments
 (0)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://togithub.com/python/typing_extensions/commit/92d508d262d84d503b11c3fa63a076e571fbdedd

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy