From: TizenOpenSource Date: Tue, 6 Feb 2024 04:14:40 +0000 (+0900) Subject: Imported Upstream version 4.9.0 X-Git-Tag: upstream/4.9.0^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=81ae1d79dfc9c6fc59e2324ad0fc4412f9408d08;p=platform%2Fupstream%2Fpython3-typing_extensions.git Imported Upstream version 4.9.0 --- diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..fedc2a3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,347 @@ +# Release 4.9.0 (December 9, 2023) + +This feature release adds `typing_extensions.ReadOnly`, as specified +by PEP 705, and makes various other improvements, especially to +`@typing_extensions.deprecated()`. + +There are no changes since 4.9.0rc1. + +# Release 4.9.0rc1 (November 29, 2023) + +- Add support for PEP 705, adding `typing_extensions.ReadOnly`. Patch + by Jelle Zijlstra. +- All parameters on `NewType.__call__` are now positional-only. This means that + the signature of `typing_extensions.NewType.__call__` now exactly matches the + signature of `typing.NewType.__call__`. Patch by Alex Waygood. +- Fix bug with using `@deprecated` on a mixin class. Inheriting from a + deprecated class now raises a `DeprecationWarning`. Patch by Jelle Zijlstra. +- `@deprecated` now gives a better error message if you pass a non-`str` + argument to the `msg` parameter. Patch by Alex Waygood. +- `@deprecated` is now implemented as a class for better introspectability. + Patch by Jelle Zijlstra. +- Exclude `__match_args__` from `Protocol` members. + Backport of https://github.com/python/cpython/pull/110683 by Nikita Sobolev. +- When creating a `typing_extensions.NamedTuple` class, ensure `__set_name__` + is called on all objects that define `__set_name__` and exist in the values + of the `NamedTuple` class's class dictionary. Patch by Alex Waygood, + backporting https://github.com/python/cpython/pull/111876. +- Improve the error message when trying to call `issubclass()` against a + `Protocol` that has non-method members. Patch by Alex Waygood (backporting + https://github.com/python/cpython/pull/112344, by Randolph Scholz). + +# Release 4.8.0 (September 17, 2023) + +No changes since 4.8.0rc1. + +# Release 4.8.0rc1 (September 7, 2023) + +- Add `typing_extensions.Doc`, as proposed by PEP 727. Patch by + Sebastián Ramírez. +- Drop support for Python 3.7 (including PyPy-3.7). Patch by Alex Waygood. +- Fix bug where `get_original_bases()` would return incorrect results when + called on a concrete subclass of a generic class. Patch by Alex Waygood + (backporting https://github.com/python/cpython/pull/107584, by James + Hilton-Balfe). +- Fix bug where `ParamSpec(default=...)` would raise a `TypeError` on Python + versions <3.11. Patch by James Hilton-Balfe + +# Release 4.7.1 (July 2, 2023) + +- Fix support for `TypedDict`, `NamedTuple` and `is_protocol` on PyPy-3.7 and + PyPy-3.8. Patch by Alex Waygood. Note that PyPy-3.7 and PyPy-3.8 are unsupported + by the PyPy project. The next feature release of typing-extensions will + drop support for PyPy-3.7 and may also drop support for PyPy-3.8. + +# Release 4.7.0 (June 28, 2023) + +- This is expected to be the last feature release supporting Python 3.7, + which reaches its end of life on June 27, 2023. Version 4.8.0 will support + only Python 3.8.0 and up. +- Fix bug where a `typing_extensions.Protocol` class that had one or more + non-callable members would raise `TypeError` when `issubclass()` + was called against it, even if it defined a custom `__subclasshook__` + method. The correct behaviour -- which has now been restored -- is not to + raise `TypeError` in these situations if a custom `__subclasshook__` method + is defined. Patch by Alex Waygood (backporting + https://github.com/python/cpython/pull/105976). + +# Release 4.7.0rc1 (June 21, 2023) + +- Add `typing_extensions.get_protocol_members` and + `typing_extensions.is_protocol` (backport of CPython PR #104878). + Patch by Jelle Zijlstra. +- `typing_extensions` now re-exports all names in the standard library's + `typing` module, except the deprecated `ByteString`. Patch by Jelle + Zijlstra. +- Due to changes in the implementation of `typing_extensions.Protocol`, + `typing.runtime_checkable` can now be used on `typing_extensions.Protocol` + (previously, users had to use `typing_extensions.runtime_checkable` if they + were using `typing_extensions.Protocol`). +- Align the implementation of `TypedDict` with the implementation in the + standard library on Python 3.9 and higher. + `typing_extensions.TypedDict` is now a function instead of a class. The + private functions `_check_fails`, `_dict_new`, and `_typeddict_new` + have been removed. `is_typeddict` now returns `False` when called with + `TypedDict` itself as the argument. Patch by Jelle Zijlstra. +- Declare support for Python 3.12. Patch by Jelle Zijlstra. +- Fix tests on Python 3.13, which removes support for creating + `TypedDict` classes through the keyword-argument syntax. Patch by + Jelle Zijlstra. +- Fix a regression introduced in v4.6.3 that meant that + ``issubclass(object, typing_extensions.Protocol)`` would erroneously raise + ``TypeError``. Patch by Alex Waygood (backporting the CPython PR + https://github.com/python/cpython/pull/105239). +- Allow `Protocol` classes to inherit from `typing_extensions.Buffer` or + `collections.abc.Buffer`. Patch by Alex Waygood (backporting + https://github.com/python/cpython/pull/104827, by Jelle Zijlstra). +- Allow classes to inherit from both `typing.Protocol` and `typing_extensions.Protocol` + simultaneously. Since v4.6.0, this caused `TypeError` to be raised due to a + metaclass conflict. Patch by Alex Waygood. +- Backport several deprecations from CPython relating to unusual ways to + create `TypedDict`s and `NamedTuple`s. CPython PRs #105609 and #105780 + by Alex Waygood; `typing_extensions` backport by Jelle Zijlstra. + - Creating a `NamedTuple` using the functional syntax with keyword arguments + (`NT = NamedTuple("NT", a=int)`) is now deprecated. + - Creating a `NamedTuple` with zero fields using the syntax `NT = NamedTuple("NT")` + or `NT = NamedTuple("NT", None)` is now deprecated. + - Creating a `TypedDict` with zero fields using the syntax `TD = TypedDict("TD")` + or `TD = TypedDict("TD", None)` is now deprecated. +- Fix bug on Python 3.7 where a protocol `X` that had a member `a` would not be + considered an implicit subclass of an unrelated protocol `Y` that only has a + member `a`. Where the members of `X` are a superset of the members of `Y`, + `X` should always be considered a subclass of `Y` iff `Y` is a + runtime-checkable protocol that only has callable members. Patch by Alex + Waygood (backporting CPython PR + https://github.com/python/cpython/pull/105835). + +# Release 4.6.3 (June 1, 2023) + +- Fix a regression introduced in v4.6.0 in the implementation of + runtime-checkable protocols. The regression meant + that doing `class Foo(X, typing_extensions.Protocol)`, where `X` was a class that + had `abc.ABCMeta` as its metaclass, would then cause subsequent + `isinstance(1, X)` calls to erroneously raise `TypeError`. Patch by + Alex Waygood (backporting the CPython PR + https://github.com/python/cpython/pull/105152). +- Sync the repository's LICENSE file with that of CPython. + `typing_extensions` is distributed under the same license as + CPython itself. +- Skip a problematic test on Python 3.12.0b1. The test fails on 3.12.0b1 due to + a bug in CPython, which will be fixed in 3.12.0b2. The + `typing_extensions` test suite now passes on 3.12.0b1. + +# Release 4.6.2 (May 25, 2023) + +- Fix use of `@deprecated` on classes with `__new__` but no `__init__`. + Patch by Jelle Zijlstra. +- Fix regression in version 4.6.1 where comparing a generic class against a + runtime-checkable protocol using `isinstance()` would cause `AttributeError` + to be raised if using Python 3.7. + +# Release 4.6.1 (May 23, 2023) + +- Change deprecated `@runtime` to formal API `@runtime_checkable` in the error + message. Patch by Xuehai Pan. +- Fix regression in 4.6.0 where attempting to define a `Protocol` that was + generic over a `ParamSpec` or a `TypeVarTuple` would cause `TypeError` to be + raised. Patch by Alex Waygood. + +# Release 4.6.0 (May 22, 2023) + +- `typing_extensions` is now documented at + https://typing-extensions.readthedocs.io/en/latest/. Patch by Jelle Zijlstra. +- Add `typing_extensions.Buffer`, a marker class for buffer types, as proposed + by PEP 688. Equivalent to `collections.abc.Buffer` in Python 3.12. Patch by + Jelle Zijlstra. +- Backport two CPython PRs fixing various issues with `typing.Literal`: + https://github.com/python/cpython/pull/23294 and + https://github.com/python/cpython/pull/23383. Both CPython PRs were + originally by Yurii Karabas, and both were backported to Python >=3.9.1, but + no earlier. Patch by Alex Waygood. + + A side effect of one of the changes is that equality comparisons of `Literal` + objects will now raise a `TypeError` if one of the `Literal` objects being + compared has a mutable parameter. (Using mutable parameters with `Literal` is + not supported by PEP 586 or by any major static type checkers.) +- `Literal` is now reimplemented on all Python versions <= 3.10.0. The + `typing_extensions` version does not suffer from the bug that was fixed in + https://github.com/python/cpython/pull/29334. (The CPython bugfix was + backported to CPython 3.10.1 and 3.9.8, but no earlier.) +- Backport [CPython PR 26067](https://github.com/python/cpython/pull/26067) + (originally by Yurii Karabas), ensuring that `isinstance()` calls on + protocols raise `TypeError` when the protocol is not decorated with + `@runtime_checkable`. Patch by Alex Waygood. +- Backport several significant performance improvements to runtime-checkable + protocols that have been made in Python 3.12 (see + https://github.com/python/cpython/issues/74690 for details). Patch by Alex + Waygood. + + A side effect of one of the performance improvements is that the members of + a runtime-checkable protocol are now considered “frozen” at runtime as soon + as the class has been created. Monkey-patching attributes onto a + runtime-checkable protocol will still work, but will have no impact on + `isinstance()` checks comparing objects to the protocol. See + ["What's New in Python 3.12"](https://docs.python.org/3.12/whatsnew/3.12.html#typing) + for more details. +- `isinstance()` checks against runtime-checkable protocols now use + `inspect.getattr_static()` rather than `hasattr()` to lookup whether + attributes exist (backporting https://github.com/python/cpython/pull/103034). + This means that descriptors and `__getattr__` methods are no longer + unexpectedly evaluated during `isinstance()` checks against runtime-checkable + protocols. However, it may also mean that some objects which used to be + considered instances of a runtime-checkable protocol on older versions of + `typing_extensions` may no longer be considered instances of that protocol + using the new release, and vice versa. Most users are unlikely to be affected + by this change. Patch by Alex Waygood. +- Backport the ability to define `__init__` methods on Protocol classes, a + change made in Python 3.11 (originally implemented in + https://github.com/python/cpython/pull/31628 by Adrian Garcia Badaracco). + Patch by Alex Waygood. +- Speedup `isinstance(3, typing_extensions.SupportsIndex)` by >10x on Python + <3.12. Patch by Alex Waygood. +- Add `typing_extensions` versions of `SupportsInt`, `SupportsFloat`, + `SupportsComplex`, `SupportsBytes`, `SupportsAbs` and `SupportsRound`. These + have the same semantics as the versions from the `typing` module, but + `isinstance()` checks against the `typing_extensions` versions are >10x faster + at runtime on Python <3.12. Patch by Alex Waygood. +- Add `__orig_bases__` to non-generic TypedDicts, call-based TypedDicts, and + call-based NamedTuples. Other TypedDicts and NamedTuples already had the attribute. + Patch by Adrian Garcia Badaracco. +- Add `typing_extensions.get_original_bases`, a backport of + [`types.get_original_bases`](https://docs.python.org/3.12/library/types.html#types.get_original_bases), + introduced in Python 3.12 (CPython PR + https://github.com/python/cpython/pull/101827, originally by James + Hilton-Balfe). Patch by Alex Waygood. + + This function should always produce correct results when called on classes + constructed using features from `typing_extensions`. However, it may + produce incorrect results when called on some `NamedTuple` or `TypedDict` + classes that use `typing.{NamedTuple,TypedDict}` on Python <=3.11. +- Constructing a call-based `TypedDict` using keyword arguments for the fields + now causes a `DeprecationWarning` to be emitted. This matches the behaviour + of `typing.TypedDict` on 3.11 and 3.12. +- Backport the implementation of `NewType` from 3.10 (where it is implemented + as a class rather than a function). This allows user-defined `NewType`s to be + pickled. Patch by Alex Waygood. +- Fix tests and import on Python 3.12, where `typing.TypeVar` can no longer be + subclassed. Patch by Jelle Zijlstra. +- Add `typing_extensions.TypeAliasType`, a backport of `typing.TypeAliasType` + from PEP 695. Patch by Jelle Zijlstra. +- Backport changes to the repr of `typing.Unpack` that were made in order to + implement [PEP 692](https://peps.python.org/pep-0692/) (backport of + https://github.com/python/cpython/pull/104048). Patch by Alex Waygood. + +# Release 4.5.0 (February 14, 2023) + +- Runtime support for PEP 702, adding `typing_extensions.deprecated`. Patch + by Jelle Zijlstra. +- Add better default value for TypeVar `default` parameter, PEP 696. Enables + runtime check if `None` was passed as default. Patch by Marc Mueller (@cdce8p). +- The `@typing_extensions.override` decorator now sets the `.__override__` + attribute. Patch by Steven Troxler. +- Fix `get_type_hints()` on cross-module inherited `TypedDict` in 3.9 and 3.10. + Patch by Carl Meyer. +- Add `frozen_default` parameter on `dataclass_transform`. Patch by Erik De Bonte. + +# Release 4.4.0 (October 6, 2022) + +- Add `typing_extensions.Any` a backport of python 3.11's Any class which is + subclassable at runtime. (backport from python/cpython#31841, by Shantanu + and Jelle Zijlstra). Patch by James Hilton-Balfe (@Gobot1234). +- Add initial support for TypeVarLike `default` parameter, PEP 696. + Patch by Marc Mueller (@cdce8p). +- Runtime support for PEP 698, adding `typing_extensions.override`. Patch by + Jelle Zijlstra. +- Add the `infer_variance` parameter to `TypeVar`, as specified in PEP 695. + Patch by Jelle Zijlstra. + +# Release 4.3.0 (July 1, 2022) + +- Add `typing_extensions.NamedTuple`, allowing for generic `NamedTuple`s on + Python <3.11 (backport from python/cpython#92027, by Serhiy Storchaka). Patch + by Alex Waygood (@AlexWaygood). +- Adjust `typing_extensions.TypedDict` to allow for generic `TypedDict`s on + Python <3.11 (backport from python/cpython#27663, by Samodya Abey). Patch by + Alex Waygood (@AlexWaygood). + +# Release 4.2.0 (April 17, 2022) + +- 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). +- 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. +- 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) + +- 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` + and `typing_extensions.Unpack`. +- Add interaction of `Required` and `NotRequired` with `__required_keys__`, + `__optional_keys__` and `get_type_hints()`. Patch by David Cabot (@d-k-bo). +- Runtime support for PEP 675 and `typing_extensions.LiteralString`. +- Add `Never` and `assert_never`. Backport from bpo-46475. +- `ParamSpec` args and kwargs are now equal to themselves. Backport from + bpo-46676. Patch by Gregory Beauregard (@GBeauregard). +- Add `reveal_type`. Backport from bpo-46414. +- Runtime support for PEP 681 and `typing_extensions.dataclass_transform`. +- `Annotated` can now wrap `ClassVar` and `Final`. Backport from + bpo-46491. Patch by Gregory Beauregard (@GBeauregard). +- Add missed `Required` and `NotRequired` to `__all__`. Patch by + Yuri Karabas (@uriyyo). +- The `@final` decorator now sets the `__final__` attribute on the + decorated object to allow runtime introspection. Backport from + bpo-46342. +- Add `is_typeddict`. Patch by Chris Moradi (@chrismoradi) and James + Hilton-Balfe (@Gobot1234). + +# Release 4.0.1 (November 30, 2021) + +- Fix broken sdist in release 4.0.0. Patch by Adam Turner (@AA-Turner). +- Fix equality comparison for `Required` and `NotRequired`. Patch by + Jelle Zijlstra (@jellezijlstra). +- Fix usage of `Self` as a type argument. Patch by Chris Wesseling + (@CharString) and James Hilton-Balfe (@Gobot1234). + +# Release 4.0.0 (November 14, 2021) + +- Starting with version 4.0.0, typing_extensions uses Semantic Versioning. + See the README for more information. +- Dropped support for Python versions 3.5 and older, including Python 2.7. +- Simplified backports for Python 3.6.0 and newer. Patch by Adam Turner (@AA-Turner). + +## Added in version 4.0.0 + +- Runtime support for PEP 673 and `typing_extensions.Self`. Patch by + James Hilton-Balfe (@Gobot1234). +- Runtime support for PEP 655 and `typing_extensions.Required` and `NotRequired`. + Patch by David Foster (@davidfstr). + +## Removed in version 4.0.0 + +The following non-exported but non-private names have been removed as they are +unneeded for supporting Python 3.6 and newer. + +- TypingMeta +- OLD_GENERICS +- SUBS_TREE +- HAVE_ANNOTATED +- HAVE_PROTOCOLS +- V_co +- VT_co + +# Previous releases + +Prior to release 4.0.0 we did not provide a changelog. Please check +the Git history for details. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f26bcf4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,279 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see https://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see https://opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +Python software and documentation are licensed under the +Python Software Foundation License Version 2. + +Starting with Python 3.8.6, examples, recipes, and other code in +the documentation are dual licensed under the PSF License Version 2 +and the Zero-Clause BSD license. + +Some software incorporated into Python is under different licenses. +The licenses are listed with code falling under that license. + + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION +---------------------------------------------------------------------- + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..863e977 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,66 @@ +Metadata-Version: 2.1 +Name: typing_extensions +Version: 4.9.0 +Summary: Backported and Experimental Type Hints for Python 3.8+ +Keywords: annotations,backport,checker,checking,function,hinting,hints,type,typechecking,typehinting,typehints,typing +Author-email: "Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Michael Lee" +Requires-Python: >=3.8 +Description-Content-Type: text/markdown +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Python Software Foundation License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Topic :: Software Development +Project-URL: Bug Tracker, https://github.com/python/typing_extensions/issues +Project-URL: Changes, https://github.com/python/typing_extensions/blob/main/CHANGELOG.md +Project-URL: Documentation, https://typing-extensions.readthedocs.io/ +Project-URL: Home, https://github.com/python/typing_extensions +Project-URL: Q & A, https://github.com/python/typing/discussions +Project-URL: Repository, https://github.com/python/typing_extensions + +# Typing Extensions + +[![Chat at https://gitter.im/python/typing](https://badges.gitter.im/python/typing.svg)](https://gitter.im/python/typing) + +[Documentation](https://typing-extensions.readthedocs.io/en/latest/#) – +[PyPI](https://pypi.org/project/typing-extensions/) + +## Overview + +The `typing_extensions` module serves two related purposes: + +- Enable use of new type system features on older Python versions. For example, + `typing.TypeGuard` is new in Python 3.10, but `typing_extensions` allows + users on previous Python versions to use it too. +- Enable experimentation with new type system PEPs before they are accepted and + added to the `typing` module. + +`typing_extensions` is treated specially by static type checkers such as +mypy and pyright. Objects defined in `typing_extensions` are treated the same +way as equivalent forms in `typing`. + +`typing_extensions` uses +[Semantic Versioning](https://semver.org/). The +major version will be incremented only for backwards-incompatible changes. +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. + +## Included items + +See [the documentation](https://typing-extensions.readthedocs.io/en/latest/#) for a +complete listing of module contents. + +## Contributing + +See [CONTRIBUTING.md](https://github.com/python/typing_extensions/blob/main/CONTRIBUTING.md) +for how to contribute to `typing_extensions`. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..1eddb2a --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# Typing Extensions + +[![Chat at https://gitter.im/python/typing](https://badges.gitter.im/python/typing.svg)](https://gitter.im/python/typing) + +[Documentation](https://typing-extensions.readthedocs.io/en/latest/#) – +[PyPI](https://pypi.org/project/typing-extensions/) + +## Overview + +The `typing_extensions` module serves two related purposes: + +- Enable use of new type system features on older Python versions. For example, + `typing.TypeGuard` is new in Python 3.10, but `typing_extensions` allows + users on previous Python versions to use it too. +- Enable experimentation with new type system PEPs before they are accepted and + added to the `typing` module. + +`typing_extensions` is treated specially by static type checkers such as +mypy and pyright. Objects defined in `typing_extensions` are treated the same +way as equivalent forms in `typing`. + +`typing_extensions` uses +[Semantic Versioning](https://semver.org/). The +major version will be incremented only for backwards-incompatible changes. +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. + +## Included items + +See [the documentation](https://typing-extensions.readthedocs.io/en/latest/#) for a +complete listing of module contents. + +## Contributing + +See [CONTRIBUTING.md](https://github.com/python/typing_extensions/blob/main/CONTRIBUTING.md) +for how to contribute to `typing_extensions`. diff --git a/packaging/python3-typing_extensions.spec b/packaging/python3-typing_extensions.spec new file mode 100644 index 0000000..fc86833 --- /dev/null +++ b/packaging/python3-typing_extensions.spec @@ -0,0 +1,53 @@ +Name: python3-typing_extensions +Version: 4.8.0 +Release: 0 +Summary: Backported and Experimental Type Hints for Python 3.8+ +License: Python-2.0 +URL: https://github.com/python/typing_extensions +Source0: https://files.pythonhosted.org/packages/source/t/typing_extensions/typing_extensions-%{version}.tar.gz + + +Provides: python-typing-extensions = %{version}-%{release} +BuildArch: noarch + +%description +The typing_extensions module serves two related purposes: + + * Enable use of new type system features on older Python versions. + For example, typing.TypeGuard is new in Python 3.10, but + typing_extensions allows users on previous Python versions to use + it too. + * Enable experimentation with new type system PEPs before they are + accepted and added to the typing module. + +New features may be added to typing_extensions as soon as they are +specified in a PEP that has been added to the python/peps repository. +If the PEP is accepted, the feature will then be added to typing for +the next CPython release. No typing PEP has been rejected so far, so +we haven't yet figured out how to deal with that possibility. + +Starting with version 4.0.0, typing_extensions uses Semantic Versioning. +The major version is incremented for all backwards-incompatible changes. +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. + +%prep +%setup -q -n typing_extensions-%{version} + +%build +%{_bindir}/python3 setup.py build + +%install +%{_bindir}/python3 setup.py install --prefix=%{_prefix} --root=%{buildroot} + +%files +%license LICENSE +%{python_sitelib}/typing_extensions.py* +%pycache_only %{python_sitelib}/__pycache__/typing_extensions* +%{python_sitelib}/typing_extensions-%{version}*-info + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5bea3e9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,61 @@ +# Build system requirements. +[build-system] +requires = ["flit_core >=3.4,<4"] +build-backend = "flit_core.buildapi" + +# Project metadata +[project] +name = "typing_extensions" +version = "4.9.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +readme = "README.md" +requires-python = ">=3.8" +license = { file = "LICENSE" } +keywords = [ + "annotations", + "backport", + "checker", + "checking", + "function", + "hinting", + "hints", + "type", + "typechecking", + "typehinting", + "typehints", + "typing", +] +# Classifiers list: https://pypi.org/classifiers/ +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: Python Software Foundation License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Software Development", +] + +[project.urls] +Home = "https://github.com/python/typing_extensions" +Repository = "https://github.com/python/typing_extensions" +Changes = "https://github.com/python/typing_extensions/blob/main/CHANGELOG.md" +Documentation = "https://typing-extensions.readthedocs.io/" +"Bug Tracker" = "https://github.com/python/typing_extensions/issues" +"Q & A" = "https://github.com/python/typing/discussions" + +# Project metadata -- authors. Flit stores this as a list of dicts, so it can't +# be inline above. +[[project.authors]] +name = "Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Michael Lee" +email = "levkivskyi@gmail.com" + +[tool.flit.sdist] +include = ["CHANGELOG.md", "README.md", "tox.ini", "*/*test*.py"] +exclude = [] diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..7f1a176 --- /dev/null +++ b/setup.py @@ -0,0 +1,4 @@ +from setuptools import setup + +if __name__ == "__main__": + setup() diff --git a/src/_typed_dict_test_helper.py b/src/_typed_dict_test_helper.py new file mode 100644 index 0000000..c5582b1 --- /dev/null +++ b/src/_typed_dict_test_helper.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from typing import Generic, Optional, T +from typing_extensions import TypedDict, Annotated, Required + + +# this class must not be imported into test_typing_extensions.py at top level, otherwise +# the test_get_type_hints_cross_module_subclass test will pass for the wrong reason +class _DoNotImport: + pass + + +class Foo(TypedDict): + a: _DoNotImport + + +class FooGeneric(TypedDict, Generic[T]): + a: Optional[T] + + +class VeryAnnotated(TypedDict, total=False): + a: Annotated[Annotated[Annotated[Required[int], "a"], "b"], "c"] diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py new file mode 100644 index 0000000..77876b7 --- /dev/null +++ b/src/test_typing_extensions.py @@ -0,0 +1,6276 @@ +import sys +import os +import abc +import gc +import io +import contextlib +import collections +from collections import defaultdict +import collections.abc +import copy +from functools import lru_cache +import importlib +import inspect +import pickle +import re +import subprocess +import tempfile +import textwrap +import types +from pathlib import Path +from unittest import TestCase, main, skipUnless, skipIf +from unittest.mock import patch +import typing +from typing import Optional, Union, AnyStr +from typing import T, KT, VT # Not in __all__. +from typing import Tuple, List, Set, Dict, Iterable, Iterator, Callable +from typing import Generic +from typing import no_type_check +import warnings + +import typing_extensions +from typing_extensions import NoReturn, Any, 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, ReadOnly +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, get_original_bases +from typing_extensions import clear_overloads, get_overloads, overload +from typing_extensions import NamedTuple +from typing_extensions import override, deprecated, Buffer, TypeAliasType, TypeVar, get_protocol_members, is_protocol +from typing_extensions import Doc +from _typed_dict_test_helper import Foo, FooGeneric, VeryAnnotated + +# Flags used to mark tests that only apply after a specific +# version of the typing module. +TYPING_3_9_0 = sys.version_info[:3] >= (3, 9, 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) + +# 3.12 changes the representation of Unpack[] (PEP 692) +TYPING_3_12_0 = sys.version_info[:3] >= (3, 12, 0) + +# https://github.com/python/cpython/pull/27017 was backported into some 3.9 and 3.10 +# versions, but not all +HAS_FORWARD_MODULE = "module" in inspect.signature(typing._type_check).parameters + +skip_if_early_py313_alpha = skipIf( + sys.version_info[:4] == (3, 13, 0, 'alpha') and sys.version_info.serial < 3, + "Bugfixes will be released in 3.13.0a3" +) + +ANN_MODULE_SOURCE = '''\ +from typing import Optional +from functools import wraps + +__annotations__[1] = 2 + +class C: + + x = 5; y: Optional['C'] = None + +from typing import Tuple +x: int = 5; y: str = x; f: Tuple[int, int] + +class M(type): + + __annotations__['123'] = 123 + o: type = object + +(pars): bool = True + +class D(C): + j: str = 'hi'; k: str= 'bye' + +from types import new_class +h_class = new_class('H', (C,)) +j_class = new_class('J') + +class F(): + z: int = 5 + def __init__(self, x): + pass + +class Y(F): + def __init__(self): + super(F, self).__init__(123) + +class Meta(type): + def __new__(meta, name, bases, namespace): + return super().__new__(meta, name, bases, namespace) + +class S(metaclass = Meta): + x: str = 'something' + y: str = 'something else' + +def foo(x: int = 10): + def bar(y: List[str]): + x: str = 'yes' + bar() + +def dec(func): + @wraps(func) + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + return wrapper +''' + +ANN_MODULE_2_SOURCE = '''\ +from typing import no_type_check, ClassVar + +i: int = 1 +j: int +x: float = i/10 + +def f(): + class C: ... + return C() + +f().new_attr: object = object() + +class C: + def __init__(self, x: int) -> None: + self.x = x + +c = C(5) +c.new_attr: int = 10 + +__annotations__ = {} + + +@no_type_check +class NTC: + def meth(self, param: complex) -> None: + ... + +class CV: + var: ClassVar['CV'] + +CV.var = CV() +''' + +ANN_MODULE_3_SOURCE = '''\ +def f_bad_ann(): + __annotations__[1] = 2 + +class C_OK: + def __init__(self, x: int) -> None: + self.x: no_such_name = x # This one is OK as proposed by Guido + +class D_bad_ann: + def __init__(self, x: int) -> None: + sfel.y: int = 0 + +def g_bad_ann(): + no_such_name.attr: int = 0 +''' + + +class BaseTestCase(TestCase): + def assertIsSubclass(self, cls, class_or_tuple, msg=None): + if not issubclass(cls, class_or_tuple): + message = f'{cls!r} is not a subclass of {repr(class_or_tuple)}' + if msg is not None: + message += f' : {msg}' + raise self.failureException(message) + + def assertNotIsSubclass(self, cls, class_or_tuple, msg=None): + if issubclass(cls, class_or_tuple): + message = f'{cls!r} is a subclass of {repr(class_or_tuple)}' + if msg is not None: + message += f' : {msg}' + raise self.failureException(message) + + +class Employee: + pass + + +class BottomTypeTestsMixin: + bottom_type: ClassVar[Any] + + def test_equality(self): + self.assertEqual(self.bottom_type, self.bottom_type) + self.assertIs(self.bottom_type, self.bottom_type) + self.assertNotEqual(self.bottom_type, None) + + def test_get_origin(self): + self.assertIs(get_origin(self.bottom_type), None) + + def test_instance_type_error(self): + with self.assertRaises(TypeError): + isinstance(42, self.bottom_type) + + def test_subclass_type_error(self): + with self.assertRaises(TypeError): + issubclass(Employee, self.bottom_type) + with self.assertRaises(TypeError): + issubclass(NoReturn, self.bottom_type) + + def test_not_generic(self): + with self.assertRaises(TypeError): + self.bottom_type[int] + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class A(self.bottom_type): + pass + with self.assertRaises(TypeError): + class A(type(self.bottom_type)): + pass + + def test_cannot_instantiate(self): + with self.assertRaises(TypeError): + self.bottom_type() + with self.assertRaises(TypeError): + type(self.bottom_type)() + + def test_pickle(self): + for proto in range(pickle.HIGHEST_PROTOCOL): + pickled = pickle.dumps(self.bottom_type, protocol=proto) + self.assertIs(self.bottom_type, pickle.loads(pickled)) + + +class NoReturnTests(BottomTypeTestsMixin, BaseTestCase): + bottom_type = NoReturn + + def test_repr(self): + if hasattr(typing, 'NoReturn'): + self.assertEqual(repr(NoReturn), 'typing.NoReturn') + else: + self.assertEqual(repr(NoReturn), 'typing_extensions.NoReturn') + + def test_get_type_hints(self): + def some(arg: NoReturn) -> NoReturn: ... + def some_str(arg: 'NoReturn') -> 'typing.NoReturn': ... + + expected = {'arg': NoReturn, 'return': NoReturn} + for target in some, some_str: + with self.subTest(target=target): + self.assertEqual(gth(target), expected) + + def test_not_equality(self): + self.assertNotEqual(NoReturn, Never) + self.assertNotEqual(Never, NoReturn) + + +class NeverTests(BottomTypeTestsMixin, BaseTestCase): + bottom_type = Never + + def test_repr(self): + if hasattr(typing, 'Never'): + self.assertEqual(repr(Never), 'typing.Never') + else: + self.assertEqual(repr(Never), 'typing_extensions.Never') + + def test_get_type_hints(self): + def some(arg: Never) -> Never: ... + def some_str(arg: 'Never') -> 'typing_extensions.Never': ... + + expected = {'arg': Never, 'return': Never} + for target in [some, some_str]: + with self.subTest(target=target): + self.assertEqual(gth(target), expected) + + +class AssertNeverTests(BaseTestCase): + def test_exception(self): + with self.assertRaises(AssertionError): + assert_never(None) + + +class OverrideTests(BaseTestCase): + def test_override(self): + class Base: + def normal_method(self): ... + @staticmethod + def static_method_good_order(): ... + @staticmethod + def static_method_bad_order(): ... + @staticmethod + def decorator_with_slots(): ... + + class Derived(Base): + @override + def normal_method(self): + return 42 + + @staticmethod + @override + def static_method_good_order(): + return 42 + + @override + @staticmethod + def static_method_bad_order(): + return 42 + + + self.assertIsSubclass(Derived, Base) + instance = Derived() + self.assertEqual(instance.normal_method(), 42) + self.assertIs(True, instance.normal_method.__override__) + self.assertEqual(Derived.static_method_good_order(), 42) + self.assertIs(True, Derived.static_method_good_order.__override__) + self.assertEqual(Derived.static_method_bad_order(), 42) + self.assertIs(False, hasattr(Derived.static_method_bad_order, "__override__")) + + +class DeprecatedTests(BaseTestCase): + def test_dunder_deprecated(self): + @deprecated("A will go away soon") + class A: + pass + + self.assertEqual(A.__deprecated__, "A will go away soon") + self.assertIsInstance(A, type) + + @deprecated("b will go away soon") + def b(): + pass + + self.assertEqual(b.__deprecated__, "b will go away soon") + self.assertIsInstance(b, types.FunctionType) + + @overload + @deprecated("no more ints") + def h(x: int) -> int: ... + @overload + def h(x: str) -> str: ... + def h(x): + return x + + overloads = get_overloads(h) + self.assertEqual(len(overloads), 2) + self.assertEqual(overloads[0].__deprecated__, "no more ints") + + def test_class(self): + @deprecated("A will go away soon") + class A: + pass + + with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"): + A() + with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"): + with self.assertRaises(TypeError): + A(42) + + def test_class_with_init(self): + @deprecated("HasInit will go away soon") + class HasInit: + def __init__(self, x): + self.x = x + + with self.assertWarnsRegex(DeprecationWarning, "HasInit will go away soon"): + instance = HasInit(42) + self.assertEqual(instance.x, 42) + + def test_class_with_new(self): + has_new_called = False + + @deprecated("HasNew will go away soon") + class HasNew: + def __new__(cls, x): + nonlocal has_new_called + has_new_called = True + return super().__new__(cls) + + def __init__(self, x) -> None: + self.x = x + + with self.assertWarnsRegex(DeprecationWarning, "HasNew will go away soon"): + instance = HasNew(42) + self.assertEqual(instance.x, 42) + self.assertTrue(has_new_called) + + def test_class_with_inherited_new(self): + new_base_called = False + + class NewBase: + def __new__(cls, x): + nonlocal new_base_called + new_base_called = True + return super().__new__(cls) + + def __init__(self, x) -> None: + self.x = x + + @deprecated("HasInheritedNew will go away soon") + class HasInheritedNew(NewBase): + pass + + with self.assertWarnsRegex(DeprecationWarning, "HasInheritedNew will go away soon"): + instance = HasInheritedNew(42) + self.assertEqual(instance.x, 42) + self.assertTrue(new_base_called) + + def test_class_with_new_but_no_init(self): + new_called = False + + @deprecated("HasNewNoInit will go away soon") + class HasNewNoInit: + def __new__(cls, x): + nonlocal new_called + new_called = True + obj = super().__new__(cls) + obj.x = x + return obj + + with self.assertWarnsRegex(DeprecationWarning, "HasNewNoInit will go away soon"): + instance = HasNewNoInit(42) + self.assertEqual(instance.x, 42) + self.assertTrue(new_called) + + def test_mixin_class(self): + @deprecated("Mixin will go away soon") + class Mixin: + pass + + class Base: + def __init__(self, a) -> None: + self.a = a + + with self.assertWarnsRegex(DeprecationWarning, "Mixin will go away soon"): + class Child(Base, Mixin): + pass + + instance = Child(42) + self.assertEqual(instance.a, 42) + + def test_existing_init_subclass(self): + @deprecated("C will go away soon") + class C: + def __init_subclass__(cls) -> None: + cls.inited = True + + with self.assertWarnsRegex(DeprecationWarning, "C will go away soon"): + C() + + with self.assertWarnsRegex(DeprecationWarning, "C will go away soon"): + class D(C): + pass + + self.assertTrue(D.inited) + self.assertIsInstance(D(), D) # no deprecation + + def test_existing_init_subclass_in_base(self): + class Base: + def __init_subclass__(cls, x) -> None: + cls.inited = x + + @deprecated("C will go away soon") + class C(Base, x=42): + pass + + self.assertEqual(C.inited, 42) + + with self.assertWarnsRegex(DeprecationWarning, "C will go away soon"): + C() + + with self.assertWarnsRegex(DeprecationWarning, "C will go away soon"): + class D(C, x=3): + pass + + self.assertEqual(D.inited, 3) + + def test_init_subclass_has_correct_cls(self): + init_subclass_saw = None + + @deprecated("Base will go away soon") + class Base: + def __init_subclass__(cls) -> None: + nonlocal init_subclass_saw + init_subclass_saw = cls + + self.assertIsNone(init_subclass_saw) + + with self.assertWarnsRegex(DeprecationWarning, "Base will go away soon"): + class C(Base): + pass + + self.assertIs(init_subclass_saw, C) + + def test_init_subclass_with_explicit_classmethod(self): + init_subclass_saw = None + + @deprecated("Base will go away soon") + class Base: + @classmethod + def __init_subclass__(cls) -> None: + nonlocal init_subclass_saw + init_subclass_saw = cls + + self.assertIsNone(init_subclass_saw) + + with self.assertWarnsRegex(DeprecationWarning, "Base will go away soon"): + class C(Base): + pass + + self.assertIs(init_subclass_saw, C) + + def test_function(self): + @deprecated("b will go away soon") + def b(): + pass + + with self.assertWarnsRegex(DeprecationWarning, "b will go away soon"): + b() + + def test_method(self): + class Capybara: + @deprecated("x will go away soon") + def x(self): + pass + + instance = Capybara() + with self.assertWarnsRegex(DeprecationWarning, "x will go away soon"): + instance.x() + + def test_property(self): + class Capybara: + @property + @deprecated("x will go away soon") + def x(self): + pass + + @property + def no_more_setting(self): + return 42 + + @no_more_setting.setter + @deprecated("no more setting") + def no_more_setting(self, value): + pass + + instance = Capybara() + with self.assertWarnsRegex(DeprecationWarning, "x will go away soon"): + instance.x + + with warnings.catch_warnings(): + warnings.simplefilter("error") + self.assertEqual(instance.no_more_setting, 42) + + with self.assertWarnsRegex(DeprecationWarning, "no more setting"): + instance.no_more_setting = 42 + + def test_category(self): + @deprecated("c will go away soon", category=RuntimeWarning) + def c(): + pass + + with self.assertWarnsRegex(RuntimeWarning, "c will go away soon"): + c() + + def test_turn_off_warnings(self): + @deprecated("d will go away soon", category=None) + def d(): + pass + + with warnings.catch_warnings(): + warnings.simplefilter("error") + d() + + def test_only_strings_allowed(self): + with self.assertRaisesRegex( + TypeError, + "Expected an object of type str for 'message', not 'type'" + ): + @deprecated + class Foo: ... + + with self.assertRaisesRegex( + TypeError, + "Expected an object of type str for 'message', not 'function'" + ): + @deprecated + def foo(): ... + + def test_no_retained_references_to_wrapper_instance(self): + @deprecated('depr') + def d(): pass + + self.assertFalse(any( + isinstance(cell.cell_contents, deprecated) for cell in d.__closure__ + )) + + +class AnyTests(BaseTestCase): + def test_can_subclass(self): + class Mock(Any): pass + self.assertTrue(issubclass(Mock, Any)) + self.assertIsInstance(Mock(), Mock) + + class Something: pass + self.assertFalse(issubclass(Something, Any)) + self.assertNotIsInstance(Something(), Mock) + + class MockSomething(Something, Mock): pass + self.assertTrue(issubclass(MockSomething, Any)) + ms = MockSomething() + self.assertIsInstance(ms, MockSomething) + self.assertIsInstance(ms, Something) + self.assertIsInstance(ms, Mock) + + class SubclassesAny(Any): + ... + + def test_repr(self): + if sys.version_info >= (3, 11): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(Any), f"{mod_name}.Any") + + @skipIf(sys.version_info[:3] == (3, 11, 0), "A bug was fixed in 3.11.1") + def test_repr_on_Any_subclass(self): + self.assertEqual( + repr(self.SubclassesAny), + f"" + ) + + def test_instantiation(self): + with self.assertRaises(TypeError): + Any() + + self.SubclassesAny() + + def test_isinstance(self): + with self.assertRaises(TypeError): + isinstance(object(), Any) + + isinstance(object(), self.SubclassesAny) + + +class ClassVarTests(BaseTestCase): + + def test_basics(self): + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + ClassVar[1] + with self.assertRaises(TypeError): + ClassVar[int, str] + with self.assertRaises(TypeError): + ClassVar[int][str] + + def test_repr(self): + if hasattr(typing, 'ClassVar'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(ClassVar), mod_name + '.ClassVar') + cv = ClassVar[int] + self.assertEqual(repr(cv), mod_name + '.ClassVar[int]') + cv = ClassVar[Employee] + self.assertEqual(repr(cv), mod_name + f'.ClassVar[{__name__}.Employee]') + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(ClassVar)): + pass + with self.assertRaises(TypeError): + class C(type(ClassVar[int])): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + ClassVar() + with self.assertRaises(TypeError): + type(ClassVar)() + with self.assertRaises(TypeError): + type(ClassVar[Optional[int]])() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, ClassVar[int]) + with self.assertRaises(TypeError): + issubclass(int, ClassVar) + + +class FinalTests(BaseTestCase): + + def test_basics(self): + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + Final[1] + with self.assertRaises(TypeError): + Final[int, str] + with self.assertRaises(TypeError): + Final[int][str] + + def test_repr(self): + self.assertEqual(repr(Final), 'typing.Final') + cv = Final[int] + self.assertEqual(repr(cv), 'typing.Final[int]') + cv = Final[Employee] + self.assertEqual(repr(cv), f'typing.Final[{__name__}.Employee]') + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(Final)): + pass + with self.assertRaises(TypeError): + class C(type(Final[int])): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + Final() + with self.assertRaises(TypeError): + type(Final)() + with self.assertRaises(TypeError): + type(Final[Optional[int]])() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, Final[int]) + with self.assertRaises(TypeError): + issubclass(int, Final) + + +class RequiredTests(BaseTestCase): + + def test_basics(self): + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + Required[1] + with self.assertRaises(TypeError): + Required[int, str] + with self.assertRaises(TypeError): + Required[int][str] + + def test_repr(self): + if hasattr(typing, 'Required'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(Required), mod_name + '.Required') + cv = Required[int] + self.assertEqual(repr(cv), mod_name + '.Required[int]') + cv = Required[Employee] + self.assertEqual(repr(cv), mod_name + '.Required[%s.Employee]' % __name__) + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(Required)): + pass + with self.assertRaises(TypeError): + class C(type(Required[int])): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + Required() + with self.assertRaises(TypeError): + type(Required)() + with self.assertRaises(TypeError): + type(Required[Optional[int]])() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, Required[int]) + with self.assertRaises(TypeError): + issubclass(int, Required) + + +class NotRequiredTests(BaseTestCase): + + def test_basics(self): + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + NotRequired[1] + with self.assertRaises(TypeError): + NotRequired[int, str] + with self.assertRaises(TypeError): + NotRequired[int][str] + + def test_repr(self): + if hasattr(typing, 'NotRequired'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(NotRequired), mod_name + '.NotRequired') + cv = NotRequired[int] + self.assertEqual(repr(cv), mod_name + '.NotRequired[int]') + cv = NotRequired[Employee] + self.assertEqual(repr(cv), mod_name + '.NotRequired[%s.Employee]' % __name__) + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(NotRequired)): + pass + with self.assertRaises(TypeError): + class C(type(NotRequired[int])): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + NotRequired() + with self.assertRaises(TypeError): + type(NotRequired)() + with self.assertRaises(TypeError): + type(NotRequired[Optional[int]])() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, NotRequired[int]) + with self.assertRaises(TypeError): + issubclass(int, NotRequired) + + +class IntVarTests(BaseTestCase): + def test_valid(self): + T_ints = IntVar("T_ints") + + def test_invalid(self): + with self.assertRaises(TypeError): + T_ints = IntVar("T_ints", int) + with self.assertRaises(TypeError): + T_ints = IntVar("T_ints", bound=int) + with self.assertRaises(TypeError): + T_ints = IntVar("T_ints", covariant=True) + + +class LiteralTests(BaseTestCase): + def test_basics(self): + Literal[1] + Literal[1, 2, 3] + Literal["x", "y", "z"] + Literal[None] + + def test_enum(self): + import enum + class My(enum.Enum): + A = 'A' + + self.assertEqual(Literal[My.A].__args__, (My.A,)) + + def test_illegal_parameters_do_not_raise_runtime_errors(self): + # Type checkers should reject these types, but we do not + # raise errors at runtime to maintain maximum flexibility + Literal[int] + Literal[Literal[1, 2], Literal[4, 5]] + Literal[3j + 2, ..., ()] + Literal[b"foo", u"bar"] + Literal[{"foo": 3, "bar": 4}] + Literal[T] + + def test_literals_inside_other_types(self): + List[Literal[1, 2, 3]] + List[Literal[("foo", "bar", "baz")]] + + def test_repr(self): + # we backport various bugfixes that were added in 3.10.1 and earlier + if sys.version_info >= (3, 10, 1): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(Literal[1]), mod_name + ".Literal[1]") + self.assertEqual(repr(Literal[1, True, "foo"]), mod_name + ".Literal[1, True, 'foo']") + self.assertEqual(repr(Literal[int]), mod_name + ".Literal[int]") + self.assertEqual(repr(Literal), mod_name + ".Literal") + self.assertEqual(repr(Literal[None]), mod_name + ".Literal[None]") + self.assertEqual(repr(Literal[1, 2, 3, 3]), mod_name + ".Literal[1, 2, 3]") + + def test_cannot_init(self): + with self.assertRaises(TypeError): + Literal() + with self.assertRaises(TypeError): + Literal[1]() + with self.assertRaises(TypeError): + type(Literal)() + with self.assertRaises(TypeError): + type(Literal[1])() + + def test_no_isinstance_or_issubclass(self): + with self.assertRaises(TypeError): + isinstance(1, Literal[1]) + with self.assertRaises(TypeError): + isinstance(int, Literal[1]) + with self.assertRaises(TypeError): + issubclass(1, Literal[1]) + with self.assertRaises(TypeError): + issubclass(int, Literal[1]) + + def test_no_subclassing(self): + with self.assertRaises(TypeError): + class Foo(Literal[1]): pass + with self.assertRaises(TypeError): + class Bar(Literal): pass + + def test_no_multiple_subscripts(self): + with self.assertRaises(TypeError): + Literal[1][1] + + def test_equal(self): + self.assertNotEqual(Literal[0], Literal[False]) + self.assertNotEqual(Literal[True], Literal[1]) + self.assertNotEqual(Literal[1], Literal[2]) + self.assertNotEqual(Literal[1, True], Literal[1]) + self.assertNotEqual(Literal[1, True], Literal[1, 1]) + self.assertNotEqual(Literal[1, 2], Literal[True, 2]) + self.assertEqual(Literal[1], Literal[1]) + self.assertEqual(Literal[1, 2], Literal[2, 1]) + self.assertEqual(Literal[1, 2, 3], Literal[1, 2, 3, 3]) + + def test_hash(self): + self.assertEqual(hash(Literal[1]), hash(Literal[1])) + self.assertEqual(hash(Literal[1, 2]), hash(Literal[2, 1])) + self.assertEqual(hash(Literal[1, 2, 3]), hash(Literal[1, 2, 3, 3])) + + def test_args(self): + self.assertEqual(Literal[1, 2, 3].__args__, (1, 2, 3)) + self.assertEqual(Literal[1, 2, 3, 3].__args__, (1, 2, 3)) + self.assertEqual(Literal[1, Literal[2], Literal[3, 4]].__args__, (1, 2, 3, 4)) + # Mutable arguments will not be deduplicated + self.assertEqual(Literal[[], []].__args__, ([], [])) + + def test_union_of_literals(self): + self.assertEqual(Union[Literal[1], Literal[2]].__args__, + (Literal[1], Literal[2])) + self.assertEqual(Union[Literal[1], Literal[1]], + Literal[1]) + + self.assertEqual(Union[Literal[False], Literal[0]].__args__, + (Literal[False], Literal[0])) + self.assertEqual(Union[Literal[True], Literal[1]].__args__, + (Literal[True], Literal[1])) + + import enum + class Ints(enum.IntEnum): + A = 0 + B = 1 + + self.assertEqual(Union[Literal[Ints.A], Literal[Ints.B]].__args__, + (Literal[Ints.A], Literal[Ints.B])) + + self.assertEqual(Union[Literal[Ints.A], Literal[Ints.A]], + Literal[Ints.A]) + self.assertEqual(Union[Literal[Ints.B], Literal[Ints.B]], + Literal[Ints.B]) + + self.assertEqual(Union[Literal[0], Literal[Ints.A], Literal[False]].__args__, + (Literal[0], Literal[Ints.A], Literal[False])) + self.assertEqual(Union[Literal[1], Literal[Ints.B], Literal[True]].__args__, + (Literal[1], Literal[Ints.B], Literal[True])) + + @skipUnless(TYPING_3_10_0, "Python 3.10+ required") + def test_or_type_operator_with_Literal(self): + self.assertEqual((Literal[1] | Literal[2]).__args__, + (Literal[1], Literal[2])) + + self.assertEqual((Literal[0] | Literal[False]).__args__, + (Literal[0], Literal[False])) + self.assertEqual((Literal[1] | Literal[True]).__args__, + (Literal[1], Literal[True])) + + self.assertEqual(Literal[1] | Literal[1], Literal[1]) + self.assertEqual(Literal['a'] | Literal['a'], Literal['a']) + + import enum + class Ints(enum.IntEnum): + A = 0 + B = 1 + + self.assertEqual(Literal[Ints.A] | Literal[Ints.A], Literal[Ints.A]) + self.assertEqual(Literal[Ints.B] | Literal[Ints.B], Literal[Ints.B]) + + self.assertEqual((Literal[Ints.B] | Literal[Ints.A]).__args__, + (Literal[Ints.B], Literal[Ints.A])) + + self.assertEqual((Literal[0] | Literal[Ints.A]).__args__, + (Literal[0], Literal[Ints.A])) + self.assertEqual((Literal[1] | Literal[Ints.B]).__args__, + (Literal[1], Literal[Ints.B])) + + def test_flatten(self): + l1 = Literal[Literal[1], Literal[2], Literal[3]] + l2 = Literal[Literal[1, 2], 3] + l3 = Literal[Literal[1, 2, 3]] + for lit in l1, l2, l3: + self.assertEqual(lit, Literal[1, 2, 3]) + self.assertEqual(lit.__args__, (1, 2, 3)) + + def test_does_not_flatten_enum(self): + import enum + class Ints(enum.IntEnum): + A = 1 + B = 2 + + literal = Literal[ + Literal[Ints.A], + Literal[Ints.B], + Literal[1], + Literal[2], + ] + self.assertEqual(literal.__args__, (Ints.A, Ints.B, 1, 2)) + + def test_caching_of_Literal_respects_type(self): + self.assertIs(type(Literal[1].__args__[0]), int) + self.assertIs(type(Literal[True].__args__[0]), bool) + + +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): + with self.assertRaises(RuntimeError): + + @overload + def blah(): + pass + + blah() + + def test_overload_succeeds(self): + @overload + def blah(): + pass + + def blah(): + pass + + blah() + + @skipIf( + sys.implementation.name == "pypy", + "sum() and print() are not compiled in pypy" + ) + @patch( + f"{registry_holder.__name__}._overload_registry", + defaultdict(lambda: defaultdict(dict)) + ) + def test_overload_on_compiled_functions(self): + registry = registry_holder._overload_registry + # The registry starts out empty: + self.assertEqual(registry, {}) + + # This should just not fail: + overload(sum) + overload(print) + + # No overloads are recorded: + self.assertEqual(get_overloads(sum), []) + self.assertEqual(get_overloads(print), []) + + 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]) + # Unrelated function still has no overloads: + def not_overloaded(): pass + self.assertEqual(list(get_overloads(not_overloaded)), []) + + # 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): + + 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') + +class AwaitableWrapper(Awaitable[T_a]): + + def __init__(self, value): + self.value = value + + def __await__(self) -> typing.Iterator[T_a]: + yield + return self.value + +class AsyncIteratorWrapper(AsyncIterator[T_a]): + + def __init__(self, value: Iterable[T_a]): + self.value = value + + def __aiter__(self) -> AsyncIterator[T_a]: + return self + + async def __anext__(self) -> T_a: + data = await self.value + if data: + return data + else: + raise StopAsyncIteration + +class ACM: + async def __aenter__(self) -> int: + return 42 + + async def __aexit__(self, etype, eval, tb): + return None + + + +class A: + y: float +class B(A): + x: ClassVar[Optional['B']] = None + y: int + b: int +class CSub(B): + z: ClassVar['CSub'] = B() +class G(Generic[T]): + lst: ClassVar[List[T]] = [] + +class Loop: + attr: Final['Loop'] + +class NoneAndForward: + parent: 'NoneAndForward' + meaning: None + +class XRepr(NamedTuple): + x: int + y: int = 1 + + def __str__(self): + return f'{self.x} -> {self.y}' + + def __add__(self, other): + return 0 + +@runtime_checkable +class HasCallProtocol(Protocol): + __call__: typing.Callable + + +async def g_with(am: AsyncContextManager[int]): + x: int + async with am as x: + return x + +try: + g_with(ACM()).send(None) +except StopIteration as e: + assert e.args[0] == 42 + +Label = TypedDict('Label', [('label', str)]) + +class Point2D(TypedDict): + x: int + y: int + +class Point2Dor3D(Point2D, total=False): + z: int + +class LabelPoint2D(Point2D, Label): ... + +class Options(TypedDict, total=False): + log_level: int + log_path: str + +class BaseAnimal(TypedDict): + name: str + +class Animal(BaseAnimal, total=False): + voice: str + tail: bool + +class Cat(Animal): + fur_color: str + +class TotalMovie(TypedDict): + title: str + year: NotRequired[int] + +class NontotalMovie(TypedDict, total=False): + title: Required[str] + year: int + +class ParentNontotalMovie(TypedDict, total=False): + title: Required[str] + +class ChildTotalMovie(ParentNontotalMovie): + year: NotRequired[int] + +class ParentDeeplyAnnotatedMovie(TypedDict): + title: Annotated[Annotated[Required[str], "foobar"], "another level"] + +class ChildDeeplyAnnotatedMovie(ParentDeeplyAnnotatedMovie): + year: NotRequired[Annotated[int, 2000]] + +class AnnotatedMovie(TypedDict): + title: Annotated[Required[str], "foobar"] + year: NotRequired[Annotated[int, 2000]] + +class WeirdlyQuotedMovie(TypedDict): + title: Annotated['Annotated[Required[str], "foobar"]', "another level"] + year: NotRequired['Annotated[int, 2000]'] + + +gth = get_type_hints + + +class GetTypeHintTests(BaseTestCase): + @classmethod + def setUpClass(cls): + with tempfile.TemporaryDirectory() as tempdir: + sys.path.append(tempdir) + Path(tempdir, "ann_module.py").write_text(ANN_MODULE_SOURCE) + Path(tempdir, "ann_module2.py").write_text(ANN_MODULE_2_SOURCE) + Path(tempdir, "ann_module3.py").write_text(ANN_MODULE_3_SOURCE) + cls.ann_module = importlib.import_module("ann_module") + cls.ann_module2 = importlib.import_module("ann_module2") + cls.ann_module3 = importlib.import_module("ann_module3") + sys.path.pop() + + @classmethod + def tearDownClass(cls): + for modname in "ann_module", "ann_module2", "ann_module3": + delattr(cls, modname) + del sys.modules[modname] + + def test_get_type_hints_modules(self): + ann_module_type_hints = {1: 2, 'f': Tuple[int, int], 'x': int, 'y': str} + self.assertEqual(gth(self.ann_module), ann_module_type_hints) + self.assertEqual(gth(self.ann_module2), {}) + self.assertEqual(gth(self.ann_module3), {}) + + def test_get_type_hints_classes(self): + self.assertEqual(gth(self.ann_module.C, self.ann_module.__dict__), + {'y': Optional[self.ann_module.C]}) + self.assertIsInstance(gth(self.ann_module.j_class), dict) + self.assertEqual(gth(self.ann_module.M), {'123': 123, 'o': type}) + self.assertEqual(gth(self.ann_module.D), + {'j': str, 'k': str, 'y': Optional[self.ann_module.C]}) + self.assertEqual(gth(self.ann_module.Y), {'z': int}) + self.assertEqual(gth(self.ann_module.h_class), + {'y': Optional[self.ann_module.C]}) + self.assertEqual(gth(self.ann_module.S), {'x': str, 'y': str}) + self.assertEqual(gth(self.ann_module.foo), {'x': int}) + self.assertEqual(gth(NoneAndForward, globals()), + {'parent': NoneAndForward, 'meaning': type(None)}) + + def test_respect_no_type_check(self): + @no_type_check + class NoTpCheck: + class Inn: + def __init__(self, x: 'not a type'): ... + self.assertTrue(NoTpCheck.__no_type_check__) + self.assertTrue(NoTpCheck.Inn.__init__.__no_type_check__) + self.assertEqual(gth(self.ann_module2.NTC.meth), {}) + class ABase(Generic[T]): + def meth(x: int): ... + @no_type_check + class Der(ABase): ... + self.assertEqual(gth(ABase.meth), {'x': int}) + + def test_get_type_hints_ClassVar(self): + self.assertEqual(gth(self.ann_module2.CV, self.ann_module2.__dict__), + {'var': ClassVar[self.ann_module2.CV]}) + self.assertEqual(gth(B, globals()), + {'y': int, 'x': ClassVar[Optional[B]], 'b': int}) + self.assertEqual(gth(CSub, globals()), + {'z': ClassVar[CSub], 'y': int, 'b': int, + 'x': ClassVar[Optional[B]]}) + self.assertEqual(gth(G), {'lst': ClassVar[List[T]]}) + + def test_final_forward_ref(self): + self.assertEqual(gth(Loop, globals())['attr'], Final[Loop]) + self.assertNotEqual(gth(Loop, globals())['attr'], Final[int]) + self.assertNotEqual(gth(Loop, globals())['attr'], Final) + + +class GetUtilitiesTestCase(TestCase): + def test_get_origin(self): + T = TypeVar('T') + P = ParamSpec('P') + Ts = TypeVarTuple('Ts') + class C(Generic[T]): pass + self.assertIs(get_origin(C[int]), C) + self.assertIs(get_origin(C[T]), C) + self.assertIs(get_origin(int), None) + self.assertIs(get_origin(ClassVar[int]), ClassVar) + self.assertIs(get_origin(Union[int, str]), Union) + self.assertIs(get_origin(Literal[42, 43]), Literal) + self.assertIs(get_origin(Final[List[int]]), Final) + self.assertIs(get_origin(Generic), Generic) + self.assertIs(get_origin(Generic[T]), Generic) + self.assertIs(get_origin(List[Tuple[T, T]][int]), list) + self.assertIs(get_origin(Annotated[T, 'thing']), Annotated) + self.assertIs(get_origin(List), list) + self.assertIs(get_origin(Tuple), tuple) + self.assertIs(get_origin(Callable), collections.abc.Callable) + if sys.version_info >= (3, 9): + self.assertIs(get_origin(list[int]), list) + self.assertIs(get_origin(list), None) + self.assertIs(get_origin(P.args), P) + self.assertIs(get_origin(P.kwargs), P) + self.assertIs(get_origin(Required[int]), Required) + self.assertIs(get_origin(NotRequired[int]), NotRequired) + self.assertIs(get_origin(Unpack[Ts]), Unpack) + self.assertIs(get_origin(Unpack), None) + + def test_get_args(self): + T = TypeVar('T') + Ts = TypeVarTuple('Ts') + class C(Generic[T]): pass + self.assertEqual(get_args(C[int]), (int,)) + self.assertEqual(get_args(C[T]), (T,)) + self.assertEqual(get_args(int), ()) + self.assertEqual(get_args(ClassVar[int]), (int,)) + self.assertEqual(get_args(Union[int, str]), (int, str)) + self.assertEqual(get_args(Literal[42, 43]), (42, 43)) + self.assertEqual(get_args(Final[List[int]]), (List[int],)) + self.assertEqual(get_args(Union[int, Tuple[T, int]][str]), + (int, Tuple[str, int])) + self.assertEqual(get_args(typing.Dict[int, Tuple[T, T]][Optional[int]]), + (int, Tuple[Optional[int], Optional[int]])) + self.assertEqual(get_args(Callable[[], T][int]), ([], int)) + self.assertEqual(get_args(Callable[..., int]), (..., int)) + self.assertEqual(get_args(Union[int, Callable[[Tuple[T, ...]], str]]), + (int, Callable[[Tuple[T, ...]], str])) + self.assertEqual(get_args(Tuple[int, ...]), (int, ...)) + 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), ()) + self.assertEqual(get_args(Callable), ()) + if sys.version_info >= (3, 9): + self.assertEqual(get_args(list[int]), (int,)) + self.assertEqual(get_args(list), ()) + if sys.version_info >= (3, 9): + # Support Python versions with and without the fix for + # https://bugs.python.org/issue42195 + # The first variant is for 3.9.2+, the second for 3.9.0 and 1 + self.assertIn(get_args(collections.abc.Callable[[int], str]), + (([int], str), ([[int]], str))) + self.assertIn(get_args(collections.abc.Callable[[], str]), + (([], str), ([[]], str))) + self.assertEqual(get_args(collections.abc.Callable[..., str]), (..., str)) + P = ParamSpec('P') + # In 3.9 and lower we use typing_extensions's hacky implementation + # of ParamSpec, which gets incorrectly wrapped in a list + self.assertIn(get_args(Callable[P, int]), [(P, int), ([P], int)]) + self.assertEqual(get_args(Callable[Concatenate[int, P], int]), + (Concatenate[int, P], int)) + self.assertEqual(get_args(Required[int]), (int,)) + self.assertEqual(get_args(NotRequired[int]), (int,)) + self.assertEqual(get_args(Unpack[Ts]), (Ts,)) + self.assertEqual(get_args(Unpack), ()) + + +class CollectionsAbcTests(BaseTestCase): + + def test_isinstance_collections(self): + self.assertNotIsInstance(1, collections.abc.Mapping) + self.assertNotIsInstance(1, collections.abc.Iterable) + self.assertNotIsInstance(1, collections.abc.Container) + self.assertNotIsInstance(1, collections.abc.Sized) + with self.assertRaises(TypeError): + isinstance(collections.deque(), typing_extensions.Deque[int]) + with self.assertRaises(TypeError): + issubclass(collections.Counter, typing_extensions.Counter[str]) + + def test_awaitable(self): + async def foo() -> typing_extensions.Awaitable[int]: + return await AwaitableWrapper(42) + + g = foo() + self.assertIsInstance(g, typing_extensions.Awaitable) + self.assertNotIsInstance(foo, typing_extensions.Awaitable) + g.send(None) # Run foo() till completion, to avoid warning. + + def test_coroutine(self): + async def foo(): + return + + g = foo() + self.assertIsInstance(g, typing_extensions.Coroutine) + with self.assertRaises(TypeError): + isinstance(g, typing_extensions.Coroutine[int]) + self.assertNotIsInstance(foo, typing_extensions.Coroutine) + try: + g.send(None) + except StopIteration: + pass + + def test_async_iterable(self): + base_it: Iterator[int] = range(10) + it = AsyncIteratorWrapper(base_it) + self.assertIsInstance(it, typing_extensions.AsyncIterable) + self.assertIsInstance(it, typing_extensions.AsyncIterable) + self.assertNotIsInstance(42, typing_extensions.AsyncIterable) + + def test_async_iterator(self): + base_it: Iterator[int] = range(10) + it = AsyncIteratorWrapper(base_it) + self.assertIsInstance(it, typing_extensions.AsyncIterator) + self.assertNotIsInstance(42, typing_extensions.AsyncIterator) + + def test_deque(self): + self.assertIsSubclass(collections.deque, typing_extensions.Deque) + class MyDeque(typing_extensions.Deque[int]): ... + self.assertIsInstance(MyDeque(), collections.deque) + + def test_counter(self): + self.assertIsSubclass(collections.Counter, typing_extensions.Counter) + + def test_defaultdict_instantiation(self): + self.assertIs( + type(typing_extensions.DefaultDict()), + collections.defaultdict) + self.assertIs( + type(typing_extensions.DefaultDict[KT, VT]()), + collections.defaultdict) + self.assertIs( + type(typing_extensions.DefaultDict[str, int]()), + collections.defaultdict) + + def test_defaultdict_subclass(self): + + class MyDefDict(typing_extensions.DefaultDict[str, int]): + pass + + dd = MyDefDict() + self.assertIsInstance(dd, MyDefDict) + + self.assertIsSubclass(MyDefDict, collections.defaultdict) + self.assertNotIsSubclass(collections.defaultdict, MyDefDict) + + def test_ordereddict_instantiation(self): + self.assertIs( + type(typing_extensions.OrderedDict()), + collections.OrderedDict) + self.assertIs( + type(typing_extensions.OrderedDict[KT, VT]()), + collections.OrderedDict) + self.assertIs( + type(typing_extensions.OrderedDict[str, int]()), + collections.OrderedDict) + + def test_ordereddict_subclass(self): + + class MyOrdDict(typing_extensions.OrderedDict[str, int]): + pass + + od = MyOrdDict() + self.assertIsInstance(od, MyOrdDict) + + self.assertIsSubclass(MyOrdDict, collections.OrderedDict) + self.assertNotIsSubclass(collections.OrderedDict, MyOrdDict) + + def test_chainmap_instantiation(self): + self.assertIs(type(typing_extensions.ChainMap()), collections.ChainMap) + self.assertIs(type(typing_extensions.ChainMap[KT, VT]()), collections.ChainMap) + self.assertIs(type(typing_extensions.ChainMap[str, int]()), collections.ChainMap) + class CM(typing_extensions.ChainMap[KT, VT]): ... + self.assertIs(type(CM[int, str]()), CM) + + def test_chainmap_subclass(self): + + class MyChainMap(typing_extensions.ChainMap[str, int]): + pass + + cm = MyChainMap() + self.assertIsInstance(cm, MyChainMap) + + self.assertIsSubclass(MyChainMap, collections.ChainMap) + self.assertNotIsSubclass(collections.ChainMap, MyChainMap) + + def test_deque_instantiation(self): + self.assertIs(type(typing_extensions.Deque()), collections.deque) + self.assertIs(type(typing_extensions.Deque[T]()), collections.deque) + self.assertIs(type(typing_extensions.Deque[int]()), collections.deque) + class D(typing_extensions.Deque[T]): ... + self.assertIs(type(D[int]()), D) + + def test_counter_instantiation(self): + self.assertIs(type(typing_extensions.Counter()), collections.Counter) + self.assertIs(type(typing_extensions.Counter[T]()), collections.Counter) + self.assertIs(type(typing_extensions.Counter[int]()), collections.Counter) + class C(typing_extensions.Counter[T]): ... + self.assertIs(type(C[int]()), C) + self.assertEqual(C.__bases__, (collections.Counter, typing.Generic)) + + def test_counter_subclass_instantiation(self): + + class MyCounter(typing_extensions.Counter[int]): + pass + + d = MyCounter() + self.assertIsInstance(d, MyCounter) + self.assertIsInstance(d, collections.Counter) + self.assertIsInstance(d, typing_extensions.Counter) + + def test_async_generator(self): + async def f(): + yield 42 + + g = f() + self.assertIsSubclass(type(g), typing_extensions.AsyncGenerator) + + def test_no_async_generator_instantiation(self): + with self.assertRaises(TypeError): + typing_extensions.AsyncGenerator() + with self.assertRaises(TypeError): + typing_extensions.AsyncGenerator[T, T]() + with self.assertRaises(TypeError): + typing_extensions.AsyncGenerator[int, int]() + + def test_subclassing_async_generator(self): + class G(typing_extensions.AsyncGenerator[int, int]): + def asend(self, value): + pass + def athrow(self, typ, val=None, tb=None): + pass + + async def g(): yield 0 + + self.assertIsSubclass(G, typing_extensions.AsyncGenerator) + self.assertIsSubclass(G, typing_extensions.AsyncIterable) + self.assertIsSubclass(G, collections.abc.AsyncGenerator) + self.assertIsSubclass(G, collections.abc.AsyncIterable) + self.assertNotIsSubclass(type(g), G) + + instance = G() + self.assertIsInstance(instance, typing_extensions.AsyncGenerator) + self.assertIsInstance(instance, typing_extensions.AsyncIterable) + self.assertIsInstance(instance, collections.abc.AsyncGenerator) + self.assertIsInstance(instance, collections.abc.AsyncIterable) + self.assertNotIsInstance(type(g), G) + self.assertNotIsInstance(g, G) + + +class OtherABCTests(BaseTestCase): + + def test_contextmanager(self): + @contextlib.contextmanager + def manager(): + yield 42 + + cm = manager() + self.assertIsInstance(cm, typing_extensions.ContextManager) + self.assertNotIsInstance(42, typing_extensions.ContextManager) + + def test_async_contextmanager(self): + class NotACM: + pass + self.assertIsInstance(ACM(), typing_extensions.AsyncContextManager) + self.assertNotIsInstance(NotACM(), typing_extensions.AsyncContextManager) + @contextlib.contextmanager + def manager(): + yield 42 + + cm = manager() + self.assertNotIsInstance(cm, typing_extensions.AsyncContextManager) + self.assertEqual(typing_extensions.AsyncContextManager[int].__args__, (int,)) + with self.assertRaises(TypeError): + isinstance(42, typing_extensions.AsyncContextManager[int]) + with self.assertRaises(TypeError): + typing_extensions.AsyncContextManager[int, str] + + +class TypeTests(BaseTestCase): + + def test_type_basic(self): + + class User: pass + class BasicUser(User): pass + class ProUser(User): pass + + def new_user(user_class: Type[User]) -> User: + return user_class() + + new_user(BasicUser) + + def test_type_typevar(self): + + class User: pass + class BasicUser(User): pass + class ProUser(User): pass + + U = TypeVar('U', bound=User) + + def new_user(user_class: Type[U]) -> U: + return user_class() + + new_user(BasicUser) + + def test_type_optional(self): + A = Optional[Type[BaseException]] + + def foo(a: A) -> Optional[BaseException]: + if a is None: + return None + else: + return a() + + assert isinstance(foo(KeyboardInterrupt), KeyboardInterrupt) + assert foo(None) is None + + +class NewTypeTests(BaseTestCase): + @classmethod + def setUpClass(cls): + global UserId + UserId = NewType('UserId', int) + cls.UserName = NewType(cls.__qualname__ + '.UserName', str) + + @classmethod + def tearDownClass(cls): + global UserId + del UserId + del cls.UserName + + def test_basic(self): + self.assertIsInstance(UserId(5), int) + self.assertIsInstance(self.UserName('Joe'), str) + self.assertEqual(UserId(5) + 1, 6) + + def test_errors(self): + with self.assertRaises(TypeError): + issubclass(UserId, int) + with self.assertRaises(TypeError): + class D(UserId): + pass + + @skipUnless(TYPING_3_10_0, "PEP 604 has yet to be") + def test_or(self): + for cls in (int, self.UserName): + with self.subTest(cls=cls): + self.assertEqual(UserId | cls, Union[UserId, cls]) + self.assertEqual(cls | UserId, Union[cls, UserId]) + + self.assertEqual(get_args(UserId | cls), (UserId, cls)) + self.assertEqual(get_args(cls | UserId), (cls, UserId)) + + def test_special_attrs(self): + self.assertEqual(UserId.__name__, 'UserId') + self.assertEqual(UserId.__qualname__, 'UserId') + self.assertEqual(UserId.__module__, __name__) + self.assertEqual(UserId.__supertype__, int) + + UserName = self.UserName + self.assertEqual(UserName.__name__, 'UserName') + self.assertEqual(UserName.__qualname__, + self.__class__.__qualname__ + '.UserName') + self.assertEqual(UserName.__module__, __name__) + self.assertEqual(UserName.__supertype__, str) + + def test_repr(self): + self.assertEqual(repr(UserId), f'{__name__}.UserId') + self.assertEqual(repr(self.UserName), + f'{__name__}.{self.__class__.__qualname__}.UserName') + + def test_pickle(self): + UserAge = NewType('UserAge', float) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(proto=proto): + pickled = pickle.dumps(UserId, proto) + loaded = pickle.loads(pickled) + self.assertIs(loaded, UserId) + + pickled = pickle.dumps(self.UserName, proto) + loaded = pickle.loads(pickled) + self.assertIs(loaded, self.UserName) + + with self.assertRaises(pickle.PicklingError): + pickle.dumps(UserAge, proto) + + def test_missing__name__(self): + code = ("import typing_extensions\n" + "NT = typing_extensions.NewType('NT', int)\n" + ) + exec(code, {}) + + def test_error_message_when_subclassing(self): + with self.assertRaisesRegex( + TypeError, + re.escape( + "Cannot subclass an instance of NewType. Perhaps you were looking for: " + "`ProUserId = NewType('ProUserId', UserId)`" + ) + ): + class ProUserId(UserId): + ... + + +class Coordinate(Protocol): + x: int + y: int + +@runtime_checkable +class Point(Coordinate, Protocol): + label: str + +class MyPoint: + x: int + y: int + label: str + +class XAxis(Protocol): + x: int + +class YAxis(Protocol): + y: int + +@runtime_checkable +class Position(XAxis, YAxis, Protocol): + pass + +@runtime_checkable +class Proto(Protocol): + attr: int + + def meth(self, arg: str) -> int: + ... + +class Concrete(Proto): + pass + +class Other: + attr: int = 1 + + def meth(self, arg: str) -> int: + if arg == 'this': + return 1 + return 0 + +class NT(NamedTuple): + x: int + y: int + + +skip_if_py312b1 = skipIf( + sys.version_info == (3, 12, 0, 'beta', 1), + "CPython had bugs in 3.12.0b1" +) + + +class ProtocolTests(BaseTestCase): + def test_runtime_alias(self): + self.assertIs(runtime, runtime_checkable) + + def test_basic_protocol(self): + @runtime_checkable + class P(Protocol): + def meth(self): + pass + class C: pass + class D: + def meth(self): + pass + def f(): + pass + self.assertIsSubclass(D, P) + self.assertIsInstance(D(), P) + self.assertNotIsSubclass(C, P) + self.assertNotIsInstance(C(), P) + self.assertNotIsSubclass(types.FunctionType, P) + self.assertNotIsInstance(f, P) + + def test_everything_implements_empty_protocol(self): + @runtime_checkable + class Empty(Protocol): pass + class C: pass + def f(): + pass + for thing in (object, type, tuple, C, types.FunctionType): + self.assertIsSubclass(thing, Empty) + for thing in (object(), 1, (), typing, f): + self.assertIsInstance(thing, Empty) + + def test_function_implements_protocol(self): + def f(): + pass + self.assertIsInstance(f, HasCallProtocol) + + def test_no_inheritance_from_nominal(self): + class C: pass + class BP(Protocol): pass + with self.assertRaises(TypeError): + class P(C, Protocol): + pass + with self.assertRaises(TypeError): + class P(Protocol, C): + pass + with self.assertRaises(TypeError): + class P(BP, C, Protocol): + pass + class D(BP, C): pass + class E(C, BP): pass + self.assertNotIsInstance(D(), E) + self.assertNotIsInstance(E(), D) + + def test_runtimecheckable_on_typing_dot_Protocol(self): + @runtime_checkable + class Foo(typing.Protocol): + x: int + + class Bar: + def __init__(self): + self.x = 42 + + self.assertIsInstance(Bar(), Foo) + self.assertNotIsInstance(object(), Foo) + + def test_typing_dot_runtimecheckable_on_Protocol(self): + @typing.runtime_checkable + class Foo(Protocol): + x: int + + class Bar: + def __init__(self): + self.x = 42 + + self.assertIsInstance(Bar(), Foo) + self.assertNotIsInstance(object(), Foo) + + def test_typing_Protocol_and_extensions_Protocol_can_mix(self): + class TypingProto(typing.Protocol): + x: int + + class ExtensionsProto(Protocol): + y: int + + class SubProto(TypingProto, ExtensionsProto, typing.Protocol): + z: int + + class SubProto2(TypingProto, ExtensionsProto, Protocol): + z: int + + class SubProto3(ExtensionsProto, TypingProto, typing.Protocol): + z: int + + class SubProto4(ExtensionsProto, TypingProto, Protocol): + z: int + + for proto in ( + ExtensionsProto, SubProto, SubProto2, SubProto3, SubProto4 + ): + with self.subTest(proto=proto.__name__): + self.assertTrue(is_protocol(proto)) + if Protocol is not typing.Protocol: + self.assertIsInstance(proto, typing_extensions._ProtocolMeta) + self.assertIsInstance(proto.__protocol_attrs__, set) + with self.assertRaisesRegex( + TypeError, "Protocols cannot be instantiated" + ): + proto() + # check these don't raise + runtime_checkable(proto) + typing.runtime_checkable(proto) + + class Concrete(SubProto): pass + class Concrete2(SubProto2): pass + class Concrete3(SubProto3): pass + class Concrete4(SubProto4): pass + + for cls in Concrete, Concrete2, Concrete3, Concrete4: + with self.subTest(cls=cls.__name__): + self.assertFalse(is_protocol(cls)) + # Check that this doesn't raise: + self.assertIsInstance(cls(), cls) + with self.assertRaises(TypeError): + runtime_checkable(cls) + with self.assertRaises(TypeError): + typing.runtime_checkable(cls) + + def test_no_instantiation(self): + class P(Protocol): pass + with self.assertRaises(TypeError): + P() + class C(P): pass + self.assertIsInstance(C(), C) + T = TypeVar('T') + class PG(Protocol[T]): pass + with self.assertRaises(TypeError): + PG() + with self.assertRaises(TypeError): + PG[int]() + with self.assertRaises(TypeError): + PG[T]() + class CG(PG[T]): pass + self.assertIsInstance(CG[int](), CG) + + def test_protocol_defining_init_does_not_get_overridden(self): + # check that P.__init__ doesn't get clobbered + # see https://bugs.python.org/issue44807 + + class P(Protocol): + x: int + def __init__(self, x: int) -> None: + self.x = x + class C: pass + + c = C() + P.__init__(c, 1) + self.assertEqual(c.x, 1) + + def test_concrete_class_inheriting_init_from_protocol(self): + class P(Protocol): + x: int + def __init__(self, x: int) -> None: + self.x = x + + class C(P): pass + + c = C(1) + self.assertIsInstance(c, C) + self.assertEqual(c.x, 1) + + def test_cannot_instantiate_abstract(self): + @runtime_checkable + class P(Protocol): + @abc.abstractmethod + def ameth(self) -> int: + raise NotImplementedError + class B(P): + pass + class C(B): + def ameth(self) -> int: + return 26 + with self.assertRaises(TypeError): + B() + self.assertIsInstance(C(), P) + + def test_subprotocols_extending(self): + class P1(Protocol): + def meth1(self): + pass + @runtime_checkable + class P2(P1, Protocol): + def meth2(self): + pass + class C: + def meth1(self): + pass + def meth2(self): + pass + class C1: + def meth1(self): + pass + class C2: + def meth2(self): + pass + self.assertNotIsInstance(C1(), P2) + self.assertNotIsInstance(C2(), P2) + self.assertNotIsSubclass(C1, P2) + self.assertNotIsSubclass(C2, P2) + self.assertIsInstance(C(), P2) + self.assertIsSubclass(C, P2) + + def test_subprotocols_merging(self): + class P1(Protocol): + def meth1(self): + pass + class P2(Protocol): + def meth2(self): + pass + @runtime_checkable + class P(P1, P2, Protocol): + pass + class C: + def meth1(self): + pass + def meth2(self): + pass + class C1: + def meth1(self): + pass + class C2: + def meth2(self): + pass + self.assertNotIsInstance(C1(), P) + self.assertNotIsInstance(C2(), P) + self.assertNotIsSubclass(C1, P) + self.assertNotIsSubclass(C2, P) + self.assertIsInstance(C(), P) + self.assertIsSubclass(C, P) + + def test_protocols_issubclass(self): + T = TypeVar('T') + @runtime_checkable + class P(Protocol): + def x(self): ... + @runtime_checkable + class PG(Protocol[T]): + def x(self): ... + class BadP(Protocol): + def x(self): ... + class BadPG(Protocol[T]): + def x(self): ... + class C: + def x(self): ... + self.assertIsSubclass(C, P) + self.assertIsSubclass(C, PG) + self.assertIsSubclass(BadP, PG) + + no_subscripted_generics = ( + "Subscripted generics cannot be used with class and instance checks" + ) + + with self.assertRaisesRegex(TypeError, no_subscripted_generics): + issubclass(C, PG[T]) + with self.assertRaisesRegex(TypeError, no_subscripted_generics): + issubclass(C, PG[C]) + + only_runtime_checkable_protocols = ( + "Instance and class checks can only be used with " + "@runtime_checkable protocols" + ) + + with self.assertRaisesRegex(TypeError, only_runtime_checkable_protocols): + issubclass(C, BadP) + with self.assertRaisesRegex(TypeError, only_runtime_checkable_protocols): + issubclass(C, BadPG) + + with self.assertRaisesRegex(TypeError, no_subscripted_generics): + issubclass(P, PG[T]) + with self.assertRaisesRegex(TypeError, no_subscripted_generics): + issubclass(PG, PG[int]) + + only_classes_allowed = r"issubclass\(\) arg 1 must be a class" + + with self.assertRaisesRegex(TypeError, only_classes_allowed): + issubclass(1, P) + with self.assertRaisesRegex(TypeError, only_classes_allowed): + issubclass(1, PG) + with self.assertRaisesRegex(TypeError, only_classes_allowed): + issubclass(1, BadP) + with self.assertRaisesRegex(TypeError, only_classes_allowed): + issubclass(1, BadPG) + + def test_implicit_issubclass_between_two_protocols(self): + @runtime_checkable + class CallableMembersProto(Protocol): + def meth(self): ... + + # All the below protocols should be considered "subclasses" + # of CallableMembersProto at runtime, + # even though none of them explicitly subclass CallableMembersProto + + class IdenticalProto(Protocol): + def meth(self): ... + + class SupersetProto(Protocol): + def meth(self): ... + def meth2(self): ... + + class NonCallableMembersProto(Protocol): + meth: Callable[[], None] + + class NonCallableMembersSupersetProto(Protocol): + meth: Callable[[], None] + meth2: Callable[[str, int], bool] + + class MixedMembersProto1(Protocol): + meth: Callable[[], None] + def meth2(self): ... + + class MixedMembersProto2(Protocol): + def meth(self): ... + meth2: Callable[[str, int], bool] + + for proto in ( + IdenticalProto, SupersetProto, NonCallableMembersProto, + NonCallableMembersSupersetProto, MixedMembersProto1, MixedMembersProto2 + ): + with self.subTest(proto=proto.__name__): + self.assertIsSubclass(proto, CallableMembersProto) + + # These two shouldn't be considered subclasses of CallableMembersProto, however, + # since they don't have the `meth` protocol member + + class EmptyProtocol(Protocol): ... + class UnrelatedProtocol(Protocol): + def wut(self): ... + + self.assertNotIsSubclass(EmptyProtocol, CallableMembersProto) + self.assertNotIsSubclass(UnrelatedProtocol, CallableMembersProto) + + # These aren't protocols at all (despite having annotations), + # so they should only be considered subclasses of CallableMembersProto + # if they *actually have an attribute* matching the `meth` member + # (just having an annotation is insufficient) + + class AnnotatedButNotAProtocol: + meth: Callable[[], None] + + class NotAProtocolButAnImplicitSubclass: + def meth(self): pass + + class NotAProtocolButAnImplicitSubclass2: + meth: Callable[[], None] + def meth(self): pass + + class NotAProtocolButAnImplicitSubclass3: + meth: Callable[[], None] + meth2: Callable[[int, str], bool] + def meth(self): pass + def meth(self, x, y): return True + + self.assertNotIsSubclass(AnnotatedButNotAProtocol, CallableMembersProto) + self.assertIsSubclass(NotAProtocolButAnImplicitSubclass, CallableMembersProto) + self.assertIsSubclass(NotAProtocolButAnImplicitSubclass2, CallableMembersProto) + self.assertIsSubclass(NotAProtocolButAnImplicitSubclass3, CallableMembersProto) + + @skip_if_py312b1 + def test_issubclass_and_isinstance_on_Protocol_itself(self): + class C: + def x(self): pass + + self.assertNotIsSubclass(object, Protocol) + self.assertNotIsInstance(object(), Protocol) + + self.assertNotIsSubclass(str, Protocol) + self.assertNotIsInstance('foo', Protocol) + + self.assertNotIsSubclass(C, Protocol) + self.assertNotIsInstance(C(), Protocol) + + only_classes_allowed = r"issubclass\(\) arg 1 must be a class" + + with self.assertRaisesRegex(TypeError, only_classes_allowed): + issubclass(1, Protocol) + with self.assertRaisesRegex(TypeError, only_classes_allowed): + issubclass('foo', Protocol) + with self.assertRaisesRegex(TypeError, only_classes_allowed): + issubclass(C(), Protocol) + + T = TypeVar('T') + + @runtime_checkable + class EmptyProtocol(Protocol): pass + + @runtime_checkable + class SupportsStartsWith(Protocol): + def startswith(self, x: str) -> bool: ... + + @runtime_checkable + class SupportsX(Protocol[T]): + def x(self): ... + + for proto in EmptyProtocol, SupportsStartsWith, SupportsX: + with self.subTest(proto=proto.__name__): + self.assertIsSubclass(proto, Protocol) + + # gh-105237 / PR #105239: + # check that the presence of Protocol subclasses + # where `issubclass(X, )` evaluates to True + # doesn't influence the result of `issubclass(X, Protocol)` + + self.assertIsSubclass(object, EmptyProtocol) + self.assertIsInstance(object(), EmptyProtocol) + self.assertNotIsSubclass(object, Protocol) + self.assertNotIsInstance(object(), Protocol) + + self.assertIsSubclass(str, SupportsStartsWith) + self.assertIsInstance('foo', SupportsStartsWith) + self.assertNotIsSubclass(str, Protocol) + self.assertNotIsInstance('foo', Protocol) + + self.assertIsSubclass(C, SupportsX) + self.assertIsInstance(C(), SupportsX) + self.assertNotIsSubclass(C, Protocol) + self.assertNotIsInstance(C(), Protocol) + + @skip_if_py312b1 + def test_isinstance_checks_not_at_whim_of_gc(self): + self.addCleanup(gc.enable) + gc.disable() + + with self.assertRaisesRegex( + TypeError, + "Protocols can only inherit from other protocols" + ): + class Foo(collections.abc.Mapping, Protocol): + pass + + self.assertNotIsInstance([], collections.abc.Mapping) + + def test_protocols_issubclass_non_callable(self): + class C: + x = 1 + + @runtime_checkable + class PNonCall(Protocol): + x = 1 + + non_callable_members_illegal = ( + "Protocols with non-method members don't support issubclass()" + ) + + with self.assertRaisesRegex(TypeError, non_callable_members_illegal): + issubclass(C, PNonCall) + + self.assertIsInstance(C(), PNonCall) + PNonCall.register(C) + + with self.assertRaisesRegex(TypeError, non_callable_members_illegal): + issubclass(C, PNonCall) + + self.assertIsInstance(C(), PNonCall) + + # check that non-protocol subclasses are not affected + class D(PNonCall): ... + + self.assertNotIsSubclass(C, D) + self.assertNotIsInstance(C(), D) + D.register(C) + self.assertIsSubclass(C, D) + self.assertIsInstance(C(), D) + + with self.assertRaisesRegex(TypeError, non_callable_members_illegal): + issubclass(D, PNonCall) + + def test_no_weird_caching_with_issubclass_after_isinstance(self): + @runtime_checkable + class Spam(Protocol): + x: int + + class Eggs: + def __init__(self) -> None: + self.x = 42 + + self.assertIsInstance(Eggs(), Spam) + + # gh-104555: If we didn't override ABCMeta.__subclasscheck__ in _ProtocolMeta, + # TypeError wouldn't be raised here, + # as the cached result of the isinstance() check immediately above + # would mean the issubclass() call would short-circuit + # before we got to the "raise TypeError" line + with self.assertRaisesRegex( + TypeError, + "Protocols with non-method members don't support issubclass()" + ): + issubclass(Eggs, Spam) + + def test_no_weird_caching_with_issubclass_after_isinstance_2(self): + @runtime_checkable + class Spam(Protocol): + x: int + + class Eggs: ... + + self.assertNotIsInstance(Eggs(), Spam) + + # gh-104555: If we didn't override ABCMeta.__subclasscheck__ in _ProtocolMeta, + # TypeError wouldn't be raised here, + # as the cached result of the isinstance() check immediately above + # would mean the issubclass() call would short-circuit + # before we got to the "raise TypeError" line + with self.assertRaisesRegex( + TypeError, + "Protocols with non-method members don't support issubclass()" + ): + issubclass(Eggs, Spam) + + def test_no_weird_caching_with_issubclass_after_isinstance_3(self): + @runtime_checkable + class Spam(Protocol): + x: int + + class Eggs: + def __getattr__(self, attr): + if attr == "x": + return 42 + raise AttributeError(attr) + + self.assertNotIsInstance(Eggs(), Spam) + + # gh-104555: If we didn't override ABCMeta.__subclasscheck__ in _ProtocolMeta, + # TypeError wouldn't be raised here, + # as the cached result of the isinstance() check immediately above + # would mean the issubclass() call would short-circuit + # before we got to the "raise TypeError" line + with self.assertRaisesRegex( + TypeError, + "Protocols with non-method members don't support issubclass()" + ): + issubclass(Eggs, Spam) + + def test_protocols_isinstance(self): + T = TypeVar('T') + @runtime_checkable + class P(Protocol): + def meth(x): ... + @runtime_checkable + class PG(Protocol[T]): + def meth(x): ... + @runtime_checkable + class WeirdProto(Protocol): + meth = str.maketrans + @runtime_checkable + class WeirdProto2(Protocol): + meth = lambda *args, **kwargs: None # noqa: E731 + class CustomCallable: + def __call__(self, *args, **kwargs): + pass + @runtime_checkable + class WeirderProto(Protocol): + meth = CustomCallable() + class BadP(Protocol): + def meth(x): ... + class BadPG(Protocol[T]): + def meth(x): ... + class C: + def meth(x): ... + class C2: + def __init__(self): + self.meth = lambda: None + for klass in C, C2: + for proto in P, PG, WeirdProto, WeirdProto2, WeirderProto: + with self.subTest(klass=klass.__name__, proto=proto.__name__): + self.assertIsInstance(klass(), proto) + + no_subscripted_generics = ( + "Subscripted generics cannot be used with class and instance checks" + ) + + with self.assertRaisesRegex(TypeError, no_subscripted_generics): + isinstance(C(), PG[T]) + with self.assertRaisesRegex(TypeError, no_subscripted_generics): + isinstance(C(), PG[C]) + + only_runtime_checkable_msg = ( + "Instance and class checks can only be used " + "with @runtime_checkable protocols" + ) + + with self.assertRaisesRegex(TypeError, only_runtime_checkable_msg): + isinstance(C(), BadP) + with self.assertRaisesRegex(TypeError, only_runtime_checkable_msg): + isinstance(C(), BadPG) + + def test_protocols_isinstance_properties_and_descriptors(self): + class C: + @property + def attr(self): + return 42 + + class CustomDescriptor: + def __get__(self, obj, objtype=None): + return 42 + + class D: + attr = CustomDescriptor() + + # Check that properties set on superclasses + # are still found by the isinstance() logic + class E(C): ... + class F(D): ... + + class Empty: ... + + T = TypeVar('T') + + @runtime_checkable + class P(Protocol): + @property + def attr(self): ... + + @runtime_checkable + class P1(Protocol): + attr: int + + @runtime_checkable + class PG(Protocol[T]): + @property + def attr(self): ... + + @runtime_checkable + class PG1(Protocol[T]): + attr: T + + @runtime_checkable + class MethodP(Protocol): + def attr(self): ... + + @runtime_checkable + class MethodPG(Protocol[T]): + def attr(self) -> T: ... + + for protocol_class in P, P1, PG, PG1, MethodP, MethodPG: + for klass in C, D, E, F: + with self.subTest( + klass=klass.__name__, + protocol_class=protocol_class.__name__ + ): + self.assertIsInstance(klass(), protocol_class) + + with self.subTest(klass="Empty", protocol_class=protocol_class.__name__): + self.assertNotIsInstance(Empty(), protocol_class) + + class BadP(Protocol): + @property + def attr(self): ... + + class BadP1(Protocol): + attr: int + + class BadPG(Protocol[T]): + @property + def attr(self): ... + + class BadPG1(Protocol[T]): + attr: T + + cases = ( + PG[T], PG[C], PG1[T], PG1[C], MethodPG[T], + MethodPG[C], BadP, BadP1, BadPG, BadPG1 + ) + + for obj in cases: + for klass in C, D, E, F, Empty: + with self.subTest(klass=klass.__name__, obj=obj): + with self.assertRaises(TypeError): + isinstance(klass(), obj) + + def test_protocols_isinstance_not_fooled_by_custom_dir(self): + @runtime_checkable + class HasX(Protocol): + x: int + + class CustomDirWithX: + x = 10 + def __dir__(self): + return [] + + class CustomDirWithoutX: + def __dir__(self): + return ["x"] + + self.assertIsInstance(CustomDirWithX(), HasX) + self.assertNotIsInstance(CustomDirWithoutX(), HasX) + + def test_protocols_isinstance_attribute_access_with_side_effects(self): + class C: + @property + def attr(self): + raise AttributeError('no') + + class CustomDescriptor: + def __get__(self, obj, objtype=None): + raise RuntimeError("NO") + + class D: + attr = CustomDescriptor() + + # Check that properties set on superclasses + # are still found by the isinstance() logic + class E(C): ... + class F(D): ... + + class WhyWouldYouDoThis: + def __getattr__(self, name): + raise RuntimeError("wut") + + T = TypeVar('T') + + @runtime_checkable + class P(Protocol): + @property + def attr(self): ... + + @runtime_checkable + class P1(Protocol): + attr: int + + @runtime_checkable + class PG(Protocol[T]): + @property + def attr(self): ... + + @runtime_checkable + class PG1(Protocol[T]): + attr: T + + @runtime_checkable + class MethodP(Protocol): + def attr(self): ... + + @runtime_checkable + class MethodPG(Protocol[T]): + def attr(self) -> T: ... + + for protocol_class in P, P1, PG, PG1, MethodP, MethodPG: + for klass in C, D, E, F: + with self.subTest( + klass=klass.__name__, + protocol_class=protocol_class.__name__ + ): + self.assertIsInstance(klass(), protocol_class) + + with self.subTest( + klass="WhyWouldYouDoThis", + protocol_class=protocol_class.__name__ + ): + self.assertNotIsInstance(WhyWouldYouDoThis(), protocol_class) + + def test_protocols_isinstance___slots__(self): + # As per the consensus in https://github.com/python/typing/issues/1367, + # this is desirable behaviour + @runtime_checkable + class HasX(Protocol): + x: int + + class HasNothingButSlots: + __slots__ = ("x",) + + self.assertIsInstance(HasNothingButSlots(), HasX) + + def test_protocols_isinstance_py36(self): + class APoint: + def __init__(self, x, y, label): + self.x = x + self.y = y + self.label = label + class BPoint: + label = 'B' + def __init__(self, x, y): + self.x = x + self.y = y + class C: + def __init__(self, attr): + self.attr = attr + def meth(self, arg): + return 0 + class Bad: pass + self.assertIsInstance(APoint(1, 2, 'A'), Point) + self.assertIsInstance(BPoint(1, 2), Point) + self.assertNotIsInstance(MyPoint(), Point) + self.assertIsInstance(BPoint(1, 2), Position) + self.assertIsInstance(Other(), Proto) + self.assertIsInstance(Concrete(), Proto) + self.assertIsInstance(C(42), Proto) + self.assertNotIsInstance(Bad(), Proto) + self.assertNotIsInstance(Bad(), Point) + self.assertNotIsInstance(Bad(), Position) + self.assertNotIsInstance(Bad(), Concrete) + self.assertNotIsInstance(Other(), Concrete) + self.assertIsInstance(NT(1, 2), Position) + + def test_runtime_checkable_with_match_args(self): + @runtime_checkable + class P_regular(Protocol): + x: int + y: int + + @runtime_checkable + class P_match(Protocol): + __match_args__ = ("x", "y") + x: int + y: int + + class Regular: + def __init__(self, x: int, y: int): + self.x = x + self.y = y + + class WithMatch: + __match_args__ = ("x", "y", "z") + def __init__(self, x: int, y: int, z: int): + self.x = x + self.y = y + self.z = z + + class Nope: ... + + self.assertIsInstance(Regular(1, 2), P_regular) + self.assertIsInstance(Regular(1, 2), P_match) + self.assertIsInstance(WithMatch(1, 2, 3), P_regular) + self.assertIsInstance(WithMatch(1, 2, 3), P_match) + self.assertNotIsInstance(Nope(), P_regular) + self.assertNotIsInstance(Nope(), P_match) + + def test_protocols_isinstance_init(self): + T = TypeVar('T') + @runtime_checkable + class P(Protocol): + x = 1 + @runtime_checkable + class PG(Protocol[T]): + x = 1 + class C: + def __init__(self, x): + self.x = x + self.assertIsInstance(C(1), P) + self.assertIsInstance(C(1), PG) + + def test_protocols_isinstance_monkeypatching(self): + @runtime_checkable + class HasX(Protocol): + x: int + + class Foo: ... + + f = Foo() + self.assertNotIsInstance(f, HasX) + f.x = 42 + self.assertIsInstance(f, HasX) + del f.x + self.assertNotIsInstance(f, HasX) + + @skip_if_py312b1 + def test_runtime_checkable_generic_non_protocol(self): + # Make sure this doesn't raise AttributeError + with self.assertRaisesRegex( + TypeError, + "@runtime_checkable can be only applied to protocol classes", + ): + @runtime_checkable + class Foo(Generic[T]): ... + + def test_runtime_checkable_generic(self): + @runtime_checkable + class Foo(Protocol[T]): + def meth(self) -> T: ... + + class Impl: + def meth(self) -> int: ... + + self.assertIsSubclass(Impl, Foo) + + class NotImpl: + def method(self) -> int: ... + + self.assertNotIsSubclass(NotImpl, Foo) + + if sys.version_info >= (3, 12): + exec(textwrap.dedent( + """ + @skip_if_py312b1 + def test_pep695_generics_can_be_runtime_checkable(self): + @runtime_checkable + class HasX(Protocol): + x: int + + class Bar[T]: + x: T + def __init__(self, x): + self.x = x + + class Capybara[T]: + y: str + def __init__(self, y): + self.y = y + + self.assertIsInstance(Bar(1), HasX) + self.assertNotIsInstance(Capybara('a'), HasX) + """ + )) + + @skip_if_py312b1 + def test_protocols_isinstance_generic_classes(self): + T = TypeVar("T") + + class Foo(Generic[T]): + x: T + + def __init__(self, x): + self.x = x + + class Bar(Foo[int]): + ... + + @runtime_checkable + class HasX(Protocol): + x: int + + foo = Foo(1) + self.assertIsInstance(foo, HasX) + + bar = Bar(2) + self.assertIsInstance(bar, HasX) + + def test_protocols_support_register(self): + @runtime_checkable + class P(Protocol): + x = 1 + class PM(Protocol): + def meth(self): pass + class D(PM): pass + class C: pass + D.register(C) + P.register(C) + self.assertIsInstance(C(), P) + self.assertIsInstance(C(), D) + + def test_none_on_non_callable_doesnt_block_implementation(self): + @runtime_checkable + class P(Protocol): + x = 1 + class A: + x = 1 + class B(A): + x = None + class C: + def __init__(self): + self.x = None + self.assertIsInstance(B(), P) + self.assertIsInstance(C(), P) + + def test_none_on_callable_blocks_implementation(self): + @runtime_checkable + class P(Protocol): + def x(self): ... + class A: + def x(self): ... + class B(A): + x = None + class C: + def __init__(self): + self.x = None + self.assertNotIsInstance(B(), P) + self.assertNotIsInstance(C(), P) + + def test_non_protocol_subclasses(self): + class P(Protocol): + x = 1 + @runtime_checkable + class PR(Protocol): + def meth(self): pass + class NonP(P): + x = 1 + class NonPR(PR): pass + class C(metaclass=abc.ABCMeta): + x = 1 + class D(metaclass=abc.ABCMeta): # noqa: B024 + def meth(self): pass # noqa: B027 + self.assertNotIsInstance(C(), NonP) + self.assertNotIsInstance(D(), NonPR) + self.assertNotIsSubclass(C, NonP) + self.assertNotIsSubclass(D, NonPR) + self.assertIsInstance(NonPR(), PR) + self.assertIsSubclass(NonPR, PR) + + self.assertNotIn("__protocol_attrs__", vars(NonP)) + self.assertNotIn("__protocol_attrs__", vars(NonPR)) + self.assertNotIn("__callable_proto_members_only__", vars(NonP)) + self.assertNotIn("__callable_proto_members_only__", vars(NonPR)) + + acceptable_extra_attrs = { + '_is_protocol', '_is_runtime_protocol', '__parameters__', + '__init__', '__annotations__', '__subclasshook__', + } + self.assertLessEqual(vars(NonP).keys(), vars(C).keys() | acceptable_extra_attrs) + self.assertLessEqual( + vars(NonPR).keys(), vars(D).keys() | acceptable_extra_attrs + ) + + def test_custom_subclasshook(self): + class P(Protocol): + x = 1 + class OKClass: pass + class BadClass: + x = 1 + class C(P): + @classmethod + def __subclasshook__(cls, other): + return other.__name__.startswith("OK") + self.assertIsInstance(OKClass(), C) + self.assertNotIsInstance(BadClass(), C) + self.assertIsSubclass(OKClass, C) + self.assertNotIsSubclass(BadClass, C) + + @skipIf( + sys.version_info[:4] == (3, 12, 0, 'beta') and sys.version_info[4] < 4, + "Early betas of Python 3.12 had a bug" + ) + def test_custom_subclasshook_2(self): + @runtime_checkable + class HasX(Protocol): + # The presence of a non-callable member + # would mean issubclass() checks would fail with TypeError + # if it weren't for the custom `__subclasshook__` method + x = 1 + + @classmethod + def __subclasshook__(cls, other): + return hasattr(other, 'x') + + class Empty: pass + + class ImplementsHasX: + x = 1 + + self.assertIsInstance(ImplementsHasX(), HasX) + self.assertNotIsInstance(Empty(), HasX) + self.assertIsSubclass(ImplementsHasX, HasX) + self.assertNotIsSubclass(Empty, HasX) + + # isinstance() and issubclass() checks against this still raise TypeError, + # despite the presence of the custom __subclasshook__ method, + # as it's not decorated with @runtime_checkable + class NotRuntimeCheckable(Protocol): + @classmethod + def __subclasshook__(cls, other): + return hasattr(other, 'x') + + must_be_runtime_checkable = ( + "Instance and class checks can only be used " + "with @runtime_checkable protocols" + ) + + with self.assertRaisesRegex(TypeError, must_be_runtime_checkable): + issubclass(object, NotRuntimeCheckable) + with self.assertRaisesRegex(TypeError, must_be_runtime_checkable): + isinstance(object(), NotRuntimeCheckable) + + @skip_if_py312b1 + def test_issubclass_fails_correctly(self): + @runtime_checkable + class P(Protocol): + x = 1 + class C: pass + with self.assertRaisesRegex(TypeError, r"issubclass\(\) arg 1 must be a class"): + issubclass(C(), P) + + def test_defining_generic_protocols(self): + T = TypeVar('T') + S = TypeVar('S') + @runtime_checkable + class PR(Protocol[T, S]): + def meth(self): pass + class P(PR[int, T], Protocol[T]): + y = 1 + with self.assertRaises(TypeError): + issubclass(PR[int, T], PR) + with self.assertRaises(TypeError): + issubclass(P[str], PR) + with self.assertRaises(TypeError): + PR[int] + with self.assertRaises(TypeError): + 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) + + def test_defining_generic_protocols_old_style(self): + T = TypeVar('T') + S = TypeVar('S') + @runtime_checkable + class PR(Protocol, Generic[T, S]): + def meth(self): pass + class P(PR[int, str], Protocol): + y = 1 + with self.assertRaises(TypeError): + self.assertIsSubclass(PR[int, str], PR) + self.assertIsSubclass(P, PR) + with self.assertRaises(TypeError): + PR[int] + if not TYPING_3_10_0: + with self.assertRaises(TypeError): + PR[int, 1] + class P1(Protocol, Generic[T]): + def bar(self, x: T) -> str: ... + class P2(Generic[T], Protocol): + def bar(self, x: T) -> str: ... + @runtime_checkable + class PSub(P1[str], Protocol): + x = 1 + class Test: + x = 1 + def bar(self, x: str) -> str: + return x + self.assertIsInstance(Test(), PSub) + if not TYPING_3_10_0: + with self.assertRaises(TypeError): + PR[int, ClassVar] + + if hasattr(typing, "TypeAliasType"): + exec(textwrap.dedent( + """ + def test_pep695_generic_protocol_callable_members(self): + @runtime_checkable + class Foo[T](Protocol): + def meth(self, x: T) -> None: ... + + class Bar[T]: + def meth(self, x: T) -> None: ... + + self.assertIsInstance(Bar(), Foo) + self.assertIsSubclass(Bar, Foo) + + @runtime_checkable + class SupportsTrunc[T](Protocol): + def __trunc__(self) -> T: ... + + self.assertIsInstance(0.0, SupportsTrunc) + self.assertIsSubclass(float, SupportsTrunc) + + def test_no_weird_caching_with_issubclass_after_isinstance_pep695(self): + @runtime_checkable + class Spam[T](Protocol): + x: T + + class Eggs[T]: + def __init__(self, x: T) -> None: + self.x = x + + self.assertIsInstance(Eggs(42), Spam) + + # gh-104555: If we didn't override ABCMeta.__subclasscheck__ in _ProtocolMeta, + # TypeError wouldn't be raised here, + # as the cached result of the isinstance() check immediately above + # would mean the issubclass() call would short-circuit + # before we got to the "raise TypeError" line + with self.assertRaises(TypeError): + issubclass(Eggs, Spam) + """ + )) + + def test_init_called(self): + T = TypeVar('T') + class P(Protocol[T]): pass + class C(P[T]): + def __init__(self): + self.test = 'OK' + self.assertEqual(C[int]().test, 'OK') + + def test_protocols_bad_subscripts(self): + T = TypeVar('T') + S = TypeVar('S') + with self.assertRaises(TypeError): + class P(Protocol[T, T]): pass + with self.assertRaises(TypeError): + class P(Protocol[int]): pass + with self.assertRaises(TypeError): + class P(Protocol[T], Protocol[S]): pass + with self.assertRaises(TypeError): + class P(typing.Mapping[T, S], Protocol[T]): pass + + def test_generic_protocols_repr(self): + T = TypeVar('T') + S = TypeVar('S') + class P(Protocol[T, S]): pass + self.assertTrue(repr(P[T, S]).endswith('P[~T, ~S]')) + self.assertTrue(repr(P[int, str]).endswith('P[int, str]')) + + def test_generic_protocols_eq(self): + T = TypeVar('T') + S = TypeVar('S') + class P(Protocol[T, S]): pass + self.assertEqual(P, P) + self.assertEqual(P[int, T], P[int, T]) + self.assertEqual(P[T, T][Tuple[T, S]][int, str], + P[Tuple[int, str], Tuple[int, str]]) + + def test_generic_protocols_special_from_generic(self): + T = TypeVar('T') + class P(Protocol[T]): pass + self.assertEqual(P.__parameters__, (T,)) + self.assertEqual(P[int].__parameters__, ()) + self.assertEqual(P[int].__args__, (int,)) + self.assertIs(P[int].__origin__, P) + + def test_generic_protocols_special_from_protocol(self): + @runtime_checkable + class PR(Protocol): + x = 1 + class P(Protocol): + def meth(self): + pass + T = TypeVar('T') + class PG(Protocol[T]): + x = 1 + def meth(self): + pass + self.assertTrue(P._is_protocol) + self.assertTrue(PR._is_protocol) + self.assertTrue(PG._is_protocol) + self.assertFalse(P._is_runtime_protocol) + self.assertTrue(PR._is_runtime_protocol) + self.assertTrue(PG[int]._is_protocol) + self.assertEqual(typing_extensions._get_protocol_attrs(P), {'meth'}) + self.assertEqual(typing_extensions._get_protocol_attrs(PR), {'x'}) + self.assertEqual(frozenset(typing_extensions._get_protocol_attrs(PG)), + frozenset({'x', 'meth'})) + + def test_no_runtime_deco_on_nominal(self): + with self.assertRaises(TypeError): + @runtime_checkable + class C: pass + class Proto(Protocol): + x = 1 + with self.assertRaises(TypeError): + @runtime_checkable + class Concrete(Proto): + pass + + def test_none_treated_correctly(self): + @runtime_checkable + class P(Protocol): + x: int = None + class B(object): pass + self.assertNotIsInstance(B(), P) + class C: + x = 1 + class D: + x = None + self.assertIsInstance(C(), P) + self.assertIsInstance(D(), P) + class CI: + def __init__(self): + self.x = 1 + class DI: + def __init__(self): + self.x = None + self.assertIsInstance(CI(), P) + self.assertIsInstance(DI(), P) + + def test_protocols_in_unions(self): + class P(Protocol): + x: int = None + Alias = typing.Union[typing.Iterable, P] + Alias2 = typing.Union[P, typing.Iterable] + self.assertEqual(Alias, Alias2) + + def test_protocols_pickleable(self): + global P, CP # pickle wants to reference the class by name + T = TypeVar('T') + + @runtime_checkable + class P(Protocol[T]): + x = 1 + class CP(P[int]): + pass + + c = CP() + c.foo = 42 + c.bar = 'abc' + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + z = pickle.dumps(c, proto) + x = pickle.loads(z) + self.assertEqual(x.foo, 42) + self.assertEqual(x.bar, 'abc') + self.assertEqual(x.x, 1) + self.assertEqual(x.__dict__, {'foo': 42, 'bar': 'abc'}) + s = pickle.dumps(P) + D = pickle.loads(s) + class E: + x = 1 + self.assertIsInstance(E(), D) + + def test_collections_protocols_allowed(self): + @runtime_checkable + class Custom(collections.abc.Iterable, Protocol): + def close(self): pass + + class A: ... + class B: + def __iter__(self): + return [] + def close(self): + return 0 + + self.assertIsSubclass(B, Custom) + self.assertNotIsSubclass(A, Custom) + + @skipUnless( + hasattr(collections.abc, "Buffer"), + "needs collections.abc.Buffer to exist" + ) + @skip_if_py312b1 + def test_collections_abc_buffer_protocol_allowed(self): + @runtime_checkable + class ReleasableBuffer(collections.abc.Buffer, Protocol): + def __release_buffer__(self, mv: memoryview) -> None: ... + + class C: pass + class D: + def __buffer__(self, flags: int) -> memoryview: + return memoryview(b'') + def __release_buffer__(self, mv: memoryview) -> None: + pass + + self.assertIsSubclass(D, ReleasableBuffer) + self.assertIsInstance(D(), ReleasableBuffer) + self.assertNotIsSubclass(C, ReleasableBuffer) + self.assertNotIsInstance(C(), ReleasableBuffer) + + def test_builtin_protocol_allowlist(self): + with self.assertRaises(TypeError): + class CustomProtocol(TestCase, Protocol): + pass + + class CustomContextManager(typing.ContextManager, Protocol): + pass + + @skip_if_py312b1 + def test_typing_extensions_protocol_allowlist(self): + @runtime_checkable + class ReleasableBuffer(Buffer, Protocol): + def __release_buffer__(self, mv: memoryview) -> None: ... + + class C: pass + class D: + def __buffer__(self, flags: int) -> memoryview: + return memoryview(b'') + def __release_buffer__(self, mv: memoryview) -> None: + pass + + self.assertIsSubclass(D, ReleasableBuffer) + self.assertIsInstance(D(), ReleasableBuffer) + self.assertNotIsSubclass(C, ReleasableBuffer) + self.assertNotIsInstance(C(), ReleasableBuffer) + + def test_non_runtime_protocol_isinstance_check(self): + class P(Protocol): + x: int + + with self.assertRaisesRegex(TypeError, "@runtime_checkable"): + isinstance(1, P) + + def test_no_init_same_for_different_protocol_implementations(self): + class CustomProtocolWithoutInitA(Protocol): + pass + + class CustomProtocolWithoutInitB(Protocol): + pass + + self.assertEqual(CustomProtocolWithoutInitA.__init__, CustomProtocolWithoutInitB.__init__) + + def test_protocol_generic_over_paramspec(self): + P = ParamSpec("P") + T = TypeVar("T") + T2 = TypeVar("T2") + + class MemoizedFunc(Protocol[P, T, T2]): + cache: typing.Dict[T2, T] + def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T: ... + + self.assertEqual(MemoizedFunc.__parameters__, (P, T, T2)) + self.assertTrue(MemoizedFunc._is_protocol) + + with self.assertRaises(TypeError): + MemoizedFunc[[int, str, str]] + + if sys.version_info >= (3, 10): + # These unfortunately don't pass on <=3.9, + # due to typing._type_check on older Python versions + X = MemoizedFunc[[int, str, str], T, T2] + self.assertEqual(X.__parameters__, (T, T2)) + self.assertEqual(X.__args__, ((int, str, str), T, T2)) + + Y = X[bytes, memoryview] + self.assertEqual(Y.__parameters__, ()) + self.assertEqual(Y.__args__, ((int, str, str), bytes, memoryview)) + + def test_protocol_generic_over_typevartuple(self): + Ts = TypeVarTuple("Ts") + T = TypeVar("T") + T2 = TypeVar("T2") + + class MemoizedFunc(Protocol[Unpack[Ts], T, T2]): + cache: typing.Dict[T2, T] + def __call__(self, *args: Unpack[Ts]) -> T: ... + + self.assertEqual(MemoizedFunc.__parameters__, (Ts, T, T2)) + self.assertTrue(MemoizedFunc._is_protocol) + + things = "arguments" if sys.version_info >= (3, 11) else "parameters" + + # A bug was fixed in 3.11.1 + # (https://github.com/python/cpython/commit/74920aa27d0c57443dd7f704d6272cca9c507ab3) + # That means this assertion doesn't pass on 3.11.0, + # but it passes on all other Python versions + if sys.version_info[:3] != (3, 11, 0): + with self.assertRaisesRegex(TypeError, f"Too few {things}"): + MemoizedFunc[int] + + X = MemoizedFunc[int, T, T2] + self.assertEqual(X.__parameters__, (T, T2)) + self.assertEqual(X.__args__, (int, T, T2)) + + Y = X[bytes, memoryview] + self.assertEqual(Y.__parameters__, ()) + self.assertEqual(Y.__args__, (int, bytes, memoryview)) + + def test_get_protocol_members(self): + with self.assertRaisesRegex(TypeError, "not a Protocol"): + get_protocol_members(object) + with self.assertRaisesRegex(TypeError, "not a Protocol"): + get_protocol_members(object()) + with self.assertRaisesRegex(TypeError, "not a Protocol"): + get_protocol_members(Protocol) + with self.assertRaisesRegex(TypeError, "not a Protocol"): + get_protocol_members(Generic) + + class P(Protocol): + a: int + def b(self) -> str: ... + @property + def c(self) -> int: ... + + self.assertEqual(get_protocol_members(P), {'a', 'b', 'c'}) + self.assertIsInstance(get_protocol_members(P), frozenset) + self.assertIsNot(get_protocol_members(P), P.__protocol_attrs__) + + class Concrete: + a: int + def b(self) -> str: return "capybara" + @property + def c(self) -> int: return 5 + + with self.assertRaisesRegex(TypeError, "not a Protocol"): + get_protocol_members(Concrete) + with self.assertRaisesRegex(TypeError, "not a Protocol"): + get_protocol_members(Concrete()) + + class ConcreteInherit(P): + a: int = 42 + def b(self) -> str: return "capybara" + @property + def c(self) -> int: return 5 + + with self.assertRaisesRegex(TypeError, "not a Protocol"): + get_protocol_members(ConcreteInherit) + with self.assertRaisesRegex(TypeError, "not a Protocol"): + get_protocol_members(ConcreteInherit()) + + def test_get_protocol_members_typing(self): + with self.assertRaisesRegex(TypeError, "not a Protocol"): + get_protocol_members(typing.Protocol) + + class P(typing.Protocol): + a: int + def b(self) -> str: ... + @property + def c(self) -> int: ... + + self.assertEqual(get_protocol_members(P), {'a', 'b', 'c'}) + self.assertIsInstance(get_protocol_members(P), frozenset) + if hasattr(P, "__protocol_attrs__"): + self.assertIsNot(get_protocol_members(P), P.__protocol_attrs__) + + class Concrete: + a: int + def b(self) -> str: return "capybara" + @property + def c(self) -> int: return 5 + + with self.assertRaisesRegex(TypeError, "not a Protocol"): + get_protocol_members(Concrete) + with self.assertRaisesRegex(TypeError, "not a Protocol"): + get_protocol_members(Concrete()) + + class ConcreteInherit(P): + a: int = 42 + def b(self) -> str: return "capybara" + @property + def c(self) -> int: return 5 + + with self.assertRaisesRegex(TypeError, "not a Protocol"): + get_protocol_members(ConcreteInherit) + with self.assertRaisesRegex(TypeError, "not a Protocol"): + get_protocol_members(ConcreteInherit()) + + def test_is_protocol(self): + self.assertTrue(is_protocol(Proto)) + self.assertTrue(is_protocol(Point)) + self.assertFalse(is_protocol(Concrete)) + self.assertFalse(is_protocol(Concrete())) + self.assertFalse(is_protocol(Generic)) + self.assertFalse(is_protocol(object)) + + # Protocol is not itself a protocol + self.assertFalse(is_protocol(Protocol)) + + def test_is_protocol_with_typing(self): + self.assertFalse(is_protocol(typing.Protocol)) + + class TypingProto(typing.Protocol): + a: int + + self.assertTrue(is_protocol(TypingProto)) + + class Concrete(TypingProto): + a: int + + self.assertFalse(is_protocol(Concrete)) + + @skip_if_py312b1 + def test_interaction_with_isinstance_checks_on_superclasses_with_ABCMeta(self): + # Ensure the cache is empty, or this test won't work correctly + collections.abc.Sized._abc_registry_clear() + + class Foo(collections.abc.Sized, Protocol): pass + + # CPython gh-105144: this previously raised TypeError + # if a Protocol subclass of Sized had been created + # before any isinstance() checks against Sized + self.assertNotIsInstance(1, collections.abc.Sized) + + @skip_if_py312b1 + def test_interaction_with_isinstance_checks_on_superclasses_with_ABCMeta_2(self): + # Ensure the cache is empty, or this test won't work correctly + collections.abc.Sized._abc_registry_clear() + + class Foo(typing.Sized, Protocol): pass + + # CPython gh-105144: this previously raised TypeError + # if a Protocol subclass of Sized had been created + # before any isinstance() checks against Sized + self.assertNotIsInstance(1, typing.Sized) + + def test_empty_protocol_decorated_with_final(self): + @final + @runtime_checkable + class EmptyProtocol(Protocol): ... + + self.assertIsSubclass(object, EmptyProtocol) + self.assertIsInstance(object(), EmptyProtocol) + + def test_protocol_decorated_with_final_callable_members(self): + @final + @runtime_checkable + class ProtocolWithMethod(Protocol): + def startswith(self, string: str) -> bool: ... + + self.assertIsSubclass(str, ProtocolWithMethod) + self.assertNotIsSubclass(int, ProtocolWithMethod) + self.assertIsInstance('foo', ProtocolWithMethod) + self.assertNotIsInstance(42, ProtocolWithMethod) + + def test_protocol_decorated_with_final_noncallable_members(self): + @final + @runtime_checkable + class ProtocolWithNonCallableMember(Protocol): + x: int + + class Foo: + x = 42 + + only_callable_members_please = ( + r"Protocols with non-method members don't support issubclass()" + ) + + with self.assertRaisesRegex(TypeError, only_callable_members_please): + issubclass(Foo, ProtocolWithNonCallableMember) + + with self.assertRaisesRegex(TypeError, only_callable_members_please): + issubclass(int, ProtocolWithNonCallableMember) + + self.assertIsInstance(Foo(), ProtocolWithNonCallableMember) + self.assertNotIsInstance(42, ProtocolWithNonCallableMember) + + def test_protocol_decorated_with_final_mixed_members(self): + @final + @runtime_checkable + class ProtocolWithMixedMembers(Protocol): + x: int + def method(self) -> None: ... + + class Foo: + x = 42 + def method(self) -> None: ... + + only_callable_members_please = ( + r"Protocols with non-method members don't support issubclass()" + ) + + with self.assertRaisesRegex(TypeError, only_callable_members_please): + issubclass(Foo, ProtocolWithMixedMembers) + + with self.assertRaisesRegex(TypeError, only_callable_members_please): + issubclass(int, ProtocolWithMixedMembers) + + self.assertIsInstance(Foo(), ProtocolWithMixedMembers) + self.assertNotIsInstance(42, ProtocolWithMixedMembers) + + @skip_if_early_py313_alpha + def test_protocol_issubclass_error_message(self): + class Vec2D(Protocol): + x: float + y: float + + def square_norm(self) -> float: + return self.x ** 2 + self.y ** 2 + + self.assertEqual(Vec2D.__protocol_attrs__, {'x', 'y', 'square_norm'}) + expected_error_message = ( + "Protocols with non-method members don't support issubclass()." + " Non-method members: 'x', 'y'." + ) + with self.assertRaisesRegex(TypeError, re.escape(expected_error_message)): + issubclass(int, Vec2D) + + +class Point2DGeneric(Generic[T], TypedDict): + a: T + b: T + + +class Bar(Foo): + b: int + + +class BarGeneric(FooGeneric[T], total=False): + b: int + + +class TypedDictTests(BaseTestCase): + def test_basics_functional_syntax(self): + Emp = TypedDict('Emp', {'name': str, 'id': int}) + self.assertIsSubclass(Emp, dict) + self.assertIsSubclass(Emp, typing.MutableMapping) + self.assertNotIsSubclass(Emp, collections.abc.Sequence) + jim = Emp(name='Jim', id=1) + self.assertIs(type(jim), dict) + self.assertEqual(jim['name'], 'Jim') + self.assertEqual(jim['id'], 1) + self.assertEqual(Emp.__name__, 'Emp') + self.assertEqual(Emp.__module__, __name__) + self.assertEqual(Emp.__bases__, (dict,)) + self.assertEqual(Emp.__annotations__, {'name': str, 'id': int}) + self.assertEqual(Emp.__total__, True) + + @skipIf(sys.version_info < (3, 13), "Change in behavior in 3.13") + def test_keywords_syntax_raises_on_3_13(self): + with self.assertRaises(TypeError): + Emp = TypedDict('Emp', name=str, id=int) + + @skipIf(sys.version_info >= (3, 13), "3.13 removes support for kwargs") + def test_basics_keywords_syntax(self): + with self.assertWarns(DeprecationWarning): + Emp = TypedDict('Emp', name=str, id=int) + self.assertIsSubclass(Emp, dict) + self.assertIsSubclass(Emp, typing.MutableMapping) + self.assertNotIsSubclass(Emp, collections.abc.Sequence) + jim = Emp(name='Jim', id=1) + self.assertIs(type(jim), dict) + self.assertEqual(jim['name'], 'Jim') + self.assertEqual(jim['id'], 1) + self.assertEqual(Emp.__name__, 'Emp') + self.assertEqual(Emp.__module__, __name__) + self.assertEqual(Emp.__bases__, (dict,)) + self.assertEqual(Emp.__annotations__, {'name': str, 'id': int}) + self.assertEqual(Emp.__total__, True) + + @skipIf(sys.version_info >= (3, 13), "3.13 removes support for kwargs") + def test_typeddict_special_keyword_names(self): + with self.assertWarns(DeprecationWarning): + TD = TypedDict("TD", cls=type, self=object, typename=str, _typename=int, + fields=list, _fields=dict) + self.assertEqual(TD.__name__, 'TD') + self.assertEqual(TD.__annotations__, {'cls': type, 'self': object, 'typename': str, + '_typename': int, 'fields': list, '_fields': dict}) + a = TD(cls=str, self=42, typename='foo', _typename=53, + fields=[('bar', tuple)], _fields={'baz', set}) + self.assertEqual(a['cls'], str) + self.assertEqual(a['self'], 42) + self.assertEqual(a['typename'], 'foo') + self.assertEqual(a['_typename'], 53) + self.assertEqual(a['fields'], [('bar', tuple)]) + self.assertEqual(a['_fields'], {'baz', set}) + + def test_typeddict_create_errors(self): + with self.assertRaises(TypeError): + TypedDict.__new__() + with self.assertRaises(TypeError): + TypedDict() + with self.assertRaises(TypeError): + TypedDict('Emp', [('name', str)], None) + + def test_typeddict_errors(self): + Emp = TypedDict('Emp', {'name': str, 'id': int}) + self.assertEqual(TypedDict.__module__, 'typing_extensions') + jim = Emp(name='Jim', id=1) + with self.assertRaises(TypeError): + isinstance({}, Emp) + with self.assertRaises(TypeError): + isinstance(jim, Emp) + with self.assertRaises(TypeError): + issubclass(dict, Emp) + + if not TYPING_3_11_0: + with self.assertRaises(TypeError), self.assertWarns(DeprecationWarning): + TypedDict('Hi', x=1) + with self.assertRaises(TypeError): + TypedDict('Hi', [('x', int), ('y', 1)]) + with self.assertRaises(TypeError): + TypedDict('Hi', [('x', int)], y=int) + + def test_py36_class_syntax_usage(self): + self.assertEqual(LabelPoint2D.__name__, 'LabelPoint2D') + self.assertEqual(LabelPoint2D.__module__, __name__) + self.assertEqual(LabelPoint2D.__annotations__, {'x': int, 'y': int, 'label': str}) + self.assertEqual(LabelPoint2D.__bases__, (dict,)) + self.assertEqual(LabelPoint2D.__total__, True) + self.assertNotIsSubclass(LabelPoint2D, typing.Sequence) + not_origin = Point2D(x=0, y=1) + self.assertEqual(not_origin['x'], 0) + self.assertEqual(not_origin['y'], 1) + other = LabelPoint2D(x=0, y=1, label='hi') + self.assertEqual(other['label'], 'hi') + + def test_pickle(self): + global EmpD # pickle wants to reference the class by name + EmpD = TypedDict('EmpD', {'name': str, 'id': int}) + jane = EmpD({'name': 'jane', 'id': 37}) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + z = pickle.dumps(jane, proto) + jane2 = pickle.loads(z) + self.assertEqual(jane2, jane) + self.assertEqual(jane2, {'name': 'jane', 'id': 37}) + ZZ = pickle.dumps(EmpD, proto) + EmpDnew = pickle.loads(ZZ) + self.assertEqual(EmpDnew({'name': 'jane', 'id': 37}), jane) + + def test_pickle_generic(self): + point = Point2DGeneric(a=5.0, b=3.0) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + z = pickle.dumps(point, proto) + point2 = pickle.loads(z) + self.assertEqual(point2, point) + self.assertEqual(point2, {'a': 5.0, 'b': 3.0}) + ZZ = pickle.dumps(Point2DGeneric, proto) + Point2DGenericNew = pickle.loads(ZZ) + self.assertEqual(Point2DGenericNew({'a': 5.0, 'b': 3.0}), point) + + def test_optional(self): + EmpD = TypedDict('EmpD', {'name': str, 'id': int}) + + self.assertEqual(typing.Optional[EmpD], typing.Union[None, EmpD]) + self.assertNotEqual(typing.List[EmpD], typing.Tuple[EmpD]) + + def test_total(self): + D = TypedDict('D', {'x': int}, total=False) + self.assertEqual(D(), {}) + self.assertEqual(D(x=1), {'x': 1}) + self.assertEqual(D.__total__, False) + self.assertEqual(D.__required_keys__, frozenset()) + self.assertEqual(D.__optional_keys__, {'x'}) + + self.assertEqual(Options(), {}) + self.assertEqual(Options(log_level=2), {'log_level': 2}) + self.assertEqual(Options.__total__, False) + self.assertEqual(Options.__required_keys__, frozenset()) + self.assertEqual(Options.__optional_keys__, {'log_level', 'log_path'}) + + def test_optional_keys(self): + class Point2Dor3D(Point2D, total=False): + z: int + + assert Point2Dor3D.__required_keys__ == frozenset(['x', 'y']) + assert Point2Dor3D.__optional_keys__ == frozenset(['z']) + + def test_keys_inheritance(self): + class BaseAnimal(TypedDict): + name: str + + class Animal(BaseAnimal, total=False): + voice: str + tail: bool + + class Cat(Animal): + fur_color: str + + assert BaseAnimal.__required_keys__ == frozenset(['name']) + assert BaseAnimal.__optional_keys__ == frozenset([]) + assert BaseAnimal.__annotations__ == {'name': str} + + assert Animal.__required_keys__ == frozenset(['name']) + assert Animal.__optional_keys__ == frozenset(['tail', 'voice']) + assert Animal.__annotations__ == { + 'name': str, + 'tail': bool, + 'voice': str, + } + + assert Cat.__required_keys__ == frozenset(['name', 'fur_color']) + assert Cat.__optional_keys__ == frozenset(['tail', 'voice']) + assert Cat.__annotations__ == { + 'fur_color': str, + 'name': str, + 'tail': bool, + 'voice': str, + } + + def test_required_notrequired_keys(self): + self.assertEqual(NontotalMovie.__required_keys__, + frozenset({"title"})) + self.assertEqual(NontotalMovie.__optional_keys__, + frozenset({"year"})) + + self.assertEqual(TotalMovie.__required_keys__, + frozenset({"title"})) + self.assertEqual(TotalMovie.__optional_keys__, + frozenset({"year"})) + + self.assertEqual(VeryAnnotated.__required_keys__, + frozenset()) + self.assertEqual(VeryAnnotated.__optional_keys__, + frozenset({"a"})) + + self.assertEqual(AnnotatedMovie.__required_keys__, + frozenset({"title"})) + self.assertEqual(AnnotatedMovie.__optional_keys__, + frozenset({"year"})) + + self.assertEqual(WeirdlyQuotedMovie.__required_keys__, + frozenset({"title"})) + self.assertEqual(WeirdlyQuotedMovie.__optional_keys__, + frozenset({"year"})) + + self.assertEqual(ChildTotalMovie.__required_keys__, + frozenset({"title"})) + self.assertEqual(ChildTotalMovie.__optional_keys__, + frozenset({"year"})) + + self.assertEqual(ChildDeeplyAnnotatedMovie.__required_keys__, + frozenset({"title"})) + self.assertEqual(ChildDeeplyAnnotatedMovie.__optional_keys__, + frozenset({"year"})) + + def test_multiple_inheritance(self): + class One(TypedDict): + one: int + class Two(TypedDict): + two: str + class Untotal(TypedDict, total=False): + untotal: str + Inline = TypedDict('Inline', {'inline': bool}) + class Regular: + pass + + class Child(One, Two): + child: bool + self.assertEqual( + Child.__required_keys__, + frozenset(['one', 'two', 'child']), + ) + self.assertEqual( + Child.__optional_keys__, + frozenset([]), + ) + self.assertEqual( + Child.__annotations__, + {'one': int, 'two': str, 'child': bool}, + ) + + class ChildWithOptional(One, Untotal): + child: bool + self.assertEqual( + ChildWithOptional.__required_keys__, + frozenset(['one', 'child']), + ) + self.assertEqual( + ChildWithOptional.__optional_keys__, + frozenset(['untotal']), + ) + self.assertEqual( + ChildWithOptional.__annotations__, + {'one': int, 'untotal': str, 'child': bool}, + ) + + class ChildWithTotalFalse(One, Untotal, total=False): + child: bool + self.assertEqual( + ChildWithTotalFalse.__required_keys__, + frozenset(['one']), + ) + self.assertEqual( + ChildWithTotalFalse.__optional_keys__, + frozenset(['untotal', 'child']), + ) + self.assertEqual( + ChildWithTotalFalse.__annotations__, + {'one': int, 'untotal': str, 'child': bool}, + ) + + class ChildWithInlineAndOptional(Untotal, Inline): + child: bool + self.assertEqual( + ChildWithInlineAndOptional.__required_keys__, + frozenset(['inline', 'child']), + ) + self.assertEqual( + ChildWithInlineAndOptional.__optional_keys__, + frozenset(['untotal']), + ) + self.assertEqual( + ChildWithInlineAndOptional.__annotations__, + {'inline': bool, 'untotal': str, 'child': bool}, + ) + + wrong_bases = [ + (One, Regular), + (Regular, One), + (One, Two, Regular), + (Inline, Regular), + (Untotal, Regular), + ] + for bases in wrong_bases: + with self.subTest(bases=bases): + with self.assertRaisesRegex( + TypeError, + 'cannot inherit from both a TypedDict type and a non-TypedDict', + ): + class Wrong(*bases): + pass + + def test_is_typeddict(self): + self.assertIs(is_typeddict(Point2D), True) + self.assertIs(is_typeddict(Point2Dor3D), True) + self.assertIs(is_typeddict(Union[str, int]), False) + # classes, not instances + self.assertIs(is_typeddict(Point2D()), False) + call_based = TypedDict('call_based', {'a': int}) + self.assertIs(is_typeddict(call_based), True) + self.assertIs(is_typeddict(call_based()), False) + + T = TypeVar("T") + class BarGeneric(TypedDict, Generic[T]): + a: T + self.assertIs(is_typeddict(BarGeneric), True) + self.assertIs(is_typeddict(BarGeneric[int]), False) + self.assertIs(is_typeddict(BarGeneric()), False) + + if hasattr(typing, "TypeAliasType"): + ns = {"TypedDict": TypedDict} + exec("""if True: + class NewGeneric[T](TypedDict): + a: T + """, ns) + NewGeneric = ns["NewGeneric"] + self.assertIs(is_typeddict(NewGeneric), True) + self.assertIs(is_typeddict(NewGeneric[int]), False) + self.assertIs(is_typeddict(NewGeneric()), False) + + # The TypedDict constructor is not itself a TypedDict + self.assertIs(is_typeddict(TypedDict), False) + if hasattr(typing, "TypedDict"): + self.assertIs(is_typeddict(typing.TypedDict), False) + + def test_is_typeddict_against_typeddict_from_typing(self): + Point = typing.TypedDict('Point', {'x': int, 'y': int}) + + class PointDict2D(typing.TypedDict): + x: int + y: int + + class PointDict3D(PointDict2D, total=False): + z: int + + assert is_typeddict(Point) is True + assert is_typeddict(PointDict2D) is True + assert is_typeddict(PointDict3D) is True + + @skipUnless(HAS_FORWARD_MODULE, "ForwardRef.__forward_module__ was added in 3.9") + def test_get_type_hints_cross_module_subclass(self): + self.assertNotIn("_DoNotImport", globals()) + self.assertEqual( + {k: v.__name__ for k, v in get_type_hints(Bar).items()}, + {'a': "_DoNotImport", 'b': "int"} + ) + + def test_get_type_hints_generic(self): + self.assertEqual( + get_type_hints(BarGeneric), + {'a': typing.Optional[T], 'b': int} + ) + + class FooBarGeneric(BarGeneric[int]): + c: str + + self.assertEqual( + get_type_hints(FooBarGeneric), + {'a': typing.Optional[T], 'b': int, 'c': str} + ) + + @skipUnless(TYPING_3_12_0, "PEP 695 required") + def test_pep695_generic_typeddict(self): + ns = {"TypedDict": TypedDict} + exec("""if True: + class A[T](TypedDict): + a: T + """, ns) + A = ns["A"] + T, = A.__type_params__ + self.assertIsInstance(T, TypeVar) + self.assertEqual(T.__name__, 'T') + self.assertEqual(A.__bases__, (Generic, dict)) + self.assertEqual(A.__orig_bases__, (TypedDict, Generic[T])) + self.assertEqual(A.__mro__, (A, Generic, dict, object)) + self.assertEqual(A.__parameters__, (T,)) + self.assertEqual(A[str].__parameters__, ()) + self.assertEqual(A[str].__args__, (str,)) + + def test_generic_inheritance(self): + class A(TypedDict, Generic[T]): + a: T + + self.assertEqual(A.__bases__, (Generic, dict)) + self.assertEqual(A.__orig_bases__, (TypedDict, Generic[T])) + self.assertEqual(A.__mro__, (A, Generic, dict, object)) + self.assertEqual(A.__parameters__, (T,)) + self.assertEqual(A[str].__parameters__, ()) + self.assertEqual(A[str].__args__, (str,)) + + class A2(Generic[T], TypedDict): + a: T + + self.assertEqual(A2.__bases__, (Generic, dict)) + self.assertEqual(A2.__orig_bases__, (Generic[T], TypedDict)) + self.assertEqual(A2.__mro__, (A2, Generic, dict, object)) + self.assertEqual(A2.__parameters__, (T,)) + self.assertEqual(A2[str].__parameters__, ()) + self.assertEqual(A2[str].__args__, (str,)) + + class B(A[KT], total=False): + b: KT + + self.assertEqual(B.__bases__, (Generic, dict)) + self.assertEqual(B.__orig_bases__, (A[KT],)) + self.assertEqual(B.__mro__, (B, Generic, dict, object)) + self.assertEqual(B.__parameters__, (KT,)) + self.assertEqual(B.__total__, False) + self.assertEqual(B.__optional_keys__, frozenset(['b'])) + self.assertEqual(B.__required_keys__, frozenset(['a'])) + + self.assertEqual(B[str].__parameters__, ()) + self.assertEqual(B[str].__args__, (str,)) + self.assertEqual(B[str].__origin__, B) + + class C(B[int]): + c: int + + self.assertEqual(C.__bases__, (Generic, dict)) + self.assertEqual(C.__orig_bases__, (B[int],)) + self.assertEqual(C.__mro__, (C, Generic, dict, object)) + self.assertEqual(C.__parameters__, ()) + self.assertEqual(C.__total__, True) + self.assertEqual(C.__optional_keys__, frozenset(['b'])) + self.assertEqual(C.__required_keys__, frozenset(['a', 'c'])) + assert C.__annotations__ == { + 'a': T, + 'b': KT, + 'c': int, + } + with self.assertRaises(TypeError): + C[str] + + + class Point3D(Point2DGeneric[T], Generic[T, KT]): + c: KT + + self.assertEqual(Point3D.__bases__, (Generic, dict)) + self.assertEqual(Point3D.__orig_bases__, (Point2DGeneric[T], Generic[T, KT])) + self.assertEqual(Point3D.__mro__, (Point3D, Generic, dict, object)) + self.assertEqual(Point3D.__parameters__, (T, KT)) + self.assertEqual(Point3D.__total__, True) + self.assertEqual(Point3D.__optional_keys__, frozenset()) + self.assertEqual(Point3D.__required_keys__, frozenset(['a', 'b', 'c'])) + self.assertEqual(Point3D.__annotations__, { + 'a': T, + 'b': T, + 'c': KT, + }) + self.assertEqual(Point3D[int, str].__origin__, Point3D) + + with self.assertRaises(TypeError): + Point3D[int] + + with self.assertRaises(TypeError): + class Point3D(Point2DGeneric[T], Generic[KT]): + c: KT + + def test_implicit_any_inheritance(self): + class A(TypedDict, Generic[T]): + a: T + + class B(A[KT], total=False): + b: KT + + class WithImplicitAny(B): + c: int + + self.assertEqual(WithImplicitAny.__bases__, (Generic, dict,)) + self.assertEqual(WithImplicitAny.__mro__, (WithImplicitAny, Generic, dict, object)) + # Consistent with GenericTests.test_implicit_any + self.assertEqual(WithImplicitAny.__parameters__, ()) + self.assertEqual(WithImplicitAny.__total__, True) + self.assertEqual(WithImplicitAny.__optional_keys__, frozenset(['b'])) + self.assertEqual(WithImplicitAny.__required_keys__, frozenset(['a', 'c'])) + assert WithImplicitAny.__annotations__ == { + 'a': T, + 'b': KT, + 'c': int, + } + with self.assertRaises(TypeError): + WithImplicitAny[str] + + @skipUnless(TYPING_3_9_0, "Was changed in 3.9") + def test_non_generic_subscript(self): + # For backward compatibility, subscription works + # on arbitrary TypedDict types. + # (But we don't attempt to backport this misfeature onto 3.8.) + class TD(TypedDict): + a: T + A = TD[int] + self.assertEqual(A.__origin__, TD) + self.assertEqual(A.__parameters__, ()) + self.assertEqual(A.__args__, (int,)) + a = A(a=1) + self.assertIs(type(a), dict) + self.assertEqual(a, {'a': 1}) + + def test_orig_bases(self): + T = TypeVar('T') + + class Parent(TypedDict): + pass + + class Child(Parent): + pass + + class OtherChild(Parent): + pass + + class MixedChild(Child, OtherChild, Parent): + pass + + class GenericParent(TypedDict, Generic[T]): + pass + + class GenericChild(GenericParent[int]): + pass + + class OtherGenericChild(GenericParent[str]): + pass + + class MixedGenericChild(GenericChild, OtherGenericChild, GenericParent[float]): + pass + + class MultipleGenericBases(GenericParent[int], GenericParent[float]): + pass + + CallTypedDict = TypedDict('CallTypedDict', {}) + + self.assertEqual(Parent.__orig_bases__, (TypedDict,)) + self.assertEqual(Child.__orig_bases__, (Parent,)) + self.assertEqual(OtherChild.__orig_bases__, (Parent,)) + self.assertEqual(MixedChild.__orig_bases__, (Child, OtherChild, Parent,)) + self.assertEqual(GenericParent.__orig_bases__, (TypedDict, Generic[T])) + self.assertEqual(GenericChild.__orig_bases__, (GenericParent[int],)) + self.assertEqual(OtherGenericChild.__orig_bases__, (GenericParent[str],)) + self.assertEqual(MixedGenericChild.__orig_bases__, (GenericChild, OtherGenericChild, GenericParent[float])) + self.assertEqual(MultipleGenericBases.__orig_bases__, (GenericParent[int], GenericParent[float])) + self.assertEqual(CallTypedDict.__orig_bases__, (TypedDict,)) + + def test_zero_fields_typeddicts(self): + T1 = TypedDict("T1", {}) + class T2(TypedDict): pass + try: + ns = {"TypedDict": TypedDict} + exec("class T3[tvar](TypedDict): pass", ns) + T3 = ns["T3"] + except SyntaxError: + class T3(TypedDict): pass + S = TypeVar("S") + class T4(TypedDict, Generic[S]): pass + + expected_warning = re.escape( + "Failing to pass a value for the 'fields' parameter is deprecated " + "and will be disallowed in Python 3.15. " + "To create a TypedDict class with 0 fields " + "using the functional syntax, " + "pass an empty dictionary, e.g. `T5 = TypedDict('T5', {})`." + ) + with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"): + T5 = TypedDict('T5') + + expected_warning = re.escape( + "Passing `None` as the 'fields' parameter is deprecated " + "and will be disallowed in Python 3.15. " + "To create a TypedDict class with 0 fields " + "using the functional syntax, " + "pass an empty dictionary, e.g. `T6 = TypedDict('T6', {})`." + ) + with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"): + T6 = TypedDict('T6', None) + + for klass in T1, T2, T3, T4, T5, T6: + with self.subTest(klass=klass.__name__): + self.assertEqual(klass.__annotations__, {}) + self.assertEqual(klass.__required_keys__, set()) + self.assertEqual(klass.__optional_keys__, set()) + self.assertIsInstance(klass(), dict) + + def test_readonly_inheritance(self): + class Base1(TypedDict): + a: ReadOnly[int] + + class Child1(Base1): + b: str + + self.assertEqual(Child1.__readonly_keys__, frozenset({'a'})) + self.assertEqual(Child1.__mutable_keys__, frozenset({'b'})) + + class Base2(TypedDict): + a: ReadOnly[int] + + class Child2(Base2): + b: str + + self.assertEqual(Child1.__readonly_keys__, frozenset({'a'})) + self.assertEqual(Child1.__mutable_keys__, frozenset({'b'})) + + def test_cannot_make_mutable_key_readonly(self): + class Base(TypedDict): + a: int + + with self.assertRaises(TypeError): + class Child(Base): + a: ReadOnly[int] + + def test_can_make_readonly_key_mutable(self): + class Base(TypedDict): + a: ReadOnly[int] + + class Child(Base): + a: int + + self.assertEqual(Child.__readonly_keys__, frozenset()) + self.assertEqual(Child.__mutable_keys__, frozenset({'a'})) + + def test_combine_qualifiers(self): + class AllTheThings(TypedDict): + a: Annotated[Required[ReadOnly[int]], "why not"] + b: Required[Annotated[ReadOnly[int], "why not"]] + c: ReadOnly[NotRequired[Annotated[int, "why not"]]] + d: NotRequired[Annotated[int, "why not"]] + + self.assertEqual(AllTheThings.__required_keys__, frozenset({'a', 'b'})) + self.assertEqual(AllTheThings.__optional_keys__, frozenset({'c', 'd'})) + self.assertEqual(AllTheThings.__readonly_keys__, frozenset({'a', 'b', 'c'})) + self.assertEqual(AllTheThings.__mutable_keys__, frozenset({'d'})) + + +class AnnotatedTests(BaseTestCase): + + def test_repr(self): + if hasattr(typing, 'Annotated'): + mod_name = 'typing' + else: + mod_name = "typing_extensions" + self.assertEqual( + repr(Annotated[int, 4, 5]), + mod_name + ".Annotated[int, 4, 5]" + ) + self.assertEqual( + repr(Annotated[List[int], 4, 5]), + mod_name + ".Annotated[typing.List[int], 4, 5]" + ) + + def test_flatten(self): + A = Annotated[Annotated[int, 4], 5] + self.assertEqual(A, Annotated[int, 4, 5]) + self.assertEqual(A.__metadata__, (4, 5)) + 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",)) + self.assertEqual(L[int].__origin__, List[int]) + with self.assertRaises(TypeError): + LI[int] + with self.assertRaises(TypeError): + L[int, float] + + def test_hash_eq(self): + self.assertEqual(len({Annotated[int, 4, 5], Annotated[int, 4, 5]}), 1) + self.assertNotEqual(Annotated[int, 4, 5], Annotated[int, 5, 4]) + self.assertNotEqual(Annotated[int, 4, 5], Annotated[str, 4, 5]) + self.assertNotEqual(Annotated[int, 4], Annotated[int, 4, 4]) + self.assertEqual( + {Annotated[int, 4, 5], Annotated[int, 4, 5], Annotated[T, 4, 5]}, + {Annotated[int, 4, 5], Annotated[T, 4, 5]} + ) + + def test_instantiate(self): + class C: + classvar = 4 + + def __init__(self, x): + self.x = x + + def __eq__(self, other): + if not isinstance(other, C): + return NotImplemented + return other.x == self.x + + A = Annotated[C, "a decoration"] + a = A(5) + c = C(5) + self.assertEqual(a, c) + self.assertEqual(a.x, c.x) + self.assertEqual(a.classvar, c.classvar) + + def test_instantiate_generic(self): + MyCount = Annotated[typing_extensions.Counter[T], "my decoration"] + self.assertEqual(MyCount([4, 4, 5]), {4: 2, 5: 1}) + self.assertEqual(MyCount[int]([4, 4, 5]), {4: 2, 5: 1}) + + def test_cannot_instantiate_forward(self): + A = Annotated["int", (5, 6)] + with self.assertRaises(TypeError): + A(5) + + def test_cannot_instantiate_type_var(self): + A = Annotated[T, (5, 6)] + with self.assertRaises(TypeError): + A(5) + + def test_cannot_getattr_typevar(self): + with self.assertRaises(AttributeError): + Annotated[T, (5, 7)].x + + def test_attr_passthrough(self): + class C: + classvar = 4 + + A = Annotated[C, "a decoration"] + self.assertEqual(A.classvar, 4) + A.x = 5 + self.assertEqual(C.x, 5) + + @skipIf(sys.version_info[:2] in ((3, 9), (3, 10)), "Waiting for bpo-46491 bugfix.") + def test_special_form_containment(self): + class C: + classvar: Annotated[ClassVar[int], "a decoration"] = 4 + const: Annotated[Final[int], "Const"] = 4 + + self.assertEqual(get_type_hints(C, globals())["classvar"], ClassVar[int]) + self.assertEqual(get_type_hints(C, globals())["const"], Final[int]) + + def test_cannot_subclass(self): + with self.assertRaisesRegex(TypeError, "Cannot subclass .*Annotated"): + class C(Annotated): + pass + + def test_cannot_check_instance(self): + with self.assertRaises(TypeError): + isinstance(5, Annotated[int, "positive"]) + + def test_cannot_check_subclass(self): + with self.assertRaises(TypeError): + issubclass(int, Annotated[int, "positive"]) + + def test_pickle(self): + samples = [typing.Any, typing.Union[int, str], + typing.Optional[str], Tuple[int, ...], + typing.Callable[[str], bytes], + Self, LiteralString, Never] + + for t in samples: + x = Annotated[t, "a"] + + for prot in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(protocol=prot, type=t): + pickled = pickle.dumps(x, prot) + restored = pickle.loads(pickled) + self.assertEqual(x, restored) + + global _Annotated_test_G + + class _Annotated_test_G(Generic[T]): + x = 1 + + G = Annotated[_Annotated_test_G[int], "A decoration"] + G.foo = 42 + G.bar = 'abc' + + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + z = pickle.dumps(G, proto) + x = pickle.loads(z) + self.assertEqual(x.foo, 42) + self.assertEqual(x.bar, 'abc') + self.assertEqual(x.x, 1) + + def test_subst(self): + dec = "a decoration" + dec2 = "another decoration" + + S = Annotated[T, dec2] + self.assertEqual(S[int], Annotated[int, dec2]) + + self.assertEqual(S[Annotated[int, dec]], Annotated[int, dec, dec2]) + L = Annotated[List[T], dec] + + self.assertEqual(L[int], Annotated[List[int], dec]) + with self.assertRaises(TypeError): + L[int, int] + + self.assertEqual(S[L[int]], Annotated[List[int], dec, dec2]) + + D = Annotated[Dict[KT, VT], dec] + self.assertEqual(D[str, int], Annotated[Dict[str, int], dec]) + with self.assertRaises(TypeError): + D[int] + + It = Annotated[int, dec] + with self.assertRaises(TypeError): + It[None] + + LI = L[int] + with self.assertRaises(TypeError): + LI[None] + + def test_annotated_in_other_types(self): + X = List[Annotated[T, 5]] + self.assertEqual(X[int], List[Annotated[int, 5]]) + + +class GetTypeHintsTests(BaseTestCase): + def test_get_type_hints(self): + def foobar(x: List['X']): ... + X = Annotated[int, (1, 10)] + self.assertEqual( + get_type_hints(foobar, globals(), locals()), + {'x': List[int]} + ) + self.assertEqual( + get_type_hints(foobar, globals(), locals(), include_extras=True), + {'x': List[Annotated[int, (1, 10)]]} + ) + BA = Tuple[Annotated[T, (1, 0)], ...] + def barfoo(x: BA): ... + self.assertEqual(get_type_hints(barfoo, globals(), locals())['x'], Tuple[T, ...]) + self.assertIs( + get_type_hints(barfoo, globals(), locals(), include_extras=True)['x'], + BA + ) + def barfoo2(x: typing.Callable[..., Annotated[List[T], "const"]], + y: typing.Union[int, Annotated[T, "mutable"]]): ... + self.assertEqual( + get_type_hints(barfoo2, globals(), locals()), + {'x': typing.Callable[..., List[T]], 'y': typing.Union[int, T]} + ) + BA2 = typing.Callable[..., List[T]] + def barfoo3(x: BA2): ... + self.assertIs( + get_type_hints(barfoo3, globals(), locals(), include_extras=True)["x"], + BA2 + ) + + def test_get_type_hints_refs(self): + + Const = Annotated[T, "Const"] + + class MySet(Generic[T]): + + def __ior__(self, other: "Const[MySet[T]]") -> "MySet[T]": + ... + + def __iand__(self, other: Const["MySet[T]"]) -> "MySet[T]": + ... + + self.assertEqual( + get_type_hints(MySet.__iand__, globals(), locals()), + {'other': MySet[T], 'return': MySet[T]} + ) + + self.assertEqual( + get_type_hints(MySet.__iand__, globals(), locals(), include_extras=True), + {'other': Const[MySet[T]], 'return': MySet[T]} + ) + + self.assertEqual( + get_type_hints(MySet.__ior__, globals(), locals()), + {'other': MySet[T], 'return': MySet[T]} + ) + + def test_get_type_hints_typeddict(self): + assert get_type_hints(TotalMovie) == {'title': str, 'year': int} + assert get_type_hints(TotalMovie, include_extras=True) == { + 'title': str, + 'year': NotRequired[int], + } + + assert get_type_hints(AnnotatedMovie) == {'title': str, 'year': int} + assert get_type_hints(AnnotatedMovie, include_extras=True) == { + 'title': Annotated[Required[str], "foobar"], + 'year': NotRequired[Annotated[int, 2000]], + } + + def test_orig_bases(self): + T = TypeVar('T') + + class Parent(TypedDict): + pass + + class Child(Parent): + pass + + class OtherChild(Parent): + pass + + class MixedChild(Child, OtherChild, Parent): + pass + + class GenericParent(TypedDict, Generic[T]): + pass + + class GenericChild(GenericParent[int]): + pass + + class OtherGenericChild(GenericParent[str]): + pass + + class MixedGenericChild(GenericChild, OtherGenericChild, GenericParent[float]): + pass + + class MultipleGenericBases(GenericParent[int], GenericParent[float]): + pass + + CallTypedDict = TypedDict('CallTypedDict', {}) + + self.assertEqual(Parent.__orig_bases__, (TypedDict,)) + self.assertEqual(Child.__orig_bases__, (Parent,)) + self.assertEqual(OtherChild.__orig_bases__, (Parent,)) + self.assertEqual(MixedChild.__orig_bases__, (Child, OtherChild, Parent,)) + self.assertEqual(GenericParent.__orig_bases__, (TypedDict, Generic[T])) + self.assertEqual(GenericChild.__orig_bases__, (GenericParent[int],)) + self.assertEqual(OtherGenericChild.__orig_bases__, (GenericParent[str],)) + self.assertEqual(MixedGenericChild.__orig_bases__, (GenericChild, OtherGenericChild, GenericParent[float])) + self.assertEqual(MultipleGenericBases.__orig_bases__, (GenericParent[int], GenericParent[float])) + self.assertEqual(CallTypedDict.__orig_bases__, (TypedDict,)) + + +class TypeAliasTests(BaseTestCase): + def test_canonical_usage_with_variable_annotation(self): + ns = {} + exec('Alias: TypeAlias = Employee', globals(), ns) + + def test_canonical_usage_with_type_comment(self): + Alias: TypeAlias = Employee + + def test_cannot_instantiate(self): + with self.assertRaises(TypeError): + TypeAlias() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(42, TypeAlias) + + def test_no_issubclass(self): + with self.assertRaises(TypeError): + issubclass(Employee, TypeAlias) + + with self.assertRaises(TypeError): + issubclass(TypeAlias, Employee) + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(TypeAlias): + pass + + with self.assertRaises(TypeError): + class C(type(TypeAlias)): + pass + + def test_repr(self): + if hasattr(typing, 'TypeAlias'): + self.assertEqual(repr(TypeAlias), 'typing.TypeAlias') + else: + self.assertEqual(repr(TypeAlias), 'typing_extensions.TypeAlias') + + def test_cannot_subscript(self): + with self.assertRaises(TypeError): + TypeAlias[int] + +class ParamSpecTests(BaseTestCase): + + def test_basic_plain(self): + P = ParamSpec('P') + self.assertEqual(P, P) + self.assertIsInstance(P, ParamSpec) + self.assertEqual(P.__name__, 'P') + # Should be hashable + hash(P) + + def test_repr(self): + P = ParamSpec('P') + P_co = ParamSpec('P_co', covariant=True) + P_contra = ParamSpec('P_contra', contravariant=True) + P_infer = ParamSpec('P_infer', infer_variance=True) + P_2 = ParamSpec('P_2') + self.assertEqual(repr(P), '~P') + self.assertEqual(repr(P_2), '~P_2') + + # Note: PEP 612 doesn't require these to be repr-ed correctly, but + # just follow CPython. + self.assertEqual(repr(P_co), '+P_co') + self.assertEqual(repr(P_contra), '-P_contra') + # On other versions we use typing.ParamSpec, but it is not aware of + # infer_variance=. Not worth creating our own version of ParamSpec + # for this. + if hasattr(typing, 'TypeAliasType') or not hasattr(typing, 'ParamSpec'): + self.assertEqual(repr(P_infer), 'P_infer') + else: + self.assertEqual(repr(P_infer), '~P_infer') + + def test_variance(self): + P_co = ParamSpec('P_co', covariant=True) + P_contra = ParamSpec('P_contra', contravariant=True) + P_infer = ParamSpec('P_infer', infer_variance=True) + + self.assertIs(P_co.__covariant__, True) + self.assertIs(P_co.__contravariant__, False) + self.assertIs(P_co.__infer_variance__, False) + + self.assertIs(P_contra.__covariant__, False) + self.assertIs(P_contra.__contravariant__, True) + self.assertIs(P_contra.__infer_variance__, False) + + self.assertIs(P_infer.__covariant__, False) + self.assertIs(P_infer.__contravariant__, False) + self.assertIs(P_infer.__infer_variance__, True) + + def test_valid_uses(self): + P = ParamSpec('P') + T = TypeVar('T') + C1 = typing.Callable[P, int] + self.assertEqual(C1.__args__, (P, int)) + self.assertEqual(C1.__parameters__, (P,)) + C2 = typing.Callable[P, T] + self.assertEqual(C2.__args__, (P, T)) + self.assertEqual(C2.__parameters__, (P, T)) + + # Test collections.abc.Callable too. + if sys.version_info[:2] >= (3, 9): + # Note: no tests for Callable.__parameters__ here + # because types.GenericAlias Callable is hardcoded to search + # for tp_name "TypeVar" in C. This was changed in 3.10. + C3 = collections.abc.Callable[P, int] + self.assertEqual(C3.__args__, (P, int)) + C4 = collections.abc.Callable[P, T] + self.assertEqual(C4.__args__, (P, T)) + + # ParamSpec instances should also have args and kwargs attributes. + # Note: not in dir(P) because of __class__ hacks + self.assertTrue(hasattr(P, 'args')) + self.assertTrue(hasattr(P, 'kwargs')) + + @skipIf((3, 10, 0) <= sys.version_info[:3] <= (3, 10, 2), "Needs bpo-46676.") + def test_args_kwargs(self): + P = ParamSpec('P') + P_2 = ParamSpec('P_2') + # Note: not in dir(P) because of __class__ hacks + self.assertTrue(hasattr(P, 'args')) + self.assertTrue(hasattr(P, 'kwargs')) + self.assertIsInstance(P.args, ParamSpecArgs) + self.assertIsInstance(P.kwargs, ParamSpecKwargs) + self.assertIs(P.args.__origin__, P) + self.assertIs(P.kwargs.__origin__, P) + self.assertEqual(P.args, P.args) + self.assertEqual(P.kwargs, P.kwargs) + self.assertNotEqual(P.args, P_2.args) + self.assertNotEqual(P.kwargs, P_2.kwargs) + self.assertNotEqual(P.args, P.kwargs) + self.assertNotEqual(P.kwargs, P.args) + self.assertNotEqual(P.args, P_2.kwargs) + self.assertEqual(repr(P.args), "P.args") + self.assertEqual(repr(P.kwargs), "P.kwargs") + + def test_user_generics(self): + T = TypeVar("T") + P = ParamSpec("P") + P_2 = ParamSpec("P_2") + + class X(Generic[T, P]): + pass + + class Y(Protocol[T, P]): + pass + + for klass in X, Y: + with self.subTest(klass=klass.__name__): + G1 = klass[int, P_2] + self.assertEqual(G1.__args__, (int, P_2)) + self.assertEqual(G1.__parameters__, (P_2,)) + + G2 = klass[int, Concatenate[int, P_2]] + self.assertEqual(G2.__args__, (int, Concatenate[int, P_2])) + self.assertEqual(G2.__parameters__, (P_2,)) + + # The following are some valid uses cases in PEP 612 that don't work: + # These do not work in 3.9, _type_check blocks the list and ellipsis. + # G3 = X[int, [int, bool]] + # G4 = X[int, ...] + # G5 = Z[[int, str, bool]] + # Not working because this is special-cased in 3.10. + # G6 = Z[int, str, bool] + + class Z(Generic[P]): + pass + + class ProtoZ(Protocol[P]): + pass + + def test_pickle(self): + global P, P_co, P_contra, P_default + P = ParamSpec('P') + P_co = ParamSpec('P_co', covariant=True) + P_contra = ParamSpec('P_contra', contravariant=True) + P_default = ParamSpec('P_default', default=int) + for proto in range(pickle.HIGHEST_PROTOCOL): + with self.subTest(f'Pickle protocol {proto}'): + for paramspec in (P, P_co, P_contra, P_default): + z = pickle.loads(pickle.dumps(paramspec, proto)) + self.assertEqual(z.__name__, paramspec.__name__) + self.assertEqual(z.__covariant__, paramspec.__covariant__) + self.assertEqual(z.__contravariant__, paramspec.__contravariant__) + self.assertEqual(z.__bound__, paramspec.__bound__) + self.assertEqual(z.__default__, paramspec.__default__) + + def test_eq(self): + P = ParamSpec('P') + self.assertEqual(P, P) + self.assertEqual(hash(P), hash(P)) + # ParamSpec should compare by id similar to TypeVar in CPython + self.assertNotEqual(ParamSpec('P'), P) + self.assertIsNot(ParamSpec('P'), P) + # Note: normally you don't test this as it breaks when there's + # a hash collision. However, ParamSpec *must* guarantee that + # as long as two objects don't have the same ID, their hashes + # won't be the same. + self.assertNotEqual(hash(ParamSpec('P')), hash(P)) + + +class ConcatenateTests(BaseTestCase): + def test_basics(self): + P = ParamSpec('P') + + class MyClass: ... + + c = Concatenate[MyClass, P] + self.assertNotEqual(c, Concatenate) + + def test_valid_uses(self): + P = ParamSpec('P') + T = TypeVar('T') + + C1 = Callable[Concatenate[int, P], int] + C2 = Callable[Concatenate[int, T, P], T] + + # Test collections.abc.Callable too. + if sys.version_info[:2] >= (3, 9): + C3 = collections.abc.Callable[Concatenate[int, P], int] + C4 = collections.abc.Callable[Concatenate[int, T, P], T] + + def test_invalid_uses(self): + P = ParamSpec('P') + T = TypeVar('T') + + with self.assertRaisesRegex( + TypeError, + 'Cannot take a Concatenate of no types', + ): + Concatenate[()] + + with self.assertRaisesRegex( + TypeError, + 'The last parameter to Concatenate should be a ParamSpec variable', + ): + Concatenate[P, T] + + 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') + C1 = Concatenate[int, P] + C2 = Concatenate[int, T, P] + self.assertEqual(C1.__origin__, Concatenate) + self.assertEqual(C1.__args__, (int, P)) + self.assertEqual(C2.__origin__, Concatenate) + self.assertEqual(C2.__args__, (int, T, P)) + + def test_eq(self): + P = ParamSpec('P') + C1 = Concatenate[int, P] + C2 = Concatenate[int, P] + C3 = Concatenate[int, T, P] + self.assertEqual(C1, C2) + self.assertEqual(hash(C1), hash(C2)) + self.assertNotEqual(C1, C3) + + +class TypeGuardTests(BaseTestCase): + def test_basics(self): + TypeGuard[int] # OK + self.assertEqual(TypeGuard[int], TypeGuard[int]) + + def foo(arg) -> TypeGuard[int]: ... + self.assertEqual(gth(foo), {'return': TypeGuard[int]}) + + def test_repr(self): + if hasattr(typing, 'TypeGuard'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(TypeGuard), f'{mod_name}.TypeGuard') + cv = TypeGuard[int] + self.assertEqual(repr(cv), f'{mod_name}.TypeGuard[int]') + cv = TypeGuard[Employee] + self.assertEqual(repr(cv), f'{mod_name}.TypeGuard[{__name__}.Employee]') + cv = TypeGuard[Tuple[int]] + self.assertEqual(repr(cv), f'{mod_name}.TypeGuard[typing.Tuple[int]]') + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(TypeGuard)): + pass + with self.assertRaises(TypeError): + class C(type(TypeGuard[int])): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + TypeGuard() + with self.assertRaises(TypeError): + type(TypeGuard)() + with self.assertRaises(TypeError): + type(TypeGuard[Optional[int]])() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, TypeGuard[int]) + with self.assertRaises(TypeError): + issubclass(int, TypeGuard) + + +class LiteralStringTests(BaseTestCase): + def test_basics(self): + class Foo: + def bar(self) -> LiteralString: ... + def baz(self) -> "LiteralString": ... + + self.assertEqual(gth(Foo.bar), {'return': LiteralString}) + self.assertEqual(gth(Foo.baz), {'return': LiteralString}) + + def test_get_origin(self): + self.assertIsNone(get_origin(LiteralString)) + + def test_repr(self): + if hasattr(typing, 'LiteralString'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(LiteralString), '{}.LiteralString'.format(mod_name)) + + def test_cannot_subscript(self): + with self.assertRaises(TypeError): + LiteralString[int] + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(LiteralString)): + pass + with self.assertRaises(TypeError): + class C(LiteralString): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + LiteralString() + with self.assertRaises(TypeError): + type(LiteralString)() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, LiteralString) + with self.assertRaises(TypeError): + issubclass(int, LiteralString) + + def test_alias(self): + StringTuple = Tuple[LiteralString, LiteralString] + class Alias: + def return_tuple(self) -> StringTuple: + return ("foo", "pep" + "675") + + def test_typevar(self): + StrT = TypeVar("StrT", bound=LiteralString) + self.assertIs(StrT.__bound__, LiteralString) + + def test_pickle(self): + for proto in range(pickle.HIGHEST_PROTOCOL): + pickled = pickle.dumps(LiteralString, protocol=proto) + self.assertIs(LiteralString, pickle.loads(pickled)) + + +class SelfTests(BaseTestCase): + def test_basics(self): + class Foo: + def bar(self) -> Self: ... + + self.assertEqual(gth(Foo.bar), {'return': Self}) + + def test_repr(self): + if hasattr(typing, 'Self'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(Self), '{}.Self'.format(mod_name)) + + def test_cannot_subscript(self): + with self.assertRaises(TypeError): + Self[int] + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(Self)): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + Self() + with self.assertRaises(TypeError): + type(Self)() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, Self) + with self.assertRaises(TypeError): + issubclass(int, Self) + + def test_alias(self): + TupleSelf = Tuple[Self, Self] + class Alias: + def return_tuple(self) -> TupleSelf: + return (self, self) + + def test_pickle(self): + for proto in range(pickle.HIGHEST_PROTOCOL): + pickled = pickle.dumps(Self, protocol=proto) + self.assertIs(Self, pickle.loads(pickled)) + + +class UnpackTests(BaseTestCase): + def test_basic_plain(self): + Ts = TypeVarTuple('Ts') + self.assertEqual(Unpack[Ts], Unpack[Ts]) + with self.assertRaises(TypeError): + Unpack() + + def test_repr(self): + Ts = TypeVarTuple('Ts') + self.assertEqual(repr(Unpack[Ts]), f'{Unpack.__module__}.Unpack[Ts]') + + def test_cannot_subclass_vars(self): + with self.assertRaises(TypeError): + class V(Unpack[TypeVarTuple('Ts')]): + pass + + def test_tuple(self): + Ts = TypeVarTuple('Ts') + Tuple[Unpack[Ts]] + + def test_union(self): + Xs = TypeVarTuple('Xs') + Ys = TypeVarTuple('Ys') + self.assertEqual( + Union[Unpack[Xs]], + Unpack[Xs] + ) + self.assertNotEqual( + Union[Unpack[Xs]], + Union[Unpack[Xs], Unpack[Ys]] + ) + self.assertEqual( + Union[Unpack[Xs], Unpack[Xs]], + Unpack[Xs] + ) + self.assertNotEqual( + Union[Unpack[Xs], int], + Union[Unpack[Xs]] + ) + self.assertNotEqual( + Union[Unpack[Xs], int], + Union[int] + ) + self.assertEqual( + Union[Unpack[Xs], int].__args__, + (Unpack[Xs], int) + ) + self.assertEqual( + Union[Unpack[Xs], int].__parameters__, + (Xs,) + ) + self.assertIs( + Union[Unpack[Xs], int].__origin__, + Union + ) + + def test_concatenation(self): + Xs = TypeVarTuple('Xs') + self.assertEqual(Tuple[int, Unpack[Xs]].__args__, (int, Unpack[Xs])) + self.assertEqual(Tuple[Unpack[Xs], int].__args__, (Unpack[Xs], int)) + self.assertEqual(Tuple[int, Unpack[Xs], str].__args__, + (int, Unpack[Xs], str)) + class C(Generic[Unpack[Xs]]): pass + class D(Protocol[Unpack[Xs]]): pass + for klass in C, D: + with self.subTest(klass=klass.__name__): + self.assertEqual(klass[int, Unpack[Xs]].__args__, (int, Unpack[Xs])) + self.assertEqual(klass[Unpack[Xs], int].__args__, (Unpack[Xs], int)) + self.assertEqual(klass[int, Unpack[Xs], str].__args__, + (int, Unpack[Xs], str)) + + def test_class(self): + Ts = TypeVarTuple('Ts') + + class C(Generic[Unpack[Ts]]): pass + class D(Protocol[Unpack[Ts]]): pass + + for klass in C, D: + with self.subTest(klass=klass.__name__): + self.assertEqual(klass[int].__args__, (int,)) + self.assertEqual(klass[int, str].__args__, (int, str)) + + with self.assertRaises(TypeError): + class C(Generic[Unpack[Ts], int]): pass + + with self.assertRaises(TypeError): + class D(Protocol[Unpack[Ts], int]): pass + + T1 = TypeVar('T') + T2 = TypeVar('T') + class C(Generic[T1, T2, Unpack[Ts]]): pass + class D(Protocol[T1, T2, Unpack[Ts]]): pass + for klass in C, D: + with self.subTest(klass=klass.__name__): + self.assertEqual(klass[int, str].__args__, (int, str)) + self.assertEqual(klass[int, str, float].__args__, (int, str, float)) + self.assertEqual( + klass[int, str, float, bool].__args__, (int, str, float, bool) + ) + # A bug was fixed in 3.11.1 + # (https://github.com/python/cpython/commit/74920aa27d0c57443dd7f704d6272cca9c507ab3) + # That means this assertion doesn't pass on 3.11.0, + # but it passes on all other Python versions + if sys.version_info[:3] != (3, 11, 0): + with self.assertRaises(TypeError): + klass[int] + + +class TypeVarTupleTests(BaseTestCase): + + def test_basic_plain(self): + Ts = TypeVarTuple('Ts') + self.assertEqual(Ts, Ts) + self.assertIsInstance(Ts, TypeVarTuple) + Xs = TypeVarTuple('Xs') + Ys = TypeVarTuple('Ys') + self.assertNotEqual(Xs, Ys) + + def test_repr(self): + Ts = TypeVarTuple('Ts') + self.assertEqual(repr(Ts), 'Ts') + + def test_no_redefinition(self): + self.assertNotEqual(TypeVarTuple('Ts'), TypeVarTuple('Ts')) + + def test_cannot_subclass_vars(self): + with self.assertRaises(TypeError): + class V(TypeVarTuple('Ts')): + pass + + def test_cannot_subclass_var_itself(self): + with self.assertRaises(TypeError): + class V(TypeVarTuple): + pass + + def test_cannot_instantiate_vars(self): + Ts = TypeVarTuple('Ts') + with self.assertRaises(TypeError): + Ts() + + def test_tuple(self): + Ts = TypeVarTuple('Ts') + # Not legal at type checking time but we can't really check against it. + Tuple[Ts] + + def test_args_and_parameters(self): + Ts = TypeVarTuple('Ts') + + t = Tuple[tuple(Ts)] + if sys.version_info >= (3, 11): + self.assertEqual(t.__args__, (typing.Unpack[Ts],)) + else: + self.assertEqual(t.__args__, (Unpack[Ts],)) + self.assertEqual(t.__parameters__, (Ts,)) + + def test_pickle(self): + global Ts, Ts_default # pickle wants to reference the class by name + Ts = TypeVarTuple('Ts') + Ts_default = TypeVarTuple('Ts_default', default=Unpack[Tuple[int, str]]) + + for proto in range(pickle.HIGHEST_PROTOCOL): + for typevartuple in (Ts, Ts_default): + z = pickle.loads(pickle.dumps(typevartuple, proto)) + self.assertEqual(z.__name__, typevartuple.__name__) + self.assertEqual(z.__default__, typevartuple.__default__) + + +class FinalDecoratorTests(BaseTestCase): + def test_final_unmodified(self): + def func(x): ... + self.assertIs(func, final(func)) + + def test_dunder_final(self): + @final + def func(): ... + @final + class Cls: ... + self.assertIs(True, func.__final__) + self.assertIs(True, Cls.__final__) + + class Wrapper: + __slots__ = ("func",) + def __init__(self, func): + self.func = func + def __call__(self, *args, **kwargs): + return self.func(*args, **kwargs) + + # Check that no error is thrown if the attribute + # is not writable. + @final + @Wrapper + def wrapped(): ... + self.assertIsInstance(wrapped, Wrapper) + self.assertIs(False, hasattr(wrapped, "__final__")) + + class Meta(type): + @property + def __final__(self): return "can't set me" + @final + class WithMeta(metaclass=Meta): ... + self.assertEqual(WithMeta.__final__, "can't set me") + + # Builtin classes throw TypeError if you try to set an + # attribute. + final(int) + self.assertIs(False, hasattr(int, "__final__")) + + # Make sure it works with common builtin decorators + class Methods: + @final + @classmethod + def clsmethod(cls): ... + + @final + @staticmethod + def stmethod(): ... + + # The other order doesn't work because property objects + # don't allow attribute assignment. + @property + @final + def prop(self): ... + + @final + @lru_cache() # noqa: B019 + def cached(self): ... + + # Use getattr_static because the descriptor returns the + # underlying function, which doesn't have __final__. + self.assertIs( + True, + inspect.getattr_static(Methods, "clsmethod").__final__ + ) + self.assertIs( + True, + inspect.getattr_static(Methods, "stmethod").__final__ + ) + self.assertIs(True, Methods.prop.fget.__final__) + self.assertIs(True, Methods.cached.__final__) + + +class RevealTypeTests(BaseTestCase): + def test_reveal_type(self): + obj = object() + + with contextlib.redirect_stderr(io.StringIO()) as stderr: + self.assertIs(obj, reveal_type(obj)) + self.assertEqual("Runtime type is 'object'", stderr.getvalue().strip()) + + +class DataclassTransformTests(BaseTestCase): + def test_decorator(self): + def create_model(*, frozen: bool = False, kw_only: bool = True): + return lambda cls: cls + + decorated = dataclass_transform(kw_only_default=True, order_default=False)(create_model) + + class CustomerModel: + id: int + + self.assertIs(decorated, create_model) + self.assertEqual( + decorated.__dataclass_transform__, + { + "eq_default": True, + "order_default": False, + "kw_only_default": True, + "frozen_default": False, + "field_specifiers": (), + "kwargs": {}, + } + ) + self.assertIs( + decorated(frozen=True, kw_only=False)(CustomerModel), + CustomerModel + ) + + def test_base_class(self): + class ModelBase: + def __init_subclass__(cls, *, frozen: bool = False): ... + + 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 + + self.assertIs(Decorated, ModelBase) + self.assertEqual( + Decorated.__dataclass_transform__, + { + "eq_default": True, + "order_default": True, + "kw_only_default": False, + "frozen_default": False, + "field_specifiers": (), + "kwargs": {"make_everything_awesome": True}, + } + ) + self.assertIsSubclass(CustomerModel, Decorated) + + def test_metaclass(self): + class Field: ... + + class ModelMeta(type): + def __new__( + cls, name, bases, namespace, *, init: bool = True, + ): + return super().__new__(cls, name, bases, namespace) + + Decorated = dataclass_transform( + order_default=True, field_specifiers=(Field,) + )(ModelMeta) + + class ModelBase(metaclass=Decorated): ... + + class CustomerModel(ModelBase, init=False): + id: int + + self.assertIs(Decorated, ModelMeta) + self.assertEqual( + Decorated.__dataclass_transform__, + { + "eq_default": True, + "order_default": True, + "kw_only_default": False, + "frozen_default": False, + "field_specifiers": (Field,), + "kwargs": {}, + } + ) + self.assertIsInstance(CustomerModel, Decorated) + + +class AllTests(BaseTestCase): + + def test_drop_in_for_typing(self): + # Check that the typing_extensions.__all__ is a superset of + # typing.__all__. + t_all = set(typing.__all__) + te_all = set(typing_extensions.__all__) + exceptions = {"ByteString"} + self.assertGreaterEqual(te_all, t_all - exceptions) + # Deprecated, to be removed in 3.14 + self.assertFalse(hasattr(typing_extensions, "ByteString")) + # These were never included in `typing.__all__`, + # and have been removed in Python 3.13 + self.assertNotIn('re', te_all) + self.assertNotIn('io', te_all) + + def test_typing_extensions_includes_standard(self): + a = typing_extensions.__all__ + self.assertIn('ClassVar', a) + self.assertIn('Type', a) + self.assertIn('ChainMap', a) + self.assertIn('ContextManager', a) + self.assertIn('Counter', a) + self.assertIn('DefaultDict', a) + self.assertIn('Deque', a) + self.assertIn('NewType', a) + self.assertIn('overload', a) + self.assertIn('Text', a) + self.assertIn('TYPE_CHECKING', a) + self.assertIn('TypeAlias', a) + self.assertIn('ParamSpec', a) + self.assertIn("Concatenate", a) + + self.assertIn('Annotated', a) + self.assertIn('get_type_hints', a) + + self.assertIn('Awaitable', a) + self.assertIn('AsyncIterator', a) + self.assertIn('AsyncIterable', a) + self.assertIn('Coroutine', a) + self.assertIn('AsyncContextManager', a) + + self.assertIn('AsyncGenerator', a) + + self.assertIn('Protocol', a) + self.assertIn('runtime', a) + + # Check that all objects in `__all__` are present in the module + 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 = { + 'dataclass_transform', + 'overload', + 'ParamSpec', + 'TypeVar', + 'TypeVarTuple', + 'get_type_hints', + } + if sys.version_info < (3, 10): + exclude |= {'get_args', 'get_origin'} + if sys.version_info < (3, 10, 1): + exclude |= {"Literal"} + if sys.version_info < (3, 11): + exclude |= {'final', 'Any', 'NewType'} + if sys.version_info < (3, 12): + exclude |= { + 'SupportsAbs', 'SupportsBytes', + 'SupportsComplex', 'SupportsFloat', 'SupportsIndex', 'SupportsInt', + 'SupportsRound', 'Unpack', + } + if sys.version_info < (3, 13): + exclude |= {'NamedTuple', 'Protocol'} + if not hasattr(typing, 'ReadOnly'): + exclude |= {'TypedDict', 'is_typeddict'} + for item in typing_extensions.__all__: + if item not in exclude and hasattr(typing, item): + self.assertIs( + getattr(typing_extensions, item), + getattr(typing, item)) + + def test_typing_extensions_compiles_with_opt(self): + file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), + 'typing_extensions.py') + try: + subprocess.check_output(f'{sys.executable} -OO {file_path}', + stderr=subprocess.STDOUT, + shell=True) + except subprocess.CalledProcessError: + self.fail('Module does not compile with optimize=2 (-OO flag).') + + +class CoolEmployee(NamedTuple): + name: str + cool: int + + +class CoolEmployeeWithDefault(NamedTuple): + name: str + cool: int = 0 + + +class XMeth(NamedTuple): + x: int + + def double(self): + return 2 * self.x + + +class XRepr(NamedTuple): + x: int + y: int = 1 + + def __str__(self): + return f'{self.x} -> {self.y}' + + def __add__(self, other): + return 0 + + +class NamedTupleTests(BaseTestCase): + class NestedEmployee(NamedTuple): + name: str + cool: int + + def test_basics(self): + Emp = NamedTuple('Emp', [('name', str), ('id', int)]) + self.assertIsSubclass(Emp, tuple) + joe = Emp('Joe', 42) + jim = Emp(name='Jim', id=1) + self.assertIsInstance(joe, Emp) + self.assertIsInstance(joe, tuple) + self.assertEqual(joe.name, 'Joe') + self.assertEqual(joe.id, 42) + self.assertEqual(jim.name, 'Jim') + self.assertEqual(jim.id, 1) + self.assertEqual(Emp.__name__, 'Emp') + self.assertEqual(Emp._fields, ('name', 'id')) + self.assertEqual(Emp.__annotations__, + collections.OrderedDict([('name', str), ('id', int)])) + + def test_annotation_usage(self): + tim = CoolEmployee('Tim', 9000) + self.assertIsInstance(tim, CoolEmployee) + self.assertIsInstance(tim, tuple) + self.assertEqual(tim.name, 'Tim') + self.assertEqual(tim.cool, 9000) + self.assertEqual(CoolEmployee.__name__, 'CoolEmployee') + self.assertEqual(CoolEmployee._fields, ('name', 'cool')) + self.assertEqual(CoolEmployee.__annotations__, + collections.OrderedDict(name=str, cool=int)) + + def test_annotation_usage_with_default(self): + jelle = CoolEmployeeWithDefault('Jelle') + self.assertIsInstance(jelle, CoolEmployeeWithDefault) + self.assertIsInstance(jelle, tuple) + self.assertEqual(jelle.name, 'Jelle') + self.assertEqual(jelle.cool, 0) + cooler_employee = CoolEmployeeWithDefault('Sjoerd', 1) + self.assertEqual(cooler_employee.cool, 1) + + self.assertEqual(CoolEmployeeWithDefault.__name__, 'CoolEmployeeWithDefault') + self.assertEqual(CoolEmployeeWithDefault._fields, ('name', 'cool')) + self.assertEqual(CoolEmployeeWithDefault.__annotations__, + dict(name=str, cool=int)) + + with self.assertRaisesRegex( + TypeError, + 'Non-default namedtuple field y cannot follow default field x' + ): + class NonDefaultAfterDefault(NamedTuple): + x: int = 3 + y: int + + def test_field_defaults(self): + self.assertEqual(CoolEmployeeWithDefault._field_defaults, dict(cool=0)) + + def test_annotation_usage_with_methods(self): + self.assertEqual(XMeth(1).double(), 2) + self.assertEqual(XMeth(42).x, XMeth(42)[0]) + self.assertEqual(str(XRepr(42)), '42 -> 1') + self.assertEqual(XRepr(1, 2) + XRepr(3), 0) + + bad_overwrite_error_message = 'Cannot overwrite NamedTuple attribute' + + with self.assertRaisesRegex(AttributeError, bad_overwrite_error_message): + class XMethBad(NamedTuple): + x: int + def _fields(self): + return 'no chance for this' + + with self.assertRaisesRegex(AttributeError, bad_overwrite_error_message): + class XMethBad2(NamedTuple): + x: int + def _source(self): + return 'no chance for this as well' + + def test_multiple_inheritance(self): + class A: + pass + with self.assertRaisesRegex( + TypeError, + 'can only inherit from a NamedTuple type and Generic' + ): + class X(NamedTuple, A): + x: int + + with self.assertRaisesRegex( + TypeError, + 'can only inherit from a NamedTuple type and Generic' + ): + class X(NamedTuple, tuple): + x: int + + with self.assertRaisesRegex(TypeError, 'duplicate base class'): + class X(NamedTuple, NamedTuple): + x: int + + class A(NamedTuple): + x: int + with self.assertRaisesRegex( + TypeError, + 'can only inherit from a NamedTuple type and Generic' + ): + class X(NamedTuple, A): + y: str + + def test_generic(self): + class X(NamedTuple, Generic[T]): + x: T + self.assertEqual(X.__bases__, (tuple, Generic)) + self.assertEqual(X.__orig_bases__, (NamedTuple, Generic[T])) + self.assertEqual(X.__mro__, (X, tuple, Generic, object)) + + class Y(Generic[T], NamedTuple): + x: T + self.assertEqual(Y.__bases__, (Generic, tuple)) + self.assertEqual(Y.__orig_bases__, (Generic[T], NamedTuple)) + self.assertEqual(Y.__mro__, (Y, Generic, tuple, object)) + + for G in X, Y: + with self.subTest(type=G): + self.assertEqual(G.__parameters__, (T,)) + A = G[int] + self.assertIs(A.__origin__, G) + self.assertEqual(A.__args__, (int,)) + self.assertEqual(A.__parameters__, ()) + + a = A(3) + self.assertIs(type(a), G) + self.assertIsInstance(a, G) + self.assertEqual(a.x, 3) + + things = "arguments" if sys.version_info >= (3, 11) else "parameters" + + with self.assertRaisesRegex(TypeError, f'Too many {things}'): + G[int, str] + + @skipUnless(TYPING_3_9_0, "tuple.__class_getitem__ was added in 3.9") + def test_non_generic_subscript_py39_plus(self): + # For backward compatibility, subscription works + # on arbitrary NamedTuple types. + class Group(NamedTuple): + key: T + group: list[T] + A = Group[int] + self.assertEqual(A.__origin__, Group) + self.assertEqual(A.__parameters__, ()) + self.assertEqual(A.__args__, (int,)) + a = A(1, [2]) + self.assertIs(type(a), Group) + self.assertEqual(a, (1, [2])) + + @skipIf(TYPING_3_9_0, "Test isn't relevant to 3.9+") + def test_non_generic_subscript_error_message_py38(self): + class Group(NamedTuple): + key: T + group: List[T] + + with self.assertRaisesRegex(TypeError, 'not subscriptable'): + Group[int] + + for attr in ('__args__', '__origin__', '__parameters__'): + with self.subTest(attr=attr): + self.assertFalse(hasattr(Group, attr)) + + def test_namedtuple_keyword_usage(self): + with self.assertWarnsRegex( + DeprecationWarning, + "Creating NamedTuple classes using keyword arguments is deprecated" + ): + LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int) + + nick = LocalEmployee('Nick', 25) + self.assertIsInstance(nick, tuple) + self.assertEqual(nick.name, 'Nick') + self.assertEqual(LocalEmployee.__name__, 'LocalEmployee') + self.assertEqual(LocalEmployee._fields, ('name', 'age')) + self.assertEqual(LocalEmployee.__annotations__, dict(name=str, age=int)) + + with self.assertRaisesRegex( + TypeError, + "Either list of fields or keywords can be provided to NamedTuple, not both" + ): + NamedTuple('Name', [('x', int)], y=str) + + with self.assertRaisesRegex( + TypeError, + "Either list of fields or keywords can be provided to NamedTuple, not both" + ): + NamedTuple('Name', [], y=str) + + with self.assertRaisesRegex( + TypeError, + ( + r"Cannot pass `None` as the 'fields' parameter " + r"and also specify fields using keyword arguments" + ) + ): + NamedTuple('Name', None, x=int) + + def test_namedtuple_special_keyword_names(self): + with self.assertWarnsRegex( + DeprecationWarning, + "Creating NamedTuple classes using keyword arguments is deprecated" + ): + NT = NamedTuple("NT", cls=type, self=object, typename=str, fields=list) + + self.assertEqual(NT.__name__, 'NT') + self.assertEqual(NT._fields, ('cls', 'self', 'typename', 'fields')) + a = NT(cls=str, self=42, typename='foo', fields=[('bar', tuple)]) + self.assertEqual(a.cls, str) + self.assertEqual(a.self, 42) + self.assertEqual(a.typename, 'foo') + self.assertEqual(a.fields, [('bar', tuple)]) + + def test_empty_namedtuple(self): + expected_warning = re.escape( + "Failing to pass a value for the 'fields' parameter is deprecated " + "and will be disallowed in Python 3.15. " + "To create a NamedTuple class with 0 fields " + "using the functional syntax, " + "pass an empty list, e.g. `NT1 = NamedTuple('NT1', [])`." + ) + with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"): + NT1 = NamedTuple('NT1') + + expected_warning = re.escape( + "Passing `None` as the 'fields' parameter is deprecated " + "and will be disallowed in Python 3.15. " + "To create a NamedTuple class with 0 fields " + "using the functional syntax, " + "pass an empty list, e.g. `NT2 = NamedTuple('NT2', [])`." + ) + with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"): + NT2 = NamedTuple('NT2', None) + + NT3 = NamedTuple('NT2', []) + + class CNT(NamedTuple): + pass # empty body + + for struct in NT1, NT2, NT3, CNT: + with self.subTest(struct=struct): + self.assertEqual(struct._fields, ()) + self.assertEqual(struct.__annotations__, {}) + self.assertIsInstance(struct(), struct) + self.assertEqual(struct._field_defaults, {}) + + def test_namedtuple_errors(self): + with self.assertRaises(TypeError): + NamedTuple.__new__() + with self.assertRaises(TypeError): + NamedTuple() + with self.assertRaises(TypeError): + NamedTuple('Emp', [('name', str)], None) + with self.assertRaisesRegex(ValueError, 'cannot start with an underscore'): + NamedTuple('Emp', [('_name', str)]) + with self.assertRaises(TypeError): + NamedTuple(typename='Emp', name=str, id=int) + + def test_copy_and_pickle(self): + global Emp # pickle wants to reference the class by name + Emp = NamedTuple('Emp', [('name', str), ('cool', int)]) + for cls in Emp, CoolEmployee, self.NestedEmployee: + with self.subTest(cls=cls): + jane = cls('jane', 37) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + z = pickle.dumps(jane, proto) + jane2 = pickle.loads(z) + self.assertEqual(jane2, jane) + self.assertIsInstance(jane2, cls) + + jane2 = copy.copy(jane) + self.assertEqual(jane2, jane) + self.assertIsInstance(jane2, cls) + + jane2 = copy.deepcopy(jane) + self.assertEqual(jane2, jane) + self.assertIsInstance(jane2, cls) + + def test_docstring(self): + self.assertIsInstance(NamedTuple.__doc__, str) + + @skipUnless(TYPING_3_9_0, "NamedTuple was a class on 3.8 and lower") + def test_same_as_typing_NamedTuple_39_plus(self): + self.assertEqual( + set(dir(NamedTuple)) - {"__text_signature__"}, + set(dir(typing.NamedTuple)) + ) + self.assertIs(type(NamedTuple), type(typing.NamedTuple)) + + @skipIf(TYPING_3_9_0, "tests are only relevant to <=3.8") + def test_same_as_typing_NamedTuple_38_minus(self): + self.assertEqual( + self.NestedEmployee.__annotations__, + self.NestedEmployee._field_types + ) + + def test_orig_bases(self): + T = TypeVar('T') + + class SimpleNamedTuple(NamedTuple): + pass + + class GenericNamedTuple(NamedTuple, Generic[T]): + pass + + self.assertEqual(SimpleNamedTuple.__orig_bases__, (NamedTuple,)) + self.assertEqual(GenericNamedTuple.__orig_bases__, (NamedTuple, Generic[T])) + + CallNamedTuple = NamedTuple('CallNamedTuple', []) + + self.assertEqual(CallNamedTuple.__orig_bases__, (NamedTuple,)) + + @skip_if_early_py313_alpha + def test_setname_called_on_values_in_class_dictionary(self): + class Vanilla: + def __set_name__(self, owner, name): + self.name = name + + class Foo(NamedTuple): + attr = Vanilla() + + foo = Foo() + self.assertEqual(len(foo), 0) + self.assertNotIn('attr', Foo._fields) + self.assertIsInstance(foo.attr, Vanilla) + self.assertEqual(foo.attr.name, "attr") + + class Bar(NamedTuple): + attr: Vanilla = Vanilla() + + bar = Bar() + self.assertEqual(len(bar), 1) + self.assertIn('attr', Bar._fields) + self.assertIsInstance(bar.attr, Vanilla) + self.assertEqual(bar.attr.name, "attr") + + @skipIf( + TYPING_3_12_0, + "__set_name__ behaviour changed on py312+ to use BaseException.add_note()" + ) + def test_setname_raises_the_same_as_on_other_classes_py311_minus(self): + class CustomException(BaseException): pass + + class Annoying: + def __set_name__(self, owner, name): + raise CustomException + + annoying = Annoying() + + with self.assertRaises(RuntimeError) as cm: + class NormalClass: + attr = annoying + normal_exception = cm.exception + + with self.assertRaises(RuntimeError) as cm: + class NamedTupleClass(NamedTuple): + attr = annoying + namedtuple_exception = cm.exception + + expected_note = ( + "Error calling __set_name__ on 'Annoying' instance " + "'attr' in 'NamedTupleClass'" + ) + + self.assertIs(type(namedtuple_exception), RuntimeError) + self.assertIs(type(namedtuple_exception), type(normal_exception)) + self.assertEqual(len(namedtuple_exception.args), len(normal_exception.args)) + self.assertEqual( + namedtuple_exception.args[0], + normal_exception.args[0].replace("NormalClass", "NamedTupleClass") + ) + + self.assertIs(type(namedtuple_exception.__cause__), CustomException) + self.assertIs( + type(namedtuple_exception.__cause__), type(normal_exception.__cause__) + ) + self.assertEqual( + namedtuple_exception.__cause__.args, normal_exception.__cause__.args + ) + + @skipUnless( + TYPING_3_12_0, + "__set_name__ behaviour changed on py312+ to use BaseException.add_note()" + ) + @skip_if_early_py313_alpha + def test_setname_raises_the_same_as_on_other_classes_py312_plus(self): + class CustomException(BaseException): pass + + class Annoying: + def __set_name__(self, owner, name): + raise CustomException + + annoying = Annoying() + + with self.assertRaises(CustomException) as cm: + class NormalClass: + attr = annoying + normal_exception = cm.exception + + with self.assertRaises(CustomException) as cm: + class NamedTupleClass(NamedTuple): + attr = annoying + namedtuple_exception = cm.exception + + expected_note = ( + "Error calling __set_name__ on 'Annoying' instance " + "'attr' in 'NamedTupleClass'" + ) + + self.assertIs(type(namedtuple_exception), CustomException) + self.assertIs(type(namedtuple_exception), type(normal_exception)) + self.assertEqual(namedtuple_exception.args, normal_exception.args) + + self.assertEqual(len(namedtuple_exception.__notes__), 1) + self.assertEqual( + len(namedtuple_exception.__notes__), len(normal_exception.__notes__) + ) + + self.assertEqual(namedtuple_exception.__notes__[0], expected_note) + self.assertEqual( + namedtuple_exception.__notes__[0], + normal_exception.__notes__[0].replace("NormalClass", "NamedTupleClass") + ) + + @skip_if_early_py313_alpha + def test_strange_errors_when_accessing_set_name_itself(self): + class CustomException(Exception): pass + + class Meta(type): + def __getattribute__(self, attr): + if attr == "__set_name__": + raise CustomException + return object.__getattribute__(self, attr) + + class VeryAnnoying(metaclass=Meta): pass + + very_annoying = VeryAnnoying() + + with self.assertRaises(CustomException): + class Foo(NamedTuple): + attr = very_annoying + + +class TypeVarTests(BaseTestCase): + def test_basic_plain(self): + T = TypeVar('T') + # T equals itself. + self.assertEqual(T, T) + # T is an instance of TypeVar + self.assertIsInstance(T, TypeVar) + self.assertEqual(T.__name__, 'T') + self.assertEqual(T.__constraints__, ()) + self.assertIs(T.__bound__, None) + self.assertIs(T.__covariant__, False) + self.assertIs(T.__contravariant__, False) + self.assertIs(T.__infer_variance__, False) + + def test_attributes(self): + T_bound = TypeVar('T_bound', bound=int) + self.assertEqual(T_bound.__name__, 'T_bound') + self.assertEqual(T_bound.__constraints__, ()) + self.assertIs(T_bound.__bound__, int) + + T_constraints = TypeVar('T_constraints', int, str) + self.assertEqual(T_constraints.__name__, 'T_constraints') + self.assertEqual(T_constraints.__constraints__, (int, str)) + self.assertIs(T_constraints.__bound__, None) + + T_co = TypeVar('T_co', covariant=True) + self.assertEqual(T_co.__name__, 'T_co') + self.assertIs(T_co.__covariant__, True) + self.assertIs(T_co.__contravariant__, False) + self.assertIs(T_co.__infer_variance__, False) + + T_contra = TypeVar('T_contra', contravariant=True) + self.assertEqual(T_contra.__name__, 'T_contra') + self.assertIs(T_contra.__covariant__, False) + self.assertIs(T_contra.__contravariant__, True) + self.assertIs(T_contra.__infer_variance__, False) + + T_infer = TypeVar('T_infer', infer_variance=True) + self.assertEqual(T_infer.__name__, 'T_infer') + self.assertIs(T_infer.__covariant__, False) + self.assertIs(T_infer.__contravariant__, False) + self.assertIs(T_infer.__infer_variance__, True) + + def test_typevar_instance_type_error(self): + T = TypeVar('T') + with self.assertRaises(TypeError): + isinstance(42, T) + + def test_typevar_subclass_type_error(self): + T = TypeVar('T') + with self.assertRaises(TypeError): + issubclass(int, T) + with self.assertRaises(TypeError): + issubclass(T, int) + + def test_constrained_error(self): + with self.assertRaises(TypeError): + X = TypeVar('X', int) + X + + def test_union_unique(self): + X = TypeVar('X') + Y = TypeVar('Y') + self.assertNotEqual(X, Y) + self.assertEqual(Union[X], X) + self.assertNotEqual(Union[X], Union[X, Y]) + self.assertEqual(Union[X, X], X) + self.assertNotEqual(Union[X, int], Union[X]) + self.assertNotEqual(Union[X, int], Union[int]) + self.assertEqual(Union[X, int].__args__, (X, int)) + self.assertEqual(Union[X, int].__parameters__, (X,)) + self.assertIs(Union[X, int].__origin__, Union) + + if hasattr(types, "UnionType"): + def test_or(self): + X = TypeVar('X') + # use a string because str doesn't implement + # __or__/__ror__ itself + self.assertEqual(X | "x", Union[X, "x"]) + self.assertEqual("x" | X, Union["x", X]) + # make sure the order is correct + self.assertEqual(get_args(X | "x"), (X, typing.ForwardRef("x"))) + self.assertEqual(get_args("x" | X), (typing.ForwardRef("x"), X)) + + def test_union_constrained(self): + A = TypeVar('A', str, bytes) + self.assertNotEqual(Union[A, str], Union[A]) + + def test_repr(self): + self.assertEqual(repr(T), '~T') + self.assertEqual(repr(KT), '~KT') + self.assertEqual(repr(VT), '~VT') + self.assertEqual(repr(AnyStr), '~AnyStr') + T_co = TypeVar('T_co', covariant=True) + self.assertEqual(repr(T_co), '+T_co') + T_contra = TypeVar('T_contra', contravariant=True) + self.assertEqual(repr(T_contra), '-T_contra') + + def test_no_redefinition(self): + self.assertNotEqual(TypeVar('T'), TypeVar('T')) + self.assertNotEqual(TypeVar('T', int, str), TypeVar('T', int, str)) + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class V(TypeVar): pass + T = TypeVar("T") + with self.assertRaises(TypeError): + class V(T): pass + + def test_cannot_instantiate_vars(self): + with self.assertRaises(TypeError): + TypeVar('A')() + + def test_bound_errors(self): + with self.assertRaises(TypeError): + TypeVar('X', bound=Union) + with self.assertRaises(TypeError): + TypeVar('X', str, float, bound=Employee) + with self.assertRaisesRegex(TypeError, + r"Bound must be a type\. Got \(1, 2\)\."): + TypeVar('X', bound=(1, 2)) + + # Technically we could run it on later versions of 3.8, + # but that's not worth the effort. + @skipUnless(TYPING_3_9_0, "Fix was not backported") + def test_missing__name__(self): + # See bpo-39942 + code = ("import typing\n" + "T = typing.TypeVar('T')\n" + ) + exec(code, {}) + + def test_no_bivariant(self): + with self.assertRaises(ValueError): + TypeVar('T', covariant=True, contravariant=True) + + def test_cannot_combine_explicit_and_infer(self): + with self.assertRaises(ValueError): + TypeVar('T', covariant=True, infer_variance=True) + with self.assertRaises(ValueError): + TypeVar('T', contravariant=True, infer_variance=True) + + +class TypeVarLikeDefaultsTests(BaseTestCase): + def test_typevar(self): + T = typing_extensions.TypeVar('T', default=int) + typing_T = typing.TypeVar('T') + self.assertEqual(T.__default__, int) + self.assertIsInstance(T, typing_extensions.TypeVar) + self.assertIsInstance(T, typing.TypeVar) + self.assertIsInstance(typing_T, typing.TypeVar) + self.assertIsInstance(typing_T, typing_extensions.TypeVar) + + class A(Generic[T]): ... + Alias = Optional[T] + + def test_typevar_none(self): + U = typing_extensions.TypeVar('U') + U_None = typing_extensions.TypeVar('U_None', default=None) + self.assertEqual(U.__default__, None) + self.assertEqual(U_None.__default__, type(None)) + + def test_paramspec(self): + P = ParamSpec('P', default=(str, int)) + self.assertEqual(P.__default__, (str, int)) + self.assertIsInstance(P, ParamSpec) + if hasattr(typing, "ParamSpec"): + self.assertIsInstance(P, typing.ParamSpec) + typing_P = typing.ParamSpec('P') + self.assertIsInstance(typing_P, typing.ParamSpec) + self.assertIsInstance(typing_P, ParamSpec) + + class A(Generic[P]): ... + Alias = typing.Callable[P, None] + + P_default = ParamSpec('P_default', default=...) + self.assertIs(P_default.__default__, ...) + + def test_typevartuple(self): + Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]]) + self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]]) + self.assertIsInstance(Ts, TypeVarTuple) + if hasattr(typing, "TypeVarTuple"): + self.assertIsInstance(Ts, typing.TypeVarTuple) + typing_Ts = typing.TypeVarTuple('Ts') + self.assertIsInstance(typing_Ts, typing.TypeVarTuple) + self.assertIsInstance(typing_Ts, TypeVarTuple) + + class A(Generic[Unpack[Ts]]): ... + Alias = Optional[Unpack[Ts]] + + def test_pickle(self): + global U, U_co, U_contra, U_default # pickle wants to reference the class by name + U = typing_extensions.TypeVar('U') + U_co = typing_extensions.TypeVar('U_co', covariant=True) + U_contra = typing_extensions.TypeVar('U_contra', contravariant=True) + U_default = typing_extensions.TypeVar('U_default', default=int) + for proto in range(pickle.HIGHEST_PROTOCOL): + for typevar in (U, U_co, U_contra, U_default): + z = pickle.loads(pickle.dumps(typevar, proto)) + self.assertEqual(z.__name__, typevar.__name__) + self.assertEqual(z.__covariant__, typevar.__covariant__) + self.assertEqual(z.__contravariant__, typevar.__contravariant__) + self.assertEqual(z.__bound__, typevar.__bound__) + self.assertEqual(z.__default__, typevar.__default__) + + +class TypeVarInferVarianceTests(BaseTestCase): + def test_typevar(self): + T = typing_extensions.TypeVar('T') + self.assertFalse(T.__infer_variance__) + T_infer = typing_extensions.TypeVar('T_infer', infer_variance=True) + self.assertTrue(T_infer.__infer_variance__) + T_noinfer = typing_extensions.TypeVar('T_noinfer', infer_variance=False) + self.assertFalse(T_noinfer.__infer_variance__) + + def test_pickle(self): + global U, U_infer # pickle wants to reference the class by name + U = typing_extensions.TypeVar('U') + U_infer = typing_extensions.TypeVar('U_infer', infer_variance=True) + for proto in range(pickle.HIGHEST_PROTOCOL): + for typevar in (U, U_infer): + z = pickle.loads(pickle.dumps(typevar, proto)) + self.assertEqual(z.__name__, typevar.__name__) + self.assertEqual(z.__covariant__, typevar.__covariant__) + self.assertEqual(z.__contravariant__, typevar.__contravariant__) + self.assertEqual(z.__bound__, typevar.__bound__) + self.assertEqual(z.__infer_variance__, typevar.__infer_variance__) + + +class BufferTests(BaseTestCase): + def test(self): + self.assertIsInstance(memoryview(b''), Buffer) + self.assertIsInstance(bytearray(), Buffer) + self.assertIsInstance(b"x", Buffer) + self.assertNotIsInstance(1, Buffer) + + self.assertIsSubclass(bytearray, Buffer) + self.assertIsSubclass(memoryview, Buffer) + self.assertIsSubclass(bytes, Buffer) + self.assertNotIsSubclass(int, Buffer) + + class MyRegisteredBuffer: + def __buffer__(self, flags: int) -> memoryview: + return memoryview(b'') + + # On 3.12, collections.abc.Buffer does a structural compatibility check + if TYPING_3_12_0: + self.assertIsInstance(MyRegisteredBuffer(), Buffer) + self.assertIsSubclass(MyRegisteredBuffer, Buffer) + else: + self.assertNotIsInstance(MyRegisteredBuffer(), Buffer) + self.assertNotIsSubclass(MyRegisteredBuffer, Buffer) + Buffer.register(MyRegisteredBuffer) + self.assertIsInstance(MyRegisteredBuffer(), Buffer) + self.assertIsSubclass(MyRegisteredBuffer, Buffer) + + class MySubclassedBuffer(Buffer): + def __buffer__(self, flags: int) -> memoryview: + return memoryview(b'') + + self.assertIsInstance(MySubclassedBuffer(), Buffer) + self.assertIsSubclass(MySubclassedBuffer, Buffer) + + +class GetOriginalBasesTests(BaseTestCase): + def test_basics(self): + T = TypeVar('T') + class A: pass + class B(Generic[T]): pass + class C(B[int]): pass + class D(B[str], float): pass + self.assertEqual(get_original_bases(A), (object,)) + self.assertEqual(get_original_bases(B), (Generic[T],)) + self.assertEqual(get_original_bases(C), (B[int],)) + self.assertEqual(get_original_bases(int), (object,)) + self.assertEqual(get_original_bases(D), (B[str], float)) + + with self.assertRaisesRegex(TypeError, "Expected an instance of type"): + get_original_bases(object()) + + @skipUnless(TYPING_3_9_0, "PEP 585 is yet to be") + def test_builtin_generics(self): + class E(list[T]): pass + class F(list[int]): pass + + self.assertEqual(get_original_bases(E), (list[T],)) + self.assertEqual(get_original_bases(F), (list[int],)) + + @skipIf( + sys.version_info[:3] == (3, 12, 0) and sys.version_info[3] in {"alpha", "beta"}, + "Early versions of py312 had a bug" + ) + def test_concrete_subclasses_of_generic_classes(self): + T = TypeVar("T") + + class FirstBase(Generic[T]): pass + class SecondBase(Generic[T]): pass + class First(FirstBase[int]): pass + class Second(SecondBase[int]): pass + class G(First, Second): pass + self.assertEqual(get_original_bases(G), (First, Second)) + + class First_(Generic[T]): pass + class Second_(Generic[T]): pass + class H(First_, Second_): pass + self.assertEqual(get_original_bases(H), (First_, Second_)) + + def test_namedtuples(self): + # On 3.12, this should work well with typing.NamedTuple and typing_extensions.NamedTuple + # On lower versions, it will only work fully with typing_extensions.NamedTuple + if sys.version_info >= (3, 12): + namedtuple_classes = (typing.NamedTuple, typing_extensions.NamedTuple) + else: + namedtuple_classes = (typing_extensions.NamedTuple,) + + for NamedTuple in namedtuple_classes: # noqa: F402 + with self.subTest(cls=NamedTuple): + class ClassBasedNamedTuple(NamedTuple): + x: int + + class GenericNamedTuple(NamedTuple, Generic[T]): + x: T + + CallBasedNamedTuple = NamedTuple("CallBasedNamedTuple", [("x", int)]) + + self.assertIs( + get_original_bases(ClassBasedNamedTuple)[0], NamedTuple + ) + self.assertEqual( + get_original_bases(GenericNamedTuple), + (NamedTuple, Generic[T]) + ) + self.assertIs( + get_original_bases(CallBasedNamedTuple)[0], NamedTuple + ) + + def test_typeddicts(self): + # On 3.12, this should work well with typing.TypedDict and typing_extensions.TypedDict + # On lower versions, it will only work fully with typing_extensions.TypedDict + if sys.version_info >= (3, 12): + typeddict_classes = (typing.TypedDict, typing_extensions.TypedDict) + else: + typeddict_classes = (typing_extensions.TypedDict,) + + for TypedDict in typeddict_classes: # noqa: F402 + with self.subTest(cls=TypedDict): + class ClassBasedTypedDict(TypedDict): + x: int + + class GenericTypedDict(TypedDict, Generic[T]): + x: T + + CallBasedTypedDict = TypedDict("CallBasedTypedDict", {"x": int}) + + self.assertIs( + get_original_bases(ClassBasedTypedDict)[0], + TypedDict + ) + self.assertEqual( + get_original_bases(GenericTypedDict), + (TypedDict, Generic[T]) + ) + self.assertIs( + get_original_bases(CallBasedTypedDict)[0], + TypedDict + ) + + +class TypeAliasTypeTests(BaseTestCase): + def test_attributes(self): + Simple = TypeAliasType("Simple", int) + self.assertEqual(Simple.__name__, "Simple") + self.assertIs(Simple.__value__, int) + self.assertEqual(Simple.__type_params__, ()) + self.assertEqual(Simple.__parameters__, ()) + + T = TypeVar("T") + ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) + self.assertEqual(ListOrSetT.__name__, "ListOrSetT") + self.assertEqual(ListOrSetT.__value__, Union[List[T], Set[T]]) + self.assertEqual(ListOrSetT.__type_params__, (T,)) + self.assertEqual(ListOrSetT.__parameters__, (T,)) + + Ts = TypeVarTuple("Ts") + Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) + self.assertEqual(Variadic.__name__, "Variadic") + self.assertEqual(Variadic.__value__, Tuple[int, Unpack[Ts]]) + self.assertEqual(Variadic.__type_params__, (Ts,)) + self.assertEqual(Variadic.__parameters__, tuple(iter(Ts))) + + def test_cannot_set_attributes(self): + Simple = TypeAliasType("Simple", int) + with self.assertRaisesRegex(AttributeError, "readonly attribute"): + Simple.__name__ = "NewName" + with self.assertRaisesRegex( + AttributeError, + "attribute '__value__' of 'typing.TypeAliasType' objects is not writable", + ): + Simple.__value__ = str + with self.assertRaisesRegex( + AttributeError, + "attribute '__type_params__' of 'typing.TypeAliasType' objects is not writable", + ): + Simple.__type_params__ = (T,) + with self.assertRaisesRegex( + AttributeError, + "attribute '__parameters__' of 'typing.TypeAliasType' objects is not writable", + ): + Simple.__parameters__ = (T,) + with self.assertRaisesRegex( + AttributeError, + "attribute '__module__' of 'typing.TypeAliasType' objects is not writable", + ): + Simple.__module__ = 42 + with self.assertRaisesRegex( + AttributeError, + "'typing.TypeAliasType' object has no attribute 'some_attribute'", + ): + Simple.some_attribute = "not allowed" + + def test_cannot_delete_attributes(self): + Simple = TypeAliasType("Simple", int) + with self.assertRaisesRegex(AttributeError, "readonly attribute"): + del Simple.__name__ + with self.assertRaisesRegex( + AttributeError, + "attribute '__value__' of 'typing.TypeAliasType' objects is not writable", + ): + del Simple.__value__ + with self.assertRaisesRegex( + AttributeError, + "'typing.TypeAliasType' object has no attribute 'some_attribute'", + ): + del Simple.some_attribute + + def test_or(self): + Alias = TypeAliasType("Alias", int) + if sys.version_info >= (3, 10): + self.assertEqual(Alias | int, Union[Alias, int]) + self.assertEqual(Alias | None, Union[Alias, None]) + self.assertEqual(Alias | (int | str), Union[Alias, int | str]) + self.assertEqual(Alias | list[float], Union[Alias, list[float]]) + else: + with self.assertRaises(TypeError): + Alias | int + # Rejected on all versions + with self.assertRaises(TypeError): + Alias | "Ref" + + def test_getitem(self): + ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) + subscripted = ListOrSetT[int] + self.assertEqual(get_args(subscripted), (int,)) + self.assertIs(get_origin(subscripted), ListOrSetT) + with self.assertRaises(TypeError): + subscripted[str] + + still_generic = ListOrSetT[Iterable[T]] + self.assertEqual(get_args(still_generic), (Iterable[T],)) + self.assertIs(get_origin(still_generic), ListOrSetT) + fully_subscripted = still_generic[float] + self.assertEqual(get_args(fully_subscripted), (Iterable[float],)) + self.assertIs(get_origin(fully_subscripted), ListOrSetT) + + def test_pickle(self): + global Alias + Alias = TypeAliasType("Alias", int) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(proto=proto): + pickled = pickle.dumps(Alias, proto) + unpickled = pickle.loads(pickled) + self.assertIs(unpickled, Alias) + + def test_no_instance_subclassing(self): + with self.assertRaises(TypeError): + class MyAlias(TypeAliasType): + pass + + +class DocTests(BaseTestCase): + def test_annotation(self): + + def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: pass + + hints = get_type_hints(hi, include_extras=True) + doc_info = hints["to"].__metadata__[0] + self.assertEqual(doc_info.documentation, "Who to say hi to") + self.assertIsInstance(doc_info, Doc) + + def test_repr(self): + doc_info = Doc("Who to say hi to") + self.assertEqual(repr(doc_info), "Doc('Who to say hi to')") + + def test_hashability(self): + doc_info = Doc("Who to say hi to") + self.assertIsInstance(hash(doc_info), int) + self.assertNotEqual(hash(doc_info), hash(Doc("Who not to say hi to"))) + + def test_equality(self): + doc_info = Doc("Who to say hi to") + # Equal to itself + self.assertEqual(doc_info, doc_info) + # Equal to another instance with the same string + self.assertEqual(doc_info, Doc("Who to say hi to")) + # Not equal to another instance with a different string + self.assertNotEqual(doc_info, Doc("Who not to say hi to")) + + def test_pickle(self): + doc_info = Doc("Who to say hi to") + for proto in range(pickle.HIGHEST_PROTOCOL): + pickled = pickle.dumps(doc_info, protocol=proto) + self.assertEqual(doc_info, pickle.loads(pickled)) + + +if __name__ == '__main__': + main() diff --git a/src/typing_extensions.py b/src/typing_extensions.py new file mode 100644 index 0000000..1666e96 --- /dev/null +++ b/src/typing_extensions.py @@ -0,0 +1,3053 @@ +import abc +import collections +import collections.abc +import functools +import inspect +import operator +import sys +import types as _types +import typing +import warnings + +__all__ = [ + # Super-special typing primitives. + 'Any', + 'ClassVar', + 'Concatenate', + 'Final', + 'LiteralString', + 'ParamSpec', + 'ParamSpecArgs', + 'ParamSpecKwargs', + 'Self', + 'Type', + 'TypeVar', + 'TypeVarTuple', + 'Unpack', + + # ABCs (from collections.abc). + 'Awaitable', + 'AsyncIterator', + 'AsyncIterable', + 'Coroutine', + 'AsyncGenerator', + 'AsyncContextManager', + 'Buffer', + 'ChainMap', + + # Concrete collection types. + 'ContextManager', + 'Counter', + 'Deque', + 'DefaultDict', + 'NamedTuple', + 'OrderedDict', + 'TypedDict', + + # Structural checks, a.k.a. protocols. + 'SupportsAbs', + 'SupportsBytes', + 'SupportsComplex', + 'SupportsFloat', + 'SupportsIndex', + 'SupportsInt', + 'SupportsRound', + + # One-off things. + 'Annotated', + 'assert_never', + 'assert_type', + 'clear_overloads', + 'dataclass_transform', + 'deprecated', + 'Doc', + 'get_overloads', + 'final', + 'get_args', + 'get_origin', + 'get_original_bases', + 'get_protocol_members', + 'get_type_hints', + 'IntVar', + 'is_protocol', + 'is_typeddict', + 'Literal', + 'NewType', + 'overload', + 'override', + 'Protocol', + 'reveal_type', + 'runtime', + 'runtime_checkable', + 'Text', + 'TypeAlias', + 'TypeAliasType', + 'TypeGuard', + 'TYPE_CHECKING', + 'Never', + 'NoReturn', + 'ReadOnly', + 'Required', + 'NotRequired', + + # Pure aliases, have always been in typing + 'AbstractSet', + 'AnyStr', + 'BinaryIO', + 'Callable', + 'Collection', + 'Container', + 'Dict', + 'ForwardRef', + 'FrozenSet', + 'Generator', + 'Generic', + 'Hashable', + 'IO', + 'ItemsView', + 'Iterable', + 'Iterator', + 'KeysView', + 'List', + 'Mapping', + 'MappingView', + 'Match', + 'MutableMapping', + 'MutableSequence', + 'MutableSet', + 'Optional', + 'Pattern', + 'Reversible', + 'Sequence', + 'Set', + 'Sized', + 'TextIO', + 'Tuple', + 'Union', + 'ValuesView', + 'cast', + 'no_type_check', + 'no_type_check_decorator', +] + +# 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. + + +class _Sentinel: + def __repr__(self): + return "" + + +_marker = _Sentinel() + + +def _check_generic(cls, parameters, elen=_marker): + """Check correct count for parameters of a generic cls (internal helper). + This gives a nice error message in case of count mismatch. + """ + if not elen: + raise TypeError(f"{cls} is not a generic class") + if elen is _marker: + if not hasattr(cls, "__parameters__") or not cls.__parameters__: + raise TypeError(f"{cls} is not a generic class") + elen = len(cls.__parameters__) + alen = len(parameters) + if alen != elen: + if hasattr(cls, "__parameters__"): + parameters = [p for p in cls.__parameters__ if not _is_unpack(p)] + num_tv_tuples = sum(isinstance(p, TypeVarTuple) for p in parameters) + if (num_tv_tuples > 0) and (alen >= elen - num_tv_tuples): + return + raise TypeError(f"Too {'many' if alen > elen else 'few'} parameters for {cls};" + f" actual {alen}, expected {elen}") + + +if sys.version_info >= (3, 10): + def _should_collect_from_parameters(t): + return isinstance( + t, (typing._GenericAlias, _types.GenericAlias, _types.UnionType) + ) +elif sys.version_info >= (3, 9): + def _should_collect_from_parameters(t): + return isinstance(t, (typing._GenericAlias, _types.GenericAlias)) +else: + def _should_collect_from_parameters(t): + return isinstance(t, typing._GenericAlias) and not t._special + + +def _collect_type_vars(types, typevar_types=None): + """Collect all type variable contained in types in order of + first appearance (lexicographic order). For example:: + + _collect_type_vars((T, List[S, T])) == (T, S) + """ + if typevar_types is None: + typevar_types = typing.TypeVar + tvars = [] + for t in types: + if ( + isinstance(t, typevar_types) and + t not in tvars and + not _is_unpack(t) + ): + tvars.append(t) + if _should_collect_from_parameters(t): + tvars.extend([t for t in t.__parameters__ if t not in tvars]) + return tuple(tvars) + + +NoReturn = typing.NoReturn + +# Some unconstrained type variables. These are used by the container types. +# (These are not for export.) +T = typing.TypeVar('T') # Any type. +KT = typing.TypeVar('KT') # Key type. +VT = typing.TypeVar('VT') # Value type. +T_co = typing.TypeVar('T_co', covariant=True) # Any type covariant containers. +T_contra = typing.TypeVar('T_contra', contravariant=True) # Ditto contravariant. + + +if sys.version_info >= (3, 11): + from typing import Any +else: + + class _AnyMeta(type): + def __instancecheck__(self, obj): + if self is Any: + raise TypeError("typing_extensions.Any cannot be used with isinstance()") + return super().__instancecheck__(obj) + + def __repr__(self): + if self is Any: + return "typing_extensions.Any" + return super().__repr__() + + class Any(metaclass=_AnyMeta): + """Special type indicating an unconstrained type. + - Any is compatible with every type. + - Any assumed to have all methods. + - All values assumed to be instances of Any. + Note that all the above statements are true from the point of view of + static type checkers. At runtime, Any should not be used with instance + checks. + """ + def __new__(cls, *args, **kwargs): + if cls is Any: + raise TypeError("Any cannot be instantiated") + return super().__new__(cls, *args, **kwargs) + + +ClassVar = typing.ClassVar + + +class _ExtensionsSpecialForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + +Final = typing.Final + +if sys.version_info >= (3, 11): + final = typing.final +else: + # @final exists in 3.8+, but we backport it for all versions + # before 3.11 to keep support for the __final__ attribute. + # See https://bugs.python.org/issue46342 + def final(f): + """This decorator can be used to indicate to type checkers that + the decorated method cannot be overridden, and decorated class + cannot be subclassed. For example: + + class Base: + @final + def done(self) -> None: + ... + class Sub(Base): + def done(self) -> None: # Error reported by type checker + ... + @final + class Leaf: + ... + class Other(Leaf): # Error reported by type checker + ... + + There is no runtime checking of these properties. The decorator + sets the ``__final__`` attribute to ``True`` on the decorated object + to allow runtime introspection. + """ + try: + f.__final__ = True + except (AttributeError, TypeError): + # Skip the attribute silently if it is not writable. + # AttributeError happens if the object has __slots__ or a + # read-only property, TypeError if it's a builtin class. + pass + return f + + +def IntVar(name): + return typing.TypeVar(name) + + +# A Literal bug was fixed in 3.11.0, 3.10.1 and 3.9.8 +if sys.version_info >= (3, 10, 1): + Literal = typing.Literal +else: + def _flatten_literal_params(parameters): + """An internal helper for Literal creation: flatten Literals among parameters""" + params = [] + for p in parameters: + if isinstance(p, _LiteralGenericAlias): + params.extend(p.__args__) + else: + params.append(p) + return tuple(params) + + def _value_and_type_iter(params): + for p in params: + yield p, type(p) + + class _LiteralGenericAlias(typing._GenericAlias, _root=True): + def __eq__(self, other): + if not isinstance(other, _LiteralGenericAlias): + return NotImplemented + these_args_deduped = set(_value_and_type_iter(self.__args__)) + other_args_deduped = set(_value_and_type_iter(other.__args__)) + return these_args_deduped == other_args_deduped + + def __hash__(self): + return hash(frozenset(_value_and_type_iter(self.__args__))) + + class _LiteralForm(_ExtensionsSpecialForm, _root=True): + def __init__(self, doc: str): + self._name = 'Literal' + self._doc = self.__doc__ = doc + + def __getitem__(self, parameters): + if not isinstance(parameters, tuple): + parameters = (parameters,) + + parameters = _flatten_literal_params(parameters) + + val_type_pairs = list(_value_and_type_iter(parameters)) + try: + deduped_pairs = set(val_type_pairs) + except TypeError: + # unhashable parameters + pass + else: + # similar logic to typing._deduplicate on Python 3.9+ + if len(deduped_pairs) < len(val_type_pairs): + new_parameters = [] + for pair in val_type_pairs: + if pair in deduped_pairs: + new_parameters.append(pair[0]) + deduped_pairs.remove(pair) + assert not deduped_pairs, deduped_pairs + parameters = tuple(new_parameters) + + return _LiteralGenericAlias(self, parameters) + + Literal = _LiteralForm(doc="""\ + 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.""") + + +_overload_dummy = typing._overload_dummy + + +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. +Type = typing.Type + +# Various ABCs mimicking those in collections.abc. +# A few are simply re-exported for completeness. +Awaitable = typing.Awaitable +Coroutine = typing.Coroutine +AsyncIterable = typing.AsyncIterable +AsyncIterator = typing.AsyncIterator +Deque = typing.Deque +ContextManager = typing.ContextManager +AsyncContextManager = typing.AsyncContextManager +DefaultDict = typing.DefaultDict +OrderedDict = typing.OrderedDict +Counter = typing.Counter +ChainMap = typing.ChainMap +AsyncGenerator = typing.AsyncGenerator +Text = typing.Text +TYPE_CHECKING = typing.TYPE_CHECKING + + +_PROTO_ALLOWLIST = { + 'collections.abc': [ + 'Callable', 'Awaitable', 'Iterable', 'Iterator', 'AsyncIterable', + 'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', 'Buffer', + ], + 'contextlib': ['AbstractContextManager', 'AbstractAsyncContextManager'], + 'typing_extensions': ['Buffer'], +} + + +_EXCLUDED_ATTRS = { + "__abstractmethods__", "__annotations__", "__weakref__", "_is_protocol", + "_is_runtime_protocol", "__dict__", "__slots__", "__parameters__", + "__orig_bases__", "__module__", "_MutableMapping__marker", "__doc__", + "__subclasshook__", "__orig_class__", "__init__", "__new__", + "__protocol_attrs__", "__callable_proto_members_only__", + "__match_args__", +} + +if sys.version_info >= (3, 9): + _EXCLUDED_ATTRS.add("__class_getitem__") + +if sys.version_info >= (3, 12): + _EXCLUDED_ATTRS.add("__type_params__") + +_EXCLUDED_ATTRS = frozenset(_EXCLUDED_ATTRS) + + +def _get_protocol_attrs(cls): + attrs = set() + for base in cls.__mro__[:-1]: # without object + if base.__name__ in {'Protocol', 'Generic'}: + continue + annotations = getattr(base, '__annotations__', {}) + for attr in (*base.__dict__, *annotations): + if (not attr.startswith('_abc_') and attr not in _EXCLUDED_ATTRS): + attrs.add(attr) + return attrs + + +def _caller(depth=2): + try: + return sys._getframe(depth).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): # For platforms without _getframe() + return None + + +# `__match_args__` attribute was removed from protocol members in 3.13, +# we want to backport this change to older Python versions. +if sys.version_info >= (3, 13): + Protocol = typing.Protocol +else: + def _allow_reckless_class_checks(depth=3): + """Allow instance and class checks for special stdlib modules. + The abc and functools modules indiscriminately call isinstance() and + issubclass() on the whole MRO of a user class, which may contain protocols. + """ + return _caller(depth) in {'abc', 'functools', None} + + def _no_init(self, *args, **kwargs): + if type(self)._is_protocol: + raise TypeError('Protocols cannot be instantiated') + + # Inheriting from typing._ProtocolMeta isn't actually desirable, + # but is necessary to allow typing.Protocol and typing_extensions.Protocol + # to mix without getting TypeErrors about "metaclass conflict" + class _ProtocolMeta(type(typing.Protocol)): + # This metaclass is somewhat unfortunate, + # but is necessary for several reasons... + # + # NOTE: DO NOT call super() in any methods in this class + # That would call the methods on typing._ProtocolMeta on Python 3.8-3.11 + # and those are slow + def __new__(mcls, name, bases, namespace, **kwargs): + if name == "Protocol" and len(bases) < 2: + pass + elif {Protocol, typing.Protocol} & set(bases): + for base in bases: + if not ( + base in {object, typing.Generic, Protocol, typing.Protocol} + or base.__name__ in _PROTO_ALLOWLIST.get(base.__module__, []) + or is_protocol(base) + ): + raise TypeError( + f"Protocols can only inherit from other protocols, " + f"got {base!r}" + ) + return abc.ABCMeta.__new__(mcls, name, bases, namespace, **kwargs) + + def __init__(cls, *args, **kwargs): + abc.ABCMeta.__init__(cls, *args, **kwargs) + if getattr(cls, "_is_protocol", False): + cls.__protocol_attrs__ = _get_protocol_attrs(cls) + # PEP 544 prohibits using issubclass() + # with protocols that have non-method members. + cls.__callable_proto_members_only__ = all( + callable(getattr(cls, attr, None)) for attr in cls.__protocol_attrs__ + ) + + def __subclasscheck__(cls, other): + if cls is Protocol: + return type.__subclasscheck__(cls, other) + if ( + getattr(cls, '_is_protocol', False) + and not _allow_reckless_class_checks() + ): + if not isinstance(other, type): + # Same error message as for issubclass(1, int). + raise TypeError('issubclass() arg 1 must be a class') + if ( + not cls.__callable_proto_members_only__ + and cls.__dict__.get("__subclasshook__") is _proto_hook + ): + non_method_attrs = sorted( + attr for attr in cls.__protocol_attrs__ + if not callable(getattr(cls, attr, None)) + ) + raise TypeError( + "Protocols with non-method members don't support issubclass()." + f" Non-method members: {str(non_method_attrs)[1:-1]}." + ) + if not getattr(cls, '_is_runtime_protocol', False): + raise TypeError( + "Instance and class checks can only be used with " + "@runtime_checkable protocols" + ) + return abc.ABCMeta.__subclasscheck__(cls, other) + + def __instancecheck__(cls, instance): + # We need this method for situations where attributes are + # assigned in __init__. + if cls is Protocol: + return type.__instancecheck__(cls, instance) + if not getattr(cls, "_is_protocol", False): + # i.e., it's a concrete subclass of a protocol + return abc.ABCMeta.__instancecheck__(cls, instance) + + if ( + not getattr(cls, '_is_runtime_protocol', False) and + not _allow_reckless_class_checks() + ): + raise TypeError("Instance and class checks can only be used with" + " @runtime_checkable protocols") + + if abc.ABCMeta.__instancecheck__(cls, instance): + return True + + for attr in cls.__protocol_attrs__: + try: + val = inspect.getattr_static(instance, attr) + except AttributeError: + break + if val is None and callable(getattr(cls, attr, None)): + break + else: + return True + + return False + + def __eq__(cls, other): + # Hack so that typing.Generic.__class_getitem__ + # treats typing_extensions.Protocol + # as equivalent to typing.Protocol + if abc.ABCMeta.__eq__(cls, other) is True: + return True + return cls is Protocol and other is typing.Protocol + + # This has to be defined, or the abc-module cache + # complains about classes with this metaclass being unhashable, + # if we define only __eq__! + def __hash__(cls) -> int: + return type.__hash__(cls) + + @classmethod + def _proto_hook(cls, other): + if not cls.__dict__.get('_is_protocol', False): + return NotImplemented + + for attr in cls.__protocol_attrs__: + for base in other.__mro__: + # Check if the members appears in the class dictionary... + if attr in base.__dict__: + if base.__dict__[attr] is None: + return NotImplemented + break + + # ...or in annotations, if it is a sub-protocol. + annotations = getattr(base, '__annotations__', {}) + if ( + isinstance(annotations, collections.abc.Mapping) + and attr in annotations + and is_protocol(other) + ): + break + else: + return NotImplemented + return True + + class Protocol(typing.Generic, metaclass=_ProtocolMeta): + __doc__ = typing.Protocol.__doc__ + __slots__ = () + _is_protocol = True + _is_runtime_protocol = False + + def __init_subclass__(cls, *args, **kwargs): + super().__init_subclass__(*args, **kwargs) + + # Determine if this is a protocol or a concrete subclass. + if not cls.__dict__.get('_is_protocol', False): + cls._is_protocol = any(b is Protocol for b in cls.__bases__) + + # Set (or override) the protocol subclass hook. + if '__subclasshook__' not in cls.__dict__: + cls.__subclasshook__ = _proto_hook + + # Prohibit instantiation for protocol classes + if cls._is_protocol and cls.__init__ is Protocol.__init__: + cls.__init__ = _no_init + + +# The "runtime" alias exists for backwards compatibility. +runtime = runtime_checkable = typing.runtime_checkable + + +# Our version of runtime-checkable protocols is faster on Python 3.8-3.11 +if sys.version_info >= (3, 12): + SupportsInt = typing.SupportsInt + SupportsFloat = typing.SupportsFloat + SupportsComplex = typing.SupportsComplex + SupportsBytes = typing.SupportsBytes + SupportsIndex = typing.SupportsIndex + SupportsAbs = typing.SupportsAbs + SupportsRound = typing.SupportsRound +else: + @runtime_checkable + class SupportsInt(Protocol): + """An ABC with one abstract method __int__.""" + __slots__ = () + + @abc.abstractmethod + def __int__(self) -> int: + pass + + @runtime_checkable + class SupportsFloat(Protocol): + """An ABC with one abstract method __float__.""" + __slots__ = () + + @abc.abstractmethod + def __float__(self) -> float: + pass + + @runtime_checkable + class SupportsComplex(Protocol): + """An ABC with one abstract method __complex__.""" + __slots__ = () + + @abc.abstractmethod + def __complex__(self) -> complex: + pass + + @runtime_checkable + class SupportsBytes(Protocol): + """An ABC with one abstract method __bytes__.""" + __slots__ = () + + @abc.abstractmethod + def __bytes__(self) -> bytes: + pass + + @runtime_checkable + class SupportsIndex(Protocol): + __slots__ = () + + @abc.abstractmethod + def __index__(self) -> int: + pass + + @runtime_checkable + class SupportsAbs(Protocol[T_co]): + """ + An ABC with one abstract method __abs__ that is covariant in its return type. + """ + __slots__ = () + + @abc.abstractmethod + def __abs__(self) -> T_co: + pass + + @runtime_checkable + class SupportsRound(Protocol[T_co]): + """ + An ABC with one abstract method __round__ that is covariant in its return type. + """ + __slots__ = () + + @abc.abstractmethod + def __round__(self, ndigits: int = 0) -> T_co: + pass + + +def _ensure_subclassable(mro_entries): + def inner(func): + if sys.implementation.name == "pypy" and sys.version_info < (3, 9): + cls_dict = { + "__call__": staticmethod(func), + "__mro_entries__": staticmethod(mro_entries) + } + t = type(func.__name__, (), cls_dict) + return functools.update_wrapper(t(), func) + else: + func.__mro_entries__ = mro_entries + return func + return inner + + +if hasattr(typing, "ReadOnly"): + # The standard library TypedDict in Python 3.8 does not store runtime information + # about which (if any) keys are optional. See https://bugs.python.org/issue38834 + # The standard library TypedDict in Python 3.9.0/1 does not honour the "total" + # keyword with old-style TypedDict(). See https://bugs.python.org/issue42059 + # The standard library TypedDict below Python 3.11 does not store runtime + # information about optional and required keys when using Required or NotRequired. + # Generic TypedDicts are also impossible using typing.TypedDict on Python <3.11. + # Aaaand on 3.12 we add __orig_bases__ to TypedDict + # to enable better runtime introspection. + # On 3.13 we deprecate some odd ways of creating TypedDicts. + # PEP 705 proposes adding the ReadOnly[] qualifier. + TypedDict = typing.TypedDict + _TypedDictMeta = typing._TypedDictMeta + is_typeddict = typing.is_typeddict +else: + # 3.10.0 and later + _TAKES_MODULE = "module" in inspect.signature(typing._type_check).parameters + + def _get_typeddict_qualifiers(annotation_type): + while True: + 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] + else: + break + elif annotation_origin is Required: + yield Required + annotation_type, = get_args(annotation_type) + elif annotation_origin is NotRequired: + yield NotRequired + annotation_type, = get_args(annotation_type) + elif annotation_origin is ReadOnly: + yield ReadOnly + annotation_type, = get_args(annotation_type) + else: + break + + class _TypedDictMeta(type): + def __new__(cls, name, bases, ns, *, total=True): + """Create new typed dict class object. + + This method is called when TypedDict is subclassed, + or when TypedDict is instantiated. This way + TypedDict supports all three syntax forms described in its docstring. + Subclasses and instances of TypedDict return actual dictionaries. + """ + for base in bases: + if type(base) is not _TypedDictMeta and base is not typing.Generic: + raise TypeError('cannot inherit from both a TypedDict type ' + 'and a non-TypedDict base class') + + if any(issubclass(b, typing.Generic) for b in bases): + generic_base = (typing.Generic,) + else: + generic_base = () + + # typing.py generally doesn't let you inherit from plain Generic, unless + # the name of the class happens to be "Protocol" + tp_dict = type.__new__(_TypedDictMeta, "Protocol", (*generic_base, dict), ns) + tp_dict.__name__ = name + if tp_dict.__qualname__ == "Protocol": + tp_dict.__qualname__ = name + + if not hasattr(tp_dict, '__orig_bases__'): + tp_dict.__orig_bases__ = bases + + annotations = {} + own_annotations = ns.get('__annotations__', {}) + msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type" + if _TAKES_MODULE: + own_annotations = { + n: typing._type_check(tp, msg, module=tp_dict.__module__) + for n, tp in own_annotations.items() + } + else: + own_annotations = { + n: typing._type_check(tp, msg) + for n, tp in own_annotations.items() + } + required_keys = set() + optional_keys = set() + readonly_keys = set() + mutable_keys = set() + + for base in bases: + base_dict = base.__dict__ + + annotations.update(base_dict.get('__annotations__', {})) + required_keys.update(base_dict.get('__required_keys__', ())) + optional_keys.update(base_dict.get('__optional_keys__', ())) + readonly_keys.update(base_dict.get('__readonly_keys__', ())) + mutable_keys.update(base_dict.get('__mutable_keys__', ())) + + annotations.update(own_annotations) + for annotation_key, annotation_type in own_annotations.items(): + qualifiers = set(_get_typeddict_qualifiers(annotation_type)) + + if Required in qualifiers: + required_keys.add(annotation_key) + elif NotRequired in qualifiers: + optional_keys.add(annotation_key) + elif total: + required_keys.add(annotation_key) + else: + optional_keys.add(annotation_key) + if ReadOnly in qualifiers: + if annotation_key in mutable_keys: + raise TypeError( + f"Cannot override mutable key {annotation_key!r}" + " with read-only key" + ) + readonly_keys.add(annotation_key) + else: + mutable_keys.add(annotation_key) + readonly_keys.discard(annotation_key) + + tp_dict.__annotations__ = annotations + tp_dict.__required_keys__ = frozenset(required_keys) + tp_dict.__optional_keys__ = frozenset(optional_keys) + tp_dict.__readonly_keys__ = frozenset(readonly_keys) + tp_dict.__mutable_keys__ = frozenset(mutable_keys) + if not hasattr(tp_dict, '__total__'): + tp_dict.__total__ = total + return tp_dict + + __call__ = dict # static method + + def __subclasscheck__(cls, other): + # Typed dicts are only for static structural subtyping. + raise TypeError('TypedDict does not support instance and class checks') + + __instancecheck__ = __subclasscheck__ + + _TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {}) + + @_ensure_subclassable(lambda bases: (_TypedDict,)) + def TypedDict(typename, fields=_marker, /, *, total=True, **kwargs): + """A simple typed namespace. At runtime it is equivalent to a plain dict. + + TypedDict creates a dictionary type such that a type checker will expect all + instances to have a certain set of keys, where each key is + associated with a value of a consistent type. This expectation + is not checked at runtime. + + Usage:: + + class Point2D(TypedDict): + x: int + y: int + label: str + + a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK + b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check + + assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first') + + The type info can be accessed via the Point2D.__annotations__ dict, and + the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets. + TypedDict supports an additional equivalent form:: + + Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str}) + + By default, all keys must be present in a TypedDict. It is possible + to override this by specifying totality:: + + class Point2D(TypedDict, total=False): + x: int + y: int + + This means that a Point2D TypedDict can have any of the keys omitted. A type + checker is only expected to support a literal False or True as the value of + the total argument. True is the default, and makes all items defined in the + class body be required. + + The Required and NotRequired special forms can also be used to mark + individual keys as being required or not required:: + + class Point2D(TypedDict): + x: int # the "x" key must always be present (Required is the default) + y: NotRequired[int] # the "y" key can be omitted + + See PEP 655 for more details on Required and NotRequired. + """ + if fields is _marker or fields is None: + if fields is _marker: + deprecated_thing = "Failing to pass a value for the 'fields' parameter" + else: + deprecated_thing = "Passing `None` as the 'fields' parameter" + + example = f"`{typename} = TypedDict({typename!r}, {{}})`" + deprecation_msg = ( + f"{deprecated_thing} is deprecated and will be disallowed in " + "Python 3.15. To create a TypedDict class with 0 fields " + "using the functional syntax, pass an empty dictionary, e.g. " + ) + example + "." + warnings.warn(deprecation_msg, DeprecationWarning, stacklevel=2) + fields = kwargs + elif kwargs: + raise TypeError("TypedDict takes either a dict or keyword arguments," + " but not both") + if kwargs: + if sys.version_info >= (3, 13): + raise TypeError("TypedDict takes no keyword arguments") + warnings.warn( + "The kwargs-based syntax for TypedDict definitions is deprecated " + "in Python 3.11, will be removed in Python 3.13, and may not be " + "understood by third-party type checkers.", + DeprecationWarning, + stacklevel=2, + ) + + ns = {'__annotations__': dict(fields)} + module = _caller() + if module is not None: + # Setting correct module is necessary to make typed dict classes pickleable. + ns['__module__'] = module + + td = _TypedDictMeta(typename, (), ns, total=total) + td.__orig_bases__ = (TypedDict,) + return td + + if hasattr(typing, "_TypedDictMeta"): + _TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta) + else: + _TYPEDDICT_TYPES = (_TypedDictMeta,) + + def is_typeddict(tp): + """Check if an annotation is a TypedDict class + + For example:: + class Film(TypedDict): + title: str + year: int + + is_typeddict(Film) # => True + is_typeddict(Union[list, str]) # => False + """ + # On 3.8, this would otherwise return True + if hasattr(typing, "TypedDict") and tp is typing.TypedDict: + return False + return isinstance(tp, _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"): # 3.11+ + get_type_hints = typing.get_type_hints +else: # <=3.10 + # replaces _strip_annotations() + def _strip_extras(t): + """Strips Annotated, Required and NotRequired from a given type.""" + if isinstance(t, _AnnotatedAlias): + return _strip_extras(t.__origin__) + if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired): + return _strip_extras(t.__args__[0]) + if isinstance(t, typing._GenericAlias): + stripped_args = tuple(_strip_extras(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + return t.copy_with(stripped_args) + if hasattr(_types, "GenericAlias") and isinstance(t, _types.GenericAlias): + stripped_args = tuple(_strip_extras(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + return _types.GenericAlias(t.__origin__, stripped_args) + if hasattr(_types, "UnionType") and isinstance(t, _types.UnionType): + stripped_args = tuple(_strip_extras(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + return functools.reduce(operator.or_, stripped_args) + + return t + + def get_type_hints(obj, globalns=None, localns=None, include_extras=False): + """Return type hints for an object. + + This is often the same as obj.__annotations__, but it handles + forward references encoded as string literals, adds Optional[t] if a + default value equal to None is set and recursively replaces all + 'Annotated[T, ...]', 'Required[T]' or 'NotRequired[T]' with 'T' + (unless 'include_extras=True'). + + The argument may be a module, class, method, or function. The annotations + are returned as a dictionary. For classes, annotations include also + inherited members. + + TypeError is raised if the argument is not of a type that can contain + annotations, and an empty dictionary is returned if no annotations are + present. + + BEWARE -- the behavior of globalns and localns is counterintuitive + (unless you are familiar with how eval() and exec() work). The + search order is locals first, then globals. + + - If no dict arguments are passed, an attempt is made to use the + globals from obj (or the respective module's globals for classes), + and these are also used as the locals. If the object does not appear + to have globals, an empty dictionary is used. + + - If one dict argument is passed, it is used for both globals and + locals. + + - If two dict arguments are passed, they specify globals and + locals, respectively. + """ + if hasattr(typing, "Annotated"): # 3.9+ + hint = typing.get_type_hints( + obj, globalns=globalns, localns=localns, include_extras=True + ) + else: # 3.8 + hint = typing.get_type_hints(obj, globalns=globalns, localns=localns) + if include_extras: + return hint + return {k: _strip_extras(t) for k, t in hint.items()} + + +# Python 3.9+ has PEP 593 (Annotated) +if hasattr(typing, 'Annotated'): + Annotated = typing.Annotated + # Not exported and not a public API, but needed for get_origin() and get_args() + # to work. + _AnnotatedAlias = typing._AnnotatedAlias +# 3.8 +else: + class _AnnotatedAlias(typing._GenericAlias, _root=True): + """Runtime representation of an annotated type. + + At its core 'Annotated[t, dec1, dec2, ...]' is an alias for the type 't' + with extra annotations. The alias behaves like a normal typing alias, + instantiating is the same as instantiating the underlying type, binding + it to types is also the same. + """ + def __init__(self, origin, metadata): + if isinstance(origin, _AnnotatedAlias): + metadata = origin.__metadata__ + metadata + origin = origin.__origin__ + super().__init__(origin, origin) + self.__metadata__ = metadata + + def copy_with(self, params): + assert len(params) == 1 + new_type = params[0] + return _AnnotatedAlias(new_type, self.__metadata__) + + def __repr__(self): + return (f"typing_extensions.Annotated[{typing._type_repr(self.__origin__)}, " + f"{', '.join(repr(a) for a in self.__metadata__)}]") + + def __reduce__(self): + return operator.getitem, ( + Annotated, (self.__origin__,) + self.__metadata__ + ) + + def __eq__(self, other): + if not isinstance(other, _AnnotatedAlias): + return NotImplemented + if self.__origin__ != other.__origin__: + return False + return self.__metadata__ == other.__metadata__ + + def __hash__(self): + return hash((self.__origin__, self.__metadata__)) + + class Annotated: + """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 (and will be in + the __origin__ field), the remaining arguments are kept as a tuple in + the __extra__ 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()] + """ + + __slots__ = () + + def __new__(cls, *args, **kwargs): + raise TypeError("Type Annotated cannot be instantiated.") + + @typing._tp_cache + def __class_getitem__(cls, params): + if not isinstance(params, tuple) or len(params) < 2: + raise TypeError("Annotated[...] should be used " + "with at least two arguments (a type and an " + "annotation).") + allowed_special_forms = (ClassVar, Final) + if get_origin(params[0]) in allowed_special_forms: + origin = params[0] + else: + msg = "Annotated[t, ...]: t must be a type." + origin = typing._type_check(params[0], msg) + metadata = tuple(params[1:]) + return _AnnotatedAlias(origin, metadata) + + def __init_subclass__(cls, *args, **kwargs): + raise TypeError( + f"Cannot subclass {cls.__module__}.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.8-3.9 +else: + try: + # 3.9+ + from typing import _BaseGenericAlias + except ImportError: + _BaseGenericAlias = typing._GenericAlias + try: + # 3.9+ + from typing import GenericAlias as _typing_GenericAlias + except ImportError: + _typing_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, _typing_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. + + For unions, basic simplifications used by Union constructor are performed. + Examples:: + get_args(Dict[str, int]) == (str, int) + get_args(int) == () + get_args(Union[int, Union[T, int], str][int]) == (int, str) + get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int]) + get_args(Callable[[], T][int]) == ([], int) + """ + if isinstance(tp, _AnnotatedAlias): + return (tp.__origin__,) + tp.__metadata__ + if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias)): + if getattr(tp, "_special", False): + return () + res = tp.__args__ + if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis: + res = (list(res[:-1]), res[-1]) + return res + return () + + +# 3.10+ +if hasattr(typing, 'TypeAlias'): + TypeAlias = typing.TypeAlias +# 3.9 +elif sys.version_info[:2] >= (3, 9): + @_ExtensionsSpecialForm + def TypeAlias(self, parameters): + """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. + """ + raise TypeError(f"{self} is not subscriptable") +# 3.8 +else: + TypeAlias = _ExtensionsSpecialForm( + 'TypeAlias', + doc="""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.""" + ) + + +def _set_default(type_param, default): + if isinstance(default, (tuple, list)): + type_param.__default__ = tuple((typing._type_check(d, "Default must be a type") + for d in default)) + elif default != _marker: + if isinstance(type_param, ParamSpec) and default is ...: # ... not valid <3.11 + type_param.__default__ = default + else: + type_param.__default__ = typing._type_check(default, "Default must be a type") + else: + type_param.__default__ = None + + +def _set_module(typevarlike): + # for pickling: + def_mod = _caller(depth=3) + if def_mod != 'typing_extensions': + typevarlike.__module__ = def_mod + + +class _DefaultMixin: + """Mixin for TypeVarLike defaults.""" + + __slots__ = () + __init__ = _set_default + + +# Classes using this metaclass must provide a _backported_typevarlike ClassVar +class _TypeVarLikeMeta(type): + def __instancecheck__(cls, __instance: Any) -> bool: + return isinstance(__instance, cls._backported_typevarlike) + + +# Add default and infer_variance parameters from PEP 696 and 695 +class TypeVar(metaclass=_TypeVarLikeMeta): + """Type variable.""" + + _backported_typevarlike = typing.TypeVar + + def __new__(cls, name, *constraints, bound=None, + covariant=False, contravariant=False, + default=_marker, infer_variance=False): + if hasattr(typing, "TypeAliasType"): + # PEP 695 implemented (3.12+), can pass infer_variance to typing.TypeVar + typevar = typing.TypeVar(name, *constraints, bound=bound, + covariant=covariant, contravariant=contravariant, + infer_variance=infer_variance) + else: + typevar = typing.TypeVar(name, *constraints, bound=bound, + covariant=covariant, contravariant=contravariant) + if infer_variance and (covariant or contravariant): + raise ValueError("Variance cannot be specified with infer_variance.") + typevar.__infer_variance__ = infer_variance + _set_default(typevar, default) + _set_module(typevar) + return typevar + + def __init_subclass__(cls) -> None: + raise TypeError(f"type '{__name__}.TypeVar' is not an acceptable base type") + + +# Python 3.10+ has PEP 612 +if hasattr(typing, 'ParamSpecArgs'): + ParamSpecArgs = typing.ParamSpecArgs + ParamSpecKwargs = typing.ParamSpecKwargs +# 3.8-3.9 +else: + class _Immutable: + """Mixin to indicate that object should not be copied.""" + __slots__ = () + + def __copy__(self): + return self + + def __deepcopy__(self, memo): + return self + + class ParamSpecArgs(_Immutable): + """The args for a ParamSpec object. + + Given a ParamSpec object P, P.args is an instance of ParamSpecArgs. + + ParamSpecArgs objects have a reference back to their ParamSpec: + + P.args.__origin__ is P + + This type is meant for runtime introspection and has no special meaning to + static type checkers. + """ + def __init__(self, origin): + self.__origin__ = origin + + def __repr__(self): + return f"{self.__origin__.__name__}.args" + + def __eq__(self, other): + if not isinstance(other, ParamSpecArgs): + return NotImplemented + return self.__origin__ == other.__origin__ + + class ParamSpecKwargs(_Immutable): + """The kwargs for a ParamSpec object. + + Given a ParamSpec object P, P.kwargs is an instance of ParamSpecKwargs. + + ParamSpecKwargs objects have a reference back to their ParamSpec: + + P.kwargs.__origin__ is P + + This type is meant for runtime introspection and has no special meaning to + static type checkers. + """ + def __init__(self, origin): + self.__origin__ = origin + + def __repr__(self): + return f"{self.__origin__.__name__}.kwargs" + + def __eq__(self, other): + if not isinstance(other, ParamSpecKwargs): + return NotImplemented + return self.__origin__ == other.__origin__ + +# 3.10+ +if hasattr(typing, 'ParamSpec'): + + # Add default parameter - PEP 696 + class ParamSpec(metaclass=_TypeVarLikeMeta): + """Parameter specification.""" + + _backported_typevarlike = typing.ParamSpec + + def __new__(cls, name, *, bound=None, + covariant=False, contravariant=False, + infer_variance=False, default=_marker): + if hasattr(typing, "TypeAliasType"): + # PEP 695 implemented, can pass infer_variance to typing.TypeVar + paramspec = typing.ParamSpec(name, bound=bound, + covariant=covariant, + contravariant=contravariant, + infer_variance=infer_variance) + else: + paramspec = typing.ParamSpec(name, bound=bound, + covariant=covariant, + contravariant=contravariant) + paramspec.__infer_variance__ = infer_variance + + _set_default(paramspec, default) + _set_module(paramspec) + return paramspec + + def __init_subclass__(cls) -> None: + raise TypeError(f"type '{__name__}.ParamSpec' is not an acceptable base type") + +# 3.8-3.9 +else: + + # Inherits from list as a workaround for Callable checks in Python < 3.9.2. + class ParamSpec(list, _DefaultMixin): + """Parameter specification variable. + + Usage:: + + P = ParamSpec('P') + + Parameter specification variables exist primarily for the benefit of static + type checkers. They are used to forward the parameter types of one + callable to another callable, a pattern commonly found in higher order + functions and decorators. They are only valid when used in ``Concatenate``, + or s the first argument to ``Callable``. In Python 3.10 and higher, + they are also supported in user-defined Generics at runtime. + See class Generic for more information on generic types. An + example for annotating a decorator:: + + T = TypeVar('T') + P = ParamSpec('P') + + def add_logging(f: Callable[P, T]) -> Callable[P, T]: + '''A type-safe decorator to add logging to a function.''' + def inner(*args: P.args, **kwargs: P.kwargs) -> T: + logging.info(f'{f.__name__} was called') + return f(*args, **kwargs) + return inner + + @add_logging + def add_two(x: float, y: float) -> float: + '''Add two numbers together.''' + return x + y + + Parameter specification variables defined with covariant=True or + contravariant=True can be used to declare covariant or contravariant + generic types. These keyword arguments are valid, but their actual semantics + are yet to be decided. See PEP 612 for details. + + Parameter specification variables can be introspected. e.g.: + + P.__name__ == 'T' + P.__bound__ == None + P.__covariant__ == False + P.__contravariant__ == False + + Note that only parameter specification variables defined in global scope can + be pickled. + """ + + # Trick Generic __parameters__. + __class__ = typing.TypeVar + + @property + def args(self): + return ParamSpecArgs(self) + + @property + def kwargs(self): + return ParamSpecKwargs(self) + + def __init__(self, name, *, bound=None, covariant=False, contravariant=False, + infer_variance=False, default=_marker): + super().__init__([self]) + self.__name__ = name + self.__covariant__ = bool(covariant) + self.__contravariant__ = bool(contravariant) + self.__infer_variance__ = bool(infer_variance) + if bound: + self.__bound__ = typing._type_check(bound, 'Bound must be a type.') + else: + self.__bound__ = None + _DefaultMixin.__init__(self, default) + + # for pickling: + def_mod = _caller() + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + def __repr__(self): + if self.__infer_variance__: + prefix = '' + elif self.__covariant__: + prefix = '+' + elif self.__contravariant__: + prefix = '-' + else: + prefix = '~' + return prefix + self.__name__ + + def __hash__(self): + return object.__hash__(self) + + def __eq__(self, other): + return self is other + + def __reduce__(self): + return self.__name__ + + # Hack to get typing._type_check to pass. + def __call__(self, *args, **kwargs): + pass + + +# 3.8-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__. + __class__ = typing._GenericAlias + + # Flag in 3.8. + _special = False + + def __init__(self, origin, args): + super().__init__(args) + self.__origin__ = origin + self.__args__ = args + + def __repr__(self): + _type_repr = typing._type_repr + return (f'{_type_repr(self.__origin__)}' + f'[{", ".join(_type_repr(arg) for arg in self.__args__)}]') + + def __hash__(self): + return hash((self.__origin__, self.__args__)) + + # Hack to get typing._type_check to pass in Generic. + def __call__(self, *args, **kwargs): + pass + + @property + def __parameters__(self): + return tuple( + tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec)) + ) + + +# 3.8-3.9 +@typing._tp_cache +def _concatenate_getitem(self, parameters): + if parameters == (): + raise TypeError("Cannot take a Concatenate of no types.") + if not isinstance(parameters, tuple): + parameters = (parameters,) + if not isinstance(parameters[-1], ParamSpec): + raise TypeError("The last parameter to Concatenate should be a " + "ParamSpec variable.") + msg = "Concatenate[arg, ...]: each arg must be a type." + parameters = tuple(typing._type_check(p, msg) for p in parameters) + return _ConcatenateGenericAlias(self, parameters) + + +# 3.10+ +if hasattr(typing, 'Concatenate'): + Concatenate = typing.Concatenate + _ConcatenateGenericAlias = typing._ConcatenateGenericAlias # noqa: F811 +# 3.9 +elif sys.version_info[:2] >= (3, 9): + @_ExtensionsSpecialForm + def Concatenate(self, parameters): + """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. + """ + return _concatenate_getitem(self, parameters) +# 3.8 +else: + class _ConcatenateForm(_ExtensionsSpecialForm, _root=True): + def __getitem__(self, parameters): + return _concatenate_getitem(self, parameters) + + Concatenate = _ConcatenateForm( + 'Concatenate', + doc="""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. + """) + +# 3.10+ +if hasattr(typing, 'TypeGuard'): + TypeGuard = typing.TypeGuard +# 3.9 +elif sys.version_info[:2] >= (3, 9): + @_ExtensionsSpecialForm + def TypeGuard(self, parameters): + """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). + """ + item = typing._type_check(parameters, f'{self} accepts only a single type.') + return typing._GenericAlias(self, (item,)) +# 3.8 +else: + class _TypeGuardForm(_ExtensionsSpecialForm, _root=True): + def __getitem__(self, parameters): + item = typing._type_check(parameters, + f'{self._name} accepts only a single type') + return typing._GenericAlias(self, (item,)) + + TypeGuard = _TypeGuardForm( + 'TypeGuard', + doc="""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). + """) + + +# 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 __getattr__(self, item): + if item in {'__name__', '__qualname__'}: + return self._name + + raise AttributeError(item) + + def __mro_entries__(self, bases): + raise TypeError(f"Cannot subclass {self!r}") + + def __repr__(self): + return f'typing_extensions.{self._name}' + + def __reduce__(self): + return self._name + + def __call__(self, *args, **kwds): + raise TypeError(f"Cannot instantiate {self!r}") + + def __or__(self, other): + return typing.Union[self, other] + + def __ror__(self, other): + return typing.Union[other, self] + + 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()") + + @typing._tp_cache + def __getitem__(self, parameters): + return self._getitem(self, parameters) + + +if hasattr(typing, "LiteralString"): # 3.11+ + LiteralString = typing.LiteralString +else: + @_SpecialForm + def LiteralString(self, params): + """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. + + """ + raise TypeError(f"{self} is not subscriptable") + + +if hasattr(typing, "Self"): # 3.11+ + Self = typing.Self +else: + @_SpecialForm + def Self(self, params): + """Used to spell the type of "self" in classes. + + Example:: + + from typing import Self + + class ReturnsSelf: + def parse(self, data: bytes) -> Self: + ... + return self + + """ + + raise TypeError(f"{self} is not subscriptable") + + +if hasattr(typing, "Never"): # 3.11+ + Never = typing.Never +else: + @_SpecialForm + def Never(self, params): + """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 + + """ + + raise TypeError(f"{self} is not subscriptable") + + +if hasattr(typing, 'Required'): # 3.11+ + Required = typing.Required + NotRequired = typing.NotRequired +elif sys.version_info[:2] >= (3, 9): # 3.9-3.10 + @_ExtensionsSpecialForm + def Required(self, parameters): + """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. + """ + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + @_ExtensionsSpecialForm + def NotRequired(self, parameters): + """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, + ) + """ + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + +else: # 3.8 + class _RequiredForm(_ExtensionsSpecialForm, _root=True): + def __getitem__(self, parameters): + item = typing._type_check(parameters, + f'{self._name} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + Required = _RequiredForm( + 'Required', + doc="""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. + """) + NotRequired = _RequiredForm( + 'NotRequired', + doc="""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, + ) + """) + + +if hasattr(typing, 'ReadOnly'): + ReadOnly = typing.ReadOnly +elif sys.version_info[:2] >= (3, 9): # 3.9-3.12 + @_ExtensionsSpecialForm + def ReadOnly(self, parameters): + """A special typing construct to mark an item of a TypedDict as read-only. + + For example: + + class Movie(TypedDict): + title: ReadOnly[str] + year: int + + def mutate_movie(m: Movie) -> None: + m["year"] = 1992 # allowed + m["title"] = "The Matrix" # typechecker error + + There is no runtime checking for this property. + """ + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + +else: # 3.8 + class _ReadOnlyForm(_ExtensionsSpecialForm, _root=True): + def __getitem__(self, parameters): + item = typing._type_check(parameters, + f'{self._name} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + ReadOnly = _ReadOnlyForm( + 'ReadOnly', + doc="""A special typing construct to mark a key of a TypedDict as read-only. + + For example: + + class Movie(TypedDict): + title: ReadOnly[str] + year: int + + def mutate_movie(m: Movie) -> None: + m["year"] = 1992 # allowed + m["title"] = "The Matrix" # typechecker error + + There is no runtime checking for this propery. + """) + + +_UNPACK_DOC = """\ +Type unpack operator. + +The type unpack operator takes the child types from some container type, +such as `tuple[int, str]` or a `TypeVarTuple`, and 'pulls them out'. For +example: + + # For some generic class `Foo`: + Foo[Unpack[tuple[int, str]]] # Equivalent to Foo[int, str] + + Ts = TypeVarTuple('Ts') + # Specifies that `Bar` is generic in an arbitrary number of types. + # (Think of `Ts` as a tuple of an arbitrary number of individual + # `TypeVar`s, which the `Unpack` is 'pulling out' directly into the + # `Generic[]`.) + class Bar(Generic[Unpack[Ts]]): ... + Bar[int] # Valid + Bar[int, str] # Also valid + +From Python 3.11, this can also be done using the `*` operator: + + Foo[*tuple[int, str]] + class Bar(Generic[*Ts]): ... + +The operator can also be used along with a `TypedDict` to annotate +`**kwargs` in a function signature. For instance: + + class Movie(TypedDict): + name: str + year: int + + # This function expects two keyword arguments - *name* of type `str` and + # *year* of type `int`. + def foo(**kwargs: Unpack[Movie]): ... + +Note that there is only some runtime checking of this operator. Not +everything the runtime allows may be accepted by static type checkers. + +For more information, see PEP 646 and PEP 692. +""" + + +if sys.version_info >= (3, 12): # PEP 692 changed the repr of Unpack[] + Unpack = typing.Unpack + + def _is_unpack(obj): + return get_origin(obj) is Unpack + +elif sys.version_info[:2] >= (3, 9): # 3.9+ + class _UnpackSpecialForm(_ExtensionsSpecialForm, _root=True): + def __init__(self, getitem): + super().__init__(getitem) + self.__doc__ = _UNPACK_DOC + + class _UnpackAlias(typing._GenericAlias, _root=True): + __class__ = typing.TypeVar + + @_UnpackSpecialForm + def Unpack(self, parameters): + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') + return _UnpackAlias(self, (item,)) + + def _is_unpack(obj): + return isinstance(obj, _UnpackAlias) + +else: # 3.8 + class _UnpackAlias(typing._GenericAlias, _root=True): + __class__ = typing.TypeVar + + class _UnpackForm(_ExtensionsSpecialForm, _root=True): + def __getitem__(self, parameters): + item = typing._type_check(parameters, + f'{self._name} accepts only a single type.') + return _UnpackAlias(self, (item,)) + + Unpack = _UnpackForm('Unpack', doc=_UNPACK_DOC) + + def _is_unpack(obj): + return isinstance(obj, _UnpackAlias) + + +if hasattr(typing, "TypeVarTuple"): # 3.11+ + + # Add default parameter - PEP 696 + class TypeVarTuple(metaclass=_TypeVarLikeMeta): + """Type variable tuple.""" + + _backported_typevarlike = typing.TypeVarTuple + + def __new__(cls, name, *, default=_marker): + tvt = typing.TypeVarTuple(name) + _set_default(tvt, default) + _set_module(tvt) + return tvt + + def __init_subclass__(self, *args, **kwds): + raise TypeError("Cannot subclass special typing classes") + +else: # <=3.10 + class TypeVarTuple(_DefaultMixin): + """Type variable tuple. + + Usage:: + + 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]``. + + Type variable tuples can be used in ``Generic`` declarations. + Consider the following example:: + + 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. + + 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]): + + def __init__(self, shape: Tuple[*Ts]): + self._shape: Tuple[*Ts] = 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] + + """ + + # Trick Generic __parameters__. + __class__ = typing.TypeVar + + def __iter__(self): + yield self.__unpacked__ + + def __init__(self, name, *, default=_marker): + self.__name__ = name + _DefaultMixin.__init__(self, default) + + # for pickling: + def_mod = _caller() + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + self.__unpacked__ = Unpack[self] + + def __repr__(self): + return self.__name__ + + def __hash__(self): + return object.__hash__(self) + + def __eq__(self, other): + return self is other + + def __reduce__(self): + return self.__name__ + + def __init_subclass__(self, *args, **kwds): + if '_root' not in kwds: + raise TypeError("Cannot subclass special typing classes") + + +if hasattr(typing, "reveal_type"): # 3.11+ + reveal_type = typing.reveal_type +else: # <=3.10 + def reveal_type(obj: T, /) -> T: + """Reveal the inferred type of a variable. + + When a static type checker encounters a call to ``reveal_type()``, + it will emit the inferred type of the argument:: + + x: int = 1 + reveal_type(x) + + Running a static type checker (e.g., ``mypy``) on this example + will produce output similar to 'Revealed type is "builtins.int"'. + + At runtime, the function prints the runtime type of the + argument and returns it unchanged. + + """ + print(f"Runtime type is {type(obj).__name__!r}", file=sys.stderr) + return obj + + +if hasattr(typing, "assert_never"): # 3.11+ + assert_never = typing.assert_never +else: # <=3.10 + def assert_never(arg: Never, /) -> Never: + """Assert to the type checker that a line of code is unreachable. + + Example:: + + def int_or_str(arg: int | str) -> None: + match arg: + case int(): + print("It's an int") + case str(): + print("It's a str") + case _: + assert_never(arg) + + If a type checker finds that a call to assert_never() is + reachable, it will emit an error. + + At runtime, this throws an exception when called. + + """ + raise AssertionError("Expected code to be unreachable") + + +if sys.version_info >= (3, 12): # 3.12+ + # dataclass_transform exists in 3.11 but lacks the frozen_default parameter + dataclass_transform = typing.dataclass_transform +else: # <=3.11 + def dataclass_transform( + *, + eq_default: bool = True, + order_default: bool = False, + kw_only_default: bool = False, + frozen_default: bool = False, + 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. + + Example: + + from typing_extensions import dataclass_transform + + _T = TypeVar("_T") + + # Used on a decorator function + @dataclass_transform() + def create_model(cls: type[_T]) -> type[_T]: + ... + return cls + + @create_model + class CustomerModel: + id: int + name: str + + # Used on a base class + @dataclass_transform() + class ModelBase: ... + + class CustomerModel(ModelBase): + id: int + name: str + + # Used on a metaclass + @dataclass_transform() + class ModelMeta(type): ... + + class ModelBase(metaclass=ModelMeta): ... + + class CustomerModel(ModelBase): + id: int + name: str + + Each of the ``CustomerModel`` classes defined in this example will now + behave similarly to a dataclass created with the ``@dataclasses.dataclass`` + decorator. For example, the type checker will synthesize an ``__init__`` + method. + + The arguments to this decorator can be used to customize this behavior: + - ``eq_default`` indicates whether the ``eq`` parameter is assumed to be + True or False if it is omitted by the caller. + - ``order_default`` indicates whether the ``order`` parameter is + 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. + - ``frozen_default`` indicates whether the ``frozen`` parameter is + assumed to be True or False if it is omitted by the caller. + - ``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. + + See PEP 681 for details. + + """ + def decorator(cls_or_fn): + cls_or_fn.__dataclass_transform__ = { + "eq_default": eq_default, + "order_default": order_default, + "kw_only_default": kw_only_default, + "frozen_default": frozen_default, + "field_specifiers": field_specifiers, + "kwargs": kwargs, + } + return cls_or_fn + return decorator + + +if hasattr(typing, "override"): # 3.12+ + override = typing.override +else: # <=3.11 + _F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any]) + + def override(arg: _F, /) -> _F: + """Indicate that a method is intended to override a method in a base class. + + Usage: + + class Base: + def method(self) -> None: + pass + + class Child(Base): + @override + def method(self) -> None: + super().method() + + When this decorator is applied to a method, the type checker will + validate that it overrides a method with the same name on a base class. + This helps prevent bugs that may occur when a base class is changed + without an equivalent change to a child class. + + There is no runtime checking of these properties. The decorator + sets the ``__override__`` attribute to ``True`` on the decorated object + to allow runtime introspection. + + See PEP 698 for details. + + """ + try: + arg.__override__ = True + except (AttributeError, TypeError): + # Skip the attribute silently if it is not writable. + # AttributeError happens if the object has __slots__ or a + # read-only property, TypeError if it's a builtin class. + pass + return arg + + +if hasattr(warnings, "deprecated"): + deprecated = warnings.deprecated +else: + _T = typing.TypeVar("_T") + + class deprecated: + """Indicate that a class, function or overload is deprecated. + + When this decorator is applied to an object, the type checker + will generate a diagnostic on usage of the deprecated object. + + Usage: + + @deprecated("Use B instead") + class A: + pass + + @deprecated("Use g instead") + def f(): + pass + + @overload + @deprecated("int support is deprecated") + def g(x: int) -> int: ... + @overload + def g(x: str) -> int: ... + + The warning specified by *category* will be emitted at runtime + on use of deprecated objects. For functions, that happens on calls; + for classes, on instantiation and on creation of subclasses. + If the *category* is ``None``, no warning is emitted at runtime. + The *stacklevel* determines where the + warning is emitted. If it is ``1`` (the default), the warning + is emitted at the direct caller of the deprecated object; if it + is higher, it is emitted further up the stack. + Static type checker behavior is not affected by the *category* + and *stacklevel* arguments. + + The deprecation message passed to the decorator is saved in the + ``__deprecated__`` attribute on the decorated object. + If applied to an overload, the decorator + must be after the ``@overload`` decorator for the attribute to + exist on the overload as returned by ``get_overloads()``. + + See PEP 702 for details. + + """ + def __init__( + self, + message: str, + /, + *, + category: typing.Optional[typing.Type[Warning]] = DeprecationWarning, + stacklevel: int = 1, + ) -> None: + if not isinstance(message, str): + raise TypeError( + "Expected an object of type str for 'message', not " + f"{type(message).__name__!r}" + ) + self.message = message + self.category = category + self.stacklevel = stacklevel + + def __call__(self, arg: _T, /) -> _T: + # Make sure the inner functions created below don't + # retain a reference to self. + msg = self.message + category = self.category + stacklevel = self.stacklevel + if category is None: + arg.__deprecated__ = msg + return arg + elif isinstance(arg, type): + import functools + from types import MethodType + + original_new = arg.__new__ + + @functools.wraps(original_new) + def __new__(cls, *args, **kwargs): + if cls is arg: + warnings.warn(msg, category=category, stacklevel=stacklevel + 1) + if original_new is not object.__new__: + return original_new(cls, *args, **kwargs) + # Mirrors a similar check in object.__new__. + elif cls.__init__ is object.__init__ and (args or kwargs): + raise TypeError(f"{cls.__name__}() takes no arguments") + else: + return original_new(cls) + + arg.__new__ = staticmethod(__new__) + + original_init_subclass = arg.__init_subclass__ + # We need slightly different behavior if __init_subclass__ + # is a bound method (likely if it was implemented in Python) + if isinstance(original_init_subclass, MethodType): + original_init_subclass = original_init_subclass.__func__ + + @functools.wraps(original_init_subclass) + def __init_subclass__(*args, **kwargs): + warnings.warn(msg, category=category, stacklevel=stacklevel + 1) + return original_init_subclass(*args, **kwargs) + + arg.__init_subclass__ = classmethod(__init_subclass__) + # Or otherwise, which likely means it's a builtin such as + # object's implementation of __init_subclass__. + else: + @functools.wraps(original_init_subclass) + def __init_subclass__(*args, **kwargs): + warnings.warn(msg, category=category, stacklevel=stacklevel + 1) + return original_init_subclass(*args, **kwargs) + + arg.__init_subclass__ = __init_subclass__ + + arg.__deprecated__ = __new__.__deprecated__ = msg + __init_subclass__.__deprecated__ = msg + return arg + elif callable(arg): + import functools + + @functools.wraps(arg) + def wrapper(*args, **kwargs): + warnings.warn(msg, category=category, stacklevel=stacklevel + 1) + return arg(*args, **kwargs) + + arg.__deprecated__ = wrapper.__deprecated__ = msg + return wrapper + else: + raise TypeError( + "@deprecated decorator with non-None category must be applied to " + f"a class or callable, not {arg!r}" + ) + + +# 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 + + +# Backport typing.NamedTuple as it exists in Python 3.13. +# In 3.11, the ability to define generic `NamedTuple`s was supported. +# This was explicitly disallowed in 3.9-3.10, and only half-worked in <=3.8. +# On 3.12, we added __orig_bases__ to call-based NamedTuples +# On 3.13, we deprecated kwargs-based NamedTuples +if sys.version_info >= (3, 13): + NamedTuple = typing.NamedTuple +else: + def _make_nmtuple(name, types, module, defaults=()): + fields = [n for n, t in types] + annotations = {n: typing._type_check(t, f"field {n} annotation must be a type") + for n, t in types} + nm_tpl = collections.namedtuple(name, fields, + defaults=defaults, module=module) + nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = annotations + # The `_field_types` attribute was removed in 3.9; + # in earlier versions, it is the same as the `__annotations__` attribute + if sys.version_info < (3, 9): + nm_tpl._field_types = annotations + return nm_tpl + + _prohibited_namedtuple_fields = typing._prohibited + _special_namedtuple_fields = frozenset({'__module__', '__name__', '__annotations__'}) + + class _NamedTupleMeta(type): + def __new__(cls, typename, bases, ns): + assert _NamedTuple in bases + for base in bases: + if base is not _NamedTuple and base is not typing.Generic: + raise TypeError( + 'can only inherit from a NamedTuple type and Generic') + bases = tuple(tuple if base is _NamedTuple else base for base in bases) + types = ns.get('__annotations__', {}) + default_names = [] + for field_name in types: + if field_name in ns: + default_names.append(field_name) + elif default_names: + raise TypeError(f"Non-default namedtuple field {field_name} " + f"cannot follow default field" + f"{'s' if len(default_names) > 1 else ''} " + f"{', '.join(default_names)}") + nm_tpl = _make_nmtuple( + typename, types.items(), + defaults=[ns[n] for n in default_names], + module=ns['__module__'] + ) + nm_tpl.__bases__ = bases + if typing.Generic in bases: + if hasattr(typing, '_generic_class_getitem'): # 3.12+ + nm_tpl.__class_getitem__ = classmethod(typing._generic_class_getitem) + else: + class_getitem = typing.Generic.__class_getitem__.__func__ + nm_tpl.__class_getitem__ = classmethod(class_getitem) + # update from user namespace without overriding special namedtuple attributes + for key, val in ns.items(): + if key in _prohibited_namedtuple_fields: + raise AttributeError("Cannot overwrite NamedTuple attribute " + key) + elif key not in _special_namedtuple_fields: + if key not in nm_tpl._fields: + setattr(nm_tpl, key, ns[key]) + try: + set_name = type(val).__set_name__ + except AttributeError: + pass + else: + try: + set_name(val, nm_tpl, key) + except BaseException as e: + msg = ( + f"Error calling __set_name__ on {type(val).__name__!r} " + f"instance {key!r} in {typename!r}" + ) + # BaseException.add_note() existed on py311, + # but the __set_name__ machinery didn't start + # using add_note() until py312. + # Making sure exceptions are raised in the same way + # as in "normal" classes seems most important here. + if sys.version_info >= (3, 12): + e.add_note(msg) + raise + else: + raise RuntimeError(msg) from e + + if typing.Generic in bases: + nm_tpl.__init_subclass__() + return nm_tpl + + _NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {}) + + def _namedtuple_mro_entries(bases): + assert NamedTuple in bases + return (_NamedTuple,) + + @_ensure_subclassable(_namedtuple_mro_entries) + def NamedTuple(typename, fields=_marker, /, **kwargs): + """Typed version of namedtuple. + + Usage:: + + class Employee(NamedTuple): + name: str + id: int + + This is equivalent to:: + + Employee = collections.namedtuple('Employee', ['name', 'id']) + + The resulting class has an extra __annotations__ attribute, giving a + dict that maps field names to types. (The field names are also in + the _fields attribute, which is part of the namedtuple API.) + An alternative equivalent functional syntax is also accepted:: + + Employee = NamedTuple('Employee', [('name', str), ('id', int)]) + """ + if fields is _marker: + if kwargs: + deprecated_thing = "Creating NamedTuple classes using keyword arguments" + deprecation_msg = ( + "{name} is deprecated and will be disallowed in Python {remove}. " + "Use the class-based or functional syntax instead." + ) + else: + deprecated_thing = "Failing to pass a value for the 'fields' parameter" + example = f"`{typename} = NamedTuple({typename!r}, [])`" + deprecation_msg = ( + "{name} is deprecated and will be disallowed in Python {remove}. " + "To create a NamedTuple class with 0 fields " + "using the functional syntax, " + "pass an empty list, e.g. " + ) + example + "." + elif fields is None: + if kwargs: + raise TypeError( + "Cannot pass `None` as the 'fields' parameter " + "and also specify fields using keyword arguments" + ) + else: + deprecated_thing = "Passing `None` as the 'fields' parameter" + example = f"`{typename} = NamedTuple({typename!r}, [])`" + deprecation_msg = ( + "{name} is deprecated and will be disallowed in Python {remove}. " + "To create a NamedTuple class with 0 fields " + "using the functional syntax, " + "pass an empty list, e.g. " + ) + example + "." + elif kwargs: + raise TypeError("Either list of fields or keywords" + " can be provided to NamedTuple, not both") + if fields is _marker or fields is None: + warnings.warn( + deprecation_msg.format(name=deprecated_thing, remove="3.15"), + DeprecationWarning, + stacklevel=2, + ) + fields = kwargs.items() + nt = _make_nmtuple(typename, fields, module=_caller()) + nt.__orig_bases__ = (NamedTuple,) + return nt + + +if hasattr(collections.abc, "Buffer"): + Buffer = collections.abc.Buffer +else: + class Buffer(abc.ABC): + """Base class for classes that implement the buffer protocol. + + The buffer protocol allows Python objects to expose a low-level + memory buffer interface. Before Python 3.12, it is not possible + to implement the buffer protocol in pure Python code, or even + to check whether a class implements the buffer protocol. In + Python 3.12 and higher, the ``__buffer__`` method allows access + to the buffer protocol from Python code, and the + ``collections.abc.Buffer`` ABC allows checking whether a class + implements the buffer protocol. + + To indicate support for the buffer protocol in earlier versions, + inherit from this ABC, either in a stub file or at runtime, + or use ABC registration. This ABC provides no methods, because + there is no Python-accessible methods shared by pre-3.12 buffer + classes. It is useful primarily for static checks. + + """ + + # As a courtesy, register the most common stdlib buffer classes. + Buffer.register(memoryview) + Buffer.register(bytearray) + Buffer.register(bytes) + + +# Backport of types.get_original_bases, available on 3.12+ in CPython +if hasattr(_types, "get_original_bases"): + get_original_bases = _types.get_original_bases +else: + def get_original_bases(cls, /): + """Return the class's "original" bases prior to modification by `__mro_entries__`. + + Examples:: + + from typing import TypeVar, Generic + from typing_extensions import NamedTuple, TypedDict + + T = TypeVar("T") + class Foo(Generic[T]): ... + class Bar(Foo[int], float): ... + class Baz(list[str]): ... + Eggs = NamedTuple("Eggs", [("a", int), ("b", str)]) + Spam = TypedDict("Spam", {"a": int, "b": str}) + + assert get_original_bases(Bar) == (Foo[int], float) + assert get_original_bases(Baz) == (list[str],) + assert get_original_bases(Eggs) == (NamedTuple,) + assert get_original_bases(Spam) == (TypedDict,) + assert get_original_bases(int) == (object,) + """ + try: + return cls.__dict__.get("__orig_bases__", cls.__bases__) + except AttributeError: + raise TypeError( + f'Expected an instance of type, not {type(cls).__name__!r}' + ) from None + + +# NewType is a class on Python 3.10+, making it pickleable +# The error message for subclassing instances of NewType was improved on 3.11+ +if sys.version_info >= (3, 11): + NewType = typing.NewType +else: + class NewType: + """NewType creates simple unique types with almost zero + runtime overhead. NewType(name, tp) is considered a subtype of tp + by static type checkers. At runtime, NewType(name, tp) returns + a dummy callable that simply returns its argument. Usage:: + UserId = NewType('UserId', int) + def name_by_id(user_id: UserId) -> str: + ... + UserId('user') # Fails type check + name_by_id(42) # Fails type check + name_by_id(UserId(42)) # OK + num = UserId(5) + 1 # type: int + """ + + def __call__(self, obj, /): + return obj + + def __init__(self, name, tp): + self.__qualname__ = name + if '.' in name: + name = name.rpartition('.')[-1] + self.__name__ = name + self.__supertype__ = tp + def_mod = _caller() + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + def __mro_entries__(self, bases): + # We defined __mro_entries__ to get a better error message + # if a user attempts to subclass a NewType instance. bpo-46170 + supercls_name = self.__name__ + + class Dummy: + def __init_subclass__(cls): + subcls_name = cls.__name__ + raise TypeError( + f"Cannot subclass an instance of NewType. " + f"Perhaps you were looking for: " + f"`{subcls_name} = NewType({subcls_name!r}, {supercls_name})`" + ) + + return (Dummy,) + + def __repr__(self): + return f'{self.__module__}.{self.__qualname__}' + + def __reduce__(self): + return self.__qualname__ + + if sys.version_info >= (3, 10): + # PEP 604 methods + # It doesn't make sense to have these methods on Python <3.10 + + def __or__(self, other): + return typing.Union[self, other] + + def __ror__(self, other): + return typing.Union[other, self] + + +if hasattr(typing, "TypeAliasType"): + TypeAliasType = typing.TypeAliasType +else: + def _is_unionable(obj): + """Corresponds to is_unionable() in unionobject.c in CPython.""" + return obj is None or isinstance(obj, ( + type, + _types.GenericAlias, + _types.UnionType, + TypeAliasType, + )) + + class TypeAliasType: + """Create named, parameterized type aliases. + + This provides a backport of the new `type` statement in Python 3.12: + + type ListOrSet[T] = list[T] | set[T] + + is equivalent to: + + T = TypeVar("T") + ListOrSet = TypeAliasType("ListOrSet", list[T] | set[T], type_params=(T,)) + + The name ListOrSet can then be used as an alias for the type it refers to. + + The type_params argument should contain all the type parameters used + in the value of the type alias. If the alias is not generic, this + argument is omitted. + + Static type checkers should only support type aliases declared using + TypeAliasType that follow these rules: + + - The first argument (the name) must be a string literal. + - The TypeAliasType instance must be immediately assigned to a variable + of the same name. (For example, 'X = TypeAliasType("Y", int)' is invalid, + as is 'X, Y = TypeAliasType("X", int), TypeAliasType("Y", int)'). + + """ + + def __init__(self, name: str, value, *, type_params=()): + if not isinstance(name, str): + raise TypeError("TypeAliasType name must be a string") + self.__value__ = value + self.__type_params__ = type_params + + parameters = [] + for type_param in type_params: + if isinstance(type_param, TypeVarTuple): + parameters.extend(type_param) + else: + parameters.append(type_param) + self.__parameters__ = tuple(parameters) + def_mod = _caller() + if def_mod != 'typing_extensions': + self.__module__ = def_mod + # Setting this attribute closes the TypeAliasType from further modification + self.__name__ = name + + def __setattr__(self, name: str, value: object, /) -> None: + if hasattr(self, "__name__"): + self._raise_attribute_error(name) + super().__setattr__(name, value) + + def __delattr__(self, name: str, /) -> Never: + self._raise_attribute_error(name) + + def _raise_attribute_error(self, name: str) -> Never: + # Match the Python 3.12 error messages exactly + if name == "__name__": + raise AttributeError("readonly attribute") + elif name in {"__value__", "__type_params__", "__parameters__", "__module__"}: + raise AttributeError( + f"attribute '{name}' of 'typing.TypeAliasType' objects " + "is not writable" + ) + else: + raise AttributeError( + f"'typing.TypeAliasType' object has no attribute '{name}'" + ) + + def __repr__(self) -> str: + return self.__name__ + + def __getitem__(self, parameters): + if not isinstance(parameters, tuple): + parameters = (parameters,) + parameters = [ + typing._type_check( + item, f'Subscripting {self.__name__} requires a type.' + ) + for item in parameters + ] + return typing._GenericAlias(self, tuple(parameters)) + + def __reduce__(self): + return self.__name__ + + def __init_subclass__(cls, *args, **kwargs): + raise TypeError( + "type 'typing_extensions.TypeAliasType' is not an acceptable base type" + ) + + # The presence of this method convinces typing._type_check + # that TypeAliasTypes are types. + def __call__(self): + raise TypeError("Type alias is not callable") + + if sys.version_info >= (3, 10): + def __or__(self, right): + # For forward compatibility with 3.12, reject Unions + # that are not accepted by the built-in Union. + if not _is_unionable(right): + return NotImplemented + return typing.Union[self, right] + + def __ror__(self, left): + if not _is_unionable(left): + return NotImplemented + return typing.Union[left, self] + + +if hasattr(typing, "is_protocol"): + is_protocol = typing.is_protocol + get_protocol_members = typing.get_protocol_members +else: + def is_protocol(tp: type, /) -> bool: + """Return True if the given type is a Protocol. + + Example:: + + >>> from typing_extensions import Protocol, is_protocol + >>> class P(Protocol): + ... def a(self) -> str: ... + ... b: int + >>> is_protocol(P) + True + >>> is_protocol(int) + False + """ + return ( + isinstance(tp, type) + and getattr(tp, '_is_protocol', False) + and tp is not Protocol + and tp is not typing.Protocol + ) + + def get_protocol_members(tp: type, /) -> typing.FrozenSet[str]: + """Return the set of members defined in a Protocol. + + Example:: + + >>> from typing_extensions import Protocol, get_protocol_members + >>> class P(Protocol): + ... def a(self) -> str: ... + ... b: int + >>> get_protocol_members(P) + frozenset({'a', 'b'}) + + Raise a TypeError for arguments that are not Protocols. + """ + if not is_protocol(tp): + raise TypeError(f'{tp!r} is not a Protocol') + if hasattr(tp, '__protocol_attrs__'): + return frozenset(tp.__protocol_attrs__) + return frozenset(_get_protocol_attrs(tp)) + + +if hasattr(typing, "Doc"): + Doc = typing.Doc +else: + class Doc: + """Define the documentation of a type annotation using ``Annotated``, to be + used in class attributes, function and method parameters, return values, + and variables. + + The value should be a positional-only string literal to allow static tools + like editors and documentation generators to use it. + + This complements docstrings. + + The string value passed is available in the attribute ``documentation``. + + Example:: + + >>> from typing_extensions import Annotated, Doc + >>> def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: ... + """ + def __init__(self, documentation: str, /) -> None: + self.documentation = documentation + + def __repr__(self) -> str: + return f"Doc({self.documentation!r})" + + def __hash__(self) -> int: + return hash(self.documentation) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Doc): + return NotImplemented + return self.documentation == other.documentation + + +# Aliases for items that have always been in typing. +# Explicitly assign these (rather than using `from typing import *` at the top), +# so that we get a CI error if one of these is deleted from typing.py +# in a future version of Python +AbstractSet = typing.AbstractSet +AnyStr = typing.AnyStr +BinaryIO = typing.BinaryIO +Callable = typing.Callable +Collection = typing.Collection +Container = typing.Container +Dict = typing.Dict +ForwardRef = typing.ForwardRef +FrozenSet = typing.FrozenSet +Generator = typing.Generator +Generic = typing.Generic +Hashable = typing.Hashable +IO = typing.IO +ItemsView = typing.ItemsView +Iterable = typing.Iterable +Iterator = typing.Iterator +KeysView = typing.KeysView +List = typing.List +Mapping = typing.Mapping +MappingView = typing.MappingView +Match = typing.Match +MutableMapping = typing.MutableMapping +MutableSequence = typing.MutableSequence +MutableSet = typing.MutableSet +Optional = typing.Optional +Pattern = typing.Pattern +Reversible = typing.Reversible +Sequence = typing.Sequence +Set = typing.Set +Sized = typing.Sized +TextIO = typing.TextIO +Tuple = typing.Tuple +Union = typing.Union +ValuesView = typing.ValuesView +cast = typing.cast +no_type_check = typing.no_type_check +no_type_check_decorator = typing.no_type_check_decorator diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..5be7adb --- /dev/null +++ b/tox.ini @@ -0,0 +1,7 @@ +[tox] +isolated_build = True +envlist = py38, py39, py310, py311, py312, py313 + +[testenv] +changedir = src +commands = python -m unittest discover