├── .flake8 ├── tox_asdf ├── __init__.py └── plugin.py ├── tox.ini ├── CHANGELOG.md ├── .editorconfig ├── .travis.yml ├── .pre-commit-config.yaml ├── bumpr.rc ├── .github └── workflows │ └── ci.yml ├── LICENSE ├── tests ├── test_options.py ├── test_versions.py ├── test_asdf.py ├── conftest.py └── test_tox_python_executable.py ├── .gitignore ├── README.md ├── pyproject.toml └── pdm.lock /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 110 3 | exclude = 4 | .git 5 | .tox 6 | venv/ 7 | .venv/ 8 | -------------------------------------------------------------------------------- /tox_asdf/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.1.dev" 2 | __description__ = "A plugin telling tox to use asdf to find python executables" 3 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{37,38,39,310,311,py3}, lint 3 | isolated_build = true 4 | 5 | [testenv] 6 | groups = test 7 | commands = test {posargs} 8 | 9 | [testenv:lint] 10 | groups = 11 | test 12 | lint 13 | commands = lint 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Current (in progress) 4 | 5 | - Supports Python >=3.7 6 | - Replace `pkg_resources` by `packaging` 7 | - Support all Python flavours provided by ASDF (and removed `LegacyVersion` deprecation warning) 8 | 9 | ## 0.1.0 (2019-01-05) 10 | 11 | Initial release 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | indent_style = space 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.py] 14 | indent_size = 4 15 | max_line_length = 120 16 | 17 | [*.{yml,yaml,toml,json}] 18 | indent_size = 2 19 | 20 | [*.md] 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | dist: bionic 3 | python: 4 | - 2.7 5 | - 3.5 6 | - 3.6 7 | - 3.7 8 | - 3.8 9 | - pypy2.7-7.2.0 10 | - pypy3.6-7.2.0 11 | install: 12 | - pip install -e . -r requirements/develop.pip tox codecov 13 | script: 14 | - flake8 15 | - pytest -v --cov=tox_asdf --cov-report=term --cov-report=xml:reports/coverage.xml 16 | after_success: 17 | - codecov --file=reports/coverage.xml 18 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | ci: 2 | autoupdate_schedule: monthly 3 | repos: 4 | - repo: https://github.com/psf/black 5 | rev: 22.10.0 6 | hooks: 7 | - id: black 8 | 9 | - repo: https://github.com/PyCQA/flake8 10 | rev: 6.0.0 11 | hooks: 12 | - id: flake8 13 | 14 | - repo: https://github.com/pycqa/isort 15 | rev: 5.10.1 16 | hooks: 17 | - id: isort 18 | 19 | - repo: https://github.com/pre-commit/mirrors-mypy 20 | rev: v0.991 21 | hooks: 22 | - id: mypy 23 | additional_dependencies: 24 | - packaging 25 | - tox 26 | exclude: ^tests/ 27 | 28 | -------------------------------------------------------------------------------- /bumpr.rc: -------------------------------------------------------------------------------- 1 | [bumpr] 2 | file = tox_asdf/__init__.py 3 | vcs = git 4 | tag_annotation = version {version} 5 | push = true 6 | tests = tox 7 | publish = 8 | python setup.py sdist bdist_wheel 9 | twine upload dist/* 10 | clean = 11 | python setup.py clean 12 | rm -fr dist 13 | 14 | files = README.md 15 | 16 | [bump] 17 | unsuffix = true 18 | message = Bumped version {version} 19 | 20 | [prepare] 21 | part = patch 22 | suffix = dev 23 | 24 | [changelog] 25 | file = CHANGELOG.md 26 | bump = ## {version} ({date:%Y-%m-%d}) 27 | prepare = ## Current (in progress) 28 | separator = 29 | 30 | [replace] 31 | dev = ?branch=master 32 | stable = ?tag={version} 33 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - "*.md" 7 | push: 8 | branches: 9 | - main 10 | paths-ignore: 11 | - "*.md" 12 | 13 | jobs: 14 | tests: 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev"] 19 | os: [ubuntu-latest, macOS-latest, windows-latest] 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Set up PDM 24 | uses: pdm-project/setup-pdm@main 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | prerelease: true 28 | 29 | - name: Install dependencies 30 | run: pdm sync -d -G test 31 | 32 | - name: Run Tests 33 | run: pdm run cover -v 34 | 35 | - name: Upload coverage report to Codecov 36 | uses: codecov/codecov-action@v3 37 | with: 38 | files: reports/coverage.xml 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Api Hackers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/test_options.py: -------------------------------------------------------------------------------- 1 | import tox.session 2 | 3 | 4 | def init(args): 5 | config = tox.session.load_config(args) 6 | return tox.session.build_session(config) 7 | 8 | 9 | class TestToxOptions: 10 | def test_default_options(self, CFG): 11 | init([]) 12 | assert CFG.verbose is False 13 | assert CFG.debug is False 14 | assert CFG.install is False 15 | assert CFG.no_fallback is False 16 | 17 | def test_asdf_no_fallback(self, CFG): 18 | init(["--asdf-no-fallback"]) 19 | assert CFG.verbose is False 20 | assert CFG.debug is False 21 | assert CFG.install is False 22 | assert CFG.no_fallback is True 23 | 24 | def test_asdf_install(self, CFG): 25 | init(["--asdf-install"]) 26 | assert CFG.verbose is False 27 | assert CFG.debug is False 28 | assert CFG.install is True 29 | assert CFG.no_fallback is False 30 | 31 | def test_verbose(self, CFG): 32 | init(["-v"]) 33 | assert CFG.verbose is True 34 | assert CFG.debug is False 35 | assert CFG.install is False 36 | assert CFG.no_fallback is False 37 | 38 | def test_debug(self, CFG): 39 | init(["-vv"]) 40 | assert CFG.verbose is True 41 | assert CFG.debug is True 42 | assert CFG.install is False 43 | assert CFG.no_fallback is False 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | reports/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | db.sqlite3 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # Environments 86 | .env 87 | .venv 88 | env/ 89 | venv/ 90 | ENV/ 91 | env.bak/ 92 | venv.bak/ 93 | .pdm.toml 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | 108 | # asdf 109 | .tool-versions 110 | 111 | # VSCode 112 | .vscode 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tox-asdf 2 | 3 | [![Tests](https://github.com/apihackers/tox-asdf/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/apihackers/tox-asdf/actions/workflows/ci.yml) 4 | [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/apihackers/tox-asdf/main.svg)](https://results.pre-commit.ci/latest/github/apihackers/tox-asdf/main) 5 | [![codecov](https://codecov.io/gh/apihackers/tox-asdf/branch/main/graph/badge.svg)](https://codecov.io/gh/apihackers/tox-asdf) 6 | [![Last version](https://img.shields.io/pypi/v/tox-asdf.svg)](https://pypi.org/project/tox-asdf) 7 | [![License](https://img.shields.io/pypi/l/tox-asdf.svg?style=flat)](https://pypi.org/project/tox-asdf) 8 | [![Supported Python versions](https://img.shields.io/pypi/pyversions/tox-asdf.svg)](https://pypi.org/project/tox-asdf) 9 | 10 | A Tox plugin using [asdf] to find python executables. 11 | 12 | 13 | ## Prerequisites 14 | 15 | This plugin is made exclusively to support [asdf] so obviously you will need a functional [asdf] installation as well as the [`asdf-python`][asdf-python] plugin. 16 | 17 | 18 | ## Installation 19 | 20 | Simply install `tox-asdf` in addition to `tox` to get ready: 21 | 22 | ```shell 23 | pip install tox tox-asdf 24 | ``` 25 | 26 | That's it, you can now run `tox` as usual but using [asdf] Python installations. 27 | 28 | ## Options 29 | 30 | ### No fallback on system pythons 31 | 32 | By default this plugin won't fail if a required Python version is missing from `æsdf`, tox will fallback on its classic way of finding Python binaries from `$PATH`. 33 | You can override this behavior and force `tox` to only use `asdf` by using the `--asdf-no-fallback` option: 34 | 35 | ```shell 36 | tox --asdf-no-fallback 37 | ``` 38 | 39 | ### Automatically install pythons 40 | 41 | By default, `tox-asdf` won't try to install missing Python version, but you can force this by using the `--asdf-install` option. 42 | 43 | ```shell 44 | tox --asdf-install 45 | ``` 46 | 47 | Obviously this will only useful on the first run. 48 | 49 | 50 | [asdf]: https://github.com/asdf-vm/asdf 51 | [asdf-python]: https://github.com/asdf-vm/asdf-python 52 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["pdm-pep517>=1.0.0"] 3 | build-backend = "pdm.pep517.api" 4 | 5 | [project] 6 | name = "tox-asdf" 7 | version = "0.1.1.dev" 8 | description = "A plugin telling tox to use asdf to find python executables" 9 | authors = [ 10 | {name = "Axel H.", email = "noirbizarre+tox-asdf@gmail.com"}, 11 | {name = "API Hackers", email = "pypi+tox-asdf@apihackers.com"}, 12 | ] 13 | requires-python = ">=3.7" 14 | dependencies = [ 15 | "tox>=2.0", 16 | "packaging>=21.3", 17 | ] 18 | readme = "README.md" 19 | license = {text = "MIT"} 20 | keywords = [ 21 | "tox", 22 | "asdf", 23 | ] 24 | classifiers = [ 25 | "Development Status :: 3 - Alpha", 26 | "Intended Audience :: Developers", 27 | "License :: OSI Approved :: MIT License", 28 | "Operating System :: OS Independent", 29 | "Programming Language :: Python", 30 | "Programming Language :: Python :: 3", 31 | "Programming Language :: Python :: 3.7", 32 | "Programming Language :: Python :: 3.8", 33 | "Programming Language :: Python :: 3.9", 34 | "Programming Language :: Python :: 3.10", 35 | "Programming Language :: Python :: 3.11", 36 | "Programming Language :: Python :: Implementation :: CPython", 37 | "Programming Language :: Python :: Implementation :: PyPy", 38 | "Topic :: Software Development", 39 | "Topic :: Software Development :: Libraries :: Python Modules", 40 | ] 41 | 42 | [project.entry-points.tox] 43 | asdf = "tox_asdf.plugin" 44 | 45 | [project.urls] 46 | Homepage = "https://github.com/apihackers/tox-asdf" 47 | 48 | [project.optional-dependencies] 49 | test = [ 50 | "pytest>=4.0.0", 51 | "pytest-cov", 52 | "pytest-mock", 53 | "pytest-sugar", 54 | ] 55 | lint = [ 56 | "flake8", 57 | "black", 58 | "isort", 59 | "mypy", 60 | ] 61 | tox = [ 62 | "tox", 63 | "tox-pdm>=0.5", 64 | ] 65 | 66 | [tool.pdm] 67 | [tool.pdm.scripts] 68 | lint = "pre-commit run --all-files" 69 | test = {cmd = "pytest", help = "Run the test suite"} 70 | cover = {composite = [ 71 | "test --cov --cov-report=term --cov-report=xml --cov-report=html --junitxml=reports/tests.xml", 72 | ], help = "Run the test suite with coverage"} 73 | tox = "tox" 74 | 75 | 76 | [tool.pdm.vscode] 77 | linters = ["flake8"] 78 | formatter = "black" 79 | test = "pytest" 80 | isort = true 81 | 82 | 83 | [tool.black] 84 | line-length = 100 85 | preview = true 86 | 87 | 88 | [tool.isort] 89 | profile = "black" 90 | atomic = true 91 | filter_files = true 92 | known_first_party = ["tox_asdf"] 93 | known_third_party = ["tox"] 94 | 95 | 96 | [tool.pytest.ini_options] 97 | addopts = "-ra" 98 | testpaths = [ 99 | "tests/", 100 | ] 101 | markers = [ 102 | "pythons: mark fake Python installs", 103 | "all_pythons: makr fake available Pythons", 104 | "asdf_error: Expect an ASDF error", 105 | "asdf_missing: Mark a test runnning without ASDF", 106 | "asdf_python_missing: Mark a test running with a missing Python version" 107 | ] 108 | 109 | 110 | [tool.mypy] 111 | python_version = "3.10" 112 | warn_return_any = true 113 | warn_unused_configs = true 114 | 115 | 116 | [tool.coverage] 117 | [tool.coverage.run] 118 | branch = true 119 | source = [ 120 | "tox_asdf", 121 | ] 122 | [tool.coverage.html] 123 | directory = "reports/coverage" 124 | [tool.coverage.xml] 125 | output = "reports/coverage.xml" 126 | -------------------------------------------------------------------------------- /tests/test_versions.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | import pytest 4 | 5 | from tox_asdf import plugin 6 | 7 | 8 | def test_only_one_minor(): 9 | versions = "2.7.0", "3.5.0", "3.6.0", "3.7.0" 10 | version = plugin.best_version("3.6", versions) 11 | assert version == "3.6.0" 12 | 13 | 14 | def test_best_patch(): 15 | versions = "2.7.0", "3.5.0", "3.6.0", "3.6.1", "3.6.2", "3.6.3", "3.7.0" 16 | version = plugin.best_version("3.6", versions) 17 | assert version == "3.6.3" 18 | 19 | 20 | def test_best_patch_not_alphabetical(): 21 | versions = "2.7.9", "2.7.17" 22 | version = plugin.best_version("2.7", versions) 23 | assert version == "2.7.17" 24 | 25 | 26 | def test_multiple_digits(): 27 | versions = "2.7.0", "3.5.0", "3.6.0", "3.6.1", "3.6.10", "3.6.11", "3.7.0" 28 | version = plugin.best_version("3.6", versions) 29 | assert version == "3.6.11" 30 | 31 | 32 | def test_dev_suffix(): 33 | versions = "2.7.0", "3.5.0", "3.6-dev", "3.6.0", "3.7.0" 34 | version = plugin.best_version("3.6", versions) 35 | assert version == "3.6.0" 36 | 37 | 38 | def test_dev_only(): 39 | versions = "2.7.0", "3.5.0", "3.6-dev", "3.7.0", "3.8.0", "3.9.0", "3.10.0", "3.11-dev" 40 | assert plugin.best_version("3.6", versions) == "3.6-dev" 41 | assert plugin.best_version("3.11", versions) == "3.11-dev" 42 | 43 | 44 | @pytest.mark.parametrize("version", ["2.6", "3.4", "3.8"]) 45 | def test_not_found(version): 46 | versions = "2.7.0", "3.5.0", "3.6.0", "3.6.1", "3.6.2", "3.6.3", "3.7.0" 47 | assert plugin.best_version("2.6", versions) is None 48 | 49 | 50 | @pytest.mark.parametrize("flavour", plugin.KNOWN_FLAVOURS) 51 | def test_known_flavours(flavour: str): 52 | best_version = f"{flavour}-3.10.5" 53 | versions = ( 54 | "2.7.0", 55 | "3.6.0", 56 | "3.7.0", 57 | f"{flavour}-3.1.0", 58 | f"{flavour}-3.10.0", 59 | best_version, 60 | ) 61 | with warnings.catch_warnings(): # Do not rely on LegacyVersion 62 | warnings.simplefilter("error") 63 | version = plugin.best_version(flavour, versions) 64 | assert version == best_version 65 | 66 | 67 | def test_pypy(): 68 | versions = ( 69 | "2.7.0", 70 | "3.6.0", 71 | "3.7.0", 72 | "pypy2.7-5.0.0", 73 | "pypy2.7-6.0.0", 74 | "pypy3.8-7.0.0", 75 | ) 76 | version = plugin.best_version("pypy2.7", versions) 77 | assert version == "pypy2.7-6.0.0" 78 | 79 | 80 | def test_pypy_not_found(): 81 | versions = "2.7.0", "3.6.0", "3.7.0", "pypy3.8-7.0.0" 82 | assert plugin.best_version("pypy2.7", versions) is None 83 | 84 | 85 | @pytest.mark.parametrize( 86 | "version, match", 87 | [ 88 | ("pypy3", "pypy3.8-7.0.0"), 89 | ("pypy3.8", "pypy3.8-7.0.0"), 90 | ("pypy3.5", "pypy3.5-6.0.0"), 91 | ], 92 | ) 93 | def test_pypy3(version, match): 94 | versions = ( 95 | "2.7.0", 96 | "3.6.0", 97 | "3.7.0", 98 | "pypy2.7-6.0.0", 99 | "pypy3.5-6.0.0", 100 | "pypy3.8-6.0.0", 101 | "pypy3.8-7.0.0", 102 | ) 103 | best = plugin.best_version(version, versions) 104 | assert best == match 105 | 106 | 107 | def test_pypy3_not_found(): 108 | versions = "2.7.0", "3.6.0", "3.7.0", "pypy2.7-6.0.0" 109 | assert plugin.best_version("pypy3.5", versions) is None 110 | -------------------------------------------------------------------------------- /tests/test_asdf.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from tox_asdf import plugin 4 | 5 | 6 | class TestAsdfGetInstalled: 7 | @pytest.mark.asdf_missing 8 | def test_asdf_missing(self, asdf): 9 | with pytest.raises(plugin.AsdfMissing): 10 | plugin.asdf_get_installed("3.6") 11 | 12 | @pytest.mark.asdf_python_missing 13 | def test_asdf_python_plugin_missing(self, asdf): 14 | with pytest.raises(plugin.AsdfPluginMissing): 15 | plugin.asdf_get_installed("3.6") 16 | 17 | @pytest.mark.asdf_error(42, "Unknown error") 18 | def test_unknown_error(self, asdf): 19 | with pytest.raises(plugin.AsdfError): 20 | plugin.asdf_get_installed("3.6") 21 | 22 | @pytest.mark.pythons("2.7.15", "3.6.0", "pypy2.7-6.0.0", "pypy3.8-7.0.0") 23 | def test_return_python_binary_path(self, asdf): 24 | assert plugin.asdf_get_installed("3.6") == "3.6.0" 25 | 26 | @pytest.mark.pythons("2.7.15", "3.6.0", "pypy2.7-6.0.0", "pypy3.8-7.0.0") 27 | def test_return_pypy_python_binary_path(self, asdf): 28 | assert plugin.asdf_get_installed("pypy2.7") == "pypy2.7-6.0.0" 29 | 30 | @pytest.mark.pythons("2.7.15", "3.6.0", "pypy2.7-6.0.0", "pypy3.5-6.0.0", "pypy3.8-7.0.0") 31 | def test_return_pypy3_5_python_binary_path(self, asdf): 32 | assert plugin.asdf_get_installed("pypy3.5") == "pypy3.5-6.0.0" 33 | 34 | @pytest.mark.pythons("2.7.15", "3.6.0", "pypy2.7-6.0.0", "pypy3.8-7.0.0") 35 | def test_return_pypy3_python_binary_path(self, asdf): 36 | assert plugin.asdf_get_installed("pypy3.8") == "pypy3.8-7.0.0" 37 | 38 | @pytest.mark.pythons("2.7.15", "3.6.0", "pypy2.7-6.0.0", "pypy3.8-7.0.0") 39 | def test_rely_on_best_version(self, asdf, mocker): 40 | best_version = mocker.patch.object(plugin, "best_version", return_value="result") 41 | assert plugin.asdf_get_installed("3.6") == "result" 42 | best_version.assert_called_once_with("3.6", mocker.ANY) 43 | 44 | def test_return_none_if_no_python(self, asdf): 45 | assert plugin.asdf_get_installed("3.6") is None 46 | 47 | 48 | class TestAsdfWhich: 49 | def test_matching_version(self, asdf): 50 | assert plugin.asdf_which("3.6.0") == asdf.python_bin("3.6.0") 51 | 52 | @pytest.mark.asdf_error(42, "Unknown error") 53 | def test_error(self, asdf): 54 | with pytest.raises(plugin.AsdfError): 55 | plugin.asdf_which("3.6.0") 56 | 57 | 58 | class TestAsdfInstall: 59 | @pytest.mark.asdf_error("list-all", 42, "Unknown error") 60 | def test_list_all_error(self, asdf): 61 | with pytest.raises(plugin.AsdfError): 62 | plugin.asdf_install("3.6") 63 | 64 | @pytest.mark.asdf_error("install", 42, "Unknown error") 65 | def test_install_error(self, asdf): 66 | with pytest.raises(plugin.AsdfError): 67 | plugin.asdf_install("3.6") 68 | 69 | @pytest.mark.all_pythons("2.7.15", "3.6.0", "pypy2.7-6.0.0", "pypy3.8-7.0.0") 70 | def test_install_python(self, asdf): 71 | assert plugin.asdf_install("3.6") == "3.6.0" 72 | 73 | @pytest.mark.all_pythons("2.7.15", "3.6.0", "pypy2.7-6.0.0", "pypy3.8-7.0.0") 74 | def test_install_pypy(self, asdf): 75 | assert plugin.asdf_install("pypy2.7") == "pypy2.7-6.0.0" 76 | 77 | @pytest.mark.all_pythons("2.7.15", "3.6.0", "pypy2.7-6.0.0", "pypy3.8-7.0.0") 78 | def test_install_pypy3(self, asdf): 79 | assert plugin.asdf_install("pypy3") == "pypy3.8-7.0.0" 80 | 81 | @pytest.mark.all_pythons("2.7.15", "3.6.0", "pypy2.7-6.0.0", "pypy3.8-7.0.0") 82 | def test_rely_on_best_version(self, asdf, mocker): 83 | best_version = mocker.patch.object(plugin, "best_version", return_value="result") 84 | assert plugin.asdf_install("3.6") == "result" 85 | best_version.assert_called_once_with("3.6", mocker.ANY) 86 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | import pytest 5 | 6 | 7 | class MockPopen(object): 8 | def __init__(self, args): 9 | self.args = args 10 | self.returncode = 0 11 | self.stdin = None 12 | self.stdout = None 13 | self.stderr = None 14 | self.pid = None 15 | self.encoding = None 16 | self.errors = None 17 | 18 | def communicate(self, input=None): 19 | pass 20 | 21 | def __enter__(self): 22 | return self 23 | 24 | def __exit__(self, type, value, traceback): 25 | pass 26 | 27 | def poll(self): 28 | return self.returncode 29 | 30 | def wait(self, timeout=None): 31 | return self.returncode 32 | 33 | 34 | class Asdf: 35 | installs = "/path/to/installs" 36 | installed = True 37 | plugin_installed = True 38 | error_cmd = None 39 | error_code = None 40 | error_text = None 41 | 42 | def __init__(self, mocker, pythons, all_pythons=None): 43 | self.mocker = mocker 44 | self.pythons = pythons 45 | self.all_pythons = all_pythons 46 | self.commands = { 47 | "list": self.list_python, 48 | "list-all": self.list_all_python, 49 | "where": self.where_python, 50 | "install": self.install_python, 51 | } 52 | 53 | def python_home(self, python): 54 | return os.path.join(self.installs, "python", python) 55 | 56 | def python_bin(self, python): 57 | return os.path.join(self.python_home(python), "bin", "python") 58 | 59 | def popen(self, *args, **kwargs): 60 | cmd = args[0] 61 | popen = MockPopen(cmd) 62 | stdout, stderr, code = "", "", -1 63 | 64 | if isinstance(cmd, str): 65 | cmd = [p.strip() for p in cmd.split()] 66 | cmd, args = cmd[0], cmd[1:] 67 | 68 | if self.error_code and self.error_cmd is None: 69 | code = self.error_code 70 | stderr = self.error_text 71 | elif cmd == "asdf" and not self.installed: 72 | code = 127 73 | elif cmd == "asdf" and len(args): 74 | action = args[0] 75 | command = self.commands.get(action) 76 | if self.error_cmd and self.error_cmd == action: 77 | code = self.error_code 78 | stderr = self.error_text 79 | elif command: 80 | stdout, stderr, code = command(*args) 81 | popen.returncode = code 82 | if kwargs.get("stderr", None) is subprocess.STDOUT: 83 | stdout = stdout + stderr 84 | stderr = None 85 | popen.communicate = self.mocker.Mock(return_value=(stdout, stderr)) 86 | return popen 87 | 88 | def _asdf_call(self, args, length, output): 89 | if len(args) < 2: 90 | return self.invalid_command(*args) 91 | elif len(args) != length or args[1] != "python": 92 | return self.invalid_command(*args) 93 | elif not self.plugin_installed: 94 | return "", "No such plugin: python", 1 95 | return output(args), "", 0 96 | 97 | def invalid_command(self, *args): 98 | cmd = "asdf {}".format(" ".join(args)) 99 | return "", "Invalid asdf command syntax: {}".format(cmd), -1 100 | 101 | def list_python(self, *args): 102 | return self._asdf_call(args, 2, lambda a: "\n".join(self.pythons)) 103 | 104 | def list_all_python(self, *args): 105 | return self._asdf_call(args, 2, lambda a: "\n".join(self.all_pythons)) 106 | 107 | def where_python(self, *args): 108 | return self._asdf_call(args, 3, lambda a: self.python_home(a[2])) 109 | 110 | def install_python(self, *args): 111 | return self._asdf_call(args, 3, lambda a: self.python_home(a[2])) 112 | 113 | 114 | @pytest.fixture(name="asdf") 115 | def mock_asdf(request, mocker): 116 | marker = request.node.get_closest_marker("pythons") 117 | pythons = set(marker.args if marker and marker.args else []) 118 | 119 | marker = request.node.get_closest_marker("all_pythons") 120 | all_pythons = set(marker.args if marker and marker.args else []) 121 | 122 | asdf = Asdf(mocker, pythons, all_pythons) 123 | 124 | marker = request.node.get_closest_marker("asdf_error") 125 | if marker and marker.args: 126 | if len(marker.args) == 3: 127 | asdf.error_cmd, asdf.error_code, asdf.error_text = marker.args 128 | elif len(marker.args) == 2: 129 | asdf.error_code, asdf.error_text = marker.args 130 | elif len(marker.args) == 1: 131 | asdf.error_code = marker.args[0] 132 | else: 133 | msg = "Unknown marker signature asdf_error({})" 134 | raise ValueError(msg.format(", ".join(marker.args))) 135 | if request.node.get_closest_marker("asdf_missing"): 136 | asdf.installed = False 137 | if request.node.get_closest_marker("asdf_python_missing"): 138 | asdf.plugin_installed = False 139 | subprocess.Popen = asdf.popen 140 | 141 | return asdf 142 | 143 | 144 | @pytest.fixture(name="LOG") 145 | def mock_log(mocker): 146 | from tox_asdf import plugin 147 | 148 | LOG = mocker.patch.object(plugin, "LOG") 149 | return LOG 150 | 151 | 152 | @pytest.fixture(name="CFG") 153 | def mock_cfg(mocker): 154 | from tox_asdf import plugin 155 | 156 | backup = plugin.CFG 157 | plugin.CFG = plugin.Config() 158 | yield plugin.CFG 159 | plugin.CFG = backup 160 | -------------------------------------------------------------------------------- /tests/test_tox_python_executable.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from tox_asdf import plugin 4 | 5 | 6 | class EnvConfig(object): 7 | """A mock envconfig""" 8 | 9 | def __init__(self, basepython="python3.6"): 10 | self.basepython = basepython 11 | 12 | 13 | class TestToxGetPythonExecutable: 14 | @pytest.mark.asdf_missing 15 | def test_asdf_missing(self, asdf, LOG): 16 | python = plugin.tox_get_python_executable(EnvConfig()) 17 | assert python is None 18 | LOG.error.assert_called_once() 19 | 20 | @pytest.mark.asdf_python_missing 21 | def test_asdf_python_plugin_missing(self, asdf, LOG): 22 | python = plugin.tox_get_python_executable(EnvConfig()) 23 | assert python is None 24 | LOG.error.assert_called_once() 25 | 26 | @pytest.mark.asdf_error(42, "Unknown error") 27 | def test_asdf_unknown_error(self, asdf, LOG): 28 | python = plugin.tox_get_python_executable(EnvConfig()) 29 | assert python is None 30 | LOG.error.assert_called_once() 31 | 32 | @pytest.mark.asdf_error("list", 42, "Unknown error") 33 | def test_asdf_list_error(self, asdf, LOG): 34 | python = plugin.tox_get_python_executable(EnvConfig()) 35 | assert python is None 36 | LOG.error.assert_called_once() 37 | 38 | @pytest.mark.pythons("3.6.0") 39 | @pytest.mark.asdf_error("where", 42, "Unknown error") 40 | def test_asdf_where_error(self, asdf, LOG): 41 | python = plugin.tox_get_python_executable(EnvConfig()) 42 | assert python is None 43 | LOG.error.assert_called_once() 44 | 45 | def test_basepython_is_not_python(self): 46 | envconfig = EnvConfig("*TEST*") 47 | python = plugin.tox_get_python_executable(envconfig) 48 | assert python is None 49 | 50 | def test_return_none_if_no_python(self, asdf): 51 | assert plugin.tox_get_python_executable(EnvConfig()) is None 52 | 53 | @pytest.mark.all_pythons("2.7.15", "3.6.0", "pypy2.7-6.0.0", "pypy3.8-7.0.0") 54 | def test_install_python_if_no_python_but_flag(self, asdf, CFG): 55 | CFG.install = True 56 | python = plugin.tox_get_python_executable(EnvConfig()) 57 | assert python == asdf.python_bin("3.6.0") 58 | 59 | @pytest.mark.all_pythons("2.7.15", "pypy2.7-6.0.0", "pypy3.8-7.0.0") 60 | def test_install_python_but_return_none_if_no_match(self, asdf, CFG): 61 | CFG.install = True 62 | assert plugin.tox_get_python_executable(EnvConfig()) is None 63 | 64 | @pytest.mark.pythons("2.7.15", "3.6.0", "pypy2.7-6.0.0", "pypy3.8-7.0.0") 65 | def test_return_python_binary_path(self, asdf): 66 | python = plugin.tox_get_python_executable(EnvConfig()) 67 | assert python == asdf.python_bin("3.6.0") 68 | 69 | @pytest.mark.pythons("2.7.15", "3.6.0", "pypy2.7-6.0.0", "pypy3.8-7.0.0") 70 | def test_return_pypy_python_binary_path(self, asdf): 71 | envconfig = EnvConfig("pypy") 72 | python = plugin.tox_get_python_executable(envconfig) 73 | assert python == asdf.python_bin("pypy2.7-6.0.0") 74 | 75 | @pytest.mark.pythons("2.7.15", "3.6.0", "pypy2.7-6.0.0", "pypy3.8-7.0.0") 76 | def test_return_pypy3_python_binary_path(self, asdf): 77 | envconfig = EnvConfig("pypy3") 78 | python = plugin.tox_get_python_executable(envconfig) 79 | assert python == asdf.python_bin("pypy3.8-7.0.0") 80 | 81 | 82 | class TestToxGetPythonExecutableNoFallback: 83 | @pytest.fixture(autouse=True) 84 | def set_no_fallback(self, CFG): 85 | CFG.no_fallback = True 86 | 87 | @pytest.mark.asdf_missing 88 | def test_asdf_missing(self, asdf, LOG): 89 | with pytest.raises(plugin.AsdfError): 90 | plugin.tox_get_python_executable(EnvConfig()) 91 | LOG.error.assert_called_once() 92 | 93 | @pytest.mark.asdf_python_missing 94 | def test_asdf_python_plugin_missing(self, asdf, LOG): 95 | with pytest.raises(plugin.AsdfError): 96 | plugin.tox_get_python_executable(EnvConfig()) 97 | LOG.error.assert_called_once() 98 | 99 | @pytest.mark.asdf_error(42, "Unknown error") 100 | def test_asdf_unknown_error(self, asdf, LOG): 101 | with pytest.raises(plugin.AsdfError): 102 | plugin.tox_get_python_executable(EnvConfig()) 103 | LOG.error.assert_called_once() 104 | 105 | @pytest.mark.asdf_error("list", 42, "Unknown error") 106 | def test_asdf_list_error(self, asdf, LOG): 107 | with pytest.raises(plugin.AsdfError): 108 | plugin.tox_get_python_executable(EnvConfig()) 109 | LOG.error.assert_called_once() 110 | 111 | @pytest.mark.pythons("3.6.0") 112 | @pytest.mark.asdf_error("where", 42, "Unknown error") 113 | def test_asdf_which_error(self, asdf, LOG): 114 | with pytest.raises(plugin.AsdfError): 115 | plugin.tox_get_python_executable(EnvConfig()) 116 | LOG.error.assert_called_once() 117 | 118 | def test_basepython_is_not_python(self): 119 | envconfig = EnvConfig("*TEST*") 120 | assert plugin.tox_get_python_executable(envconfig) is None 121 | 122 | def test_raise_if_no_python(self, asdf): 123 | with pytest.raises(plugin.AsdfError): 124 | plugin.tox_get_python_executable(EnvConfig()) 125 | 126 | @pytest.mark.all_pythons("2.7.15", "3.6.0", "pypy2.7-6.0.0", "pypy3.8-7.0.0") 127 | def test_install_python_if_no_python_but_flag(self, asdf, CFG): 128 | CFG.install = True 129 | python = plugin.tox_get_python_executable(EnvConfig()) 130 | assert python == asdf.python_bin("3.6.0") 131 | 132 | @pytest.mark.all_pythons("2.7.15", "pypy2.7-6.0.0", "pypy3.8-7.0.0") 133 | def test_install_python_but_raise_if_no_match(self, asdf, CFG): 134 | CFG.install = True 135 | with pytest.raises(plugin.AsdfError): 136 | plugin.tox_get_python_executable(EnvConfig()) 137 | 138 | @pytest.mark.pythons("2.7.15", "3.6.0", "pypy2.7-6.0.0", "pypy3.8-7.0.0") 139 | def test_return_python_binary_path(self, asdf): 140 | python = plugin.tox_get_python_executable(EnvConfig()) 141 | assert python == asdf.python_bin("3.6.0") 142 | 143 | @pytest.mark.pythons("2.7.15", "3.6.0", "pypy2.7-6.0.0", "pypy3.8-7.0.0") 144 | def test_return_pypy_python_binary_path(self, asdf): 145 | envconfig = EnvConfig("pypy") 146 | python = plugin.tox_get_python_executable(envconfig) 147 | assert python == asdf.python_bin("pypy2.7-6.0.0") 148 | 149 | @pytest.mark.pythons("2.7.15", "3.6.0", "pypy2.7-6.0.0", "pypy3.8-7.0.0") 150 | def test_return_pypy3_python_binary_path(self, asdf): 151 | envconfig = EnvConfig("pypy3") 152 | python = plugin.tox_get_python_executable(envconfig) 153 | assert python == asdf.python_bin("pypy3.8-7.0.0") 154 | -------------------------------------------------------------------------------- /tox_asdf/plugin.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import subprocess 4 | 5 | import tox 6 | from packaging.version import Version 7 | 8 | 9 | class AsdfError(Exception): 10 | """Base ASDF error.""" 11 | 12 | def __init__(self, msg, *args, **kwargs): 13 | self.msg = msg 14 | self.args = args 15 | self.kwargs = kwargs 16 | 17 | def __str__(self): 18 | return self.msg.format(*self.args, **self.kwargs) 19 | 20 | 21 | class AsdfMissing(AsdfError, RuntimeError): 22 | """The asdf program is not installed.""" 23 | 24 | 25 | class AsdfPluginMissing(AsdfError, RuntimeError): 26 | """The asdf python plugin is not installed.""" 27 | 28 | 29 | class Config(object): 30 | def __init__(self): 31 | self.verbose = False 32 | self.debug = False 33 | self.no_fallback = False 34 | self.install = False 35 | self.pypy2_version = "pypy2.7" 36 | self.pypy3_version = "pypy3.8" 37 | 38 | 39 | KNOWN_FLAVOURS = ( 40 | "activepython", 41 | "anaconda", 42 | "anaconda2", 43 | "anaconda3", 44 | "graalpython", 45 | "ironpython", 46 | "jython", 47 | "mambaforge", 48 | "micropython", 49 | "miniconda", 50 | "miniconda2", 51 | "miniconda3", 52 | "miniforge3", 53 | "pypy", 54 | "pypy2", 55 | "pypy2.7", 56 | "pypy3", 57 | "pypy3.3", 58 | "pypy3.5", 59 | "pypy3.6", 60 | "pypy3.7", 61 | "pypy3.8", 62 | "pypy3.9", 63 | "pyston", 64 | "stackless", 65 | ) 66 | 67 | CFG = Config() 68 | 69 | 70 | class ToxLogger: 71 | def __init__(self, logger): 72 | self.logger = logger 73 | 74 | def format(self, msg, args, kwargs): 75 | return "ASDF: {}".format(str(msg).format(*args, **kwargs)) 76 | 77 | def debug(self, msg, *args, **kwargs): 78 | if CFG.debug: 79 | print(self.format(msg, args, kwargs)) 80 | 81 | def info(self, msg, *args, **kwargs): 82 | if CFG.verbose: 83 | print(self.format(msg, args, kwargs)) 84 | 85 | def warning(self, msg, *args, **kwargs): 86 | self.logger.warning(self.format(msg, args, kwargs)) 87 | 88 | def error(self, msg, *args, **kwargs): 89 | self.logger.error(self.format(msg, args, kwargs)) 90 | 91 | 92 | LOG = ToxLogger(logging.getLogger(__name__)) 93 | 94 | 95 | @tox.hookimpl 96 | def tox_addoption(parser): 97 | group = parser.argparser.add_argument_group(title="tox-asdf plugin options") 98 | group.add_argument( 99 | "--asdf-no-fallback", 100 | dest="asdf_no_fallback", 101 | default=False, 102 | action="store_true", 103 | help=( 104 | "If `asdf where {basepython}` exits non-zero when looking " 105 | "up the python executable, do not allow fallback to tox's " 106 | "built-in default logic." 107 | ), 108 | ) 109 | group.add_argument( 110 | "--asdf-install", 111 | dest="asdf_install", 112 | default=False, 113 | action="store_true", 114 | help=( 115 | "If `asdf where {basepython}` exits non-zero when looking " 116 | "up the python executable, do not allow fallback to tox's " 117 | "built-in default logic." 118 | ), 119 | ) 120 | 121 | 122 | @tox.hookimpl 123 | def tox_configure(config): 124 | CFG.verbose = config.option.verbose_level > 0 125 | CFG.debug = config.option.verbose_level > 1 126 | CFG.no_fallback = config.option.asdf_no_fallback 127 | CFG.install = config.option.asdf_install 128 | parse_config_versions(config._cfg.sections, CFG) 129 | 130 | 131 | def parse_config_versions(tox_config, plugin_config): 132 | """Parse the [asdf] plugin section in tox.ini""" 133 | config_asdf = tox_config.get("asdf", {}) 134 | pypy2 = config_asdf.get("pypy2", "pypy2.7") 135 | pypy3 = config_asdf.get("pypy3", "pypy3.8") 136 | plugin_config.pypy2_version = pypy2 137 | plugin_config.pypy3_version = pypy3 138 | 139 | 140 | def _version_key(version: str) -> Version: 141 | if version.startswith(KNOWN_FLAVOURS): 142 | return Version(version.split("-", 1)[-1]) 143 | return Version(version) 144 | 145 | 146 | def best_version(version, versions): 147 | """Find the best (latest stable) release matching version""" 148 | compatibles = (v for v in versions if v.startswith(version)) 149 | sorted_compatibles = sorted(compatibles, reverse=True, key=_version_key) 150 | return next(iter(sorted_compatibles), None) 151 | 152 | 153 | def handle_asdf_error(error): 154 | if error.returncode == 127: 155 | raise AsdfMissing("asdf is not installed") 156 | elif error.returncode == 1 and error.output.startswith("No such plugin:"): 157 | msg = "python plugin is missing. Install it with `asdf plugin-add python`" 158 | raise AsdfPluginMissing(msg) 159 | if error.output: 160 | msg = "`{}` failed with code {} and output: \n{}" 161 | else: 162 | msg = "`{}` failed with code {}" 163 | raise AsdfError(msg, error.cmd, error.returncode, (error.output or "").strip()) 164 | 165 | 166 | def asdf_get_installed(version): 167 | """Get the best matching installed version""" 168 | try: 169 | output = subprocess.check_output( 170 | "asdf list python", shell=True, stderr=subprocess.STDOUT, universal_newlines=True 171 | ) 172 | except subprocess.CalledProcessError as e: 173 | handle_asdf_error(e) 174 | 175 | versions = (s.strip() for s in output.splitlines()) 176 | return best_version(version, versions) 177 | 178 | 179 | def asdf_install(version): 180 | """Install the best matching version""" 181 | try: 182 | output = subprocess.check_output( 183 | "asdf list-all python", shell=True, universal_newlines=True 184 | ) 185 | except subprocess.CalledProcessError as e: 186 | handle_asdf_error(e) 187 | 188 | versions = (s.strip() for s in output.splitlines()) 189 | version = best_version(version, versions) 190 | 191 | try: 192 | subprocess.check_call("asdf install python {}".format(version), shell=True) 193 | except subprocess.CalledProcessError as e: 194 | handle_asdf_error(e) 195 | return version 196 | 197 | 198 | def asdf_which(version): 199 | """Get the python binary path for a given installed version""" 200 | try: 201 | cmd = "asdf where python {}".format(version) 202 | python_home = subprocess.check_output( 203 | cmd, shell=True, stderr=subprocess.STDOUT, universal_newlines=True 204 | ).strip() 205 | except subprocess.CalledProcessError as e: 206 | handle_asdf_error(e) 207 | return os.path.join(python_home, "bin", "python") 208 | 209 | 210 | @tox.hookimpl 211 | def tox_get_python_executable(envconfig): 212 | """ 213 | Return a python executable for the given python base name. 214 | 215 | The first plugin/hook which returns an executable path will determine it. 216 | 217 | ``envconfig`` is the testenv configuration which contains 218 | per-testenv configuration, notably the ``.envname`` and ``.basepython`` 219 | setting. 220 | """ 221 | if envconfig.basepython.startswith("python"): 222 | expected = envconfig.basepython.replace("python", "", 1) 223 | elif envconfig.basepython == "pypy": 224 | expected = CFG.pypy2_version 225 | elif envconfig.basepython == "pypy3": 226 | expected = CFG.pypy3_version 227 | else: 228 | return 229 | 230 | try: 231 | version = asdf_get_installed(expected) 232 | except AsdfError as e: 233 | LOG.error(e) 234 | if CFG.no_fallback: 235 | raise 236 | return 237 | 238 | if version is None: 239 | if not CFG.install: 240 | if CFG.no_fallback: 241 | raise AsdfError("No candidate version found") 242 | return 243 | version = asdf_install(expected) 244 | 245 | if version is None: 246 | if CFG.no_fallback: 247 | raise AsdfError("No candidate version to install found") 248 | return 249 | 250 | try: 251 | python = asdf_which(version) 252 | except AsdfError as e: 253 | LOG.error(e) 254 | if CFG.no_fallback: 255 | raise 256 | return 257 | else: 258 | LOG.info("Using {}", python) 259 | return python 260 | -------------------------------------------------------------------------------- /pdm.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "atomicwrites" 3 | version = "1.4.1" 4 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 5 | summary = "Atomic file writes." 6 | 7 | [[package]] 8 | name = "attrs" 9 | version = "21.4.0" 10 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 11 | summary = "Classes Without Boilerplate" 12 | 13 | [[package]] 14 | name = "black" 15 | version = "22.6.0" 16 | requires_python = ">=3.6.2" 17 | summary = "The uncompromising code formatter." 18 | dependencies = [ 19 | "click>=8.0.0", 20 | "mypy-extensions>=0.4.3", 21 | "pathspec>=0.9.0", 22 | "platformdirs>=2", 23 | "tomli>=1.1.0; python_full_version < \"3.11.0a7\"", 24 | "typed-ast>=1.4.2; python_version < \"3.8\" and implementation_name == \"cpython\"", 25 | "typing-extensions>=3.10.0.0; python_version < \"3.10\"", 26 | ] 27 | 28 | [[package]] 29 | name = "click" 30 | version = "8.1.3" 31 | requires_python = ">=3.7" 32 | summary = "Composable command line interface toolkit" 33 | dependencies = [ 34 | "colorama; platform_system == \"Windows\"", 35 | "importlib-metadata; python_version < \"3.8\"", 36 | ] 37 | 38 | [[package]] 39 | name = "colorama" 40 | version = "0.4.5" 41 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 42 | summary = "Cross-platform colored terminal text." 43 | 44 | [[package]] 45 | name = "coverage" 46 | version = "6.4.2" 47 | requires_python = ">=3.7" 48 | summary = "Code coverage measurement for Python" 49 | 50 | [[package]] 51 | name = "coverage" 52 | version = "6.4.2" 53 | extras = ["toml"] 54 | requires_python = ">=3.7" 55 | summary = "Code coverage measurement for Python" 56 | dependencies = [ 57 | "coverage==6.4.2", 58 | "tomli; python_full_version <= \"3.11.0a6\"", 59 | ] 60 | 61 | [[package]] 62 | name = "distlib" 63 | version = "0.3.4" 64 | summary = "Distribution utilities" 65 | 66 | [[package]] 67 | name = "filelock" 68 | version = "3.7.1" 69 | requires_python = ">=3.7" 70 | summary = "A platform independent file lock." 71 | 72 | [[package]] 73 | name = "flake8" 74 | version = "4.0.1" 75 | requires_python = ">=3.6" 76 | summary = "the modular source code checker: pep8 pyflakes and co" 77 | dependencies = [ 78 | "importlib-metadata<4.3; python_version < \"3.8\"", 79 | "mccabe<0.7.0,>=0.6.0", 80 | "pycodestyle<2.9.0,>=2.8.0", 81 | "pyflakes<2.5.0,>=2.4.0", 82 | ] 83 | 84 | [[package]] 85 | name = "importlib-metadata" 86 | version = "4.2.0" 87 | requires_python = ">=3.6" 88 | summary = "Read metadata from Python packages" 89 | dependencies = [ 90 | "typing-extensions>=3.6.4; python_version < \"3.8\"", 91 | "zipp>=0.5", 92 | ] 93 | 94 | [[package]] 95 | name = "iniconfig" 96 | version = "1.1.1" 97 | summary = "iniconfig: brain-dead simple config-ini parsing" 98 | 99 | [[package]] 100 | name = "isort" 101 | version = "5.10.1" 102 | requires_python = ">=3.6.1,<4.0" 103 | summary = "A Python utility / library to sort Python imports." 104 | 105 | [[package]] 106 | name = "mccabe" 107 | version = "0.6.1" 108 | summary = "McCabe checker, plugin for flake8" 109 | 110 | [[package]] 111 | name = "mypy" 112 | version = "0.961" 113 | requires_python = ">=3.6" 114 | summary = "Optional static typing for Python" 115 | dependencies = [ 116 | "mypy-extensions>=0.4.3", 117 | "tomli>=1.1.0; python_version < \"3.11\"", 118 | "typed-ast<2,>=1.4.0; python_version < \"3.8\"", 119 | "typing-extensions>=3.10", 120 | ] 121 | 122 | [[package]] 123 | name = "mypy-extensions" 124 | version = "0.4.3" 125 | summary = "Experimental type system extensions for programs checked with the mypy typechecker." 126 | 127 | [[package]] 128 | name = "packaging" 129 | version = "21.3" 130 | requires_python = ">=3.6" 131 | summary = "Core utilities for Python packages" 132 | dependencies = [ 133 | "pyparsing!=3.0.5,>=2.0.2", 134 | ] 135 | 136 | [[package]] 137 | name = "pathspec" 138 | version = "0.9.0" 139 | requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 140 | summary = "Utility library for gitignore style pattern matching of file paths." 141 | 142 | [[package]] 143 | name = "platformdirs" 144 | version = "2.5.2" 145 | requires_python = ">=3.7" 146 | summary = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 147 | 148 | [[package]] 149 | name = "pluggy" 150 | version = "1.0.0" 151 | requires_python = ">=3.6" 152 | summary = "plugin and hook calling mechanisms for python" 153 | dependencies = [ 154 | "importlib-metadata>=0.12; python_version < \"3.8\"", 155 | ] 156 | 157 | [[package]] 158 | name = "py" 159 | version = "1.11.0" 160 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 161 | summary = "library with cross-python path, ini-parsing, io, code, log facilities" 162 | 163 | [[package]] 164 | name = "pycodestyle" 165 | version = "2.8.0" 166 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 167 | summary = "Python style guide checker" 168 | 169 | [[package]] 170 | name = "pyflakes" 171 | version = "2.4.0" 172 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 173 | summary = "passive checker of Python programs" 174 | 175 | [[package]] 176 | name = "pyparsing" 177 | version = "3.0.9" 178 | requires_python = ">=3.6.8" 179 | summary = "pyparsing module - Classes and methods to define and execute parsing grammars" 180 | 181 | [[package]] 182 | name = "pytest" 183 | version = "7.1.2" 184 | requires_python = ">=3.7" 185 | summary = "pytest: simple powerful testing with Python" 186 | dependencies = [ 187 | "atomicwrites>=1.0; sys_platform == \"win32\"", 188 | "attrs>=19.2.0", 189 | "colorama; sys_platform == \"win32\"", 190 | "importlib-metadata>=0.12; python_version < \"3.8\"", 191 | "iniconfig", 192 | "packaging", 193 | "pluggy<2.0,>=0.12", 194 | "py>=1.8.2", 195 | "tomli>=1.0.0", 196 | ] 197 | 198 | [[package]] 199 | name = "pytest-cov" 200 | version = "3.0.0" 201 | requires_python = ">=3.6" 202 | summary = "Pytest plugin for measuring coverage." 203 | dependencies = [ 204 | "coverage[toml]>=5.2.1", 205 | "pytest>=4.6", 206 | ] 207 | 208 | [[package]] 209 | name = "pytest-mock" 210 | version = "3.8.2" 211 | requires_python = ">=3.7" 212 | summary = "Thin-wrapper around the mock package for easier use with pytest" 213 | dependencies = [ 214 | "pytest>=5.0", 215 | ] 216 | 217 | [[package]] 218 | name = "pytest-sugar" 219 | version = "0.9.5" 220 | summary = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." 221 | dependencies = [ 222 | "packaging>=14.1", 223 | "pytest>=2.9", 224 | "termcolor>=1.1.0", 225 | ] 226 | 227 | [[package]] 228 | name = "six" 229 | version = "1.16.0" 230 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 231 | summary = "Python 2 and 3 compatibility utilities" 232 | 233 | [[package]] 234 | name = "termcolor" 235 | version = "1.1.0" 236 | summary = "ANSII Color formatting for output in terminal." 237 | 238 | [[package]] 239 | name = "toml" 240 | version = "0.10.2" 241 | requires_python = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 242 | summary = "Python Library for Tom's Obvious, Minimal Language" 243 | 244 | [[package]] 245 | name = "tomli" 246 | version = "2.0.1" 247 | requires_python = ">=3.7" 248 | summary = "A lil' TOML parser" 249 | 250 | [[package]] 251 | name = "tox" 252 | version = "3.25.1" 253 | requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 254 | summary = "tox is a generic virtualenv management and test command line tool" 255 | dependencies = [ 256 | "colorama>=0.4.1; platform_system == \"Windows\"", 257 | "filelock>=3.0.0", 258 | "importlib-metadata>=0.12; python_version < \"3.8\"", 259 | "packaging>=14", 260 | "pluggy>=0.12.0", 261 | "py>=1.4.17", 262 | "six>=1.14.0", 263 | "toml>=0.9.4", 264 | "virtualenv!=20.0.0,!=20.0.1,!=20.0.2,!=20.0.3,!=20.0.4,!=20.0.5,!=20.0.6,!=20.0.7,>=16.0.0", 265 | ] 266 | 267 | [[package]] 268 | name = "tox-pdm" 269 | version = "0.5.0" 270 | requires_python = ">=3.7" 271 | summary = "A plugin for tox that utilizes PDM as the package manager and installer" 272 | dependencies = [ 273 | "toml>=0.10", 274 | "tox>=3.18.0", 275 | ] 276 | 277 | [[package]] 278 | name = "typed-ast" 279 | version = "1.5.4" 280 | requires_python = ">=3.6" 281 | summary = "a fork of Python 2 and 3 ast modules with type comment support" 282 | 283 | [[package]] 284 | name = "typing-extensions" 285 | version = "4.3.0" 286 | requires_python = ">=3.7" 287 | summary = "Backported and Experimental Type Hints for Python 3.7+" 288 | 289 | [[package]] 290 | name = "virtualenv" 291 | version = "20.15.1" 292 | requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 293 | summary = "Virtual Python Environment builder" 294 | dependencies = [ 295 | "distlib<1,>=0.3.1", 296 | "filelock<4,>=3.2", 297 | "importlib-metadata>=0.12; python_version < \"3.8\"", 298 | "platformdirs<3,>=2", 299 | "six<2,>=1.9.0", 300 | ] 301 | 302 | [[package]] 303 | name = "zipp" 304 | version = "3.8.1" 305 | requires_python = ">=3.7" 306 | summary = "Backport of pathlib-compatible object wrapper for zip files" 307 | 308 | [metadata] 309 | lock_version = "4.0" 310 | content_hash = "sha256:a7347e61a353643014034c9e67b551a0b566d0ebcd4b36a11d54e621504c5050" 311 | 312 | [metadata.files] 313 | "atomicwrites 1.4.1" = [ 314 | {url = "https://files.pythonhosted.org/packages/87/c6/53da25344e3e3a9c01095a89f16dbcda021c609ddb42dd6d7c0528236fb2/atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, 315 | ] 316 | "attrs 21.4.0" = [ 317 | {url = "https://files.pythonhosted.org/packages/be/be/7abce643bfdf8ca01c48afa2ddf8308c2308b0c3b239a44e57d020afa0ef/attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, 318 | {url = "https://files.pythonhosted.org/packages/d7/77/ebb15fc26d0f815839ecd897b919ed6d85c050feeb83e100e020df9153d2/attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, 319 | ] 320 | "black 22.6.0" = [ 321 | {url = "https://files.pythonhosted.org/packages/80/ff/cfcfa4cdb42d8fff75b6b4dc355a1186a95de4714df8cc2a60f69f7b17f8/black-22.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f586c26118bc6e714ec58c09df0157fe2d9ee195c764f630eb0d8e7ccce72e69"}, 322 | {url = "https://files.pythonhosted.org/packages/63/96/814e02033701f51701444d5505b5e2594453b1f7e913764a097b1f701633/black-22.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807"}, 323 | {url = "https://files.pythonhosted.org/packages/1a/84/203163902ee26bcf1beaef582ee0c8df3f325da3e961b68d2ece959e19d3/black-22.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6797f58943fceb1c461fb572edbe828d811e719c24e03375fd25170ada53825e"}, 324 | {url = "https://files.pythonhosted.org/packages/a7/51/d0acd9f74a946a825a148dcc392433c2332ae405967d76292b9e64712dc8/black-22.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c85928b9d5f83b23cee7d0efcb310172412fbf7cb9d9ce963bd67fd141781def"}, 325 | {url = "https://files.pythonhosted.org/packages/86/9c/2a8a13993bc63a50bda7436ecba902231fd9a88dd1cd233e6e3f534e071c/black-22.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6fe02afde060bbeef044af7996f335fbe90b039ccf3f5eb8f16df8b20f77666"}, 326 | {url = "https://files.pythonhosted.org/packages/8a/90/69274ed80397ada663ce3c4cc0c70b7fb20b529f9baf4bf9ddf4edc14ccd/black-22.6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cfaf3895a9634e882bf9d2363fed5af8888802d670f58b279b0bece00e9a872d"}, 327 | {url = "https://files.pythonhosted.org/packages/d6/45/985c13ac6b2f67504cda61fc1d95365eb6446a4c6988ffe0f0f311f7a617/black-22.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94783f636bca89f11eb5d50437e8e17fbc6a929a628d82304c80fa9cd945f256"}, 328 | {url = "https://files.pythonhosted.org/packages/57/62/2961a0a57bdf768ccb5aea16327400be6e6bde4fb47ac05af7e9535c5289/black-22.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2ea29072e954a4d55a2ff58971b83365eba5d3d357352a07a7a4df0d95f51c78"}, 329 | {url = "https://files.pythonhosted.org/packages/ac/9d/b06f45e8dff2b10bf4644ba7a74490538c0272ae48308e04c6f551671b89/black-22.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e439798f819d49ba1c0bd9664427a05aab79bfba777a6db94fd4e56fae0cb849"}, 330 | {url = "https://files.pythonhosted.org/packages/40/d1/3f366d7887d1fb6e3e487a6c975a9e9e13618757ed0d5427197fa9e28290/black-22.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:187d96c5e713f441a5829e77120c269b6514418f4513a390b0499b0987f2ff1c"}, 331 | {url = "https://files.pythonhosted.org/packages/46/eb/f489451de8b3e91bd82ee722b9a8493b94f8719ea6649e5b8bba2376056d/black-22.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:074458dc2f6e0d3dab7928d4417bb6957bb834434516f21514138437accdbe90"}, 332 | {url = "https://files.pythonhosted.org/packages/07/eb/a757135497a3be31ab8c00ef239070c7277ad11b618104950a756bcab3c1/black-22.6.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a218d7e5856f91d20f04e931b6f16d15356db1c846ee55f01bac297a705ca24f"}, 333 | {url = "https://files.pythonhosted.org/packages/3e/fd/5e47b4d77549909e484de906a69fccc3fcfb782131d8b449073ad8b3ed3e/black-22.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:568ac3c465b1c8b34b61cd7a4e349e93f91abf0f9371eda1cf87194663ab684e"}, 334 | {url = "https://files.pythonhosted.org/packages/e7/fe/4533d110ddced851a359cbbb162685814719690ee01939a34be023410854/black-22.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6c1734ab264b8f7929cef8ae5f900b85d579e6cbfde09d7387da8f04771b51c6"}, 335 | {url = "https://files.pythonhosted.org/packages/fc/37/032c45b55f901ee3fe780fbc17fe2dc262c809d94de1288201350d8d680b/black-22.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9a3ac16efe9ec7d7381ddebcc022119794872abce99475345c5a61aa18c45ad"}, 336 | {url = "https://files.pythonhosted.org/packages/c4/67/a4e9125bf1a4eb5a2624b3b979af2dc6ee8d3c4ee0b3d2867173db4916fa/black-22.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b9fd45787ba8aa3f5e0a0a98920c1012c884622c6c920dbe98dbd05bc7c70fbf"}, 337 | {url = "https://files.pythonhosted.org/packages/1d/d2/bc58bae8ec35f5a3c796d71d5bda113060678483e623a019fb889edd8d97/black-22.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ba9be198ecca5031cd78745780d65a3f75a34b2ff9be5837045dce55db83d1c"}, 338 | {url = "https://files.pythonhosted.org/packages/9b/22/ff6d904dcb6f92bd7c20b178ed0aa9e6814ae6452df6c573806dbc465b85/black-22.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3db5b6409b96d9bd543323b23ef32a1a2b06416d525d27e0f67e74f1446c8f2"}, 339 | {url = "https://files.pythonhosted.org/packages/19/b0/13864fd5f3090ca5379f3dcf6034f1e4f02b59620e7b8b5c6f0c85622c0b/black-22.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:560558527e52ce8afba936fcce93a7411ab40c7d5fe8c2463e279e843c0328ee"}, 340 | {url = "https://files.pythonhosted.org/packages/2b/d9/7331e50dad8d5149a9e2285766960ac6b732ae9b3b9796e10916ad88ff61/black-22.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b154e6bbde1e79ea3260c4b40c0b7b3109ffcdf7bc4ebf8859169a6af72cd70b"}, 341 | {url = "https://files.pythonhosted.org/packages/55/33/752544332a2d3be0f6d54ef808075681b68ddc15cfcb90ff128f2d30c85c/black-22.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:4af5bc0e1f96be5ae9bd7aaec219c901a94d6caa2484c21983d043371c733fc4"}, 342 | {url = "https://files.pythonhosted.org/packages/2b/70/1d0e33a4df4ed73e9f02f698a29b5d94ff58e39f029c939ecf96a10fb1f3/black-22.6.0-py3-none-any.whl", hash = "sha256:ac609cf8ef5e7115ddd07d85d988d074ed00e10fbc3445aee393e70164a2219c"}, 343 | {url = "https://files.pythonhosted.org/packages/61/11/551b0d067a7e6836fc0997ab36ee46ec65259fea8f30104f4870092f3301/black-22.6.0.tar.gz", hash = "sha256:6c6d39e28aed379aec40da1c65434c77d75e65bb59a1e1c283de545fb4e7c6c9"}, 344 | ] 345 | "click 8.1.3" = [ 346 | {url = "https://files.pythonhosted.org/packages/c2/f1/df59e28c642d583f7dacffb1e0965d0e00b218e0186d7858ac5233dce840/click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 347 | {url = "https://files.pythonhosted.org/packages/59/87/84326af34517fca8c58418d148f2403df25303e02736832403587318e9e8/click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 348 | ] 349 | "colorama 0.4.5" = [ 350 | {url = "https://files.pythonhosted.org/packages/77/8b/7550e87b2d308a1b711725dfaddc19c695f8c5fa413c640b2be01662f4e6/colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, 351 | {url = "https://files.pythonhosted.org/packages/2b/65/24d033a9325ce42ccbfa3ca2d0866c7e89cc68e5b9d92ecaba9feef631df/colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, 352 | ] 353 | "coverage 6.4.2" = [ 354 | {url = "https://files.pythonhosted.org/packages/68/8d/8218b3604ca937f2d1a4b05033de4c5dc92adfc0262e54636ad21c67a132/coverage-6.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a9032f9b7d38bdf882ac9f66ebde3afb8145f0d4c24b2e600bc4c6304aafb87e"}, 355 | {url = "https://files.pythonhosted.org/packages/97/16/d27ebd964fa8099ece60a66bd9766c906a3c017462060799ede33905900a/coverage-6.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e0524adb49c716ca763dbc1d27bedce36b14f33e6b8af6dba56886476b42957c"}, 356 | {url = "https://files.pythonhosted.org/packages/11/89/8d8ab7adfef71d9c7da1672328d34ec6c733bf12eeddd6aab880596b50eb/coverage-6.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4548be38a1c810d79e097a38107b6bf2ff42151900e47d49635be69943763d8"}, 357 | {url = "https://files.pythonhosted.org/packages/aa/21/01d0421d493eddfc5bfd4cb25902c5c685f2355474da98a9232971a2e7f5/coverage-6.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f23876b018dfa5d3e98e96f5644b109090f16a4acb22064e0f06933663005d39"}, 358 | {url = "https://files.pythonhosted.org/packages/96/1d/0b615e00ab0f78474112b9ef63605d7b0053900746a5c2592f011e850b93/coverage-6.4.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fe75dcfcb889b6800f072f2af5a331342d63d0c1b3d2bf0f7b4f6c353e8c9c0"}, 359 | {url = "https://files.pythonhosted.org/packages/a8/b6/3a235f3c2a186039d5d1ea30e538b9a759e43fad9221c26b79c6f06c6bf1/coverage-6.4.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2f8553878a24b00d5ab04b7a92a2af50409247ca5c4b7a2bf4eabe94ed20d3ee"}, 360 | {url = "https://files.pythonhosted.org/packages/35/1d/9b01738822e5f472ded472904b3feed4eb7354f724ae5d48ca10608d30ff/coverage-6.4.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d774d9e97007b018a651eadc1b3970ed20237395527e22cbeb743d8e73e0563d"}, 361 | {url = "https://files.pythonhosted.org/packages/ec/0b/7eff35443ce30d957e582ea7d4040d1d107e5e392ad68e4ce2a01d20525e/coverage-6.4.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d56f105592188ce7a797b2bd94b4a8cb2e36d5d9b0d8a1d2060ff2a71e6b9bbc"}, 362 | {url = "https://files.pythonhosted.org/packages/05/48/d5f97f5cef736aedefcca7534f600ca8434224018fb33009d333d008e6f5/coverage-6.4.2-cp310-cp310-win32.whl", hash = "sha256:d230d333b0be8042ac34808ad722eabba30036232e7a6fb3e317c49f61c93386"}, 363 | {url = "https://files.pythonhosted.org/packages/58/7a/1c2eb46936a3a6f5be715d6b40775f675ef424137010fb58634eeba08aab/coverage-6.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:5ef42e1db047ca42827a85e34abe973971c635f83aed49611b7f3ab49d0130f0"}, 364 | {url = "https://files.pythonhosted.org/packages/81/d7/556e38d4eea9414e47fa7d16889ed19d77ecf812afbb76caf8a5cbc5d47a/coverage-6.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:25b7ec944f114f70803d6529394b64f8749e93cbfac0fe6c5ea1b7e6c14e8a46"}, 365 | {url = "https://files.pythonhosted.org/packages/6b/d0/32ed1a6542c21af97d249ae795d1e8249e8bb8460018231df558bd1001e7/coverage-6.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bb00521ab4f99fdce2d5c05a91bddc0280f0afaee0e0a00425e28e209d4af07"}, 366 | {url = "https://files.pythonhosted.org/packages/cc/da/c62039af21a3f04745c9a8438f3c0870ea957f32da19a89225f291c2b393/coverage-6.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dff52b3e7f76ada36f82124703f4953186d9029d00d6287f17c68a75e2e6039"}, 367 | {url = "https://files.pythonhosted.org/packages/6a/ab/5d4bb7b9e741728f08688eb82470c36c2e0d1e7d67d0b840ece6c013bb69/coverage-6.4.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147605e1702d996279bb3cc3b164f408698850011210d133a2cb96a73a2f7996"}, 368 | {url = "https://files.pythonhosted.org/packages/88/6b/b457d4d5650d83358a0d743a8ee7a1c5a4906107e62d8e0e1e70f216501f/coverage-6.4.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:422fa44070b42fef9fb8dabd5af03861708cdd6deb69463adc2130b7bf81332f"}, 369 | {url = "https://files.pythonhosted.org/packages/91/f3/c28dd8e6578d02100f1816aa2b6b33306d8f07a315a742df4d77f4c22f41/coverage-6.4.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8af6c26ba8df6338e57bedbf916d76bdae6308e57fc8f14397f03b5da8622b4e"}, 370 | {url = "https://files.pythonhosted.org/packages/a2/5f/b0af66e78459c8dc6a46f402db90c8b3e6235fcfebc3c7449a7a6651c0c4/coverage-6.4.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5336e0352c0b12c7e72727d50ff02557005f79a0b8dcad9219c7c4940a930083"}, 371 | {url = "https://files.pythonhosted.org/packages/c3/9e/00bd0bb6ef17db30a71d64192e087b0c540fcf56025fc5af80cf051d3109/coverage-6.4.2-cp37-cp37m-win32.whl", hash = "sha256:0f211df2cba951ffcae210ee00e54921ab42e2b64e0bf2c0befc977377fb09b7"}, 372 | {url = "https://files.pythonhosted.org/packages/d4/16/7732d4fceffc9d2aff7aaded3820cc1ed0bf83d534e19e69d21474ba9624/coverage-6.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a13772c19619118903d65a91f1d5fea84be494d12fd406d06c849b00d31bf120"}, 373 | {url = "https://files.pythonhosted.org/packages/f6/53/6353cec1305e478f003a52dc4ed42e05232af99faccd4dd300c5e687d5d1/coverage-6.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f7bd0ffbcd03dc39490a1f40b2669cc414fae0c4e16b77bb26806a4d0b7d1452"}, 374 | {url = "https://files.pythonhosted.org/packages/18/72/757fe0070c5da9467835904e410b0e4aae2edf9f9ffd9e20285ff96af6fa/coverage-6.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0895ea6e6f7f9939166cc835df8fa4599e2d9b759b02d1521b574e13b859ac32"}, 375 | {url = "https://files.pythonhosted.org/packages/7d/f4/0047566a8699dbe1f0b96e478f05f852857dc904e887bbd7329f60925259/coverage-6.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e7ced84a11c10160c0697a6cc0b214a5d7ab21dfec1cd46e89fbf77cc66fae"}, 376 | {url = "https://files.pythonhosted.org/packages/8c/a1/9eb00bf2c58bb2de77d66da26fad697e76498039d6bb515cef371a5d58ba/coverage-6.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80db4a47a199c4563d4a25919ff29c97c87569130375beca3483b41ad5f698e8"}, 377 | {url = "https://files.pythonhosted.org/packages/84/90/97d0d88ffcbb9019cd6e8628b07d761522f04b5ea79b8ee14d18b7eeee06/coverage-6.4.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3def6791adf580d66f025223078dc84c64696a26f174131059ce8e91452584e1"}, 378 | {url = "https://files.pythonhosted.org/packages/55/58/6d11b1933a0fe5cae0ecfa21d1570dbd3290ed874512e20117858084533b/coverage-6.4.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4f89d8e03c8a3757aae65570d14033e8edf192ee9298303db15955cadcff0c63"}, 379 | {url = "https://files.pythonhosted.org/packages/c2/ba/b1a3e8f810948cc33c178081bd386479ea3cc5ad7bc7ca646911080a98fe/coverage-6.4.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6d0b48aff8e9720bdec315d67723f0babd936a7211dc5df453ddf76f89c59933"}, 380 | {url = "https://files.pythonhosted.org/packages/e9/e8/f1e0df418a2797ba551e9820e788e5b6db714b9758c473f5a729ba927897/coverage-6.4.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2b20286c2b726f94e766e86a3fddb7b7e37af5d0c635bdfa7e4399bc523563de"}, 381 | {url = "https://files.pythonhosted.org/packages/c2/46/7293b2f5d7bbb44e9333bdb84d3dd635df702b21f7fecaa0d4b94b700373/coverage-6.4.2-cp38-cp38-win32.whl", hash = "sha256:d714af0bdba67739598849c9f18efdcc5a0412f4993914a0ec5ce0f1e864d783"}, 382 | {url = "https://files.pythonhosted.org/packages/d7/51/ab0bb6bef9601d308c75bf6221a5806ad81635dde7f8edbdc750fcccd6f2/coverage-6.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:5f65e5d3ff2d895dab76b1faca4586b970a99b5d4b24e9aafffc0ce94a6022d6"}, 383 | {url = "https://files.pythonhosted.org/packages/44/b7/1b43ea58557b32c0364676ef0f18f347a9e870d4ef196188c67613c75758/coverage-6.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a697977157adc052284a7160569b36a8bbec09db3c3220642e6323b47cec090f"}, 384 | {url = "https://files.pythonhosted.org/packages/88/6f/b1b2ef142c9f5fd46795b54384478a5ff8e3a25f2ff6d333fa8b3c579d3a/coverage-6.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c77943ef768276b61c96a3eb854eba55633c7a3fddf0a79f82805f232326d33f"}, 385 | {url = "https://files.pythonhosted.org/packages/41/1f/6aff3dde884bf8142ee510b3c8593b2f45b2b1bab840d123b37be98d3d2d/coverage-6.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54d8d0e073a7f238f0666d3c7c0d37469b2aa43311e4024c925ee14f5d5a1cbe"}, 386 | {url = "https://files.pythonhosted.org/packages/8b/ef/88f6068a4533ddb46f0136c3265939bd1369bb1fd491fbac41d5e40d08df/coverage-6.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22325010d8824594820d6ce84fa830838f581a7fd86a9235f0d2ed6deb61e29"}, 387 | {url = "https://files.pythonhosted.org/packages/f6/73/cb9a3c2d8de315bb9f5fbbcaecd1cea2cacaf530885159159ec2d9c7757e/coverage-6.4.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24b04d305ea172ccb21bee5bacd559383cba2c6fcdef85b7701cf2de4188aa55"}, 388 | {url = "https://files.pythonhosted.org/packages/58/6c/ac61774179efe5049affbf9933ee095489965de9c264410a4a5de5da0257/coverage-6.4.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:866ebf42b4c5dbafd64455b0a1cd5aa7b4837a894809413b930026c91e18090b"}, 389 | {url = "https://files.pythonhosted.org/packages/70/e7/756d9dc8c22c79f39b984f8e49bd97fd873ca29e73061db7a8303477701f/coverage-6.4.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e36750fbbc422c1c46c9d13b937ab437138b998fe74a635ec88989afb57a3978"}, 390 | {url = "https://files.pythonhosted.org/packages/b3/23/4088b1c3408ccdcf73a24b874409a78c826898a32f9ab7070504db633165/coverage-6.4.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:79419370d6a637cb18553ecb25228893966bd7935a9120fa454e7076f13b627c"}, 391 | {url = "https://files.pythonhosted.org/packages/62/d2/279009b64d97afec27f38472778c35b9521b98abe47822cdad5712f43a38/coverage-6.4.2-cp39-cp39-win32.whl", hash = "sha256:b5e28db9199dd3833cc8a07fa6cf429a01227b5d429facb56eccd765050c26cd"}, 392 | {url = "https://files.pythonhosted.org/packages/c1/12/f8bb5023fc58fb0cdc3503935942928f92e336432bbb22255af5937e3f31/coverage-6.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:edfdabe7aa4f97ed2b9dd5dde52d2bb29cb466993bb9d612ddd10d0085a683cf"}, 393 | {url = "https://files.pythonhosted.org/packages/0f/29/135eecb31b3ab5984417b8c21a0b5d30554d771ebde93e8009b986286d01/coverage-6.4.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:e2618cb2cf5a7cc8d698306e42ebcacd02fb7ef8cfc18485c59394152c70be97"}, 394 | {url = "https://files.pythonhosted.org/packages/ea/34/5a4f7a48da3be173273cd9b866c998eb59e234da2ee4a30c1068e85c0e99/coverage-6.4.2.tar.gz", hash = "sha256:6c3ccfe89c36f3e5b9837b9ee507472310164f352c9fe332120b764c9d60adbe"}, 395 | ] 396 | "distlib 0.3.4" = [ 397 | {url = "https://files.pythonhosted.org/packages/ac/a3/8ee4f54d5f12e16eeeda6b7df3dfdbda24e6cc572c86ff959a4ce110391b/distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, 398 | {url = "https://files.pythonhosted.org/packages/85/01/88529c93e41607f1a78c1e4b346b24c74ee43d2f41cfe33ecd2e20e0c7e3/distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, 399 | ] 400 | "filelock 3.7.1" = [ 401 | {url = "https://files.pythonhosted.org/packages/a6/d5/17f02b379525d1ff9678bfa58eb9548f561c8826deb0b85797aa0eed582d/filelock-3.7.1-py3-none-any.whl", hash = "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404"}, 402 | {url = "https://files.pythonhosted.org/packages/f3/c7/5c1aef87f1197d2134a096c0264890969213c9cbfb8a4102087e8d758b5c/filelock-3.7.1.tar.gz", hash = "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04"}, 403 | ] 404 | "flake8 4.0.1" = [ 405 | {url = "https://files.pythonhosted.org/packages/34/39/cde2c8a227abb4f9ce62fe55586b920f438f1d2903a1a22514d0b982c333/flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, 406 | {url = "https://files.pythonhosted.org/packages/e6/84/d8db922289195c435779b4ca3a3f583f263f87e67954f7b2e83c8da21f48/flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, 407 | ] 408 | "importlib-metadata 4.2.0" = [ 409 | {url = "https://files.pythonhosted.org/packages/22/51/52442c59db26637681148c21f8984eed58c9db67053a0a4783a047010c98/importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, 410 | {url = "https://files.pythonhosted.org/packages/c7/7c/126a8686399ebe256b5e4343ea80b6f2ee91549969da2eef0bb2891b8d24/importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, 411 | ] 412 | "iniconfig 1.1.1" = [ 413 | {url = "https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 414 | {url = "https://files.pythonhosted.org/packages/23/a2/97899f6bd0e873fed3a7e67ae8d3a08b21799430fb4da15cfedf10d6e2c2/iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 415 | ] 416 | "isort 5.10.1" = [ 417 | {url = "https://files.pythonhosted.org/packages/b8/5b/f18e227df38b94b4ee30d2502fd531bebac23946a2497e5595067a561274/isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, 418 | {url = "https://files.pythonhosted.org/packages/ab/e9/964cb0b2eedd80c92f5172f1f8ae0443781a9d461c1372a3ce5762489593/isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, 419 | ] 420 | "mccabe 0.6.1" = [ 421 | {url = "https://files.pythonhosted.org/packages/87/89/479dc97e18549e21354893e4ee4ef36db1d237534982482c3681ee6e7b57/mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 422 | {url = "https://files.pythonhosted.org/packages/06/18/fa675aa501e11d6d6ca0ae73a101b2f3571a565e0f7d38e062eec18a91ee/mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 423 | ] 424 | "mypy 0.961" = [ 425 | {url = "https://files.pythonhosted.org/packages/86/df/16748348d0119a6f2528f370e20f644a15ffb8a335a79e91023babc34d0b/mypy-0.961-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:697540876638ce349b01b6786bc6094ccdaba88af446a9abb967293ce6eaa2b0"}, 426 | {url = "https://files.pythonhosted.org/packages/46/d7/3cf7605655f78daded7a23e66f632b50a3b3a8b4262739782f558f8949cf/mypy-0.961-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b117650592e1782819829605a193360a08aa99f1fc23d1d71e1a75a142dc7e15"}, 427 | {url = "https://files.pythonhosted.org/packages/ba/85/d59bd3b0c84a59d862d2494a3461710ad4a2a1616312414ecc95b2393310/mypy-0.961-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bdd5ca340beffb8c44cb9dc26697628d1b88c6bddf5c2f6eb308c46f269bb6f3"}, 428 | {url = "https://files.pythonhosted.org/packages/9c/4b/37c32ab752c1a16cd528bfa239553207108ae33c7b617f9cfea3bb6e2f00/mypy-0.961-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3e09f1f983a71d0672bbc97ae33ee3709d10c779beb613febc36805a6e28bb4e"}, 429 | {url = "https://files.pythonhosted.org/packages/02/e0/6c11b3a6828d1bf8edd066d87588f9461bce3154e4dcc542729bbe6cdc3e/mypy-0.961-cp310-cp310-win_amd64.whl", hash = "sha256:e999229b9f3198c0c880d5e269f9f8129c8862451ce53a011326cad38b9ccd24"}, 430 | {url = "https://files.pythonhosted.org/packages/4c/66/d1f7145dd6678fd17256817f942b5faa01c4de1c87d7c45e0d717d0cba43/mypy-0.961-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b24be97351084b11582fef18d79004b3e4db572219deee0212078f7cf6352723"}, 431 | {url = "https://files.pythonhosted.org/packages/b2/6b/af771c4ac0b7f2562e68c2723fda5b59c56aa34fa6dcd4a1f055d52874d1/mypy-0.961-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f4a21d01fc0ba4e31d82f0fff195682e29f9401a8bdb7173891070eb260aeb3b"}, 432 | {url = "https://files.pythonhosted.org/packages/7b/8f/854b81a840ccf6df5235241eeeab9fa85089f817459d25bc377fe5f12520/mypy-0.961-cp36-cp36m-win_amd64.whl", hash = "sha256:439c726a3b3da7ca84a0199a8ab444cd8896d95012c4a6c4a0d808e3147abf5d"}, 433 | {url = "https://files.pythonhosted.org/packages/f6/fa/fc6a127fd06f266f2e615b2ee8cd4ab8a49adecc740965ceec072569427c/mypy-0.961-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a0b53747f713f490affdceef835d8f0cb7285187a6a44c33821b6d1f46ed813"}, 434 | {url = "https://files.pythonhosted.org/packages/a0/dc/8356726d7964f4418a9cc197395b220921c55259ba55d9d20f07c001a8e3/mypy-0.961-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e9f70df36405c25cc530a86eeda1e0867863d9471fe76d1273c783df3d35c2e"}, 435 | {url = "https://files.pythonhosted.org/packages/38/05/b804a13a7042b6eed98aa14f037765d13e251052fe18dd855690cb256965/mypy-0.961-cp37-cp37m-win_amd64.whl", hash = "sha256:b88f784e9e35dcaa075519096dc947a388319cb86811b6af621e3523980f1c8a"}, 436 | {url = "https://files.pythonhosted.org/packages/d3/c1/044316d783ce5ff2df602cc7562c6a6a2c1f8500d1449e1e0d06a36a8ae1/mypy-0.961-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d5aaf1edaa7692490f72bdb9fbd941fbf2e201713523bdb3f4038be0af8846c6"}, 437 | {url = "https://files.pythonhosted.org/packages/83/ac/29e343bc9423c7061d55ba061ad58af2143f26fbf4ab57c9817286ab10ce/mypy-0.961-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9f5f5a74085d9a81a1f9c78081d60a0040c3efb3f28e5c9912b900adf59a16e6"}, 438 | {url = "https://files.pythonhosted.org/packages/99/99/c1d2e6d00b7336b67b033beec51c346017f33c3d5e36e1cf62239310b950/mypy-0.961-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f4b794db44168a4fc886e3450201365c9526a522c46ba089b55e1f11c163750d"}, 439 | {url = "https://files.pythonhosted.org/packages/7e/10/617b3a85123226906353fa8c5d574390ee1899fae2fe3e62109f87f48f4e/mypy-0.961-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:64759a273d590040a592e0f4186539858c948302c653c2eac840c7a3cd29e51b"}, 440 | {url = "https://files.pythonhosted.org/packages/84/1b/69513ca691f4b00a61c3ebfa9978e7a8fa7034833a5328fdb055d53e98cc/mypy-0.961-cp38-cp38-win_amd64.whl", hash = "sha256:63e85a03770ebf403291ec50097954cc5caf2a9205c888ce3a61bd3f82e17569"}, 441 | {url = "https://files.pythonhosted.org/packages/31/73/71ae84ece879f7c6fd5fbbe7c750c4663dcbd53fa5c7fa7023be59af27f5/mypy-0.961-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f1332964963d4832a94bebc10f13d3279be3ce8f6c64da563d6ee6e2eeda932"}, 442 | {url = "https://files.pythonhosted.org/packages/5e/5a/1b46c161aacdff572632695a483e64ea908089b5c1b6bb92c7e59685c889/mypy-0.961-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:006be38474216b833eca29ff6b73e143386f352e10e9c2fbe76aa8549e5554f5"}, 443 | {url = "https://files.pythonhosted.org/packages/e3/66/9942860fc360e529dbd695b1da3dd80336095d0d3c956e416eb656fba7dd/mypy-0.961-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9940e6916ed9371809b35b2154baf1f684acba935cd09928952310fbddaba648"}, 444 | {url = "https://files.pythonhosted.org/packages/b4/5f/6f116003e4ddb472912c13f999127d658e56a210177e23c9d6b1538a5184/mypy-0.961-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a5ea0875a049de1b63b972456542f04643daf320d27dc592d7c3d9cd5d9bf950"}, 445 | {url = "https://files.pythonhosted.org/packages/17/63/6a173d3b80eb6b48e22fb81f2935047657089b7509d70602c6e7dcee3cfe/mypy-0.961-cp39-cp39-win_amd64.whl", hash = "sha256:1ece702f29270ec6af25db8cf6185c04c02311c6bb21a69f423d40e527b75c56"}, 446 | {url = "https://files.pythonhosted.org/packages/cb/30/03a46a37902348b309aa5fe8a92636b9417fdcea77e20dfc1455581a0ae7/mypy-0.961-py3-none-any.whl", hash = "sha256:03c6cc893e7563e7b2949b969e63f02c000b32502a1b4d1314cabe391aa87d66"}, 447 | {url = "https://files.pythonhosted.org/packages/67/48/e73045183ce9824d98365f18255a79d0b01638f40a0a68f898dc8f3cebcc/mypy-0.961.tar.gz", hash = "sha256:f730d56cb924d371c26b8eaddeea3cc07d78ff51c521c6d04899ac6904b75492"}, 448 | ] 449 | "mypy-extensions 0.4.3" = [ 450 | {url = "https://files.pythonhosted.org/packages/5c/eb/975c7c080f3223a5cdaff09612f3a5221e4ba534f7039db34c35d95fa6a5/mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 451 | {url = "https://files.pythonhosted.org/packages/63/60/0582ce2eaced55f65a4406fc97beba256de4b7a95a0034c6576458c6519f/mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 452 | ] 453 | "packaging 21.3" = [ 454 | {url = "https://files.pythonhosted.org/packages/05/8e/8de486cbd03baba4deef4142bd643a3e7bbe954a784dc1bb17142572d127/packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 455 | {url = "https://files.pythonhosted.org/packages/df/9e/d1a7217f69310c1db8fdf8ab396229f55a699ce34a203691794c5d1cad0c/packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 456 | ] 457 | "pathspec 0.9.0" = [ 458 | {url = "https://files.pythonhosted.org/packages/42/ba/a9d64c7bcbc7e3e8e5f93a52721b377e994c22d16196e2b0f1236774353a/pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, 459 | {url = "https://files.pythonhosted.org/packages/f6/33/436c5cb94e9f8902e59d1d544eb298b83c84b9ec37b5b769c5a0ad6edb19/pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, 460 | ] 461 | "platformdirs 2.5.2" = [ 462 | {url = "https://files.pythonhosted.org/packages/ed/22/967181c94c3a4063fe64e15331b4cb366bdd7dfbf46fcb8ad89650026fec/platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, 463 | {url = "https://files.pythonhosted.org/packages/ff/7b/3613df51e6afbf2306fc2465671c03390229b55e3ef3ab9dd3f846a53be6/platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, 464 | ] 465 | "pluggy 1.0.0" = [ 466 | {url = "https://files.pythonhosted.org/packages/9e/01/f38e2ff29715251cf25532b9082a1589ab7e4f571ced434f98d0139336dc/pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 467 | {url = "https://files.pythonhosted.org/packages/a1/16/db2d7de3474b6e37cbb9c008965ee63835bba517e22cdb8c35b5116b5ce1/pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 468 | ] 469 | "py 1.11.0" = [ 470 | {url = "https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 471 | {url = "https://files.pythonhosted.org/packages/98/ff/fec109ceb715d2a6b4c4a85a61af3b40c723a961e8828319fbcb15b868dc/py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 472 | ] 473 | "pycodestyle 2.8.0" = [ 474 | {url = "https://files.pythonhosted.org/packages/15/94/bc43a2efb7b8615e38acde2b6624cae8c9ec86faf718ff5676c5179a7714/pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, 475 | {url = "https://files.pythonhosted.org/packages/08/dc/b29daf0a202b03f57c19e7295b60d1d5e1281c45a6f5f573e41830819918/pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, 476 | ] 477 | "pyflakes 2.4.0" = [ 478 | {url = "https://files.pythonhosted.org/packages/43/fb/38848eb494af7df9aeb2d7673ace8b213313eb7e391691a79dbaeb6a838f/pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, 479 | {url = "https://files.pythonhosted.org/packages/15/60/c577e54518086e98470e9088278247f4af1d39cb43bcbd731e2c307acd6a/pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, 480 | ] 481 | "pyparsing 3.0.9" = [ 482 | {url = "https://files.pythonhosted.org/packages/6c/10/a7d0fa5baea8fe7b50f448ab742f26f52b80bfca85ac2be9d35cdd9a3246/pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, 483 | {url = "https://files.pythonhosted.org/packages/71/22/207523d16464c40a0310d2d4d8926daffa00ac1f5b1576170a32db749636/pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, 484 | ] 485 | "pytest 7.1.2" = [ 486 | {url = "https://files.pythonhosted.org/packages/fb/d0/bae533985f2338c5d02184b4a7083b819f6b3fc101da792e0d96e6e5299d/pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, 487 | {url = "https://files.pythonhosted.org/packages/4e/1f/34657c6ac56f3c58df650ba41f8ffb2620281ead8e11bcdc7db63cf72a78/pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, 488 | ] 489 | "pytest-cov 3.0.0" = [ 490 | {url = "https://files.pythonhosted.org/packages/61/41/e046526849972555928a6d31c2068410e47a31fb5ab0a77f868596811329/pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, 491 | {url = "https://files.pythonhosted.org/packages/20/49/b3e0edec68d81846f519c602ac38af9db86e1e71275528b3e814ae236063/pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, 492 | ] 493 | "pytest-mock 3.8.2" = [ 494 | {url = "https://files.pythonhosted.org/packages/b6/78/4094a83dcd41e94f4c7e830983aef9089aaf6b3412da600a566cb04de1a5/pytest-mock-3.8.2.tar.gz", hash = "sha256:77f03f4554392558700295e05aed0b1096a20d4a60a4f3ddcde58b0c31c8fca2"}, 495 | {url = "https://files.pythonhosted.org/packages/64/ec/e21d6a5c31df566847de2dc7e7c1870c67cf10cb97cb0a1ea0e389446e60/pytest_mock-3.8.2-py3-none-any.whl", hash = "sha256:8a9e226d6c0ef09fcf20c94eb3405c388af438a90f3e39687f84166da82d5948"}, 496 | ] 497 | "pytest-sugar 0.9.5" = [ 498 | {url = "https://files.pythonhosted.org/packages/e2/d0/b770e5d47794323f06dab98c328c6d25758f4aca015017e709af3d1abee2/pytest-sugar-0.9.5.tar.gz", hash = "sha256:eea78b6f15b635277d3d90280cd386d8feea1cab0f9be75947a626e8b02b477d"}, 499 | {url = "https://files.pythonhosted.org/packages/59/30/4a8c1dc18c9b33a24d66bed3b1f164bb67341055cefe1469a7d4363da28f/pytest_sugar-0.9.5-py2.py3-none-any.whl", hash = "sha256:3da42de32ce4e1e95b448d61c92804433f5d4058c0a765096991c2e93d5a289f"}, 500 | ] 501 | "six 1.16.0" = [ 502 | {url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 503 | {url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 504 | ] 505 | "termcolor 1.1.0" = [ 506 | {url = "https://files.pythonhosted.org/packages/8a/48/a76be51647d0eb9f10e2a4511bf3ffb8cc1e6b14e9e4fab46173aa79f981/termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"}, 507 | ] 508 | "toml 0.10.2" = [ 509 | {url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 510 | {url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 511 | ] 512 | "tomli 2.0.1" = [ 513 | {url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 514 | {url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 515 | ] 516 | "tox 3.25.1" = [ 517 | {url = "https://files.pythonhosted.org/packages/83/14/f827cd5cb88aec768bb43c9e630df5a6bd358da54728122621133e2d1850/tox-3.25.1-py2.py3-none-any.whl", hash = "sha256:c38e15f4733683a9cc0129fba078633e07eb0961f550a010ada879e95fb32632"}, 518 | {url = "https://files.pythonhosted.org/packages/52/cb/16711c38f6503e8cd8c691a7b5500364fb0d7ee32a79fc3f8929968cd519/tox-3.25.1.tar.gz", hash = "sha256:c138327815f53bc6da4fe56baec5f25f00622ae69ef3fe4e1e385720e22486f9"}, 519 | ] 520 | "tox-pdm 0.5.0" = [ 521 | {url = "https://files.pythonhosted.org/packages/0e/bb/7b4956b7549bba40ef8d7de808a4732e340b81fd2c1975cebc5c16ba54d0/tox-pdm-0.5.0.tar.gz", hash = "sha256:45531c80c9670e7d9a0109ec5ab0d9add0492db4b41aa4ae0f20b295e4fd60e3"}, 522 | {url = "https://files.pythonhosted.org/packages/25/de/6616dd1a9d80a41165358534f3aa2e4f303a68abe587ca10acd5034d1024/tox_pdm-0.5.0-py3-none-any.whl", hash = "sha256:0075ff9ed47ee13dc6e55122e6da121661a5553a633b8dd278013fbee81e4bb4"}, 523 | ] 524 | "typed-ast 1.5.4" = [ 525 | {url = "https://files.pythonhosted.org/packages/0f/59/430b86961d63278fcbced5ba72655ee93aa35e8e908bad4ff138480eb25d/typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, 526 | {url = "https://files.pythonhosted.org/packages/48/6c/d96a545d337589dc5d7ecc0f8991122800ffec8dc10a24090619883b515e/typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, 527 | {url = "https://files.pythonhosted.org/packages/96/35/612258bab9e1867b28e3137910df35576b7b0fbb9b6f3013cc23435a79ed/typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, 528 | {url = "https://files.pythonhosted.org/packages/c4/90/dacf9226b34961277f357c17c33b7cae3f05a5f5b8a1d23bd630d7a97a36/typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, 529 | {url = "https://files.pythonhosted.org/packages/cd/f3/188eede730be3f6ddb9a788cd6b7289207c5fceebbf8ae190f9716dd8c05/typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, 530 | {url = "https://files.pythonhosted.org/packages/4e/c1/cddc664ed3dd7d6bb62c80286c4e088b10556efc9a8db2049b425f8f23f7/typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, 531 | {url = "https://files.pythonhosted.org/packages/38/54/48f7d5b1f954f3a4d8f76e1a11c8497ae899b900cd5a67f826fa3937f701/typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, 532 | {url = "https://files.pythonhosted.org/packages/e3/7c/7407838e9c540031439f2948bce2763cdd6882ebb72cc0a25b763c10529e/typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, 533 | {url = "https://files.pythonhosted.org/packages/70/2c/6d18e111d2c5422bb9e561bbf36885e430407859b2adef9b3fb575f189d5/typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, 534 | {url = "https://files.pythonhosted.org/packages/34/2d/17fc1845dd5210345904b054c9fa90f451d64df56de0470f429bc8d63d39/typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, 535 | {url = "https://files.pythonhosted.org/packages/dd/87/09764c19a60a192b935579c93a07e781f6a52def10b723c8c5748e69a863/typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, 536 | {url = "https://files.pythonhosted.org/packages/04/93/482d12fd3334b53ec4087e658ab161ab23affcf8b052166b4cf972ca673b/typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, 537 | {url = "https://files.pythonhosted.org/packages/1a/f6/dd891624aaf98b918d7012b9d01753d0192c4eb18cf33ce616c0e08f62ba/typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, 538 | {url = "https://files.pythonhosted.org/packages/9b/d5/5540eb496c6817eaee8120fb759c7adb36f91ef647c6bb2877f09acc0569/typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, 539 | {url = "https://files.pythonhosted.org/packages/2f/87/25abe9558ed6cbd83ad5bfdccf7210a7eefaaf0232f86de99f65992e91fd/typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, 540 | {url = "https://files.pythonhosted.org/packages/78/18/3ecf5043f227ebd4a43af57e18e6a38f9fe0b81dbfbb8d62eec669d7b69e/typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, 541 | {url = "https://files.pythonhosted.org/packages/40/1a/5731a1a3908f60032aead10c2ffc9af12ee708bc9a156ed14a5065a9873a/typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, 542 | {url = "https://files.pythonhosted.org/packages/5c/e3/f539e658614ebf5a521c8ba7cbbb98afc5f5e90ddb0332ea22c164612dad/typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, 543 | {url = "https://files.pythonhosted.org/packages/f9/57/89ac0020d5ffc762487376d0c78e5d02af795657f18c411155b73de3c765/typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, 544 | {url = "https://files.pythonhosted.org/packages/0b/e7/8ec06fc870254889198f933a595f139b7871b24bab1116d6128440731ea9/typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, 545 | {url = "https://files.pythonhosted.org/packages/2f/d5/02059fe6ca70b11bb831007962323160372ca83843e0bf296e8b6d833198/typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, 546 | {url = "https://files.pythonhosted.org/packages/d8/4e/db9505b53c44d7bc324a3d2e09bdf82b0943d6e08b183ae382860f482a87/typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, 547 | {url = "https://files.pythonhosted.org/packages/ca/da/fbc14befbf19d69d05b4b8b019edbc6554d958037a821c6d5585767fe0ff/typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, 548 | {url = "https://files.pythonhosted.org/packages/07/d2/d55702e8deba2c80282fea0df53130790d8f398648be589750954c2dcce4/typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, 549 | ] 550 | "typing-extensions 4.3.0" = [ 551 | {url = "https://files.pythonhosted.org/packages/ed/d6/2afc375a8d55b8be879d6b4986d4f69f01115e795e36827fd3a40166028b/typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, 552 | {url = "https://files.pythonhosted.org/packages/9e/1d/d128169ff58c501059330f1ad96ed62b79114a2eb30b8238af63a2e27f70/typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, 553 | ] 554 | "virtualenv 20.15.1" = [ 555 | {url = "https://files.pythonhosted.org/packages/6f/43/df7c7b1b7a5ac4e41fac24c3682c1cc32f2c1d683d308bba2500338d1e3e/virtualenv-20.15.1-py2.py3-none-any.whl", hash = "sha256:b30aefac647e86af6d82bfc944c556f8f1a9c90427b2fb4e3bfbf338cb82becf"}, 556 | {url = "https://files.pythonhosted.org/packages/a4/2f/05b77cb73501c01963de2cef343839f0803b64aab4d5476771ae303b97a6/virtualenv-20.15.1.tar.gz", hash = "sha256:288171134a2ff3bfb1a2f54f119e77cd1b81c29fc1265a2356f3e8d14c7d58c4"}, 557 | ] 558 | "zipp 3.8.1" = [ 559 | {url = "https://files.pythonhosted.org/packages/f0/36/639d6742bcc3ffdce8b85c31d79fcfae7bb04b95f0e5c4c6f8b206a038cc/zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, 560 | {url = "https://files.pythonhosted.org/packages/3b/e3/fb79a1ea5f3a7e9745f688855d3c673f2ef7921639a380ec76f7d4d83a85/zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, 561 | ] 562 | --------------------------------------------------------------------------------