From bace34dfdab3564e0f7a1be0425056d6b1f604fe Mon Sep 17 00:00:00 2001 From: Nicola Sella Date: Fri, 16 Aug 2024 15:56:35 +0200 Subject: [PATCH 1/6] Delegate project management to hatch Changes introduced by this commit: Python Packaging - package definition and dependencies are incorporated in pyproject.toml - setup.py, setup.cfg, *requirements.txt are no longer needed - packaging upstream is done via hatch see: https://packaging.python.org/en/latest/tutorials/packaging-projects/#choosing-build-backend Dependencies - black and pylint are no longer required - tox is no longer required - testing deps are no longer part of the dependencies - docs are split in a separate package Automation - cirrus does not require lint checks anymore since they are delegated to a separate check in precommit - pre-commit is used to check black, isort, pylint and it's all Signed-off-by: Nicola Sella --- .cirrus.yml | 1 - .gitignore | 3 + .pre-commit-config.yaml | 10 +++ Makefile | 27 +++--- pyproject.toml | 195 ++++++++++++++++++++++++++++++++++------ requirements.txt | 7 -- setup.cfg | 52 ----------- setup.py | 25 ------ test-requirements.txt | 9 -- tox.ini | 39 -------- 10 files changed, 194 insertions(+), 174 deletions(-) create mode 100644 .pre-commit-config.yaml delete mode 100644 requirements.txt delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 test-requirements.txt delete mode 100644 tox.ini diff --git a/.cirrus.yml b/.cirrus.yml index 5dc87f7c..ac798748 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -46,7 +46,6 @@ gating_task: script: - make - - make lint test_task: name: "Test on Fedora" diff --git a/.gitignore b/.gitignore index d8127fa6..5af41ba4 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,6 @@ venv.bak/ # mypy .mypy_cache/ + +# test containers +.containerfile.* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..51daaddf --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +repos: +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.6.0 + hooks: + # Run the linter. + - id: ruff + args: [ --fix ] + # Run the formatter. + - id: ruff-format diff --git a/Makefile b/Makefile index 11ab79da..31f514bf 100644 --- a/Makefile +++ b/Makefile @@ -13,38 +13,35 @@ export PODMAN_VERSION ?= "5.3.0" .PHONY: podman podman: rm dist/* || : - $(PYTHON) -m pip install --user -r requirements.txt + $(PYTHON) -m pip install --user . + $(PYTHON) -m pip install --user hatch PODMAN_VERSION=$(PODMAN_VERSION) \ - $(PYTHON) setup.py sdist bdist bdist_wheel - -.PHONY: lint -lint: tox - $(PYTHON) -m tox -e black,pylint + $(PYTHON) -m hatch build .PHONY: tests -tests: tox - # see tox.ini for environment variable settings - $(PYTHON) -m tox -e coverage,py39,py310,py311,py312,py313 +tests: hatch + # run tests on all the envs set in tool.hatch.envs.test.matrix + $(PYTHON) -m hatch run test:cov .PHONY: unittest unittest: coverage run -m unittest discover -s podman/tests/unit - coverage report -m --skip-covered --fail-under=80 --omit=./podman/tests/* --omit=.tox/* --omit=/usr/lib/* + coverage report -m --skip-covered --fail-under=80 --omit=./podman/tests/* --omit=/usr/lib/* .PHONY: integration integration: coverage run -m unittest discover -s podman/tests/integration - coverage report -m --skip-covered --fail-under=80 --omit=./podman/tests/* --omit=.tox/* --omit=/usr/lib/* + coverage report -m --skip-covered --fail-under=80 --omit=./podman/tests/* --omit=/usr/lib/* -.PHONY: tox -tox: +.PHONY: hatch +hatch: ifeq (, $(shell which dnf)) brew install python@3.9 python@3.10 python@3.11 python@3.12 python@3.13 else -dnf install -y python3 python3.9 python3.10 python3.11 python3.12 python3.13 endif - # ensure tox is available. It will take care of other testing requirements - $(PYTHON) -m pip install --user tox + # ensure hatch is available. It will take care of other testing requirements + $(PYTHON) -m pip install --user hatch .PHONY: test-release test-release: SOURCE = $(shell find dist -regex '.*/podman-[0-9][0-9\.]*.tar.gz' -print) diff --git a/pyproject.toml b/pyproject.toml index 9db36747..2fc48ec0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,33 +1,176 @@ -[tool.black] -line-length = 100 -skip-string-normalization = true -preview = true -target-version = ["py39"] -include = '\.pyi?$' -exclude = ''' -/( - \.git - | \.tox - | \.venv - | \.history - | build - | dist - | docs - | hack -)/ -''' -[tool.isort] -profile = "black" -line_length = 100 [build-system] -# Any changes should be copied into requirements.txt, setup.cfg, and/or test-requirements.txt -requires = [ - "setuptools>=46.4", +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "podman" +dynamic = ["version"] +description = "Bindings for Podman RESTful API" +readme = "README.md" +license = "Apache-2.0" +requires-python = ">=3.9" +authors = [ + { name = "Brent Baude" }, + { name = "Jhon Honce", email = "jhonce@redhat.com" }, + { name = "Urvashi Mohnani" }, + { name = "Nicola Sella", email = "nsella@redhat.com" }, +] +keywords = [ + "libpod", + "podman", +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Software Development :: Libraries :: Python Modules", +] +# compatible releases +# ~= with version numbers +dependencies = [ + "requests >=2.24", + "tomli>=1.2.3; python_version<'3.11'", + "urllib3", +] + +[project.optional-dependencies] +progress_bar = [ + "rich >= 12.5.1", +] +docs = [ + "sphinx" +] + +[project.urls] +"Bug Tracker" = "https://github.com/containers/podman-py/issues" +Homepage = "https://github.com/containers/podman-py" +"Libpod API" = "https://docs.podman.io/en/latest/_static/api.html" + +[tool.hatch.version] +path = "podman/version.py" + +# [tool.hatch.build.targets.sdist] +# include = [ +# "/podman", +# ] + +# to run a test environment, use `hatch shell` +# and not use virtualenv anymore +[tool.hatch.envs.default] +[tool.hatch.envs.dev] +description = "Development environment" +dependencies = [ + "coverage", + "fixtures", + "pre-commit", + "pytest", + "requests-mock >= 1.11.0", +] + +[tool.hatch.envs.test] +description = "Testing environment" +dependencies = [ + "coverage", + "fixtures", + "pytest", + "requests-mock >= 1.11.0", ] -build-backend = "setuptools.build_meta" + +[tool.hatch.envs.test.scripts] +run = "pytest{env:HATCH_TEST_ARGS:} {args}" +run-cov = "coverage run -m pytest{env:HATCH_TEST_ARGS:} {args}" +cov-combine = "coverage combine" +cov-report = "coverage report -m --skip-covered --fail-under=80 --omit=podman/tests/*" +cov-all = ["run-cov", "cov-report"] + +#TODO figure it out later +[tool.hatch.envs.test.env-vars] +PODMAN_LOG_LEVEL = "INFO" +PODMAN_BINARY = "podman" +DEBUG = "0" + +[[tool.hatch.envs.test.matrix]] +python = ["3.12", "3.11", "3.10", "3.9", "3.8", "3.6"] + +[tool.hatch.envs.docs] +features = ["docs"] + +# This is the environment used by `hatch test [args] useful to run +# specific pytest tests like +# `hatch test -vv tests/test_foo.py::test_bar` +# See more here https://hatch.pypa.io/1.11/tutorials/testing/overview/ +# To check the environment: `hatch test -s` +# See more here https://hatch.pypa.io/1.11/config/internal/testing/ +[tool.hatch.envs.hatch-test] +extra-dependencies = [ + "fixtures", + "requests-mock >= 1.11.0", +] + +[[tool.hatch.envs.hatch-test.matrix]] +python = ["3.12", "3.11", "3.10", "3.9", "3.8", "3.6"] + +[tool.ruff] +line-length = 100 +[tool.ruff.format] +exclude = [ + ".git", + ".venv", + ".history", + "build", + "dist", + "docs", + "hack", +] +quote-style = "preserve" +[tool.ruff.lint] +select = [ + # More stuff here https://docs.astral.sh/ruff/rules/ + "F", # Pyflakes + "E", # Pycodestyle Error + "W", # Pycodestyle Warning + "N", # PEP8 Naming + # TODO "UP", # Pyupgrade + # TODO "ANN", + # TODO "S", # Bandit + # "B", # Bugbear + "A", # flake-8-builtins + "YTT", # flake-8-2020 + "PLC", # Pylint Convention + "PLE", # Pylint Error + "PLW", # Pylint Warning +] +# Some checks should be enabled for code sanity disabled now +# to avoid changing too many lines +ignore = [ + "F821", # TODO Undefined name + "F541", # TODO f-string is missing placeholders + "F401", # TODO Module imported but unused + "F841", # TODO Local variable is assigned to but never used + "E402", # TODO Module level import not at top of file + "E741", # TODO ambiguous variable name + "E722", # TODO do not use bare 'except' + "E501", # TODO line too long + "N818", # TODO Error Suffix in exception name + "N80", # TODO Invalid Name + "ANN10", # Missing type annotation + "PLW2901", # TODO Redefined Loop Name +] +[tool.ruff.lint.per-file-ignores] +"podman/tests/*.py" = ["S"] +[tool.ruff.lint.flake8-builtins] +builtins-ignorelist = ["copyright", "all"] + [tool.pytest.ini_options] log_cli = true log_cli_level = "DEBUG" log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)" log_cli_date_format = "%Y-%m-%d %H:%M:%S" - diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index f16a9878..00000000 --- a/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -# Any changes should be copied into pyproject.toml -requests>=2.24 -setuptools -sphinx -tomli>=1.2.3; python_version<'3.11' -urllib3 -wheel diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index d31a2479..00000000 --- a/setup.cfg +++ /dev/null @@ -1,52 +0,0 @@ -[metadata] -name = podman -version = 5.3.0 -author = Brent Baude, Jhon Honce, Urvashi Mohnani, Nicola Sella -author_email = jhonce@redhat.com -description = Bindings for Podman RESTful API -long_description = file: README.md -long_description_content_type = text/markdown -url = https://github.com/containers/podman-py -license = Apache-2.0 -license_files = LICENSE -platforms = any -project_urls = - Bug Tracker = https://github.com/containers/podman-py/issues - Libpod API = https://docs.podman.io/en/latest/_static/api.html -classifiers = - Development Status :: 5 - Production/Stable - Intended Audience :: Developers - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Programming Language :: Python :: 3.12 - Programming Language :: Python :: 3.13 - Topic :: Software Development :: Libraries :: Python Modules -keywords = podman, libpod - -[options] -include_package_data = True -python_requires = >=3.9 -test_suite = -# Any changes should be copied into pyproject.toml -install_requires = - requests >=2.24 - tomli>=1.2.3; python_version<'3.11' - urllib3 - -[options.extras_require] -progress_bar = - rich >= 12.5.1 - -# typing_extensions are included for RHEL 8.5 -# typing_extensions;python_version<'3.8' - -[bdist_wheel] -# python < 3.6 not supported -universal = false - -[sdist] -formats = gztar diff --git a/setup.py b/setup.py deleted file mode 100644 index 01e2dd7a..00000000 --- a/setup.py +++ /dev/null @@ -1,25 +0,0 @@ -import fnmatch - -import setuptools -from setuptools import find_packages -from setuptools.command.build_py import build_py as build_py_orig - -excluded = [ - "podman/tests/*", -] - - -class build_py(build_py_orig): - def find_package_modules(self, package, package_dir): - modules = super().find_package_modules(package, package_dir) - return [ - (pkg, mod, file) - for (pkg, mod, file) in modules - if not any(fnmatch.fnmatchcase(file, pat=pattern) for pattern in excluded) - ] - - -setuptools.setup( - packages=find_packages(), - cmdclass={"build_py": build_py}, -) diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index b208312e..00000000 --- a/test-requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Any changes should be copied into pyproject.toml --r requirements.txt -black -coverage -fixtures -pylint -pytest -requests-mock >= 1.11.0 -tox diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 94d6b07a..00000000 --- a/tox.ini +++ /dev/null @@ -1,39 +0,0 @@ -[tox] -minversion = 3.2.0 -envlist = pylint,coverage,py39,py310,py311,py312,py313 -ignore_basepython_conflict = true - -[testenv] -basepython = python3 -usedevelop = True -install_command = pip install {opts} {packages} -deps = -r{toxinidir}/test-requirements.txt -commands = pytest {posargs} -setenv = - PODMAN_LOG_LEVEL = {env:PODMAN_LOG_LEVEL:INFO} - PODMAN_BINARY = {env:PODMAN_BINARY:podman} - DEBUG = {env:DEBUG:0} - -[testenv:venv] -commands = {posargs} - -[testenv:pylint] -depends = py310 -basepython = python3.10 -allowlist_externals = pylint -commands = pylint podman - -[testenv:coverage] -commands = - coverage run -m pytest - coverage report -m --skip-covered --fail-under=80 --omit=podman/tests/* --omit=.tox/* - -[testenv:black] -deps = black -commands = - black --diff --check . - -[testenv:black-format] -deps = black -commands = - black {posargs} . From 04d699a55e2c3b725ec13f5f533a73fcef686b29 Mon Sep 17 00:00:00 2001 From: Nicola Sella Date: Mon, 19 Aug 2024 14:40:53 +0200 Subject: [PATCH 2/6] Edit readme and contributing Signed-off-by: Nicola Sella --- CONTRIBUTING.md | 41 +++++++++++++++++++++++++++++------------ README.md | 4 ++-- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b63672cc..57009b58 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,12 +25,10 @@ Please don't include any private/sensitive information in your issue! ## Tools we use -- Python 3.6 -- [pylint](https://www.pylint.org/) -- [black](https://github.com/psf/black) -- [tox](https://tox.readthedocs.io/en/latest/) -- You may need to use [virtualenv](https://virtualenv.pypa.io/en/latest/) to - support Python 3.6 +- Python >= 3.6 +- [Hatch](https://hatch.pypa.io/) +- [pre-commit](https://pre-commit.com/) +- [ruff](https://docs.astral.sh/ruff/) ## Testing @@ -41,10 +39,25 @@ Integration tests would be required for large changes (TBD). Run unit tests and get coverage report: ``` -pip install tox -tox -e coverage +hatch run test:cov ``` +If you need to run individual tests you could use the hatch commands that wrap pytest: + +``` +hatch run test:run -vv podman/tests/integration/test_file.py -k "test_name" +``` + +It is worth mentioning that in this context test is not a command but it means the testing environment. The command is +specified after the colon and it could be one of the commands defined in pyproject.toml or any command that can be run +in the environment (like `test:pip` will run `pip` in the default `test` environment). + +To use a separate environment replace `test` with `test.py3.10`, for example. See all the environments available with +`hatch env show`. + +You might be used to create virtual envs and sourcing them via `source .venv/bin/activate`. Well, now you can do that +with hatch by running `hatch shell` or, for a specific environment `hatch shell test.py3.10`. + ## Submitting changes - Create a github pull request (PR) @@ -65,10 +78,14 @@ tox -e coverage ## Coding conventions -- Use [black](https://github.com/psf/black) code formatter. If you have tox - installed, run `tox -e black` to see what changes will be made. You can use - `tox -e black-format` to update the code formatting prior to committing. -- Pass pylint +- Formatting and linting are incorporated using [ruff](https://docs.astral.sh/ruff/). +- If you use [pre-commit](https://pre-commit.com/) the checks will run automatically + when you commit some changes +- If you prefer to run the ckecks with pre-commit, use `pre-commit run -a` to run the + pre-commit checks for you. +- If you'd like to see what's happening with the checks you can run the [linter](https://docs.astral.sh/ruff/linter/) + and [formatter](https://docs.astral.sh/ruff/formatter/) separately with `ruff check --diff` and `ruff format --diff` +- Checks need to pass pylint - exceptions are possible, but you will need to make a good argument - Use spaces not tabs for indentation - This is open source software. Consider the people who will read your code, diff --git a/README.md b/README.md index f2ffee71..38679722 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,8 @@ pip install podman ## Dependencies -* For runtime dependencies, see [requirements.txt](https://github.com/containers/podman-py/blob/main/requirements.txt). -* For testing and development dependencies, see [test-requirements.txt](https://github.com/containers/podman-py/blob/main/test-requirements.txt). +* Runtime dependencies are specified in [pyproject.toml](https://github.com/containers/podman-py/blob/main/pyproject.toml). +* Testing dependencies can be installed via `pip install -e .[test]` ## Example usage From 12186a4aebd436da7c85267c386fcc302e0ade9a Mon Sep 17 00:00:00 2001 From: Nicola Sella Date: Tue, 20 Aug 2024 15:55:20 +0200 Subject: [PATCH 3/6] Add pre-commit workflow Signed-off-by: Nicola Sella --- .github/workflows/pre-commit.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/pre-commit.yml diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 00000000..cb0e2c35 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,20 @@ +name: pre-commit + +on: + pull_request: + push: + branches: [main] + +jobs: + pre-commit: + runs-on: ubuntu-latest + env: + SKIP: no-commit-to-branch + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: | + 3.9 + 3.x + - uses: pre-commit/action@v3.0.0 From 17bf3ed0b2cbf8d634bb042771a551710f0f6a5e Mon Sep 17 00:00:00 2001 From: Nicola Sella Date: Mon, 19 Aug 2024 14:31:31 +0200 Subject: [PATCH 4/6] Edit files to comply with pre-commit checks Signed-off-by: Nicola Sella --- docs/source/conf.py | 6 +++--- podman/api/typing_extensions.py | 1 + podman/domain/volumes.py | 3 ++- podman/tests/integration/base.py | 1 + podman/tests/integration/test_images.py | 1 + podman/tests/integration/test_networks.py | 1 + podman/tests/integration/utils.py | 5 ++--- podman/tests/unit/test_imagesmanager.py | 3 ++- 8 files changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index caf916d3..e1e0845a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -20,9 +20,9 @@ # -- Project information ----------------------------------------------------- -project = u'Podman Python SDK' -copyright = u'2021, Red Hat Inc' -author = u'Red Hat Inc' +project = 'Podman Python SDK' +copyright = '2021, Red Hat Inc' +author = 'Red Hat Inc' # The full version, including alpha/beta/rc tags version = '3.2.1.0' diff --git a/podman/api/typing_extensions.py b/podman/api/typing_extensions.py index ff698f4c..ebddfa27 100644 --- a/podman/api/typing_extensions.py +++ b/podman/api/typing_extensions.py @@ -1604,6 +1604,7 @@ class GenProto(Protocol[T]): def meth(self) -> T: ... """ + __slots__ = () _is_protocol = True diff --git a/podman/domain/volumes.py b/podman/domain/volumes.py index 842ed7a4..6867d5c8 100644 --- a/podman/domain/volumes.py +++ b/podman/domain/volumes.py @@ -112,7 +112,8 @@ def list(self, *_, **kwargs) -> List[Volume]: return [self.prepare_model(i) for i in response.json()] def prune( - self, filters: Optional[Dict[str, str]] = None # pylint: disable=unused-argument + self, + filters: Optional[Dict[str, str]] = None, # pylint: disable=unused-argument ) -> Dict[Literal["VolumesDeleted", "SpaceReclaimed"], Any]: """Delete unused volumes. diff --git a/podman/tests/integration/base.py b/podman/tests/integration/base.py index d79711d9..3086730f 100644 --- a/podman/tests/integration/base.py +++ b/podman/tests/integration/base.py @@ -13,6 +13,7 @@ # under the License. # """Base integration test code""" + import logging import os import shutil diff --git a/podman/tests/integration/test_images.py b/podman/tests/integration/test_images.py index 298d13ec..91db11a0 100644 --- a/podman/tests/integration/test_images.py +++ b/podman/tests/integration/test_images.py @@ -13,6 +13,7 @@ # under the License. # """Images integration tests.""" + import io import queue import tarfile diff --git a/podman/tests/integration/test_networks.py b/podman/tests/integration/test_networks.py index 0b5d44d1..c034ca8d 100644 --- a/podman/tests/integration/test_networks.py +++ b/podman/tests/integration/test_networks.py @@ -13,6 +13,7 @@ # under the License. # """Network integration tests.""" + import os import random import unittest diff --git a/podman/tests/integration/utils.py b/podman/tests/integration/utils.py index 78aea63a..262bf86e 100644 --- a/podman/tests/integration/utils.py +++ b/podman/tests/integration/utils.py @@ -13,6 +13,7 @@ # under the License. # """Integration Test Utils""" + import logging import os import shutil @@ -97,9 +98,7 @@ def consume_lines(pipe, consume_fn): def consume(line: str): logger.debug(line.strip("\n") + f" refid={self.reference_id}") - self.proc = subprocess.Popen( - self.cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT - ) # pylint: disable=consider-using-with + self.proc = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # pylint: disable=consider-using-with threading.Thread(target=consume_lines, args=[self.proc.stdout, consume]).start() if not check_socket: diff --git a/podman/tests/unit/test_imagesmanager.py b/podman/tests/unit/test_imagesmanager.py index 4906daf2..cab896c7 100644 --- a/podman/tests/unit/test_imagesmanager.py +++ b/podman/tests/unit/test_imagesmanager.py @@ -627,7 +627,8 @@ def test_list_with_name_overrides_reference_filter(self, mock): # The name parameter should override the reference filter images = self.client.images.list( - name="fedora", filters={"reference": "ubuntu"} # This should be overridden + name="fedora", + filters={"reference": "ubuntu"}, # This should be overridden ) self.assertEqual(len(images), 1) From b61383f6110ffe3db552ab6e1467a1a494b4e831 Mon Sep 17 00:00:00 2001 From: Nicola Sella Date: Wed, 21 Aug 2024 16:31:56 +0200 Subject: [PATCH 5/6] Remove unused typing extensions The file is not needed anymore since python<3.8 is dropped Signed-off-by: Nicola Sella --- podman/api/typing_extensions.py | 3040 ------------------------------- pyproject.toml | 10 +- 2 files changed, 3 insertions(+), 3047 deletions(-) delete mode 100644 podman/api/typing_extensions.py diff --git a/podman/api/typing_extensions.py b/podman/api/typing_extensions.py deleted file mode 100644 index ebddfa27..00000000 --- a/podman/api/typing_extensions.py +++ /dev/null @@ -1,3040 +0,0 @@ -"""Provide typing.Literal when not supported by OS release. - -FIXME: Remove file when supported Python >= 3.8 -""" - -# Code is backup for missing typing_extensions... -# pylint: disable-all - -import abc -import collections -import collections.abc as collections_abc -import contextlib -import operator -import typing - -# These are used by Protocol implementation -# We use internal typing helpers here, but this significantly reduces -# code duplication. (Also this is only until Protocol is in typing.) -from typing import Generic, Callable, TypeVar, Tuple - -import sys - -# After PEP 560, internal typing API was substantially reworked. -# This is especially important for Protocol class which uses internal APIs -# quite extensively. -PEP_560 = sys.version_info[:3] >= (3, 7, 0) - -if PEP_560: - GenericMeta = TypingMeta = type -else: - from typing import GenericMeta, TypingMeta -OLD_GENERICS = False -try: - from typing import _type_vars, _next_in_mro, _type_check -except ImportError: - OLD_GENERICS = True -try: - from typing import _subs_tree # noqa - - SUBS_TREE = True -except ImportError: - SUBS_TREE = False -try: - from typing import _tp_cache -except ImportError: - - def _tp_cache(x): - return x - - -try: - from typing import _TypingEllipsis, _TypingEmpty -except ImportError: - - class _TypingEllipsis: - pass - - class _TypingEmpty: - pass - - -# The two functions below are copies of typing internal helpers. -# They are needed by _ProtocolMeta - - -def _no_slots_copy(dct): - dict_copy = dict(dct) - if '__slots__' in dict_copy: - for slot in dict_copy['__slots__']: - dict_copy.pop(slot, None) - return dict_copy - - -def _check_generic(cls, parameters): - if not cls.__parameters__: - raise TypeError("%s is not a generic class" % repr(cls)) - alen = len(parameters) - elen = len(cls.__parameters__) - if alen != elen: - raise TypeError( - "Too %s parameters for %s; actual %s, expected %s" - % ("many" if alen > elen else "few", repr(cls), alen, elen) - ) - - -if hasattr(typing, '_generic_new'): - _generic_new = typing._generic_new -else: - # Note: The '_generic_new(...)' function is used as a part of the - # process of creating a generic type and was added to the typing module - # as of Python 3.5.3. - # - # We've defined '_generic_new(...)' below to exactly match the behavior - # implemented in older versions of 'typing' bundled with Python 3.5.0 to - # 3.5.2. This helps eliminate redundancy when defining collection types - # like 'Deque' later. - # - # See https://github.com/python/typing/pull/308 for more details -- in - # particular, compare and contrast the definition of types like - # 'typing.List' before and after the merge. - - def _generic_new(base_cls, cls, *args, **kwargs): - return base_cls.__new__(cls, *args, **kwargs) - - -# See https://github.com/python/typing/pull/439 -if hasattr(typing, '_geqv'): - from typing import _geqv - - _geqv_defined = True -else: - _geqv = None - _geqv_defined = False - -if sys.version_info[:2] >= (3, 6): - import _collections_abc - - _check_methods_in_mro = _collections_abc._check_methods -else: - - def _check_methods_in_mro(C, *methods): - mro = C.__mro__ - for method in methods: - for B in mro: - if method in B.__dict__: - if B.__dict__[method] is None: - return NotImplemented - break - else: - return NotImplemented - return True - - -# Please keep __all__ alphabetized within each category. -__all__ = [ - # Super-special typing primitives. - 'ClassVar', - 'Concatenate', - 'Final', - 'ParamSpec', - 'Type', - # ABCs (from collections.abc). - # The following are added depending on presence - # of their non-generic counterparts in stdlib: - # '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. - 'final', - 'IntVar', - 'Literal', - 'NewType', - 'overload', - 'Text', - 'TypeAlias', - 'TypeGuard', - 'TYPE_CHECKING', -] - -# Annotated relies on substitution trees of pep 560. It will not work for -# versions of typing older than 3.5.3 -HAVE_ANNOTATED = PEP_560 or SUBS_TREE - -if PEP_560: - __all__.extend(["get_args", "get_origin", "get_type_hints"]) - -if HAVE_ANNOTATED: - __all__.append("Annotated") - -# Protocols are hard to backport to the original version of typing 3.5.0 -HAVE_PROTOCOLS = sys.version_info[:3] != (3, 5, 0) - -if HAVE_PROTOCOLS: - __all__.extend(['Protocol', 'runtime', 'runtime_checkable']) - -# TODO -if hasattr(typing, 'NoReturn'): - NoReturn = typing.NoReturn -elif hasattr(typing, '_FinalTypingBase'): - - class _NoReturn(typing._FinalTypingBase, _root=True): - """Special type indicating functions that never return. - Example:: - - from typing import NoReturn - - def stop() -> NoReturn: - raise Exception('no way') - - This type is invalid in other positions, e.g., ``List[NoReturn]`` - will fail in static type checkers. - """ - - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("NoReturn cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("NoReturn cannot be used with issubclass().") - - NoReturn = _NoReturn(_root=True) -else: - - class _NoReturnMeta(typing.TypingMeta): - """Metaclass for NoReturn""" - - def __new__(cls, name, bases, namespace, _root=False): - return super().__new__(cls, name, bases, namespace, _root=_root) - - def __instancecheck__(self, obj): - raise TypeError("NoReturn cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("NoReturn cannot be used with issubclass().") - - class NoReturn(typing.Final, metaclass=_NoReturnMeta, _root=True): - """Special type indicating functions that never return. - Example:: - - from typing import NoReturn - - def stop() -> NoReturn: - raise Exception('no way') - - This type is invalid in other positions, e.g., ``List[NoReturn]`` - will fail in static type checkers. - """ - - __slots__ = () - - -# 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. -V_co = typing.TypeVar('V_co', covariant=True) # Any type covariant containers. -VT_co = typing.TypeVar('VT_co', covariant=True) # Value type covariant containers. -T_contra = typing.TypeVar('T_contra', contravariant=True) # Ditto contravariant. - -if hasattr(typing, 'ClassVar'): - ClassVar = typing.ClassVar -elif hasattr(typing, '_FinalTypingBase'): - - class _ClassVar(typing._FinalTypingBase, _root=True): - """Special type construct to mark class variables. - - An annotation wrapped in ClassVar indicates that a given - attribute is intended to be used as a class variable and - should not be set on instances of that class. Usage:: - - class Starship: - stats: ClassVar[Dict[str, int]] = {} # class variable - damage: int = 10 # instance variable - - ClassVar accepts only types and cannot be further subscribed. - - Note that ClassVar is not a class itself, and should not - be used with isinstance() or issubclass(). - """ - - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls( - typing._type_check( - item, '{} accepts only single type.'.format(cls.__name__[1:]) - ), - _root=True, - ) - raise TypeError('{} cannot be further subscripted'.format(cls.__name__[1:])) - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _ClassVar): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - ClassVar = _ClassVar(_root=True) -else: - - class _ClassVarMeta(typing.TypingMeta): - """Metaclass for ClassVar""" - - def __new__(cls, name, bases, namespace, tp=None, _root=False): - self = super().__new__(cls, name, bases, namespace, _root=_root) - if tp is not None: - self.__type__ = tp - return self - - def __instancecheck__(self, obj): - raise TypeError("ClassVar cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("ClassVar cannot be used with issubclass().") - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is not None: - raise TypeError('{} cannot be further subscripted'.format(cls.__name__[1:])) - - param = typing._type_check( - item, '{} accepts only single type.'.format(cls.__name__[1:]) - ) - return cls(self.__name__, self.__bases__, dict(self.__dict__), tp=param, _root=True) - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)( - self.__name__, self.__bases__, dict(self.__dict__), tp=self.__type__, _root=True - ) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, ClassVar): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - class ClassVar(typing.Final, metaclass=_ClassVarMeta, _root=True): - """Special type construct to mark class variables. - - An annotation wrapped in ClassVar indicates that a given - attribute is intended to be used as a class variable and - should not be set on instances of that class. Usage:: - - class Starship: - stats: ClassVar[Dict[str, int]] = {} # class variable - damage: int = 10 # instance variable - - ClassVar accepts only types and cannot be further subscribed. - - Note that ClassVar is not a class itself, and should not - be used with isinstance() or issubclass(). - """ - - __type__ = None - - -# On older versions of typing there is an internal class named "Final". -if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7): - Final = typing.Final -elif sys.version_info[:2] >= (3, 7): - - class _FinalForm(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 _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.""", - ) -elif hasattr(typing, '_FinalTypingBase'): - - class _Final(typing._FinalTypingBase, _root=True): - """A special typing construct to indicate that a name - cannot be re-assigned or overridden in a subclass. - For example: - - MAX_SIZE: Final = 9000 - MAX_SIZE += 1 # Error reported by type checker - - class Connection: - TIMEOUT: Final[int] = 10 - class FastConnector(Connection): - TIMEOUT = 1 # Error reported by type checker - - There is no runtime checking of these properties. - """ - - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls( - typing._type_check( - item, '{} accepts only single type.'.format(cls.__name__[1:]) - ), - _root=True, - ) - raise TypeError('{} cannot be further subscripted'.format(cls.__name__[1:])) - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _Final): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - Final = _Final(_root=True) -else: - - class _FinalMeta(typing.TypingMeta): - """Metaclass for Final""" - - def __new__(cls, name, bases, namespace, tp=None, _root=False): - self = super().__new__(cls, name, bases, namespace, _root=_root) - if tp is not None: - self.__type__ = tp - return self - - def __instancecheck__(self, obj): - raise TypeError("Final cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Final cannot be used with issubclass().") - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is not None: - raise TypeError('{} cannot be further subscripted'.format(cls.__name__[1:])) - - param = typing._type_check( - item, '{} accepts only single type.'.format(cls.__name__[1:]) - ) - return cls(self.__name__, self.__bases__, dict(self.__dict__), tp=param, _root=True) - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)( - self.__name__, self.__bases__, dict(self.__dict__), tp=self.__type__, _root=True - ) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, Final): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - class Final(typing.Final, metaclass=_FinalMeta, _root=True): - """A special typing construct to indicate that a name - cannot be re-assigned or overridden in a subclass. - For example: - - MAX_SIZE: Final = 9000 - MAX_SIZE += 1 # Error reported by type checker - - class Connection: - TIMEOUT: Final[int] = 10 - class FastConnector(Connection): - TIMEOUT = 1 # Error reported by type checker - - There is no runtime checking of these properties. - """ - - __type__ = None - - -if hasattr(typing, 'final'): - final = typing.final -else: - - 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. - """ - return f - - -def IntVar(name): - return TypeVar(name) - - -if hasattr(typing, 'Literal'): - Literal = typing.Literal -elif sys.version_info[:2] >= (3, 7): - - class _LiteralForm(typing._SpecialForm, _root=True): - def __repr__(self): - return 'typing_extensions.' + self._name - - def __getitem__(self, parameters): - return _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.""", - ) -elif hasattr(typing, '_FinalTypingBase'): - - class _Literal(typing._FinalTypingBase, _root=True): - """A type that can be used to indicate to type checkers that the - corresponding value has a value literally equivalent to the - provided parameter. For example: - - var: Literal[4] = 4 - - The type checker understands that 'var' is literally equal to the - value 4 and no other value. - - Literal[...] cannot be subclassed. There is no runtime checking - verifying that the parameter is actually a value instead of a type. - """ - - __slots__ = ('__values__',) - - def __init__(self, values=None, **kwds): - self.__values__ = values - - def __getitem__(self, values): - cls = type(self) - if self.__values__ is None: - if not isinstance(values, tuple): - values = (values,) - return cls(values, _root=True) - raise TypeError('{} cannot be further subscripted'.format(cls.__name__[1:])) - - def _eval_type(self, globalns, localns): - return self - - def __repr__(self): - r = super().__repr__() - if self.__values__ is not None: - r += '[{}]'.format(', '.join(map(typing._type_repr, self.__values__))) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__values__)) - - def __eq__(self, other): - if not isinstance(other, _Literal): - return NotImplemented - if self.__values__ is not None: - return self.__values__ == other.__values__ - return self is other - - Literal = _Literal(_root=True) -else: - - class _LiteralMeta(typing.TypingMeta): - """Metaclass for Literal""" - - def __new__(cls, name, bases, namespace, values=None, _root=False): - self = super().__new__(cls, name, bases, namespace, _root=_root) - if values is not None: - self.__values__ = values - return self - - def __instancecheck__(self, obj): - raise TypeError("Literal cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Literal cannot be used with issubclass().") - - def __getitem__(self, item): - cls = type(self) - if self.__values__ is not None: - raise TypeError('{} cannot be further subscripted'.format(cls.__name__[1:])) - - if not isinstance(item, tuple): - item = (item,) - return cls(self.__name__, self.__bases__, dict(self.__dict__), values=item, _root=True) - - def _eval_type(self, globalns, localns): - return self - - def __repr__(self): - r = super().__repr__() - if self.__values__ is not None: - r += '[{}]'.format(', '.join(map(typing._type_repr, self.__values__))) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__values__)) - - def __eq__(self, other): - if not isinstance(other, Literal): - return NotImplemented - if self.__values__ is not None: - return self.__values__ == other.__values__ - return self is other - - class Literal(typing.Final, metaclass=_LiteralMeta, _root=True): - """A type that can be used to indicate to type checkers that the - corresponding value has a value literally equivalent to the - provided parameter. For example: - - var: Literal[4] = 4 - - The type checker understands that 'var' is literally equal to the - value 4 and no other value. - - Literal[...] cannot be subclassed. There is no runtime checking - verifying that the parameter is actually a value instead of a type. - """ - - __values__ = None - - -def _overload_dummy(*args, **kwds): - """Helper for @overload to raise when called.""" - raise NotImplementedError( - "You should not call an overloaded function. " - "A series of @overload-decorated functions " - "outside a stub module should always be followed " - "by an implementation that is not @overload-ed." - ) - - -def overload(func): - """Decorator for overloaded functions/methods. - - In a stub file, place two or more stub definitions for the same - function in a row, each decorated with @overload. For example: - - @overload - def utf8(value: None) -> None: ... - @overload - def utf8(value: bytes) -> bytes: ... - @overload - def utf8(value: str) -> bytes: ... - - In a non-stub file (i.e. a regular .py file), do the same but - follow it with an implementation. The implementation should *not* - be decorated with @overload. For example: - - @overload - def utf8(value: None) -> None: ... - @overload - def utf8(value: bytes) -> bytes: ... - @overload - def utf8(value: str) -> bytes: ... - def utf8(value): - # implementation goes here - """ - return _overload_dummy - - -# This is not a real generic class. Don't use outside annotations. -if hasattr(typing, 'Type'): - Type = typing.Type -else: - # Internal type variable used for Type[]. - CT_co = typing.TypeVar('CT_co', covariant=True, bound=type) - - class Type(typing.Generic[CT_co], extra=type): - """A special construct usable to annotate class objects. - - For example, suppose we have the following classes:: - - class User: ... # Abstract base for User classes - class BasicUser(User): ... - class ProUser(User): ... - class TeamUser(User): ... - - And a function that takes a class argument that's a subclass of - User and returns an instance of the corresponding class:: - - U = TypeVar('U', bound=User) - def new_user(user_class: Type[U]) -> U: - user = user_class() - # (Here we could write the user object to a database) - return user - joe = new_user(BasicUser) - - At this point the type checker knows that joe has type BasicUser. - """ - - __slots__ = () - - -# Various ABCs mimicking those in collections.abc. -# A few are simply re-exported for completeness. - - -def _define_guard(type_name): - """ - Returns True if the given type isn't defined in typing but - is defined in collections_abc. - - Adds the type to __all__ if the collection is found in either - typing or collection_abc. - """ - if hasattr(typing, type_name): - __all__.append(type_name) - globals()[type_name] = getattr(typing, type_name) - return False - elif hasattr(collections_abc, type_name): - __all__.append(type_name) - return True - else: - return False - - -class _ExtensionsGenericMeta(GenericMeta): - def __subclasscheck__(self, subclass): - """This mimics a more modern GenericMeta.__subclasscheck__() logic - (that does not have problems with recursion) to work around interactions - between collections, typing, and typing_extensions on older - versions of Python, see https://github.com/python/typing/issues/501. - """ - if sys.version_info[:3] >= (3, 5, 3) or sys.version_info[:3] < (3, 5, 0): - if self.__origin__ is not None: - if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: - raise TypeError( - "Parameterized generics cannot be used with class or instance checks" - ) - return False - if not self.__extra__: - return super().__subclasscheck__(subclass) - res = self.__extra__.__subclasshook__(subclass) - if res is not NotImplemented: - return res - if self.__extra__ in subclass.__mro__: - return True - for scls in self.__extra__.__subclasses__(): - if isinstance(scls, GenericMeta): - continue - if issubclass(subclass, scls): - return True - return False - - -if _define_guard('Awaitable'): - - class Awaitable( - typing.Generic[T_co], metaclass=_ExtensionsGenericMeta, extra=collections_abc.Awaitable - ): - __slots__ = () - - -if _define_guard('Coroutine'): - - class Coroutine( - Awaitable[V_co], - typing.Generic[T_co, T_contra, V_co], - metaclass=_ExtensionsGenericMeta, - extra=collections_abc.Coroutine, - ): - __slots__ = () - - -if _define_guard('AsyncIterable'): - - class AsyncIterable( - typing.Generic[T_co], metaclass=_ExtensionsGenericMeta, extra=collections_abc.AsyncIterable - ): - __slots__ = () - - -if _define_guard('AsyncIterator'): - - class AsyncIterator( - AsyncIterable[T_co], metaclass=_ExtensionsGenericMeta, extra=collections_abc.AsyncIterator - ): - __slots__ = () - - -if hasattr(typing, 'Deque'): - Deque = typing.Deque -elif _geqv_defined: - - class Deque( - collections.deque, - typing.MutableSequence[T], - metaclass=_ExtensionsGenericMeta, - extra=collections.deque, - ): - __slots__ = () - - def __new__(cls, *args, **kwds): - if _geqv(cls, Deque): - return collections.deque(*args, **kwds) - return _generic_new(collections.deque, cls, *args, **kwds) - -else: - - class Deque( - collections.deque, - typing.MutableSequence[T], - metaclass=_ExtensionsGenericMeta, - extra=collections.deque, - ): - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Deque: - return collections.deque(*args, **kwds) - return _generic_new(collections.deque, cls, *args, **kwds) - - -if hasattr(typing, 'ContextManager'): - ContextManager = typing.ContextManager -elif hasattr(contextlib, 'AbstractContextManager'): - - class ContextManager( - typing.Generic[T_co], - metaclass=_ExtensionsGenericMeta, - extra=contextlib.AbstractContextManager, - ): - __slots__ = () - -else: - - class ContextManager(typing.Generic[T_co]): - __slots__ = () - - def __enter__(self): - return self - - @abc.abstractmethod - def __exit__(self, exc_type, exc_value, traceback): - return None - - @classmethod - def __subclasshook__(cls, C): - if cls is ContextManager: - # In Python 3.6+, it is possible to set a method to None to - # explicitly indicate that the class does not implement an ABC - # (https://bugs.python.org/issue25958), but we do not support - # that pattern here because this fallback class is only used - # in Python 3.5 and earlier. - if any("__enter__" in B.__dict__ for B in C.__mro__) and any( - "__exit__" in B.__dict__ for B in C.__mro__ - ): - return True - return NotImplemented - - -if hasattr(typing, 'AsyncContextManager'): - AsyncContextManager = typing.AsyncContextManager - __all__.append('AsyncContextManager') -elif hasattr(contextlib, 'AbstractAsyncContextManager'): - - class AsyncContextManager( - typing.Generic[T_co], - metaclass=_ExtensionsGenericMeta, - extra=contextlib.AbstractAsyncContextManager, - ): - __slots__ = () - - __all__.append('AsyncContextManager') -elif sys.version_info[:2] >= (3, 5): - exec( - """ -class AsyncContextManager(typing.Generic[T_co]): - __slots__ = () - - async def __aenter__(self): - return self - - @abc.abstractmethod - async def __aexit__(self, exc_type, exc_value, traceback): - return None - - @classmethod - def __subclasshook__(cls, C): - if cls is AsyncContextManager: - return _check_methods_in_mro(C, "__aenter__", "__aexit__") - return NotImplemented - -__all__.append('AsyncContextManager') -""" - ) - -if hasattr(typing, 'DefaultDict'): - DefaultDict = typing.DefaultDict -elif _geqv_defined: - - class DefaultDict( - collections.defaultdict, - typing.MutableMapping[KT, VT], - metaclass=_ExtensionsGenericMeta, - extra=collections.defaultdict, - ): - __slots__ = () - - def __new__(cls, *args, **kwds): - if _geqv(cls, DefaultDict): - return collections.defaultdict(*args, **kwds) - return _generic_new(collections.defaultdict, cls, *args, **kwds) - -else: - - class DefaultDict( - collections.defaultdict, - typing.MutableMapping[KT, VT], - metaclass=_ExtensionsGenericMeta, - extra=collections.defaultdict, - ): - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is DefaultDict: - return collections.defaultdict(*args, **kwds) - return _generic_new(collections.defaultdict, cls, *args, **kwds) - - -if hasattr(typing, 'OrderedDict'): - OrderedDict = typing.OrderedDict -elif (3, 7, 0) <= sys.version_info[:3] < (3, 7, 2): - OrderedDict = typing._alias(collections.OrderedDict, (KT, VT)) -elif _geqv_defined: - - class OrderedDict( - collections.OrderedDict, - typing.MutableMapping[KT, VT], - metaclass=_ExtensionsGenericMeta, - extra=collections.OrderedDict, - ): - __slots__ = () - - def __new__(cls, *args, **kwds): - if _geqv(cls, OrderedDict): - return collections.OrderedDict(*args, **kwds) - return _generic_new(collections.OrderedDict, cls, *args, **kwds) - -else: - - class OrderedDict( - collections.OrderedDict, - typing.MutableMapping[KT, VT], - metaclass=_ExtensionsGenericMeta, - extra=collections.OrderedDict, - ): - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is OrderedDict: - return collections.OrderedDict(*args, **kwds) - return _generic_new(collections.OrderedDict, cls, *args, **kwds) - - -if hasattr(typing, 'Counter'): - Counter = typing.Counter -elif (3, 5, 0) <= sys.version_info[:3] <= (3, 5, 1): - assert _geqv_defined - _TInt = typing.TypeVar('_TInt') - - class _CounterMeta(typing.GenericMeta): - """Metaclass for Counter""" - - def __getitem__(self, item): - return super().__getitem__((item, int)) - - class Counter( - collections.Counter, typing.Dict[T, int], metaclass=_CounterMeta, extra=collections.Counter - ): - __slots__ = () - - def __new__(cls, *args, **kwds): - if _geqv(cls, Counter): - return collections.Counter(*args, **kwds) - return _generic_new(collections.Counter, cls, *args, **kwds) - -elif _geqv_defined: - - class Counter( - collections.Counter, - typing.Dict[T, int], - metaclass=_ExtensionsGenericMeta, - extra=collections.Counter, - ): - __slots__ = () - - def __new__(cls, *args, **kwds): - if _geqv(cls, Counter): - return collections.Counter(*args, **kwds) - return _generic_new(collections.Counter, cls, *args, **kwds) - -else: - - class Counter( - collections.Counter, - typing.Dict[T, int], - metaclass=_ExtensionsGenericMeta, - extra=collections.Counter, - ): - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Counter: - return collections.Counter(*args, **kwds) - return _generic_new(collections.Counter, cls, *args, **kwds) - - -if hasattr(typing, 'ChainMap'): - ChainMap = typing.ChainMap - __all__.append('ChainMap') -elif hasattr(collections, 'ChainMap'): - # ChainMap only exists in 3.3+ - if _geqv_defined: - - class ChainMap( - collections.ChainMap, - typing.MutableMapping[KT, VT], - metaclass=_ExtensionsGenericMeta, - extra=collections.ChainMap, - ): - __slots__ = () - - def __new__(cls, *args, **kwds): - if _geqv(cls, ChainMap): - return collections.ChainMap(*args, **kwds) - return _generic_new(collections.ChainMap, cls, *args, **kwds) - - else: - - class ChainMap( - collections.ChainMap, - typing.MutableMapping[KT, VT], - metaclass=_ExtensionsGenericMeta, - extra=collections.ChainMap, - ): - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is ChainMap: - return collections.ChainMap(*args, **kwds) - return _generic_new(collections.ChainMap, cls, *args, **kwds) - - __all__.append('ChainMap') - -if _define_guard('AsyncGenerator'): - - class AsyncGenerator( - AsyncIterator[T_co], - typing.Generic[T_co, T_contra], - metaclass=_ExtensionsGenericMeta, - extra=collections_abc.AsyncGenerator, - ): - __slots__ = () - - -if hasattr(typing, 'NewType'): - NewType = typing.NewType -else: - - def NewType(name, tp): - """NewType creates simple unique types with almost zero - runtime overhead. NewType(name, tp) is considered a subtype of tp - by static type checkers. At runtime, NewType(name, tp) returns - a dummy function that simply returns its argument. Usage:: - - UserId = NewType('UserId', int) - - def name_by_id(user_id: UserId) -> str: - ... - - UserId('user') # Fails type check - - name_by_id(42) # Fails type check - name_by_id(UserId(42)) # OK - - num = UserId(5) + 1 # type: int - """ - - def new_type(x): - return x - - new_type.__name__ = name - new_type.__supertype__ = tp - return new_type - - -if hasattr(typing, 'Text'): - Text = typing.Text -else: - Text = str - -if hasattr(typing, 'TYPE_CHECKING'): - TYPE_CHECKING = typing.TYPE_CHECKING -else: - # Constant that's True when type checking, but False here. - TYPE_CHECKING = False - - -def _gorg(cls): - """This function exists for compatibility with old typing versions.""" - assert isinstance(cls, GenericMeta) - if hasattr(cls, '_gorg'): - return cls._gorg - while cls.__origin__ is not None: - cls = cls.__origin__ - return cls - - -if OLD_GENERICS: - - def _next_in_mro(cls): # noqa - """This function exists for compatibility with old typing versions.""" - next_in_mro = object - for i, c in enumerate(cls.__mro__[:-1]): - if isinstance(c, GenericMeta) and _gorg(c) is Generic: - next_in_mro = cls.__mro__[i + 1] - return next_in_mro - - -_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)) - - -if hasattr(typing, 'Protocol'): - Protocol = typing.Protocol -elif HAVE_PROTOCOLS and not PEP_560: - - def _no_init(self, *args, **kwargs): - if type(self)._is_protocol: - raise TypeError('Protocols cannot be instantiated') - - class _ProtocolMeta(GenericMeta): - """Internal metaclass for Protocol. - - This exists so Protocol classes can be generic without deriving - from Generic. - """ - - if not OLD_GENERICS: - - def __new__( - cls, - name, - bases, - namespace, - tvars=None, - args=None, - origin=None, - extra=None, - orig_bases=None, - ): - # This is just a version copied from GenericMeta.__new__ that - # includes "Protocol" special treatment. (Comments removed for brevity.) - assert extra is None # Protocols should not have extra - if tvars is not None: - assert origin is not None - assert all(isinstance(t, TypeVar) for t in tvars), tvars - else: - tvars = _type_vars(bases) - gvars = None - for base in bases: - if base is Generic: - raise TypeError("Cannot inherit from plain Generic") - if isinstance(base, GenericMeta) and base.__origin__ in ( - Generic, - Protocol, - ): - if gvars is not None: - raise TypeError( - "Cannot inherit from Generic[...] or" - " Protocol[...] multiple times." - ) - gvars = base.__parameters__ - if gvars is None: - gvars = tvars - else: - tvarset = set(tvars) - gvarset = set(gvars) - if not tvarset <= gvarset: - raise TypeError( - "Some type variables (%s) are not listed in %s[%s]" - % ( - ", ".join(str(t) for t in tvars if t not in gvarset), - ( - "Generic" - if any(b.__origin__ is Generic for b in bases) - else "Protocol" - ), - ", ".join(str(g) for g in gvars), - ) - ) - tvars = gvars - - initial_bases = bases - if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: - bases = (extra,) + bases - bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b for b in bases) - if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): - bases = tuple(b for b in bases if b is not Generic) - namespace.update({'__origin__': origin, '__extra__': extra}) - self = super(GenericMeta, cls).__new__(cls, name, bases, namespace, _root=True) - super(GenericMeta, self).__setattr__('_gorg', self if not origin else _gorg(origin)) - self.__parameters__ = tvars - self.__args__ = ( - tuple( - ... if a is _TypingEllipsis else () if a is _TypingEmpty else a - for a in args - ) - if args - else None - ) - self.__next_in_mro__ = _next_in_mro(self) - if orig_bases is None: - self.__orig_bases__ = initial_bases - elif origin is not None: - self._abc_registry = origin._abc_registry - self._abc_cache = origin._abc_cache - if hasattr(self, '_subs_tree'): - self.__tree_hash__ = ( - hash(self._subs_tree()) if origin else super(GenericMeta, self).__hash__() - ) - return self - - def __init__(cls, *args, **kwargs): - super().__init__(*args, **kwargs) - if not cls.__dict__.get('_is_protocol', None): - cls._is_protocol = any( - b is Protocol or isinstance(b, _ProtocolMeta) and b.__origin__ is Protocol - for b in cls.__bases__ - ) - if cls._is_protocol: - for base in cls.__mro__[1:]: - if not ( - base in (object, Generic) - or base.__module__ == 'collections.abc' - and base.__name__ in _PROTO_WHITELIST - or isinstance(base, TypingMeta) - and base._is_protocol - or isinstance(base, GenericMeta) - and base.__origin__ is Generic - ): - raise TypeError( - 'Protocols can only inherit from other protocols, got %r' % base - ) - - cls.__init__ = _no_init - - def _proto_hook(other): - if not cls.__dict__.get('_is_protocol', None): - return NotImplemented - if not isinstance(other, type): - # Same error as for issubclass(1, int) - raise TypeError('issubclass() arg 1 must be a class') - for attr in _get_protocol_attrs(cls): - for base in other.__mro__: - if attr in base.__dict__: - if base.__dict__[attr] is None: - return NotImplemented - break - annotations = getattr(base, '__annotations__', {}) - if ( - isinstance(annotations, typing.Mapping) - and attr in annotations - and isinstance(other, _ProtocolMeta) - and other._is_protocol - ): - break - else: - return NotImplemented - return True - - if '__subclasshook__' not in cls.__dict__: - cls.__subclasshook__ = _proto_hook - - def __instancecheck__(self, instance): - # We need this method for situations where attributes are - # assigned in __init__. - if ( - not getattr(self, '_is_protocol', False) or _is_callable_members_only(self) - ) and issubclass(instance.__class__, self): - return True - if self._is_protocol: - if all( - hasattr(instance, attr) - and ( - not callable(getattr(self, attr, None)) - or getattr(instance, attr) is not None - ) - for attr in _get_protocol_attrs(self) - ): - return True - return super(GenericMeta, self).__instancecheck__(instance) - - def __subclasscheck__(self, cls): - if self.__origin__ is not None: - if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: - raise TypeError( - "Parameterized generics cannot be used with class or instance checks" - ) - return False - if self.__dict__.get('_is_protocol', None) and not self.__dict__.get( - '_is_runtime_protocol', None - ): - if sys._getframe(1).f_globals['__name__'] in ['abc', 'functools', 'typing']: - return False - raise TypeError( - "Instance and class checks can only be used with @runtime protocols" - ) - if self.__dict__.get('_is_runtime_protocol', None) and not _is_callable_members_only( - self - ): - if sys._getframe(1).f_globals['__name__'] in ['abc', 'functools', 'typing']: - return super(GenericMeta, self).__subclasscheck__(cls) - raise TypeError("Protocols with non-method members don't support issubclass()") - return super(GenericMeta, self).__subclasscheck__(cls) - - if not OLD_GENERICS: - - @_tp_cache - def __getitem__(self, params): - # We also need to copy this from GenericMeta.__getitem__ to get - # special treatment of "Protocol". (Comments removed for brevity.) - if not isinstance(params, tuple): - params = (params,) - if not params and _gorg(self) is not Tuple: - raise TypeError("Parameter list to %s[...] cannot be empty" % self.__qualname__) - msg = "Parameters to generic types must be types." - params = tuple(_type_check(p, msg) for p in params) - if self in (Generic, Protocol): - if not all(isinstance(p, TypeVar) for p in params): - raise TypeError("Parameters to %r[...] must all be type variables" % self) - if len(set(params)) != len(params): - raise TypeError("Parameters to %r[...] must all be unique" % self) - tvars = params - args = params - elif self in (Tuple, Callable): - tvars = _type_vars(params) - args = params - elif self.__origin__ in (Generic, Protocol): - raise TypeError("Cannot subscript already-subscripted %s" % repr(self)) - else: - _check_generic(self, params) - tvars = _type_vars(params) - args = params - - prepend = (self,) if self.__origin__ is None else () - return self.__class__( - self.__name__, - prepend + self.__bases__, - _no_slots_copy(self.__dict__), - tvars=tvars, - args=args, - origin=self, - extra=self.__extra__, - orig_bases=self.__orig_bases__, - ) - - class Protocol(metaclass=_ProtocolMeta): - """Base class for protocol classes. Protocol classes are defined as:: - - class Proto(Protocol): - def meth(self) -> int: - ... - - Such classes are primarily used with static type checkers that recognize - structural subtyping (static duck-typing), for example:: - - class C: - def meth(self) -> int: - return 0 - - def func(x: Proto) -> int: - return x.meth() - - func(C()) # Passes static type check - - See PEP 544 for details. Protocol classes decorated with - @typing_extensions.runtime act as simple-minded runtime protocol that checks - only the presence of given attributes, ignoring their type signatures. - - Protocol classes can be generic, they are defined as:: - - class GenProto({bases}): - def meth(self) -> T: - ... - """ - - __slots__ = () - _is_protocol = True - - def __new__(cls, *args, **kwds): - if _gorg(cls) is Protocol: - raise TypeError( - "Type Protocol cannot be instantiated; it can be used only as a base class" - ) - if OLD_GENERICS: - return _generic_new(_next_in_mro(cls), cls, *args, **kwds) - return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) - - if Protocol.__doc__ is not None: - Protocol.__doc__ = Protocol.__doc__.format( - bases="Protocol, Generic[T]" if OLD_GENERICS else "Protocol[T]" - ) - -elif PEP_560: - from typing import _type_check, _GenericAlias, _collect_type_vars # noqa - - 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) - - @_tp_cache - def __class_getitem__(cls, params): - if not isinstance(params, tuple): - params = (params,) - if not params and cls is not Tuple: - raise TypeError( - "Parameter list to {}[...] cannot be empty".format(cls.__qualname__) - ) - msg = "Parameters to generic types must be types." - params = tuple(_type_check(p, msg) for p in params) - if cls is Protocol: - # Generic can only be subscripted with unique type variables. - if not all(isinstance(p, TypeVar) for p in params): - i = 0 - while isinstance(params[i], TypeVar): - i += 1 - raise TypeError( - "Parameters to Protocol[...] must all be type variables." - " Parameter {} is {}".format(i + 1, 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) - return _GenericAlias(cls, params) - - def __init_subclass__(cls, *args, **kwargs): - tvars = [] - if '__orig_bases__' in cls.__dict__: - error = Generic in cls.__orig_bases__ - else: - error = Generic in cls.__bases__ - if error: - raise TypeError("Cannot inherit from plain Generic") - if '__orig_bases__' in cls.__dict__: - tvars = _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, _GenericAlias) and base.__origin__ in (Generic, Protocol): - # for error messages - the_base = 'Generic' if base.__origin__ is Generic else 'Protocol' - 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( - "Some type variables ({}) are not listed in {}[{}]".format( - s_vars, 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, 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 protocols, got %r' % base - ) - cls.__init__ = _no_init - - -if hasattr(typing, 'runtime_checkable'): - runtime_checkable = typing.runtime_checkable -elif HAVE_PROTOCOLS: - - 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, got %r' % cls - ) - cls._is_runtime_protocol = True - return cls - - -if HAVE_PROTOCOLS: - # Exists for backwards compatibility. - runtime = runtime_checkable - -if hasattr(typing, 'SupportsIndex'): - SupportsIndex = typing.SupportsIndex -elif HAVE_PROTOCOLS: - - @runtime_checkable - class SupportsIndex(Protocol): - __slots__ = () - - @abc.abstractmethod - def __index__(self) -> int: - pass - - -if sys.version_info >= (3, 9, 2): - # 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 - TypedDict = typing.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 ' - 'positional arguments but {} ' - 'were given'.format(len(args) + 2) - ) - 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): - # In Python 3.4 and 3.5 the __init__ method also needs to support the keyword arguments. - # See https://www.python.org/dev/peps/pep-0487/#implementation-details - super(_TypedDictMeta, cls).__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(_TypedDictMeta, cls).__new__(cls, name, (dict,), ns) - - annotations = {} - own_annotations = ns.get('__annotations__', {}) - own_annotation_keys = set(own_annotations.keys()) - 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) - if total: - required_keys.update(own_annotation_keys) - else: - optional_keys.update(own_annotation_keys) - - 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+ - """ - -# Python 3.9+ has PEP 593 (Annotated and modified get_type_hints) -if hasattr(typing, 'Annotated'): - Annotated = typing.Annotated - get_type_hints = typing.get_type_hints - # Not exported and not a public API, but needed for get_origin() and get_args() - # to work. - _AnnotatedAlias = typing._AnnotatedAlias -elif PEP_560: - - 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 "typing_extensions.Annotated[{}, {}]".format( - typing._type_repr(self.__origin__), ", ".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.") - - @_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)." - ) - 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("Cannot subclass {}.Annotated".format(cls.__module__)) - - def _strip_annotations(t): - """Strips the annotations from a given type.""" - if isinstance(t, _AnnotatedAlias): - return _strip_annotations(t.__origin__) - if isinstance(t, typing._GenericAlias): - stripped_args = tuple(_strip_annotations(a) for a in t.__args__) - if stripped_args == t.__args__: - return t - res = t.copy_with(stripped_args) - res._special = t._special - return res - 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, ...]' 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. - """ - hint = typing.get_type_hints(obj, globalns=globalns, localns=localns) - if include_extras: - return hint - return {k: _strip_annotations(t) for k, t in hint.items()} - -elif HAVE_ANNOTATED: - - def _is_dunder(name): - """Returns True if name is a __dunder_variable_name__.""" - return len(name) > 4 and name.startswith('__') and name.endswith('__') - - # Prior to Python 3.7 types did not have `copy_with`. A lot of the equality - # checks, argument expansion etc. are done on the _subs_tree. As a result we - # can't provide a get_type_hints function that strips out annotations. - - class AnnotatedMeta(typing.GenericMeta): - """Metaclass for Annotated""" - - def __new__(cls, name, bases, namespace, **kwargs): - if any(b is not object for b in bases): - raise TypeError("Cannot subclass " + str(Annotated)) - return super().__new__(cls, name, bases, namespace, **kwargs) - - @property - def __metadata__(self): - return self._subs_tree()[2] - - def _tree_repr(self, tree): - cls, origin, metadata = tree - if not isinstance(origin, tuple): - tp_repr = typing._type_repr(origin) - else: - tp_repr = origin[0]._tree_repr(origin) - metadata_reprs = ", ".join(repr(arg) for arg in metadata) - return '%s[%s, %s]' % (cls, tp_repr, metadata_reprs) - - def _subs_tree(self, tvars=None, args=None): # noqa - if self is Annotated: - return Annotated - res = super()._subs_tree(tvars=tvars, args=args) - # Flatten nested Annotated - if isinstance(res[1], tuple) and res[1][0] is Annotated: - sub_tp = res[1][1] - sub_annot = res[1][2] - return (Annotated, sub_tp, sub_annot + res[2]) - return res - - def _get_cons(self): - """Return the class used to create instance of this type.""" - if self.__origin__ is None: - raise TypeError( - "Cannot get the underlying type of a non-specialized Annotated type." - ) - tree = self._subs_tree() - while isinstance(tree, tuple) and tree[0] is Annotated: - tree = tree[1] - if isinstance(tree, tuple): - return tree[0] - else: - return tree - - @_tp_cache - def __getitem__(self, params): - if not isinstance(params, tuple): - params = (params,) - if self.__origin__ is not None: # specializing an instantiated type - return super().__getitem__(params) - elif not isinstance(params, tuple) or len(params) < 2: - raise TypeError( - "Annotated[...] should be instantiated " - "with at least two arguments (a type and an " - "annotation)." - ) - else: - msg = "Annotated[t, ...]: t must be a type." - tp = typing._type_check(params[0], msg) - metadata = tuple(params[1:]) - return self.__class__( - self.__name__, - self.__bases__, - _no_slots_copy(self.__dict__), - tvars=_type_vars((tp,)), - # Metadata is a tuple so it won't be touched by _replace_args et al. - args=(tp, metadata), - origin=self, - ) - - def __call__(self, *args, **kwargs): - cons = self._get_cons() - result = cons(*args, **kwargs) - try: - result.__orig_class__ = self - except AttributeError: - pass - return result - - def __getattr__(self, attr): - # For simplicity we just don't relay all dunder names - if self.__origin__ is not None and not _is_dunder(attr): - return getattr(self._get_cons(), attr) - raise AttributeError(attr) - - def __setattr__(self, attr, value): - if _is_dunder(attr) or attr.startswith('_abc_'): - super().__setattr__(attr, value) - elif self.__origin__ is None: - raise AttributeError(attr) - else: - setattr(self._get_cons(), attr, value) - - def __instancecheck__(self, obj): - raise TypeError("Annotated cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Annotated cannot be used with issubclass().") - - class Annotated(metaclass=AnnotatedMeta): - """Add context specific metadata to a type. - - Example: Annotated[int, runtime_check.Unsigned] indicates to the - hypothetical runtime_check module that this type is an unsigned int. - Every other consumer of this type can ignore this metadata and treat - this type as int. - - The first argument to Annotated must be a valid type, the remaining - arguments are kept as a tuple in the __metadata__ field. - - Details: - - - It's an error to call `Annotated` with less than two arguments. - - Nested Annotated are flattened:: - - Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3] - - - Instantiating an annotated type is equivalent to instantiating the - underlying type:: - - Annotated[C, Ann1](5) == C(5) - - - Annotated can be used as a generic type alias:: - - Optimized = Annotated[T, runtime.Optimize()] - Optimized[int] == Annotated[int, runtime.Optimize()] - - OptimizedList = Annotated[List[T], runtime.Optimize()] - OptimizedList[int] == Annotated[List[int], runtime.Optimize()] - """ - - -# Python 3.8 has get_origin() and get_args() but those implementations aren't -# Annotated-aware, so we can't use those, only Python 3.9 versions will do. -# Similarly, Python 3.9's implementation doesn't support ParamSpecArgs and -# ParamSpecKwargs. -if sys.version_info[:2] >= (3, 10): - get_origin = typing.get_origin - get_args = typing.get_args -elif PEP_560: - from typing import _GenericAlias - - try: - # 3.9+ - from typing import _BaseGenericAlias - except ImportError: - _BaseGenericAlias = _GenericAlias - try: - # 3.9+ - from typing import GenericAlias - except ImportError: - GenericAlias = _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, (_GenericAlias, GenericAlias, _BaseGenericAlias, ParamSpecArgs, ParamSpecKwargs) - ): - return tp.__origin__ - if tp is Generic: - return 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, (_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 () - - -if hasattr(typing, 'TypeAlias'): - TypeAlias = typing.TypeAlias -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("{} is not subscriptable".format(self)) - -elif sys.version_info[:2] >= (3, 7): - - 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.""", - ) - -elif hasattr(typing, '_FinalTypingBase'): - - class _TypeAliasMeta(typing.TypingMeta): - """Metaclass for TypeAlias""" - - def __repr__(self): - return 'typing_extensions.TypeAlias' - - class _TypeAliasBase(typing._FinalTypingBase, metaclass=_TypeAliasMeta, _root=True): - """Special marker indicating that an assignment should - be recognized as a proper type alias definition by type - checkers. - - For example:: - - Predicate: TypeAlias = Callable[..., bool] - - It's invalid when used anywhere except as in the example above. - """ - - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("TypeAlias cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("TypeAlias cannot be used with issubclass().") - - def __repr__(self): - return 'typing_extensions.TypeAlias' - - TypeAlias = _TypeAliasBase(_root=True) -else: - - class _TypeAliasMeta(typing.TypingMeta): - """Metaclass for TypeAlias""" - - def __instancecheck__(self, obj): - raise TypeError("TypeAlias cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("TypeAlias cannot be used with issubclass().") - - def __call__(self, *args, **kwargs): - raise TypeError("Cannot instantiate TypeAlias") - - class TypeAlias(metaclass=_TypeAliasMeta, _root=True): - """Special marker indicating that an assignment should - be recognized as a proper type alias definition by type - checkers. - - For example:: - - Predicate: TypeAlias = Callable[..., bool] - - It's invalid when used anywhere except as in the example above. - """ - - __slots__ = () - - -# Python 3.10+ has PEP 612 -if hasattr(typing, 'ParamSpecArgs'): - ParamSpecArgs = typing.ParamSpecArgs - ParamSpecKwargs = typing.ParamSpecKwargs -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 "{}.args".format(self.__origin__.__name__) - - 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 "{}.kwargs".format(self.__origin__.__name__) - - -if hasattr(typing, 'ParamSpec'): - ParamSpec = typing.ParamSpec -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__ = 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 - - if not PEP_560: - # Only needed in 3.6 and lower. - def _get_type_vars(self, tvars): - if self not in tvars: - tvars.append(self) - - -# Inherits from list as a workaround for Callable checks in Python < 3.9.2. -class _ConcatenateGenericAlias(list): - # Trick Generic into looking into this for __parameters__. - if PEP_560: - __class__ = _GenericAlias - elif sys.version_info[:3] == (3, 5, 2): - __class__ = typing.TypingMeta - else: - __class__ = typing._TypingBase - - # Flag in 3.8. - _special = False - # Attribute in 3.6 and earlier. - if sys.version_info[:3] == (3, 5, 2): - _gorg = typing.GenericMeta - else: - _gorg = typing.Generic - - def __init__(self, origin, args): - super().__init__(args) - self.__origin__ = origin - self.__args__ = args - - def __repr__(self): - _type_repr = typing._type_repr - return '{origin}[{args}]'.format( - origin=_type_repr(self.__origin__), - args=', '.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, (TypeVar, ParamSpec))) - - if not PEP_560: - # Only required in 3.6 and lower. - def _get_type_vars(self, tvars): - if self.__origin__ and self.__parameters__: - typing._get_type_vars(self.__parameters__, tvars) - - -@_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) - - -if hasattr(typing, 'Concatenate'): - Concatenate = typing.Concatenate - _ConcatenateGenericAlias = typing._ConcatenateGenericAlias # noqa -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) - -elif sys.version_info[:2] >= (3, 7): - - 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. - """, - ) - -elif hasattr(typing, '_FinalTypingBase'): - - class _ConcatenateAliasMeta(typing.TypingMeta): - """Metaclass for Concatenate.""" - - def __repr__(self): - return 'typing_extensions.Concatenate' - - class _ConcatenateAliasBase( - typing._FinalTypingBase, metaclass=_ConcatenateAliasMeta, _root=True - ): - """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a - higher order function which adds, removes or transforms parameters of a - callable. - - For example:: - - Callable[Concatenate[int, P], int] - - See PEP 612 for detailed information. - """ - - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("Concatenate cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Concatenate cannot be used with issubclass().") - - def __repr__(self): - return 'typing_extensions.Concatenate' - - def __getitem__(self, parameters): - return _concatenate_getitem(self, parameters) - - Concatenate = _ConcatenateAliasBase(_root=True) -# For 3.5.0 - 3.5.2 -else: - - class _ConcatenateAliasMeta(typing.TypingMeta): - """Metaclass for Concatenate.""" - - def __instancecheck__(self, obj): - raise TypeError("TypeAlias cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("TypeAlias cannot be used with issubclass().") - - def __call__(self, *args, **kwargs): - raise TypeError("Cannot instantiate TypeAlias") - - def __getitem__(self, parameters): - return _concatenate_getitem(self, parameters) - - class Concatenate(metaclass=_ConcatenateAliasMeta, _root=True): - """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a - higher order function which adds, removes or transforms parameters of a - callable. - - For example:: - - Callable[Concatenate[int, P], int] - - See PEP 612 for detailed information. - """ - - __slots__ = () - - -if hasattr(typing, 'TypeGuard'): - TypeGuard = typing.TypeGuard -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, '{} accepts only single type.'.format(self)) - return _GenericAlias(self, (item,)) - -elif sys.version_info[:2] >= (3, 7): - - class _TypeGuardForm(typing._SpecialForm, _root=True): - def __repr__(self): - return 'typing_extensions.' + self._name - - def __getitem__(self, parameters): - item = typing._type_check( - parameters, '{} accepts only a single type'.format(self._name) - ) - return _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). - """, - ) -elif hasattr(typing, '_FinalTypingBase'): - - class _TypeGuard(typing._FinalTypingBase, _root=True): - """Special typing form used to annotate the return type of a user-defined - type guard function. ``TypeGuard`` only accepts a single type argument. - At runtime, functions marked this way should return a boolean. - - ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static - type checkers to determine a more precise type of an expression within a - program's code flow. Usually type narrowing is done by analyzing - conditional code flow and applying the narrowing to a block of code. The - conditional expression here is sometimes referred to as a "type guard". - - Sometimes it would be convenient to use a user-defined boolean function - as a type guard. Such a function should use ``TypeGuard[...]`` as its - return type to alert static type checkers to this intention. - - Using ``-> TypeGuard`` tells the static type checker that for a given - function: - - 1. The return value is a boolean. - 2. If the return value is ``True``, the type of its argument - is the type inside ``TypeGuard``. - - For example:: - - def is_str(val: Union[str, float]): - # "isinstance" type guard - if isinstance(val, str): - # Type of ``val`` is narrowed to ``str`` - ... - else: - # Else, type of ``val`` is narrowed to ``float``. - ... - - Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower - form of ``TypeA`` (it can even be a wider form) and this may lead to - type-unsafe results. The main reason is to allow for things like - narrowing ``List[object]`` to ``List[str]`` even though the latter is not - a subtype of the former, since ``List`` is invariant. The responsibility of - writing type-safe type guards is left to the user. - - ``TypeGuard`` also works with type variables. For more information, see - PEP 647 (User-Defined Type Guards). - """ - - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls( - typing._type_check( - item, '{} accepts only a single type.'.format(cls.__name__[1:]) - ), - _root=True, - ) - raise TypeError('{} cannot be further subscripted'.format(cls.__name__[1:])) - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _TypeGuard): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - TypeGuard = _TypeGuard(_root=True) -else: - - class _TypeGuardMeta(typing.TypingMeta): - """Metaclass for TypeGuard""" - - def __new__(cls, name, bases, namespace, tp=None, _root=False): - self = super().__new__(cls, name, bases, namespace, _root=_root) - if tp is not None: - self.__type__ = tp - return self - - def __instancecheck__(self, obj): - raise TypeError("TypeGuard cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("TypeGuard cannot be used with issubclass().") - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is not None: - raise TypeError('{} cannot be further subscripted'.format(cls.__name__[1:])) - - param = typing._type_check( - item, '{} accepts only single type.'.format(cls.__name__[1:]) - ) - return cls(self.__name__, self.__bases__, dict(self.__dict__), tp=param, _root=True) - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)( - self.__name__, self.__bases__, dict(self.__dict__), tp=self.__type__, _root=True - ) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not hasattr(other, "__type__"): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - class TypeGuard(typing.Final, metaclass=_TypeGuardMeta, _root=True): - """Special typing form used to annotate the return type of a user-defined - type guard function. ``TypeGuard`` only accepts a single type argument. - At runtime, functions marked this way should return a boolean. - - ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static - type checkers to determine a more precise type of an expression within a - program's code flow. Usually type narrowing is done by analyzing - conditional code flow and applying the narrowing to a block of code. The - conditional expression here is sometimes referred to as a "type guard". - - Sometimes it would be convenient to use a user-defined boolean function - as a type guard. Such a function should use ``TypeGuard[...]`` as its - return type to alert static type checkers to this intention. - - Using ``-> TypeGuard`` tells the static type checker that for a given - function: - - 1. The return value is a boolean. - 2. If the return value is ``True``, the type of its argument - is the type inside ``TypeGuard``. - - For example:: - - def is_str(val: Union[str, float]): - # "isinstance" type guard - if isinstance(val, str): - # Type of ``val`` is narrowed to ``str`` - ... - else: - # Else, type of ``val`` is narrowed to ``float``. - ... - - Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower - form of ``TypeA`` (it can even be a wider form) and this may lead to - type-unsafe results. The main reason is to allow for things like - narrowing ``List[object]`` to ``List[str]`` even though the latter is not - a subtype of the former, since ``List`` is invariant. The responsibility of - writing type-safe type guards is left to the user. - - ``TypeGuard`` also works with type variables. For more information, see - PEP 647 (User-Defined Type Guards). - """ - - __type__ = None diff --git a/pyproject.toml b/pyproject.toml index 2fc48ec0..83e11b00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,12 +25,11 @@ classifiers = [ "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Libraries :: Python Modules", ] # compatible releases @@ -89,7 +88,7 @@ run = "pytest{env:HATCH_TEST_ARGS:} {args}" run-cov = "coverage run -m pytest{env:HATCH_TEST_ARGS:} {args}" cov-combine = "coverage combine" cov-report = "coverage report -m --skip-covered --fail-under=80 --omit=podman/tests/*" -cov-all = ["run-cov", "cov-report"] +cov = ["run-cov", "cov-report"] #TODO figure it out later [tool.hatch.envs.test.env-vars] @@ -98,7 +97,7 @@ PODMAN_BINARY = "podman" DEBUG = "0" [[tool.hatch.envs.test.matrix]] -python = ["3.12", "3.11", "3.10", "3.9", "3.8", "3.6"] +python = ["3.13", "3.12", "3.11", "3.10", "3.9"] [tool.hatch.envs.docs] features = ["docs"] @@ -115,9 +114,6 @@ extra-dependencies = [ "requests-mock >= 1.11.0", ] -[[tool.hatch.envs.hatch-test.matrix]] -python = ["3.12", "3.11", "3.10", "3.9", "3.8", "3.6"] - [tool.ruff] line-length = 100 [tool.ruff.format] From 5b188794a54c006e5408a9b7ffa3cea5f42846f1 Mon Sep 17 00:00:00 2001 From: Nicola Sella Date: Thu, 26 Sep 2024 14:02:33 +0200 Subject: [PATCH 6/6] Update test dependencies 1. This commit introduces the [test] subpackage 2. It removes requests-mock <= 1.11 requirement At the time of writing this commit requests-mock is packaged with the following versions in Fedora and Epel. Fedora 42 python-requests-mock-1.10.0-9.fc41 Fedora 41 python-requests-mock-1.10.0-9.fc41 Fedora 40 python-requests-mock-1.10.0-7.fc40 Fedora 39 python-requests-mock-1.10.0-5.fc39 Fedora EPEL 9 python-requests-mock-1.10.0-2.el9 Fedora EPEL 8 python-requests-mock-1.7.0-1.el8 3. It excludes the test subpackage and the check dependencies for test dependencies when run for distros which are not Fedora i.e. CentOS Stream Signed-off-by: Nicola Sella --- pyproject.toml | 6 ++++++ rpm/python-podman.spec | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 83e11b00..cf219cb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,12 @@ progress_bar = [ docs = [ "sphinx" ] +test = [ + "coverage", + "fixtures", + "pytest", + "requests-mock", +] [project.urls] "Bug Tracker" = "https://github.com/containers/podman-py/issues" diff --git a/rpm/python-podman.spec b/rpm/python-podman.spec index 75ff19e1..531844b3 100644 --- a/rpm/python-podman.spec +++ b/rpm/python-podman.spec @@ -61,7 +61,13 @@ Summary: %{summary} %if !%{defined rhel8_py} %generate_buildrequires -%pyproject_buildrequires %{?with_tests:-t} +# In fedora all testing dependencies are packaged +# In CentOS stream they are not. Tests are run upstream +%if 0%{?fedora} +%pyproject_buildrequires -x test +%else +%pyproject_buildrequires +%endif %endif %build @@ -83,7 +89,13 @@ export PBR_VERSION="0.0.0" %if !%{defined rhel8_py} %check -%pyproject_check_import -e podman.api.typing_extensions +# In fedora all testing dependencies are packaged +# In CentOS stream they are not. Tests are run upstream +%if 0%{?fedora} +%pyproject_check_import +%else +%pyproject_check_import -e podman.tests.* +%endif %endif %if %{defined rhel8_py}