diff options
author | frankfeng <frankfeng@google.com> | 2022-04-13 19:34:52 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2022-04-13 19:34:52 +0000 |
commit | b0aec83bb2188fa7dec840f6c04dfa1e3da8b9a7 (patch) | |
tree | 04520eb0a7356d8ee8e8251ebcf2c6c317bbe65f | |
parent | f75a81beb5413e7e846e60de4e693a29ad65b714 (diff) | |
parent | 422e644ed74f09f648592f40218dc28a1ee566a1 (diff) | |
download | typing-b0aec83bb2188fa7dec840f6c04dfa1e3da8b9a7.tar.gz |
Merge remote-tracking branch 'aosp/upstream-master' into typing am: a90fb3c4d9 am: f8e1873b59 am: 2ef2783001 am: f6ac0eb79f am: 422e644ed7
Original change: https://android-review.googlesource.com/c/platform/external/python/typing/+/2063011
Change-Id: I61ae84f3df5929a667c96344f1c5ddd5f8856fe2
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
48 files changed, 8903 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..26fb670 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*.{py,pyi,rst,md,yml,yaml,toml,json}] +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space + +[*.{py,pyi,toml,json}] +indent_size = 4 + +[*.{yml,yaml}] +indent_size = 2 @@ -0,0 +1,15 @@ +[flake8] + +max-line-length = 90 +ignore = + # irrelevant plugins + B3, + DW12, + # code is sometimes better without this + E129, + # consistency with mypy + W504 +exclude = + # tests have more relaxed formatting rules + # and its own specific config in .flake8-tests + typing_extensions/src/test_typing_extensions.py, diff --git a/.flake8-tests b/.flake8-tests new file mode 100644 index 0000000..5a97fe8 --- /dev/null +++ b/.flake8-tests @@ -0,0 +1,28 @@ +# This configuration is specific to test_*.py; you need to invoke it +# by specifically naming this config, like this: +# +# $ flake8 --config=.flake8-tests [SOURCES] +# +# This will be possibly merged in the future. + +[flake8] +max-line-length = 100 +ignore = + # temporary ignores until we sort it out + B017, + E302, + E303, + E306, + E501, + E701, + E704, + F722, + F811, + F821, + F841, + W503, + # irrelevant plugins + B3, + DW12, + # consistency with mypy + W504 diff --git a/.github/ISSUE_TEMPLATE/documentation-issue.md b/.github/ISSUE_TEMPLATE/documentation-issue.md new file mode 100644 index 0000000..6122c8f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation-issue.md @@ -0,0 +1,10 @@ +--- +name: Documentation issue +about: Report a problem or suggest changes for the documentation at https://typing.readthedocs.io/ +title: '' +labels: 'topic: documentation' +assignees: '' + +--- + +<!-- Please describe the problem or your idea or suggestion below. Please include the URL of the page this issue is about, if applicable. --> diff --git a/.github/ISSUE_TEMPLATE/new-typing-feature.md b/.github/ISSUE_TEMPLATE/new-typing-feature.md new file mode 100644 index 0000000..733df29 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/new-typing-feature.md @@ -0,0 +1,10 @@ +--- +name: New typing feature +about: Suggest a new feature for Python's typing system +title: '' +labels: 'topic: feature' +assignees: '' + +--- + +<!-- Please describe the problem you face as well as your idea below, ideally using examples for both. --> diff --git a/.github/ISSUE_TEMPLATE/other-issue.md b/.github/ISSUE_TEMPLATE/other-issue.md new file mode 100644 index 0000000..484282c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/other-issue.md @@ -0,0 +1,10 @@ +--- +name: Other issue +about: Report any other issue +title: '' +labels: 'topic: other' +assignees: '' + +--- + +<!-- Please describe your issue below. --> diff --git a/.github/ISSUE_TEMPLATE/typing-extensions-issue.md b/.github/ISSUE_TEMPLATE/typing-extensions-issue.md new file mode 100644 index 0000000..226796e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/typing-extensions-issue.md @@ -0,0 +1,10 @@ +--- +name: typing-extensions issue +about: Report a problem or suggest changes for the typing-extensions library +title: '' +labels: 'topic: typing-extensions' +assignees: '' + +--- + +<!-- Please describe your problem or suggestion below, ideally using code examples. Please state which version of Python and typing-extensions you are using. --> diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml new file mode 100644 index 0000000..43711d0 --- /dev/null +++ b/.github/workflows/build-docs.yml @@ -0,0 +1,26 @@ +name: Build the documentation + +on: + pull_request: + +permissions: + contents: read + +jobs: + build: + + name: Build documentation + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Install dependencies + run: | + pip install --upgrade pip + pip install -r docs/requirements.txt + - name: Build the documentation + run: make -C docs html diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..302b2ca --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,64 @@ +name: Test and lint + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + tests: + name: Run tests + + strategy: + fail-fast: false + matrix: + # We try to test on the earliest available bugfix release of each + # Python version, because typing sometimes changed between bugfix releases. + # For available versions, see: + # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json + python-version: ["3.7", "3.7.1", "3.8", "3.8.0", "3.9", "3.9.0", "3.10", "3.10.0", "3.11-dev"] + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Test typing_extensions + continue-on-error: ${{ matrix.python-version == '3.11-dev' }} + run: | + # Be wary of running `pip install` here, since it becomes easy for us to + # accidentally pick up typing_extensions as installed by a dependency + cd typing_extensions/src + python -m unittest test_typing_extensions.py + + linting: + name: Lint + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3 + uses: actions/setup-python@v2 + with: + python-version: 3 + cache: "pip" + cache-dependency-path: "test-requirements.txt" + + - name: Install dependencies + run: | + pip install --upgrade pip + pip install -r test-requirements.txt + + - name: Lint implementation + run: flake8 + + - name: Lint tests + run: flake8 --config=.flake8-tests typing_extensions/src/test_typing_extensions.py diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml new file mode 100644 index 0000000..25f9586 --- /dev/null +++ b/.github/workflows/package.yml @@ -0,0 +1,71 @@ +name: Test packaging + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + wheel: + name: Test wheel install + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3 + + - name: Install pypa/build + run: | + # Be wary of running `pip install` here, since it becomes easy for us to + # accidentally pick up typing_extensions as installed by a dependency + python -m pip install --upgrade build + python -m pip list + + - name: Build and install wheel + run: | + cd typing_extensions + python -m build . + export path_to_file=$(find dist -type f -name "typing_extensions-*.whl") + echo "::notice::Installing wheel: $path_to_file" + pip install -vvv $path_to_file + python -m pip list + + - name: Attempt to import typing_extensions + run: python -c "import typing_extensions; print(typing_extensions.__all__)" + + sdist: + name: Test sdist install + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3 + + - name: Install pypa/build + run: | + # Be wary of running `pip install` here, since it becomes easy for us to + # accidentally pick up typing_extensions as installed by a dependency + python -m pip install --upgrade build + python -m pip list + + - name: Build and install sdist + run: | + cd typing_extensions + python -m build . + export path_to_file=$(find dist -type f -name "typing_extensions-*.tar.gz") + echo "::notice::Installing sdist: $path_to_file" + pip install -vvv $path_to_file + python -m pip list + + - name: Attempt to import typing_extensions + run: python -c "import typing_extensions; print(typing_extensions.__all__)" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0ad58f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +MANIFEST + +__pycache__/ +build/ +dist/ +tmp/ +venv*/ + +.cache/ +.idea/ +.tox/ +.venv*/ +.vscode/ + +*.swp +*.pyc +*.egg-info/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..095e826 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,55 @@ +Code in this repository should follow CPython's style guidelines and +contributors need to sign the PSF Contributor Agreement. + +# typing\_extensions + +The `typing_extensions` module provides a way to access new features from the standard +library `typing` module in older versions of Python. For example, Python 3.10 adds +`typing.TypeGuard`, but users of older versions of Python can use `typing_extensions` to +use `TypeGuard` in their code even if they are unable to upgrade to Python 3.10. + +If you contribute the runtime implementation of a new `typing` feature to CPython, you +are encouraged to also implement the feature in `typing_extensions`. Because the runtime +implementation of much of the infrastructure in the `typing` module has changed over +time, this may require different code for some older Python versions. + +`typing_extensions` may also include experimental features that are not yet part of the +standard library, so that users can experiment with them before they are added to the +standard library. Such features should ideally already be specified in a PEP or draft +PEP. + +`typing_extensions` supports Python versions 3.7 and up. + +# Versioning scheme + +Starting with version 4.0.0, `typing_extensions` uses +[Semantic Versioning](https://semver.org/). The major version is incremented for all +backwards-incompatible changes. + +# Workflow for PyPI releases + +- Ensure that GitHub Actions reports no errors. + +- Update the version number in `typing_extensions/pyproject.toml` and in + `typing_extensions/CHANGELOG`. + +- Make sure your environment is up to date + + - `git checkout master` + - `git pull` + - `python -m pip install --upgrade build twine` + +- Build the source and wheel distributions: + + - `cd typing_extensions` + - `rm -rf dist/` + - `python -m build .` + +- Install the built distributions locally and test (if you were using `tox`, you already + tested the source distribution). + +- Run `twine upload dist/*`. + +- Tag the release. The tag should be just the version number, e.g. `4.1.1`. + +- `git push --tags` @@ -0,0 +1,254 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://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 http://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 (now Zope +Corporation, see http://www.zope.com). In 2001, the Python Software +Foundation (PSF, see http://www.python.org/psf/) was formed, a +non-profit organization created specifically to own Python-related +Intellectual Property. Zope Corporation is a sponsoring member of +the PSF. + +All Python releases are Open Source (see http://www.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 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 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. diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..aae60ef --- /dev/null +++ b/METADATA @@ -0,0 +1,16 @@ +name: "typing" +description: + "Static Typing for Python" +third_party { + url { + type: HOMEPAGE + value: "https://github.com/python/typing" + } + url { + type: GIT + value: "https://github.com/python/typing" + } + version: "4.1.1" + last_upgrade_date { year: 2022 month: 4 day: 13 } + license_type: NOTICE +} diff --git a/MODULE_LICENSE_GPL b/MODULE_LICENSE_GPL new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_GPL @@ -0,0 +1,254 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://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 http://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 (now Zope +Corporation, see http://www.zope.com). In 2001, the Python Software +Foundation (PSF, see http://www.python.org/psf/) was formed, a +non-profit organization created specifically to own Python-related +Intellectual Property. Zope Corporation is a sponsoring member of +the PSF. + +All Python releases are Open Source (see http://www.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 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 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. @@ -0,0 +1,8 @@ +# Android side engprod team +jdesprez@google.com +frankfeng@google.com +murj@google.com + +# Mobly team - use for mobly bugs +angli@google.com +lancefluger@google.com diff --git a/README.md b/README.md new file mode 100644 index 0000000..64b9118 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +[![Chat at https://gitter.im/python/typing](https://badges.gitter.im/python/typing.svg)](https://gitter.im/python/typing?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +Static Typing for Python +======================== + +Documentation and Support +------------------------- + +The documentation for Python's static typing can be found at +[typing.readthedocs.io](https://typing.readthedocs.io/). You can get +help either in our [support forum](https://github.com/python/typing/discussions) or +chat with us on [Gitter](https://gitter.im/python/typing). + +Improvements to the type system should be discussed on the +[typing-sig](https://mail.python.org/mailman3/lists/typing-sig.python.org/) +mailing list, although the [issues](https://github.com/python/typing/issues) in this +repository contain some historic discussions. + +Repository Content +------------------ + +This GitHub repository is used for several things: + +- The `typing_extensions` module lives in the + [typing\_extensions](./typing_extensions) directory. + +- The documentation at [typing.readthedocs.io](https://typing.readthedocs.io/) + is maintained in the [docs directory](./docs). + +- A [discussion forum](https://github.com/python/typing/discussions) for typing-related user + help is hosted here. + +Historically, this repository hosted a backport of the +[`typing` module](https://docs.python.org/3/library/typing.html) for older +Python versions. The last released version, supporting Python 2.7 and 3.4, +is [available at PyPI](https://pypi.org/project/typing/). + +Workflow +-------- + +See [CONTRIBUTING.md](/CONTRIBUTING.md) for more. diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..4f7c95a --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,46 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SOURCEDIR = . +SOURCES = +BUILDDIR = _build +PYTHON = python3 +VENVDIR = ./venv +SPHINXBUILD = PATH=$(VENVDIR)/bin:$$PATH sphinx-build + +ALLSPHINXOPTS = -b $(BUILDER) -d build/doctrees $(SPHINXOPTS) . build/$(BUILDER) $(SOURCES) + +.PHONY: help clean build html text venv Makefile + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +clean: + -rm -rf build/* $(VENVDIR)/* + +build: + -mkdir -p build + $(SPHINXBUILD) $(ALLSPHINXOPTS) + @echo + +html: BUILDER = html +html: build + @echo "Build finished. The HTML pages are in build/html." + +text: BUILDER = text +text: build + @echo "Build finished; the text files are in build/text." + +venv: + $(PYTHON) -m venv $(VENVDIR) + $(VENVDIR)/bin/python3 -m pip install -U pip setuptools + $(VENVDIR)/bin/python3 -m pip install -r requirements.txt + @echo "The venv has been created in the $(VENVDIR) directory" + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/README.rst b/docs/README.rst new file mode 100644 index 0000000..6b431c8 --- /dev/null +++ b/docs/README.rst @@ -0,0 +1,50 @@ +Python Typing Documentation +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Reading the docs +================= + +The live documentation for Python's static typing can be found at +`typing.readthedocs.io <https://typing.readthedocs.io/>`_. + +Building the docs +================= + +The documentation is built with tools which are not included in this +tree but are maintained separately and are available from +`PyPI <https://pypi.org/>`_. + +* `Sphinx <https://pypi.org/project/Sphinx/>`_ +* `python-docs-theme <https://pypi.org/project/python-docs-theme/>`_ + +The easiest way to install these tools is to create a virtual environment and +install the tools into there. + +Using make +---------- + +To get started on UNIX, you can create a virtual environment with the command :: + + make venv + +That will install all the tools necessary to build the documentation. Assuming +the virtual environment was created in the ``venv`` directory (the default; +configurable with the VENVDIR variable), you can run the following command to +build the HTML output files:: + + make html + +By default, if the virtual environment is not created, the Makefile will +look for instances of sphinxbuild and blurb installed on your process PATH +(configurable with the SPHINXBUILD and BLURB variables). + +Available make targets are: + +* "clean", which removes all build files. + +* "venv", which creates a virtual environment with all necessary tools + installed. + +* "html", which builds standalone HTML files for offline viewing. + +* "text", which builds a plain text file for each source file. diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..f16401b --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,55 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'typing' +copyright = '2021, The Python Typing Team' +author = 'The Python Typing Team' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'venv', 'README.rst'] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'python_docs_theme' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = [] + +extensions = ['sphinx.ext.intersphinx'] +intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..b5fe268 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,99 @@ +************************* +Static Typing with Python +************************* + +.. Introduction +.. ============ +.. +.. .. toctree:: +.. :maxdepth: 2 +.. +.. source/introduction + +Guides +====== + +.. toctree:: + :maxdepth: 2 + + source/guides + +Reference +========= + +.. toctree:: + :maxdepth: 2 + + source/reference + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` + +Discussions and Support +======================= + +* `User help forum <https://github.com/python/typing/discussions>`_ +* `User chat on Gitter <http://gitter.im/python/typing>`_ +* `Developer mailing list <https://mail.python.org/archives/list/typing-sig@python.org/>`_ + +Typing-related Tools +==================== + +Type Checkers +------------- + +* `mypy <http://mypy-lang.org/>`_, the reference implementation for type + checkers. Supports Python 2 and 3. +* `pyre <https://pyre-check.org/>`_, written in OCaml and optimized for + performance. Supports Python 3 only. +* `pyright <https://github.com/microsoft/pyright>`_, a type checker that + emphasizes speed. Supports Python 3 only. +* `pytype <https://google.github.io/pytype/>`_, checks and infers types for + unannotated code. Supports Python 2 and 3. + +Development Environments +------------------------ + +* `PyCharm <https://www.jetbrains.com/pycharm/>`_, an IDE that supports + type stubs both for type checking and code completion. +* `Visual Studio Code <https://code.visualstudio.com/>`_, a code editor that + supports type checking using mypy, pyright, or the + `Pylance <https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance>`_ + extension. + +Linters and Formatters +---------------------- + +* `black <https://black.readthedocs.io/>`_, a code formatter with support for + type stub files. +* `flake8-pyi <https://github.com/ambv/flake8-pyi>`_, a plugin for the + `flake8 <https://flake8.pycqa.org/>`_ linter that adds support for type + stubs. + +Typing PEPs +=========== + +* `PEP 483 <https://www.python.org/dev/peps/pep-0483/>`_, background on type hints +* `PEP 484 <https://www.python.org/dev/peps/pep-0484/>`_, type hints +* `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_, variable annotations and ``ClassVar`` +* `PEP 544 <https://www.python.org/dev/peps/pep-0544/>`_, ``Protocol`` +* `PEP 561 <https://www.python.org/dev/peps/pep-0561/>`_, distributing typed packages +* `PEP 563 <https://www.python.org/dev/peps/pep-0563/>`_, ``from __future__ import annotations`` +* `PEP 585 <https://www.python.org/dev/peps/pep-0585/>`_, subscriptable generics in the standard library +* `PEP 586 <https://www.python.org/dev/peps/pep-0586/>`_, ``Literal`` +* `PEP 589 <https://www.python.org/dev/peps/pep-0589/>`_, ``TypedDict`` +* `PEP 591 <https://www.python.org/dev/peps/pep-0591/>`_, ``Final`` +* `PEP 593 <https://www.python.org/dev/peps/pep-0593/>`_, ``Annotated`` +* `PEP 604 <https://www.python.org/dev/peps/pep-0604/>`_, union syntax with ``|`` +* `PEP 612 <https://www.python.org/dev/peps/pep-0612/>`_, ``ParamSpec`` +* `PEP 613 <https://www.python.org/dev/peps/pep-0613/>`_, ``TypeAlias`` +* `PEP 646 <https://www.python.org/dev/peps/pep-0646/>`_, variadic generics and ``TypeVarTuple`` +* `PEP 647 <https://www.python.org/dev/peps/pep-0647/>`_, ``TypeGuard`` +* `PEP 655 <https://www.python.org/dev/peps/pep-0655/>`_, ``Required`` and ``NotRequired`` +* `PEP 673 <https://www.python.org/dev/peps/pep-0673/>`_, ``Self`` +* `PEP 675 <https://www.python.org/dev/peps/pep-0675/>`_, ``LiteralString`` +* `PEP 677 <https://www.python.org/dev/peps/pep-0677/>`_ (rejected), callable type syntax +* `PEP 681 <https://www.python.org/dev/peps/pep-0681/>`_ (draft), ``@dataclass_transform()`` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..2119f51 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=.
+set BUILDDIR=_build
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..4aa5fce --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,10 @@ +# Requirements to build the Python documentation + +# Sphinx version is pinned so that new versions that introduce new warnings +# won't suddenly cause build failures. Updating the version is fine as long +# as no warnings are raised by doing so. +sphinx==4.2.0 + +# The theme used by the documentation is stored separately, so we need +# to install that as well. +python-docs-theme diff --git a/docs/source/annotations.rst b/docs/source/annotations.rst new file mode 100644 index 0000000..ea5c471 --- /dev/null +++ b/docs/source/annotations.rst @@ -0,0 +1,68 @@ +**************** +Type Annotations +**************** + +Functions +========= + +Parameters +---------- + +Asynchronous Functions +---------------------- + +Generators +---------- + +Lambdas +------- + + +Classes +======= + +Overloads and Overrides +----------------------- + +Instance vs. Class Attributes +----------------------------- + +Final attributes +---------------- + +Abstract base classes +--------------------- + + +Globals +======= + + +Attributes +========== + + +Locals +====== + +Empty Containers +---------------- + + +Runtime Considerations +====================== + +Comment vs. Inline Support +-------------------------- + +Forward References +------------------ + +Generic types that don't implement `__getitem__` +------------------------------------------------ + +Conditioning on static type checking +------------------------------------ + +from `__future__` import annotations +------------------------------------ diff --git a/docs/source/basics.rst b/docs/source/basics.rst new file mode 100644 index 0000000..9b2bf52 --- /dev/null +++ b/docs/source/basics.rst @@ -0,0 +1,3 @@ +********** +The Basics +********** diff --git a/docs/source/dependencies.rst b/docs/source/dependencies.rst new file mode 100644 index 0000000..b16fa63 --- /dev/null +++ b/docs/source/dependencies.rst @@ -0,0 +1,12 @@ +************************** +Libraries and Dependencies +************************** + +:doc:`libraries` + +Typeshed +======== + + +Placeholder Stubs +================= diff --git a/docs/source/faq.rst b/docs/source/faq.rst new file mode 100644 index 0000000..906e277 --- /dev/null +++ b/docs/source/faq.rst @@ -0,0 +1,23 @@ +************************** +Frequently Asked Questions +************************** + + +Why Types? +========== + + +Unsupported Python Features +=========================== + + +Common Errors +============= + + +Typing PEPs +=========== + + +Relevant Talks and Resources +============================ diff --git a/docs/source/guides.rst b/docs/source/guides.rst new file mode 100644 index 0000000..c9af381 --- /dev/null +++ b/docs/source/guides.rst @@ -0,0 +1,10 @@ +****************** +Type System Guides +****************** + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + libraries + unreachable diff --git a/docs/source/inference.rst b/docs/source/inference.rst new file mode 100644 index 0000000..291dec4 --- /dev/null +++ b/docs/source/inference.rst @@ -0,0 +1,28 @@ +************** +Type Inference +************** + +Rules for local inference +========================= + +Explicit vs. Implicit Local Types +--------------------------------- + +Changing Types +============== + +Asserts +------- + +Casts +----- + +Type Guards +----------- + + +Protocols and Duck Typing +========================= + +Callback Protocols +------------------ diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst new file mode 100644 index 0000000..fb97355 --- /dev/null +++ b/docs/source/introduction.rst @@ -0,0 +1,41 @@ +******************************* +Introduction to Types in Python +******************************* + + +Background +========== + +How to read type annotations +---------------------------- + +When and why types are useful +----------------------------- + + +Gradual Typing: Static Types in a Dynamic Language +================================================== + +Opt-in type checking +-------------------- + +Type stubs +---------- + +:doc:`stubs` + +Strategies for increasing coverage +---------------------------------- + + +Getting Started +=============== + +Python type checkers +-------------------- + +How to annotate an existing codebase +------------------------------------ + +Typeshed +-------- diff --git a/docs/source/libraries.rst b/docs/source/libraries.rst new file mode 100644 index 0000000..96e5370 --- /dev/null +++ b/docs/source/libraries.rst @@ -0,0 +1,602 @@ +.. _libraries: + +*********************** +Typing Python Libraries +*********************** + +Much of Python’s popularity can be attributed to the rich collection of +Python libraries available to developers. Authors of these libraries +play an important role in improving the experience for Python +developers. This document provides some recommendations and guidance for +Python library authors. + +These recommendations are intended to provide the following benefits: + +1. Consumers of libraries should have a great coding experience with + fast and accurate completion suggestions, class and function + documentation, signature help (including parameter default values), + hover text, and auto-imports. This should happen by default without + needing to download extra packages and without any special + configuration. These features should be consistent across the Python + ecosystem regardless of a developer’s choice of editor, IDE, notebook + environment, etc. +2. Consumers of libraries should be able to rely on complete and + accurate type information so static type checkers can detect and + report type inconsistencies and other violations of the interface + contract. +3. Library authors should be able to specify a well-defined interface + contract that is enforced by tools. This allows a library + implementation to evolve and improve without breaking consumers of + the library. +4. Library authors should have the benefits of static type checking to + produce high-quality, bug-free implementations. + +Inlined Type Annotations and Type Stubs +======================================= + +`PEP 561 <https://www.python.org/dev/peps/pep-0561/>`__ documents +several ways type information can be delivered for a library: inlined +type annotations, type stub files included in the package, a separate +companion type stub package, and type stubs in the typeshed repository. +Some of these options fall short on delivering the benefits above. We +therefore provide the following more specific guidance to library +authors. + +.. note:: + All libraries should include inlined type annotations for the + functions, classes, methods, and constants that comprise the public + interface for the library. + +Inlined type annotations should be included directly within the source +code that ships with the package. Of the options listed in PEP 561, +inlined type annotations offer the most benefits. They typically require +the least effort to add and maintain, they are always consistent with +the implementation, and docstrings and default parameter values are +readily available, allowing language servers to enhance the development +experience. + +There are cases where inlined type annotations are not possible — most +notably when a library’s exposed functionality is implemented in a +language other than Python. + +.. note:: + Libraries that expose symbols implemented in languages other than + Python should include stub (``.pyi``) files that describe the types for + those symbols. These stubs should also contain docstrings and default + parameter values. + +In many existing type stubs (such as those found in typeshed), default +parameter values are replaced with with ``...`` and all docstrings are +removed. We recommend that default values and docstrings remain within +the type stub file so language servers can display this information to +developers. + +Library Interface +================= + +`PEP 561 <https://www.python.org/dev/peps/pep-0561/>`__ indicates that a +``py.typed`` marker file must be included in the package if the author +wishes to support type checking of their code. + +If a ``py.typed`` module is present, a type checker will treat all modules +within that package (i.e. all files that end in ``.py`` or ``.pyi``) as +importable unless the file name begins with an underscore. These modules +comprise the supported interface for the library. + +Each module exposes a set of symbols. Some of these symbols are +considered "private” — implementation details that are not part of the +library’s interface. Type checkers can use the following rules +to determine which symbols are visible outside of the package. + +- Symbols whose names begin with an underscore (but are not dunder + names) are considered private. +- Imported symbols are considered private by default. If they use the + ``import A as A`` (a redundant module alias), ``from X import A as A`` (a + redundant symbol alias), or ``from . import A`` forms, symbol ``A`` is + not private unless the name begins with an underscore. If a file + ``__init__.py`` uses form ``from .A import X``, symbol ``A`` is treated + likewise. If a wildcard import (of the form ``from X import *``) is + used, all symbols referenced by the wildcard are not private. +- A module can expose an ``__all__`` symbol at the module level that + provides a list of names that are considered part of the interface. + This overrides all other rules above, allowing imported symbols or + symbols whose names begin with an underscore to be included in the + interface. +- Local variables within a function (including nested functions) are + always considered private. + +The following idioms are supported for defining the values contained +within ``__all__``. These restrictions allow type checkers to statically +determine the value of ``__all__``. + +- ``__all__ = ('a', b')`` +- ``__all__ = ['a', b']`` +- ``__all__ += ['a', b']`` +- ``__all__ += submodule.__all__`` +- ``__all__.extend(['a', b'])`` +- ``__all__.extend(submodule.__all__)`` +- ``__all__.append('a')`` +- ``__all__.remove('a')`` + +Type Completeness +================= + +A “py.typed” library should aim to be type complete so that type +checking and inspection can work to their full extent. Here we say that a +library is “type complete” if all of the symbols +that comprise its interface have type annotations that refer to types +that are fully known. Private symbols are exempt. + +The following are best practice recommendations for how to define “type complete”: + +Classes: + +- All class variables, instance variables, and methods that are + “visible” (not overridden) are annotated and refer to known types +- If a class is a subclass of a generic class, type arguments are + provided for each generic type parameter, and these type arguments + are known types + +Functions and Methods: + +- All input parameters have type annotations that refer to known types +- The return parameter is annotated and refers to a known type +- The result of applying one or more decorators results in a known type + +Type Aliases: + +- All of the types referenced by the type alias are known + +Variables: + +- All variables have type annotations that refer to known types + +Type annotations can be omitted in a few specific cases where the type +is obvious from the context: + +- Constants that are assigned simple literal values + (e.g. ``RED = '#F00'`` or ``MAX_TIMEOUT = 50`` or + ``room_temperature: Final = 20``). A constant is a symbol that is + assigned only once and is either annotated with ``Final`` or is named + in all-caps. A constant that is not assigned a simple literal value + requires explicit annotations, preferably with a ``Final`` annotation + (e.g. ``WOODWINDS: Final[List[str]] = ['Oboe', 'Bassoon']``). +- Enum values within an Enum class do not require annotations because + they take on the type of the Enum class. +- Type aliases do not require annotations. A type alias is a symbol + that is defined at a module level with a single assignment where the + assigned value is an instantiable type, as opposed to a class + instance + (e.g. ``Foo = Callable[[Literal["a", "b"]], Union[int, str]]`` or + ``Bar = Optional[MyGenericClass[int]]``). +- The “self” parameter in an instance method and the “cls” parameter in + a class method do not require an explicit annotation. +- The return type for an ``__init__`` method does not need to be + specified, since it is always ``None``. +- The following module-level symbols do not require type annotations: + ``__all__``,\ ``__author__``, ``__copyright__``, ``__email__``, + ``__license__``, ``__title__``, ``__uri__``, ``__version__``. +- The following class-level symbols do not require type annotations: + ``__class__``, ``__dict__``, ``__doc__``, ``__module__``, + ``__slots__``. + +Examples of known and unknown types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: python + + + # Variable with unknown type + a = [3, 4, 5] + + # Variable with known type + a: List[int] = [3, 4, 5] + + # Type alias with partially unknown type (because type + # arguments are missing for list and dict) + DictOrList = Union[list, dict] + + # Type alias with known type + DictOrList = Union[List[Any], Dict[str, Any]] + + # Generic type alias with known type + _T = TypeVar("_T") + DictOrList = Union[List[_T], Dict[str, _T]] + + # Function with known type + def func(a: Optional[int], b: Dict[str, float] = {}) -> None: + pass + + # Function with partially unknown type (because type annotations + # are missing for input parameters and return type) + def func(a, b): + pass + + # Function with partially unknown type (because of missing + # type args on Dict) + def func(a: int, b: Dict) -> None: + pass + + # Function with partially unknown type (because return type + # annotation is missing) + def func(a: int, b: Dict[str, float]): + pass + + # Decorator with partially unknown type (because type annotations + # are missing for input parameters and return type) + def my_decorator(func): + return func + + # Function with partially unknown type (because type is obscured + # by untyped decorator) + @my_decorator + def func(a: int) -> str: + pass + + + # Class with known type + class MyClass: + height: float = 2.0 + + def __init__(self, name: str, age: int): + self.age: int = age + + @property + def name(self) -> str: + ... + + # Class with partially unknown type + class MyClass: + # Missing type annotation for class variable + height = 2.0 + + # Missing input parameter annotations + def __init__(self, name, age): + # Missing type annotation for instance variable + self.age = age + + # Missing return type annotation + @property + def name(self): + ... + + # Class with partially unknown type + class BaseClass: + # Missing type annotation + height = 2.0 + + # Missing type annotation + def get_stuff(self): + ... + + # Class with known type (because it overrides all symbols + # exposed by BaseClass that have incomplete types) + class DerivedClass(BaseClass): + height: float + + def get_stuff(self) -> str: + ... + + # Class with partially unknown type because base class + # (dict) is generic, and type arguments are not specified. + class DictSubclass(dict): + pass + +Best Practices for Inlined Types +================================ + +Wide vs. Narrow Types +~~~~~~~~~~~~~~~~~~~~~ + +In type theory, when comparing two types that are related to each other, +the “wider” type is the one that is more general, and the “narrower” +type is more specific. For example, ``Sequence[str]`` is a wider type +than ``List[str]`` because all ``List`` objects are also ``Sequence`` +objects, but the converse is not true. A subclass is narrower than a +class it derives from. A union of types is wider than the individual +types that comprise the union. + +In general, a function input parameter should be annotated with the +widest possible type supported by the implementation. For example, if +the implementation requires the caller to provide an iterable collection +of strings, the parameter should be annotated as ``Iterable[str]``, not +as ``List[str]``. The latter type is narrower than necessary, so if a +user attempts to pass a tuple of strings (which is supported by the +implementation), a type checker will complain about a type +incompatibility. + +As a specific application of the “use the widest type possible” rule, +libraries should generally use immutable forms of container types +instead of mutable forms (unless the function needs to modify the +container). Use ``Sequence`` rather than ``List``, ``Mapping`` rather +than ``Dict``, etc. Immutable containers allow for more flexibility +because their type parameters are covariant rather than invariant. A +parameter that is typed as ``Sequence[Union[str, int]]`` can accept a +``List[int]``, ``Sequence[str]``, and a ``Sequence[int]``. But a +parameter typed as ``List[Union[str, int]]`` is much more restrictive +and accepts only a ``List[Union[str, int]]``. + +Overloads +~~~~~~~~~ + +If a function or method can return multiple different types and those +types can be determined based on the presence or types of certain +parameters, use the ``@overload`` mechanism defined in `PEP +484 <https://www.python.org/dev/peps/pep-0484/#id45>`__. When overloads +are used within a “.py” file, they must appear prior to the function +implementation, which should not have an ``@overload`` decorator. + +Keyword-only Parameters +~~~~~~~~~~~~~~~~~~~~~~~ + +If a function or method is intended to take parameters that are +specified only by name, use the keyword-only separator (``*``). + +.. code:: python + + def create_user(age: int, *, dob: Optional[date] = None): + ... + +Annotating Decorators +~~~~~~~~~~~~~~~~~~~~~ + +Decorators modify the behavior of a class or a function. Providing +annotations for decorators is straightforward if the decorator retains +the original signature of the decorated function. + +.. code:: python + + _F = TypeVar("_F", bound=Callable[..., Any]) + + def simple_decorator(_func: _F) -> _F: + """ + Simple decorators are invoked without parentheses like this: + @simple_decorator + def my_function(): ... + """ + ... + + def complex_decorator(*, mode: str) -> Callable[[_F], _F]: + """ + Complex decorators are invoked with arguments like this: + @complex_decorator(mode="easy") + def my_function(): ... + """ + ... + +Decorators that mutate the signature of the decorated function present +challenges for type annotations. The ``ParamSpec`` and ``Concatenate`` +mechanisms described in `PEP +612 <https://www.python.org/dev/peps/pep-0612/>`__ provide some help +here, but these are available only in Python 3.10 and newer. More +complex signature mutations may require type annotations that erase the +original signature, thus blinding type checkers and other tools that +provide signature assistance. As such, library authors are discouraged +from creating decorators that mutate function signatures in this manner. + +Generic Classes and Functions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Classes and functions that can operate in a generic manner on various +types should declare themselves as generic using the mechanisms +described in `PEP 484 <https://www.python.org/dev/peps/pep-0484/>`__. +This includes the use of ``TypeVar`` symbols. Typically, a ``TypeVar`` +should be private to the file that declares it, and should therefore +begin with an underscore. + +Type Aliases +~~~~~~~~~~~~ + +Type aliases are symbols that refer to other types. Generic type aliases +(those that refer to unspecialized generic classes) are supported by +most type checkers. + +`PEP 613 <https://www.python.org/dev/peps/pep-0613/>`__ provides a way +to explicitly designate a symbol as a type alias using the new TypeAlias +annotation. + +.. code:: python + + # Simple type alias + FamilyPet = Union[Cat, Dog, GoldFish] + + # Generic type alias + ListOrTuple = Union[List[_T], Tuple[_T, ...]] + + # Recursive type alias + TreeNode = Union[LeafNode, List["TreeNode"]] + + # Explicit type alias using PEP 613 syntax + StrOrInt: TypeAlias = Union[str, int] + +Abstract Classes and Methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Classes that must be subclassed should derive from ``ABC``, and methods +or properties that must be overridden should be decorated with the +``@abstractmethod`` decorator. This allows type checkers to validate +that the required methods have been overridden and provide developers +with useful error messages when they are not. It is customary to +implement an abstract method by raising a ``NotImplementedError`` +exception. + +.. code:: python + + from abc import ABC, abstractmethod + + class Hashable(ABC): + @property + @abstractmethod + def hash_value(self) -> int: + """Subclasses must override""" + raise NotImplementedError() + + @abstractmethod + def print(self) -> str: + """Subclasses must override""" + raise NotImplementedError() + +Final Classes and Methods +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Classes that are not intended to be subclassed should be decorated as +``@final`` as described in `PEP +591 <https://www.python.org/dev/peps/pep-0591/>`__. The same decorator +can also be used to specify methods that cannot be overridden by +subclasses. + +Literals +~~~~~~~~ + +Type annotations should make use of the Literal type where appropriate, +as described in `PEP 586 <https://www.python.org/dev/peps/pep-0586/>`__. +Literals allow for more type specificity than their non-literal +counterparts. + +Constants +~~~~~~~~~ + +Constant values (those that are read-only) can be specified using the +Final annotation as described in `PEP +591 <https://www.python.org/dev/peps/pep-0591/>`__. + +Type checkers will also typically treat variables that are named using +all upper-case characters as constants. + +In both cases, it is OK to omit the declared type of a constant if it is +assigned a literal str, int, float, bool or None value. In such cases, +the type inference rules are clear and unambiguous, and adding a literal +type annotation would be redundant. + +.. code:: python + + # All-caps constant with inferred type + COLOR_FORMAT_RGB = "rgb" + + # All-caps constant with explicit type + COLOR_FORMAT_RGB: Literal["rgb"] = "rgb" + LATEST_VERSION: Tuple[int, int] = (4, 5) + + # Final variable with inferred type + ColorFormatRgb: Final = "rgb" + + # Final variable with explicit type + ColorFormatRgb: Final[Literal["rgb"]] = "rgb" + LATEST_VERSION: Final[Tuple[int, int]] = (4, 5) + +Typed Dictionaries, Data Classes, and Named Tuples +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If your library runs only on newer versions of Python, you are +encouraged to use some of the new type-friendly classes. + +NamedTuple (described in `PEP +484 <https://www.python.org/dev/peps/pep-0484/>`__) is preferred over +namedtuple. + +Data classes (described in `PEP +557 <https://www.python.org/dev/peps/pep-0557/>`__) is preferred over +untyped dictionaries. + +TypedDict (described in `PEP +589 <https://www.python.org/dev/peps/pep-0589/>`__) is preferred over +untyped dictionaries. + +Compatibility with Older Python Versions +======================================== + +Each new version of Python from 3.5 onward has introduced new typing +constructs. This presents a challenge for library authors who want to +maintain runtime compatibility with older versions of Python. This +section documents several techniques that can be used to add types while +maintaining backward compatibility. + +Quoted Annotations +~~~~~~~~~~~~~~~~~~ + +Type annotations for variables, parameters, and return types can be +placed in quotes. The Python interpreter will then ignore them, whereas +a type checker will interpret them as type annotations. + +.. code:: python + + # Older versions of Python do not support subscripting + # for the OrderedDict type, so the annotation must be + # enclosed in quotes. + def get_config(self) -> "OrderedDict[str, str]": + return self._config + +Type Comment Annotations +~~~~~~~~~~~~~~~~~~~~~~~~ + +Python 3.0 introduced syntax for parameter and return type annotations, +as specified in `PEP 484 <https://www.python.org/dev/peps/pep-0484/>`__. +Python 3.6 introduced support for variable type annotations, as +specified in `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`__. + +If you need to support older versions of Python, type annotations can +still be provided as “type comments”. These comments take the form # +type: . + +.. code:: python + + class Foo: + # Variable type comments go at the end of the line + # where the variable is assigned. + timeout = None # type: Optional[int] + + # Function type comments can be specified on the + # line after the function signature. + def send_message(self, name, length): + # type: (str, int) -> None + ... + + # Function type comments can also specify the type + # of each parameter on its own line. + def receive_message( + self, + name, # type: str + length # type: int + ): + # type: () -> Message + ... + +typing_extensions +~~~~~~~~~~~~~~~~~ + +New type features that require runtime support are typically included in +the stdlib ``typing`` module. Where possible, these new features are +back-ported to a runtime library called ``typing_extensions`` that works +with older Python runtimes. + +TYPE_CHECKING +~~~~~~~~~~~~~ + +The ``typing`` module exposes a variable called ``TYPE_CHECKING`` which +has a value of False within the Python runtime but a value of True when +the type checker is performing its analysis. This allows type checking +statements to be conditionalized. + +Care should be taken when using ``TYPE_CHECKING`` because behavioral +changes between type checking and runtime could mask problems that the +type checker would otherwise catch. + +Non-Standard Type Behaviors +=========================== + +Type annotations provide a way to annotate typical type behaviors, but +some classes implement specialized, non-standard behaviors that cannot +be described using standard type annotations. For now, such types need +to be annotated as Any, which is unfortunate because the benefits of +static typing are lost. + +Docstrings +========== + +Docstrings should be provided for all classes, functions, and methods in +the interface. They should be formatted according to `PEP +257 <https://www.python.org/dev/peps/pep-0257/>`__. + +There is currently no single agreed-upon standard for function and +method docstrings, but several common variants have emerged. We +recommend using one of these variants. diff --git a/docs/source/quality.rst b/docs/source/quality.rst new file mode 100644 index 0000000..328550c --- /dev/null +++ b/docs/source/quality.rst @@ -0,0 +1,200 @@ +.. _tools: + +******************************************** +Testing and Ensuring Type Annotation Quality +******************************************** + +Testing Annotation Accuracy +=========================== + +When creating a package with type annotations, authors may want to validate +that the annotations they publish meet their expectations. +This is especially important for library authors, for whom the published +annotations are part of the public interface to their package. + +There are several approaches to this problem, and this document will show +a few of them. + +.. note:: + + For simplicity, we will assume that type-checking is done with ``mypy``. + Many of these strategies can be applied to other type-checkers as well. + +Testing Using ``mypy --warn-unused-ignores`` +-------------------------------------------- + +Clever use of ``--warn-unused-ignores`` can be used to check that certain +expressions are or are not well-typed. + +The idea is to write normal python files which contain valid expressions along +with invalid expressions annotated with ``type: ignore`` comments. When +``mypy --warn-unused-ignores`` is run on these files, it should pass. +A directory of test files, ``typing_tests/``, can be maintained. + +This strategy does not offer strong guarantees about the types under test, but +it requires no additional tooling. + +If the following file is under test + +.. code-block:: python + + # foo.py + def bar(x: int) -> str: + return str(x) + +Then the following file tests ``foo.py``: + +.. code-block:: python + + bar(42) + bar("42") # type: ignore [arg-type] + bar(y=42) # type: ignore [call-arg] + r1: str = bar(42) + r2: int = bar(42) # type: ignore [assignment] + +Checking ``reveal_type`` output from ``mypy.api.run`` +----------------------------------------------------- + +``mypy`` provides a subpackage named ``api`` for invoking ``mypy`` from a +python process. In combination with ``reveal_type``, this can be used to write +a function which gets the ``reveal_type`` output from an expression. Once +that's obtained, tests can assert strings and regular expression matches +against it. + +This approach requires writing a set of helpers to provide a good testing +experience, and it runs mypy once per test case (which can be slow). +However, it builds only on ``mypy`` and the test framework of your choice. + +The following example could be integrated into a testsuite written in +any framework: + +.. code-block:: python + + import re + from mypy import api + + def get_reveal_type_output(filename): + result = api.run([filename]) + stdout = result[0] + match = re.search(r'note: Revealed type is "([^"]+)"', stdout) + assert match is not None + return match.group(1) + + +For example, we can use the above to provide a ``run_reveal_type`` pytest +fixture which generates a temporary file and uses it as the input to +``get_reveal_type_output``: + +.. code-block:: python + + import os + import pytest + + @pytest.fixture + def _in_tmp_path(tmp_path): + cur = os.getcwd() + try: + os.chdir(tmp_path) + yield + finally: + os.chdir(cur) + + @pytest.fixture + def run_reveal_type(tmp_path, _in_tmp_path): + content_path = tmp_path / "reveal_type_test.py" + + def func(code_snippet, *, preamble = ""): + content_path.write_text(preamble + f"reveal_type({code_snippet})") + return get_reveal_type_output("reveal_type_test.py") + + return func + + +For more details, see `the documentation on mypy.api +<https://mypy.readthedocs.io/en/stable/extending_mypy.html#integrating-mypy-into-another-python-application>`_. + +pytest-mypy-plugins +------------------- + +`pytest-mypy-plugins <https://github.com/typeddjango/pytest-mypy-plugins>`_ is +a plugin for ``pytest`` which defines typing test cases as YAML data. +The test cases are run through ``mypy`` and the output of ``reveal_type`` can +be asserted. + +This project supports complex typing arrangements like ``pytest`` parametrized +tests and per-test ``mypy`` configuration. It requires that you are using +``pytest`` to run your tests, and runs ``mypy`` in a subprocess per test case. + +This is an example of a parametrized test with ``pytest-mypy-plugins``: + +.. code-block:: yaml + + - case: with_params + parametrized: + - val: 1 + rt: builtins.int + - val: 1.0 + rt: builtins.float + main: | + reveal_type({[ val }}) # N: Revealed type is '{{ rt }}' + +Improving Type Completeness +=========================== + +One of the goals of many libraries is to ensure that they are "fully type +annotated", meaning that they provide complete and accurate type annotations +for all functions, classes, and objects. Having full annotations is referred to +as "type completeness" or "type coverage". + +Here are some tips for increasing the type completeness score for your +library: + +- Make type completeness an output of your testing process. Several type + checkers have options for generating useful output, warnings, or even + reports. +- If your package includes tests or sample code, consider removing them + from the distribution. If there is good reason to include them, + consider placing them in a directory that begins with an underscore + so they are not considered part of your library’s interface. +- If your package includes submodules that are meant to be + implementation details, rename those files to begin with an + underscore. +- If a symbol is not intended to be part of the library’s interface and + is considered an implementation detail, rename it such that it begins + with an underscore. It will then be considered private and excluded + from the type completeness check. +- If your package exposes types from other libraries, work with the + maintainers of these other libraries to achieve type completeness. + +.. warning:: + + The ways in which different type checkers evaluate and help you achieve + better type coverage may differ. Some of the above recommendations may or + may not be helpful to you, depending on which type checking tools you use. + +``mypy`` disallow options +------------------------- + +``mypy`` offers several options which can detect untyped code. +More details can be found in `the mypy documentation on these options +<https://mypy.readthedocs.io/en/latest/command_line.html#untyped-definitions-and-calls>`_. + +Some basic usages which make ``mypy`` error on untyped data are:: + + mypy --disallow-untyped-defs + mypy --disallow-incomplete-defs + +``pyright`` type verification +----------------------------- + +pyright has a special command line flag, ``--verifytypes``, for verifying +type completeness. You can learn more about it from +`the pyright documentation on verifying type completeness +<https://github.com/microsoft/pyright/blob/main/docs/typed-libraries.md#verifying-type-completeness>`_. + +``mypy`` reports +---------------- + +``mypy`` offers several options options for generating reports on its analysis. +See `the mypy documentation on report generation +<https://mypy.readthedocs.io/en/stable/command_line.html#report-generation>`_ for details. diff --git a/docs/source/reference.rst b/docs/source/reference.rst new file mode 100644 index 0000000..c7fc57e --- /dev/null +++ b/docs/source/reference.rst @@ -0,0 +1,22 @@ +********************* +Type System Reference +********************* + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + stubs + quality + typing Module Documentation <https://docs.python.org/3/library/typing.html> + +.. The following pages are desired in a new TOC which will cover multiple +.. topics. For now, they are not linked because the pages are empty. +.. +.. basics +.. type_system +.. annotations +.. inference +.. type_compatibility +.. dependencies +.. faq diff --git a/docs/source/stubs.rst b/docs/source/stubs.rst new file mode 100644 index 0000000..ffd6c9b --- /dev/null +++ b/docs/source/stubs.rst @@ -0,0 +1,1129 @@ +.. _stubs: + +********** +Type Stubs +********** + +Introduction +============ + +*type stubs*, also called *stub files*, provide type information for untyped +Python packages and modules. Type stubs serve multiple purposes: + +* They are the only way to add type information to extension modules. +* They can provide type information for packages that do not wish to + add them inline. +* They can be distributed separately from the implementation. + This allows stubs to be developed at a different pace or by different + authors, which is especially useful when adding type annotations to + existing packages. +* They can act as documentation, succinctly explaining the external + API of a package, without including the implementation or private + members. + +This document aims to give guidance to both authors of type stubs and developers +of type checkers and other tools. It describes the constructs that can be used safely in type stubs, +suggests a style guide for them, and lists constructs that type +checkers are expected to support. + +Type stubs that only use constructs described in this document should work with +all type checkers that also follow this document. +Type stub authors can elect to use additional constructs, but +must be prepared that some type checkers will not parse them as expected. + +A type checker that conforms to this document will parse a type stub that only uses +constructs described here without error and will not interpret any +construct in a contradictory manner. However, type checkers are not +required to implement checks for all these constructs, and +can elect to ignore unsupported ones. Additionally type checkers +can support constructs not described in this document and tool authors are +encouraged to experiment with additional features. + +Syntax +====== + +Type stubs are syntactically valid Python 3.7 files with a ``.pyi`` suffix. +The Python syntax used for type stubs is independent from the Python +versions supported by the implementation, and from the Python version the type +checker runs under (if any). Therefore, type stub authors should use the +latest available syntax features in stubs (up to Python 3.7), even if the +implementation supports older, pre-3.7 Python versions. +Type checker authors are encouraged to support syntax features from +post-3.7 Python versions, although type stub authors should not use such +features if they wish to maintain compatibility with all type checkers. + +For example, Python 3.7 added the ``async`` keyword (see PEP 492 [#pep492]_). +Stub authors should use it to mark coroutines, even if the implementation +still uses the ``@coroutine`` decorator. On the other hand, type stubs should +not use the positional-only syntax from PEP 570 [#pep570]_, introduced in +Python 3.8, although type checker authors are encouraged to support it. + +Stubs are treated as if ``from __future__ import annotations`` is enabled. +In particular, built-in generics, pipe union syntax (``X | Y``), and forward +references can be used. + +Starting with Python 3.8, the :py:mod:`ast` module from the standard library supports +all syntax features required by this PEP. Older Python versions can use the +`typed_ast <https://pypi.org/project/typed-ast/>`_ package from the +Python Package Index, which also supports Python 3.7 syntax and ``# type`` +comments. + +Distribution +============ + +Type stubs can be distributed with or separately from the implementation; +see PEP 561 [#pep561]_ for more information. The +`typeshed <https://github.com/python/typeshed>`_ project +includes stubs for Python's standard library and several third-party +packages. The stubs for the standard library are usually distributed with type checkers and do not +require separate installation. Stubs for third-party libraries are +available on the `Python Package Index <https://pypi.org>`_. A stub package for +a library called ``widget`` will be called ``types-widget``. + +Supported Constructs +==================== + +This sections lists constructs that type checkers will accept in type stubs. +Type stub authors can safely use these constructs. If a +construct is marked as "unspecified", type checkers may handle it +as they best see fit or report an error. Linters should usually +flag those constructs. Type stub authors should avoid using them to +ensure compatibility across type checkers. + +Unless otherwise mentioned, type stubs support all features from the +``typing`` module of the latest released Python version. If a stub uses +typing features from a later Python version than what the implementation +supports, these features can be imported from ``typing_extensions`` instead +of ``typing``. + +For example, a stub could use ``Literal``, introduced in Python 3.8, +for a library supporting Python 3.7+:: + + from typing_extensions import Literal + + def foo(x: Literal[""]) -> int: ... + +Comments +-------- + +Standard Python comments are accepted everywhere Python syntax allows them. + +Two kinds of structured comments are accepted: + +* A ``# type: X`` comment at the end of a line that defines a variable, + declaring that the variable has type ``X``. However, PEP 526-style [#pep526]_ + variable annotations are preferred over type comments. +* A ``# type: ignore`` comment at the end of any line, which suppresses all type + errors in that line. The type checker mypy supports suppressing certain + type errors by using ``# type: ignore[error-type]``. This is not supported + by other type checkers and should not be used in stubs. + +Imports +------- + +Type stubs distinguish between imports that are re-exported and those +that are only used internally. Imports are re-exported if they use one of these +forms:[#pep484]_ + +* ``import X as X`` +* ``from Y import X as X`` +* ``from Y import *`` + +Here are some examples of imports that make names available for internal use in +a stub but do not re-export them:: + + import X + from Y import X + from Y import X as OtherX + +Type aliases can be used to re-export an import under a different name:: + + from foo import bar as _bar + new_bar = _bar # "bar" gets re-exported with the name "new_bar" + +Sub-modules are always exported when they are imported in a module. +For example, consider the following file structure:: + + foo/ + __init__.pyi + bar.pyi + +Then ``foo`` will export ``bar`` when one of the following constructs is used in +``__init__.pyi``:: + + from . import bar + from .bar import Bar + +Stubs support customizing star import semantics by defining a module-level +variable called ``__all__``. In stubs, this must be a string list literal. +Other types are not supported. Neither is the dynamic creation of this +variable (for example by concatenation). + +By default, ``from foo import *`` imports all names in ``foo`` that +do not begin with an underscore. When ``__all__`` is defined, only those names +specified in ``__all__`` are imported:: + + __all__ = ['public_attr', '_private_looking_public_attr'] + + public_attr: int + _private_looking_public_attr: int + private_attr: int + +Type checkers support cyclic imports in stub files. + +Built-in Generics +----------------- + +PEP 585 [#pep585]_ built-in generics are supported and should be used instead +of the corresponding types from ``typing``:: + + from collections import defaultdict + + def foo(t: type[MyClass]) -> list[int]: ... + x: defaultdict[int] + +Using imports from ``collections.abc`` instead of ``typing`` is +generally possible and recommended:: + + from collections.abc import Iterable + + def foo(iter: Iterable[int]) -> None: ... + +Unions +------ + +Declaring unions with ``Union`` and ``Optional`` is supported by all +type checkers. With a few exceptions [#ts-4819]_, the shorthand syntax +is also supported:: + + def foo(x: int | str) -> int | None: ... # recommended + def foo(x: Union[int, str]) -> Optional[int]: ... # ok + +Module Level Attributes +----------------------- + +Module level variables and constants can be annotated using either +type comments or variable annotation syntax:: + + x: int # recommended + x: int = 0 + x = 0 # type: int + x = ... # type: int + +The type of a variable is unspecified when the variable is unannotated or +when the annotation +and the assigned value disagree. As an exception, the ellipsis literal can +stand in for any type:: + + x = 0 # type is unspecified + x = ... # type is unspecified + x: int = "" # type is unspecified + x: int = ... # type is int + +Classes +------- + +Class definition syntax follows general Python syntax, but type checkers +are only expected to understand the following constructs in class bodies: + +* The ellipsis literal ``...`` is ignored and used for empty + class bodies. Using ``pass`` in class bodies is undefined. +* Instance attributes follow the same rules as module level attributes + (see above). +* Method definitions (see below) and properties. +* Method aliases. +* Inner class definitions. + +More complex statements don't need to be supported:: + + class Simple: ... + + class Complex(Base): + read_write: int + @property + def read_only(self) -> int: ... + def do_stuff(self, y: str) -> None: ... + doStuff = do_stuff + +The type of generic classes can be narrowed by annotating the ``self`` +argument of the ``__init__`` method:: + + class Foo(Generic[_T]): + @overload + def __init__(self: Foo[str], type: Literal["s"]) -> None: ... + @overload + def __init__(self: Foo[int], type: Literal["i"]) -> None: ... + @overload + def __init__(self, type: str) -> None: ... + +The class must match the class in which it is declared. Using other classes, +including sub or super classes, will not work. In addition, the ``self`` +annotation cannot contain type variables. + +.. _supported-functions: + +Functions and Methods +--------------------- + +Function and method definition syntax follows general Python syntax. +Unless an argument name is prefixed with two underscores (but not suffixed +with two underscores), it can be used as a keyword argument [#pep484]_:: + + # x is positional-only + # y can be used positionally or as keyword argument + # z is keyword-only + def foo(__x, y, *, z): ... + +PEP 570 [#pep570]_ style positional-only parameters are currently not +supported. + +If an argument or return type is unannotated, per PEP 484 [#pep484]_ its +type is assumed to be ``Any``. It is preferred to leave unknown +types unannotated rather than explicitly marking them as ``Any``, as some +type checkers can optionally warn about unannotated arguments. + +If an argument has a literal or constant default value, it must match the implementation +and the type of the argument (if specified) must match the default value. +Alternatively, ``...`` can be used in place of any default value:: + + # The following arguments all have type Any. + def unannotated(a, b=42, c=...): ... + # The following arguments all have type int. + def annotated(a: int, b: int = 42, c: int = ...): ... + # The following default values are invalid and the types are unspecified. + def invalid(a: int = "", b: Foo = Foo()): ... + +For a class ``C``, the type of the first argument to a classmethod is +assumed to be ``type[C]``, if unannotated. For other non-static methods, +its type is assumed to be ``C``:: + + class Foo: + def do_things(self): ... # self has type Foo + @classmethod + def create_it(cls): ... # cls has type Type[Foo] + @staticmethod + def utility(x): ... # x has type Any + +But:: + + _T = TypeVar("_T") + + class Foo: + def do_things(self: _T) -> _T: ... # self has type _T + @classmethod + def create_it(cls: _T) -> _T: ... # cls has type _T + +PEP 612 [#pep612]_ parameter specification variables (``ParamSpec``) +are supported in argument and return types:: + + _P = ParamSpec("_P") + _R = TypeVar("_R") + + def foo(cb: Callable[_P, _R], *args: _P.args, **kwargs: _P.kwargs) -> _R: ... + +However, ``Concatenate`` from PEP 612 is not yet supported; nor is using +a ``ParamSpec`` to parameterize a generic class. + +PEP 647 [#pep647]_ type guards are supported. + +Using a function or method body other than the ellipsis literal is currently +unspecified. Stub authors may experiment with other bodies, but it is up to +individual type checkers how to interpret them:: + + def foo(): ... # compatible + def bar(): pass # behavior undefined + +All variants of overloaded functions and methods must have an ``@overload`` +decorator:: + + @overload + def foo(x: str) -> str: ... + @overload + def foo(x: float) -> int: ... + +The following (which would be used in the implementation) is wrong in +type stubs:: + + @overload + def foo(x: str) -> str: ... + @overload + def foo(x: float) -> int: ... + def foo(x: str | float) -> Any: ... + +Aliases and NewType +------------------- + +Type checkers should accept module-level type aliases, optionally using +``TypeAlias`` (PEP 613 [#pep613]_), e.g.:: + + _IntList = list[int] + _StrList: TypeAlias = list[str] + +Type checkers should also accept regular module-level or class-level aliases, +e.g.:: + + def a() -> None: ... + b = a + + class C: + def f(self) -> int: ... + g = f + +A type alias may contain type variables. As per PEP 484 [#pep484]_, +all type variables must be substituted when the alias is used:: + + _K = TypeVar("_K") + _V = TypeVar("_V") + _MyMap: TypeAlias = dict[str, dict[_K, _V]] + + # either concrete types or other type variables can be substituted + def f(x: _MyMap[str, _V]) -> _V: ... + # explicitly substitute in Any rather than using a bare alias + def g(x: _MyMap[Any, Any]) -> Any: ... + +Otherwise, type variables in aliases follow the same rules as type variables in +generic class definitions. + +``typing.NewType`` is also supported in stubs. + +Decorators +---------- + +Type stubs may only use decorators defined in the ``typing`` module, plus a +fixed set of additional ones: + +* ``classmethod`` +* ``staticmethod`` +* ``property`` (including ``.setter``) +* ``abc.abstractmethod`` +* ``dataclasses.dataclass`` +* ``asyncio.coroutine`` (although ``async`` should be used instead) + +The behavior of other decorators should instead be incorporated into the types. +For example, for the following function:: + + import contextlib + @contextlib.contextmanager + def f(): + yield 42 + +the stub definition should be:: + + from contextlib import AbstractContextManager + def f() -> AbstractContextManager[int]: ... + +Version and Platform Checks +--------------------------- + +Type stubs for libraries that support multiple Python versions can use version +checks to supply version-specific type hints. Type stubs for different Python +versions should still conform to the most recent supported Python version's +syntax, as explain in the Syntax_ section above. + +Version checks are if-statements that use ``sys.version_info`` to determine the +current Python version. Version checks should only check against the ``major`` and +``minor`` parts of ``sys.version_info``. Type checkers are only required to +support the tuple-based version check syntax:: + + if sys.version_info >= (3,): + # Python 3-specific type hints. This tuple-based syntax is recommended. + else: + # Python 2-specific type hints. + + if sys.version_info >= (3, 5): + # Specific minor version features can be easily checked with tuples. + + if sys.version_info < (3,): + # This is only necessary when a feature has no Python 3 equivalent. + +Type stubs should avoid checking against ``sys.version_info.major`` +directly and should not use comparison operators other than ``<`` and ``>=``. + +No:: + + if sys.version_info.major >= 3: + # Semantically the same as the first tuple check. + + if sys.version_info[0] >= 3: + # This is also the same. + + if sys.version_info <= (2, 7): + # This does not work because e.g. (2, 7, 1) > (2, 7). + +Some type stubs also may need to specify type hints for different platforms. +Platform checks must be equality comparisons between ``sys.platform`` and the name +of a platform as a string literal: + +Yes:: + + if sys.platform == 'win32': + # Windows-specific type hints. + else: + # Posix-specific type hints. + +No:: + + if sys.platform.startswith('linux'): + # Not necessary since Python 3.3. + + if sys.platform in ['linux', 'cygwin', 'darwin']: + # Only '==' or '!=' should be used in platform checks. + +Version and platform comparisons can be chained using the ``and`` and ``or`` +operators:: + + if sys.platform == 'linux' and (sys.version_info < (3,) or sys,version_info >= (3, 7)): ... + +Enums +----- + +Enum classes are supported in stubs, regardless of the Python version targeted by +the stubs. + +Enum members may be specified just like other forms of assignments, for example as +``x: int``, ``x = 0``, or ``x = ...``. The first syntax is preferred because it +allows type checkers to correctly type the ``.value`` attribute of enum members, +without providing unnecessary information like the runtime value of the enum member. + +Additional properties on enum members should be specified with ``@property``, so they +do not get interpreted by type checkers as enum members. + +Yes:: + + from enum import Enum + + class Color(Enum): + RED: int + BLUE: int + @property + def rgb_value(self) -> int: ... + + class Color(Enum): + # discouraged; type checkers will not understand that Color.RED.value is an int + RED = ... + BLUE = ... + @property + def rgb_value(self) -> int: ... + +No:: + + from enum import Enum + + class Color(Enum): + RED: int + BLUE: int + rgb_value: int # no way for type checkers to know that this is not an enum member + +Unsupported Features +-------------------- + +Currently, the following features are not supported by all type checkers +and should not be used in stubs: + +* Positional-only argument syntax (PEP 570 [#pep570]_). Instead, use + the syntax described in the section :ref:`supported-functions`. + [#ts-4972]_ + +Type Stub Content +================= + +This section documents best practices on what elements to include or +leave out of type stubs. + +Modules excluded fom stubs +-------------------------- + +Not all modules should be included into stubs. + +It is recommended to exclude: + +1. Implementation details, with `multiprocessing/popen_spawn_win32.py <https://github.com/python/cpython/blob/main/Lib/multiprocessing/popen_spawn_win32.py>`_ as a notable example +2. Modules that are not supposed to be imported, such as ``__main__.py`` +3. Protected modules that start with a single ``_`` char. However, when needed protected modules can still be added (see :ref:`undocumented-objects` section below) + +Public Interface +---------------- + +Stubs should include the complete public interface (classes, functions, +constants, etc.) of the module they cover, but it is not always +clear exactly what is part of the interface. + +The following should always be included: + +* All objects listed in the module's documentation. +* All objects included in ``__all__`` (if present). + +Other objects may be included if they are not prefixed with an underscore +or if they are being used in practice. (See the next section.) + +.. _undocumented-objects: + +Undocumented Objects +-------------------- + +Undocumented objects may be included as long as they are marked with a comment +of the form ``# undocumented``. + +Example:: + + def list2cmdline(seq: Sequence[str]) -> str: ... # undocumented + +Such undocumented objects are allowed because omitting objects can confuse +users. Users who see an error like "module X has no attribute Y" will +not know whether the error appeared because their code had a bug or +because the stub is wrong. Although it may also be helpful for a type +checker to point out usage of private objects, false negatives (no errors for +wrong code) are preferable over false positives (type errors +for correct code). In addition, even for private objects a type checker +can be helpful in pointing out that an incorrect type was used. + +``__all__`` +------------ + +A type stub should contain an ``__all__`` variable if and only if it also +present at runtime. In that case, the contents of ``__all__`` should be +identical in the stub and at runtime. If the runtime dynamically adds +or removes elements (for example if certain functions are only available on +some platforms), include all possible elements in the stubs. + +Stub-Only Objects +----------------- + +Definitions that do not exist at runtime may be included in stubs to aid in +expressing types. Sometimes, it is desirable to make a stub-only class available +to a stub's users - for example, to allow them to type the return value of a +public method for which a library does not provided a usable runtime type:: + + from typing import Protocol + + class _Readable(Protocol): + def read(self) -> str: ... + + def get_reader() -> _Readable: ... + +Structural Types +---------------- + +As seen in the example with ``_Readable`` in the previous section, a common use +of stub-only objects is to model types that are best described by their +structure. These objects are called protocols [#pep544]_, and it is encouraged +to use them freely to describe simple structural types. + +It is `recommended <#private-definitions>`_ to prefix stubs-only object names with ``_``. + +Incomplete Stubs +---------------- + +Partial stubs can be useful, especially for larger packages, but they should +follow the following guidelines: + +* Included functions and methods should list all arguments, but the arguments + can be left unannotated. +* Do not use ``Any`` to mark unannotated arguments or return values. +* Partial classes should include a ``__getattr__()`` method marked with an + ``# incomplete`` comment (see example below). +* Partial modules (i.e. modules that are missing some or all classes, + functions, or attributes) should include a top-level ``__getattr__()`` + function marked with an ``# incomplete`` comment (see example below). +* Partial packages (i.e. packages that are missing one or more sub-modules) + should have a ``__init__.pyi`` stub that is marked as incomplete (see above). + A better alternative is to create empty stubs for all sub-modules and + mark them as incomplete individually. + +Example of a partial module with a partial class ``Foo`` and a partially +annotated function ``bar()``:: + + def __getattr__(name: str) -> Any: ... # incomplete + + class Foo: + def __getattr__(self, name: str) -> Any: # incomplete + x: int + y: str + + def bar(x: str, y, *, z=...): ... + +The ``# incomplete`` comment is mainly intended as a reminder for stub +authors, but can be used by tools to flag such items. + +Attribute Access +---------------- + +Python has several methods for customizing attribute access: ``__getattr__``, +``__getattribute__``, ``__setattr__``, and ``__delattr__``. Of these, +``__getattr__`` and ``__setattr___`` should sometimes be included in stubs. + +In addition to marking incomplete definitions, ``__getattr__`` should be +included when a class or module allows any name to be accessed. For example, consider +the following class:: + + class Foo: + def __getattribute__(self, name): + return self.__dict__.setdefault(name) + +An appropriate stub definition is:: + + from typing import Any + class Foo: + def __getattr__(self, name: str) -> Any | None: ... + +Note that only ``__getattr__``, not ``__getattribute__``, is guaranteed to be +supported in stubs. + +On the other hand, ``__getattr__`` should be omitted even if the source code +includes it, if only limited names are allowed. For example, consider this class:: + + class ComplexNumber: + def __init__(self, n): + self._n = n + def __getattr__(self, name): + if name in ("real", "imag"): + return getattr(self._n, name) + raise AttributeError(name) + +In this case, the stub should list the attributes individually:: + + class ComplexNumber: + @property + def real(self) -> float: ... + @property + def imag(self) -> float: ... + def __init__(self, n: complex) -> None: ... + +``__setattr___`` should be included when a class allows any name to be set and +restricts the type. For example:: + + class IntHolder: + def __setattr__(self, name, value): + if isinstance(value, int): + return super().__setattr__(name, value) + raise ValueError(value) + +A good stub definition would be:: + + class IntHolder: + def __setattr__(self, name: str, value: int) -> None: ... + +``__delattr__`` should not be included in stubs. + +Finally, even in the presence of ``__getattr__`` and ``__setattr__``, it is +still recommended to separately define known attributes. + +Constants +--------- + +When the value of a constant is important, annotate it using ``Literal`` +instead of its type. + +Yes:: + + TEL_LANDLINE: Literal["landline"] + TEL_MOBILE: Literal["mobile"] + DAY_FLAG: Literal[0x01] + NIGHT_FLAG: Literal[0x02] + +No:: + + TEL_LANDLINE: str + TEL_MOBILE: str + DAY_FLAG: int + NIGHT_FLAG: int + +Documentation or Implementation +------------------------------- + +Sometimes a library's documented types will differ from the actual types in the +code. In such cases, type stub authors should use their best judgment. Consider +these two examples:: + + def print_elements(x): + """Print every element of list x.""" + for y in x: + print(y) + + def maybe_raise(x): + """Raise an error if x (a boolean) is true.""" + if x: + raise ValueError() + +The implementation of ``print_elements`` takes any iterable, despite the +documented type of ``list``. In this case, annotate the argument as +``Iterable[Any]``, to follow this PEP's style recommendation of preferring +abstract types. + +For ``maybe_raise``, on the other hand, it is better to annotate the argument as +``bool`` even though the implementation accepts any object. This guards against +common mistakes like unintentionally passing in ``None``. + +If in doubt, consider asking the library maintainers about their intent. + +Style Guide +=========== + +The recommendations in this section are aimed at type stub authors +who wish to provide a consistent style for type stubs. Type checkers +should not reject stubs that do not follow these recommendations, but +linters can warn about them. + +Stub files should generally follow the Style Guide for Python Code (PEP 8) +[#pep8]_. There are a few exceptions, outlined below, that take the +different structure of stub files into account and are aimed to create +more concise files. + +Maximum Line Length +------------------- + +Type stubs should be limited to 130 characters per line. + +Blank Lines +----------- + +Do not use empty lines between functions, methods, and fields, except to +group them with one empty line. Use one empty line around classes, but do not +use empty lines between body-less classes, except for grouping. + +Yes:: + + def time_func() -> None: ... + def date_func() -> None: ... + + def ip_func() -> None: ... + + class Foo: + x: int + y: int + def __init__(self) -> None: ... + + class MyError(Exception): ... + class AnotherError(Exception): ... + +No:: + + def time_func() -> None: ... + + def date_func() -> None: ... # do no leave unnecessary empty lines + + def ip_func() -> None: ... + + + class Foo: # leave only one empty line above + x: int + class MyError(Exception): ... # leave an empty line between the classes + +Shorthand Syntax +---------------- + +Where possible, use shorthand syntax for unions instead of +``Union`` or ``Optional``. ``None`` should be the last +element of an union. + +Yes:: + + def foo(x: str | int) -> None: ... + def bar(x: str | None) -> int | None: ... + +No:: + + def foo(x: Union[str, int]) -> None: ... + def bar(x: Optional[str]) -> Optional[int]: ... + def baz(x: None | str) -> None: ... + +Module Level Attributes +----------------------- + +Do not use an assignment for module-level attributes. + +Yes:: + + CONST: Literal["const"] + x: int + +No:: + + CONST = "const" + x: int = 0 + y: float = ... + z = 0 # type: int + a = ... # type: int + +Type Aliases +------------ + +Use ``TypeAlias`` for type aliases (but not for regular aliases). + +Yes:: + + _IntList: TypeAlias = list[int] + g = os.stat + Path = pathlib.Path + ERROR = errno.EEXIST + +No:: + + _IntList = list[int] + g: TypeAlias = os.stat + Path: TypeAlias = pathlib.Path + ERROR: TypeAlias = errno.EEXIST + +Classes +------- + +Classes without bodies should use the ellipsis literal ``...`` in place +of the body on the same line as the class definition. + +Yes:: + + class MyError(Exception): ... + +No:: + + class MyError(Exception): + ... + class AnotherError(Exception): pass + +Instance attributes and class variables follow the same recommendations as +module level attributes: + +Yes:: + + class Foo: + c: ClassVar[str] + x: int + +No:: + + class Foo: + c: ClassVar[str] = "" + d: ClassVar[int] = ... + x = 4 + y: int = ... + +Functions and Methods +--------------------- + +Use the same argument names as in the implementation, because +otherwise using keyword arguments will fail. Of course, this +does not apply to positional-only arguments, which are marked with a double +underscore. + +Use the ellipsis literal ``...`` in place of actual default argument +values. Use an explicit ``X | None`` annotation instead of +a ``None`` default. + +Yes:: + + def foo(x: int = ...) -> None: ... + def bar(y: str | None = ...) -> None: ... + +No:: + + def foo(x: int = 0) -> None: ... + def bar(y: str = None) -> None: ... + def baz(z: str | None = None) -> None: ... + +Do not annotate ``self`` and ``cls`` in method definitions, except when +referencing a type variable. + +Yes:: + + _T = TypeVar("_T") + class Foo: + def bar(self) -> None: ... + @classmethod + def create(cls: type[_T]) -> _T: ... + +No:: + + class Foo: + def bar(self: Foo) -> None: ... + @classmethod + def baz(cls: type[Foo]) -> int: ... + +The bodies of functions and methods should consist of only the ellipsis +literal ``...`` on the same line as the closing parenthesis and colon. + +Yes:: + + def to_int1(x: str) -> int: ... + def to_int2( + x: str, + ) -> int: ... + +No:: + + def to_int1(x: str) -> int: + return int(x) + def to_int2(x: str) -> int: + ... + def to_int3(x: str) -> int: pass + +.. _private-definitions: + +Private Definitions +------------------- + +Type variables, type aliases, and other definitions that should not +be used outside the stub should be marked as private by prefixing them +with an underscore. + +Yes:: + + _T = TypeVar("_T") + _DictList = Dict[str, List[Optional[int]] + +No:: + + T = TypeVar("T") + DictList = Dict[str, List[Optional[int]]] + +Language Features +----------------- + +Use the latest language features available as outlined +in the Syntax_ section, even for stubs targeting older Python versions. +Do not use quotes around forward references and do not use ``__future__`` +imports. + +Yes:: + + class Py35Class: + x: int + forward_reference: OtherClass + class OtherClass: ... + +No:: + + class Py35Class: + x = 0 # type: int + forward_reference: 'OtherClass' + class OtherClass: ... + +Types +----- + +Generally, use ``Any`` when a type cannot be expressed appropriately +with the current type system or using the correct type is unergonomic. + +Use ``float`` instead of ``int | float``. +Use ``None`` instead of ``Literal[None]``. +For argument types, +use ``bytes`` instead of ``bytes | memoryview | bytearray``. + +Use ``Text`` in stubs that support Python 2 when something accepts both +``str`` and ``unicode``. Avoid using ``Text`` in stubs or branches for +Python 3 only. + +Yes:: + + if sys.version_info < (3,): + def foo(s: Text) -> None: ... + else: + def foo(s: str, *, i: int) -> None: ... + def bar(s: Text) -> None: ... + +No:: + + if sys.version_info < (3,): + def foo(s: unicode) -> None: ... + else: + def foo(s: Text, *, i: int) -> None: ... + +For arguments, prefer protocols and abstract types (``Mapping``, +``Sequence``, ``Iterable``, etc.). If an argument accepts literally any value, +use ``object`` instead of ``Any``. + +For return values, prefer concrete types (``list``, ``dict``, etc.) for +concrete implementations. The return values of protocols +and abstract base classes must be judged on a case-by-case basis. + +Yes:: + + def map_it(input: Iterable[str]) -> list[int]: ... + def create_map() -> dict[str, int]: ... + def to_string(o: object) -> str: ... # accepts any object + +No:: + + def map_it(input: list[str]) -> list[int]: ... + def create_map() -> MutableMapping[str, int]: ... + def to_string(o: Any) -> str: ... + +Maybe:: + + class MyProto(Protocol): + def foo(self) -> list[int]: ... + def bar(self) -> Mapping[str]: ... + +Avoid union return types, since they require ``isinstance()`` checks. +Use ``Any`` or ``X | Any`` if necessary. + +Use built-in generics instead of the aliases from ``typing``, +where possible. See the section `Built-in Generics`_ for cases, +where it's not possible to use them. + +Yes:: + + from collections.abc import Iterable + + def foo(x: type[MyClass]) -> list[str]: ... + def bar(x: Iterable[str]) -> None: ... + +No:: + + from typing import Iterable, List, Type + + def foo(x: Type[MyClass]) -> List[str]: ... + def bar(x: Iterable[str]) -> None: ... + +NamedTuple and TypedDict +------------------------ + +Use the class-based syntax for ``typing.NamedTuple`` and +``typing.TypedDict``, following the Classes section of this style guide. + +Yes:: + + from typing import NamedTuple, TypedDict + class Point(NamedTuple): + x: float + y: float + + class Thing(TypedDict): + stuff: str + index: int + +No:: + + from typing import NamedTuple, TypedDict + Point = NamedTuple("Point", [('x', float), ('y', float)]) + Thing = TypedDict("Thing", {'stuff': str, 'index': int}) + +References +========== + +PEPs +---- + +.. [#pep8] PEP 8 -- Style Guide for Python Code, van Rossum et al. (https://www.python.org/dev/peps/pep-0008/) +.. [#pep484] PEP 484 -- Type Hints, van Rossum et al. (https://www.python.org/dev/peps/pep-0484) +.. [#pep492] PEP 492 -- Coroutines with async and await syntax, Selivanov (https://www.python.org/dev/peps/pep-0492/) +.. [#pep526] PEP 526 -- Syntax for Variable Annotations, Gonzalez et al. (https://www.python.org/dev/peps/pep-0526) +.. [#pep544] PEP 544 -- Protocols: Structural Subtyping, Levkivskyi et al. (https://www.python.org/dev/peps/pep-0544) +.. [#pep561] PEP 561 -- Distributing and Packaging Type Information, Smith (https://www.python.org/dev/peps/pep-0561) +.. [#pep570] PEP 570 -- Python Positional-Only Parameters, Hastings et al. (https://www.python.org/dev/peps/pep-0570) +.. [#pep585] PEP 585 -- Type Hinting Generics In Standard Collections, Langa (https://www.python.org/dev/peps/pep-0585) +.. [#pep604] PEP 604 -- Allow writing union types as X | Y, Prados and Moss (https://www.python.org/dev/peps/pep-0604) +.. [#pep612] PEP 612 -- Parameter Specification Variables, Mendoza (https://www.python.org/dev/peps/pep-0612) +.. [#pep613] PEP 613 -- Explicit Type Aliases, Zhu (https://www.python.org/dev/peps/pep-0613) +.. [#pep647] PEP 647 -- User-Defined Type Guards, Traut (https://www.python.org/dev/peps/pep-0647) +.. [#pep3107] PEP 3107 -- Function Annotations, Winter and Lownds (https://www.python.org/dev/peps/pep-3107) + +Bugs +---- + +.. [#ts-4819] typeshed issue #4819 -- PEP 604 tracker (https://github.com/python/typeshed/issues/4819) +.. [#ts-4972] typeshed issue #4972 -- PEP 570 tracker (https://github.com/python/typeshed/issues/4972) + +Copyright +========= + +This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive. diff --git a/docs/source/type_compatibility.rst b/docs/source/type_compatibility.rst new file mode 100644 index 0000000..43ebd32 --- /dev/null +++ b/docs/source/type_compatibility.rst @@ -0,0 +1,17 @@ +****************** +Type Compatibility +****************** + + +The Class Hierarchy +=================== + +Mutable Containers +------------------ + +Comparing Callables: Covariance and Contravariance +-------------------------------------------------- + + +Metaclasses +=========== diff --git a/docs/source/type_system.rst b/docs/source/type_system.rst new file mode 100644 index 0000000..6d7954b --- /dev/null +++ b/docs/source/type_system.rst @@ -0,0 +1,58 @@ +*************** +The Type System +*************** + +Built-in Types +============== + +`typing Module Documentation <https://docs.python.org/3/library/typing.html>`__ + +Built-in Primitives +------------------- + +Built-in operators +------------------ + +Advanced Types +-------------- + + +Simple User-Defined Types +========================= + + +Type Aliases +============ + +Recursive Aliases +----------------- + + +Data Structures +=============== + +Typed Dictionary +---------------- + +Dataclass +--------- + + +Generic Types +============= + + +Type Variables +============== + +TypeVar +------- + +Variadics +--------- + +ParamSpec +^^^^^^^^^ + +TypeVarTuple +^^^^^^^^^^^^ diff --git a/docs/source/unreachable.rst b/docs/source/unreachable.rst new file mode 100644 index 0000000..16c37d8 --- /dev/null +++ b/docs/source/unreachable.rst @@ -0,0 +1,148 @@ +.. _unreachable: + +******************************************** +Unreachable Code and Exhaustiveness Checking +******************************************** + +Sometimes it is necessary to write code that should never execute, and +sometimes we write code that we expect to execute, but that is actually +unreachable. The type checker can help in both cases. + +In this guide, we'll cover: + +- ``Never``, the primitive type used for unreachable code +- ``assert_never()``, a helper for exhaustiveness checking +- Directly marking code as unreachable +- Detecting unexpectedly unreachable code + +``Never`` and ``NoReturn`` +========================== + +Type theory has a concept of a +`bottom type <https://en.wikipedia.org/wiki/Bottom_type>`__, +a type that has no values. Concretely, this can be used to represent +the return type of a function that never returns, or the argument type +of a function that may never be called. You can also think of the +bottom type as a union with no members. + +The Python type system has long provided a type called ``NoReturn``. +While it was originally meant only for functions that never return, +this concept is naturally extended to the bottom type in general, and all +type checkers treat ``NoReturn`` as a general bottom type. + +To make the meaning of this type more explicit, Python 3.11 and +typing-extensions 4.1 add a new primitive, ``Never``. To type checkers, +it has the same meaning as ``NoReturn``. + +In this guide, we'll use ``Never`` for the bottom type, but if you cannot +use it yet, you can always use ``typing.NoReturn`` instead. + +``assert_never()`` and Exhaustiveness Checking +============================================== + +The ``Never`` type can be leveraged to perform static exhaustiveness checking, +where we use the type checker to make sure that we covered all possible +cases. For example, this can come up when code performs a separate action +for each member of an enum, or for each type in a union. + +To have the type checker do exhaustiveness checking for us, we call a +function with a parameter typed as ``Never``. The type checker will allow +this call only if it can prove that the code is not reachable. + +As an example, consider this simple calculator: + +.. code:: python + + import enum + from typing_extensions import Never + + def assert_never(arg: Never) -> Never: + raise AssertionError("Expected code to be unreachable") + + class Op(enum.Enum): + ADD = 1 + SUBTRACT = 2 + + def calculate(left: int, op: Op, right: int) -> int: + match op: + case Op.ADD: + return left + right + case Op.SUBTRACT: + return left - right + case _: + assert_never(op) + +The ``match`` statement covers all members of the ``Op`` enum, +so the ``assert_never()`` call is unreachable and the type checker +will accept this code. However, if you add another member to the +enum (say, ``MULTIPLY``) but don't update the ``match`` statement, +the type checker will give an error saying that you are not handling +the ``MULTIPLY`` case. + +Because the ``assert_never()`` helper function is frequently useful, +it is provided by the standard library as ``typing.assert_never`` +starting in Python 3.11, +and is also present in ``typing_extensions`` starting at version 4.1. +However, it is also possible to define a similar function in your own +code, for example if you want to customize the runtime error message. + +You can also use ``assert_never()`` with a sequence of ``if`` statements: + +.. code:: python + + def calculate(left: int, op: Op, right: int) -> int: + if op is Op.ADD: + return left + right + elif op is Op.SUBTRACT: + return left - right + else: + assert_never(op) + +Marking Code as Unreachable +======================= + +Sometimes a piece of code is unreachable, but the type system is not +powerful enough to recognize that. For example, consider a function that +finds the lowest unused street number in a street: + +.. code:: python + + import itertools + + def is_used(street: str, number: int) -> bool: + ... + + def lowest_unused(street: str) -> int: + for i in itertools.count(1): + if not is_used(street, i): + return i + assert False, "unreachable" + +Because ``itertools.count()`` is an infinite iterator, this function +will never reach the ``assert False`` statement. However, there is +no way for the type checker to know that, so without the ``assert False``, +the type checker will complain that the function is missing a return +statement. + +Note how this is different from ``assert_never()``: + +- If we used ``assert_never()`` in the ``lowest_unused()`` function, + the type checker would produce an error, because the type checker + cannot prove that the line is unreachable. +- If we used ``assert False`` instead of ``assert_never()`` in the + ``calculate()`` example above, we would not get the benefits of + exhaustiveness checking. If the code is actually reachable, + the type checker will not warn us and we could hit the assertion + at runtime. + +While ``assert False`` is the most idiomatic way to express this pattern, +any statement that ends execution will do. For example, you could raise +an exception or call a function that returns ``Never``. + +Detecting Unexpectedly Unreachable Code +======================================= + +Another possible problem is code that is supposed to execute, but that +can actually be statically determined to be unreachable. +Some type checkers have an option that enables warnings for code +detected as unreachable (e.g., ``--warn-unreachable`` in mypy). diff --git a/scripts/requirements.txt b/scripts/requirements.txt new file mode 100644 index 0000000..d5f8f29 --- /dev/null +++ b/scripts/requirements.txt @@ -0,0 +1,2 @@ +requests>=2.25.0,<3 +types-requests>=2.25.0,<3 diff --git a/scripts/typing-summary.py b/scripts/typing-summary.py new file mode 100755 index 0000000..f5f9825 --- /dev/null +++ b/scripts/typing-summary.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 + +""" +Generate a summary of last week's issues tagged with "topic: feature". + +The summary will include a list of new and changed issues and is sent each +Monday at 0200 CE(S)T to the typing-sig mailing list. Due to limitation +with GitHub Actions, the mail is sent from a private server, currently +maintained by @srittau. +""" + +from __future__ import annotations + +import datetime +from dataclasses import dataclass +from typing import Any, Iterable, Sequence + +import requests + +ISSUES_API_URL = "https://api.github.com/repos/python/typing/issues" +ISSUES_URL = "https://github.com/python/typing/issues?q=label%3A%22topic%3A+feature%22" +ISSUES_LABEL = "topic: feature" +SENDER_EMAIL = "Typing Bot <noreply@python.org>" +RECEIVER_EMAIL = "typing-sig@python.org" + + +@dataclass +class Issue: + number: int + title: str + url: str + created: datetime.datetime + user: str + pull_request: bool = False + + +def main() -> None: + since = previous_week_start() + issues = fetch_issues(since) + new, updated = split_issues(issues, since) + print_summary(since, new, updated) + + +def previous_week_start() -> datetime.date: + today = datetime.date.today() + return today - datetime.timedelta(days=today.weekday() + 7) + + +def fetch_issues(since: datetime.date) -> list[Issue]: + """Return (new, updated) issues.""" + j = requests.get( + ISSUES_API_URL, + params={ + "labels": ISSUES_LABEL, + "since": f"{since:%Y-%m-%d}T00:00:00Z", + "per_page": "100", + "state": "open", + }, + headers={"Accept": "application/vnd.github.v3+json"}, + ).json() + assert isinstance(j, list) + return [parse_issue(j_i) for j_i in j] + + +def parse_issue(j: Any) -> Issue: + number = j["number"] + title = j["title"] + url = j["html_url"] + created_at = datetime.datetime.fromisoformat(j["created_at"][:-1]) + user = j["user"]["login"] + pull_request = "pull_request" in j + assert isinstance(number, int) + assert isinstance(title, str) + assert isinstance(url, str) + assert isinstance(user, str) + return Issue(number, title, url, created_at, user, pull_request) + + +def split_issues( + issues: Iterable[Issue], since: datetime.date +) -> tuple[list[Issue], list[Issue]]: + new = [] + updated = [] + for issue in issues: + if issue.created.date() >= since: + new.append(issue) + else: + updated.append(issue) + new.sort(key=lambda i: i.number) + updated.sort(key=lambda i: i.number) + return new, updated + + +def print_summary( + since: datetime.date, new: Sequence[Issue], changed: Sequence[Issue] +) -> None: + print(f"From: {SENDER_EMAIL}") + print(f"To: {RECEIVER_EMAIL}") + print(f"Subject: Opened and changed typing issues week {since:%G-W%V}") + print() + print(generate_mail(new, changed)) + + +def generate_mail(new: Sequence[Issue], changed: Sequence[Issue]) -> str: + if len(new) == 0 and len(changed) == 0: + s = ( + "No issues or pull requests with the label 'topic: feature' were opened\n" + "or updated last week in the typing repository on GitHub.\n\n" + ) + else: + s = ( + "The following is an overview of all issues and pull requests in the\n" + "typing repository on GitHub with the label 'topic: feature'\n" + "that were opened or updated last week, excluding closed issues.\n\n" + "---------------------------------------------------\n\n" + ) + if len(new) > 0: + s += "The following issues and pull requests were opened last week: \n\n" + s += "".join(generate_issue_text(issue) for issue in new) + s += "\n---------------------------------------------------\n\n" + if len(changed) > 0: + s += "The following issues and pull requests were updated last week: \n\n" + s += "".join(generate_issue_text(issue) for issue in changed) + s += "\n---------------------------------------------------\n\n" + s += ( + "All issues and pull requests with the label 'topic: feature'\n" + "can be viewed under the following URL:\n\n" + ) + s += ISSUES_URL + return s + + +def generate_issue_text(issue: Issue) -> str: + s = f"#{issue.number:<5} " + if issue.pull_request: + s += "[PR] " + s += f"{issue.title}\n" + s += f" opened by @{issue.user}\n" + s += f" {issue.url}\n" + return s + + +if __name__ == "__main__": + main() diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..658ae0a --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,3 @@ +flake8 +flake8-bugbear +flake8-pyi diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG new file mode 100644 index 0000000..a9a5980 --- /dev/null +++ b/typing_extensions/CHANGELOG @@ -0,0 +1,71 @@ +# Unreleased + +- Add `typing.assert_type`. Backport from bpo-46480. +- Drop support for Python 3.6. Original patch by Adam Turner (@AA-Turner). + +# Release 4.1.1 (February 13, 2022) + +- 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/typing_extensions/LICENSE b/typing_extensions/LICENSE new file mode 100644 index 0000000..583f9f6 --- /dev/null +++ b/typing_extensions/LICENSE @@ -0,0 +1,254 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://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 http://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 (now Zope +Corporation, see http://www.zope.com). In 2001, the Python Software +Foundation (PSF, see http://www.python.org/psf/) was formed, a +non-profit organization created specifically to own Python-related +Intellectual Property. Zope Corporation is a sponsoring member of +the PSF. + +All Python releases are Open Source (see http://www.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 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 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. diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst new file mode 100644 index 0000000..9abed04 --- /dev/null +++ b/typing_extensions/README.rst @@ -0,0 +1,146 @@ +================= +Typing Extensions +================= + +.. image:: https://badges.gitter.im/python/typing.svg + :alt: Chat at https://gitter.im/python/typing + :target: https://gitter.im/python/typing?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge + +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 Python 3.6 through 3.9 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 <https://github.com/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 <https://semver.org/>`_. 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. + +Included items +============== + +This module currently contains the following: + +- Experimental features + + - ``@dataclass_transform()`` (see PEP 681) + +- In ``typing`` since Python 3.11 + + - ``assert_never`` + - ``assert_type`` + - ``LiteralString`` (see PEP 675) + - ``Never`` + - ``NotRequired`` (see PEP 655) + - ``reveal_type`` + - ``Required`` (see PEP 655) + - ``Self`` (see PEP 673) + - ``TypeVarTuple`` (see PEP 646) + - ``Unpack`` (see PEP 646) + +- In ``typing`` since Python 3.10 + + - ``Concatenate`` (see PEP 612) + - ``ParamSpec`` (see PEP 612) + - ``ParamSpecArgs`` (see PEP 612) + - ``ParamSpecKwargs`` (see PEP 612) + - ``TypeAlias`` (see PEP 613) + - ``TypeGuard`` (see PEP 647) + - ``is_typeddict`` + +- In ``typing`` since Python 3.9 + + - ``Annotated`` (see PEP 593) + +- In ``typing`` since Python 3.8 + + - ``final`` (see PEP 591) + - ``Final`` (see PEP 591) + - ``Literal`` (see PEP 586) + - ``Protocol`` (see PEP 544) + - ``runtime_checkable`` (see PEP 544) + - ``TypedDict`` (see PEP 589) + - ``get_origin`` (``typing_extensions`` provides this function only in Python 3.7+) + - ``get_args`` (``typing_extensions`` provides this function only in Python 3.7+) + +- In ``typing`` since Python 3.7 + + - ``OrderedDict`` + +- In ``typing`` since Python 3.5 or 3.6 (see `the typing documentation + <https://docs.python.org/3.10/library/typing.html>`_ for details) + + - ``AsyncContextManager`` + - ``AsyncGenerator`` + - ``AsyncIterable`` + - ``AsyncIterator`` + - ``Awaitable`` + - ``ChainMap`` + - ``ClassVar`` (see PEP 526) + - ``ContextManager`` + - ``Coroutine`` + - ``Counter`` + - ``DefaultDict`` + - ``Deque`` + - ``NewType`` + - ``NoReturn`` + - ``overload`` + - ``Text`` + - ``Type`` + - ``TYPE_CHECKING`` + - ``get_type_hints`` + +Other Notes and Limitations +=========================== + +Certain objects were changed after they were added to ``typing``, and +``typing_extensions`` provides a backport even on newer Python versions: + +- ``TypedDict`` does not store runtime information + about which (if any) keys are non-required in Python 3.8, and does not + honor the "total" keyword with old-style ``TypedDict()`` in Python + 3.9.0 and 3.9.1. +- ``get_origin`` and ``get_args`` lack support for ``Annotated`` in + Python 3.8 and lack support for ``ParamSpecArgs`` and ``ParamSpecKwargs`` + in 3.9. +- ``@final`` was changed in Python 3.11 to set the ``.__final__`` attribute. + +There are a few types whose interface was modified between different +versions of typing. For example, ``typing.Sequence`` was modified to +subclass ``typing.Reversible`` as of Python 3.5.3. + +These changes are _not_ backported to prevent subtle compatibility +issues when mixing the differing implementations of modified classes. + +Certain types have incorrect runtime behavior due to limitations of older +versions of the typing module: + +- ``ParamSpec`` and ``Concatenate`` will not work with ``get_args`` and + ``get_origin``. Certain PEP 612 special cases in user-defined + ``Generic``\ s are also not available. + +These types are only guaranteed to work for static type checking. + +Running tests +============= + +To run tests, navigate into the appropriate source directory and run +``test_typing_extensions.py``. diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml new file mode 100644 index 0000000..fbd0180 --- /dev/null +++ b/typing_extensions/pyproject.toml @@ -0,0 +1,58 @@ +# Build system requirements. +[build-system] +requires = ["flit_core >=3.4,<4"] +build-backend = "flit_core.buildapi" + +# Project metadata +[project] +name = "typing_extensions" +version = "4.1.1" +description = "Backported and Experimental Type Hints for Python 3.6+" +readme = "README.rst" +requires-python = ">=3.7" +urls.Home = "https://github.com/python/typing/blob/master/typing_extensions/README.rst" +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 :: 3 - Alpha", + "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.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Topic :: Software Development" +] + +# 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", + "README.rst", + "*/test*.py" +] +exclude = [] diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py new file mode 100644 index 0000000..b8fe5e3 --- /dev/null +++ b/typing_extensions/src/test_typing_extensions.py @@ -0,0 +1,2779 @@ +import sys +import os +import abc +import contextlib +import collections +import collections.abc +from functools import lru_cache +import inspect +import pickle +import subprocess +import types +from unittest import TestCase, main, skipUnless, skipIf +from test import ann_module, ann_module2, ann_module3 +import typing +from typing import TypeVar, Optional, Union, Any, AnyStr +from typing import T, KT, VT # Not in __all__. +from typing import Tuple, List, Dict, Iterable, Iterator, Callable +from typing import Generic, NamedTuple +from typing import no_type_check +import typing_extensions +from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict, Self +from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard +from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired +from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict +from typing_extensions import TypeVarTuple, Unpack, dataclass_transform, reveal_type, Never, assert_never, LiteralString +from typing_extensions import assert_type, get_type_hints, get_origin, get_args + +# Flags used to mark tests that only apply after a specific +# version of the typing module. +TYPING_3_8_0 = sys.version_info[:3] >= (3, 8, 0) +TYPING_3_10_0 = sys.version_info[:3] >= (3, 10, 0) +TYPING_3_11_0 = sys.version_info[:3] >= (3, 11, 0) + + +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} + targets = [some] + + # On 3.7.0 and 3.7.1, https://github.com/python/cpython/pull/10772 + # wasn't applied yet and NoReturn fails _type_check. + if not ((3, 7, 0) <= sys.version_info < (3, 7, 2)): + targets.append(some_str) + for target in targets: + with self.subTest(target=target): + self.assertEqual(gth(target), expected) + + 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 ClassVarTests(BaseTestCase): + + def test_basics(self): + 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): + with self.assertRaises(TypeError): + Final[1] + with self.assertRaises(TypeError): + Final[int, str] + with self.assertRaises(TypeError): + Final[int][str] + + def test_repr(self): + if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(Final), mod_name + '.Final') + cv = Final[int] + self.assertEqual(repr(cv), mod_name + '.Final[int]') + cv = Final[Employee] + self.assertEqual(repr(cv), mod_name + f'.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): + 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): + 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") # noqa + + 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) # noqa + + +class LiteralTests(BaseTestCase): + def test_basics(self): + Literal[1] + Literal[1, 2, 3] + Literal["x", "y", "z"] + Literal[None] + + 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): + if hasattr(typing, 'Literal'): + 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]") + + 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] + + +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() + + +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 +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 AnnotatedMovie(TypedDict): + title: Annotated[Required[str], "foobar"] + year: NotRequired[Annotated[int, 2000]] + + +gth = get_type_hints + + +class GetTypeHintTests(BaseTestCase): + def test_get_type_hints_modules(self): + ann_module_type_hints = {1: 2, 'f': Tuple[int, int], 'x': int, 'y': str} + if (TYPING_3_11_0 + or (TYPING_3_10_0 and sys.version_info.releaselevel in {'candidate', 'final'})): + # More tests were added in 3.10rc1. + ann_module_type_hints['u'] = int | float + self.assertEqual(gth(ann_module), ann_module_type_hints) + self.assertEqual(gth(ann_module2), {}) + self.assertEqual(gth(ann_module3), {}) + + def test_get_type_hints_classes(self): + self.assertEqual(gth(ann_module.C, ann_module.__dict__), + {'y': Optional[ann_module.C]}) + self.assertIsInstance(gth(ann_module.j_class), dict) + self.assertEqual(gth(ann_module.M), {'123': 123, 'o': type}) + self.assertEqual(gth(ann_module.D), + {'j': str, 'k': str, 'y': Optional[ann_module.C]}) + self.assertEqual(gth(ann_module.Y), {'z': int}) + self.assertEqual(gth(ann_module.h_class), + {'y': Optional[ann_module.C]}) + self.assertEqual(gth(ann_module.S), {'x': str, 'y': str}) + self.assertEqual(gth(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'): ... # noqa + self.assertTrue(NoTpCheck.__no_type_check__) + self.assertTrue(NoTpCheck.Inn.__init__.__no_type_check__) + self.assertEqual(gth(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(ann_module2.CV, ann_module2.__dict__), + {'var': ClassVar[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, ...)) + 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): + ns = {} + exec( + "async def foo() -> typing_extensions.Awaitable[int]:\n" + " return await AwaitableWrapper(42)\n", + globals(), ns) + foo = ns['foo'] + 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): + ns = {} + exec( + "async def foo():\n" + " return\n", + globals(), ns) + foo = ns['foo'] + 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 = range(10) # type: Iterator[int] + 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 = range(10) # type: Iterator[int] + 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): + ns = {} + exec("async def f():\n" + " yield 42\n", globals(), ns) + g = ns['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 + + ns = {} + exec('async def g(): yield 0', globals(), ns) + g = ns['g'] + 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): + + def test_basic(self): + UserId = NewType('UserId', int) + UserName = NewType('UserName', str) + self.assertIsInstance(UserId(5), int) + self.assertIsInstance(UserName('Joe'), str) + self.assertEqual(UserId(5) + 1, 6) + + def test_errors(self): + UserId = NewType('UserId', int) + UserName = NewType('UserName', str) + with self.assertRaises(TypeError): + issubclass(UserId, int) + with self.assertRaises(TypeError): + class D(UserName): + pass + + +class Coordinate(Protocol): + x: int + y: int + +@runtime +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 +class Position(XAxis, YAxis, Protocol): + pass + +@runtime +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 + + +class ProtocolTests(BaseTestCase): + + def test_basic_protocol(self): + @runtime + 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 + 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_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_cannot_instantiate_abstract(self): + @runtime + 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 + 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 + 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 + class P(Protocol): + def x(self): ... + @runtime + 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) + with self.assertRaises(TypeError): + issubclass(C, PG[T]) + with self.assertRaises(TypeError): + issubclass(C, PG[C]) + with self.assertRaises(TypeError): + issubclass(C, BadP) + with self.assertRaises(TypeError): + issubclass(C, BadPG) + with self.assertRaises(TypeError): + issubclass(P, PG[T]) + with self.assertRaises(TypeError): + issubclass(PG, PG[int]) + + def test_protocols_issubclass_non_callable(self): + class C: + x = 1 + @runtime + class PNonCall(Protocol): + x = 1 + with self.assertRaises(TypeError): + issubclass(C, PNonCall) + self.assertIsInstance(C(), PNonCall) + PNonCall.register(C) + with self.assertRaises(TypeError): + 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.assertRaises(TypeError): + issubclass(D, PNonCall) + + def test_protocols_isinstance(self): + T = TypeVar('T') + @runtime + class P(Protocol): + def meth(x): ... + @runtime + class PG(Protocol[T]): + def meth(x): ... + class BadP(Protocol): + def meth(x): ... + class BadPG(Protocol[T]): + def meth(x): ... + class C: + def meth(x): ... + self.assertIsInstance(C(), P) + self.assertIsInstance(C(), PG) + with self.assertRaises(TypeError): + isinstance(C(), PG[T]) + with self.assertRaises(TypeError): + isinstance(C(), PG[C]) + with self.assertRaises(TypeError): + isinstance(C(), BadP) + with self.assertRaises(TypeError): + isinstance(C(), BadPG) + + 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_protocols_isinstance_init(self): + T = TypeVar('T') + @runtime + class P(Protocol): + x = 1 + @runtime + 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_support_register(self): + @runtime + 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 + 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 + 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 + class PR(Protocol): + def meth(self): pass + class NonP(P): + x = 1 + class NonPR(PR): pass + class C: + x = 1 + class D: + def meth(self): pass + self.assertNotIsInstance(C(), NonP) + self.assertNotIsInstance(D(), NonPR) + self.assertNotIsSubclass(C, NonP) + self.assertNotIsSubclass(D, NonPR) + self.assertIsInstance(NonPR(), PR) + self.assertIsSubclass(NonPR, PR) + + 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) + + def test_issubclass_fails_correctly(self): + @runtime + class P(Protocol): + x = 1 + class C: pass + with self.assertRaises(TypeError): + issubclass(C(), P) + + def test_defining_generic_protocols(self): + T = TypeVar('T') + S = TypeVar('S') + @runtime + 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 + 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 + 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] + + 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 + 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) + if hasattr(typing, 'Protocol'): + self.assertFalse(P._is_runtime_protocol) + else: + with self.assertRaises(AttributeError): + 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 + class C: pass + class Proto(Protocol): + x = 1 + with self.assertRaises(TypeError): + @runtime + class Concrete(Proto): + pass + + def test_none_treated_correctly(self): + @runtime + class P(Protocol): + x = None # type: int + 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(C(), P) + self.assertIsInstance(D(), P) + + def test_protocols_in_unions(self): + class P(Protocol): + x = None # type: int + 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 + 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) + + def test_no_init_same_for_different_protocol_implementations(self): + class CustomProtocolWithoutInitA(Protocol): + pass + + class CustomProtocolWithoutInitB(Protocol): + pass + + self.assertEqual(CustomProtocolWithoutInitA.__init__, CustomProtocolWithoutInitB.__init__) + + +class TypedDictTests(BaseTestCase): + + def test_basics_iterable_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) + + def test_basics_keywords_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) + + def test_typeddict_special_keyword_names(self): + 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}) + + @skipIf(hasattr(typing, 'TypedDict'), "Should be tested by upstream") + 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) + + with self.assertWarns(DeprecationWarning): + Emp = TypedDict(_typename='Emp', name=str, id=int) + self.assertEqual(Emp.__name__, 'Emp') + self.assertEqual(Emp.__annotations__, {'name': str, 'id': int}) + + with self.assertWarns(DeprecationWarning): + Emp = TypedDict('Emp', _fields={'name': str, 'id': int}) + self.assertEqual(Emp.__name__, 'Emp') + self.assertEqual(Emp.__annotations__, {'name': str, 'id': int}) + + def test_typeddict_errors(self): + Emp = TypedDict('Emp', {'name': str, 'id': int}) + if hasattr(typing, "Required"): + self.assertEqual(TypedDict.__module__, 'typing') + else: + 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) + with self.assertRaises(TypeError): + TypedDict('Hi', x=1) + with self.assertRaises(TypeError): + TypedDict('Hi', [('x', int), ('y', 1)]) + with self.assertRaises(TypeError): + TypedDict('Hi', [('x', int)], y=int) + + def test_py36_class_syntax_usage(self): + self.assertEqual(LabelPoint2D.__name__, 'LabelPoint2D') + self.assertEqual(LabelPoint2D.__module__, __name__) + self.assertEqual(get_type_hints(LabelPoint2D), {'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_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): + assert Point2Dor3D.__required_keys__ == frozenset(['x', 'y']) + assert Point2Dor3D.__optional_keys__ == frozenset(['z']) + + def test_required_notrequired_keys(self): + assert NontotalMovie.__required_keys__ == frozenset({'title'}) + assert NontotalMovie.__optional_keys__ == frozenset({'year'}) + + assert TotalMovie.__required_keys__ == frozenset({'title'}) + assert TotalMovie.__optional_keys__ == frozenset({'year'}) + + + def test_keys_inheritance(self): + assert BaseAnimal.__required_keys__ == frozenset(['name']) + assert BaseAnimal.__optional_keys__ == frozenset([]) + assert get_type_hints(BaseAnimal) == {'name': str} + + assert Animal.__required_keys__ == frozenset(['name']) + assert Animal.__optional_keys__ == frozenset(['tail', 'voice']) + assert get_type_hints(Animal) == { + 'name': str, + 'tail': bool, + 'voice': str, + } + + assert Cat.__required_keys__ == frozenset(['name', 'fur_color']) + assert Cat.__optional_keys__ == frozenset(['tail', 'voice']) + assert get_type_hints(Cat) == { + 'fur_color': str, + 'name': str, + 'tail': bool, + 'voice': str, + } + + def test_is_typeddict(self): + assert is_typeddict(Point2D) is True + assert is_typeddict(Point2Dor3D) is True + assert is_typeddict(Union[str, int]) is False + # classes, not instances + assert is_typeddict(Point2D()) is False + + @skipUnless(TYPING_3_8_0, "Python 3.8+ required") + 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 + + +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 + + if sys.version_info[:2] >= (3, 7): + self.assertEqual(get_type_hints(C, globals())["classvar"], ClassVar[int]) + self.assertEqual(get_type_hints(C, globals())["const"], Final[int]) + else: + self.assertEqual( + get_type_hints(C, globals())["classvar"], + Annotated[ClassVar[int], "a decoration"] + ) + self.assertEqual( + get_type_hints(C, globals())["const"], Annotated[Final[int], "Const"] + ) + + 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_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]], + } + + +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 = Employee # type: TypeAlias + + 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) + # 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_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') + + 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 + + G1 = X[int, P_2] + self.assertEqual(G1.__args__, (int, P_2)) + self.assertEqual(G1.__parameters__, (P_2,)) + + G2 = X[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 + + def test_pickle(self): + global P, P_co, P_contra + P = ParamSpec('P') + P_co = ParamSpec('P_co', covariant=True) + P_contra = ParamSpec('P_contra', contravariant=True) + for proto in range(pickle.HIGHEST_PROTOCOL): + with self.subTest(f'Pickle protocol {proto}'): + for paramspec in (P, P_co, P_contra): + 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__) + + 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] + + 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]), 'typing_extensions.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 + self.assertEqual(C[int, Unpack[Xs]].__args__, (int, Unpack[Xs])) + self.assertEqual(C[Unpack[Xs], int].__args__, (Unpack[Xs], int)) + self.assertEqual(C[int, Unpack[Xs], str].__args__, + (int, Unpack[Xs], str)) + + def test_class(self): + Ts = TypeVarTuple('Ts') + + class C(Generic[Unpack[Ts]]): pass + self.assertEqual(C[int].__args__, (int,)) + self.assertEqual(C[int, str].__args__, (int, str)) + + with self.assertRaises(TypeError): + class C(Generic[Unpack[Ts], int]): pass + + T1 = TypeVar('T') + T2 = TypeVar('T') + class C(Generic[T1, T2, Unpack[Ts]]): pass + self.assertEqual(C[int, str].__args__, (int, str)) + self.assertEqual(C[int, str, float].__args__, (int, str, float)) + self.assertEqual(C[int, str, float, bool].__args__, (int, str, float, bool)) + with self.assertRaises(TypeError): + C[int] + + +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)] + self.assertEqual(t.__args__, (Ts.__unpacked__,)) + self.assertEqual(t.__parameters__, (Ts,)) + + +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() + self.assertIs(obj, reveal_type(obj)) + + +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, + "field_descriptors": (), + } + ) + 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)(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, + "field_descriptors": (), + } + ) + 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_descriptors=(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, + "field_descriptors": (Field,), + } + ) + self.assertIsInstance(CustomerModel, Decorated) + + +class AllTests(BaseTestCase): + + 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_typing_extensions_defers_when_possible(self): + exclude = { + 'overload', + 'Text', + 'TypedDict', + 'TYPE_CHECKING', + 'Final', + 'get_type_hints', + 'is_typeddict', + } + if sys.version_info < (3, 10): + exclude |= {'get_args', 'get_origin'} + if sys.version_info < (3, 11): + exclude.add('final') + 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).') + + + +if __name__ == '__main__': + main() diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py new file mode 100644 index 0000000..5698a76 --- /dev/null +++ b/typing_extensions/src/typing_extensions.py @@ -0,0 +1,1882 @@ +import abc +import collections +import collections.abc +import operator +import sys +import types as _types +import typing + + +# Please keep __all__ alphabetized within each category. +__all__ = [ + # Super-special typing primitives. + 'ClassVar', + 'Concatenate', + 'Final', + 'LiteralString', + 'ParamSpec', + 'Self', + 'Type', + 'TypeVarTuple', + 'Unpack', + + # ABCs (from collections.abc). + 'Awaitable', + 'AsyncIterator', + 'AsyncIterable', + 'Coroutine', + 'AsyncGenerator', + 'AsyncContextManager', + 'ChainMap', + + # Concrete collection types. + 'ContextManager', + 'Counter', + 'Deque', + 'DefaultDict', + 'OrderedDict', + 'TypedDict', + + # Structural checks, a.k.a. protocols. + 'SupportsIndex', + + # One-off things. + 'Annotated', + 'assert_never', + 'dataclass_transform', + 'final', + 'get_args', + 'get_origin', + 'get_type_hints', + 'IntVar', + 'is_typeddict', + 'Literal', + 'NewType', + 'overload', + 'Protocol', + 'reveal_type', + 'runtime', + 'runtime_checkable', + 'Text', + 'TypeAlias', + 'TypeGuard', + 'TYPE_CHECKING', + 'Never', + 'NoReturn', + 'Required', + 'NotRequired', +] + +# 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. + +_marker = object() + + +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. + +ClassVar = typing.ClassVar + +# On older versions of typing there is an internal class named "Final". +# 3.8+ +if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7): + Final = typing.Final +# 3.7 +else: + class _FinalForm(typing._SpecialForm, _root=True): + + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + f'{self._name} accepts only single type') + return typing._GenericAlias(self, (item,)) + + Final = _FinalForm('Final', + doc="""A special typing construct to indicate that a name + cannot be re-assigned or overridden in a subclass. + For example: + + MAX_SIZE: Final = 9000 + MAX_SIZE += 1 # Error reported by type checker + + class Connection: + TIMEOUT: Final[int] = 10 + class FastConnector(Connection): + TIMEOUT = 1 # Error reported by type checker + + There is no runtime checking of these properties.""") + +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) + + +# 3.8+: +if hasattr(typing, 'Literal'): + Literal = typing.Literal +# 3.7: +else: + class _LiteralForm(typing._SpecialForm, _root=True): + + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + return typing._GenericAlias(self, parameters) + + Literal = _LiteralForm('Literal', + 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 # noqa +overload = typing.overload + + +# 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 + +# 3.7.2+ +if hasattr(typing, 'OrderedDict'): + OrderedDict = typing.OrderedDict +# 3.7.0-3.7.2 +else: + OrderedDict = typing._alias(collections.OrderedDict, (KT, VT)) + +Counter = typing.Counter +ChainMap = typing.ChainMap +AsyncGenerator = typing.AsyncGenerator +NewType = typing.NewType +Text = typing.Text +TYPE_CHECKING = typing.TYPE_CHECKING + + +_PROTO_WHITELIST = ['Callable', 'Awaitable', + 'Iterable', 'Iterator', 'AsyncIterable', 'AsyncIterator', + 'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', + 'ContextManager', 'AsyncContextManager'] + + +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 list(base.__dict__.keys()) + list(annotations.keys()): + if (not attr.startswith('_abc_') and attr not in ( + '__abstractmethods__', '__annotations__', '__weakref__', + '_is_protocol', '_is_runtime_protocol', '__dict__', + '__args__', '__slots__', + '__next_in_mro__', '__parameters__', '__origin__', + '__orig_bases__', '__extra__', '__tree_hash__', + '__doc__', '__subclasshook__', '__init__', '__new__', + '__module__', '_MutableMapping__marker', '_gorg')): + attrs.add(attr) + return attrs + + +def _is_callable_members_only(cls): + return all(callable(getattr(cls, attr, None)) for attr in _get_protocol_attrs(cls)) + + +# 3.8+ +if hasattr(typing, 'Protocol'): + Protocol = typing.Protocol +# 3.7 +else: + + def _no_init(self, *args, **kwargs): + if type(self)._is_protocol: + raise TypeError('Protocols cannot be instantiated') + + class _ProtocolMeta(abc.ABCMeta): + # This metaclass is a bit unfortunate and exists only because of the lack + # of __instancehook__. + def __instancecheck__(cls, instance): + # We need this method for situations where attributes are + # assigned in __init__. + if ((not getattr(cls, '_is_protocol', False) or + _is_callable_members_only(cls)) and + issubclass(instance.__class__, cls)): + return True + if cls._is_protocol: + if all(hasattr(instance, attr) and + (not callable(getattr(cls, attr, None)) or + getattr(instance, attr) is not None) + for attr in _get_protocol_attrs(cls)): + return True + return super().__instancecheck__(instance) + + class Protocol(metaclass=_ProtocolMeta): + # There is quite a lot of overlapping code with typing.Generic. + # Unfortunately it is hard to avoid this while these live in two different + # modules. The duplicated code will be removed when Protocol is moved to typing. + """Base class for protocol classes. Protocol classes are defined as:: + + class Proto(Protocol): + def meth(self) -> int: + ... + + Such classes are primarily used with static type checkers that recognize + structural subtyping (static duck-typing), for example:: + + class C: + def meth(self) -> int: + return 0 + + def func(x: Proto) -> int: + return x.meth() + + func(C()) # Passes static type check + + See PEP 544 for details. Protocol classes decorated with + @typing_extensions.runtime act as simple-minded runtime protocol that checks + only the presence of given attributes, ignoring their type signatures. + + Protocol classes can be generic, they are defined as:: + + class GenProto(Protocol[T]): + def meth(self) -> T: + ... + """ + __slots__ = () + _is_protocol = True + + def __new__(cls, *args, **kwds): + if cls is Protocol: + raise TypeError("Type Protocol cannot be instantiated; " + "it can only be used as a base class") + return super().__new__(cls) + + @typing._tp_cache + def __class_getitem__(cls, params): + if not isinstance(params, tuple): + params = (params,) + if not params and cls is not typing.Tuple: + raise TypeError( + f"Parameter list to {cls.__qualname__}[...] cannot be empty") + msg = "Parameters to generic types must be types." + params = tuple(typing._type_check(p, msg) for p in params) # noqa + if cls is Protocol: + # Generic can only be subscripted with unique type variables. + if not all(isinstance(p, typing.TypeVar) for p in params): + i = 0 + while isinstance(params[i], typing.TypeVar): + i += 1 + raise TypeError( + "Parameters to Protocol[...] must all be type variables." + f" Parameter {i + 1} is {params[i]}") + if len(set(params)) != len(params): + raise TypeError( + "Parameters to Protocol[...] must all be unique") + else: + # Subscripting a regular Generic subclass. + _check_generic(cls, params, len(cls.__parameters__)) + return typing._GenericAlias(cls, params) + + def __init_subclass__(cls, *args, **kwargs): + tvars = [] + if '__orig_bases__' in cls.__dict__: + error = typing.Generic in cls.__orig_bases__ + else: + error = typing.Generic in cls.__bases__ + if error: + raise TypeError("Cannot inherit from plain Generic") + if '__orig_bases__' in cls.__dict__: + tvars = typing._collect_type_vars(cls.__orig_bases__) + # Look for Generic[T1, ..., Tn] or Protocol[T1, ..., Tn]. + # If found, tvars must be a subset of it. + # If not found, tvars is it. + # Also check for and reject plain Generic, + # and reject multiple Generic[...] and/or Protocol[...]. + gvars = None + for base in cls.__orig_bases__: + if (isinstance(base, typing._GenericAlias) and + base.__origin__ in (typing.Generic, Protocol)): + # for error messages + the_base = base.__origin__.__name__ + if gvars is not None: + raise TypeError( + "Cannot inherit from Generic[...]" + " and/or Protocol[...] multiple types.") + gvars = base.__parameters__ + if gvars is None: + gvars = tvars + else: + tvarset = set(tvars) + gvarset = set(gvars) + if not tvarset <= gvarset: + s_vars = ', '.join(str(t) for t in tvars if t not in gvarset) + s_args = ', '.join(str(g) for g in gvars) + raise TypeError(f"Some type variables ({s_vars}) are" + f" not listed in {the_base}[{s_args}]") + tvars = gvars + cls.__parameters__ = tuple(tvars) + + # Determine if this is a protocol or a concrete subclass. + if not cls.__dict__.get('_is_protocol', None): + cls._is_protocol = any(b is Protocol for b in cls.__bases__) + + # Set (or override) the protocol subclass hook. + def _proto_hook(other): + if not cls.__dict__.get('_is_protocol', None): + return NotImplemented + if not getattr(cls, '_is_runtime_protocol', False): + if sys._getframe(2).f_globals['__name__'] in ['abc', 'functools']: + return NotImplemented + raise TypeError("Instance and class checks can only be used with" + " @runtime protocols") + if not _is_callable_members_only(cls): + if sys._getframe(2).f_globals['__name__'] in ['abc', 'functools']: + return NotImplemented + raise TypeError("Protocols with non-method members" + " don't support issubclass()") + if not isinstance(other, type): + # Same error as for issubclass(1, int) + raise TypeError('issubclass() arg 1 must be a class') + for attr in _get_protocol_attrs(cls): + for base in other.__mro__: + if attr in base.__dict__: + if base.__dict__[attr] is None: + return NotImplemented + break + annotations = getattr(base, '__annotations__', {}) + if (isinstance(annotations, typing.Mapping) and + attr in annotations and + isinstance(other, _ProtocolMeta) and + other._is_protocol): + break + else: + return NotImplemented + return True + if '__subclasshook__' not in cls.__dict__: + cls.__subclasshook__ = _proto_hook + + # We have nothing more to do for non-protocols. + if not cls._is_protocol: + return + + # Check consistency of bases. + for base in cls.__bases__: + if not (base in (object, typing.Generic) or + base.__module__ == 'collections.abc' and + base.__name__ in _PROTO_WHITELIST or + isinstance(base, _ProtocolMeta) and base._is_protocol): + raise TypeError('Protocols can only inherit from other' + f' protocols, got {repr(base)}') + cls.__init__ = _no_init + + +# 3.8+ +if hasattr(typing, 'runtime_checkable'): + runtime_checkable = typing.runtime_checkable +# 3.7 +else: + def runtime_checkable(cls): + """Mark a protocol class as a runtime protocol, so that it + can be used with isinstance() and issubclass(). Raise TypeError + if applied to a non-protocol class. + + This allows a simple-minded structural check very similar to the + one-offs in collections.abc such as Hashable. + """ + if not isinstance(cls, _ProtocolMeta) or not cls._is_protocol: + raise TypeError('@runtime_checkable can be only applied to protocol classes,' + f' got {cls!r}') + cls._is_runtime_protocol = True + return cls + + +# Exists for backwards compatibility. +runtime = runtime_checkable + + +# 3.8+ +if hasattr(typing, 'SupportsIndex'): + SupportsIndex = typing.SupportsIndex +# 3.7 +else: + @runtime_checkable + class SupportsIndex(Protocol): + __slots__ = () + + @abc.abstractmethod + def __index__(self) -> int: + pass + + +if hasattr(typing, "Required"): + # 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. + TypedDict = typing.TypedDict + _TypedDictMeta = typing._TypedDictMeta + is_typeddict = typing.is_typeddict +else: + def _check_fails(cls, other): + try: + if sys._getframe(1).f_globals['__name__'] not in ['abc', + 'functools', + 'typing']: + # Typed dicts are only for static structural subtyping. + raise TypeError('TypedDict does not support instance and class checks') + except (AttributeError, ValueError): + pass + return False + + def _dict_new(*args, **kwargs): + if not args: + raise TypeError('TypedDict.__new__(): not enough arguments') + _, args = args[0], args[1:] # allow the "cls" keyword be passed + return dict(*args, **kwargs) + + _dict_new.__text_signature__ = '($cls, _typename, _fields=None, /, **kwargs)' + + def _typeddict_new(*args, total=True, **kwargs): + if not args: + raise TypeError('TypedDict.__new__(): not enough arguments') + _, args = args[0], args[1:] # allow the "cls" keyword be passed + if args: + typename, args = args[0], args[1:] # allow the "_typename" keyword be passed + elif '_typename' in kwargs: + typename = kwargs.pop('_typename') + import warnings + warnings.warn("Passing '_typename' as keyword argument is deprecated", + DeprecationWarning, stacklevel=2) + else: + raise TypeError("TypedDict.__new__() missing 1 required positional " + "argument: '_typename'") + if args: + try: + fields, = args # allow the "_fields" keyword be passed + except ValueError: + raise TypeError('TypedDict.__new__() takes from 2 to 3 ' + f'positional arguments but {len(args) + 2} ' + 'were given') + elif '_fields' in kwargs and len(kwargs) == 1: + fields = kwargs.pop('_fields') + import warnings + warnings.warn("Passing '_fields' as keyword argument is deprecated", + DeprecationWarning, stacklevel=2) + else: + fields = None + + if fields is None: + fields = kwargs + elif kwargs: + raise TypeError("TypedDict takes either a dict or keyword arguments," + " but not both") + + ns = {'__annotations__': dict(fields)} + try: + # Setting correct module is necessary to make typed dict classes pickleable. + ns['__module__'] = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + pass + + return _TypedDictMeta(typename, (), ns, total=total) + + _typeddict_new.__text_signature__ = ('($cls, _typename, _fields=None,' + ' /, *, total=True, **kwargs)') + + class _TypedDictMeta(type): + def __init__(cls, name, bases, ns, total=True): + super().__init__(name, bases, ns) + + def __new__(cls, name, bases, ns, total=True): + # Create new typed dict class object. + # This method is called directly when TypedDict is subclassed, + # or via _typeddict_new when TypedDict is instantiated. This way + # TypedDict supports all three syntaxes described in its docstring. + # Subclasses and instances of TypedDict return actual dictionaries + # via _dict_new. + ns['__new__'] = _typeddict_new if name == 'TypedDict' else _dict_new + tp_dict = super().__new__(cls, name, (dict,), ns) + + annotations = {} + own_annotations = ns.get('__annotations__', {}) + msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type" + own_annotations = { + n: typing._type_check(tp, msg) for n, tp in own_annotations.items() + } + required_keys = set() + optional_keys = set() + + for base in bases: + annotations.update(base.__dict__.get('__annotations__', {})) + required_keys.update(base.__dict__.get('__required_keys__', ())) + optional_keys.update(base.__dict__.get('__optional_keys__', ())) + + annotations.update(own_annotations) + for annotation_key, annotation_type in own_annotations.items(): + annotation_origin = get_origin(annotation_type) + if annotation_origin is Annotated: + annotation_args = get_args(annotation_type) + if annotation_args: + annotation_type = annotation_args[0] + annotation_origin = get_origin(annotation_type) + + if annotation_origin is Required: + required_keys.add(annotation_key) + elif annotation_origin is NotRequired: + optional_keys.add(annotation_key) + elif total: + required_keys.add(annotation_key) + else: + optional_keys.add(annotation_key) + + tp_dict.__annotations__ = annotations + tp_dict.__required_keys__ = frozenset(required_keys) + tp_dict.__optional_keys__ = frozenset(optional_keys) + if not hasattr(tp_dict, '__total__'): + tp_dict.__total__ = total + return tp_dict + + __instancecheck__ = __subclasscheck__ = _check_fails + + TypedDict = _TypedDictMeta('TypedDict', (dict,), {}) + TypedDict.__module__ = __name__ + TypedDict.__doc__ = \ + """A simple typed name space. At runtime it is equivalent to a plain dict. + + TypedDict creates a dictionary type that expects all of its + instances to have a certain set of keys, with each key + associated with a value of a consistent type. This expectation + is not checked at runtime but is only enforced by type checkers. + 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 two additional equivalent forms:: + + Point2D = TypedDict('Point2D', x=int, y=int, label=str) + Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str}) + + The class syntax is only supported in Python 3.6+, while two other + syntax forms work for Python 2.7 and 3.2+ + """ + + 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 + """ + return isinstance(tp, tuple(_TYPEDDICT_TYPES)) + + +if hasattr(typing, "assert_type"): + assert_type = typing.assert_type + +else: + def assert_type(__val, __typ): + """Assert (to the type checker) that the value is of the given type. + + When the type checker encounters a call to assert_type(), it + emits an error if the value is not of the specified type:: + + def greet(name: str) -> None: + assert_type(name, str) # ok + assert_type(name, int) # type checker error + + At runtime this returns the first argument unchanged and otherwise + does nothing. + """ + return __val + + +if hasattr(typing, "Required"): + get_type_hints = typing.get_type_hints +else: + import functools + import types + + # 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"): + hint = typing.get_type_hints( + obj, globalns=globalns, localns=localns, include_extras=True + ) + else: + 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.7-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.7-3.9 +else: + try: + # 3.9+ + from typing import _BaseGenericAlias + except ImportError: + _BaseGenericAlias = typing._GenericAlias + try: + # 3.9+ + from typing import GenericAlias + except ImportError: + GenericAlias = typing._GenericAlias + + def get_origin(tp): + """Get the unsubscripted version of a type. + + This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar + and Annotated. Return None for unsupported types. Examples:: + + get_origin(Literal[42]) is Literal + get_origin(int) is None + get_origin(ClassVar[int]) is ClassVar + get_origin(Generic) is Generic + get_origin(Generic[T]) is Generic + get_origin(Union[T, int]) is Union + get_origin(List[Tuple[T, T]][int]) == list + get_origin(P.args) is P + """ + if isinstance(tp, _AnnotatedAlias): + return Annotated + if isinstance(tp, (typing._GenericAlias, GenericAlias, _BaseGenericAlias, + ParamSpecArgs, ParamSpecKwargs)): + return tp.__origin__ + if tp is typing.Generic: + return typing.Generic + return None + + def get_args(tp): + """Get type arguments with all substitutions performed. + + 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, 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): + class _TypeAliasForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + @_TypeAliasForm + 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.7-3.8 +else: + class _TypeAliasForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + TypeAlias = _TypeAliasForm('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.""") + + +# Python 3.10+ has PEP 612 +if hasattr(typing, 'ParamSpecArgs'): + ParamSpecArgs = typing.ParamSpecArgs + ParamSpecKwargs = typing.ParamSpecKwargs +# 3.7-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'): + ParamSpec = typing.ParamSpec +# 3.7-3.9 +else: + + # Inherits from list as a workaround for Callable checks in Python < 3.9.2. + class ParamSpec(list): + """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): + super().__init__([self]) + self.__name__ = name + self.__covariant__ = bool(covariant) + self.__contravariant__ = bool(contravariant) + if bound: + self.__bound__ = typing._type_check(bound, 'Bound must be a type.') + else: + self.__bound__ = None + + # for pickling: + try: + def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + def_mod = None + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + def __repr__(self): + if 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.7-3.9 +if not hasattr(typing, 'Concatenate'): + # Inherits from list as a workaround for Callable checks in Python < 3.9.2. + class _ConcatenateGenericAlias(list): + + # Trick Generic into looking into this for __parameters__. + __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.7-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 +# 3.9 +elif sys.version_info[:2] >= (3, 9): + @_TypeAliasForm + 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.7-8 +else: + class _ConcatenateForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + 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): + class _TypeGuardForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + @_TypeGuardForm + 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 single type.') + return typing._GenericAlias(self, (item,)) +# 3.7-3.8 +else: + class _TypeGuardForm(typing._SpecialForm, _root=True): + + def __repr__(self): + return 'typing_extensions.' + self._name + + 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"): + 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"): + 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"): + 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'): + Required = typing.Required + NotRequired = typing.NotRequired +elif sys.version_info[:2] >= (3, 9): + class _ExtensionsSpecialForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + @_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 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 single type') + return typing._GenericAlias(self, (item,)) + +else: + class _RequiredForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + '{} accepts only single type'.format(self._name)) + 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 sys.version_info[:2] >= (3, 9): + class _UnpackSpecialForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + class _UnpackAlias(typing._GenericAlias, _root=True): + __class__ = typing.TypeVar + + @_UnpackSpecialForm + def Unpack(self, parameters): + """A special typing construct to unpack a variadic type. For example: + + Shape = TypeVarTuple('Shape') + Batch = NewType('Batch', int) + + def add_batch_axis( + x: Array[Unpack[Shape]] + ) -> Array[Batch, Unpack[Shape]]: ... + + """ + item = typing._type_check(parameters, f'{self._name} accepts only single type') + return _UnpackAlias(self, (item,)) + + def _is_unpack(obj): + return isinstance(obj, _UnpackAlias) + +else: + class _UnpackAlias(typing._GenericAlias, _root=True): + __class__ = typing.TypeVar + + class _UnpackForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + f'{self._name} accepts only single type') + return _UnpackAlias(self, (item,)) + + Unpack = _UnpackForm( + 'Unpack', + doc="""A special typing construct to unpack a variadic type. For example: + + Shape = TypeVarTuple('Shape') + Batch = NewType('Batch', int) + + def add_batch_axis( + x: Array[Unpack[Shape]] + ) -> Array[Batch, Unpack[Shape]]: ... + + """) + + def _is_unpack(obj): + return isinstance(obj, _UnpackAlias) + + +class TypeVarTuple: + """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): + self.__name__ = name + + # for pickling: + try: + def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + def_mod = None + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + 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"): + reveal_type = typing.reveal_type +else: + 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"): + assert_never = typing.assert_never +else: + 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 hasattr(typing, 'dataclass_transform'): + dataclass_transform = typing.dataclass_transform +else: + def dataclass_transform( + *, + eq_default: bool = True, + order_default: bool = False, + kw_only_default: bool = False, + field_descriptors: typing.Tuple[ + typing.Union[typing.Type[typing.Any], typing.Callable[..., 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. + - ``field_descriptors`` 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, + "field_descriptors": field_descriptors, + } + return cls_or_fn + return decorator + + +# We have to do some monkey patching to deal with the dual nature of +# Unpack/TypeVarTuple: +# - We want Unpack to be a kind of TypeVar so it gets accepted in +# Generic[Unpack[Ts]] +# - We want it to *not* be treated as a TypeVar for the purposes of +# counting generic parameters, so that when we subscript a generic, +# the runtime doesn't try to substitute the Unpack with the subscripted type. +if not hasattr(typing, "TypeVarTuple"): + typing._collect_type_vars = _collect_type_vars + typing._check_generic = _check_generic diff --git a/typing_extensions/tox.ini b/typing_extensions/tox.ini new file mode 100644 index 0000000..0886936 --- /dev/null +++ b/typing_extensions/tox.ini @@ -0,0 +1,7 @@ +[tox] +isolated_build = True +envlist = py36, py37, py38, py39, py310 + +[testenv] +changedir = src +commands = python -m unittest discover |