From 83ed5bfb8e06033f1b1e92df454da6d0d26e1474 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 13 Feb 2022 19:10:06 -0800 Subject: [PATCH 01/20] Fix Python 3.7.1 and run more versions in CI (#1076) --- .github/workflows/ci.yml | 6 ++++- typing_extensions/CHANGELOG | 5 ++++ .../src/test_typing_extensions.py | 8 +++++- typing_extensions/src/typing_extensions.py | 26 +++++++++---------- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a89af4d5..f0e6d67b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,11 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11-dev"] + # We try to test on the earliest available bugfix release of each + # Python version, because typing sometimes changed between bugfix releases. + # For available versions, see: + # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json + python-version: ["3.6", "3.6.7", "3.7", "3.7.1", "3.8", "3.8.0", "3.9", "3.9.0", "3.10", "3.10.0", "3.11-dev"] runs-on: ubuntu-latest diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 831f6d826..d27af2913 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,3 +1,8 @@ +# Release 4.1.1 (February 13, 2022) + +- Fix importing `typing_extensions` on Python 3.7.0 and 3.7.1. Original + patch by Nikita Sobolev (@sobolevn). + # Release 4.1.0 (February 12, 2022) - Runtime support for PEP 646, adding `typing_extensions.TypeVarTuple` diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index 62a9fd7ab..a66a2f291 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -131,7 +131,13 @@ def some(arg: NoReturn) -> NoReturn: ... def some_str(arg: 'NoReturn') -> 'typing.NoReturn': ... expected = {'arg': NoReturn, 'return': NoReturn} - for target in [some, some_str]: + targets = [some] + + # On 3.7.0 and 3.7.1, https://github.com/python/cpython/pull/10772 + # wasn't applied yet and NoReturn fails _type_check. + if not ((3, 7, 0) <= sys.version_info < (3, 7, 2)): + targets.append(some_str) + for target in targets: with self.subTest(target=target): self.assertEqual(gth(target), expected) diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 144bca76e..194731cd3 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -140,7 +140,7 @@ def _collect_type_vars(types, typevar_types=None): if ( isinstance(t, typevar_types) and t not in tvars and - not isinstance(t, _UnpackAlias) + not _is_unpack(t) ): tvars.append(t) if _should_collect_from_parameters(t): @@ -148,18 +148,6 @@ def _collect_type_vars(types, typevar_types=None): return tuple(tvars) -# We have to do some monkey patching to deal with the dual nature of -# Unpack/TypeVarTuple: -# - We want Unpack to be a kind of TypeVar so it gets accepted in -# Generic[Unpack[Ts]] -# - We want it to *not* be treated as a TypeVar for the purposes of -# counting generic parameters, so that when we subscript a generic, -# the runtime doesn't try to substitute the Unpack with the subscripted type. -if not hasattr(typing, "TypeVarTuple"): - typing._collect_type_vars = _collect_type_vars - typing._check_generic = _check_generic - - # 3.6.2+ if hasattr(typing, 'NoReturn'): NoReturn = typing.NoReturn @@ -2906,3 +2894,15 @@ def decorator(cls_or_fn): } return cls_or_fn return decorator + + +# We have to do some monkey patching to deal with the dual nature of +# Unpack/TypeVarTuple: +# - We want Unpack to be a kind of TypeVar so it gets accepted in +# Generic[Unpack[Ts]] +# - We want it to *not* be treated as a TypeVar for the purposes of +# counting generic parameters, so that when we subscript a generic, +# the runtime doesn't try to substitute the Unpack with the subscripted type. +if not hasattr(typing, "TypeVarTuple"): + typing._collect_type_vars = _collect_type_vars + typing._check_generic = _check_generic From b5dadf11d324e6302d1ca8cbfbdc8c8112189b14 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 13 Feb 2022 19:15:26 -0800 Subject: [PATCH 02/20] Prepare release 4.1.1 (#1077) --- typing_extensions/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml index 38678866d..354c20682 100644 --- a/typing_extensions/pyproject.toml +++ b/typing_extensions/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "flit_core.buildapi" # Project metadata [project] name = "typing_extensions" -version = "4.1.0" +version = "4.1.1" description = "Backported and Experimental Type Hints for Python 3.6+" readme.text = """\ Typing Extensions -- Backported and Experimental Type Hints for Python From 1cb25b85dc8ddfb818923531bbaf1d1978715daa Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 14 Feb 2022 10:07:31 -0800 Subject: [PATCH 03/20] Improve release instructions (#1078) This is what I've actually been doing, but better to document it. --- CONTRIBUTING.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f76b51df8..63c5034f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,11 +30,17 @@ backwards-incompatible changes. - Ensure that GitHub Actions reports no errors. -- Update the version number in `pyproject.toml`. +- Update the version number in `typing_extensions/pyproject.toml` and in + `typing_extensions/CHANGELOG`. + +- Make sure your environment is up to date + + - `git checkout master` + - `git pull` + - `python -m pip install --upgrade build twine` - Build the source and wheel distributions: - - `python -m pip install --upgrade build` - `cd typing_extensions` - `rm -rf dist/` - `python -m build .` @@ -42,4 +48,8 @@ backwards-incompatible changes. - Install the built distributions locally and test (if you were using `tox`, you already tested the source distribution). -- Make sure twine is up to date, then run `twine upload dist/*`. +- Run `twine upload dist/*`. + +- Tag the release. The tag should be just the version number, e.g. `4.1.1`. + +- `git push --tags` From 02d79c28bcfc1971b7f540de5779860389b5f21c Mon Sep 17 00:00:00 2001 From: Stephen Rosen Date: Mon, 21 Feb 2022 13:22:23 -0500 Subject: [PATCH 04/20] Document how to test type annotations (#1071) This is a slightly broader refactor than just testing. It also consolidates information about checking type coverage/completeness. This originates from a thread on the mypy tracker [1]. In terms of presentation, the goal is to present guidance and offer up several options, many of which were proposed by contributors to that thread. Several of the goals from that thread were not achieved here, including documentation covering stubgen and monkeytype, stubtest, and potentially more. However, the document is written such that it should be possible to add a section on "Generating Annotations" as was planned earlier. [1]: https://github.com/python/mypy/issues/11506 --- docs/source/libraries.rst | 27 ----- docs/source/quality.rst | 200 ++++++++++++++++++++++++++++++++++++++ docs/source/reference.rst | 1 + 3 files changed, 201 insertions(+), 27 deletions(-) create mode 100644 docs/source/quality.rst diff --git a/docs/source/libraries.rst b/docs/source/libraries.rst index 371c1f74f..96e5370e0 100644 --- a/docs/source/libraries.rst +++ b/docs/source/libraries.rst @@ -282,33 +282,6 @@ Examples of known and unknown types class DictSubclass(dict): pass -Verifying Type Completeness -=========================== - -Some type checkers provide features that allows library authors to verify type -completeness for a “py.typed” package. E.g. Pyright has a special -`command line flag `_ for this. - -Improving Type Completeness -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Here are some tips for increasing the type completeness score for your -library: - -- If your package includes tests or sample code, consider removing them - from the distribution. If there is good reason to include them, - consider placing them in a directory that begins with an underscore - so they are not considered part of your library’s interface. -- If your package includes submodules that are meant to be - implementation details, rename those files to begin with an - underscore. -- If a symbol is not intended to be part of the library’s interface and - is considered an implementation detail, rename it such that it begins - with an underscore. It will then be considered private and excluded - from the type completeness check. -- If your package exposes types from other libraries, work with the - maintainers of these other libraries to achieve type completeness. - Best Practices for Inlined Types ================================ diff --git a/docs/source/quality.rst b/docs/source/quality.rst new file mode 100644 index 000000000..328550c90 --- /dev/null +++ b/docs/source/quality.rst @@ -0,0 +1,200 @@ +.. _tools: + +******************************************** +Testing and Ensuring Type Annotation Quality +******************************************** + +Testing Annotation Accuracy +=========================== + +When creating a package with type annotations, authors may want to validate +that the annotations they publish meet their expectations. +This is especially important for library authors, for whom the published +annotations are part of the public interface to their package. + +There are several approaches to this problem, and this document will show +a few of them. + +.. note:: + + For simplicity, we will assume that type-checking is done with ``mypy``. + Many of these strategies can be applied to other type-checkers as well. + +Testing Using ``mypy --warn-unused-ignores`` +-------------------------------------------- + +Clever use of ``--warn-unused-ignores`` can be used to check that certain +expressions are or are not well-typed. + +The idea is to write normal python files which contain valid expressions along +with invalid expressions annotated with ``type: ignore`` comments. When +``mypy --warn-unused-ignores`` is run on these files, it should pass. +A directory of test files, ``typing_tests/``, can be maintained. + +This strategy does not offer strong guarantees about the types under test, but +it requires no additional tooling. + +If the following file is under test + +.. code-block:: python + + # foo.py + def bar(x: int) -> str: + return str(x) + +Then the following file tests ``foo.py``: + +.. code-block:: python + + bar(42) + bar("42") # type: ignore [arg-type] + bar(y=42) # type: ignore [call-arg] + r1: str = bar(42) + r2: int = bar(42) # type: ignore [assignment] + +Checking ``reveal_type`` output from ``mypy.api.run`` +----------------------------------------------------- + +``mypy`` provides a subpackage named ``api`` for invoking ``mypy`` from a +python process. In combination with ``reveal_type``, this can be used to write +a function which gets the ``reveal_type`` output from an expression. Once +that's obtained, tests can assert strings and regular expression matches +against it. + +This approach requires writing a set of helpers to provide a good testing +experience, and it runs mypy once per test case (which can be slow). +However, it builds only on ``mypy`` and the test framework of your choice. + +The following example could be integrated into a testsuite written in +any framework: + +.. code-block:: python + + import re + from mypy import api + + def get_reveal_type_output(filename): + result = api.run([filename]) + stdout = result[0] + match = re.search(r'note: Revealed type is "([^"]+)"', stdout) + assert match is not None + return match.group(1) + + +For example, we can use the above to provide a ``run_reveal_type`` pytest +fixture which generates a temporary file and uses it as the input to +``get_reveal_type_output``: + +.. code-block:: python + + import os + import pytest + + @pytest.fixture + def _in_tmp_path(tmp_path): + cur = os.getcwd() + try: + os.chdir(tmp_path) + yield + finally: + os.chdir(cur) + + @pytest.fixture + def run_reveal_type(tmp_path, _in_tmp_path): + content_path = tmp_path / "reveal_type_test.py" + + def func(code_snippet, *, preamble = ""): + content_path.write_text(preamble + f"reveal_type({code_snippet})") + return get_reveal_type_output("reveal_type_test.py") + + return func + + +For more details, see `the documentation on mypy.api +`_. + +pytest-mypy-plugins +------------------- + +`pytest-mypy-plugins `_ is +a plugin for ``pytest`` which defines typing test cases as YAML data. +The test cases are run through ``mypy`` and the output of ``reveal_type`` can +be asserted. + +This project supports complex typing arrangements like ``pytest`` parametrized +tests and per-test ``mypy`` configuration. It requires that you are using +``pytest`` to run your tests, and runs ``mypy`` in a subprocess per test case. + +This is an example of a parametrized test with ``pytest-mypy-plugins``: + +.. code-block:: yaml + + - case: with_params + parametrized: + - val: 1 + rt: builtins.int + - val: 1.0 + rt: builtins.float + main: | + reveal_type({[ val }}) # N: Revealed type is '{{ rt }}' + +Improving Type Completeness +=========================== + +One of the goals of many libraries is to ensure that they are "fully type +annotated", meaning that they provide complete and accurate type annotations +for all functions, classes, and objects. Having full annotations is referred to +as "type completeness" or "type coverage". + +Here are some tips for increasing the type completeness score for your +library: + +- Make type completeness an output of your testing process. Several type + checkers have options for generating useful output, warnings, or even + reports. +- If your package includes tests or sample code, consider removing them + from the distribution. If there is good reason to include them, + consider placing them in a directory that begins with an underscore + so they are not considered part of your library’s interface. +- If your package includes submodules that are meant to be + implementation details, rename those files to begin with an + underscore. +- If a symbol is not intended to be part of the library’s interface and + is considered an implementation detail, rename it such that it begins + with an underscore. It will then be considered private and excluded + from the type completeness check. +- If your package exposes types from other libraries, work with the + maintainers of these other libraries to achieve type completeness. + +.. warning:: + + The ways in which different type checkers evaluate and help you achieve + better type coverage may differ. Some of the above recommendations may or + may not be helpful to you, depending on which type checking tools you use. + +``mypy`` disallow options +------------------------- + +``mypy`` offers several options which can detect untyped code. +More details can be found in `the mypy documentation on these options +`_. + +Some basic usages which make ``mypy`` error on untyped data are:: + + mypy --disallow-untyped-defs + mypy --disallow-incomplete-defs + +``pyright`` type verification +----------------------------- + +pyright has a special command line flag, ``--verifytypes``, for verifying +type completeness. You can learn more about it from +`the pyright documentation on verifying type completeness +`_. + +``mypy`` reports +---------------- + +``mypy`` offers several options options for generating reports on its analysis. +See `the mypy documentation on report generation +`_ for details. diff --git a/docs/source/reference.rst b/docs/source/reference.rst index d18df3aa1..c7fc57ee6 100644 --- a/docs/source/reference.rst +++ b/docs/source/reference.rst @@ -7,6 +7,7 @@ Type System Reference :caption: Contents: stubs + quality typing Module Documentation .. The following pages are desired in a new TOC which will cover multiple From 95c9c2bab2034f0359e8457360d93a288a65e55d Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 3 Mar 2022 06:32:01 +0100 Subject: [PATCH 05/20] Improve distribution package (#1097) --- typing_extensions/MANIFEST.in | 3 --- typing_extensions/pyproject.toml | 23 +++++++++-------------- 2 files changed, 9 insertions(+), 17 deletions(-) delete mode 100644 typing_extensions/MANIFEST.in diff --git a/typing_extensions/MANIFEST.in b/typing_extensions/MANIFEST.in deleted file mode 100644 index e3e1b70c1..000000000 --- a/typing_extensions/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -include CHANGELOG LICENSE README.rst -include src/typing_extensions.py -include src/test_typing_extensions.py diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml index 354c20682..fd6e1859a 100644 --- a/typing_extensions/pyproject.toml +++ b/typing_extensions/pyproject.toml @@ -8,20 +8,7 @@ build-backend = "flit_core.buildapi" name = "typing_extensions" version = "4.1.1" description = "Backported and Experimental Type Hints for Python 3.6+" -readme.text = """\ -Typing Extensions -- Backported and Experimental Type Hints for Python - -The ``typing`` module was added to the standard library in Python 3.5, but -many new features have been added to the module since then. -This means users of older Python versions who are unable to upgrade will not be -able to take advantage of new types added to the ``typing`` module, such as -``typing.Protocol`` or ``typing.TypedDict``. - -The ``typing_extensions`` module contains backports of these changes. -Experimental types that may eventually be added to the ``typing`` -module are also included in ``typing_extensions``. -""" -readme.content-type = "text/x-rst" +readme = "README.rst" requires-python = ">=3.6" urls.Home = "https://github.com/python/typing/blob/master/typing_extensions/README.rst" license.file = "LICENSE" @@ -61,3 +48,11 @@ classifiers = [ [[project.authors]] name = "Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Michael Lee" email = "levkivskyi@gmail.com" + +[tool.flit.sdist] +include = [ + "CHANGELOG", + "README.rst", + "*/test*.py" +] +exclude = [] From 0ca0e458324ed27f96d81265faf94595f57d84af Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 17 Mar 2022 01:09:58 -0700 Subject: [PATCH 06/20] typing-extensions: Drop Python 3.6 (#1104) --- .github/workflows/ci.yml | 2 +- CONTRIBUTING.md | 2 +- typing_extensions/CHANGELOG | 4 + typing_extensions/README.rst | 9 +- typing_extensions/pyproject.toml | 2 +- .../src/test_typing_extensions.py | 90 +- typing_extensions/src/typing_extensions.py | 1285 ++--------------- 7 files changed, 148 insertions(+), 1246 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0e6d67b3..302b2cae6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: # Python version, because typing sometimes changed between bugfix releases. # For available versions, see: # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json - python-version: ["3.6", "3.6.7", "3.7", "3.7.1", "3.8", "3.8.0", "3.9", "3.9.0", "3.10", "3.10.0", "3.11-dev"] + python-version: ["3.7", "3.7.1", "3.8", "3.8.0", "3.9", "3.9.0", "3.10", "3.10.0", "3.11-dev"] runs-on: ubuntu-latest diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 63c5034f4..095e8262b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,7 @@ standard library, so that users can experiment with them before they are added t standard library. Such features should ideally already be specified in a PEP or draft PEP. -`typing_extensions` supports Python versions 3.6 and up. +`typing_extensions` supports Python versions 3.7 and up. # Versioning scheme diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index d27af2913..f098464eb 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,3 +1,7 @@ +# Unreleased + +- Drop support for Python 3.6. Original patch by Adam Turner (@AA-Turner). + # Release 4.1.1 (February 13, 2022) - Fix importing `typing_extensions` on Python 3.7.0 and 3.7.1. Original diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 5db69d150..bb8906895 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -30,6 +30,10 @@ Therefore, it's safe to depend on ``typing_extensions`` like this: ``typing_extensions >=x.y, <(x+1)``, where ``x.y`` is the first version that includes all features you need. +``typing_extensions`` supports Python versions 3.7 and higher. In the future, +support for older Python versions will be dropped some time after that version +reaches end of life. + Included items ============== @@ -101,7 +105,7 @@ This module currently contains the following: - ``Text`` - ``Type`` - ``TYPE_CHECKING`` - - ``get_type_hints`` (``typing_extensions`` provides this function only in Python 3.7+) + - ``get_type_hints`` Other Notes and Limitations =========================== @@ -131,9 +135,6 @@ versions of the typing module: - ``ParamSpec`` and ``Concatenate`` will not work with ``get_args`` and ``get_origin``. Certain PEP 612 special cases in user-defined ``Generic``\ s are also not available. -- ``Unpack`` from PEP 646 does not work properly with user-defined - ``Generic``\ s in Python 3.6: ``class X(Generic[Unpack[Ts]]):`` does - not work. These types are only guaranteed to work for static type checking. diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml index fd6e1859a..fbd018017 100644 --- a/typing_extensions/pyproject.toml +++ b/typing_extensions/pyproject.toml @@ -9,7 +9,7 @@ name = "typing_extensions" version = "4.1.1" description = "Backported and Experimental Type Hints for Python 3.6+" readme = "README.rst" -requires-python = ">=3.6" +requires-python = ">=3.7" urls.Home = "https://github.com/python/typing/blob/master/typing_extensions/README.rst" license.file = "LICENSE" keywords = [ diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index a66a2f291..20e35f431 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -23,32 +23,14 @@ from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict from typing_extensions import TypeVarTuple, Unpack, dataclass_transform, reveal_type, Never, assert_never, LiteralString -try: - from typing_extensions import get_type_hints -except ImportError: - from typing import get_type_hints - -PEP_560 = sys.version_info[:3] >= (3, 7, 0) - -OLD_GENERICS = False -try: - from typing import _type_vars, _next_in_mro, _type_check # noqa -except ImportError: - OLD_GENERICS = True +from typing_extensions import get_type_hints, get_origin, get_args # Flags used to mark tests that only apply after a specific # version of the typing module. -TYPING_3_6_1 = sys.version_info[:3] >= (3, 6, 1) TYPING_3_8_0 = sys.version_info[:3] >= (3, 8, 0) TYPING_3_10_0 = sys.version_info[:3] >= (3, 10, 0) TYPING_3_11_0 = sys.version_info[:3] >= (3, 11, 0) -# For typing versions where instantiating collection -# types are allowed. -# -# See https://github.com/python/typing/issues/367 -CAN_INSTANTIATE_COLLECTIONS = TYPING_3_6_1 - class BaseTestCase(TestCase): def assertIsSubclass(self, cls, class_or_tuple, msg=None): @@ -78,9 +60,7 @@ def test_equality(self): self.assertIs(self.bottom_type, self.bottom_type) self.assertNotEqual(self.bottom_type, None) - @skipUnless(PEP_560, "Python 3.7+ required") def test_get_origin(self): - from typing_extensions import get_origin self.assertIs(get_origin(self.bottom_type), None) def test_instance_type_error(self): @@ -621,11 +601,8 @@ def test_final_forward_ref(self): self.assertNotEqual(gth(Loop, globals())['attr'], Final) -@skipUnless(PEP_560, "Python 3.7+ required") class GetUtilitiesTestCase(TestCase): def test_get_origin(self): - from typing_extensions import get_origin - T = TypeVar('T') P = ParamSpec('P') Ts = TypeVarTuple('Ts') @@ -655,8 +632,6 @@ class C(Generic[T]): pass self.assertIs(get_origin(Unpack), None) def test_get_args(self): - from typing_extensions import get_args - T = TypeVar('T') Ts = TypeVarTuple('Ts') class C(Generic[T]): pass @@ -767,7 +742,6 @@ class MyDeque(typing_extensions.Deque[int]): ... def test_counter(self): self.assertIsSubclass(collections.Counter, typing_extensions.Counter) - @skipUnless(CAN_INSTANTIATE_COLLECTIONS, "Behavior added in typing 3.6.1") def test_defaultdict_instantiation(self): self.assertIs( type(typing_extensions.DefaultDict()), @@ -790,7 +764,6 @@ class MyDefDict(typing_extensions.DefaultDict[str, int]): self.assertIsSubclass(MyDefDict, collections.defaultdict) self.assertNotIsSubclass(collections.defaultdict, MyDefDict) - @skipUnless(CAN_INSTANTIATE_COLLECTIONS, "Behavior added in typing 3.6.1") def test_ordereddict_instantiation(self): self.assertIs( type(typing_extensions.OrderedDict()), @@ -844,10 +817,7 @@ def test_counter_instantiation(self): self.assertIs(type(typing_extensions.Counter[int]()), collections.Counter) class C(typing_extensions.Counter[T]): ... self.assertIs(type(C[int]()), C) - if not PEP_560: - self.assertEqual(C.__bases__, (typing_extensions.Counter,)) - else: - self.assertEqual(C.__bases__, (collections.Counter, typing.Generic)) + self.assertEqual(C.__bases__, (collections.Counter, typing.Generic)) def test_counter_subclass_instantiation(self): @@ -922,9 +892,8 @@ def manager(): cm = manager() self.assertNotIsInstance(cm, typing_extensions.AsyncContextManager) self.assertEqual(typing_extensions.AsyncContextManager[int].__args__, (int,)) - if TYPING_3_6_1: - with self.assertRaises(TypeError): - isinstance(42, typing_extensions.AsyncContextManager[int]) + with self.assertRaises(TypeError): + isinstance(42, typing_extensions.AsyncContextManager[int]) with self.assertRaises(TypeError): typing_extensions.AsyncContextManager[int, str] @@ -1189,10 +1158,6 @@ def x(self): ... self.assertIsSubclass(C, P) self.assertIsSubclass(C, PG) self.assertIsSubclass(BadP, PG) - if not PEP_560: - self.assertIsSubclass(PG[int], PG) - self.assertIsSubclass(BadPG[int], P) - self.assertIsSubclass(BadPG[T], PG) with self.assertRaises(TypeError): issubclass(C, PG[T]) with self.assertRaises(TypeError): @@ -1383,7 +1348,6 @@ class C: pass with self.assertRaises(TypeError): issubclass(C(), P) - @skipUnless(not OLD_GENERICS, "New style generics required") def test_defining_generic_protocols(self): T = TypeVar('T') S = TypeVar('S') @@ -1392,16 +1356,19 @@ class PR(Protocol[T, S]): def meth(self): pass class P(PR[int, T], Protocol[T]): y = 1 - self.assertIsSubclass(PR[int, T], PR) - self.assertIsSubclass(P[str], PR) with self.assertRaises(TypeError): - PR[int] + issubclass(PR[int, T], PR) with self.assertRaises(TypeError): - P[int, str] + issubclass(P[str], PR) with self.assertRaises(TypeError): - PR[int, 1] + PR[int] with self.assertRaises(TypeError): - PR[int, ClassVar] + P[int, str] + if not TYPING_3_10_0: + with self.assertRaises(TypeError): + PR[int, 1] + with self.assertRaises(TypeError): + PR[int, ClassVar] class C(PR[int, T]): pass self.assertIsInstance(C[str](), C) @@ -1413,11 +1380,8 @@ class PR(Protocol, Generic[T, S]): def meth(self): pass class P(PR[int, str], Protocol): y = 1 - if not PEP_560: + with self.assertRaises(TypeError): self.assertIsSubclass(PR[int, str], PR) - else: - with self.assertRaises(TypeError): - self.assertIsSubclass(PR[int, str], PR) self.assertIsSubclass(P, PR) with self.assertRaises(TypeError): PR[int] @@ -1448,7 +1412,6 @@ def __init__(self): self.test = 'OK' self.assertEqual(C[int]().test, 'OK') - @skipUnless(not OLD_GENERICS, "New style generics required") def test_protocols_bad_subscripts(self): T = TypeVar('T') S = TypeVar('S') @@ -1465,9 +1428,6 @@ def test_generic_protocols_repr(self): T = TypeVar('T') S = TypeVar('S') class P(Protocol[T, S]): pass - # After PEP 560 unsubscripted generics have a standard repr. - if not PEP_560: - self.assertTrue(repr(P).endswith('P')) self.assertTrue(repr(P[T, S]).endswith('P[~T, ~S]')) self.assertTrue(repr(P[int, str]).endswith('P[int, str]')) @@ -1480,13 +1440,10 @@ class P(Protocol[T, S]): pass self.assertEqual(P[T, T][Tuple[T, S]][int, str], P[Tuple[int, str], Tuple[int, str]]) - @skipUnless(not OLD_GENERICS, "New style generics required") def test_generic_protocols_special_from_generic(self): T = TypeVar('T') class P(Protocol[T]): pass self.assertEqual(P.__parameters__, (T,)) - self.assertIs(P.__args__, None) - self.assertIs(P.__origin__, None) self.assertEqual(P[int].__parameters__, ()) self.assertEqual(P[int].__args__, (int,)) self.assertIs(P[int].__origin__, P) @@ -1517,9 +1474,6 @@ def meth(self): self.assertEqual(typing_extensions._get_protocol_attrs(PR), {'x'}) self.assertEqual(frozenset(typing_extensions._get_protocol_attrs(PG)), frozenset({'x', 'meth'})) - if not PEP_560: - self.assertEqual(frozenset(typing_extensions._get_protocol_attrs(PG[int])), - frozenset({'x', 'meth'})) def test_no_runtime_deco_on_nominal(self): with self.assertRaises(TypeError): @@ -1747,7 +1701,6 @@ def test_optional_keys(self): assert Point2Dor3D.__required_keys__ == frozenset(['x', 'y']) assert Point2Dor3D.__optional_keys__ == frozenset(['z']) - @skipUnless(PEP_560, "runtime support for Required and NotRequired requires PEP 560") def test_required_notrequired_keys(self): assert NontotalMovie.__required_keys__ == frozenset({'title'}) assert NontotalMovie.__optional_keys__ == frozenset({'year'}) @@ -1821,16 +1774,14 @@ def test_flatten(self): A = Annotated[Annotated[int, 4], 5] self.assertEqual(A, Annotated[int, 4, 5]) self.assertEqual(A.__metadata__, (4, 5)) - if PEP_560: - self.assertEqual(A.__origin__, int) + self.assertEqual(A.__origin__, int) def test_specialize(self): L = Annotated[List[T], "my decoration"] LI = Annotated[List[int], "my decoration"] self.assertEqual(L[int], Annotated[List[int], "my decoration"]) self.assertEqual(L[int].__metadata__, ("my decoration",)) - if PEP_560: - self.assertEqual(L[int].__origin__, List[int]) + self.assertEqual(L[int].__origin__, List[int]) with self.assertRaises(TypeError): LI[int] with self.assertRaises(TypeError): @@ -1934,7 +1885,6 @@ def test_cannot_check_subclass(self): with self.assertRaises(TypeError): issubclass(int, Annotated[int, "positive"]) - @skipUnless(PEP_560, "pickle support was added with PEP 560") def test_pickle(self): samples = [typing.Any, typing.Union[int, str], typing.Optional[str], Tuple[int, ...], @@ -2000,7 +1950,6 @@ def test_annotated_in_other_types(self): self.assertEqual(X[int], List[Annotated[int, 5]]) -@skipUnless(PEP_560, "Python 3.7 required") class GetTypeHintsTests(BaseTestCase): def test_get_type_hints(self): def foobar(x: List['X']): ... @@ -2355,9 +2304,7 @@ def baz(self) -> "LiteralString": ... self.assertEqual(gth(Foo.bar), {'return': LiteralString}) self.assertEqual(gth(Foo.baz), {'return': LiteralString}) - @skipUnless(PEP_560, "Python 3.7+ required") def test_get_origin(self): - from typing_extensions import get_origin self.assertIsNone(get_origin(LiteralString)) def test_repr(self): @@ -2510,7 +2457,6 @@ def test_union(self): Union ) - @skipUnless(PEP_560, "Unimplemented for 3.6") def test_concatenation(self): Xs = TypeVarTuple('Xs') self.assertEqual(Tuple[int, Unpack[Xs]].__args__, (int, Unpack[Xs])) @@ -2523,7 +2469,6 @@ class C(Generic[Unpack[Xs]]): pass self.assertEqual(C[int, Unpack[Xs], str].__args__, (int, Unpack[Xs], str)) - @skipUnless(PEP_560, "Unimplemented for 3.6") def test_class(self): Ts = TypeVarTuple('Ts') @@ -2766,8 +2711,7 @@ def test_typing_extensions_includes_standard(self): self.assertIn("Concatenate", a) self.assertIn('Annotated', a) - if PEP_560: - self.assertIn('get_type_hints', a) + self.assertIn('get_type_hints', a) self.assertIn('Awaitable', a) self.assertIn('AsyncIterator', a) diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 194731cd3..5c43354ee 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -6,17 +6,6 @@ import types as _types import typing -# After PEP 560, internal typing API was substantially reworked. -# This is especially important for Protocol class which uses internal APIs -# quite extensively. -PEP_560 = sys.version_info[:3] >= (3, 7, 0) - -if PEP_560: - GenericMeta = type -else: - # 3.6 - from typing import GenericMeta, _type_vars # noqa - # Please keep __all__ alphabetized within each category. __all__ = [ @@ -56,6 +45,9 @@ 'assert_never', 'dataclass_transform', 'final', + 'get_args', + 'get_origin', + 'get_type_hints', 'IntVar', 'is_typeddict', 'Literal', @@ -75,21 +67,13 @@ 'NotRequired', ] -if PEP_560: - __all__.extend(["get_args", "get_origin", "get_type_hints"]) +# for backward compatibility +PEP_560 = True +GenericMeta = type # The functions below are modified copies of typing internal helpers. # They are needed by _ProtocolMeta and they provide support for PEP 646. - -def _no_slots_copy(dct): - dict_copy = dict(dct) - if '__slots__' in dict_copy: - for slot in dict_copy['__slots__']: - dict_copy.pop(slot, None) - return dict_copy - - _marker = object() @@ -148,32 +132,7 @@ def _collect_type_vars(types, typevar_types=None): return tuple(tvars) -# 3.6.2+ -if hasattr(typing, 'NoReturn'): - NoReturn = typing.NoReturn -# 3.6.0-3.6.1 -else: - class _NoReturn(typing._FinalTypingBase, _root=True): - """Special type indicating functions that never return. - Example:: - - from typing import NoReturn - - def stop() -> NoReturn: - raise Exception('no way') - - This type is invalid in other positions, e.g., ``List[NoReturn]`` - will fail in static type checkers. - """ - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("NoReturn cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("NoReturn cannot be used with issubclass().") - - NoReturn = _NoReturn(_root=True) +NoReturn = typing.NoReturn # Some unconstrained type variables. These are used by the container types. # (These are not for export.) @@ -190,7 +149,7 @@ def __subclasscheck__(self, cls): if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7): Final = typing.Final # 3.7 -elif sys.version_info[:2] >= (3, 7): +else: class _FinalForm(typing._SpecialForm, _root=True): def __repr__(self): @@ -215,61 +174,6 @@ class FastConnector(Connection): TIMEOUT = 1 # Error reported by type checker There is no runtime checking of these properties.""") -# 3.6 -else: - class _Final(typing._FinalTypingBase, _root=True): - """A special typing construct to indicate that a name - cannot be re-assigned or overridden in a subclass. - For example: - - MAX_SIZE: Final = 9000 - MAX_SIZE += 1 # Error reported by type checker - - class Connection: - TIMEOUT: Final[int] = 10 - class FastConnector(Connection): - TIMEOUT = 1 # Error reported by type checker - - There is no runtime checking of these properties. - """ - - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(typing._type_check(item, - f'{cls.__name__[1:]} accepts only single type.'), - _root=True) - raise TypeError(f'{cls.__name__[1:]} cannot be further subscripted') - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += f'[{typing._type_repr(self.__type__)}]' - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _Final): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - Final = _Final(_root=True) - if sys.version_info >= (3, 11): final = typing.final @@ -317,7 +221,7 @@ def IntVar(name): if hasattr(typing, 'Literal'): Literal = typing.Literal # 3.7: -elif sys.version_info[:2] >= (3, 7): +else: class _LiteralForm(typing._SpecialForm, _root=True): def __repr__(self): @@ -339,55 +243,6 @@ def __getitem__(self, parameters): Literal[...] cannot be subclassed. There is no runtime checking verifying that the parameter is actually a value instead of a type.""") -# 3.6: -else: - class _Literal(typing._FinalTypingBase, _root=True): - """A type that can be used to indicate to type checkers that the - corresponding value has a value literally equivalent to the - provided parameter. For example: - - var: Literal[4] = 4 - - The type checker understands that 'var' is literally equal to the - value 4 and no other value. - - Literal[...] cannot be subclassed. There is no runtime checking - verifying that the parameter is actually a value instead of a type. - """ - - __slots__ = ('__values__',) - - def __init__(self, values=None, **kwds): - self.__values__ = values - - def __getitem__(self, values): - cls = type(self) - if self.__values__ is None: - if not isinstance(values, tuple): - values = (values,) - return cls(values, _root=True) - raise TypeError(f'{cls.__name__[1:]} cannot be further subscripted') - - def _eval_type(self, globalns, localns): - return self - - def __repr__(self): - r = super().__repr__() - if self.__values__ is not None: - r += f'[{", ".join(map(typing._type_repr, self.__values__))}]' - return r - - def __hash__(self): - return hash((type(self).__name__, self.__values__)) - - def __eq__(self, other): - if not isinstance(other, _Literal): - return NotImplemented - if self.__values__ is not None: - return self.__values__ == other.__values__ - return self is other - - Literal = _Literal(_root=True) _overload_dummy = typing._overload_dummy # noqa @@ -401,154 +256,30 @@ def __eq__(self, other): # A few are simply re-exported for completeness. -class _ExtensionsGenericMeta(GenericMeta): - def __subclasscheck__(self, subclass): - """This mimics a more modern GenericMeta.__subclasscheck__() logic - (that does not have problems with recursion) to work around interactions - between collections, typing, and typing_extensions on older - versions of Python, see https://github.com/python/typing/issues/501. - """ - if self.__origin__ is not None: - if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: - raise TypeError("Parameterized generics cannot be used with class " - "or instance checks") - return False - if not self.__extra__: - return super().__subclasscheck__(subclass) - res = self.__extra__.__subclasshook__(subclass) - if res is not NotImplemented: - return res - if self.__extra__ in subclass.__mro__: - return True - for scls in self.__extra__.__subclasses__(): - if isinstance(scls, GenericMeta): - continue - if issubclass(subclass, scls): - return True - return False - - Awaitable = typing.Awaitable Coroutine = typing.Coroutine AsyncIterable = typing.AsyncIterable AsyncIterator = typing.AsyncIterator - -# 3.6.1+ -if hasattr(typing, 'Deque'): - Deque = typing.Deque -# 3.6.0 -else: - class Deque(collections.deque, typing.MutableSequence[T], - metaclass=_ExtensionsGenericMeta, - extra=collections.deque): - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Deque: - return collections.deque(*args, **kwds) - return typing._generic_new(collections.deque, cls, *args, **kwds) - +Deque = typing.Deque ContextManager = typing.ContextManager -# 3.6.2+ -if hasattr(typing, 'AsyncContextManager'): - AsyncContextManager = typing.AsyncContextManager -# 3.6.0-3.6.1 -else: - from _collections_abc import _check_methods as _check_methods_in_mro # noqa - - class AsyncContextManager(typing.Generic[T_co]): - __slots__ = () - - async def __aenter__(self): - return self - - @abc.abstractmethod - async def __aexit__(self, exc_type, exc_value, traceback): - return None - - @classmethod - def __subclasshook__(cls, C): - if cls is AsyncContextManager: - return _check_methods_in_mro(C, "__aenter__", "__aexit__") - return NotImplemented - +AsyncContextManager = typing.AsyncContextManager DefaultDict = typing.DefaultDict # 3.7.2+ if hasattr(typing, 'OrderedDict'): OrderedDict = typing.OrderedDict # 3.7.0-3.7.2 -elif (3, 7, 0) <= sys.version_info[:3] < (3, 7, 2): - OrderedDict = typing._alias(collections.OrderedDict, (KT, VT)) -# 3.6 -else: - class OrderedDict(collections.OrderedDict, typing.MutableMapping[KT, VT], - metaclass=_ExtensionsGenericMeta, - extra=collections.OrderedDict): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is OrderedDict: - return collections.OrderedDict(*args, **kwds) - return typing._generic_new(collections.OrderedDict, cls, *args, **kwds) - -# 3.6.2+ -if hasattr(typing, 'Counter'): - Counter = typing.Counter -# 3.6.0-3.6.1 -else: - class Counter(collections.Counter, - typing.Dict[T, int], - metaclass=_ExtensionsGenericMeta, extra=collections.Counter): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Counter: - return collections.Counter(*args, **kwds) - return typing._generic_new(collections.Counter, cls, *args, **kwds) - -# 3.6.1+ -if hasattr(typing, 'ChainMap'): - ChainMap = typing.ChainMap -elif hasattr(collections, 'ChainMap'): - class ChainMap(collections.ChainMap, typing.MutableMapping[KT, VT], - metaclass=_ExtensionsGenericMeta, - extra=collections.ChainMap): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is ChainMap: - return collections.ChainMap(*args, **kwds) - return typing._generic_new(collections.ChainMap, cls, *args, **kwds) - -# 3.6.1+ -if hasattr(typing, 'AsyncGenerator'): - AsyncGenerator = typing.AsyncGenerator -# 3.6.0 else: - class AsyncGenerator(AsyncIterator[T_co], typing.Generic[T_co, T_contra], - metaclass=_ExtensionsGenericMeta, - extra=collections.abc.AsyncGenerator): - __slots__ = () + OrderedDict = typing._alias(collections.OrderedDict, (KT, VT)) +Counter = typing.Counter +ChainMap = typing.ChainMap +AsyncGenerator = typing.AsyncGenerator NewType = typing.NewType Text = typing.Text TYPE_CHECKING = typing.TYPE_CHECKING -def _gorg(cls): - """This function exists for compatibility with old typing versions.""" - assert isinstance(cls, GenericMeta) - if hasattr(cls, '_gorg'): - return cls._gorg - while cls.__origin__ is not None: - cls = cls.__origin__ - return cls - - _PROTO_WHITELIST = ['Callable', 'Awaitable', 'Iterable', 'Iterator', 'AsyncIterable', 'AsyncIterator', 'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', @@ -582,7 +313,7 @@ def _is_callable_members_only(cls): if hasattr(typing, 'Protocol'): Protocol = typing.Protocol # 3.7 -elif PEP_560: +else: def _no_init(self, *args, **kwargs): if type(self)._is_protocol: @@ -764,250 +495,12 @@ def _proto_hook(other): raise TypeError('Protocols can only inherit from other' f' protocols, got {repr(base)}') cls.__init__ = _no_init -# 3.6 -else: - from typing import _next_in_mro, _type_check # noqa - - def _no_init(self, *args, **kwargs): - if type(self)._is_protocol: - raise TypeError('Protocols cannot be instantiated') - - class _ProtocolMeta(GenericMeta): - """Internal metaclass for Protocol. - - This exists so Protocol classes can be generic without deriving - from Generic. - """ - def __new__(cls, name, bases, namespace, - tvars=None, args=None, origin=None, extra=None, orig_bases=None): - # This is just a version copied from GenericMeta.__new__ that - # includes "Protocol" special treatment. (Comments removed for brevity.) - assert extra is None # Protocols should not have extra - if tvars is not None: - assert origin is not None - assert all(isinstance(t, typing.TypeVar) for t in tvars), tvars - else: - tvars = _type_vars(bases) - gvars = None - for base in bases: - if base is typing.Generic: - raise TypeError("Cannot inherit from plain Generic") - if (isinstance(base, GenericMeta) and - base.__origin__ in (typing.Generic, Protocol)): - if gvars is not None: - raise TypeError( - "Cannot inherit from Generic[...] or" - " Protocol[...] multiple times.") - gvars = base.__parameters__ - if gvars is None: - gvars = tvars - else: - tvarset = set(tvars) - gvarset = set(gvars) - if not tvarset <= gvarset: - s_vars = ", ".join(str(t) for t in tvars if t not in gvarset) - s_args = ", ".join(str(g) for g in gvars) - cls_name = "Generic" if any(b.__origin__ is typing.Generic - for b in bases) else "Protocol" - raise TypeError(f"Some type variables ({s_vars}) are" - f" not listed in {cls_name}[{s_args}]") - tvars = gvars - - initial_bases = bases - if (extra is not None and type(extra) is abc.ABCMeta and - extra not in bases): - bases = (extra,) + bases - bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b - for b in bases) - if any(isinstance(b, GenericMeta) and b is not typing.Generic for b in bases): - bases = tuple(b for b in bases if b is not typing.Generic) - namespace.update({'__origin__': origin, '__extra__': extra}) - self = super(GenericMeta, cls).__new__(cls, name, bases, namespace, - _root=True) - super(GenericMeta, self).__setattr__('_gorg', - self if not origin else - _gorg(origin)) - self.__parameters__ = tvars - self.__args__ = tuple(... if a is typing._TypingEllipsis else - () if a is typing._TypingEmpty else - a for a in args) if args else None - self.__next_in_mro__ = _next_in_mro(self) - if orig_bases is None: - self.__orig_bases__ = initial_bases - elif origin is not None: - self._abc_registry = origin._abc_registry - self._abc_cache = origin._abc_cache - if hasattr(self, '_subs_tree'): - self.__tree_hash__ = (hash(self._subs_tree()) if origin else - super(GenericMeta, self).__hash__()) - return self - - def __init__(cls, *args, **kwargs): - super().__init__(*args, **kwargs) - if not cls.__dict__.get('_is_protocol', None): - cls._is_protocol = any(b is Protocol or - isinstance(b, _ProtocolMeta) and - b.__origin__ is Protocol - for b in cls.__bases__) - if cls._is_protocol: - for base in cls.__mro__[1:]: - if not (base in (object, typing.Generic) or - base.__module__ == 'collections.abc' and - base.__name__ in _PROTO_WHITELIST or - isinstance(base, typing.TypingMeta) and base._is_protocol or - isinstance(base, GenericMeta) and - base.__origin__ is typing.Generic): - raise TypeError(f'Protocols can only inherit from other' - f' protocols, got {repr(base)}') - - cls.__init__ = _no_init - - def _proto_hook(other): - if not cls.__dict__.get('_is_protocol', None): - return NotImplemented - if not isinstance(other, type): - # Same error as for issubclass(1, int) - raise TypeError('issubclass() arg 1 must be a class') - for attr in _get_protocol_attrs(cls): - for base in other.__mro__: - if attr in base.__dict__: - if base.__dict__[attr] is None: - return NotImplemented - break - annotations = getattr(base, '__annotations__', {}) - if (isinstance(annotations, typing.Mapping) and - attr in annotations and - isinstance(other, _ProtocolMeta) and - other._is_protocol): - break - else: - return NotImplemented - return True - if '__subclasshook__' not in cls.__dict__: - cls.__subclasshook__ = _proto_hook - - def __instancecheck__(self, instance): - # We need this method for situations where attributes are - # assigned in __init__. - if ((not getattr(self, '_is_protocol', False) or - _is_callable_members_only(self)) and - issubclass(instance.__class__, self)): - return True - if self._is_protocol: - if all(hasattr(instance, attr) and - (not callable(getattr(self, attr, None)) or - getattr(instance, attr) is not None) - for attr in _get_protocol_attrs(self)): - return True - return super(GenericMeta, self).__instancecheck__(instance) - - def __subclasscheck__(self, cls): - if self.__origin__ is not None: - if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: - raise TypeError("Parameterized generics cannot be used with class " - "or instance checks") - return False - if (self.__dict__.get('_is_protocol', None) and - not self.__dict__.get('_is_runtime_protocol', None)): - if sys._getframe(1).f_globals['__name__'] in ['abc', - 'functools', - 'typing']: - return False - raise TypeError("Instance and class checks can only be used with" - " @runtime protocols") - if (self.__dict__.get('_is_runtime_protocol', None) and - not _is_callable_members_only(self)): - if sys._getframe(1).f_globals['__name__'] in ['abc', - 'functools', - 'typing']: - return super(GenericMeta, self).__subclasscheck__(cls) - raise TypeError("Protocols with non-method members" - " don't support issubclass()") - return super(GenericMeta, self).__subclasscheck__(cls) - - @typing._tp_cache - def __getitem__(self, params): - # We also need to copy this from GenericMeta.__getitem__ to get - # special treatment of "Protocol". (Comments removed for brevity.) - if not isinstance(params, tuple): - params = (params,) - if not params and _gorg(self) is not typing.Tuple: - raise TypeError( - f"Parameter list to {self.__qualname__}[...] cannot be empty") - msg = "Parameters to generic types must be types." - params = tuple(_type_check(p, msg) for p in params) - if self in (typing.Generic, Protocol): - if not all(isinstance(p, typing.TypeVar) for p in params): - raise TypeError( - f"Parameters to {repr(self)}[...] must all be type variables") - if len(set(params)) != len(params): - raise TypeError( - f"Parameters to {repr(self)}[...] must all be unique") - tvars = params - args = params - elif self in (typing.Tuple, typing.Callable): - tvars = _type_vars(params) - args = params - elif self.__origin__ in (typing.Generic, Protocol): - raise TypeError(f"Cannot subscript already-subscripted {repr(self)}") - else: - _check_generic(self, params, len(self.__parameters__)) - tvars = _type_vars(params) - args = params - - prepend = (self,) if self.__origin__ is None else () - return self.__class__(self.__name__, - prepend + self.__bases__, - _no_slots_copy(self.__dict__), - tvars=tvars, - args=args, - origin=self, - extra=self.__extra__, - orig_bases=self.__orig_bases__) - - class Protocol(metaclass=_ProtocolMeta): - """Base class for protocol classes. Protocol classes are defined as:: - - class Proto(Protocol): - def meth(self) -> int: - ... - - Such classes are primarily used with static type checkers that recognize - structural subtyping (static duck-typing), for example:: - - class C: - def meth(self) -> int: - return 0 - - def func(x: Proto) -> int: - return x.meth() - - func(C()) # Passes static type check - - See PEP 544 for details. Protocol classes decorated with - @typing_extensions.runtime act as simple-minded runtime protocol that checks - only the presence of given attributes, ignoring their type signatures. - - Protocol classes can be generic, they are defined as:: - - class GenProto(Protocol[T]): - def meth(self) -> T: - ... - """ - __slots__ = () - _is_protocol = True - - def __new__(cls, *args, **kwds): - if _gorg(cls) is Protocol: - raise TypeError("Type Protocol cannot be instantiated; " - "it can be used only as a base class") - return typing._generic_new(cls.__next_in_mro__, cls, *args, **kwds) # 3.8+ if hasattr(typing, 'runtime_checkable'): runtime_checkable = typing.runtime_checkable -# 3.6-3.7 +# 3.7 else: def runtime_checkable(cls): """Mark a protocol class as a runtime protocol, so that it @@ -1031,7 +524,7 @@ def runtime_checkable(cls): # 3.8+ if hasattr(typing, 'SupportsIndex'): SupportsIndex = typing.SupportsIndex -# 3.6-3.7 +# 3.7 else: @runtime_checkable class SupportsIndex(Protocol): @@ -1148,29 +641,22 @@ def __new__(cls, name, bases, ns, total=True): optional_keys.update(base.__dict__.get('__optional_keys__', ())) annotations.update(own_annotations) - if PEP_560: - for annotation_key, annotation_type in own_annotations.items(): - annotation_origin = get_origin(annotation_type) - if annotation_origin is Annotated: - annotation_args = get_args(annotation_type) - if annotation_args: - annotation_type = annotation_args[0] - annotation_origin = get_origin(annotation_type) - - if annotation_origin is Required: - required_keys.add(annotation_key) - elif annotation_origin is NotRequired: - optional_keys.add(annotation_key) - elif total: - required_keys.add(annotation_key) - else: - optional_keys.add(annotation_key) - else: - own_annotation_keys = set(own_annotations.keys()) - if total: - required_keys.update(own_annotation_keys) + for annotation_key, annotation_type in own_annotations.items(): + annotation_origin = get_origin(annotation_type) + if annotation_origin is Annotated: + annotation_args = get_args(annotation_type) + if annotation_args: + annotation_type = annotation_args[0] + annotation_origin = get_origin(annotation_type) + + if annotation_origin is Required: + required_keys.add(annotation_key) + elif annotation_origin is NotRequired: + optional_keys.add(annotation_key) + elif total: + required_keys.add(annotation_key) else: - optional_keys.update(own_annotation_keys) + optional_keys.add(annotation_key) tp_dict.__annotations__ = annotations tp_dict.__required_keys__ = frozenset(required_keys) @@ -1233,7 +719,7 @@ class Film(TypedDict): if hasattr(typing, "Required"): get_type_hints = typing.get_type_hints -elif PEP_560: +else: import functools import types @@ -1312,7 +798,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False): # to work. _AnnotatedAlias = typing._AnnotatedAlias # 3.7-3.8 -elif PEP_560: +else: class _AnnotatedAlias(typing._GenericAlias, _root=True): """Runtime representation of an annotated type. @@ -1409,198 +895,52 @@ def __init_subclass__(cls, *args, **kwargs): raise TypeError( f"Cannot subclass {cls.__module__}.Annotated" ) -# 3.6 -else: - - def _is_dunder(name): - """Returns True if name is a __dunder_variable_name__.""" - return len(name) > 4 and name.startswith('__') and name.endswith('__') - # Prior to Python 3.7 types did not have `copy_with`. A lot of the equality - # checks, argument expansion etc. are done on the _subs_tre. As a result we - # can't provide a get_type_hints function that strips out annotations. - - class AnnotatedMeta(typing.GenericMeta): - """Metaclass for Annotated""" +# Python 3.8 has get_origin() and get_args() but those implementations aren't +# Annotated-aware, so we can't use those. Python 3.9's versions don't support +# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do. +if sys.version_info[:2] >= (3, 10): + get_origin = typing.get_origin + get_args = typing.get_args +# 3.7-3.9 +else: + try: + # 3.9+ + from typing import _BaseGenericAlias + except ImportError: + _BaseGenericAlias = typing._GenericAlias + try: + # 3.9+ + from typing import GenericAlias + except ImportError: + GenericAlias = typing._GenericAlias - def __new__(cls, name, bases, namespace, **kwargs): - if any(b is not object for b in bases): - raise TypeError("Cannot subclass " + str(Annotated)) - return super().__new__(cls, name, bases, namespace, **kwargs) + def get_origin(tp): + """Get the unsubscripted version of a type. - @property - def __metadata__(self): - return self._subs_tree()[2] + This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar + and Annotated. Return None for unsupported types. Examples:: - def _tree_repr(self, tree): - cls, origin, metadata = tree - if not isinstance(origin, tuple): - tp_repr = typing._type_repr(origin) - else: - tp_repr = origin[0]._tree_repr(origin) - metadata_reprs = ", ".join(repr(arg) for arg in metadata) - return f'{cls}[{tp_repr}, {metadata_reprs}]' - - def _subs_tree(self, tvars=None, args=None): # noqa - if self is Annotated: - return Annotated - res = super()._subs_tree(tvars=tvars, args=args) - # Flatten nested Annotated - if isinstance(res[1], tuple) and res[1][0] is Annotated: - sub_tp = res[1][1] - sub_annot = res[1][2] - return (Annotated, sub_tp, sub_annot + res[2]) - return res + get_origin(Literal[42]) is Literal + get_origin(int) is None + get_origin(ClassVar[int]) is ClassVar + get_origin(Generic) is Generic + get_origin(Generic[T]) is Generic + get_origin(Union[T, int]) is Union + get_origin(List[Tuple[T, T]][int]) == list + get_origin(P.args) is P + """ + if isinstance(tp, _AnnotatedAlias): + return Annotated + if isinstance(tp, (typing._GenericAlias, GenericAlias, _BaseGenericAlias, + ParamSpecArgs, ParamSpecKwargs)): + return tp.__origin__ + if tp is typing.Generic: + return typing.Generic + return None - def _get_cons(self): - """Return the class used to create instance of this type.""" - if self.__origin__ is None: - raise TypeError("Cannot get the underlying type of a " - "non-specialized Annotated type.") - tree = self._subs_tree() - while isinstance(tree, tuple) and tree[0] is Annotated: - tree = tree[1] - if isinstance(tree, tuple): - return tree[0] - else: - return tree - - @typing._tp_cache - def __getitem__(self, params): - if not isinstance(params, tuple): - params = (params,) - if self.__origin__ is not None: # specializing an instantiated type - return super().__getitem__(params) - elif not isinstance(params, tuple) or len(params) < 2: - raise TypeError("Annotated[...] should be instantiated " - "with at least two arguments (a type and an " - "annotation).") - else: - if ( - isinstance(params[0], typing._TypingBase) and - type(params[0]).__name__ == "_ClassVar" - ): - tp = params[0] - else: - msg = "Annotated[t, ...]: t must be a type." - tp = typing._type_check(params[0], msg) - metadata = tuple(params[1:]) - return self.__class__( - self.__name__, - self.__bases__, - _no_slots_copy(self.__dict__), - tvars=_type_vars((tp,)), - # Metadata is a tuple so it won't be touched by _replace_args et al. - args=(tp, metadata), - origin=self, - ) - - def __call__(self, *args, **kwargs): - cons = self._get_cons() - result = cons(*args, **kwargs) - try: - result.__orig_class__ = self - except AttributeError: - pass - return result - - def __getattr__(self, attr): - # For simplicity we just don't relay all dunder names - if self.__origin__ is not None and not _is_dunder(attr): - return getattr(self._get_cons(), attr) - raise AttributeError(attr) - - def __setattr__(self, attr, value): - if _is_dunder(attr) or attr.startswith('_abc_'): - super().__setattr__(attr, value) - elif self.__origin__ is None: - raise AttributeError(attr) - else: - setattr(self._get_cons(), attr, value) - - def __instancecheck__(self, obj): - raise TypeError("Annotated cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Annotated cannot be used with issubclass().") - - class Annotated(metaclass=AnnotatedMeta): - """Add context specific metadata to a type. - - Example: Annotated[int, runtime_check.Unsigned] indicates to the - hypothetical runtime_check module that this type is an unsigned int. - Every other consumer of this type can ignore this metadata and treat - this type as int. - - The first argument to Annotated must be a valid type, the remaining - arguments are kept as a tuple in the __metadata__ field. - - Details: - - - It's an error to call `Annotated` with less than two arguments. - - Nested Annotated are flattened:: - - Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3] - - - Instantiating an annotated type is equivalent to instantiating the - underlying type:: - - Annotated[C, Ann1](5) == C(5) - - - Annotated can be used as a generic type alias:: - - Optimized = Annotated[T, runtime.Optimize()] - Optimized[int] == Annotated[int, runtime.Optimize()] - - OptimizedList = Annotated[List[T], runtime.Optimize()] - OptimizedList[int] == Annotated[List[int], runtime.Optimize()] - """ - -# Python 3.8 has get_origin() and get_args() but those implementations aren't -# Annotated-aware, so we can't use those. Python 3.9's versions don't support -# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do. -if sys.version_info[:2] >= (3, 10): - get_origin = typing.get_origin - get_args = typing.get_args -# 3.7-3.9 -elif PEP_560: - try: - # 3.9+ - from typing import _BaseGenericAlias - except ImportError: - _BaseGenericAlias = typing._GenericAlias - try: - # 3.9+ - from typing import GenericAlias - except ImportError: - GenericAlias = typing._GenericAlias - - def get_origin(tp): - """Get the unsubscripted version of a type. - - This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar - and Annotated. Return None for unsupported types. Examples:: - - get_origin(Literal[42]) is Literal - get_origin(int) is None - get_origin(ClassVar[int]) is ClassVar - get_origin(Generic) is Generic - get_origin(Generic[T]) is Generic - get_origin(Union[T, int]) is Union - get_origin(List[Tuple[T, T]][int]) == list - get_origin(P.args) is P - """ - if isinstance(tp, _AnnotatedAlias): - return Annotated - if isinstance(tp, (typing._GenericAlias, GenericAlias, _BaseGenericAlias, - ParamSpecArgs, ParamSpecKwargs)): - return tp.__origin__ - if tp is typing.Generic: - return typing.Generic - return None - - def get_args(tp): - """Get type arguments with all substitutions performed. + def get_args(tp): + """Get type arguments with all substitutions performed. For unions, basic simplifications used by Union constructor are performed. Examples:: @@ -1645,7 +985,7 @@ def TypeAlias(self, parameters): """ raise TypeError(f"{self} is not subscriptable") # 3.7-3.8 -elif sys.version_info[:2] >= (3, 7): +else: class _TypeAliasForm(typing._SpecialForm, _root=True): def __repr__(self): return 'typing_extensions.' + self._name @@ -1661,44 +1001,13 @@ def __repr__(self): It's invalid when used anywhere except as in the example above.""") -# 3.6 -else: - class _TypeAliasMeta(typing.TypingMeta): - """Metaclass for TypeAlias""" - - def __repr__(self): - return 'typing_extensions.TypeAlias' - - class _TypeAliasBase(typing._FinalTypingBase, metaclass=_TypeAliasMeta, _root=True): - """Special marker indicating that an assignment should - be recognized as a proper type alias definition by type - checkers. - - For example:: - - Predicate: TypeAlias = Callable[..., bool] - - It's invalid when used anywhere except as in the example above. - """ - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("TypeAlias cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("TypeAlias cannot be used with issubclass().") - - def __repr__(self): - return 'typing_extensions.TypeAlias' - - TypeAlias = _TypeAliasBase(_root=True) # Python 3.10+ has PEP 612 if hasattr(typing, 'ParamSpecArgs'): ParamSpecArgs = typing.ParamSpecArgs ParamSpecKwargs = typing.ParamSpecKwargs -# 3.6-3.9 +# 3.7-3.9 else: class _Immutable: """Mixin to indicate that object should not be copied.""" @@ -1759,7 +1068,7 @@ def __eq__(self, other): # 3.10+ if hasattr(typing, 'ParamSpec'): ParamSpec = typing.ParamSpec -# 3.6-3.9 +# 3.7-3.9 else: # Inherits from list as a workaround for Callable checks in Python < 3.9.2. @@ -1861,28 +1170,17 @@ def __reduce__(self): def __call__(self, *args, **kwargs): pass - if not PEP_560: - # Only needed in 3.6. - def _get_type_vars(self, tvars): - if self not in tvars: - tvars.append(self) - -# 3.6-3.9 +# 3.7-3.9 if not hasattr(typing, 'Concatenate'): # Inherits from list as a workaround for Callable checks in Python < 3.9.2. class _ConcatenateGenericAlias(list): # Trick Generic into looking into this for __parameters__. - if PEP_560: - __class__ = typing._GenericAlias - else: - __class__ = typing._TypingBase + __class__ = typing._GenericAlias # Flag in 3.8. _special = False - # Attribute in 3.6 and earlier. - _gorg = typing.Generic def __init__(self, origin, args): super().__init__(args) @@ -1907,14 +1205,8 @@ def __parameters__(self): tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec)) ) - if not PEP_560: - # Only required in 3.6. - def _get_type_vars(self, tvars): - if self.__origin__ and self.__parameters__: - typing._get_type_vars(self.__parameters__, tvars) - -# 3.6-3.9 +# 3.7-3.9 @typing._tp_cache def _concatenate_getitem(self, parameters): if parameters == (): @@ -1949,7 +1241,7 @@ def Concatenate(self, parameters): """ return _concatenate_getitem(self, parameters) # 3.7-8 -elif sys.version_info[:2] >= (3, 7): +else: class _ConcatenateForm(typing._SpecialForm, _root=True): def __repr__(self): return 'typing_extensions.' + self._name @@ -1969,42 +1261,6 @@ def __getitem__(self, parameters): See PEP 612 for detailed information. """) -# 3.6 -else: - class _ConcatenateAliasMeta(typing.TypingMeta): - """Metaclass for Concatenate.""" - - def __repr__(self): - return 'typing_extensions.Concatenate' - - class _ConcatenateAliasBase(typing._FinalTypingBase, - metaclass=_ConcatenateAliasMeta, - _root=True): - """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a - higher order function which adds, removes or transforms parameters of a - callable. - - For example:: - - Callable[Concatenate[int, P], int] - - See PEP 612 for detailed information. - """ - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("Concatenate cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Concatenate cannot be used with issubclass().") - - def __repr__(self): - return 'typing_extensions.Concatenate' - - def __getitem__(self, parameters): - return _concatenate_getitem(self, parameters) - - Concatenate = _ConcatenateAliasBase(_root=True) # 3.10+ if hasattr(typing, 'TypeGuard'): @@ -2062,7 +1318,7 @@ def is_str(val: Union[str, float]): item = typing._type_check(parameters, f'{self} accepts only single type.') return typing._GenericAlias(self, (item,)) # 3.7-3.8 -elif sys.version_info[:2] >= (3, 7): +else: class _TypeGuardForm(typing._SpecialForm, _root=True): def __repr__(self): @@ -2117,138 +1373,55 @@ def is_str(val: Union[str, float]): ``TypeGuard`` also works with type variables. For more information, see PEP 647 (User-Defined Type Guards). """) -# 3.6 -else: - class _TypeGuard(typing._FinalTypingBase, _root=True): - """Special typing form used to annotate the return type of a user-defined - type guard function. ``TypeGuard`` only accepts a single type argument. - At runtime, functions marked this way should return a boolean. - - ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static - type checkers to determine a more precise type of an expression within a - program's code flow. Usually type narrowing is done by analyzing - conditional code flow and applying the narrowing to a block of code. The - conditional expression here is sometimes referred to as a "type guard". - - Sometimes it would be convenient to use a user-defined boolean function - as a type guard. Such a function should use ``TypeGuard[...]`` as its - return type to alert static type checkers to this intention. - - Using ``-> TypeGuard`` tells the static type checker that for a given - function: - - 1. The return value is a boolean. - 2. If the return value is ``True``, the type of its argument - is the type inside ``TypeGuard``. - - For example:: - - def is_str(val: Union[str, float]): - # "isinstance" type guard - if isinstance(val, str): - # Type of ``val`` is narrowed to ``str`` - ... - else: - # Else, type of ``val`` is narrowed to ``float``. - ... - - Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower - form of ``TypeA`` (it can even be a wider form) and this may lead to - type-unsafe results. The main reason is to allow for things like - narrowing ``List[object]`` to ``List[str]`` even though the latter is not - a subtype of the former, since ``List`` is invariant. The responsibility of - writing type-safe type guards is left to the user. - - ``TypeGuard`` also works with type variables. For more information, see - PEP 647 (User-Defined Type Guards). - """ - - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(typing._type_check(item, - f'{cls.__name__[1:]} accepts only a single type.'), - _root=True) - raise TypeError(f'{cls.__name__[1:]} cannot be further subscripted') - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += f'[{typing._type_repr(self.__type__)}]' - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _TypeGuard): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - TypeGuard = _TypeGuard(_root=True) +# Vendored from cpython typing._SpecialFrom +class _SpecialForm(typing._Final, _root=True): + __slots__ = ('_name', '__doc__', '_getitem') -if sys.version_info[:2] >= (3, 7): - # Vendored from cpython typing._SpecialFrom - class _SpecialForm(typing._Final, _root=True): - __slots__ = ('_name', '__doc__', '_getitem') + def __init__(self, getitem): + self._getitem = getitem + self._name = getitem.__name__ + self.__doc__ = getitem.__doc__ - def __init__(self, getitem): - self._getitem = getitem - self._name = getitem.__name__ - self.__doc__ = getitem.__doc__ - - def __getattr__(self, item): - if item in {'__name__', '__qualname__'}: - return self._name + def __getattr__(self, item): + if item in {'__name__', '__qualname__'}: + return self._name - raise AttributeError(item) + raise AttributeError(item) - def __mro_entries__(self, bases): - raise TypeError(f"Cannot subclass {self!r}") + def __mro_entries__(self, bases): + raise TypeError(f"Cannot subclass {self!r}") - def __repr__(self): - return f'typing_extensions.{self._name}' + def __repr__(self): + return f'typing_extensions.{self._name}' - def __reduce__(self): - return self._name + def __reduce__(self): + return self._name - def __call__(self, *args, **kwds): - raise TypeError(f"Cannot instantiate {self!r}") + def __call__(self, *args, **kwds): + raise TypeError(f"Cannot instantiate {self!r}") - def __or__(self, other): - return typing.Union[self, other] + def __or__(self, other): + return typing.Union[self, other] - def __ror__(self, other): - return typing.Union[other, self] + def __ror__(self, other): + return typing.Union[other, self] - def __instancecheck__(self, obj): - raise TypeError(f"{self} cannot be used with isinstance()") + def __instancecheck__(self, obj): + raise TypeError(f"{self} cannot be used with isinstance()") - def __subclasscheck__(self, cls): - raise TypeError(f"{self} cannot be used with issubclass()") + def __subclasscheck__(self, cls): + raise TypeError(f"{self} cannot be used with issubclass()") - @typing._tp_cache - def __getitem__(self, parameters): - return self._getitem(self, parameters) + @typing._tp_cache + def __getitem__(self, parameters): + return self._getitem(self, parameters) if hasattr(typing, "LiteralString"): LiteralString = typing.LiteralString -elif sys.version_info[:2] >= (3, 7): +else: @_SpecialForm def LiteralString(self, params): """Represents an arbitrary literal string. @@ -2267,38 +1440,11 @@ def query(sql: LiteralString) -> ...: """ raise TypeError(f"{self} is not subscriptable") -else: - class _LiteralString(typing._FinalTypingBase, _root=True): - """Represents an arbitrary literal string. - - Example:: - - from typing_extensions import LiteralString - - def query(sql: LiteralString) -> ...: - ... - - query("SELECT * FROM table") # ok - query(f"SELECT * FROM {input()}") # not ok - - See PEP 675 for details. - - """ - - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError(f"{self} cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError(f"{self} cannot be used with issubclass().") - - LiteralString = _LiteralString(_root=True) if hasattr(typing, "Self"): Self = typing.Self -elif sys.version_info[:2] >= (3, 7): +else: @_SpecialForm def Self(self, params): """Used to spell the type of "self" in classes. @@ -2315,35 +1461,11 @@ def parse(self, data: bytes) -> Self: """ raise TypeError(f"{self} is not subscriptable") -else: - class _Self(typing._FinalTypingBase, _root=True): - """Used to spell the type of "self" in classes. - - Example:: - - from typing import Self - - class ReturnsSelf: - def parse(self, data: bytes) -> Self: - ... - return self - - """ - - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError(f"{self} cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError(f"{self} cannot be used with issubclass().") - - Self = _Self(_root=True) if hasattr(typing, "Never"): Never = typing.Never -elif sys.version_info[:2] >= (3, 7): +else: @_SpecialForm def Never(self, params): """The bottom type, a type that has no members. @@ -2369,39 +1491,6 @@ def int_or_str(arg: int | str) -> None: """ raise TypeError(f"{self} is not subscriptable") -else: - class _Never(typing._FinalTypingBase, _root=True): - """The bottom type, a type that has no members. - - This can be used to define a function that should never be - called, or a function that never returns:: - - from typing_extensions import Never - - def never_call_me(arg: Never) -> None: - pass - - def int_or_str(arg: int | str) -> None: - never_call_me(arg) # type checker error - match arg: - case int(): - print("It's an int") - case str(): - print("It's a str") - case _: - never_call_me(arg) # ok, arg is of type Never - - """ - - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError(f"{self} cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError(f"{self} cannot be used with issubclass().") - - Never = _Never(_root=True) if hasattr(typing, 'Required'): @@ -2449,7 +1538,7 @@ class Movie(TypedDict): item = typing._type_check(parameters, f'{self._name} accepts only single type') return typing._GenericAlias(self, (item,)) -elif sys.version_info[:2] >= (3, 7): +else: class _RequiredForm(typing._SpecialForm, _root=True): def __repr__(self): return 'typing_extensions.' + self._name @@ -2490,78 +1579,6 @@ class Movie(TypedDict): year=1999, ) """) -else: - # NOTE: Modeled after _Final's implementation when _FinalTypingBase available - class _MaybeRequired(typing._FinalTypingBase, _root=True): - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(typing._type_check(item, - '{} accepts only single type.'.format(cls.__name__[1:])), - _root=True) - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, type(self)): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - class _Required(_MaybeRequired, _root=True): - """A special typing construct to mark a key of a total=False TypedDict - as required. For example: - - class Movie(TypedDict, total=False): - title: Required[str] - year: int - - m = Movie( - title='The Matrix', # typechecker error if key is omitted - year=1999, - ) - - There is no runtime checking that a required key is actually provided - when instantiating a related TypedDict. - """ - - class _NotRequired(_MaybeRequired, _root=True): - """A special typing construct to mark a key of a TypedDict as - potentially missing. For example: - - class Movie(TypedDict): - title: str - year: NotRequired[int] - - m = Movie( - title='The Matrix', # typechecker error if key is omitted - year=1999, - ) - """ - - Required = _Required(_root=True) - NotRequired = _NotRequired(_root=True) if sys.version_info[:2] >= (3, 9): @@ -2590,7 +1607,7 @@ def add_batch_axis( def _is_unpack(obj): return isinstance(obj, _UnpackAlias) -elif sys.version_info[:2] >= (3, 7): +else: class _UnpackAlias(typing._GenericAlias, _root=True): __class__ = typing.TypeVar @@ -2619,64 +1636,6 @@ def add_batch_axis( def _is_unpack(obj): return isinstance(obj, _UnpackAlias) -else: - # NOTE: Modeled after _Final's implementation when _FinalTypingBase available - class _Unpack(typing._FinalTypingBase, _root=True): - """A special typing construct to unpack a variadic type. For example: - - Shape = TypeVarTuple('Shape') - Batch = NewType('Batch', int) - - def add_batch_axis( - x: Array[Unpack[Shape]] - ) -> Array[Batch, Unpack[Shape]]: ... - - """ - __slots__ = ('__type__',) - __class__ = typing.TypeVar - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(typing._type_check(item, - 'Unpack accepts only single type.'), - _root=True) - raise TypeError('Unpack cannot be further subscripted') - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _Unpack): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - # For 3.6 only - def _get_type_vars(self, tvars): - self.__type__._get_type_vars(tvars) - - Unpack = _Unpack(_root=True) - - def _is_unpack(obj): - return isinstance(obj, _Unpack) - class TypeVarTuple: """Type variable tuple. @@ -2757,12 +1716,6 @@ def __init_subclass__(self, *args, **kwds): if '_root' not in kwds: raise TypeError("Cannot subclass special typing classes") - if not PEP_560: - # Only needed in 3.6. - def _get_type_vars(self, tvars): - if self not in tvars: - tvars.append(self) - if hasattr(typing, "reveal_type"): reveal_type = typing.reveal_type From 3bcfc9ed3b0511e67119dc356fd736889e884fde Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Tue, 22 Mar 2022 14:45:53 -0700 Subject: [PATCH 07/20] test_typing_extensions: fix lint (#1111) --- typing_extensions/src/test_typing_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index 20e35f431..53a8343fa 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -2591,7 +2591,7 @@ def stmethod(): ... def prop(self): ... @final - @lru_cache() + @lru_cache() # noqa: B019 def cached(self): ... # Use getattr_static because the descriptor returns the From 07fb800b29127a46a9c422d4dbc4e4d96df69d3f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 22 Mar 2022 14:46:02 -0700 Subject: [PATCH 08/20] LiteralString, NotRequired, Required will be in 3.11 (#1110) --- typing_extensions/README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index bb8906895..d29d9f535 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -41,16 +41,16 @@ This module currently contains the following: - Experimental features - - ``LiteralString`` (see PEP 675) - ``@dataclass_transform()`` (see PEP 681) - - ``NotRequired`` (see PEP 655) - - ``Required`` (see PEP 655) - In ``typing`` since Python 3.11 - ``assert_never`` + - ``LiteralString`` (see PEP 675) - ``Never`` + - ``NotRequired`` (see PEP 655) - ``reveal_type`` + - ``Required`` (see PEP 655) - ``Self`` (see PEP 673) - ``TypeVarTuple`` (see PEP 646) - ``Unpack`` (see PEP 646) From db20497ae1be03d89e9cd083aa93cdff9c786831 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 22 Mar 2022 15:29:58 -0700 Subject: [PATCH 09/20] Add assert_type (#1103) --- typing_extensions/CHANGELOG | 1 + typing_extensions/README.rst | 1 + .../src/test_typing_extensions.py | 19 +++++++++++++++-- typing_extensions/src/typing_extensions.py | 21 +++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index f098464eb..a9a598045 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,6 @@ # Unreleased +- Add `typing.assert_type`. Backport from bpo-46480. - Drop support for Python 3.6. Original patch by Adam Turner (@AA-Turner). # Release 4.1.1 (February 13, 2022) diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index d29d9f535..9abed0442 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -46,6 +46,7 @@ This module currently contains the following: - In ``typing`` since Python 3.11 - ``assert_never`` + - ``assert_type`` - ``LiteralString`` (see PEP 675) - ``Never`` - ``NotRequired`` (see PEP 655) diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index 53a8343fa..b8fe5e352 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -12,7 +12,7 @@ from unittest import TestCase, main, skipUnless, skipIf from test import ann_module, ann_module2, ann_module3 import typing -from typing import TypeVar, Optional, Union, Any +from typing import TypeVar, Optional, Union, Any, AnyStr from typing import T, KT, VT # Not in __all__. from typing import Tuple, List, Dict, Iterable, Iterator, Callable from typing import Generic, NamedTuple @@ -23,7 +23,7 @@ from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict from typing_extensions import TypeVarTuple, Unpack, dataclass_transform, reveal_type, Never, assert_never, LiteralString -from typing_extensions import get_type_hints, get_origin, get_args +from typing_extensions import assert_type, get_type_hints, get_origin, get_args # Flags used to mark tests that only apply after a specific # version of the typing module. @@ -425,6 +425,21 @@ def blah(): blah() +class AssertTypeTests(BaseTestCase): + + def test_basics(self): + arg = 42 + self.assertIs(assert_type(arg, int), arg) + self.assertIs(assert_type(arg, Union[str, float]), arg) + self.assertIs(assert_type(arg, AnyStr), arg) + self.assertIs(assert_type(arg, None), arg) + + def test_errors(self): + # Bogus calls are not expected to fail. + arg = 42 + self.assertIs(assert_type(arg, 42), arg) + self.assertIs(assert_type(arg, 'hello'), arg) + T_a = TypeVar('T_a') diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 5c43354ee..5698a76cc 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -717,6 +717,27 @@ class Film(TypedDict): """ return isinstance(tp, tuple(_TYPEDDICT_TYPES)) + +if hasattr(typing, "assert_type"): + assert_type = typing.assert_type + +else: + def assert_type(__val, __typ): + """Assert (to the type checker) that the value is of the given type. + + When the type checker encounters a call to assert_type(), it + emits an error if the value is not of the specified type:: + + def greet(name: str) -> None: + assert_type(name, str) # ok + assert_type(name, int) # type checker error + + At runtime this returns the first argument unchanged and otherwise + does nothing. + """ + return __val + + if hasattr(typing, "Required"): get_type_hints = typing.get_type_hints else: From 5eed34c3daf3b38edf4d1246b3c063f38579cbe7 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 5 Apr 2022 19:09:21 +0200 Subject: [PATCH 10/20] TypeAlias can now be used, add guidance (#1116) --- docs/source/stubs.rst | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/docs/source/stubs.rst b/docs/source/stubs.rst index 8408c8050..539e8cb75 100644 --- a/docs/source/stubs.rst +++ b/docs/source/stubs.rst @@ -356,20 +356,28 @@ type stubs:: Aliases and NewType ------------------- -Type checkers should accept module-level and class-level aliases, e.g.:: +Type checkers should accept module-level type aliases, optionally using +``TypeAlias`` (PEP 613 [#pep613]_), e.g.:: _IntList = list[int] + _StrList: TypeAlias = list[str] + +Type checkers should also accept regular module-level or class-level aliases, +e.g.:: + + def a() -> None: ... + b = a class C: def f(self) -> int: ... g = f -An alias to a type may contain type variables. As per PEP 484 [#pep484]_, +A type alias may contain type variables. As per PEP 484 [#pep484]_, all type variables must be substituted when the alias is used:: _K = TypeVar("_K") _V = TypeVar("_V") - _MyMap = Dict[str, Dict[_K, _V]] + _MyMap: TypeAlias = dict[str, dict[_K, _V]] # either concrete types or other type variables can be substituted def f(x: _MyMap[str, _V]) -> _V: ... @@ -518,8 +526,6 @@ and should not be used in stubs: * Positional-only argument syntax (PEP 570 [#pep570]_). Instead, use the syntax described in the section :ref:`supported-functions`. [#ts-4972]_ -* ``TypeAlias`` (PEP 613 [#pep613]_). Instead, use a simple - assigment to define a type alias. [#ts-4913]_ Type Stub Content ================= @@ -827,7 +833,7 @@ No:: But the following is still necessary:: - TYPE_ALIAS = Optional[Union[str, int]] + TYPE_ALIAS: TypeAlias = Optional[Union[str, int]] Module Level Attributes ----------------------- @@ -847,6 +853,25 @@ No:: z = 0 # type: int a = ... # type: int +Type Aliases +------------ + +Use ``TypeAlias`` for type aliases (but not for regular aliases). + +Yes:: + + _IntList: TypeAlias = list[int] + g = os.stat + Path = pathlib.Path + ERROR = errno.EEXIST + +No:: + + _IntList = list[int] + g: TypeAlias = os.stat + Path: TypeAlias = pathlib.Path + ERROR: TypeAlias = errno.EEXIST + Classes ------- @@ -1104,7 +1129,6 @@ Bugs ---- .. [#ts-4819] typeshed issue #4819 -- PEP 604 tracker (https://github.com/python/typeshed/issues/4819) -.. [#ts-4913] typeshed issue #4913 -- PEP 613 tracker (https://github.com/python/typeshed/issues/4913) .. [#ts-4972] typeshed issue #4972 -- PEP 570 tracker (https://github.com/python/typeshed/issues/4972) Copyright From f9d318138fcec1f9724ee1808aeff3ed1bf976a5 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 5 Apr 2022 19:22:31 +0200 Subject: [PATCH 11/20] Don't list specific bugs with union short-hand syntax (#1119) --- docs/source/stubs.rst | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/docs/source/stubs.rst b/docs/source/stubs.rst index 539e8cb75..ffd6c9b86 100644 --- a/docs/source/stubs.rst +++ b/docs/source/stubs.rst @@ -193,15 +193,12 @@ Unions ------ Declaring unions with ``Union`` and ``Optional`` is supported by all -type checkers. With the exception of type aliases [#ts-4819]_, the shorthand syntax +type checkers. With a few exceptions [#ts-4819]_, the shorthand syntax is also supported:: def foo(x: int | str) -> int | None: ... # recommended def foo(x: Union[int, str]) -> Optional[int]: ... # ok - TYPE_ALIAS = Union[int, str] # ok - TYPE_ALIAS = int | str # does not work with all type checkers - Module Level Attributes ----------------------- @@ -817,8 +814,7 @@ Shorthand Syntax Where possible, use shorthand syntax for unions instead of ``Union`` or ``Optional``. ``None`` should be the last -element of an union. See the Unions_ section for cases where -using the shorthand syntax is not possible. +element of an union. Yes:: @@ -831,10 +827,6 @@ No:: def bar(x: Optional[str]) -> Optional[int]: ... def baz(x: None | str) -> None: ... -But the following is still necessary:: - - TYPE_ALIAS: TypeAlias = Optional[Union[str, int]] - Module Level Attributes ----------------------- From 91691524757b82ddef8c14cef29f1cd186b40ca2 Mon Sep 17 00:00:00 2001 From: Redowan Delowar Date: Mon, 11 Apr 2022 08:27:03 +0600 Subject: [PATCH 12/20] Update the statuses of the typing PEPs (#1128) --- docs/index.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 695097f96..b5fe26888 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,10 +4,10 @@ Static Typing with Python .. Introduction .. ============ -.. +.. .. .. toctree:: .. :maxdepth: 2 -.. +.. .. source/introduction Guides @@ -92,8 +92,8 @@ Typing PEPs * `PEP 613 `_, ``TypeAlias`` * `PEP 646 `_, variadic generics and ``TypeVarTuple`` * `PEP 647 `_, ``TypeGuard`` -* `PEP 655 `_ (draft), ``Required`` and ``NotRequired`` +* `PEP 655 `_, ``Required`` and ``NotRequired`` * `PEP 673 `_, ``Self`` -* `PEP 675 `_ (draft), ``LiteralString`` -* `PEP 677 `_ (draft), callable type syntax +* `PEP 675 `_, ``LiteralString`` +* `PEP 677 `_ (rejected), callable type syntax * `PEP 681 `_ (draft), ``@dataclass_transform()`` From cba3a90903fd19a33ef65784744f164e9582b7ee Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 11 Apr 2022 10:37:18 -0700 Subject: [PATCH 13/20] Fix "accepts only single type" errors (#1130) - Add "a" to make the message grammatical - Add a trailing period because _type_check adds another sentence after it. For example, `Unpack[3]` on 3.10 currently fails with `TypeError: Unpack accepts only single type Got 3.`. - Use an f-string --- typing_extensions/src/typing_extensions.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 5698a76cc..bd2ab337f 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -157,7 +157,7 @@ def __repr__(self): def __getitem__(self, parameters): item = typing._type_check(parameters, - f'{self._name} accepts only single type') + f'{self._name} accepts only a single type.') return typing._GenericAlias(self, (item,)) Final = _FinalForm('Final', @@ -1336,7 +1336,7 @@ def is_str(val: Union[str, float]): ``TypeGuard`` also works with type variables. For more information, see PEP 647 (User-Defined Type Guards). """ - item = typing._type_check(parameters, f'{self} accepts only single type.') + item = typing._type_check(parameters, f'{self} accepts only a single type.') return typing._GenericAlias(self, (item,)) # 3.7-3.8 else: @@ -1539,7 +1539,7 @@ class Movie(TypedDict, total=False): There is no runtime checking that a required key is actually provided when instantiating a related TypedDict. """ - item = typing._type_check(parameters, f'{self._name} accepts only single type') + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') return typing._GenericAlias(self, (item,)) @_ExtensionsSpecialForm @@ -1556,7 +1556,7 @@ class Movie(TypedDict): year=1999, ) """ - item = typing._type_check(parameters, f'{self._name} accepts only single type') + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') return typing._GenericAlias(self, (item,)) else: @@ -1566,7 +1566,7 @@ def __repr__(self): def __getitem__(self, parameters): item = typing._type_check(parameters, - '{} accepts only single type'.format(self._name)) + f'{self._name} accepts only a single type.') return typing._GenericAlias(self, (item,)) Required = _RequiredForm( @@ -1622,7 +1622,7 @@ def add_batch_axis( ) -> Array[Batch, Unpack[Shape]]: ... """ - item = typing._type_check(parameters, f'{self._name} accepts only single type') + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') return _UnpackAlias(self, (item,)) def _is_unpack(obj): @@ -1638,7 +1638,7 @@ def __repr__(self): def __getitem__(self, parameters): item = typing._type_check(parameters, - f'{self._name} accepts only single type') + f'{self._name} accepts only a single type.') return _UnpackAlias(self, (item,)) Unpack = _UnpackForm( From e7bc381dc7a4dbc94441759af31f576bbd7302c6 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 15 Apr 2022 23:16:04 +0100 Subject: [PATCH 14/20] Add `assert_type` to `__all__` (#1136) Looks like this is in `typing.__all__` but was missed out of `typing_extensions.__all__` --- typing_extensions/src/typing_extensions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index bd2ab337f..c959adbfe 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -43,6 +43,7 @@ # One-off things. 'Annotated', 'assert_never', + 'assert_type', 'dataclass_transform', 'final', 'get_args', From 2acaa5acd01aeabb295e961913c111a7df52656d Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 15 Apr 2022 17:10:25 -0700 Subject: [PATCH 15/20] test that all names are present in __all__ (#1138) --- .../src/test_typing_extensions.py | 21 +++++++++++++++++++ typing_extensions/src/typing_extensions.py | 10 +++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index b8fe5e352..1439e5174 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -2743,6 +2743,27 @@ def test_typing_extensions_includes_standard(self): for name in a: self.assertTrue(hasattr(typing_extensions, name)) + def test_all_names_in___all__(self): + exclude = { + 'GenericMeta', + 'KT', + 'PEP_560', + 'T', + 'T_co', + 'T_contra', + 'VT', + } + actual_names = { + name for name in dir(typing_extensions) + if not name.startswith("_") + and not isinstance(getattr(typing_extensions, name), types.ModuleType) + } + # Make sure all public names are in __all__ + self.assertEqual({*exclude, *typing_extensions.__all__}, + actual_names) + # Make sure all excluded names actually exist + self.assertLessEqual(exclude, actual_names) + def test_typing_extensions_defers_when_possible(self): exclude = { 'overload', diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index c959adbfe..d5e404972 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -15,6 +15,8 @@ 'Final', 'LiteralString', 'ParamSpec', + 'ParamSpecArgs', + 'ParamSpecKwargs', 'Self', 'Type', 'TypeVarTuple', @@ -933,9 +935,9 @@ def __init_subclass__(cls, *args, **kwargs): _BaseGenericAlias = typing._GenericAlias try: # 3.9+ - from typing import GenericAlias + from typing import GenericAlias as _typing_GenericAlias except ImportError: - GenericAlias = typing._GenericAlias + _typing_GenericAlias = typing._GenericAlias def get_origin(tp): """Get the unsubscripted version of a type. @@ -954,7 +956,7 @@ def get_origin(tp): """ if isinstance(tp, _AnnotatedAlias): return Annotated - if isinstance(tp, (typing._GenericAlias, GenericAlias, _BaseGenericAlias, + if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias, _BaseGenericAlias, ParamSpecArgs, ParamSpecKwargs)): return tp.__origin__ if tp is typing.Generic: @@ -974,7 +976,7 @@ def get_args(tp): """ if isinstance(tp, _AnnotatedAlias): return (tp.__origin__,) + tp.__metadata__ - if isinstance(tp, (typing._GenericAlias, GenericAlias)): + if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias)): if getattr(tp, "_special", False): return () res = tp.__args__ From 35dff91370a382e312dd53d002d948e3efedb317 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 16 Apr 2022 10:11:18 -0700 Subject: [PATCH 16/20] Add get_overloads() (#1140) Co-authored-by: Alex Waygood --- typing_extensions/CHANGELOG | 5 +- typing_extensions/README.rst | 6 ++ .../src/test_typing_extensions.py | 74 ++++++++++++++++++- typing_extensions/src/typing_extensions.py | 70 +++++++++++++++++- 4 files changed, 152 insertions(+), 3 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index a9a598045..970bbd4a6 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,6 +1,9 @@ # Unreleased -- Add `typing.assert_type`. Backport from bpo-46480. +- Add `typing_extensions.get_overloads` and + `typing_extensions.clear_overloads`, and add registry support to + `typing_extensions.overload`. Backport from python/cpython#89263. +- Add `typing_extensions.assert_type`. Backport from bpo-46480. - Drop support for Python 3.6. Original patch by Adam Turner (@AA-Turner). # Release 4.1.1 (February 13, 2022) diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 9abed0442..3a23b7559 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -47,6 +47,8 @@ This module currently contains the following: - ``assert_never`` - ``assert_type`` + - ``clear_overloads`` + - ``get_overloads`` - ``LiteralString`` (see PEP 675) - ``Never`` - ``NotRequired`` (see PEP 655) @@ -122,6 +124,10 @@ Certain objects were changed after they were added to ``typing``, and Python 3.8 and lack support for ``ParamSpecArgs`` and ``ParamSpecKwargs`` in 3.9. - ``@final`` was changed in Python 3.11 to set the ``.__final__`` attribute. +- ``@overload`` was changed in Python 3.11 to make function overloads + introspectable at runtime. In order to access overloads with + ``typing_extensions.get_overloads()``, you must use + ``@typing_extensions.overload``. There are a few types whose interface was modified between different versions of typing. For example, ``typing.Sequence`` was modified to diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index 1439e5174..ab03244d3 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -3,6 +3,7 @@ import abc import contextlib import collections +from collections import defaultdict import collections.abc from functools import lru_cache import inspect @@ -10,6 +11,7 @@ import subprocess import types from unittest import TestCase, main, skipUnless, skipIf +from unittest.mock import patch from test import ann_module, ann_module2, ann_module3 import typing from typing import TypeVar, Optional, Union, Any, AnyStr @@ -21,9 +23,10 @@ from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict, Self from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired -from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict +from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, final, is_typeddict from typing_extensions import TypeVarTuple, Unpack, dataclass_transform, reveal_type, Never, assert_never, LiteralString from typing_extensions import assert_type, get_type_hints, get_origin, get_args +from typing_extensions import clear_overloads, get_overloads, overload # Flags used to mark tests that only apply after a specific # version of the typing module. @@ -403,6 +406,20 @@ def test_no_multiple_subscripts(self): Literal[1][1] +class MethodHolder: + @classmethod + def clsmethod(cls): ... + @staticmethod + def stmethod(): ... + def method(self): ... + + +if TYPING_3_11_0: + registry_holder = typing +else: + registry_holder = typing_extensions + + class OverloadTests(BaseTestCase): def test_overload_fails(self): @@ -424,6 +441,61 @@ def blah(): blah() + def set_up_overloads(self): + def blah(): + pass + + overload1 = blah + overload(blah) + + def blah(): + pass + + overload2 = blah + overload(blah) + + def blah(): + pass + + return blah, [overload1, overload2] + + # Make sure we don't clear the global overload registry + @patch( + f"{registry_holder.__name__}._overload_registry", + defaultdict(lambda: defaultdict(dict)) + ) + def test_overload_registry(self): + registry = registry_holder._overload_registry + # The registry starts out empty + self.assertEqual(registry, {}) + + impl, overloads = self.set_up_overloads() + self.assertNotEqual(registry, {}) + self.assertEqual(list(get_overloads(impl)), overloads) + + def some_other_func(): pass + overload(some_other_func) + other_overload = some_other_func + def some_other_func(): pass + self.assertEqual(list(get_overloads(some_other_func)), [other_overload]) + + # Make sure that after we clear all overloads, the registry is + # completely empty. + clear_overloads() + self.assertEqual(registry, {}) + self.assertEqual(get_overloads(impl), []) + + # Querying a function with no overloads shouldn't change the registry. + def the_only_one(): pass + self.assertEqual(get_overloads(the_only_one), []) + self.assertEqual(registry, {}) + + def test_overload_registry_repeated(self): + for _ in range(2): + impl, overloads = self.set_up_overloads() + + self.assertEqual(list(get_overloads(impl)), overloads) + class AssertTypeTests(BaseTestCase): diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index d5e404972..491109991 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -1,6 +1,7 @@ import abc import collections import collections.abc +import functools import operator import sys import types as _types @@ -46,7 +47,9 @@ 'Annotated', 'assert_never', 'assert_type', + 'clear_overloads', 'dataclass_transform', + 'get_overloads', 'final', 'get_args', 'get_origin', @@ -249,7 +252,72 @@ def __getitem__(self, parameters): _overload_dummy = typing._overload_dummy # noqa -overload = typing.overload + + +if hasattr(typing, "get_overloads"): # 3.11+ + overload = typing.overload + get_overloads = typing.get_overloads + clear_overloads = typing.clear_overloads +else: + # {module: {qualname: {firstlineno: func}}} + _overload_registry = collections.defaultdict( + functools.partial(collections.defaultdict, dict) + ) + + def overload(func): + """Decorator for overloaded functions/methods. + + In a stub file, place two or more stub definitions for the same + function in a row, each decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + + In a non-stub file (i.e. a regular .py file), do the same but + follow it with an implementation. The implementation should *not* + be decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + def utf8(value): + # implementation goes here + + The overloads for a function can be retrieved at runtime using the + get_overloads() function. + """ + # classmethod and staticmethod + f = getattr(func, "__func__", func) + try: + _overload_registry[f.__module__][f.__qualname__][ + f.__code__.co_firstlineno + ] = func + except AttributeError: + # Not a normal function; ignore. + pass + return _overload_dummy + + def get_overloads(func): + """Return all defined overloads for *func* as a sequence.""" + # classmethod and staticmethod + f = getattr(func, "__func__", func) + if f.__module__ not in _overload_registry: + return [] + mod_dict = _overload_registry[f.__module__] + if f.__qualname__ not in mod_dict: + return [] + return list(mod_dict[f.__qualname__].values()) + + def clear_overloads(): + """Clear all overloads in the registry.""" + _overload_registry.clear() # This is not a real generic class. Don't use outside annotations. From b595c737c153a0d0f638be9d32469d043b7128a6 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 16 Apr 2022 10:17:25 -0700 Subject: [PATCH 17/20] dataclass_transform: accept **kwargs, rename field_descriptors (#1120) --- typing_extensions/CHANGELOG | 3 +++ .../src/test_typing_extensions.py | 18 +++++++++++++----- typing_extensions/src/typing_extensions.py | 10 ++++++---- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 970bbd4a6..550a14631 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,8 @@ # Unreleased +- Update `typing_extensions.dataclass_transform` to rename the + `field_descriptors` parameter to `field_specifiers` and accept + arbitrary keyword arguments. - Add `typing_extensions.get_overloads` and `typing_extensions.clear_overloads`, and add registry support to `typing_extensions.overload`. Backport from python/cpython#89263. diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index ab03244d3..8e11eb671 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -2718,7 +2718,8 @@ class CustomerModel: "eq_default": True, "order_default": False, "kw_only_default": True, - "field_descriptors": (), + "field_specifiers": (), + "kwargs": {}, } ) self.assertIs( @@ -2730,7 +2731,12 @@ def test_base_class(self): class ModelBase: def __init_subclass__(cls, *, frozen: bool = False): ... - Decorated = dataclass_transform(eq_default=True, order_default=True)(ModelBase) + Decorated = dataclass_transform( + eq_default=True, + order_default=True, + # Arbitrary unrecognized kwargs are accepted at runtime. + make_everything_awesome=True, + )(ModelBase) class CustomerModel(Decorated, frozen=True): id: int @@ -2742,7 +2748,8 @@ class CustomerModel(Decorated, frozen=True): "eq_default": True, "order_default": True, "kw_only_default": False, - "field_descriptors": (), + "field_specifiers": (), + "kwargs": {"make_everything_awesome": True}, } ) self.assertIsSubclass(CustomerModel, Decorated) @@ -2757,7 +2764,7 @@ def __new__( return super().__new__(cls, name, bases, namespace) Decorated = dataclass_transform( - order_default=True, field_descriptors=(Field,) + order_default=True, field_specifiers=(Field,) )(ModelMeta) class ModelBase(metaclass=Decorated): ... @@ -2772,7 +2779,8 @@ class CustomerModel(ModelBase, init=False): "eq_default": True, "order_default": True, "kw_only_default": False, - "field_descriptors": (Field,), + "field_specifiers": (Field,), + "kwargs": {}, } ) self.assertIsInstance(CustomerModel, Decorated) diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 491109991..1e3e12829 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -1866,10 +1866,11 @@ def dataclass_transform( eq_default: bool = True, order_default: bool = False, kw_only_default: bool = False, - field_descriptors: typing.Tuple[ + field_specifiers: typing.Tuple[ typing.Union[typing.Type[typing.Any], typing.Callable[..., typing.Any]], ... ] = (), + **kwargs: typing.Any, ) -> typing.Callable[[T], T]: """Decorator that marks a function, class, or metaclass as providing dataclass-like behavior. @@ -1921,8 +1922,8 @@ class CustomerModel(ModelBase): assumed to be True or False if it is omitted by the caller. - ``kw_only_default`` indicates whether the ``kw_only`` parameter is assumed to be True or False if it is omitted by the caller. - - ``field_descriptors`` specifies a static list of supported classes - or functions, that describe fields, similar to ``dataclasses.field()``. + - ``field_specifiers`` specifies a static list of supported classes + or functions that describe fields, similar to ``dataclasses.field()``. At runtime, this decorator records its arguments in the ``__dataclass_transform__`` attribute on the decorated object. @@ -1935,7 +1936,8 @@ def decorator(cls_or_fn): "eq_default": eq_default, "order_default": order_default, "kw_only_default": kw_only_default, - "field_descriptors": field_descriptors, + "field_specifiers": field_specifiers, + "kwargs": kwargs, } return cls_or_fn return decorator From 2312c741154b61d5dfea3fa9f6b6f1adfb4b9aca Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 16 Apr 2022 10:35:37 -0700 Subject: [PATCH 18/20] Add to the CHANGELOG (#1141) --- typing_extensions/CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 550a14631..569fe50c2 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,8 @@ # Unreleased +- Add `ParamSpecArgs` and `ParamSpecKwargs` to `__all__`. +- Improve "accepts only single type" error messages. +- Improve the distributed package. Patch by Marc Mueller (@cdce8p). - Update `typing_extensions.dataclass_transform` to rename the `field_descriptors` parameter to `field_specifiers` and accept arbitrary keyword arguments. From 783c8ca10ffb8b2cd00e62977c052f8d5a31bc3f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 16 Apr 2022 10:58:25 -0700 Subject: [PATCH 19/20] Fix tests on Python 3.11 (#1139) - Defer to the PEP 646 implementation in typing.py on 3.11 - Adjust some tests accordingly. Noted a bug in https://github.com/python/cpython/pull/32341#issuecomment-1100466389 - typing._type_check() is more lenient in 3.11 and no longer rejects ints - The representation of the empty tuple type changed Tests pass for me on a 3.11 build from today now. --- typing_extensions/CHANGELOG | 1 + .../src/test_typing_extensions.py | 61 +++++---- typing_extensions/src/typing_extensions.py | 119 +++++++++--------- 3 files changed, 102 insertions(+), 79 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 569fe50c2..8900c5885 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,6 @@ # Unreleased +- Re-export `typing.Unpack` and `typing.TypeVarTuple` on Python 3.11. - Add `ParamSpecArgs` and `ParamSpecKwargs` to `__all__`. - Improve "accepts only single type" error messages. - Improve the distributed package. Patch by Marc Mueller (@cdce8p). diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index 8e11eb671..7f14f3f9d 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -32,6 +32,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) @@ -157,8 +159,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): @@ -201,8 +204,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): @@ -245,8 +249,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): @@ -289,8 +294,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): @@ -738,7 +744,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), ()) @@ -1731,10 +1740,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) @@ -2313,11 +2324,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') @@ -2497,7 +2509,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]') def test_cannot_subclass_vars(self): with self.assertRaises(TypeError): @@ -2572,8 +2587,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): @@ -2617,7 +2634,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,)) diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 1e3e12829..dc0388196 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -1673,7 +1673,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 @@ -1729,84 +1731,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"): From 9a39406fa45fbfa76eb5d35afb7863f5f727ad87 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 17 Apr 2022 14:21:53 -0700 Subject: [PATCH 20/20] prepare release 4.2.0 (#1144) --- typing_extensions/CHANGELOG | 2 +- typing_extensions/pyproject.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 8900c5885..aa66e55c0 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,4 +1,4 @@ -# Unreleased +# Release 4.2.0 (April 17, 2022) - Re-export `typing.Unpack` and `typing.TypeVarTuple` on Python 3.11. - Add `ParamSpecArgs` and `ParamSpecKwargs` to `__all__`. diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml index fbd018017..217b9499a 100644 --- a/typing_extensions/pyproject.toml +++ b/typing_extensions/pyproject.toml @@ -6,8 +6,8 @@ build-backend = "flit_core.buildapi" # Project metadata [project] name = "typing_extensions" -version = "4.1.1" -description = "Backported and Experimental Type Hints for Python 3.6+" +version = "4.2.0" +description = "Backported and Experimental Type Hints for Python 3.7+" readme = "README.rst" requires-python = ">=3.7" urls.Home = "https://github.com/python/typing/blob/master/typing_extensions/README.rst" @@ -28,7 +28,7 @@ keywords = [ ] # Classifiers list: https://pypi.org/classifiers/ classifiers = [ - "Development Status :: 3 - Alpha", + "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: Python Software Foundation License",