From 281b11a86349007772c30851729bf97ae8f591f2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 00:19:33 +0000 Subject: [PATCH 001/151] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.2.0 → v3.2.2](https://github.com/asottile/pyupgrade/compare/v3.2.0...v3.2.2) - [github.com/hadialqattan/pycln: v2.1.1 → v2.1.2](https://github.com/hadialqattan/pycln/compare/v2.1.1...v2.1.2) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4b09dc9d55e..045dd8aec78 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -47,14 +47,14 @@ repos: - flake8-pie==0.16.0 - repo: https://github.com/asottile/pyupgrade - rev: v3.2.0 + rev: v3.2.2 hooks: - id: pyupgrade args: [--py37-plus] exclude: ^(install|get)-poetry.py$ - repo: https://github.com/hadialqattan/pycln - rev: v2.1.1 + rev: v2.1.2 hooks: - id: pycln args: [--all] From 544b4a14661fd1eeaee0a35244657214997227ed Mon Sep 17 00:00:00 2001 From: finswimmer Date: Sat, 15 Oct 2022 21:44:50 +0200 Subject: [PATCH 002/151] feat(cli): added cli option to specify path to project to run on --- src/poetry/console/application.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/poetry/console/application.py b/src/poetry/console/application.py index e54c679be9c..533233cadf3 100644 --- a/src/poetry/console/application.py +++ b/src/poetry/console/application.py @@ -121,8 +121,13 @@ def poetry(self) -> Poetry: if self._poetry is not None: return self._poetry + project_path = Path.cwd() + + if self._io and self._io.input.option("directory"): + project_path = self._io.input.option("directory") + self._poetry = Factory().create_poetry( - Path.cwd(), + cwd=project_path, io=self._io, disable_plugins=self._disable_plugins, disable_cache=self._disable_cache, @@ -367,6 +372,18 @@ def _default_definition(self) -> Definition: ) ) + definition.add_option( + Option( + "--directory", + "-C", + flag=False, + description=( + "The working directory for the Poetry command (defaults to the" + " current working directory)." + ), + ) + ) + return definition def _get_solution_provider_repository(self) -> SolutionProviderRepository: From eb4b45d7853d7c2ed37c27b07a691515f155bcbf Mon Sep 17 00:00:00 2001 From: finswimmer Date: Sat, 15 Oct 2022 22:41:25 +0200 Subject: [PATCH 003/151] feat(cli): interpret --directory option for `init` as target folder --- src/poetry/console/commands/init.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index af259f5c059..e34fa2a7d36 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -77,7 +77,17 @@ def handle(self) -> int: from poetry.layouts import layout from poetry.utils.env import SystemEnv - pyproject = PyProjectTOML(Path.cwd() / "pyproject.toml") + project_path = Path.cwd() + + if self.io.input.option("directory"): + project_path = Path(self.io.input.option("directory")) + if not project_path.exists() or not project_path.is_dir(): + self.line_error( + "The --directory path is not a directory." + ) + return 1 + + pyproject = PyProjectTOML(project_path / "pyproject.toml") if pyproject.file.exists(): if pyproject.is_poetry_project(): From 80296c2e48682259a9eb823943a1a78828fc6f1a Mon Sep 17 00:00:00 2001 From: finswimmer Date: Sat, 15 Oct 2022 22:42:16 +0200 Subject: [PATCH 004/151] feat(cli): added warning about ignoring --directory when trying to apply `new` command --- src/poetry/console/commands/new.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/poetry/console/commands/new.py b/src/poetry/console/commands/new.py index cde571aa202..968a3a2a90b 100644 --- a/src/poetry/console/commands/new.py +++ b/src/poetry/console/commands/new.py @@ -34,6 +34,12 @@ def handle(self) -> int: from poetry.layouts import layout from poetry.utils.env import SystemEnv + if self.io.input.option("directory"): + self.line_error( + "--directory only makes sense with existing projects, and will" + " be ignored. You should consider the option --path instead." + ) + if self.option("src"): layout_cls = layout("src") else: From 4962622e6717dd51015030b1d08d77c75ff37416 Mon Sep 17 00:00:00 2001 From: finswimmer Date: Sun, 16 Oct 2022 08:43:59 +0200 Subject: [PATCH 005/151] feat(cli): added --directory option to docs --- docs/cli.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/cli.md b/docs/cli.md index dee328483da..9a71e0e1b92 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -29,6 +29,7 @@ then `--help` combined with any of those can give you more information. * `--no-interaction (-n)`: Do not ask any interactive question. * `--no-plugins`: Disables plugins. * `--no-cache`: Disables Poetry source caches. +* `--directory=DIRECTORY (-C)`: The working directory for the Poetry command (defaults to the current working directory). ## new From 17e059e35064dfb3c809a6cb243cc1f25a60a284 Mon Sep 17 00:00:00 2001 From: rc-mattschwager <93352730+rc-mattschwager@users.noreply.github.com> Date: Wed, 16 Nov 2022 08:35:40 -0700 Subject: [PATCH 006/151] Relay return code as exit code when running script (#6824) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Randy Döring <30527984+radoering@users.noreply.github.com> --- src/poetry/console/commands/run.py | 2 +- tests/console/commands/test_run.py | 41 +++++++++++++++++++ tests/fixtures/scripts/README.md | 0 tests/fixtures/scripts/pyproject.toml | 17 ++++++++ tests/fixtures/scripts/scripts/__init__.py | 0 tests/fixtures/scripts/scripts/exit_code.py | 9 ++++ tests/fixtures/scripts/scripts/return_code.py | 9 ++++ 7 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/scripts/README.md create mode 100644 tests/fixtures/scripts/pyproject.toml create mode 100644 tests/fixtures/scripts/scripts/__init__.py create mode 100644 tests/fixtures/scripts/scripts/exit_code.py create mode 100644 tests/fixtures/scripts/scripts/return_code.py diff --git a/src/poetry/console/commands/run.py b/src/poetry/console/commands/run.py index 6496b45af24..50e1ae118f6 100644 --- a/src/poetry/console/commands/run.py +++ b/src/poetry/console/commands/run.py @@ -58,7 +58,7 @@ def run_script(self, script: str | dict[str, str], args: str) -> int: "import sys; " "from importlib import import_module; " f"sys.argv = {args!r}; {src_in_sys_path}" - f"import_module('{module}').{callable_}()" + f"sys.exit(import_module('{module}').{callable_}())" ] return self.env.execute(*cmd) diff --git a/tests/console/commands/test_run.py b/tests/console/commands/test_run.py index 6465c7f55ef..60bc94e74a7 100644 --- a/tests/console/commands/test_run.py +++ b/tests/console/commands/test_run.py @@ -1,5 +1,7 @@ from __future__ import annotations +import subprocess + from typing import TYPE_CHECKING import pytest @@ -12,9 +14,12 @@ from cleo.testers.command_tester import CommandTester from pytest_mock import MockerFixture + from poetry.poetry import Poetry from poetry.utils.env import MockEnv from poetry.utils.env import VirtualEnv from tests.types import CommandTesterFactory + from tests.types import FixtureDirGetter + from tests.types import ProjectFactory @pytest.fixture @@ -27,6 +32,19 @@ def patches(mocker: MockerFixture, env: MockEnv) -> None: mocker.patch("poetry.utils.env.EnvManager.get", return_value=env) +@pytest.fixture +def poetry_with_scripts( + project_factory: ProjectFactory, fixture_dir: FixtureDirGetter +) -> Poetry: + source = fixture_dir("scripts") + + return project_factory( + name="scripts", + pyproject_content=(source / "pyproject.toml").read_text(encoding="utf-8"), + source=source, + ) + + def test_run_passes_all_args(app_tester: ApplicationTester, env: MockEnv): app_tester.execute("run python -V") assert [["python", "-V"]] == env.executed @@ -105,3 +123,26 @@ def test_run_console_scripts_of_editable_dependencies_on_windows( # We prove that the CMD script executed successfully by verifying the exit code # matches what we wrote in the script assert tester.execute("quix") == 123 + + +def test_run_script_exit_code( + poetry_with_scripts: Poetry, + command_tester_factory: CommandTesterFactory, + tmp_venv: VirtualEnv, + mocker: MockerFixture, +) -> None: + mocker.patch( + "os.execvpe", + lambda file, args, env: subprocess.call([file] + args[1:], env=env), + ) + install_tester = command_tester_factory( + "install", + poetry=poetry_with_scripts, + environment=tmp_venv, + ) + assert install_tester.execute() == 0 + tester = command_tester_factory( + "run", poetry=poetry_with_scripts, environment=tmp_venv + ) + assert tester.execute("exit-code") == 42 + assert tester.execute("return-code") == 42 diff --git a/tests/fixtures/scripts/README.md b/tests/fixtures/scripts/README.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/fixtures/scripts/pyproject.toml b/tests/fixtures/scripts/pyproject.toml new file mode 100644 index 00000000000..a53f36b930e --- /dev/null +++ b/tests/fixtures/scripts/pyproject.toml @@ -0,0 +1,17 @@ +[tool.poetry] +name = "scripts" +version = "0.1.0" +description = "" +authors = ["Your Name "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.7" + +[tool.poetry.scripts] +exit-code = "scripts.exit_code:main" +return-code = "scripts.return_code:main" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/fixtures/scripts/scripts/__init__.py b/tests/fixtures/scripts/scripts/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/fixtures/scripts/scripts/exit_code.py b/tests/fixtures/scripts/scripts/exit_code.py new file mode 100644 index 00000000000..862e3956cbe --- /dev/null +++ b/tests/fixtures/scripts/scripts/exit_code.py @@ -0,0 +1,9 @@ +from __future__ import annotations + + +def main() -> None: + raise SystemExit(42) + + +if __name__ == "__main__": + main() diff --git a/tests/fixtures/scripts/scripts/return_code.py b/tests/fixtures/scripts/scripts/return_code.py new file mode 100644 index 00000000000..f645790ee37 --- /dev/null +++ b/tests/fixtures/scripts/scripts/return_code.py @@ -0,0 +1,9 @@ +from __future__ import annotations + + +def main() -> int: + return 42 + + +if __name__ == "__main__": + raise SystemExit(main()) From 286f4ddb70394ddcf30015e924b70895616f1074 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Wed, 16 Nov 2022 16:25:34 +0000 Subject: [PATCH 007/151] search_for should not raise ValueError to indicate that a package could not be found (#6790) subclasses of ValueError like UnicodeEncodeError have to be propagated (see #6784) --- src/poetry/mixology/incompatibility.py | 8 ----- src/poetry/mixology/incompatibility_cause.py | 14 --------- src/poetry/mixology/version_solver.py | 32 +++----------------- 3 files changed, 4 insertions(+), 50 deletions(-) diff --git a/src/poetry/mixology/incompatibility.py b/src/poetry/mixology/incompatibility.py index b629ea7a699..8ac85731a17 100644 --- a/src/poetry/mixology/incompatibility.py +++ b/src/poetry/mixology/incompatibility.py @@ -5,7 +5,6 @@ from poetry.mixology.incompatibility_cause import ConflictCause from poetry.mixology.incompatibility_cause import DependencyCause from poetry.mixology.incompatibility_cause import NoVersionsCause -from poetry.mixology.incompatibility_cause import PackageNotFoundCause from poetry.mixology.incompatibility_cause import PlatformCause from poetry.mixology.incompatibility_cause import PythonCause from poetry.mixology.incompatibility_cause import RootCause @@ -146,11 +145,6 @@ def __str__(self) -> str: f"no versions of {self._terms[0].dependency.name} match" f" {self._terms[0].constraint}" ) - elif isinstance(self._cause, PackageNotFoundCause): - assert len(self._terms) == 1 - assert self._terms[0].is_positive() - - return f"{self._terms[0].dependency.name} doesn't exist" elif isinstance(self._cause, RootCause): assert len(self._terms) == 1 assert not self._terms[0].is_positive() @@ -420,8 +414,6 @@ def _try_requires_forbidden( buffer.append(f"which requires Python {cause.python_version}") elif isinstance(latter.cause, NoVersionsCause): buffer.append("which doesn't match any versions") - elif isinstance(latter.cause, PackageNotFoundCause): - buffer.append("which doesn't exist") else: buffer.append("which is forbidden") diff --git a/src/poetry/mixology/incompatibility_cause.py b/src/poetry/mixology/incompatibility_cause.py index 87eee3eef16..1536d1b22b2 100644 --- a/src/poetry/mixology/incompatibility_cause.py +++ b/src/poetry/mixology/incompatibility_cause.py @@ -79,17 +79,3 @@ def __init__(self, platform: str) -> None: @property def platform(self) -> str: return self._platform - - -class PackageNotFoundCause(IncompatibilityCause): - """ - The incompatibility represents a package that couldn't be found by its - source. - """ - - def __init__(self, error: Exception) -> None: - self._error = error - - @property - def error(self) -> Exception: - return self._error diff --git a/src/poetry/mixology/version_solver.py b/src/poetry/mixology/version_solver.py index 06ae39443e0..6c66dc4c5e9 100644 --- a/src/poetry/mixology/version_solver.py +++ b/src/poetry/mixology/version_solver.py @@ -3,7 +3,6 @@ import functools import time -from contextlib import suppress from typing import TYPE_CHECKING from poetry.core.packages.dependency import Dependency @@ -12,7 +11,6 @@ from poetry.mixology.incompatibility import Incompatibility from poetry.mixology.incompatibility_cause import ConflictCause from poetry.mixology.incompatibility_cause import NoVersionsCause -from poetry.mixology.incompatibility_cause import PackageNotFoundCause from poetry.mixology.incompatibility_cause import RootCause from poetry.mixology.partial_solution import PartialSolution from poetry.mixology.result import SolverResult @@ -395,10 +393,8 @@ def _get_min(dependency: Dependency) -> tuple[bool, int, int]: if locked: return is_specific_marker, Preference.LOCKED, 1 - try: - num_packages = len(self._dependency_cache.search_for(dependency)) - except ValueError: - num_packages = 0 + num_packages = len(self._dependency_cache.search_for(dependency)) + if num_packages < 2: preference = Preference.NO_CHOICE elif use_latest: @@ -414,28 +410,8 @@ def _get_min(dependency: Dependency) -> tuple[bool, int, int]: locked = self._provider.get_locked(dependency) if locked is None: - try: - packages = self._dependency_cache.search_for(dependency) - except ValueError as e: - self._add_incompatibility( - Incompatibility([Term(dependency, True)], PackageNotFoundCause(e)) - ) - complete_name: str = dependency.complete_name - return complete_name - - package = None - if locked is not None: - package = next( - ( - p - for p in packages - if p.package.version == locked.package.version - ), - None, - ) - if package is None: - with suppress(IndexError): - package = packages[0] + packages = self._dependency_cache.search_for(dependency) + package = next(iter(packages), None) if package is None: # If there are no versions that satisfy the constraint, From ba97fea031aecd5fd31878e15ece24b3ddc55752 Mon Sep 17 00:00:00 2001 From: Moonsik Park Date: Thu, 17 Nov 2022 03:15:18 +0900 Subject: [PATCH 008/151] Fix: Remove `NullLocker` (#7025) --- poetry.lock | 59 ++++++++++++++++++++--------------- pyproject.toml | 2 +- src/poetry/packages/locker.py | 5 --- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/poetry.lock b/poetry.lock index cf437c6d0a9..df3d6547bdb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -396,7 +396,7 @@ python-versions = "*" [[package]] name = "mypy" -version = "0.982" +version = "0.990" description = "Optional static typing for Python" category = "dev" optional = false @@ -410,6 +410,7 @@ typing-extensions = ">=3.10" [package.extras] dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] python2 = ["typed-ast (>=1.4.0,<2)"] reports = ["lxml"] @@ -951,7 +952,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "5f69872916713817bf4617699cc1ed4a0baf50ed78422cc821cc14e43ff2de4b" +content-hash = "d81d2884ee157f8ecc240a6c1ae2c8c95dfb9a9a34272b7e5831d2a2a62a31b6" [metadata.files] attrs = [ @@ -1298,30 +1299,36 @@ msgpack = [ {file = "msgpack-1.0.4.tar.gz", hash = "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f"}, ] mypy = [ - {file = "mypy-0.982-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5085e6f442003fa915aeb0a46d4da58128da69325d8213b4b35cc7054090aed5"}, - {file = "mypy-0.982-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:41fd1cf9bc0e1c19b9af13a6580ccb66c381a5ee2cf63ee5ebab747a4badeba3"}, - {file = "mypy-0.982-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f793e3dd95e166b66d50e7b63e69e58e88643d80a3dcc3bcd81368e0478b089c"}, - {file = "mypy-0.982-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86ebe67adf4d021b28c3f547da6aa2cce660b57f0432617af2cca932d4d378a6"}, - {file = "mypy-0.982-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:175f292f649a3af7082fe36620369ffc4661a71005aa9f8297ea473df5772046"}, - {file = "mypy-0.982-cp310-cp310-win_amd64.whl", hash = "sha256:8ee8c2472e96beb1045e9081de8e92f295b89ac10c4109afdf3a23ad6e644f3e"}, - {file = "mypy-0.982-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58f27ebafe726a8e5ccb58d896451dd9a662a511a3188ff6a8a6a919142ecc20"}, - {file = "mypy-0.982-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6af646bd46f10d53834a8e8983e130e47d8ab2d4b7a97363e35b24e1d588947"}, - {file = "mypy-0.982-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7aeaa763c7ab86d5b66ff27f68493d672e44c8099af636d433a7f3fa5596d40"}, - {file = "mypy-0.982-cp37-cp37m-win_amd64.whl", hash = "sha256:724d36be56444f569c20a629d1d4ee0cb0ad666078d59bb84f8f887952511ca1"}, - {file = "mypy-0.982-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:14d53cdd4cf93765aa747a7399f0961a365bcddf7855d9cef6306fa41de01c24"}, - {file = "mypy-0.982-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:26ae64555d480ad4b32a267d10cab7aec92ff44de35a7cd95b2b7cb8e64ebe3e"}, - {file = "mypy-0.982-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6389af3e204975d6658de4fb8ac16f58c14e1bacc6142fee86d1b5b26aa52bda"}, - {file = "mypy-0.982-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b35ce03a289480d6544aac85fa3674f493f323d80ea7226410ed065cd46f206"}, - {file = "mypy-0.982-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c6e564f035d25c99fd2b863e13049744d96bd1947e3d3d2f16f5828864506763"}, - {file = "mypy-0.982-cp38-cp38-win_amd64.whl", hash = "sha256:cebca7fd333f90b61b3ef7f217ff75ce2e287482206ef4a8b18f32b49927b1a2"}, - {file = "mypy-0.982-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a705a93670c8b74769496280d2fe6cd59961506c64f329bb179970ff1d24f9f8"}, - {file = "mypy-0.982-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75838c649290d83a2b83a88288c1eb60fe7a05b36d46cbea9d22efc790002146"}, - {file = "mypy-0.982-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:91781eff1f3f2607519c8b0e8518aad8498af1419e8442d5d0afb108059881fc"}, - {file = "mypy-0.982-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaa97b9ddd1dd9901a22a879491dbb951b5dec75c3b90032e2baa7336777363b"}, - {file = "mypy-0.982-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a692a8e7d07abe5f4b2dd32d731812a0175626a90a223d4b58f10f458747dd8a"}, - {file = "mypy-0.982-cp39-cp39-win_amd64.whl", hash = "sha256:eb7a068e503be3543c4bd329c994103874fa543c1727ba5288393c21d912d795"}, - {file = "mypy-0.982-py3-none-any.whl", hash = "sha256:1021c241e8b6e1ca5a47e4d52601274ac078a89845cfde66c6d5f769819ffa1d"}, - {file = "mypy-0.982.tar.gz", hash = "sha256:85f7a343542dc8b1ed0a888cdd34dca56462654ef23aa673907305b260b3d746"}, + {file = "mypy-0.990-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aaf1be63e0207d7d17be942dcf9a6b641745581fe6c64df9a38deb562a7dbafa"}, + {file = "mypy-0.990-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d555aa7f44cecb7ea3c0ac69d58b1a5afb92caa017285a8e9c4efbf0518b61b4"}, + {file = "mypy-0.990-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f694d6d09a460b117dccb6857dda269188e3437c880d7b60fa0014fa872d1e9"}, + {file = "mypy-0.990-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:269f0dfb6463b8780333310ff4b5134425157ef0d2b1d614015adaf6d6a7eabd"}, + {file = "mypy-0.990-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8798c8ed83aa809f053abff08664bdca056038f5a02af3660de00b7290b64c47"}, + {file = "mypy-0.990-cp310-cp310-win_amd64.whl", hash = "sha256:47a9955214615108c3480a500cfda8513a0b1cd3c09a1ed42764ca0dd7b931dd"}, + {file = "mypy-0.990-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4a8a6c10f4c63fbf6ad6c03eba22c9331b3946a4cec97f008e9ffb4d3b31e8e2"}, + {file = "mypy-0.990-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd2dd3730ba894ec2a2082cc703fbf3e95a08479f7be84912e3131fc68809d46"}, + {file = "mypy-0.990-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7da0005e47975287a92b43276e460ac1831af3d23032c34e67d003388a0ce8d0"}, + {file = "mypy-0.990-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262c543ef24deb10470a3c1c254bb986714e2b6b1a67d66daf836a548a9f316c"}, + {file = "mypy-0.990-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3ff201a0c6d3ea029d73b1648943387d75aa052491365b101f6edd5570d018ea"}, + {file = "mypy-0.990-cp311-cp311-win_amd64.whl", hash = "sha256:1767830da2d1afa4e62b684647af0ff79b401f004d7fa08bc5b0ce2d45bcd5ec"}, + {file = "mypy-0.990-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6826d9c4d85bbf6d68cb279b561de6a4d8d778ca8e9ab2d00ee768ab501a9852"}, + {file = "mypy-0.990-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46897755f944176fbc504178422a5a2875bbf3f7436727374724842c0987b5af"}, + {file = "mypy-0.990-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0680389c34284287fe00e82fc8bccdea9aff318f7e7d55b90d967a13a9606013"}, + {file = "mypy-0.990-cp37-cp37m-win_amd64.whl", hash = "sha256:b08541a06eed35b543ae1a6b301590eb61826a1eb099417676ddc5a42aa151c5"}, + {file = "mypy-0.990-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:be88d665e76b452c26fb2bdc3d54555c01226fba062b004ede780b190a50f9db"}, + {file = "mypy-0.990-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b8f4a8213b1fd4b751e26b59ae0e0c12896568d7e805861035c7a15ed6dc9eb"}, + {file = "mypy-0.990-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2b6f85c2ad378e3224e017904a051b26660087b3b76490d533b7344f1546d3ff"}, + {file = "mypy-0.990-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ee5f99817ee70254e7eb5cf97c1b11dda29c6893d846c8b07bce449184e9466"}, + {file = "mypy-0.990-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49082382f571c3186ce9ea0bd627cb1345d4da8d44a8377870f4442401f0a706"}, + {file = "mypy-0.990-cp38-cp38-win_amd64.whl", hash = "sha256:aba38e3dd66bdbafbbfe9c6e79637841928ea4c79b32e334099463c17b0d90ef"}, + {file = "mypy-0.990-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9d851c09b981a65d9d283a8ccb5b1d0b698e580493416a10942ef1a04b19fd37"}, + {file = "mypy-0.990-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d847dd23540e2912d9667602271e5ebf25e5788e7da46da5ffd98e7872616e8e"}, + {file = "mypy-0.990-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc6019808580565040cd2a561b593d7c3c646badd7e580e07d875eb1bf35c695"}, + {file = "mypy-0.990-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a3150d409609a775c8cb65dbe305c4edd7fe576c22ea79d77d1454acd9aeda8"}, + {file = "mypy-0.990-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3227f14fe943524f5794679156488f18bf8d34bfecd4623cf76bc55958d229c5"}, + {file = "mypy-0.990-cp39-cp39-win_amd64.whl", hash = "sha256:c76c769c46a1e6062a84837badcb2a7b0cdb153d68601a61f60739c37d41cc74"}, + {file = "mypy-0.990-py3-none-any.whl", hash = "sha256:8f1940325a8ed460ba03d19ab83742260fa9534804c317224e5d4e5aa588e2d6"}, + {file = "mypy-0.990.tar.gz", hash = "sha256:72382cb609142dba3f04140d016c94b4092bc7b4d98ca718740dc989e5271b8d"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, diff --git a/pyproject.toml b/pyproject.toml index a07edeb1d37..ae9aba79c57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,7 +93,7 @@ pytest-xdist = { version = "^2.5", extras = ["psutil"] } zipp = { version = "^3.4", python = "<3.8" } [tool.poetry.group.typing.dependencies] -mypy = ">=0.971" +mypy = ">=0.990" types-html5lib = ">=1.1.9" types-jsonschema = ">=4.9.0" types-requests = ">=2.28.8" diff --git a/src/poetry/packages/locker.py b/src/poetry/packages/locker.py index f9ff42b8ba7..7fd19669457 100644 --- a/src/poetry/packages/locker.py +++ b/src/poetry/packages/locker.py @@ -464,8 +464,3 @@ def _dump_package(self, package: Package) -> dict[str, Any]: data["develop"] = package.develop return data - - -class NullLocker(Locker): - def set_lock_data(self, root: Package, packages: list[Package]) -> bool: - pass From 14b7f1ee0503533d1099684700716ddcdbcdaad7 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Tue, 22 Nov 2022 15:19:49 +0000 Subject: [PATCH 009/151] Cleo 2.0 (#7070) --- poetry.lock | 134 +++++++++++++++--- pyproject.toml | 7 +- src/poetry/console/application.py | 27 ++-- src/poetry/console/command_loader.py | 6 +- src/poetry/console/commands/command.py | 11 +- src/poetry/console/commands/debug/info.py | 2 +- src/poetry/console/commands/debug/resolve.py | 10 +- src/poetry/console/commands/init.py | 12 +- src/poetry/console/commands/show.py | 3 +- src/poetry/console/commands/source/show.py | 8 +- src/poetry/console/exceptions.py | 4 +- .../console/io/inputs/run_argv_input.py | 2 +- .../python_requirement_solution_provider.py | 11 +- .../solutions/python_requirement_solution.py | 2 +- src/poetry/puzzle/provider.py | 2 +- src/poetry/vcs/git/backend.py | 12 +- tests/integration/test_utils_vcs_git.py | 6 +- 17 files changed, 180 insertions(+), 79 deletions(-) diff --git a/poetry.lock b/poetry.lock index df3d6547bdb..8de70938b6c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -90,15 +90,15 @@ unicode-backport = ["unicodedata2"] [[package]] name = "cleo" -version = "1.0.0a5" +version = "2.0.0" description = "Cleo allows you to create beautiful and testable command-line interfaces." category = "main" optional = false python-versions = ">=3.7,<4.0" [package.dependencies] -crashtest = ">=0.3.1,<0.4.0" -pylev = ">=1.3.0,<2.0.0" +crashtest = ">=0.4.1,<0.5.0" +rapidfuzz = ">=2.2.0,<3.0.0" [[package]] name = "colorama" @@ -124,11 +124,11 @@ toml = ["tomli"] [[package]] name = "crashtest" -version = "0.3.1" +version = "0.4.1" description = "Manage Python errors with ease" category = "main" optional = false -python-versions = ">=3.6,<4.0" +python-versions = ">=3.7,<4.0" [[package]] name = "cryptography" @@ -587,14 +587,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "pylev" -version = "1.4.0" -description = "A pure Python Levenshtein implementation that's not freaking GPL'd." -category = "main" -optional = false -python-versions = "*" - [[package]] name = "pyparsing" version = "3.0.9" @@ -734,6 +726,17 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "rapidfuzz" +version = "2.13.2" +description = "rapid fuzzy string matching" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +full = ["numpy"] + [[package]] name = "requests" version = "2.28.1" @@ -952,7 +955,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "d81d2884ee157f8ecc240a6c1ae2c8c95dfb9a9a34272b7e5831d2a2a62a31b6" +content-hash = "c2607df5992f1075a9a0d1a067e8e935a32e65dac79f0a2445e0c619b08883c7" [metadata.files] attrs = [ @@ -1050,8 +1053,8 @@ charset-normalizer = [ {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, ] cleo = [ - {file = "cleo-1.0.0a5-py3-none-any.whl", hash = "sha256:ff53056589300976e960f75afb792dfbfc9c78dcbb5a448e207a17b643826360"}, - {file = "cleo-1.0.0a5.tar.gz", hash = "sha256:097c9d0e0332fd53cc89fc11eb0a6ba0309e6a3933c08f7b38558555486925d3"}, + {file = "cleo-2.0.0-py3-none-any.whl", hash = "sha256:daad7ff76134ebe2c7bf74520b1bbd59e6e77026535b967efc5a15a0eaa2e19c"}, + {file = "cleo-2.0.0.tar.gz", hash = "sha256:fbc5cb141cbc31ea8ffd3d5cd67d3b183fa38aa5098fd37e39e9a953a232fda9"}, ] colorama = [ {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, @@ -1110,8 +1113,8 @@ coverage = [ {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, ] crashtest = [ - {file = "crashtest-0.3.1-py3-none-any.whl", hash = "sha256:300f4b0825f57688b47b6d70c6a31de33512eb2fa1ac614f780939aa0cf91680"}, - {file = "crashtest-0.3.1.tar.gz", hash = "sha256:42ca7b6ce88b6c7433e2ce47ea884e91ec93104a4b754998be498a8e6c3d37dd"}, + {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, + {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, ] cryptography = [ {file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f"}, @@ -1424,10 +1427,6 @@ pycparser = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] -pylev = [ - {file = "pylev-1.4.0-py2.py3-none-any.whl", hash = "sha256:7b2e2aa7b00e05bb3f7650eb506fc89f474f70493271a35c242d9a92188ad3dd"}, - {file = "pylev-1.4.0.tar.gz", hash = "sha256:9e77e941042ad3a4cc305dcdf2b2dec1aec2fbe3dd9015d2698ad02b173006d1"}, -] pyparsing = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, @@ -1529,6 +1528,97 @@ PyYAML = [ {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] +rapidfuzz = [ + {file = "rapidfuzz-2.13.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91c049f7591d9e9f8bcc3c556c0c4b448223f564ad04511a8719d28f5d38daed"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:26e4b7f7941b92546a9b06ed75b40b5d7ceace8f3074d06cb3369349388d700d"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba2a8fbd21079093118c40e8e80068750c1619a5988e54220ea0929de48e7d65"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de707808f1997574014d9ba87c2d9f8a619688d615520e3dce958bf4398514c7"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba3f47a5b82de7304ae08e2a111ccc90a6ea06ecc3f25d7870d08be0973c94cb"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a181b6ef9b480b56b29bdc58dc50c198e93d33398d2f8e57da05cbddb095bd9e"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1e569953a2abe945f116a6c22b71e8fc02d7c27068af2af40990115f25c93e4"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:026f6ecd8948e168a89fc015ef34b6bcb200f30ac33f1480554d722181b38bea"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daf5e4f6b048c225a494c941a21463a0d397c39a080db8fece9b3136297ed240"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e39ae60598ed533f513db6d0370755685666024ab187a144fc688dd16cfa2d33"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e8d71f1611431c445ced872b303cd61f215551a11df0c7171e5993bed84867d5"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f5d07dca69bf5a9f1e1cd5756ded6c197a27e8d8f2d8a3d99565add37a3bd1ec"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ac95981911559c842e1e4532e2f89ca255531db1d87257e5e69cd8c0c0d585fc"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-win32.whl", hash = "sha256:b4162b96d0908cb0ca218513eab559e9a77c8a1d9705c9133813634d9db27f4f"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-win_amd64.whl", hash = "sha256:84fd3cfc1cb872019e60a3844b1deedb176de0b9ded11bf30147137ac65185f5"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a599cc5cec196c0776faf65b74ac957354bd036f878905a16be9e20884870d02"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dbad2b7dad98b854a468d2c6a0b11464f68ce841428aded2f24f201a17a144eb"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad78fb90540dc752b532345065146371acd3804a917c31fdd8a337951da9def2"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed0f99e0037b7f9f7117493e8723851c9eece4629906b2d5da21d3ef124149a2"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9abdffc590ef08d27dfd14d32e571f4a0f5f797f433f00c5faf4cf56ab62792a"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:352c920e166e838bc560014885ba979df656938fcc29a12c73ff06dc76b150d8"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c40acbadc965e72f1b44b3c665a59ec78a5e959757e52520bf73687c84ce6854"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a4053d5b62cedec83ff67d55e50da35f7736bed0a3b2af51fa6143f5fef3785"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0c324d82871fe50471f7ba38a21c3e68167e868f541f57ac0ef23c053bbef6e6"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cb4bd75518838b141dab8fe663de988c4d08502999068dc0b3949d43bd86ace6"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4b785ffbc16795fca27c9e899993df7721d886249061689c48dbfe60fa7d02a1"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:1f363bf95d79dbafa8eac17697965e02e74da6f21b231b3fb808b2185bfed337"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f7cfc25d8143a7570f5e4c9da072a1e1c335d81a6926eb10c1fd3f637fa3c022"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-win32.whl", hash = "sha256:580f32cda7f911fef8266c7d811e580c18734cd12308d099b9975b914f33fcaf"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:98be3e873c8f9d90a982891b2b061521ba4e5e49552ba2d3c1b0806dd5677f88"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:de8ec700127b645b0e2e28e694a2bba6dcb6a305ef080ad312f3086d47fb6973"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0ec73e6d3ad9442cfb5b94c137cf4241fff2860d81a9ee8be8c3d987bb400c0"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da5b7f35fc824cff36a2baa62486d5b427bf0fd7714c19704b5a7df82c2950b4"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f186b3a32d78af7a805584a7e1c2fdf6f6fd62939936e4f3df869158c147a55"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68f2e23eec59fc77bef164157889a2f7fb9800c47d615c58ee3809e2be3c8509"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4291a8c02d32aa6ebdffe63cf91abc2846383de95ae04a275f036c4e7a27f9ba"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a2eeee09ff716c8ff75942c1b93f0bca129590499f1127cbeb1b5cefbdc0c3d5"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2345656b30d7e18d18a4df5b765e4059111860a69bf3a36608a7d625e92567e6"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1e1dd1a328464dd2ae70f0e31ec403593fbb1b254bab7ac9f0cd08ba71c797d0"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:54fe1835f96c1033cdb7e4677497e784704c81d028c962d2222239ded93d978b"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6b68b6a12411cfacca16ace22d42ae8e9946315d79f49c6c97089789c235e795"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-win32.whl", hash = "sha256:9a740ddd3f7725c80e500f16b1b02b83a58b47164c0f3ddd9379208629c8c4b5"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-win_amd64.whl", hash = "sha256:378554acdcf8370cc5c777b1312921a2a670f68888e999ea1305599c55b67f5d"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa96955f2878116239db55506fe825f574651a8893d07a83de7b3c76a2f0386e"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b4df886481ca27a6d53d30a73625fb86dd308cf7d6d99d32e0dfbfcc8e8a75b9"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c66f3b8e93cdc3063ffd7224cad84951834d9434ffd27fa3fabad2e942ddab7"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d6d5ab0f12f2d7ae6aad77af67ae6253b6c1d54c320484f1acd2fce38b39ac2"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f0574d5d97722cfaf51b7dd667c8c836fa9fdf5a7d8158a787b98ee2788f6c5"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:83ff31d33c1391a0a6b23273b7f839dc8f7b5fb75ddca59ce4f334b83ca822bb"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94d8c65f48665f82064bea8a48ff185409a309ba396f5aec3a846831cbe36e6d"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c065a83883af2a9a0303b6c06844a700af0db97ff6dc894324f656ad8efe405"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:773c60a5368a361253efea194552ff9ed6879756f6feb71b61b514723f8cb726"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:12ece1a4d024297afa4b76d2ce71c2c65fc7eaa487a9ae9f6e17c160253cfd23"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2b491f2fac36718247070c3343f53aadbbe8684f3e0cf3b6cce1bd099e1d05cb"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:31370273787dca851e2df6f32f1ec8c61f86e9bbeb1cc42787020b6dfff952fd"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:47b5b227dc0bd53530dda55f344e1b24087fa99bb1bd7fceb6f5a2b1e2831ad4"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-win32.whl", hash = "sha256:8f09a16ae84b1decb9df9a7e393ec84a0b2a11da6356c3eedcf86da8cabe3071"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-win_amd64.whl", hash = "sha256:e038e187270cbb987cf7c5d4b574fce7a32bc3d9593e9346d129874a7dc08dc3"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:aee5dce78e157e503269121ad6f886acab4b1ab3e3956bcdf0549d54596eab57"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80073e897af0669f496d23899583b5c2f0decc2ec06aa7c36a3b8fb16eda5e0e"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ce40c2a68fe28e05a4f66229c11885ef928086fbcd2eff086decdacfe5254da9"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd268701bf930bbb2d12f6f7f75c681e16fee646ea1663d258e825bf919ca7a1"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5d93e77881497f76e77056feea4c375732d27151151273d6e4cb8a1defbf17a"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b27c3e2b1789a635b9df1d74838ae032dc2dbc596ece5d89f9de2c37ba0a6dfe"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e49f412fe58c793af61b04fb5536534dfc95000b6c2bf0bfa42fcf7eb1453d42"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27bbdee91718019e251d315c6e9b03aa5b7663b90e4228ac1ddb0a567ff3634b"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b51d45cb9ed81669206e338413ba224c06a8900ab0cc9106f4750ac73dc687bb"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3479a2fd88504cc41eb707650e81fd7ce864f2418fee24f7224775b539536b39"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7adb4327453c1550f51d6ba13d718a84091f82230c1d0daca6db628e57d0fa5a"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3a4e87aae287d757d9c5b045c819c985b02b38dea3f75630cc24d53826e640be"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e175b1643306558a3d7604789c4a8c217a64406fe82bf1a9e52efb5dea53ae"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-win32.whl", hash = "sha256:fb896fafa206db4d55f4412135c3ae28fbc56b8afc476970d0c5f29d2ce50948"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-win_amd64.whl", hash = "sha256:37a9a8f5737b8e429291148be67d2dd8ba779a69a87ad95d2785bb3d80fd1df7"}, + {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d6cb51a8459e7160366c6c7b31e8f9a671f7d617591c0ad305f2697707061da2"}, + {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:343fe1fcbbf55c994b22962bfb46f6b6903faeac5a2671b2f0fa5e3664de3e66"}, + {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d9d081cd8e0110661c8a3e728d7b491a903bb54d34de40b17d19144563bd5f6"}, + {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f93a6740fef239a8aca6521cc1891d448664115b53528a3dd7f95c1781a5fa6"}, + {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:deaf26cc23cfbf90650993108de888533635b981a7157a0234b4753527ac6e5c"}, + {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b6a0617ba60f81a8df3b9ddca09f591a0a0c8269402169825fcd50daa03e5c25"}, + {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bee1065d55edfeabdb98211bb673cb44a8b118cded42d743f7d59c07b05a80d"}, + {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e5afd5477332ceeb960e2002d5bb0b04ad00b40037a0ab1de9916041badcf00"}, + {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eead76c172ba08d49ea621016cf84031fff1ee33d7db751d7003e491e55e66af"}, + {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:83b1e8aca6c3fad058d8a2b7653b7496df0c4aca903d589bb0e4184868290767"}, + {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:41610c3a9be4febcbcac2b69b2f45d0da33e39d1194e5ffa3dd3a104d5a67a70"}, + {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3aacc4eb58d6bccf6ec571619bee35861d4103961b9873d9b0829d347ca8a63e"}, + {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:791d90aa1c68b5485f6340a8dc485aba7e9bcb729572449174ded0692e7e7ad0"}, + {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d4f94b408c9f9218d61e8af55e43c8102f813eea2cf82de10906b032ddcb9aa"}, + {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ac6a8a34f858f3862798383f51012788df6be823e2874fa426667a4da94ded7e"}, + {file = "rapidfuzz-2.13.2.tar.gz", hash = "sha256:1c67007161655c59e13bba130a2db29d7c9e5c81bcecb8846a3dd7386065eb24"}, +] requests = [ {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, diff --git a/pyproject.toml b/pyproject.toml index ae9aba79c57..d0257a59230 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,8 +51,8 @@ poetry-core = "^1.3.2" poetry-plugin-export = "^1.2.0" "backports.cached-property" = { version = "^1.0.2", python = "<3.8" } cachecontrol = { version = "^0.12.9", extras = ["filecache"] } -cleo = "^1.0.0a5" -crashtest = "^0.3.0" +cleo = "^2.0.0" +crashtest = "^0.4.1" dulwich = "^0.20.46" filelock = "^3.8.0" html5lib = "^1.0" @@ -168,9 +168,6 @@ warn_unused_ignores = false [[tool.mypy.overrides]] module = [ 'cachecontrol.*', - 'cachy.*', - 'cleo.*', - 'crashtest.*', 'lockfile.*', 'pexpect.*', 'pkginfo.*', diff --git a/src/poetry/console/application.py b/src/poetry/console/application.py index 533233cadf3..7c60f99836d 100644 --- a/src/poetry/console/application.py +++ b/src/poetry/console/application.py @@ -6,13 +6,13 @@ from contextlib import suppress from importlib import import_module from typing import TYPE_CHECKING -from typing import Any from typing import cast from cleo.application import Application as BaseApplication +from cleo.events.console_command_event import ConsoleCommandEvent from cleo.events.console_events import COMMAND from cleo.events.event_dispatcher import EventDispatcher -from cleo.exceptions import CleoException +from cleo.exceptions import CleoError from cleo.formatters.style import Style from cleo.io.null_io import NullIO @@ -24,7 +24,7 @@ if TYPE_CHECKING: from collections.abc import Callable - from cleo.events.console_command_event import ConsoleCommandEvent + from cleo.events.event import Event from cleo.io.inputs.argv_input import ArgvInput from cleo.io.inputs.definition import Definition from cleo.io.inputs.input import Input @@ -93,7 +93,7 @@ def _load() -> Command: ] -class Application(BaseApplication): # type: ignore[misc] +class Application(BaseApplication): def __init__(self) -> None: super().__init__("poetry", __version__) @@ -137,8 +137,8 @@ def poetry(self) -> Poetry: @property def command_loader(self) -> CommandLoader: - command_loader: CommandLoader | None = self._command_loader - assert command_loader is not None + command_loader = self._command_loader + assert isinstance(command_loader, CommandLoader) return command_loader def reset_poetry(self) -> None: @@ -194,7 +194,7 @@ def _configure_io(self, io: IO) -> None: # We need to check if the command being run # is the "run" command. definition = self.definition - with suppress(CleoException): + with suppress(CleoError): io.input.bind(definition) name = io.input.first_argument @@ -215,7 +215,7 @@ def _configure_io(self, io: IO) -> None: for shortcut in shortcuts: run_input.add_parameter_option("-" + shortcut.lstrip("-")) - with suppress(CleoException): + with suppress(CleoError): run_input.bind(definition) for option_name, value in input.options.items(): @@ -227,12 +227,13 @@ def _configure_io(self, io: IO) -> None: super()._configure_io(io) def register_command_loggers( - self, event: ConsoleCommandEvent, event_name: str, _: Any + self, event: Event, event_name: str, _: EventDispatcher ) -> None: from poetry.console.logging.filters import POETRY_FILTER from poetry.console.logging.io_formatter import IOFormatter from poetry.console.logging.io_handler import IOHandler + assert isinstance(event, ConsoleCommandEvent) command = event.command if not isinstance(command, Command): return @@ -277,12 +278,11 @@ def register_command_loggers( logger.setLevel(_level) - def configure_env( - self, event: ConsoleCommandEvent, event_name: str, _: Any - ) -> None: + def configure_env(self, event: Event, event_name: str, _: EventDispatcher) -> None: from poetry.console.commands.env_command import EnvCommand from poetry.console.commands.self.self_command import SelfCommand + assert isinstance(event, ConsoleCommandEvent) command = event.command if not isinstance(command, EnvCommand) or isinstance(command, SelfCommand): return @@ -305,10 +305,11 @@ def configure_env( @classmethod def configure_installer_for_event( - cls, event: ConsoleCommandEvent, event_name: str, _: Any + cls, event: Event, event_name: str, _: EventDispatcher ) -> None: from poetry.console.commands.installer_command import InstallerCommand + assert isinstance(event, ConsoleCommandEvent) command = event.command if not isinstance(command, InstallerCommand): return diff --git a/src/poetry/console/command_loader.py b/src/poetry/console/command_loader.py index a590552078b..74ae8bcd080 100644 --- a/src/poetry/console/command_loader.py +++ b/src/poetry/console/command_loader.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from cleo.exceptions import LogicException +from cleo.exceptions import CleoLogicError from cleo.loaders.factory_command_loader import FactoryCommandLoader @@ -12,11 +12,11 @@ from cleo.commands.command import Command -class CommandLoader(FactoryCommandLoader): # type: ignore[misc] +class CommandLoader(FactoryCommandLoader): def register_factory( self, command_name: str, factory: Callable[[], Command] ) -> None: if command_name in self._factories: - raise LogicException(f'The command "{command_name}" already exists.') + raise CleoLogicError(f'The command "{command_name}" already exists.') self._factories[command_name] = factory diff --git a/src/poetry/console/commands/command.py b/src/poetry/console/commands/command.py index 4bc26ad567b..2aba4e6be24 100644 --- a/src/poetry/console/commands/command.py +++ b/src/poetry/console/commands/command.py @@ -4,7 +4,7 @@ from typing import Any from cleo.commands.command import Command as BaseCommand -from cleo.exceptions import ValueException +from cleo.exceptions import CleoValueError if TYPE_CHECKING: @@ -12,7 +12,7 @@ from poetry.poetry import Poetry -class Command(BaseCommand): # type: ignore[misc] +class Command(BaseCommand): loggers: list[str] = [] _poetry: Poetry | None = None @@ -28,7 +28,10 @@ def set_poetry(self, poetry: Poetry) -> None: self._poetry = poetry def get_application(self) -> Application: - application: Application = self.application + from poetry.console.application import Application + + application = self.application + assert isinstance(application, Application) return application def reset_poetry(self) -> None: @@ -37,5 +40,5 @@ def reset_poetry(self) -> None: def option(self, name: str, default: Any = None) -> Any: try: return super().option(name) - except ValueException: + except CleoValueError: return default diff --git a/src/poetry/console/commands/debug/info.py b/src/poetry/console/commands/debug/info.py index f90d8e794d3..d76c808cee9 100644 --- a/src/poetry/console/commands/debug/info.py +++ b/src/poetry/console/commands/debug/info.py @@ -22,7 +22,7 @@ def handle(self) -> int: ] ) ) - command = self.application.get("env info") + command = self.get_application().get("env info") exit_code: int = command.run(self.io) return exit_code diff --git a/src/poetry/console/commands/debug/resolve.py b/src/poetry/console/commands/debug/resolve.py index cd8bd3ed466..05cf4b15736 100644 --- a/src/poetry/console/commands/debug/resolve.py +++ b/src/poetry/console/commands/debug/resolve.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import TYPE_CHECKING + from cleo.helpers import argument from cleo.helpers import option from cleo.io.outputs.output import Verbosity @@ -8,6 +10,10 @@ from poetry.console.commands.show import ShowCommand +if TYPE_CHECKING: + from cleo.ui.table import Rows + + class DebugResolveCommand(InitCommand): name = "debug resolve" description = "Debugs dependency resolution." @@ -86,7 +92,7 @@ def handle(self) -> int: self.line("") if self.option("tree"): - show_command = self.application.find("show") + show_command = self.get_application().find("show") assert isinstance(show_command, ShowCommand) show_command.init_styles(self.io) @@ -103,7 +109,7 @@ def handle(self) -> int: table = self.table(style="compact") table.style.set_vertical_border_chars("", " ") - rows = [] + rows: Rows = [] if self.option("install"): env = EnvManager(self.poetry).get() diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index e34fa2a7d36..329afc4d374 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -176,7 +176,7 @@ def handle(self) -> int: self._determine_requirements(self.option("dependency")) ) - question = "Would you like to define your main dependencies interactively?" + question_text = "Would you like to define your main dependencies interactively?" help_message = """\ You can specify a package in the following forms: - A single name (requests): this will search for matches on PyPI @@ -190,7 +190,7 @@ def handle(self) -> int: """ help_displayed = False - if self.confirm(question, True): + if self.confirm(question_text, True): if self.io.is_interactive(): self.line(help_message) help_displayed = True @@ -206,10 +206,10 @@ def handle(self) -> int: self._determine_requirements(self.option("dev-dependency")) ) - question = ( + question_text = ( "Would you like to define your development dependencies interactively?" ) - if self.confirm(question, True): + if self.confirm(question_text, True): if self.io.is_interactive() and not help_displayed: self.line(help_message) @@ -338,8 +338,8 @@ def _determine_requirements( "Enter the version constraint to require " "(or leave blank to use the latest version):" ) - question.attempts = 3 - question.validator = lambda x: (x or "").strip() or False + question.set_max_attempts(3) + question.set_validator(lambda x: (x or "").strip() or None) package_constraint = self.ask(question) diff --git a/src/poetry/console/commands/show.py b/src/poetry/console/commands/show.py index 5f8e4f0db0f..3c92574bc32 100644 --- a/src/poetry/console/commands/show.py +++ b/src/poetry/console/commands/show.py @@ -12,6 +12,7 @@ if TYPE_CHECKING: from cleo.io.io import IO + from cleo.ui.table import Rows from packaging.utils import NormalizedName from poetry.core.packages.dependency import Dependency from poetry.core.packages.package import Package @@ -160,7 +161,7 @@ def _display_single_package_information( return 0 - rows = [ + rows: Rows = [ ["name", f" : {pkg.pretty_name}"], ["version", f" : {pkg.pretty_version}"], ["description", f" : {pkg.description}"], diff --git a/src/poetry/console/commands/source/show.py b/src/poetry/console/commands/source/show.py index 9643118c5e0..8a89a39a55c 100644 --- a/src/poetry/console/commands/source/show.py +++ b/src/poetry/console/commands/source/show.py @@ -1,10 +1,16 @@ from __future__ import annotations +from typing import TYPE_CHECKING + from cleo.helpers import argument from poetry.console.commands.command import Command +if TYPE_CHECKING: + from cleo.ui.table import Rows + + class SourceShowCommand(Command): name = "source show" description = "Show information about sources configured for the project." @@ -40,7 +46,7 @@ def handle(self) -> int: continue table = self.table(style="compact") - rows = [ + rows: Rows = [ ["name", f" : {source.name}"], ["url", f" : {source.url}"], [ diff --git a/src/poetry/console/exceptions.py b/src/poetry/console/exceptions.py index 09fa60ad81e..aadc8c17e7f 100644 --- a/src/poetry/console/exceptions.py +++ b/src/poetry/console/exceptions.py @@ -1,7 +1,7 @@ from __future__ import annotations -from cleo.exceptions import CleoSimpleException +from cleo.exceptions import CleoError -class PoetrySimpleConsoleException(CleoSimpleException): # type: ignore[misc] +class PoetryConsoleError(CleoError): pass diff --git a/src/poetry/console/io/inputs/run_argv_input.py b/src/poetry/console/io/inputs/run_argv_input.py index b27f19cab37..964d88c2c17 100644 --- a/src/poetry/console/io/inputs/run_argv_input.py +++ b/src/poetry/console/io/inputs/run_argv_input.py @@ -9,7 +9,7 @@ from cleo.io.inputs.definition import Definition -class RunArgvInput(ArgvInput): # type: ignore[misc] +class RunArgvInput(ArgvInput): def __init__( self, argv: list[str] | None = None, diff --git a/src/poetry/mixology/solutions/providers/python_requirement_solution_provider.py b/src/poetry/mixology/solutions/providers/python_requirement_solution_provider.py index dba0d58480e..b7d6e83bed2 100644 --- a/src/poetry/mixology/solutions/providers/python_requirement_solution_provider.py +++ b/src/poetry/mixology/solutions/providers/python_requirement_solution_provider.py @@ -6,17 +6,15 @@ from crashtest.contracts.has_solutions_for_exception import HasSolutionsForException +from poetry.puzzle.exceptions import SolverProblemError + if TYPE_CHECKING: from crashtest.contracts.solution import Solution - from poetry.puzzle.exceptions import SolverProblemError - -class PythonRequirementSolutionProvider(HasSolutionsForException): # type: ignore[misc] +class PythonRequirementSolutionProvider(HasSolutionsForException): def can_solve(self, exception: Exception) -> bool: - from poetry.puzzle.exceptions import SolverProblemError - if not isinstance(exception, SolverProblemError): return False @@ -28,9 +26,10 @@ def can_solve(self, exception: Exception) -> bool: return bool(m) - def get_solutions(self, exception: SolverProblemError) -> list[Solution]: + def get_solutions(self, exception: Exception) -> list[Solution]: from poetry.mixology.solutions.solutions.python_requirement_solution import ( PythonRequirementSolution, ) + assert isinstance(exception, SolverProblemError) return [PythonRequirementSolution(exception)] diff --git a/src/poetry/mixology/solutions/solutions/python_requirement_solution.py b/src/poetry/mixology/solutions/solutions/python_requirement_solution.py index 54e6c819107..b625e124610 100644 --- a/src/poetry/mixology/solutions/solutions/python_requirement_solution.py +++ b/src/poetry/mixology/solutions/solutions/python_requirement_solution.py @@ -10,7 +10,7 @@ from poetry.puzzle.exceptions import SolverProblemError -class PythonRequirementSolution(Solution): # type: ignore[misc] +class PythonRequirementSolution(Solution): def __init__(self, exception: SolverProblemError) -> None: from poetry.core.constraints.version import parse_constraint diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index 4d2e2d30b67..91e589b15af 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -59,7 +59,7 @@ logger = logging.getLogger(__name__) -class Indicator(ProgressIndicator): # type: ignore[misc] +class Indicator(ProgressIndicator): CONTEXT: str | None = None @staticmethod diff --git a/src/poetry/vcs/git/backend.py b/src/poetry/vcs/git/backend.py index 7b37d259690..786a9689b93 100644 --- a/src/poetry/vcs/git/backend.py +++ b/src/poetry/vcs/git/backend.py @@ -17,7 +17,7 @@ from dulwich.refs import ANNOTATED_TAG_SUFFIX from dulwich.repo import Repo -from poetry.console.exceptions import PoetrySimpleConsoleException +from poetry.console.exceptions import PoetryConsoleError from poetry.utils.authenticator import get_default_authenticator from poetry.utils.helpers import remove_directory @@ -223,7 +223,7 @@ def _clone_legacy(url: str, refspec: GitRefSpec, target: Path) -> Repo: try: SystemGit.clone(url, target) except CalledProcessError: - raise PoetrySimpleConsoleException( + raise PoetryConsoleError( f"Failed to clone {url}, check your git configuration and permissions" " for this repository." ) @@ -235,9 +235,7 @@ def _clone_legacy(url: str, refspec: GitRefSpec, target: Path) -> Repo: try: SystemGit.checkout(revision, target) except CalledProcessError: - raise PoetrySimpleConsoleException( - f"Failed to checkout {url} at '{revision}'" - ) + raise PoetryConsoleError(f"Failed to checkout {url} at '{revision}'") repo = Repo(str(target)) return repo @@ -264,7 +262,7 @@ def _clone(cls, url: str, refspec: GitRefSpec, target: Path) -> Repo: try: refspec.resolve(remote_refs=remote_refs) except KeyError: # branch / ref does not exist - raise PoetrySimpleConsoleException( + raise PoetryConsoleError( f"Failed to clone {url} at '{refspec.key}', verify ref exists on" " remote." ) @@ -313,7 +311,7 @@ def _clone(cls, url: str, refspec: GitRefSpec, target: Path) -> Repo: e, ) - raise PoetrySimpleConsoleException( + raise PoetryConsoleError( f"Failed to clone {url} at '{refspec.key}', verify ref exists on" " remote." ) diff --git a/tests/integration/test_utils_vcs_git.py b/tests/integration/test_utils_vcs_git.py index 1ca7ea0c843..5c399d5311b 100644 --- a/tests/integration/test_utils_vcs_git.py +++ b/tests/integration/test_utils_vcs_git.py @@ -16,7 +16,7 @@ from dulwich.repo import Repo from poetry.core.pyproject.toml import PyProjectTOML -from poetry.console.exceptions import PoetrySimpleConsoleException +from poetry.console.exceptions import PoetryConsoleError from poetry.utils.authenticator import Authenticator from poetry.vcs.git import Git from poetry.vcs.git.backend import GitRefSpec @@ -146,7 +146,7 @@ def test_git_clone_default_branch_head( def test_git_clone_fails_for_non_existent_branch(source_url: str): branch = uuid.uuid4().hex - with pytest.raises(PoetrySimpleConsoleException) as e: + with pytest.raises(PoetryConsoleError) as e: Git.clone(url=source_url, branch=branch) assert f"Failed to clone {source_url} at '{branch}'" in str(e.value) @@ -155,7 +155,7 @@ def test_git_clone_fails_for_non_existent_branch(source_url: str): def test_git_clone_fails_for_non_existent_revision(source_url: str): revision = sha1(uuid.uuid4().bytes).hexdigest() - with pytest.raises(PoetrySimpleConsoleException) as e: + with pytest.raises(PoetryConsoleError) as e: Git.clone(url=source_url, revision=revision) assert f"Failed to clone {source_url} at '{revision}'" in str(e.value) From 78b3b551deaf29823553c9163bdadf7a2d93b546 Mon Sep 17 00:00:00 2001 From: finswimmer Date: Sun, 6 Nov 2022 17:08:12 +0100 Subject: [PATCH 010/151] refactor: IO for EnvManager is now set on init --- src/poetry/console/application.py | 4 +- src/poetry/console/commands/env/use.py | 6 +-- src/poetry/utils/env.py | 51 +++++++++++++++----------- tests/utils/test_env.py | 41 ++++++++++----------- 4 files changed, 53 insertions(+), 49 deletions(-) diff --git a/src/poetry/console/application.py b/src/poetry/console/application.py index 7c60f99836d..2dd422573fa 100644 --- a/src/poetry/console/application.py +++ b/src/poetry/console/application.py @@ -295,8 +295,8 @@ def configure_env(self, event: Event, event_name: str, _: EventDispatcher) -> No io = event.io poetry = command.poetry - env_manager = EnvManager(poetry) - env = env_manager.create_venv(io) + env_manager = EnvManager(poetry, io=io) + env = env_manager.create_venv() if env.is_venv() and io.is_verbose(): io.write_line(f"Using virtualenv: {env.path}") diff --git a/src/poetry/console/commands/env/use.py b/src/poetry/console/commands/env/use.py index cdfc8cbe554..c48312d9e4c 100644 --- a/src/poetry/console/commands/env/use.py +++ b/src/poetry/console/commands/env/use.py @@ -14,14 +14,14 @@ class EnvUseCommand(Command): def handle(self) -> int: from poetry.utils.env import EnvManager - manager = EnvManager(self.poetry) + manager = EnvManager(self.poetry, io=self.io) if self.argument("python") == "system": - manager.deactivate(self.io) + manager.deactivate() return 0 - env = manager.activate(self.argument("python"), self.io) + env = manager.activate(self.argument("python")) self.line(f"Using virtualenv: {env.path}") diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 4c60cf9e46c..2d0c6469413 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -25,6 +25,7 @@ import tomlkit import virtualenv +from cleo.io.null_io import NullIO from cleo.io.outputs.output import Verbosity from packaging.tags import Tag from packaging.tags import interpreter_name @@ -515,8 +516,9 @@ class EnvManager: ENVS_FILE = "envs.toml" - def __init__(self, poetry: Poetry) -> None: + def __init__(self, poetry: Poetry, io: None | IO = None) -> None: self._poetry = poetry + self._io = io or NullIO() def _full_python_path(self, python: str) -> str: try: @@ -533,26 +535,28 @@ def _full_python_path(self, python: str) -> str: return executable - def _detect_active_python(self, io: IO) -> str | None: + def _detect_active_python(self) -> str | None: executable = None try: - io.write_error_line( + self._io.write_error_line( "Trying to detect current active python executable as specified in the" " config.", verbosity=Verbosity.VERBOSE, ) executable = self._full_python_path("python") - io.write_error_line(f"Found: {executable}", verbosity=Verbosity.VERBOSE) + self._io.write_error_line( + f"Found: {executable}", verbosity=Verbosity.VERBOSE + ) except CalledProcessError: - io.write_error_line( + self._io.write_error_line( "Unable to detect the current active python executable. Falling back to" " default.", verbosity=Verbosity.VERBOSE, ) return executable - def activate(self, python: str, io: IO) -> Env: + def activate(self, python: str) -> Env: venv_path = self._poetry.config.virtualenvs_path cwd = self._poetry.file.parent @@ -598,7 +602,7 @@ def activate(self, python: str, io: IO) -> Env: if patch != current_patch: create = True - self.create_venv(io, executable=python, force=create) + self.create_venv(executable=python, force=create) return self.get(reload=True) @@ -632,7 +636,7 @@ def activate(self, python: str, io: IO) -> Env: if patch != current_patch: create = True - self.create_venv(io, executable=python, force=create) + self.create_venv(executable=python, force=create) # Activate envs[base_env_name] = {"minor": minor, "patch": patch} @@ -640,7 +644,7 @@ def activate(self, python: str, io: IO) -> Env: return self.get(reload=True) - def deactivate(self, io: IO) -> None: + def deactivate(self) -> None: venv_path = self._poetry.config.virtualenvs_path name = self.generate_env_name( self._poetry.package.name, str(self._poetry.file.parent) @@ -652,7 +656,7 @@ def deactivate(self, io: IO) -> None: env = envs.get(name) if env is not None: venv = venv_path / f"{name}-py{env['minor']}" - io.write_error_line( + self._io.write_error_line( f"Deactivating virtualenv: {venv}" ) del envs[name] @@ -855,7 +859,6 @@ def remove(self, python: str) -> Env: def create_venv( self, - io: IO, name: str | None = None, executable: str | None = None, force: bool = False, @@ -888,7 +891,7 @@ def create_venv( venv_prompt = self._poetry.config.get("virtualenvs.prompt") if not executable and prefer_active_python: - executable = self._detect_active_python(io) + executable = self._detect_active_python() venv_path = cwd / ".venv" if root_venv else self._poetry.config.virtualenvs_path if not name: @@ -921,7 +924,7 @@ def create_venv( self._poetry.package.python_versions, python_patch ) - io.write_error_line( + self._io.write_error_line( f"The currently activated Python version {python_patch} is not" f" supported by the project ({self._poetry.package.python_versions}).\n" "Trying to find and use a compatible version. " @@ -944,8 +947,8 @@ def create_venv( python = "python" + python_to_try - if io.is_debug(): - io.write_error_line(f"Trying {python}") + if self._io.is_debug(): + self._io.write_error_line(f"Trying {python}") try: python_patch = decode( @@ -964,7 +967,9 @@ def create_venv( continue if supported_python.allows(Version.parse(python_patch)): - io.write_error_line(f"Using {python} ({python_patch})") + self._io.write_error_line( + f"Using {python} ({python_patch})" + ) executable = python python_minor = ".".join(python_patch.split(".")[:2]) break @@ -989,7 +994,7 @@ def create_venv( if not venv.exists(): if create_venv is False: - io.write_error_line( + self._io.write_error_line( "" "Skipping virtualenv creation, " "as specified in config file." @@ -998,7 +1003,7 @@ def create_venv( return self.get_system_env() - io.write_error_line( + self._io.write_error_line( f"Creating virtualenv {name} in" f" {venv_path if not WINDOWS else get_real_windows_path(venv_path)!s}" ) @@ -1006,15 +1011,17 @@ def create_venv( create_venv = False if force: if not env.is_sane(): - io.write_error_line( + self._io.write_error_line( f"The virtual environment found in {env.path} seems to" " be broken." ) - io.write_error_line(f"Recreating virtualenv {name} in {venv!s}") + self._io.write_error_line( + f"Recreating virtualenv {name} in {venv!s}" + ) self.remove_venv(venv) create_venv = True - elif io.is_very_verbose(): - io.write_error_line(f"Virtualenv {name} already exists.") + elif self._io.is_very_verbose(): + self._io.write_error_line(f"Virtualenv {name} already exists.") if create_venv: self.build_venv( diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index dcd379302e8..8df2abb175f 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -11,7 +11,6 @@ import pytest import tomlkit -from cleo.io.null_io import NullIO from poetry.core.constraints.version import Version from poetry.core.toml.file import TOMLFile @@ -226,7 +225,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( ) m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv) - env = manager.activate("python3.7", NullIO()) + env = manager.activate("python3.7") m.assert_called_with( Path(tmp_dir) / f"{venv_name}-py3.7", @@ -275,7 +274,7 @@ def test_activate_activates_existing_virtualenv_no_envs_file( ) m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv) - env = manager.activate("python3.7", NullIO()) + env = manager.activate("python3.7") m.assert_not_called() @@ -319,7 +318,7 @@ def test_activate_activates_same_virtualenv_with_envs_file( ) m = mocker.patch("poetry.utils.env.EnvManager.create_venv") - env = manager.activate("python3.7", NullIO()) + env = manager.activate("python3.7") m.assert_not_called() @@ -362,7 +361,7 @@ def test_activate_activates_different_virtualenv_with_envs_file( ) m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv) - env = manager.activate("python3.6", NullIO()) + env = manager.activate("python3.6") m.assert_called_with( Path(tmp_dir) / f"{venv_name}-py3.6", @@ -426,7 +425,7 @@ def test_activate_activates_recreates_for_different_patch( "poetry.utils.env.EnvManager.remove_venv", side_effect=EnvManager.remove_venv ) - env = manager.activate("python3.7", NullIO()) + env = manager.activate("python3.7") build_venv_m.assert_called_with( Path(tmp_dir) / f"{venv_name}-py3.7", @@ -487,7 +486,7 @@ def test_activate_does_not_recreate_when_switching_minor( "poetry.utils.env.EnvManager.remove_venv", side_effect=EnvManager.remove_venv ) - env = manager.activate("python3.6", NullIO()) + env = manager.activate("python3.6") build_venv_m.assert_not_called() remove_venv_m.assert_not_called() @@ -523,7 +522,7 @@ def test_deactivate_non_activated_but_existing( side_effect=check_output_wrapper(), ) - manager.deactivate(NullIO()) + manager.deactivate() env = manager.get() assert env.path == Path(tmp_dir) / f"{venv_name}-py{python}" @@ -563,7 +562,7 @@ def test_deactivate_activated( side_effect=check_output_wrapper(), ) - manager.deactivate(NullIO()) + manager.deactivate() env = manager.get() assert env.path == Path(tmp_dir) / f"{venv_name}-py{version.major}.{version.minor}" @@ -999,7 +998,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ "poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: "" ) - manager.create_venv(NullIO()) + manager.create_venv() m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py3.7", @@ -1033,7 +1032,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific "poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: "" ) - manager.create_venv(NullIO()) + manager.create_venv() m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py3.9", @@ -1062,7 +1061,7 @@ def test_create_venv_fails_if_no_compatible_python_version_could_be_found( ) with pytest.raises(NoCompatiblePythonVersionFound) as e: - manager.create_venv(NullIO()) + manager.create_venv() expected_message = ( "Poetry was unable to find a compatible version. " @@ -1088,7 +1087,7 @@ def test_create_venv_does_not_try_to_find_compatible_versions_with_executable( ) with pytest.raises(NoCompatiblePythonVersionFound) as e: - manager.create_venv(NullIO(), executable="3.8") + manager.create_venv(executable="3.8") expected_message = ( "The specified Python version (3.8.0) is not supported by the project (^4.8).\n" @@ -1125,7 +1124,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility( "poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: "" ) - manager.create_venv(NullIO()) + manager.create_venv() assert not check_output.called m.assert_called_with( @@ -1165,9 +1164,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable( "poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: "" ) - manager.create_venv( - NullIO(), executable=f"python{version.major}.{version.minor - 1}" - ) + manager.create_venv(executable=f"python{version.major}.{version.minor - 1}") assert check_output.called m.assert_called_with( @@ -1189,7 +1186,7 @@ def test_create_venv_fails_if_current_python_version_is_not_supported( if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] - manager.create_venv(NullIO()) + manager.create_venv() current_version = Version.parse(".".join(str(c) for c in sys.version_info[:3])) next_version = ".".join( @@ -1199,7 +1196,7 @@ def test_create_venv_fails_if_current_python_version_is_not_supported( poetry.package.python_versions = package_version with pytest.raises(InvalidCurrentPythonVersionError) as e: - manager.create_venv(NullIO()) + manager.create_venv() expected_message = ( f"Current Python version ({current_version}) is not allowed by the project" @@ -1239,7 +1236,7 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( ) m = mocker.patch("poetry.utils.env.EnvManager.build_venv") - manager.activate("python3.7", NullIO()) + manager.activate("python3.7") m.assert_called_with( poetry.file.parent / ".venv", @@ -1472,7 +1469,7 @@ def test_create_venv_accepts_fallback_version_w_nonzero_patchlevel( "poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: "" ) - manager.create_venv(NullIO()) + manager.create_venv() assert check_output.called m.assert_called_with( @@ -1581,7 +1578,7 @@ def test_create_venv_project_name_empty_sets_correct_prompt( "poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: "" ) - manager.create_venv(NullIO()) + manager.create_venv() m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py3.7", From d02c71cf0262b4741852a9ca0333db1021ac4b9f Mon Sep 17 00:00:00 2001 From: finswimmer Date: Sun, 6 Nov 2022 18:52:50 +0100 Subject: [PATCH 011/151] fix: take `virtualenvs.prefer-active-python` into account when getting current environment --- src/poetry/utils/env.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 2d0c6469413..e521ea0c526 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -20,6 +20,7 @@ from subprocess import CalledProcessError from typing import TYPE_CHECKING from typing import Any +from typing import cast import packaging.tags import tomlkit @@ -556,6 +557,26 @@ def _detect_active_python(self) -> str | None: ) return executable + def _get_python_version(self) -> tuple[int, int, int]: + version_info = tuple(sys.version_info[:3]) + + if self._poetry.config.get("virtualenvs.prefer-active-python"): + executable = self._detect_active_python() + + if executable: + python_patch = decode( + subprocess.check_output( + list_to_shell_command( + [executable, "-c", GET_PYTHON_VERSION_ONELINER] + ), + shell=True, + ).strip() + ) + + version_info = tuple(int(v) for v in python_patch.split(".")[:3]) + + return cast("tuple[int, int, int]", version_info) + def activate(self, python: str) -> Env: venv_path = self._poetry.config.virtualenvs_path cwd = self._poetry.file.parent @@ -667,7 +688,7 @@ def get(self, reload: bool = False) -> Env: if self._env is not None and not reload: return self._env - python_minor = ".".join([str(v) for v in sys.version_info[:2]]) + python_minor = ".".join([str(v) for v in self._get_python_version()[:2]]) venv_path = self._poetry.config.virtualenvs_path From a2683800c481d25411bf18ce73fe802ddbc3f18a Mon Sep 17 00:00:00 2001 From: David Hotham Date: Tue, 22 Nov 2022 18:00:05 +0000 Subject: [PATCH 012/151] poetry-core 1.4.0 --- poetry.lock | 8 ++++---- pyproject.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8de70938b6c..91636f00777 100644 --- a/poetry.lock +++ b/poetry.lock @@ -514,7 +514,7 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "poetry-core" -version = "1.3.2" +version = "1.4.0" description = "Poetry PEP 517 Build Backend" category = "main" optional = false @@ -955,7 +955,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "c2607df5992f1075a9a0d1a067e8e935a32e65dac79f0a2445e0c619b08883c7" +content-hash = "728bc093f740faad6db2b3a1c7a61ea05ca6c6a998b1f144f071cdea5a3b2771" [metadata.files] attrs = [ @@ -1370,8 +1370,8 @@ pluggy = [ {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] poetry-core = [ - {file = "poetry-core-1.3.2.tar.gz", hash = "sha256:0ab006a40cb38d6a38b97264f6835da2f08a96912f2728ce668e9ac6a34f686f"}, - {file = "poetry_core-1.3.2-py3-none-any.whl", hash = "sha256:ea0f5a90b339cde132b4e43cff78a1b440cd928db864bb67cfc97fdfcefe7218"}, + {file = "poetry_core-1.4.0-py3-none-any.whl", hash = "sha256:5559ab80384ac021db329ef317086417e140ee1176bcfcb3a3838b544e213c8e"}, + {file = "poetry_core-1.4.0.tar.gz", hash = "sha256:514bd33c30e0bf56b0ed44ee15e120d7e47b61ad908b2b1011da68c48a84ada9"}, ] poetry-plugin-export = [ {file = "poetry_plugin_export-1.2.0-py3-none-any.whl", hash = "sha256:109fb43ebfd0e79d8be2e7f9d43ba2ae357c4975a18dfc0cfdd9597dd086790e"}, diff --git a/pyproject.toml b/pyproject.toml index d0257a59230..a34a609aacd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ generate-setup-file = false [tool.poetry.dependencies] python = "^3.7" -poetry-core = "^1.3.2" +poetry-core = "^1.4.0" poetry-plugin-export = "^1.2.0" "backports.cached-property" = { version = "^1.0.2", python = "<3.8" } cachecontrol = { version = "^0.12.9", extras = ["filecache"] } @@ -70,7 +70,7 @@ shellingham = "^1.5" tomli = { version = "^2.0.1", python = "<3.11" } # exclude 0.11.2 and 0.11.3 due to https://github.com/sdispater/tomlkit/issues/225 tomlkit = ">=0.11.1,<1.0.0,!=0.11.2,!=0.11.3" -trove-classifiers = "^2022.5.19" +trove-classifiers = ">=2022.5.19" # exclude 20.4.5 - 20.4.6 due to https://github.com/pypa/pip/issues/9953 virtualenv = "^20.4.3,!=20.4.5,!=20.4.6" xattr = { version = "^0.10.0", markers = "sys_platform == 'darwin'" } From dcd48c8df6d22246c21c0243fd387e3a9b189f93 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sat, 8 Oct 2022 19:22:06 +0100 Subject: [PATCH 013/151] Simplify analysis of locked packages --- src/poetry/puzzle/provider.py | 12 +--- tests/installation/test_installer.py | 104 +++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 11 deletions(-) diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index 91e589b15af..0effa9598a8 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -853,17 +853,7 @@ def get_locked(self, dependency: Dependency) -> DependencyPackage | None: locked = self._locked.get(dependency.name, []) for dependency_package in locked: package = dependency_package.package - if ( - # Locked dependencies are always without features. - # Thus, we can't use is_same_package_as() here because it compares - # the complete_name (including features). - dependency.name == package.name - and ( - dependency.source_type is None - or dependency.is_same_source_as(package) - ) - and dependency.constraint.allows(package.version) - ): + if package.satisfies(dependency): return DependencyPackage(dependency, package) return None diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 85cc2caed82..70761e48ebf 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -2555,3 +2555,107 @@ def test_installer_should_use_the_locked_version_of_git_dependencies_without_ref source_reference="HEAD", source_resolved_reference=expected_reference, ) + + +# https://github.com/python-poetry/poetry/issues/6710 +@pytest.mark.parametrize("env_platform", ["darwin", "linux"]) +def test_installer_distinguishes_locked_packages_by_source( + pool: RepositoryPool, + locker: Locker, + installed: CustomInstalledRepository, + config: Config, + repo: Repository, + package: ProjectPackage, + env_platform: str, +): + # Require 1.11.0+cpu from pytorch for most platforms, but specify 1.11.0 and pypi on + # darwin. + package.add_dependency( + Factory.create_dependency( + "torch", + { + "version": "1.11.0+cpu", + "markers": "sys_platform != 'darwin'", + "source": "pytorch", + }, + ) + ) + package.add_dependency( + Factory.create_dependency( + "torch", + { + "version": "1.11.0", + "markers": "sys_platform == 'darwin'", + "source": "pypi", + }, + ) + ) + + # Locking finds both the pypi and the pytorch packages. + locker.locked(True) + locker.mock_lock_data( + { + "package": [ + { + "name": "torch", + "version": "1.11.0", + "category": "main", + "optional": False, + "files": [], + "python-versions": "*", + }, + { + "name": "torch", + "version": "1.11.0+cpu", + "category": "main", + "optional": False, + "files": [], + "python-versions": "*", + "source": { + "type": "legacy", + "url": "https://download.pytorch.org/whl", + "reference": "pytorch", + }, + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + }, + } + ) + installer = Installer( + NullIO(), + MockEnv(platform=env_platform), + package, + locker, + pool, + config, + installed=installed, + executor=Executor( + MockEnv(platform=env_platform), + pool, + config, + NullIO(), + ), + ) + installer.use_executor(True) + installer.run() + + # Results of installation are consistent with the platform requirements. + version = "1.11.0" if env_platform == "darwin" else "1.11.0+cpu" + source_type = None if env_platform == "darwin" else "legacy" + source_url = ( + None if env_platform == "darwin" else "https://download.pytorch.org/whl" + ) + source_reference = None if env_platform == "darwin" else "pytorch" + + assert len(installer.executor.installations) == 1 + assert installer.executor.installations[0] == Package( + "torch", + version, + source_type=source_type, + source_url=source_url, + source_reference=source_reference, + ) From e9730ddcb467c12656eca0b5d3ed965c53e6816c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Fri, 25 Nov 2022 17:13:21 +0100 Subject: [PATCH 014/151] refactor(provider): extract handling of any marker dependencies into separate method --- src/poetry/puzzle/provider.py | 103 ++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 49 deletions(-) diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index 0effa9598a8..ccc2394f754 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -740,55 +740,7 @@ def fmt_warning(d: Dependency) -> str: f"Different requirements found for {warnings}." ) - # We need to check if one of the duplicate dependencies - # has no markers. If there is one, we need to change its - # environment markers to the inverse of the union of the - # other dependencies markers. - # For instance, if we have the following dependencies: - # - ipython - # - ipython (1.2.4) ; implementation_name == "pypy" - # - # the marker for `ipython` will become `implementation_name != "pypy"`. - # - # Further, we have to merge the constraints of the requirements - # without markers into the constraints of the requirements with markers. - # for instance, if we have the following dependencies: - # - foo (>= 1.2) - # - foo (!= 1.2.1) ; python == 3.10 - # - # the constraint for the second entry will become (!= 1.2.1, >= 1.2) - any_markers_dependencies = [d for d in deps if d.marker.is_any()] - other_markers_dependencies = [d for d in deps if not d.marker.is_any()] - - marker = other_markers_dependencies[0].marker - for other_dep in other_markers_dependencies[1:]: - marker = marker.union(other_dep.marker) - inverted_marker = marker.invert() - - if any_markers_dependencies: - for dep_any in any_markers_dependencies: - dep_any.marker = inverted_marker - for dep_other in other_markers_dependencies: - dep_other.constraint = dep_other.constraint.intersect( - dep_any.constraint - ) - elif not inverted_marker.is_empty() and self._python_constraint.allows_any( - get_python_constraint_from_marker(inverted_marker) - ): - # if there is no any marker dependency - # and the inverted marker is not empty, - # a dependency with the inverted union of all markers is required - # in order to not miss other dependencies later, for instance: - # - foo (1.0) ; python == 3.7 - # - foo (2.0) ; python == 3.8 - # - bar (2.0) ; python == 3.8 - # - bar (3.0) ; python == 3.9 - # - # the last dependency would be missed without this, - # because the intersection with both foo dependencies is empty - inverted_marker_dep = deps[0].with_constraint(EmptyConstraint()) - inverted_marker_dep.marker = inverted_marker - deps.append(inverted_marker_dep) + self._handle_any_marker_dependencies(deps) overrides = [] overrides_marker_intersection: BaseMarker = AnyMarker() @@ -1021,3 +973,56 @@ def _merge_dependencies_by_marker( ) deps.append(_deps[0].with_constraint(new_constraint)) return deps + + def _handle_any_marker_dependencies(self, dependencies: list[Dependency]) -> None: + """ + We need to check if one of the duplicate dependencies + has no markers. If there is one, we need to change its + environment markers to the inverse of the union of the + other dependencies markers. + For instance, if we have the following dependencies: + - ipython + - ipython (1.2.4) ; implementation_name == "pypy" + + the marker for `ipython` will become `implementation_name != "pypy"`. + + Further, we have to merge the constraints of the requirements + without markers into the constraints of the requirements with markers. + for instance, if we have the following dependencies: + - foo (>= 1.2) + - foo (!= 1.2.1) ; python == 3.10 + + the constraint for the second entry will become (!= 1.2.1, >= 1.2). + """ + any_markers_dependencies = [d for d in dependencies if d.marker.is_any()] + other_markers_dependencies = [d for d in dependencies if not d.marker.is_any()] + + marker = other_markers_dependencies[0].marker + for other_dep in other_markers_dependencies[1:]: + marker = marker.union(other_dep.marker) + inverted_marker = marker.invert() + + if any_markers_dependencies: + for dep_any in any_markers_dependencies: + dep_any.marker = inverted_marker + for dep_other in other_markers_dependencies: + dep_other.constraint = dep_other.constraint.intersect( + dep_any.constraint + ) + elif not inverted_marker.is_empty() and self._python_constraint.allows_any( + get_python_constraint_from_marker(inverted_marker) + ): + # if there is no any marker dependency + # and the inverted marker is not empty, + # a dependency with the inverted union of all markers is required + # in order to not miss other dependencies later, for instance: + # - foo (1.0) ; python == 3.7 + # - foo (2.0) ; python == 3.8 + # - bar (2.0) ; python == 3.8 + # - bar (3.0) ; python == 3.9 + # + # the last dependency would be missed without this, + # because the intersection with both foo dependencies is empty + inverted_marker_dep = dependencies[0].with_constraint(EmptyConstraint()) + inverted_marker_dep.marker = inverted_marker + dependencies.append(inverted_marker_dep) From 28ddcd8b060d97623b3a47dc3400d05597c269a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Fri, 25 Nov 2022 17:02:45 +0100 Subject: [PATCH 015/151] provider: discard any marker dependencies if the resulting marker after merging is empty, not compatible with the project's python constraint or not compatible with the set environment --- src/poetry/puzzle/provider.py | 60 +++++++++------ tests/puzzle/test_solver.py | 137 ++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 24 deletions(-) diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index ccc2394f754..7c87f7401a1 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -740,7 +740,7 @@ def fmt_warning(d: Dependency) -> str: f"Different requirements found for {warnings}." ) - self._handle_any_marker_dependencies(deps) + deps = self._handle_any_marker_dependencies(deps) overrides = [] overrides_marker_intersection: BaseMarker = AnyMarker() @@ -974,7 +974,9 @@ def _merge_dependencies_by_marker( deps.append(_deps[0].with_constraint(new_constraint)) return deps - def _handle_any_marker_dependencies(self, dependencies: list[Dependency]) -> None: + def _handle_any_marker_dependencies( + self, dependencies: list[Dependency] + ) -> list[Dependency]: """ We need to check if one of the duplicate dependencies has no markers. If there is one, we need to change its @@ -997,32 +999,42 @@ def _handle_any_marker_dependencies(self, dependencies: list[Dependency]) -> Non any_markers_dependencies = [d for d in dependencies if d.marker.is_any()] other_markers_dependencies = [d for d in dependencies if not d.marker.is_any()] + for dep_any in any_markers_dependencies: + for dep_other in other_markers_dependencies: + dep_other.constraint = dep_other.constraint.intersect( + dep_any.constraint + ) + marker = other_markers_dependencies[0].marker for other_dep in other_markers_dependencies[1:]: marker = marker.union(other_dep.marker) inverted_marker = marker.invert() - if any_markers_dependencies: - for dep_any in any_markers_dependencies: - dep_any.marker = inverted_marker - for dep_other in other_markers_dependencies: - dep_other.constraint = dep_other.constraint.intersect( - dep_any.constraint - ) - elif not inverted_marker.is_empty() and self._python_constraint.allows_any( + if ( + not inverted_marker.is_empty() + and self._python_constraint.allows_any( get_python_constraint_from_marker(inverted_marker) + ) + and (not self._env or inverted_marker.validate(self._env.marker_env)) ): - # if there is no any marker dependency - # and the inverted marker is not empty, - # a dependency with the inverted union of all markers is required - # in order to not miss other dependencies later, for instance: - # - foo (1.0) ; python == 3.7 - # - foo (2.0) ; python == 3.8 - # - bar (2.0) ; python == 3.8 - # - bar (3.0) ; python == 3.9 - # - # the last dependency would be missed without this, - # because the intersection with both foo dependencies is empty - inverted_marker_dep = dependencies[0].with_constraint(EmptyConstraint()) - inverted_marker_dep.marker = inverted_marker - dependencies.append(inverted_marker_dep) + if any_markers_dependencies: + for dep_any in any_markers_dependencies: + dep_any.marker = inverted_marker + else: + # If there is no any marker dependency + # and the inverted marker is not empty, + # a dependency with the inverted union of all markers is required + # in order to not miss other dependencies later, for instance: + # - foo (1.0) ; python == 3.7 + # - foo (2.0) ; python == 3.8 + # - bar (2.0) ; python == 3.8 + # - bar (3.0) ; python == 3.9 + # + # the last dependency would be missed without this, + # because the intersection with both foo dependencies is empty. + inverted_marker_dep = dependencies[0].with_constraint(EmptyConstraint()) + inverted_marker_dep.marker = inverted_marker + dependencies.append(inverted_marker_dep) + else: + dependencies = other_markers_dependencies + return dependencies diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index d55ea5b2915..7d05ffae884 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -1480,6 +1480,143 @@ def test_solver_duplicate_dependencies_different_constraints_merge_no_markers( ) +def test_solver_duplicate_dependencies_different_constraints_discard_no_markers1( + solver: Solver, repo: Repository, package: ProjectPackage +) -> None: + """ + Initial dependencies: + A (>=1.0) + A (<1.2) ; python >= 3.10 + A (<1.1) ; python < 3.10 + + Merged dependencies: + A (>=1.0) ; + A (>=1.0,<1.2) ; python >= 3.10 + A (>=1.0,<1.1) ; python < 3.10 + + The dependency with an empty marker has to be ignored. + """ + package.add_dependency(Factory.create_dependency("A", ">=1.0")) + package.add_dependency( + Factory.create_dependency("A", {"version": "<1.2", "python": ">=3.10"}) + ) + package.add_dependency( + Factory.create_dependency("A", {"version": "<1.1", "python": "<3.10"}) + ) + package.add_dependency(Factory.create_dependency("B", "*")) + + package_a10 = get_package("A", "1.0") + package_a11 = get_package("A", "1.1") + package_a12 = get_package("A", "1.2") + package_b = get_package("B", "1.0") + package_b.add_dependency(Factory.create_dependency("A", "*")) + + repo.add_package(package_a10) + repo.add_package(package_a11) + repo.add_package(package_a12) + repo.add_package(package_b) + + transaction = solver.solve() + + check_solver_result( + transaction, + [ + # only a10 and a11, not a12 + {"job": "install", "package": package_a10}, + {"job": "install", "package": package_a11}, + {"job": "install", "package": package_b}, + ], + ) + + +def test_solver_duplicate_dependencies_different_constraints_discard_no_markers2( + solver: Solver, repo: Repository, package: ProjectPackage +) -> None: + """ + Initial dependencies: + A (>=1.0) + A (<1.2) ; python == 3.10 + + Merged dependencies: + A (>=1.0) ; python != 3.10 + A (>=1.0,<1.2) ; python == 3.10 + + The first dependency has to be ignored + because it is not compatible with the project's python constraint. + """ + set_package_python_versions(solver.provider, "~3.10") + package.add_dependency(Factory.create_dependency("A", ">=1.0")) + package.add_dependency( + Factory.create_dependency("A", {"version": "<1.2", "python": "3.10"}) + ) + package.add_dependency(Factory.create_dependency("B", "*")) + + package_a10 = get_package("A", "1.0") + package_a11 = get_package("A", "1.1") + package_a12 = get_package("A", "1.2") + package_b = get_package("B", "1.0") + package_b.add_dependency(Factory.create_dependency("A", "*")) + + repo.add_package(package_a10) + repo.add_package(package_a11) + repo.add_package(package_a12) + repo.add_package(package_b) + + transaction = solver.solve() + + check_solver_result( + transaction, + [ + {"job": "install", "package": package_a11}, # only a11, not a12 + {"job": "install", "package": package_b}, + ], + ) + + +def test_solver_duplicate_dependencies_different_constraints_discard_no_markers3( + solver: Solver, repo: Repository, package: ProjectPackage +) -> None: + """ + Initial dependencies: + A (>=1.0) + A (<1.2) ; python == 3.10 + + Merged dependencies: + A (>=1.0) ; python != 3.10 + A (>=1.0,<1.2) ; python == 3.10 + + The first dependency has to be ignored + because it is not compatible with the current environment. + """ + package.add_dependency(Factory.create_dependency("A", ">=1.0")) + package.add_dependency( + Factory.create_dependency("A", {"version": "<1.2", "python": "3.10"}) + ) + package.add_dependency(Factory.create_dependency("B", "*")) + + package_a10 = get_package("A", "1.0") + package_a11 = get_package("A", "1.1") + package_a12 = get_package("A", "1.2") + package_b = get_package("B", "1.0") + package_b.add_dependency(Factory.create_dependency("A", "*")) + + repo.add_package(package_a10) + repo.add_package(package_a11) + repo.add_package(package_a12) + repo.add_package(package_b) + + with solver.use_environment(MockEnv((3, 10, 0))): + transaction = solver.solve() + + check_solver_result( + transaction, + [ + {"job": "install", "package": package_a11}, # only a11, not a12 + {"job": "install", "package": package_b}, + ], + ) + + def test_solver_duplicate_dependencies_ignore_overrides_with_empty_marker_intersection( solver: Solver, repo: Repository, package: ProjectPackage ): From b5ab46ed7c812486a03830589df2457e81de8e2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sat, 26 Nov 2022 16:57:01 +0100 Subject: [PATCH 016/151] provider: raise error if there are incompatible constraints in the requirements of a package --- src/poetry/puzzle/provider.py | 29 +++++++++++++++++++++++------ tests/puzzle/test_solver.py | 24 ++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index 7c87f7401a1..9c7457f7e7c 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -59,6 +59,18 @@ logger = logging.getLogger(__name__) +class IncompatibleConstraintsError(Exception): + """ + Exception when there are duplicate dependencies with incompatible constraints. + """ + + def __init__(self, package: Package, *dependencies: Dependency) -> None: + constraints = "\n".join(dep.to_pep_508() for dep in dependencies) + super().__init__( + f"Incompatible constraints in requirements of {package}:\n{constraints}" + ) + + class Indicator(ProgressIndicator): CONTEXT: str | None = None @@ -740,7 +752,7 @@ def fmt_warning(d: Dependency) -> str: f"Different requirements found for {warnings}." ) - deps = self._handle_any_marker_dependencies(deps) + deps = self._handle_any_marker_dependencies(package, deps) overrides = [] overrides_marker_intersection: BaseMarker = AnyMarker() @@ -975,7 +987,7 @@ def _merge_dependencies_by_marker( return deps def _handle_any_marker_dependencies( - self, dependencies: list[Dependency] + self, package: Package, dependencies: list[Dependency] ) -> list[Dependency]: """ We need to check if one of the duplicate dependencies @@ -999,11 +1011,16 @@ def _handle_any_marker_dependencies( any_markers_dependencies = [d for d in dependencies if d.marker.is_any()] other_markers_dependencies = [d for d in dependencies if not d.marker.is_any()] - for dep_any in any_markers_dependencies: + if any_markers_dependencies: for dep_other in other_markers_dependencies: - dep_other.constraint = dep_other.constraint.intersect( - dep_any.constraint - ) + new_constraint = dep_other.constraint + for dep_any in any_markers_dependencies: + new_constraint = new_constraint.intersect(dep_any.constraint) + if new_constraint.is_empty(): + raise IncompatibleConstraintsError( + package, dep_other, *any_markers_dependencies + ) + dep_other.constraint = new_constraint marker = other_markers_dependencies[0].marker for other_dep in other_markers_dependencies[1:]: diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 7d05ffae884..04f2dd70b35 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -1,5 +1,7 @@ from __future__ import annotations +import re + from pathlib import Path from typing import TYPE_CHECKING from typing import Any @@ -19,6 +21,7 @@ from poetry.packages import DependencyPackage from poetry.puzzle import Solver from poetry.puzzle.exceptions import SolverProblemError +from poetry.puzzle.provider import IncompatibleConstraintsError from poetry.repositories.repository import Repository from poetry.repositories.repository_pool import RepositoryPool from poetry.utils.env import MockEnv @@ -1480,6 +1483,27 @@ def test_solver_duplicate_dependencies_different_constraints_merge_no_markers( ) +def test_solver_duplicate_dependencies_different_constraints_conflict( + solver: Solver, repo: Repository, package: ProjectPackage +) -> None: + package.add_dependency(Factory.create_dependency("A", ">=1.1")) + package.add_dependency( + Factory.create_dependency("A", {"version": "<1.1", "python": "3.10"}) + ) + + repo.add_package(get_package("A", "1.0")) + repo.add_package(get_package("A", "1.1")) + repo.add_package(get_package("A", "1.2")) + + expectation = ( + "Incompatible constraints in requirements of root (1.0):\n" + 'A (<1.1) ; python_version == "3.10"\n' + "A (>=1.1)" + ) + with pytest.raises(IncompatibleConstraintsError, match=re.escape(expectation)): + solver.solve() + + def test_solver_duplicate_dependencies_different_constraints_discard_no_markers1( solver: Solver, repo: Repository, package: ProjectPackage ) -> None: From 41706e649cf3ee9b066c59920b4e10e686f8c6d5 Mon Sep 17 00:00:00 2001 From: Bjorn Neergaard Date: Wed, 2 Nov 2022 23:23:58 -0600 Subject: [PATCH 017/151] chore: re-lock Poetry's dependencies We can now lock using the new lock file format as Poetry 1.2.2 has spent sufficient time in the wild. A re-lock is also advisable as the full release of Python 3.11 is here and we want to make sure to use any new wheels in CI. --- poetry.lock | 1756 ++++++++++++++++++++++++++------------------------- 1 file changed, 893 insertions(+), 863 deletions(-) diff --git a/poetry.lock b/poetry.lock index 91636f00777..7b8db67ea57 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + [[package]] name = "attrs" version = "22.1.0" @@ -5,6 +7,10 @@ description = "Classes Without Boilerplate" category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] [package.extras] dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] @@ -13,20 +19,28 @@ tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900 tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] [[package]] -name = "backports.cached-property" +name = "backports-cached-property" version = "1.0.2" description = "cached_property() - computed once per instance, cached as attribute" category = "main" optional = false python-versions = ">=3.6.0" +files = [ + {file = "backports.cached-property-1.0.2.tar.gz", hash = "sha256:9306f9eed6ec55fd156ace6bc1094e2c86fae5fb2bf07b6a9c00745c656e75dd"}, + {file = "backports.cached_property-1.0.2-py3-none-any.whl", hash = "sha256:baeb28e1cd619a3c9ab8941431fe34e8490861fb998c6c4590693d50171db0cc"}, +] [[package]] -name = "CacheControl" +name = "cachecontrol" version = "0.12.11" description = "httplib2 caching for requests" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "CacheControl-0.12.11-py2.py3-none-any.whl", hash = "sha256:2c75d6a8938cb1933c75c50184549ad42728a27e9f6b92fd677c3151aa72555b"}, + {file = "CacheControl-0.12.11.tar.gz", hash = "sha256:a5b9fcc986b184db101aa280b42ecdcdfc524892596f606858e0b7a8b4d9e144"}, +] [package.dependencies] lockfile = {version = ">=0.9", optional = true, markers = "extra == \"filecache\""} @@ -44,6 +58,10 @@ description = "Cachy provides a simple yet effective caching library." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "cachy-0.3.0-py2.py3-none-any.whl", hash = "sha256:338ca09c8860e76b275aff52374330efedc4d5a5e45dc1c5b539c1ead0786fe7"}, + {file = "cachy-0.3.0.tar.gz", hash = "sha256:186581f4ceb42a0bbe040c407da73c14092379b1e4c0e327fdb72ae4a9b269b1"}, +] [package.extras] memcached = ["python-memcached (>=1.59,<2.0)"] @@ -57,6 +75,10 @@ description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, +] [[package]] name = "cffi" @@ -65,6 +87,72 @@ description = "Foreign Function Interface for Python calling C code." category = "main" optional = false python-versions = "*" +files = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] [package.dependencies] pycparser = "*" @@ -76,6 +164,10 @@ description = "Validate configuration and produce human readable error messages. category = "dev" optional = false python-versions = ">=3.6.1" +files = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] [[package]] name = "charset-normalizer" @@ -84,17 +176,25 @@ description = "The Real First Universal Charset Detector. Open, modern and activ category = "main" optional = false python-versions = ">=3.6.0" +files = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] [package.extras] unicode-backport = ["unicodedata2"] [[package]] name = "cleo" -version = "2.0.0" +version = "2.0.1" description = "Cleo allows you to create beautiful and testable command-line interfaces." category = "main" optional = false python-versions = ">=3.7,<4.0" +files = [ + {file = "cleo-2.0.1-py3-none-any.whl", hash = "sha256:6eb133670a3ed1f3b052d53789017b6e50fca66d1287e6e6696285f4cb8ea448"}, + {file = "cleo-2.0.1.tar.gz", hash = "sha256:eb4b2e1f3063c11085cebe489a6e9124163c226575a3c3be69b2e51af4a15ec5"}, +] [package.dependencies] crashtest = ">=0.4.1,<0.5.0" @@ -102,11 +202,15 @@ rapidfuzz = ">=2.2.0,<3.0.0" [[package]] name = "colorama" -version = "0.4.5" +version = "0.4.6" description = "Cross-platform colored terminal text." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] [[package]] name = "coverage" @@ -115,6 +219,58 @@ description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, + {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, + {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, + {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, + {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, + {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, + {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, + {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, + {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, + {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, + {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, + {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, + {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, + {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, + {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, +] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} @@ -129,14 +285,46 @@ description = "Manage Python errors with ease" category = "main" optional = false python-versions = ">=3.7,<4.0" +files = [ + {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, + {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, +] [[package]] name = "cryptography" -version = "38.0.1" +version = "38.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "cryptography-38.0.3-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:984fe150f350a3c91e84de405fe49e688aa6092b3525f407a18b9646f6612320"}, + {file = "cryptography-38.0.3-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:ed7b00096790213e09eb11c97cc6e2b757f15f3d2f85833cd2d3ec3fe37c1722"}, + {file = "cryptography-38.0.3-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:bbf203f1a814007ce24bd4d51362991d5cb90ba0c177a9c08825f2cc304d871f"}, + {file = "cryptography-38.0.3-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:554bec92ee7d1e9d10ded2f7e92a5d70c1f74ba9524947c0ba0c850c7b011828"}, + {file = "cryptography-38.0.3-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b52c9e5f8aa2b802d48bd693190341fae201ea51c7a167d69fc48b60e8a959"}, + {file = "cryptography-38.0.3-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:728f2694fa743a996d7784a6194da430f197d5c58e2f4e278612b359f455e4a2"}, + {file = "cryptography-38.0.3-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dfb4f4dd568de1b6af9f4cda334adf7d72cf5bc052516e1b2608b683375dd95c"}, + {file = "cryptography-38.0.3-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5419a127426084933076132d317911e3c6eb77568a1ce23c3ac1e12d111e61e0"}, + {file = "cryptography-38.0.3-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9b24bcff7853ed18a63cfb0c2b008936a9554af24af2fb146e16d8e1aed75748"}, + {file = "cryptography-38.0.3-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:25c1d1f19729fb09d42e06b4bf9895212292cb27bb50229f5aa64d039ab29146"}, + {file = "cryptography-38.0.3-cp36-abi3-win32.whl", hash = "sha256:7f836217000342d448e1c9a342e9163149e45d5b5eca76a30e84503a5a96cab0"}, + {file = "cryptography-38.0.3-cp36-abi3-win_amd64.whl", hash = "sha256:c46837ea467ed1efea562bbeb543994c2d1f6e800785bd5a2c98bc096f5cb220"}, + {file = "cryptography-38.0.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06fc3cc7b6f6cca87bd56ec80a580c88f1da5306f505876a71c8cfa7050257dd"}, + {file = "cryptography-38.0.3-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:65535bc550b70bd6271984d9863a37741352b4aad6fb1b3344a54e6950249b55"}, + {file = "cryptography-38.0.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5e89468fbd2fcd733b5899333bc54d0d06c80e04cd23d8c6f3e0542358c6060b"}, + {file = "cryptography-38.0.3-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6ab9516b85bebe7aa83f309bacc5f44a61eeb90d0b4ec125d2d003ce41932d36"}, + {file = "cryptography-38.0.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:068147f32fa662c81aebab95c74679b401b12b57494872886eb5c1139250ec5d"}, + {file = "cryptography-38.0.3-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:402852a0aea73833d982cabb6d0c3bb582c15483d29fb7085ef2c42bfa7e38d7"}, + {file = "cryptography-38.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b1b35d9d3a65542ed2e9d90115dfd16bbc027b3f07ee3304fc83580f26e43249"}, + {file = "cryptography-38.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6addc3b6d593cd980989261dc1cce38263c76954d758c3c94de51f1e010c9a50"}, + {file = "cryptography-38.0.3-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:be243c7e2bfcf6cc4cb350c0d5cdf15ca6383bbcb2a8ef51d3c9411a9d4386f0"}, + {file = "cryptography-38.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78cf5eefac2b52c10398a42765bfa981ce2372cbc0457e6bf9658f41ec3c41d8"}, + {file = "cryptography-38.0.3-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:4e269dcd9b102c5a3d72be3c45d8ce20377b8076a43cbed6f660a1afe365e436"}, + {file = "cryptography-38.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8d41a46251bf0634e21fac50ffd643216ccecfaf3701a063257fe0b2be1b6548"}, + {file = "cryptography-38.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:785e4056b5a8b28f05a533fab69febf5004458e20dad7e2e13a3120d8ecec75a"}, + {file = "cryptography-38.0.3.tar.gz", hash = "sha256:bfbe6ee19615b07a98b1d2287d6a6073f734735b49ee45b11324d85efc4d5cbd"}, +] [package.dependencies] cffi = ">=1.12" @@ -156,6 +344,10 @@ description = "Deep Difference and Search of any Python object/data." category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "deepdiff-5.8.1-py3-none-any.whl", hash = "sha256:e9aea49733f34fab9a0897038d8f26f9d94a97db1790f1b814cced89e9e0d2b7"}, + {file = "deepdiff-5.8.1.tar.gz", hash = "sha256:8d4eb2c4e6cbc80b811266419cb71dd95a157094a3947ccf937a94d44943c7b8"}, +] [package.dependencies] ordered-set = ">=4.1.0,<4.2.0" @@ -170,14 +362,83 @@ description = "Distribution utilities" category = "main" optional = false python-versions = "*" +files = [ + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, +] [[package]] name = "dulwich" -version = "0.20.46" +version = "0.20.50" description = "Python Git Library" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "dulwich-0.20.50-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:97f02f8d500d4af08dc022d697c56e8539171acc3f575c2fe9acf3b078e5c8c9"}, + {file = "dulwich-0.20.50-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7301773e5cc16d521bc6490e73772a86a4d1d0263de506f08b54678cc4e2f061"}, + {file = "dulwich-0.20.50-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b70106580ed11f45f4c32d2831d0c9c9f359bc2415fff4a6be443e3a36811398"}, + {file = "dulwich-0.20.50-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f9c4f2455f966cad94648278fa9972e4695b35d04f82792fa58e1ea15dd83f0"}, + {file = "dulwich-0.20.50-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9163fbb021a8ad9c35a0814a5eedf45a8eb3a0b764b865d7016d901fc5a947fc"}, + {file = "dulwich-0.20.50-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:322ff8ff6aa4d6d36294cd36de1c84767eb1903c7db3e7b4475ad091febf5363"}, + {file = "dulwich-0.20.50-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5d3290a45651c8e534f8e83ae2e30322aefdd162f0f338bae2e79a6ee5a87513"}, + {file = "dulwich-0.20.50-cp310-cp310-win32.whl", hash = "sha256:80ab07131a6e68594441f5c4767e9e44e87fceafc3e347e541c928a18c679bd8"}, + {file = "dulwich-0.20.50-cp310-cp310-win_amd64.whl", hash = "sha256:eefe786a6010f8546baac4912113eeed4e397ddb8c433a345b548a04d4176496"}, + {file = "dulwich-0.20.50-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:df3562dde3079d57287c233d45b790bc967c5aae975c9a7b07ca30e60e055512"}, + {file = "dulwich-0.20.50-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e1ae18d5805f0c0c5dac65795f8d48660437166b12ee2c0ffea95bfdbf9c1051"}, + {file = "dulwich-0.20.50-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d2f7df39bd1378d3b0bfb3e7fc930fd0191924af1f0ef587bcd9946afe076c06"}, + {file = "dulwich-0.20.50-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:731e7f319b34251fadeb362ada1d52cc932369d9cdfa25c0e41150cda28773d0"}, + {file = "dulwich-0.20.50-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4d11d44176e5d2fa8271fc86ad1e0a8731b9ad8f77df64c12846b30e16135eb"}, + {file = "dulwich-0.20.50-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7aaabb8e4beadd53f75f853a981caaadef3ef130e5645c902705704eaf136daa"}, + {file = "dulwich-0.20.50-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c3dc9f97ec8d3db08d9723b9fd06f3e52c15b84c800d153cfb59b0a3dc8b8d40"}, + {file = "dulwich-0.20.50-cp311-cp311-win32.whl", hash = "sha256:3b1964fa80cafd5a1fd71615b0313daf6f3295c6ab05656ea0c1d2423539904a"}, + {file = "dulwich-0.20.50-cp311-cp311-win_amd64.whl", hash = "sha256:a24a3893108f3b97beb958670d5f3f2a3bec73a1fe18637a572a85abd949a1c4"}, + {file = "dulwich-0.20.50-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6d409a282f8848fd6c8d7c7545ad2f75c16de5d5977de202642f1d50fdaac554"}, + {file = "dulwich-0.20.50-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5411d0f1092152e1c0bb916ae490fe181953ae1b8d13f4e68661253e10b78dbb"}, + {file = "dulwich-0.20.50-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6343569f998ce429e2a5d813c56768ac51b496522401db950f0aa44240bfa901"}, + {file = "dulwich-0.20.50-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a405cd236766060894411614a272cfb86fe86cde5ca73ef264fc4fa5a715fff4"}, + {file = "dulwich-0.20.50-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ee0f9b02019c0ea84cdd31c00a0c283669b771c85612997a911715cf84e33d99"}, + {file = "dulwich-0.20.50-cp36-cp36m-win32.whl", hash = "sha256:2644466270267270f2157ea6f1c0aa224f6f3bf06a307fc39954e6b4b3d82bae"}, + {file = "dulwich-0.20.50-cp36-cp36m-win_amd64.whl", hash = "sha256:d4629635a97e3af1b5da48071e00c8e70fad85f3266fadabe1f5a8f49172c507"}, + {file = "dulwich-0.20.50-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0e4862f318d99cc8a500e3622a89613a88c07d957a0f628cdc2ed86addff790f"}, + {file = "dulwich-0.20.50-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c96e3fb9d48c0454dc242c7accc7819780c9a7f29e441a9eff12361ed0fa35f9"}, + {file = "dulwich-0.20.50-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cc6092a4f0bbbff2e553e87a9c6325955b64ea43fca21297c8182e19ae8a43c"}, + {file = "dulwich-0.20.50-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:519b627d49d273e2fd01c79d09e578675ca6cd05193c1787e9ef165c9a1d66ea"}, + {file = "dulwich-0.20.50-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a75cab01b909c4c683c2083e060e378bc01701b7366b5a7d9846ef6d3b9e3d5"}, + {file = "dulwich-0.20.50-cp37-cp37m-win32.whl", hash = "sha256:ea8ffe26d91dbcd5580dbd5a07270a12ea57b091604d77184da0a0d9fad50ed3"}, + {file = "dulwich-0.20.50-cp37-cp37m-win_amd64.whl", hash = "sha256:8f3af857f94021cae1322d86925bfc0dd31e501e885ab5db275473bfac0bb39d"}, + {file = "dulwich-0.20.50-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3fb35cedb1243bc420d885ef5b4afd642c6ac8f07ddfc7fdbca1becf9948bf7e"}, + {file = "dulwich-0.20.50-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4bb23a9cec63e16c0e432335f068169b73dd44fa9318dd7cd7a4ca83607ff367"}, + {file = "dulwich-0.20.50-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5267619b34ddaf8d9a6b841492cd17a971fd25bf9a5657f2de928385c3a08b94"}, + {file = "dulwich-0.20.50-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9091f1d53a3c0747cbf0bd127c64e7f09b770264d8fb53e284383fcdf69154e7"}, + {file = "dulwich-0.20.50-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6ec7c8fea2b44187a3b545e6c11ab9947ffb122647b07abcdb7cc3aaa770c0e"}, + {file = "dulwich-0.20.50-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:11b180b80363b4fc70664197028181a17ae4c52df9965a29b62a6c52e40c2dbe"}, + {file = "dulwich-0.20.50-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c83e7840d9d0a94d7033bc109efe0c22dfcdcd816bcd4469085e42809e3bf5ba"}, + {file = "dulwich-0.20.50-cp38-cp38-win32.whl", hash = "sha256:c075f69c2de19d9fd97e3b70832d2b42c6a4a5d909b3ffd1963b67d86029f95f"}, + {file = "dulwich-0.20.50-cp38-cp38-win_amd64.whl", hash = "sha256:06775c5713cfeda778c7c67d4422b5e7554d3a7f644f1dde646cdf486a30285a"}, + {file = "dulwich-0.20.50-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:49f66f1c057c18d7d60363f461f4ab8329320fbe1f02a7a33c255864a7d3c942"}, + {file = "dulwich-0.20.50-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4e541cd690a5e3d55082ed51732d755917e933cddeb4b0204f2a5ec5d5d7b60b"}, + {file = "dulwich-0.20.50-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:80e8750ee2fa0ab2784a095956077758e5f6107de27f637c4b9d18406652c22c"}, + {file = "dulwich-0.20.50-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fbb6368f18451dc44c95c55e1a609d1a01d3821f7ed480b22b2aea1baca0f4a7"}, + {file = "dulwich-0.20.50-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3ee45001411b638641819b7b3b33f31f13467c84066e432256580fcab7d8815"}, + {file = "dulwich-0.20.50-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4842e22ed863a776b36ef8ffe9ed7b772eb452b42c8d02975c29d27e3bc50ab4"}, + {file = "dulwich-0.20.50-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:790e4a641284a7fb4d56ebdaf8b324a5826fbbb9c54307c06f586f9f6a5e56db"}, + {file = "dulwich-0.20.50-cp39-cp39-win32.whl", hash = "sha256:f08406b6b789dea5c95ba1130a0801d8748a67f18be940fe7486a8b481fde875"}, + {file = "dulwich-0.20.50-cp39-cp39-win_amd64.whl", hash = "sha256:78c388ad421199000fb7b5ed5f0c7b509b3e31bd7cad303786a4d0bf89b82f60"}, + {file = "dulwich-0.20.50-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cb194c53109131bcbcd1ca430fcd437cdaf2d33e204e45fbe121c47eaa43e9af"}, + {file = "dulwich-0.20.50-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7542a72c5640dd0620862d6df8688f02a6c336359b5af9b3fcfe11b7fa6652f"}, + {file = "dulwich-0.20.50-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4aa1d0861517ebbbe0e0084cc9ab4f7ab720624a3eda2bd10e45f774ab858db8"}, + {file = "dulwich-0.20.50-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:583c6bbc27f13fe2e41a19f6987a42681c6e4f6959beae0a6e5bb033b8b081a8"}, + {file = "dulwich-0.20.50-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0c61c193d02c0e1e0d758cdd57ae76685c368d09a01f00d704ba88bd96767cfe"}, + {file = "dulwich-0.20.50-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2edbff3053251985f10702adfafbee118298d383ef5b5b432a5f22d1f1915df"}, + {file = "dulwich-0.20.50-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a344230cadfc5d315752add6ce9d4cfcfc6c85e36bbf57fce9444bcc7c6ea8fb"}, + {file = "dulwich-0.20.50-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:57bff9bde0b6b05b00c6acbb1a94357caddb2908ed7026a48c715ff50d220335"}, + {file = "dulwich-0.20.50-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e29a3c2037761fa816aa556e78364dfc8e3f44b873db2d17aed96f9b06ac83a3"}, + {file = "dulwich-0.20.50-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2aa2a4a84029625bf9c63771f8a628db1f3be2d2ea3cb8b17942cd4317797152"}, + {file = "dulwich-0.20.50-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd9fa00971ecf059bb358085a942ecac5be4ff71acdf299f44c8cbc45c18659f"}, + {file = "dulwich-0.20.50-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:af4adac92fb95671ea3a24f2f8e5e5e8f638711ce9c33a3ca6cd68bf1ff7d99f"}, + {file = "dulwich-0.20.50.tar.gz", hash = "sha256:50a941796b2c675be39be728d540c16b5b7ce77eb9e1b3f855650ece6832d2be"}, +] [package.dependencies] urllib3 = ">=1.25" @@ -188,6 +449,21 @@ https = ["urllib3 (>=1.24.1)"] paramiko = ["paramiko"] pgp = ["gpg"] +[[package]] +name = "exceptiongroup" +version = "1.0.4" +description = "Backport of PEP 654 (exception groups)" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"}, + {file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "execnet" version = "1.9.0" @@ -195,6 +471,10 @@ description = "execnet: rapid multi-Python deployment" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, + {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, +] [package.extras] testing = ["pre-commit"] @@ -206,6 +486,10 @@ description = "A platform independent file lock." category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"}, + {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"}, +] [package.extras] docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"] @@ -218,6 +502,9 @@ description = "Python module for interacting with nested dicts as a single level category = "dev" optional = false python-versions = "*" +files = [ + {file = "flatdict-4.0.1.tar.gz", hash = "sha256:cd32f08fd31ed21eb09ebc76f06b6bd12046a24f77beb1fd0281917e47f26742"}, +] [[package]] name = "html5lib" @@ -226,6 +513,10 @@ description = "HTML parser based on the WHATWG HTML specification" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, + {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, +] [package.dependencies] six = ">=1.9" @@ -244,14 +535,21 @@ description = "HTTP client mock for Python" category = "dev" optional = false python-versions = ">=3" +files = [ + {file = "httpretty-1.1.4.tar.gz", hash = "sha256:20de0e5dd5a18292d36d928cc3d6e52f8b2ac73daec40d41eb62dee154933b68"}, +] [[package]] name = "identify" -version = "2.5.6" +version = "2.5.9" description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "identify-2.5.9-py2.py3-none-any.whl", hash = "sha256:a390fb696e164dbddb047a0db26e57972ae52fbd037ae68797e5ae2f4492485d"}, + {file = "identify-2.5.9.tar.gz", hash = "sha256:906036344ca769539610436e40a684e170c3648b552194980bb7b617a8daeb9f"}, +] [package.extras] license = ["ukkonen"] @@ -263,6 +561,10 @@ description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] [[package]] name = "importlib-metadata" @@ -271,6 +573,10 @@ description = "Read metadata from Python packages" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-4.13.0-py3-none-any.whl", hash = "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116"}, + {file = "importlib_metadata-4.13.0.tar.gz", hash = "sha256:dd0173e8f150d6815e098fd354f6414b0f079af4644ddfe90c71e2fc6174346d"}, +] [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} @@ -288,6 +594,10 @@ description = "Read resources from Python packages" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "importlib_resources-5.10.0-py3-none-any.whl", hash = "sha256:ee17ec648f85480d523596ce49eae8ead87d5631ae1551f913c0100b5edd3437"}, + {file = "importlib_resources-5.10.0.tar.gz", hash = "sha256:c01b1b94210d9849f286b86bb51bcea7cd56dde0600d8db721d7b81330711668"}, +] [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} @@ -303,14 +613,22 @@ description = "iniconfig: brain-dead simple config-ini parsing" category = "dev" optional = false python-versions = "*" +files = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] [[package]] -name = "jaraco.classes" +name = "jaraco-classes" version = "3.2.3" description = "Utility functions for Python class constructs" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "jaraco.classes-3.2.3-py3-none-any.whl", hash = "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158"}, + {file = "jaraco.classes-3.2.3.tar.gz", hash = "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a"}, +] [package.dependencies] more-itertools = "*" @@ -326,6 +644,10 @@ description = "Low-level, pure Python DBus protocol wrapper." category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +] [package.extras] test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] @@ -333,11 +655,15 @@ trio = ["async_generator", "trio"] [[package]] name = "jsonschema" -version = "4.16.0" +version = "4.17.1" description = "An implementation of JSON Schema validation for Python" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "jsonschema-4.17.1-py3-none-any.whl", hash = "sha256:410ef23dcdbca4eaedc08b850079179883c2ed09378bd1f760d4af4aacfa28d7"}, + {file = "jsonschema-4.17.1.tar.gz", hash = "sha256:05b2d22c83640cde0b7e0aa329ca7754fbd98ea66ad8ae24aa61328dfe057fa3"}, +] [package.dependencies] attrs = ">=17.4.0" @@ -353,21 +679,25 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- [[package]] name = "keyring" -version = "23.9.3" +version = "23.11.0" description = "Store and access your passwords safely." category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "keyring-23.11.0-py3-none-any.whl", hash = "sha256:3dd30011d555f1345dec2c262f0153f2f0ca6bca041fb1dc4588349bb4c0ac1e"}, + {file = "keyring-23.11.0.tar.gz", hash = "sha256:ad192263e2cdd5f12875dedc2da13534359a7e760e77f8d04b50968a821c2361"}, +] [package.dependencies] -importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} +importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} "jaraco.classes" = "*" jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""} SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] -docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [[package]] @@ -377,14 +707,22 @@ description = "Platform-independent file locking module" category = "main" optional = false python-versions = "*" +files = [ + {file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"}, + {file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"}, +] [[package]] name = "more-itertools" -version = "8.14.0" +version = "9.0.0" description = "More routines for operating on iterables, beyond itertools" category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" +files = [ + {file = "more-itertools-9.0.0.tar.gz", hash = "sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab"}, + {file = "more_itertools-9.0.0-py3-none-any.whl", hash = "sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41"}, +] [[package]] name = "msgpack" @@ -393,14 +731,100 @@ description = "MessagePack serializer" category = "main" optional = false python-versions = "*" +files = [ + {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"}, + {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88"}, + {file = "msgpack-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:002b5c72b6cd9b4bafd790f364b8480e859b4712e91f43014fe01e4f957b8467"}, + {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35bc0faa494b0f1d851fd29129b2575b2e26d41d177caacd4206d81502d4c6a6"}, + {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4733359808c56d5d7756628736061c432ded018e7a1dff2d35a02439043321aa"}, + {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb514ad14edf07a1dbe63761fd30f89ae79b42625731e1ccf5e1f1092950eaa6"}, + {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c23080fdeec4716aede32b4e0ef7e213c7b1093eede9ee010949f2a418ced6ba"}, + {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:49565b0e3d7896d9ea71d9095df15b7f75a035c49be733051c34762ca95bbf7e"}, + {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aca0f1644d6b5a73eb3e74d4d64d5d8c6c3d577e753a04c9e9c87d07692c58db"}, + {file = "msgpack-1.0.4-cp310-cp310-win32.whl", hash = "sha256:0dfe3947db5fb9ce52aaea6ca28112a170db9eae75adf9339a1aec434dc954ef"}, + {file = "msgpack-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dea20515f660aa6b7e964433b1808d098dcfcabbebeaaad240d11f909298075"}, + {file = "msgpack-1.0.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e83f80a7fec1a62cf4e6c9a660e39c7f878f603737a0cdac8c13131d11d97f52"}, + {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c11a48cf5e59026ad7cb0dc29e29a01b5a66a3e333dc11c04f7e991fc5510a9"}, + {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1276e8f34e139aeff1c77a3cefb295598b504ac5314d32c8c3d54d24fadb94c9"}, + {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c9566f2c39ccced0a38d37c26cc3570983b97833c365a6044edef3574a00c08"}, + {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fcb8a47f43acc113e24e910399376f7277cf8508b27e5b88499f053de6b115a8"}, + {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:76ee788122de3a68a02ed6f3a16bbcd97bc7c2e39bd4d94be2f1821e7c4a64e6"}, + {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0a68d3ac0104e2d3510de90a1091720157c319ceeb90d74f7b5295a6bee51bae"}, + {file = "msgpack-1.0.4-cp36-cp36m-win32.whl", hash = "sha256:85f279d88d8e833ec015650fd15ae5eddce0791e1e8a59165318f371158efec6"}, + {file = "msgpack-1.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:c1683841cd4fa45ac427c18854c3ec3cd9b681694caf5bff04edb9387602d661"}, + {file = "msgpack-1.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a75dfb03f8b06f4ab093dafe3ddcc2d633259e6c3f74bb1b01996f5d8aa5868c"}, + {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9667bdfdf523c40d2511f0e98a6c9d3603be6b371ae9a238b7ef2dc4e7a427b0"}, + {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11184bc7e56fd74c00ead4f9cc9a3091d62ecb96e97653add7a879a14b003227"}, + {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac5bd7901487c4a1dd51a8c58f2632b15d838d07ceedaa5e4c080f7190925bff"}, + {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1e91d641d2bfe91ba4c52039adc5bccf27c335356055825c7f88742c8bb900dd"}, + {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2a2df1b55a78eb5f5b7d2a4bb221cd8363913830145fad05374a80bf0877cb1e"}, + {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:545e3cf0cf74f3e48b470f68ed19551ae6f9722814ea969305794645da091236"}, + {file = "msgpack-1.0.4-cp37-cp37m-win32.whl", hash = "sha256:2cc5ca2712ac0003bcb625c96368fd08a0f86bbc1a5578802512d87bc592fe44"}, + {file = "msgpack-1.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:eba96145051ccec0ec86611fe9cf693ce55f2a3ce89c06ed307de0e085730ec1"}, + {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7760f85956c415578c17edb39eed99f9181a48375b0d4a94076d84148cf67b2d"}, + {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:449e57cc1ff18d3b444eb554e44613cffcccb32805d16726a5494038c3b93dab"}, + {file = "msgpack-1.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d603de2b8d2ea3f3bcb2efe286849aa7a81531abc52d8454da12f46235092bcb"}, + {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f5d88c99f64c456413d74a975bd605a9b0526293218a3b77220a2c15458ba9"}, + {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916c78f33602ecf0509cc40379271ba0f9ab572b066bd4bdafd7434dee4bc6e"}, + {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81fc7ba725464651190b196f3cd848e8553d4d510114a954681fd0b9c479d7e1"}, + {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5b5b962221fa2c5d3a7f8133f9abffc114fe218eb4365e40f17732ade576c8e"}, + {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:77ccd2af37f3db0ea59fb280fa2165bf1b096510ba9fe0cc2bf8fa92a22fdb43"}, + {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b17be2478b622939e39b816e0aa8242611cc8d3583d1cd8ec31b249f04623243"}, + {file = "msgpack-1.0.4-cp38-cp38-win32.whl", hash = "sha256:2bb8cdf50dd623392fa75525cce44a65a12a00c98e1e37bf0fb08ddce2ff60d2"}, + {file = "msgpack-1.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:26b8feaca40a90cbe031b03d82b2898bf560027160d3eae1423f4a67654ec5d6"}, + {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:462497af5fd4e0edbb1559c352ad84f6c577ffbbb708566a0abaaa84acd9f3ae"}, + {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2999623886c5c02deefe156e8f869c3b0aaeba14bfc50aa2486a0415178fce55"}, + {file = "msgpack-1.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f0029245c51fd9473dc1aede1160b0a29f4a912e6b1dd353fa6d317085b219da"}, + {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed6f7b854a823ea44cf94919ba3f727e230da29feb4a99711433f25800cf747f"}, + {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df96d6eaf45ceca04b3f3b4b111b86b33785683d682c655063ef8057d61fd92"}, + {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a4192b1ab40f8dca3f2877b70e63799d95c62c068c84dc028b40a6cb03ccd0f"}, + {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e3590f9fb9f7fbc36df366267870e77269c03172d086fa76bb4eba8b2b46624"}, + {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1576bd97527a93c44fa856770197dec00d223b0b9f36ef03f65bac60197cedf8"}, + {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63e29d6e8c9ca22b21846234913c3466b7e4ee6e422f205a2988083de3b08cae"}, + {file = "msgpack-1.0.4-cp39-cp39-win32.whl", hash = "sha256:fb62ea4b62bfcb0b380d5680f9a4b3f9a2d166d9394e9bbd9666c0ee09a3645c"}, + {file = "msgpack-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce"}, + {file = "msgpack-1.0.4.tar.gz", hash = "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f"}, +] [[package]] name = "mypy" -version = "0.990" +version = "0.991" description = "Optional static typing for Python" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "mypy-0.991-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab"}, + {file = "mypy-0.991-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d"}, + {file = "mypy-0.991-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6"}, + {file = "mypy-0.991-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb"}, + {file = "mypy-0.991-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305"}, + {file = "mypy-0.991-cp310-cp310-win_amd64.whl", hash = "sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c"}, + {file = "mypy-0.991-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372"}, + {file = "mypy-0.991-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f"}, + {file = "mypy-0.991-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33"}, + {file = "mypy-0.991-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05"}, + {file = "mypy-0.991-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad"}, + {file = "mypy-0.991-cp311-cp311-win_amd64.whl", hash = "sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297"}, + {file = "mypy-0.991-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813"}, + {file = "mypy-0.991-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711"}, + {file = "mypy-0.991-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd"}, + {file = "mypy-0.991-cp37-cp37m-win_amd64.whl", hash = "sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef"}, + {file = "mypy-0.991-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a"}, + {file = "mypy-0.991-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93"}, + {file = "mypy-0.991-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf"}, + {file = "mypy-0.991-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135"}, + {file = "mypy-0.991-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70"}, + {file = "mypy-0.991-cp38-cp38-win_amd64.whl", hash = "sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243"}, + {file = "mypy-0.991-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d"}, + {file = "mypy-0.991-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5"}, + {file = "mypy-0.991-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3"}, + {file = "mypy-0.991-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648"}, + {file = "mypy-0.991-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476"}, + {file = "mypy-0.991-cp39-cp39-win_amd64.whl", hash = "sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461"}, + {file = "mypy-0.991-py3-none-any.whl", hash = "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb"}, + {file = "mypy-0.991.tar.gz", hash = "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06"}, +] [package.dependencies] mypy-extensions = ">=0.4.3" @@ -421,6 +845,10 @@ description = "Experimental type system extensions for programs checked with the category = "dev" optional = false python-versions = "*" +files = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] [[package]] name = "nodeenv" @@ -429,6 +857,10 @@ description = "Node.js virtual environment builder" category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, + {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, +] [package.dependencies] setuptools = "*" @@ -440,6 +872,10 @@ description = "An OrderedSet is a custom MutableSet that remembers its order, so category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8"}, + {file = "ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562"}, +] [package.extras] dev = ["black", "mypy", "pytest"] @@ -451,6 +887,10 @@ description = "Core utilities for Python packages" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" @@ -462,6 +902,10 @@ description = "Pexpect allows easy control of interactive console applications." category = "main" optional = false python-versions = "*" +files = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] [package.dependencies] ptyprocess = ">=0.5" @@ -473,29 +917,41 @@ description = "Query metadatdata from sdists / bdists / installed packages." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "pkginfo-1.8.3-py2.py3-none-any.whl", hash = "sha256:848865108ec99d4901b2f7e84058b6e7660aae8ae10164e015a6dcf5b242a594"}, + {file = "pkginfo-1.8.3.tar.gz", hash = "sha256:a84da4318dd86f870a9447a8c98340aa06216bfc6f2b7bdc4b8766984ae1867c"}, +] [package.extras] testing = ["coverage", "nose"] [[package]] -name = "pkgutil_resolve_name" +name = "pkgutil-resolve-name" version = "1.3.10" description = "Resolve a name to an object." category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, + {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, +] [[package]] name = "platformdirs" -version = "2.5.2" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "2.5.4" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, + {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, +] [package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] +docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] +test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" @@ -504,6 +960,10 @@ description = "plugin and hook calling mechanisms for python" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} @@ -519,6 +979,10 @@ description = "Poetry PEP 517 Build Backend" category = "main" optional = false python-versions = ">=3.7,<4.0" +files = [ + {file = "poetry_core-1.4.0-py3-none-any.whl", hash = "sha256:5559ab80384ac021db329ef317086417e140ee1176bcfcb3a3838b544e213c8e"}, + {file = "poetry_core-1.4.0.tar.gz", hash = "sha256:514bd33c30e0bf56b0ed44ee15e120d7e47b61ad908b2b1011da68c48a84ada9"}, +] [package.dependencies] importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} @@ -530,6 +994,10 @@ description = "Poetry plugin to export the dependencies to various formats" category = "main" optional = false python-versions = ">=3.7,<4.0" +files = [ + {file = "poetry_plugin_export-1.2.0-py3-none-any.whl", hash = "sha256:109fb43ebfd0e79d8be2e7f9d43ba2ae357c4975a18dfc0cfdd9597dd086790e"}, + {file = "poetry_plugin_export-1.2.0.tar.gz", hash = "sha256:9a1dd42765408931d7831738749022651d43a2968b67c988db1b7a567dfe41ef"}, +] [package.dependencies] poetry = ">=1.2.2,<2.0.0" @@ -542,6 +1010,10 @@ description = "A framework for managing and maintaining multi-language pre-commi category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"}, + {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"}, +] [package.dependencies] cfgv = ">=2.0.0" @@ -554,11 +1026,27 @@ virtualenv = ">=20.0.8" [[package]] name = "psutil" -version = "5.9.2" +version = "5.9.4" description = "Cross-platform lib for process and system monitoring in Python." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"}, + {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"}, + {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"}, + {file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"}, + {file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"}, + {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"}, + {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"}, + {file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"}, + {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"}, + {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"}, + {file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"}, + {file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"}, + {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"}, + {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"}, +] [package.extras] test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] @@ -570,6 +1058,10 @@ description = "Run a subprocess in a pseudo terminal" category = "main" optional = false python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] [[package]] name = "py" @@ -578,6 +1070,10 @@ description = "library with cross-python path, ini-parsing, io, code, log facili category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] [[package]] name = "pycparser" @@ -586,6 +1082,10 @@ description = "C parser in Python" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] [[package]] name = "pyparsing" @@ -594,35 +1094,67 @@ description = "pyparsing module - Classes and methods to define and execute pars category = "main" optional = false python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] [package.extras] diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pyrsistent" -version = "0.18.1" +version = "0.19.2" description = "Persistent/Functional/Immutable data structures" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "pyrsistent-0.19.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d6982b5a0237e1b7d876b60265564648a69b14017f3b5f908c5be2de3f9abb7a"}, + {file = "pyrsistent-0.19.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:187d5730b0507d9285a96fca9716310d572e5464cadd19f22b63a6976254d77a"}, + {file = "pyrsistent-0.19.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:055ab45d5911d7cae397dc418808d8802fb95262751872c841c170b0dbf51eed"}, + {file = "pyrsistent-0.19.2-cp310-cp310-win32.whl", hash = "sha256:456cb30ca8bff00596519f2c53e42c245c09e1a4543945703acd4312949bfd41"}, + {file = "pyrsistent-0.19.2-cp310-cp310-win_amd64.whl", hash = "sha256:b39725209e06759217d1ac5fcdb510e98670af9e37223985f330b611f62e7425"}, + {file = "pyrsistent-0.19.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2aede922a488861de0ad00c7630a6e2d57e8023e4be72d9d7147a9fcd2d30712"}, + {file = "pyrsistent-0.19.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879b4c2f4d41585c42df4d7654ddffff1239dc4065bc88b745f0341828b83e78"}, + {file = "pyrsistent-0.19.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c43bec251bbd10e3cb58ced80609c5c1eb238da9ca78b964aea410fb820d00d6"}, + {file = "pyrsistent-0.19.2-cp37-cp37m-win32.whl", hash = "sha256:d690b18ac4b3e3cab73b0b7aa7dbe65978a172ff94970ff98d82f2031f8971c2"}, + {file = "pyrsistent-0.19.2-cp37-cp37m-win_amd64.whl", hash = "sha256:3ba4134a3ff0fc7ad225b6b457d1309f4698108fb6b35532d015dca8f5abed73"}, + {file = "pyrsistent-0.19.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a178209e2df710e3f142cbd05313ba0c5ebed0a55d78d9945ac7a4e09d923308"}, + {file = "pyrsistent-0.19.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e371b844cec09d8dc424d940e54bba8f67a03ebea20ff7b7b0d56f526c71d584"}, + {file = "pyrsistent-0.19.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111156137b2e71f3a9936baf27cb322e8024dac3dc54ec7fb9f0bcf3249e68bb"}, + {file = "pyrsistent-0.19.2-cp38-cp38-win32.whl", hash = "sha256:e5d8f84d81e3729c3b506657dddfe46e8ba9c330bf1858ee33108f8bb2adb38a"}, + {file = "pyrsistent-0.19.2-cp38-cp38-win_amd64.whl", hash = "sha256:9cd3e9978d12b5d99cbdc727a3022da0430ad007dacf33d0bf554b96427f33ab"}, + {file = "pyrsistent-0.19.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f1258f4e6c42ad0b20f9cfcc3ada5bd6b83374516cd01c0960e3cb75fdca6770"}, + {file = "pyrsistent-0.19.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21455e2b16000440e896ab99e8304617151981ed40c29e9507ef1c2e4314ee95"}, + {file = "pyrsistent-0.19.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd880614c6237243ff53a0539f1cb26987a6dc8ac6e66e0c5a40617296a045e"}, + {file = "pyrsistent-0.19.2-cp39-cp39-win32.whl", hash = "sha256:71d332b0320642b3261e9fee47ab9e65872c2bd90260e5d225dabeed93cbd42b"}, + {file = "pyrsistent-0.19.2-cp39-cp39-win_amd64.whl", hash = "sha256:dec3eac7549869365fe263831f576c8457f6c833937c68542d08fde73457d291"}, + {file = "pyrsistent-0.19.2-py3-none-any.whl", hash = "sha256:ea6b79a02a28550c98b6ca9c35b9f492beaa54d7c5c9e9949555893c8a9234d0"}, + {file = "pyrsistent-0.19.2.tar.gz", hash = "sha256:bfa0351be89c9fcbcb8c9879b826f4353be10f58f8a677efab0c017bf7137ec2"}, +] [[package]] name = "pytest" -version = "7.1.3" +version = "7.2.0" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, + {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, +] [package.dependencies] attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -tomli = ">=1.0.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] @@ -634,6 +1166,10 @@ description = "Pytest plugin for measuring coverage." category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, + {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, +] [package.dependencies] coverage = {version = ">=5.2.1", extras = ["toml"]} @@ -649,6 +1185,10 @@ description = "run tests in isolated forked subprocesses" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pytest-forked-1.4.0.tar.gz", hash = "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e"}, + {file = "pytest_forked-1.4.0-py3-none-any.whl", hash = "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8"}, +] [package.dependencies] py = "*" @@ -661,6 +1201,10 @@ description = "pytest plugin to annotate failed tests with a workflow command fo category = "dev" optional = false python-versions = "*" +files = [ + {file = "pytest-github-actions-annotate-failures-0.1.7.tar.gz", hash = "sha256:c6af8f9d13f1f09ef4c104a30875a4975db131ddbba979c8e48fdc456c8dde1f"}, + {file = "pytest_github_actions_annotate_failures-0.1.7-py2.py3-none-any.whl", hash = "sha256:c4a7346d1d95f731a6b53e9a45f10ca56593978149266dd7526876cce403ea38"}, +] [package.dependencies] pytest = ">=4.0.0" @@ -672,6 +1216,10 @@ description = "Thin-wrapper around the mock package for easier use with pytest" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"}, + {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"}, +] [package.dependencies] pytest = ">=5.0" @@ -686,6 +1234,10 @@ description = "Pytest plugin to randomly order tests and control random.seed." category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pytest-randomly-3.12.0.tar.gz", hash = "sha256:d60c2db71ac319aee0fc6c4110a7597d611a8b94a5590918bfa8583f00caccb2"}, + {file = "pytest_randomly-3.12.0-py3-none-any.whl", hash = "sha256:f4f2e803daf5d1ba036cc22bf4fe9dbbf99389ec56b00e5cba732fb5c1d07fdd"}, +] [package.dependencies] importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} @@ -698,6 +1250,10 @@ description = "pytest xdist plugin for distributed testing and loop-on-failing m category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"}, + {file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"}, +] [package.dependencies] execnet = ">=1.1" @@ -717,14 +1273,60 @@ description = "" category = "main" optional = false python-versions = "*" +files = [ + {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, + {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, +] [[package]] -name = "PyYAML" +name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] [[package]] name = "rapidfuzz" @@ -733,6 +1335,97 @@ description = "rapid fuzzy string matching" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "rapidfuzz-2.13.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91c049f7591d9e9f8bcc3c556c0c4b448223f564ad04511a8719d28f5d38daed"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:26e4b7f7941b92546a9b06ed75b40b5d7ceace8f3074d06cb3369349388d700d"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba2a8fbd21079093118c40e8e80068750c1619a5988e54220ea0929de48e7d65"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de707808f1997574014d9ba87c2d9f8a619688d615520e3dce958bf4398514c7"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba3f47a5b82de7304ae08e2a111ccc90a6ea06ecc3f25d7870d08be0973c94cb"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a181b6ef9b480b56b29bdc58dc50c198e93d33398d2f8e57da05cbddb095bd9e"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1e569953a2abe945f116a6c22b71e8fc02d7c27068af2af40990115f25c93e4"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:026f6ecd8948e168a89fc015ef34b6bcb200f30ac33f1480554d722181b38bea"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daf5e4f6b048c225a494c941a21463a0d397c39a080db8fece9b3136297ed240"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e39ae60598ed533f513db6d0370755685666024ab187a144fc688dd16cfa2d33"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e8d71f1611431c445ced872b303cd61f215551a11df0c7171e5993bed84867d5"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f5d07dca69bf5a9f1e1cd5756ded6c197a27e8d8f2d8a3d99565add37a3bd1ec"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ac95981911559c842e1e4532e2f89ca255531db1d87257e5e69cd8c0c0d585fc"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-win32.whl", hash = "sha256:b4162b96d0908cb0ca218513eab559e9a77c8a1d9705c9133813634d9db27f4f"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-win_amd64.whl", hash = "sha256:84fd3cfc1cb872019e60a3844b1deedb176de0b9ded11bf30147137ac65185f5"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a599cc5cec196c0776faf65b74ac957354bd036f878905a16be9e20884870d02"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dbad2b7dad98b854a468d2c6a0b11464f68ce841428aded2f24f201a17a144eb"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad78fb90540dc752b532345065146371acd3804a917c31fdd8a337951da9def2"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed0f99e0037b7f9f7117493e8723851c9eece4629906b2d5da21d3ef124149a2"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9abdffc590ef08d27dfd14d32e571f4a0f5f797f433f00c5faf4cf56ab62792a"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:352c920e166e838bc560014885ba979df656938fcc29a12c73ff06dc76b150d8"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c40acbadc965e72f1b44b3c665a59ec78a5e959757e52520bf73687c84ce6854"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a4053d5b62cedec83ff67d55e50da35f7736bed0a3b2af51fa6143f5fef3785"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0c324d82871fe50471f7ba38a21c3e68167e868f541f57ac0ef23c053bbef6e6"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cb4bd75518838b141dab8fe663de988c4d08502999068dc0b3949d43bd86ace6"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4b785ffbc16795fca27c9e899993df7721d886249061689c48dbfe60fa7d02a1"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:1f363bf95d79dbafa8eac17697965e02e74da6f21b231b3fb808b2185bfed337"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f7cfc25d8143a7570f5e4c9da072a1e1c335d81a6926eb10c1fd3f637fa3c022"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-win32.whl", hash = "sha256:580f32cda7f911fef8266c7d811e580c18734cd12308d099b9975b914f33fcaf"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:98be3e873c8f9d90a982891b2b061521ba4e5e49552ba2d3c1b0806dd5677f88"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:de8ec700127b645b0e2e28e694a2bba6dcb6a305ef080ad312f3086d47fb6973"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0ec73e6d3ad9442cfb5b94c137cf4241fff2860d81a9ee8be8c3d987bb400c0"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da5b7f35fc824cff36a2baa62486d5b427bf0fd7714c19704b5a7df82c2950b4"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f186b3a32d78af7a805584a7e1c2fdf6f6fd62939936e4f3df869158c147a55"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68f2e23eec59fc77bef164157889a2f7fb9800c47d615c58ee3809e2be3c8509"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4291a8c02d32aa6ebdffe63cf91abc2846383de95ae04a275f036c4e7a27f9ba"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a2eeee09ff716c8ff75942c1b93f0bca129590499f1127cbeb1b5cefbdc0c3d5"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2345656b30d7e18d18a4df5b765e4059111860a69bf3a36608a7d625e92567e6"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1e1dd1a328464dd2ae70f0e31ec403593fbb1b254bab7ac9f0cd08ba71c797d0"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:54fe1835f96c1033cdb7e4677497e784704c81d028c962d2222239ded93d978b"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6b68b6a12411cfacca16ace22d42ae8e9946315d79f49c6c97089789c235e795"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-win32.whl", hash = "sha256:9a740ddd3f7725c80e500f16b1b02b83a58b47164c0f3ddd9379208629c8c4b5"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-win_amd64.whl", hash = "sha256:378554acdcf8370cc5c777b1312921a2a670f68888e999ea1305599c55b67f5d"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa96955f2878116239db55506fe825f574651a8893d07a83de7b3c76a2f0386e"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b4df886481ca27a6d53d30a73625fb86dd308cf7d6d99d32e0dfbfcc8e8a75b9"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c66f3b8e93cdc3063ffd7224cad84951834d9434ffd27fa3fabad2e942ddab7"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d6d5ab0f12f2d7ae6aad77af67ae6253b6c1d54c320484f1acd2fce38b39ac2"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f0574d5d97722cfaf51b7dd667c8c836fa9fdf5a7d8158a787b98ee2788f6c5"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:83ff31d33c1391a0a6b23273b7f839dc8f7b5fb75ddca59ce4f334b83ca822bb"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94d8c65f48665f82064bea8a48ff185409a309ba396f5aec3a846831cbe36e6d"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c065a83883af2a9a0303b6c06844a700af0db97ff6dc894324f656ad8efe405"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:773c60a5368a361253efea194552ff9ed6879756f6feb71b61b514723f8cb726"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:12ece1a4d024297afa4b76d2ce71c2c65fc7eaa487a9ae9f6e17c160253cfd23"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2b491f2fac36718247070c3343f53aadbbe8684f3e0cf3b6cce1bd099e1d05cb"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:31370273787dca851e2df6f32f1ec8c61f86e9bbeb1cc42787020b6dfff952fd"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:47b5b227dc0bd53530dda55f344e1b24087fa99bb1bd7fceb6f5a2b1e2831ad4"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-win32.whl", hash = "sha256:8f09a16ae84b1decb9df9a7e393ec84a0b2a11da6356c3eedcf86da8cabe3071"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-win_amd64.whl", hash = "sha256:e038e187270cbb987cf7c5d4b574fce7a32bc3d9593e9346d129874a7dc08dc3"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:aee5dce78e157e503269121ad6f886acab4b1ab3e3956bcdf0549d54596eab57"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80073e897af0669f496d23899583b5c2f0decc2ec06aa7c36a3b8fb16eda5e0e"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ce40c2a68fe28e05a4f66229c11885ef928086fbcd2eff086decdacfe5254da9"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd268701bf930bbb2d12f6f7f75c681e16fee646ea1663d258e825bf919ca7a1"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5d93e77881497f76e77056feea4c375732d27151151273d6e4cb8a1defbf17a"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b27c3e2b1789a635b9df1d74838ae032dc2dbc596ece5d89f9de2c37ba0a6dfe"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e49f412fe58c793af61b04fb5536534dfc95000b6c2bf0bfa42fcf7eb1453d42"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27bbdee91718019e251d315c6e9b03aa5b7663b90e4228ac1ddb0a567ff3634b"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b51d45cb9ed81669206e338413ba224c06a8900ab0cc9106f4750ac73dc687bb"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3479a2fd88504cc41eb707650e81fd7ce864f2418fee24f7224775b539536b39"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7adb4327453c1550f51d6ba13d718a84091f82230c1d0daca6db628e57d0fa5a"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3a4e87aae287d757d9c5b045c819c985b02b38dea3f75630cc24d53826e640be"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e175b1643306558a3d7604789c4a8c217a64406fe82bf1a9e52efb5dea53ae"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-win32.whl", hash = "sha256:fb896fafa206db4d55f4412135c3ae28fbc56b8afc476970d0c5f29d2ce50948"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-win_amd64.whl", hash = "sha256:37a9a8f5737b8e429291148be67d2dd8ba779a69a87ad95d2785bb3d80fd1df7"}, + {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d6cb51a8459e7160366c6c7b31e8f9a671f7d617591c0ad305f2697707061da2"}, + {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:343fe1fcbbf55c994b22962bfb46f6b6903faeac5a2671b2f0fa5e3664de3e66"}, + {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d9d081cd8e0110661c8a3e728d7b491a903bb54d34de40b17d19144563bd5f6"}, + {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f93a6740fef239a8aca6521cc1891d448664115b53528a3dd7f95c1781a5fa6"}, + {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:deaf26cc23cfbf90650993108de888533635b981a7157a0234b4753527ac6e5c"}, + {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b6a0617ba60f81a8df3b9ddca09f591a0a0c8269402169825fcd50daa03e5c25"}, + {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bee1065d55edfeabdb98211bb673cb44a8b118cded42d743f7d59c07b05a80d"}, + {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e5afd5477332ceeb960e2002d5bb0b04ad00b40037a0ab1de9916041badcf00"}, + {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eead76c172ba08d49ea621016cf84031fff1ee33d7db751d7003e491e55e66af"}, + {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:83b1e8aca6c3fad058d8a2b7653b7496df0c4aca903d589bb0e4184868290767"}, + {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:41610c3a9be4febcbcac2b69b2f45d0da33e39d1194e5ffa3dd3a104d5a67a70"}, + {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3aacc4eb58d6bccf6ec571619bee35861d4103961b9873d9b0829d347ca8a63e"}, + {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:791d90aa1c68b5485f6340a8dc485aba7e9bcb729572449174ded0692e7e7ad0"}, + {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d4f94b408c9f9218d61e8af55e43c8102f813eea2cf82de10906b032ddcb9aa"}, + {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ac6a8a34f858f3862798383f51012788df6be823e2874fa426667a4da94ded7e"}, + {file = "rapidfuzz-2.13.2.tar.gz", hash = "sha256:1c67007161655c59e13bba130a2db29d7c9e5c81bcecb8846a3dd7386065eb24"}, +] [package.extras] full = ["numpy"] @@ -744,6 +1437,10 @@ description = "Python HTTP for Humans." category = "main" optional = false python-versions = ">=3.7, <4" +files = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] [package.dependencies] certifi = ">=2017.4.17" @@ -757,22 +1454,30 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-toolbelt" -version = "0.9.1" +version = "0.10.1" description = "A utility belt for advanced users of python-requests" category = "main" optional = false -python-versions = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-toolbelt-0.10.1.tar.gz", hash = "sha256:62e09f7ff5ccbda92772a29f394a49c3ad6cb181d568b1337626b2abb628a63d"}, + {file = "requests_toolbelt-0.10.1-py2.py3-none-any.whl", hash = "sha256:18565aa58116d9951ac39baa288d3adb5b3ff975c4f25eee78555d89e8f247f7"}, +] [package.dependencies] requests = ">=2.0.1,<3.0.0" [[package]] -name = "SecretStorage" +name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, + {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, +] [package.dependencies] cryptography = ">=2.0" @@ -780,15 +1485,19 @@ jeepney = ">=0.6" [[package]] name = "setuptools" -version = "65.4.1" +version = "65.6.3" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, + {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, +] [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] @@ -798,6 +1507,10 @@ description = "Tool to Detect Surrounding Shell" category = "main" optional = false python-versions = ">=3.4" +files = [ + {file = "shellingham-1.5.0-py2.py3-none-any.whl", hash = "sha256:a8f02ba61b69baaa13facdba62908ca8690a94b8119b69f5ec5873ea85f7391b"}, + {file = "shellingham-1.5.0.tar.gz", hash = "sha256:72fb7f5c63103ca2cb91b23dee0c71fe8ad6fbfd46418ef17dbe40db51592dad"}, +] [[package]] name = "six" @@ -806,6 +1519,10 @@ description = "Python 2 and 3 compatibility utilities" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] [[package]] name = "toml" @@ -814,6 +1531,10 @@ description = "Python Library for Tom's Obvious, Minimal Language" category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] [[package]] name = "tomli" @@ -822,22 +1543,34 @@ description = "A lil' TOML parser" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] [[package]] name = "tomlkit" -version = "0.11.5" +version = "0.11.6" description = "Style preserving TOML library" category = "main" optional = false -python-versions = ">=3.6,<4.0" +python-versions = ">=3.6" +files = [ + {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, + {file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"}, +] [[package]] name = "trove-classifiers" -version = "2022.9.26" +version = "2022.10.19" description = "Canonical source for classifiers on PyPI (pypi.org)." category = "main" optional = false python-versions = "*" +files = [ + {file = "trove-classifiers-2022.10.19.tar.gz", hash = "sha256:3a58b43b5149a0e125037e7e875d35e4471435a6a5a5b7e6b68cbee7afd23739"}, + {file = "trove_classifiers-2022.10.19-py3-none-any.whl", hash = "sha256:887261694f96a6af04a6d09566d4fd46b70edd5f1025b9ffc35cb4961218e8b0"}, +] [[package]] name = "typed-ast" @@ -846,41 +1579,83 @@ description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "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"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "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"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "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"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "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"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "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"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, +] [[package]] name = "types-html5lib" -version = "1.1.11" +version = "1.1.11.10" description = "Typing stubs for html5lib" category = "dev" optional = false python-versions = "*" +files = [ + {file = "types-html5lib-1.1.11.10.tar.gz", hash = "sha256:90eeffad04010bf67bcccf77b9cc6cf4783eec13cbd22ebbaaf20f24d5f2ca11"}, + {file = "types_html5lib-1.1.11.10-py3-none-any.whl", hash = "sha256:18c34ce51eac062321b224365b3aee5b5cdc1613872ed8469c9c0c4ccd3e02e9"}, +] [[package]] name = "types-jsonschema" -version = "4.16.1" +version = "4.17.0.1" description = "Typing stubs for jsonschema" category = "dev" optional = false python-versions = "*" +files = [ + {file = "types-jsonschema-4.17.0.1.tar.gz", hash = "sha256:62625d492e4930411a431909ac32301aeab6180500e70ee222f81d43204cfb3c"}, + {file = "types_jsonschema-4.17.0.1-py3-none-any.whl", hash = "sha256:77badbe3881cbf79ac9561be2be2b1f37ab104b13afd2231840e6dd6e94e63c2"}, +] [[package]] name = "types-requests" -version = "2.28.11.2" +version = "2.28.11.5" description = "Typing stubs for requests" category = "dev" optional = false python-versions = "*" +files = [ + {file = "types-requests-2.28.11.5.tar.gz", hash = "sha256:a7df37cc6fb6187a84097da951f8e21d335448aa2501a6b0a39cbd1d7ca9ee2a"}, + {file = "types_requests-2.28.11.5-py3-none-any.whl", hash = "sha256:091d4a5a33c1b4f20d8b1b952aa8fa27a6e767c44c3cf65e56580df0b05fd8a9"}, +] [package.dependencies] types-urllib3 = "<1.27" [[package]] name = "types-urllib3" -version = "1.26.25" +version = "1.26.25.4" description = "Typing stubs for urllib3" category = "dev" optional = false python-versions = "*" +files = [ + {file = "types-urllib3-1.26.25.4.tar.gz", hash = "sha256:eec5556428eec862b1ac578fb69aab3877995a99ffec9e5a12cf7fbd0cc9daee"}, + {file = "types_urllib3-1.26.25.4-py3-none-any.whl", hash = "sha256:ed6b9e8a8be488796f72306889a06a3fc3cb1aa99af02ab8afb50144d7317e49"}, +] [[package]] name = "typing-extensions" @@ -889,835 +1664,70 @@ description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false python-versions = ">=3.7" - -[[package]] -name = "urllib3" -version = "1.26.12" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "virtualenv" -version = "20.16.5" -description = "Virtual Python Environment builder" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -distlib = ">=0.3.5,<1" -filelock = ">=3.4.1,<4" -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""} -platformdirs = ">=2.4,<3" - -[package.extras] -docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"] -testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] - -[[package]] -name = "webencodings" -version = "0.5.1" -description = "Character encoding aliases for legacy web content" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "xattr" -version = "0.10.0" -description = "Python wrapper for extended filesystem attributes" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -cffi = ">=1.0" - -[[package]] -name = "zipp" -version = "3.9.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - -[metadata] -lock-version = "1.1" -python-versions = "^3.7" -content-hash = "728bc093f740faad6db2b3a1c7a61ea05ca6c6a998b1f144f071cdea5a3b2771" - -[metadata.files] -attrs = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] -"backports.cached-property" = [ - {file = "backports.cached-property-1.0.2.tar.gz", hash = "sha256:9306f9eed6ec55fd156ace6bc1094e2c86fae5fb2bf07b6a9c00745c656e75dd"}, - {file = "backports.cached_property-1.0.2-py3-none-any.whl", hash = "sha256:baeb28e1cd619a3c9ab8941431fe34e8490861fb998c6c4590693d50171db0cc"}, -] -CacheControl = [ - {file = "CacheControl-0.12.11-py2.py3-none-any.whl", hash = "sha256:2c75d6a8938cb1933c75c50184549ad42728a27e9f6b92fd677c3151aa72555b"}, - {file = "CacheControl-0.12.11.tar.gz", hash = "sha256:a5b9fcc986b184db101aa280b42ecdcdfc524892596f606858e0b7a8b4d9e144"}, -] -cachy = [ - {file = "cachy-0.3.0-py2.py3-none-any.whl", hash = "sha256:338ca09c8860e76b275aff52374330efedc4d5a5e45dc1c5b539c1ead0786fe7"}, - {file = "cachy-0.3.0.tar.gz", hash = "sha256:186581f4ceb42a0bbe040c407da73c14092379b1e4c0e327fdb72ae4a9b269b1"}, -] -certifi = [ - {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, - {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, -] -cffi = [ - {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, - {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, - {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, - {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, - {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, - {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, - {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, - {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, - {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, - {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, - {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, - {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, - {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, - {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, - {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, - {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, - {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, - {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, - {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, -] -cfgv = [ - {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, - {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, -] -charset-normalizer = [ - {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, - {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, -] -cleo = [ - {file = "cleo-2.0.0-py3-none-any.whl", hash = "sha256:daad7ff76134ebe2c7bf74520b1bbd59e6e77026535b967efc5a15a0eaa2e19c"}, - {file = "cleo-2.0.0.tar.gz", hash = "sha256:fbc5cb141cbc31ea8ffd3d5cd67d3b183fa38aa5098fd37e39e9a953a232fda9"}, -] -colorama = [ - {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, - {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, -] -coverage = [ - {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, - {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, - {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, - {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, - {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, - {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, - {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, - {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, - {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, - {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, - {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, - {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, - {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, - {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, - {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, - {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, -] -crashtest = [ - {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, - {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, -] -cryptography = [ - {file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f"}, - {file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad"}, - {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153"}, - {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407"}, - {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e"}, - {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0"}, - {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd"}, - {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6"}, - {file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a"}, - {file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294"}, - {file = "cryptography-38.0.1-cp36-abi3-win32.whl", hash = "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0"}, - {file = "cryptography-38.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a"}, - {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d"}, - {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9"}, - {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d"}, - {file = "cryptography-38.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818"}, - {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6"}, - {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750"}, - {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013"}, - {file = "cryptography-38.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5"}, - {file = "cryptography-38.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61"}, - {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac"}, - {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb"}, - {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a"}, - {file = "cryptography-38.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b"}, - {file = "cryptography-38.0.1.tar.gz", hash = "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7"}, -] -deepdiff = [ - {file = "deepdiff-5.8.1-py3-none-any.whl", hash = "sha256:e9aea49733f34fab9a0897038d8f26f9d94a97db1790f1b814cced89e9e0d2b7"}, - {file = "deepdiff-5.8.1.tar.gz", hash = "sha256:8d4eb2c4e6cbc80b811266419cb71dd95a157094a3947ccf937a94d44943c7b8"}, -] -distlib = [ - {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, - {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, -] -dulwich = [ - {file = "dulwich-0.20.46-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:6676196e9cf377cde62aa2f5d741e93207437343e0c62368bd0d784c322a3c49"}, - {file = "dulwich-0.20.46-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a1ca555a3eafe7388d6cb81bb08f34608a1592500f0bd4c26734c91d208a546"}, - {file = "dulwich-0.20.46-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:769442c9657b10fc35ac625beeaf440540c9288c96fcfaba3e58adf745c5cafd"}, - {file = "dulwich-0.20.46-cp310-cp310-win32.whl", hash = "sha256:de22a54f68c6c4e97f9b924abd46da4618536d7934b9849066be9fc5cd31205d"}, - {file = "dulwich-0.20.46-cp310-cp310-win_amd64.whl", hash = "sha256:42fa5a68908556eb6c40f231a67caf6a4660588aad707a9d6b334fa1d8f04bf7"}, - {file = "dulwich-0.20.46-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:3e16376031466848e44aabf3489fafb054482143744b21167dbd168731041c74"}, - {file = "dulwich-0.20.46-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:153c7512587384a290c60fef330f1ab397a59559e19e8b02a0169ff21b4c69fb"}, - {file = "dulwich-0.20.46-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5b68bd815cd2769c75e5a78708eb0440612df19b370a977aa9e01a056baa9ed"}, - {file = "dulwich-0.20.46-cp311-cp311-win32.whl", hash = "sha256:b1339bca70764eb8e780d80c72e7c1cb4651201dc9e43ec5d616bf51eb3bb3a6"}, - {file = "dulwich-0.20.46-cp311-cp311-win_amd64.whl", hash = "sha256:1162fdafb2abdfe66649617061f3853cb26384fade1f6884f6fe6e9c570a7552"}, - {file = "dulwich-0.20.46-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:6826512f778eaa47e2e8c0a46cdc555958f9f5286771490b8642b4b508ea5d25"}, - {file = "dulwich-0.20.46-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:100d39bc18196a07c521fd5f60f78f397493303daa0b8690216864bbc621cd5d"}, - {file = "dulwich-0.20.46-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4cd2cd7baa81246bdc8c5272d4e9224e2255da7a0618a220aab5e07b9888e9b"}, - {file = "dulwich-0.20.46-cp36-cp36m-win32.whl", hash = "sha256:6eed5a3194d64112605fc0f638f4fa91771495e8674fa3e6d6b33bf150d297d5"}, - {file = "dulwich-0.20.46-cp36-cp36m-win_amd64.whl", hash = "sha256:9ca4d73987f5b0e2e843497876f9bb39a47384a2e50597a85542285f5c890293"}, - {file = "dulwich-0.20.46-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:b9f49de83911eed7adbe83136229837ef9d102e42dbe6aacb1a18be45c997ace"}, - {file = "dulwich-0.20.46-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38be7d3a78d608ecab3348f7920d6b9002e7972dd245206dc8075cfdb91621d"}, - {file = "dulwich-0.20.46-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4b7a7feb966a4669c254b18385fe0b3c639f3b1f5ddef0d9e083364cc762847"}, - {file = "dulwich-0.20.46-cp37-cp37m-win32.whl", hash = "sha256:f9552ac246bceab1c5cdd1ec3cfe9446fe76b9853eaf59d3244df03eb27fd3fe"}, - {file = "dulwich-0.20.46-cp37-cp37m-win_amd64.whl", hash = "sha256:90a075aeb0fdbad7e18b9db3af161e3d635e2b7697b7a4b467e6844a13b0b210"}, - {file = "dulwich-0.20.46-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:8d6fee82cedb2362942d9ef94061901f7e07d7d8674e4c7b6fceeef7822ae275"}, - {file = "dulwich-0.20.46-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:669c6b3d82996518a7fec4604771bd285e23f0860f41f565fef5987265d431d9"}, - {file = "dulwich-0.20.46-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd3eac228117487a959ac8f49ea2787eac34acc69999fe7adae70b23e3c3571c"}, - {file = "dulwich-0.20.46-cp38-cp38-win32.whl", hash = "sha256:92024f572d32680e021219f77015c8b443c38022e502b7f51ad7cf51a6285a36"}, - {file = "dulwich-0.20.46-cp38-cp38-win_amd64.whl", hash = "sha256:d928de1eba0326a2a8a52ed94c9bf7c315ff4db606a1aa3ae688d39574f93267"}, - {file = "dulwich-0.20.46-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:a5d1b7a3a7d84a5dedbb90092e00097357106b9642ac08a96c2ae89ccd8afd9a"}, - {file = "dulwich-0.20.46-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b739d759c10e2af7c964dcc97fd4e5dc49e8567d080eed8906fc422c79b7fdcf"}, - {file = "dulwich-0.20.46-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fc7a4f633f5468453d5dd84a753cd99d4433f0397437229a0a8b10347935591"}, - {file = "dulwich-0.20.46-cp39-cp39-win32.whl", hash = "sha256:525115c4d1fbf60a5fe98f340b4ca597ba47b2c75d9c5ec750dd0e9115ef8ec6"}, - {file = "dulwich-0.20.46-cp39-cp39-win_amd64.whl", hash = "sha256:73e2585a9fcf1f8cdad8597a0c384c0b365b2e8346463130c96d9ea1478587ae"}, - {file = "dulwich-0.20.46.tar.gz", hash = "sha256:4f0e88ffff5db1523d93d92f1525fe5fa161318ffbaad502c1b9b3be7a067172"}, -] -execnet = [ - {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, - {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, -] -filelock = [ - {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"}, - {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"}, -] -flatdict = [ - {file = "flatdict-4.0.1.tar.gz", hash = "sha256:cd32f08fd31ed21eb09ebc76f06b6bd12046a24f77beb1fd0281917e47f26742"}, -] -html5lib = [ - {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, - {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, -] -httpretty = [ - {file = "httpretty-1.1.4.tar.gz", hash = "sha256:20de0e5dd5a18292d36d928cc3d6e52f8b2ac73daec40d41eb62dee154933b68"}, -] -identify = [ - {file = "identify-2.5.6-py2.py3-none-any.whl", hash = "sha256:b276db7ec52d7e89f5bc4653380e33054ddc803d25875952ad90b0f012cbcdaa"}, - {file = "identify-2.5.6.tar.gz", hash = "sha256:6c32dbd747aa4ceee1df33f25fed0b0f6e0d65721b15bd151307ff7056d50245"}, -] -idna = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, -] -importlib-metadata = [ - {file = "importlib_metadata-4.13.0-py3-none-any.whl", hash = "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116"}, - {file = "importlib_metadata-4.13.0.tar.gz", hash = "sha256:dd0173e8f150d6815e098fd354f6414b0f079af4644ddfe90c71e2fc6174346d"}, -] -importlib-resources = [ - {file = "importlib_resources-5.10.0-py3-none-any.whl", hash = "sha256:ee17ec648f85480d523596ce49eae8ead87d5631ae1551f913c0100b5edd3437"}, - {file = "importlib_resources-5.10.0.tar.gz", hash = "sha256:c01b1b94210d9849f286b86bb51bcea7cd56dde0600d8db721d7b81330711668"}, -] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] -"jaraco.classes" = [ - {file = "jaraco.classes-3.2.3-py3-none-any.whl", hash = "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158"}, - {file = "jaraco.classes-3.2.3.tar.gz", hash = "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a"}, -] -jeepney = [ - {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, - {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, -] -jsonschema = [ - {file = "jsonschema-4.16.0-py3-none-any.whl", hash = "sha256:9e74b8f9738d6a946d70705dc692b74b5429cd0960d58e79ffecfc43b2221eb9"}, - {file = "jsonschema-4.16.0.tar.gz", hash = "sha256:165059f076eff6971bae5b742fc029a7b4ef3f9bcf04c14e4776a7605de14b23"}, -] -keyring = [ - {file = "keyring-23.9.3-py3-none-any.whl", hash = "sha256:69732a15cb1433bdfbc3b980a8a36a04878a6cfd7cb99f497b573f31618001c0"}, - {file = "keyring-23.9.3.tar.gz", hash = "sha256:69b01dd83c42f590250fe7a1f503fc229b14de83857314b1933a3ddbf595c4a5"}, -] -lockfile = [ - {file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"}, - {file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"}, -] -more-itertools = [ - {file = "more-itertools-8.14.0.tar.gz", hash = "sha256:c09443cd3d5438b8dafccd867a6bc1cb0894389e90cb53d227456b0b0bccb750"}, - {file = "more_itertools-8.14.0-py3-none-any.whl", hash = "sha256:1bc4f91ee5b1b31ac7ceacc17c09befe6a40a503907baf9c839c229b5095cfd2"}, -] -msgpack = [ - {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"}, - {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88"}, - {file = "msgpack-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:002b5c72b6cd9b4bafd790f364b8480e859b4712e91f43014fe01e4f957b8467"}, - {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35bc0faa494b0f1d851fd29129b2575b2e26d41d177caacd4206d81502d4c6a6"}, - {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4733359808c56d5d7756628736061c432ded018e7a1dff2d35a02439043321aa"}, - {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb514ad14edf07a1dbe63761fd30f89ae79b42625731e1ccf5e1f1092950eaa6"}, - {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c23080fdeec4716aede32b4e0ef7e213c7b1093eede9ee010949f2a418ced6ba"}, - {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:49565b0e3d7896d9ea71d9095df15b7f75a035c49be733051c34762ca95bbf7e"}, - {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aca0f1644d6b5a73eb3e74d4d64d5d8c6c3d577e753a04c9e9c87d07692c58db"}, - {file = "msgpack-1.0.4-cp310-cp310-win32.whl", hash = "sha256:0dfe3947db5fb9ce52aaea6ca28112a170db9eae75adf9339a1aec434dc954ef"}, - {file = "msgpack-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dea20515f660aa6b7e964433b1808d098dcfcabbebeaaad240d11f909298075"}, - {file = "msgpack-1.0.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e83f80a7fec1a62cf4e6c9a660e39c7f878f603737a0cdac8c13131d11d97f52"}, - {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c11a48cf5e59026ad7cb0dc29e29a01b5a66a3e333dc11c04f7e991fc5510a9"}, - {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1276e8f34e139aeff1c77a3cefb295598b504ac5314d32c8c3d54d24fadb94c9"}, - {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c9566f2c39ccced0a38d37c26cc3570983b97833c365a6044edef3574a00c08"}, - {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fcb8a47f43acc113e24e910399376f7277cf8508b27e5b88499f053de6b115a8"}, - {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:76ee788122de3a68a02ed6f3a16bbcd97bc7c2e39bd4d94be2f1821e7c4a64e6"}, - {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0a68d3ac0104e2d3510de90a1091720157c319ceeb90d74f7b5295a6bee51bae"}, - {file = "msgpack-1.0.4-cp36-cp36m-win32.whl", hash = "sha256:85f279d88d8e833ec015650fd15ae5eddce0791e1e8a59165318f371158efec6"}, - {file = "msgpack-1.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:c1683841cd4fa45ac427c18854c3ec3cd9b681694caf5bff04edb9387602d661"}, - {file = "msgpack-1.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a75dfb03f8b06f4ab093dafe3ddcc2d633259e6c3f74bb1b01996f5d8aa5868c"}, - {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9667bdfdf523c40d2511f0e98a6c9d3603be6b371ae9a238b7ef2dc4e7a427b0"}, - {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11184bc7e56fd74c00ead4f9cc9a3091d62ecb96e97653add7a879a14b003227"}, - {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac5bd7901487c4a1dd51a8c58f2632b15d838d07ceedaa5e4c080f7190925bff"}, - {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1e91d641d2bfe91ba4c52039adc5bccf27c335356055825c7f88742c8bb900dd"}, - {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2a2df1b55a78eb5f5b7d2a4bb221cd8363913830145fad05374a80bf0877cb1e"}, - {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:545e3cf0cf74f3e48b470f68ed19551ae6f9722814ea969305794645da091236"}, - {file = "msgpack-1.0.4-cp37-cp37m-win32.whl", hash = "sha256:2cc5ca2712ac0003bcb625c96368fd08a0f86bbc1a5578802512d87bc592fe44"}, - {file = "msgpack-1.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:eba96145051ccec0ec86611fe9cf693ce55f2a3ce89c06ed307de0e085730ec1"}, - {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7760f85956c415578c17edb39eed99f9181a48375b0d4a94076d84148cf67b2d"}, - {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:449e57cc1ff18d3b444eb554e44613cffcccb32805d16726a5494038c3b93dab"}, - {file = "msgpack-1.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d603de2b8d2ea3f3bcb2efe286849aa7a81531abc52d8454da12f46235092bcb"}, - {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f5d88c99f64c456413d74a975bd605a9b0526293218a3b77220a2c15458ba9"}, - {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916c78f33602ecf0509cc40379271ba0f9ab572b066bd4bdafd7434dee4bc6e"}, - {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81fc7ba725464651190b196f3cd848e8553d4d510114a954681fd0b9c479d7e1"}, - {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5b5b962221fa2c5d3a7f8133f9abffc114fe218eb4365e40f17732ade576c8e"}, - {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:77ccd2af37f3db0ea59fb280fa2165bf1b096510ba9fe0cc2bf8fa92a22fdb43"}, - {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b17be2478b622939e39b816e0aa8242611cc8d3583d1cd8ec31b249f04623243"}, - {file = "msgpack-1.0.4-cp38-cp38-win32.whl", hash = "sha256:2bb8cdf50dd623392fa75525cce44a65a12a00c98e1e37bf0fb08ddce2ff60d2"}, - {file = "msgpack-1.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:26b8feaca40a90cbe031b03d82b2898bf560027160d3eae1423f4a67654ec5d6"}, - {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:462497af5fd4e0edbb1559c352ad84f6c577ffbbb708566a0abaaa84acd9f3ae"}, - {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2999623886c5c02deefe156e8f869c3b0aaeba14bfc50aa2486a0415178fce55"}, - {file = "msgpack-1.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f0029245c51fd9473dc1aede1160b0a29f4a912e6b1dd353fa6d317085b219da"}, - {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed6f7b854a823ea44cf94919ba3f727e230da29feb4a99711433f25800cf747f"}, - {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df96d6eaf45ceca04b3f3b4b111b86b33785683d682c655063ef8057d61fd92"}, - {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a4192b1ab40f8dca3f2877b70e63799d95c62c068c84dc028b40a6cb03ccd0f"}, - {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e3590f9fb9f7fbc36df366267870e77269c03172d086fa76bb4eba8b2b46624"}, - {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1576bd97527a93c44fa856770197dec00d223b0b9f36ef03f65bac60197cedf8"}, - {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63e29d6e8c9ca22b21846234913c3466b7e4ee6e422f205a2988083de3b08cae"}, - {file = "msgpack-1.0.4-cp39-cp39-win32.whl", hash = "sha256:fb62ea4b62bfcb0b380d5680f9a4b3f9a2d166d9394e9bbd9666c0ee09a3645c"}, - {file = "msgpack-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce"}, - {file = "msgpack-1.0.4.tar.gz", hash = "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f"}, -] -mypy = [ - {file = "mypy-0.990-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aaf1be63e0207d7d17be942dcf9a6b641745581fe6c64df9a38deb562a7dbafa"}, - {file = "mypy-0.990-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d555aa7f44cecb7ea3c0ac69d58b1a5afb92caa017285a8e9c4efbf0518b61b4"}, - {file = "mypy-0.990-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f694d6d09a460b117dccb6857dda269188e3437c880d7b60fa0014fa872d1e9"}, - {file = "mypy-0.990-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:269f0dfb6463b8780333310ff4b5134425157ef0d2b1d614015adaf6d6a7eabd"}, - {file = "mypy-0.990-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8798c8ed83aa809f053abff08664bdca056038f5a02af3660de00b7290b64c47"}, - {file = "mypy-0.990-cp310-cp310-win_amd64.whl", hash = "sha256:47a9955214615108c3480a500cfda8513a0b1cd3c09a1ed42764ca0dd7b931dd"}, - {file = "mypy-0.990-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4a8a6c10f4c63fbf6ad6c03eba22c9331b3946a4cec97f008e9ffb4d3b31e8e2"}, - {file = "mypy-0.990-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd2dd3730ba894ec2a2082cc703fbf3e95a08479f7be84912e3131fc68809d46"}, - {file = "mypy-0.990-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7da0005e47975287a92b43276e460ac1831af3d23032c34e67d003388a0ce8d0"}, - {file = "mypy-0.990-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262c543ef24deb10470a3c1c254bb986714e2b6b1a67d66daf836a548a9f316c"}, - {file = "mypy-0.990-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3ff201a0c6d3ea029d73b1648943387d75aa052491365b101f6edd5570d018ea"}, - {file = "mypy-0.990-cp311-cp311-win_amd64.whl", hash = "sha256:1767830da2d1afa4e62b684647af0ff79b401f004d7fa08bc5b0ce2d45bcd5ec"}, - {file = "mypy-0.990-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6826d9c4d85bbf6d68cb279b561de6a4d8d778ca8e9ab2d00ee768ab501a9852"}, - {file = "mypy-0.990-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46897755f944176fbc504178422a5a2875bbf3f7436727374724842c0987b5af"}, - {file = "mypy-0.990-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0680389c34284287fe00e82fc8bccdea9aff318f7e7d55b90d967a13a9606013"}, - {file = "mypy-0.990-cp37-cp37m-win_amd64.whl", hash = "sha256:b08541a06eed35b543ae1a6b301590eb61826a1eb099417676ddc5a42aa151c5"}, - {file = "mypy-0.990-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:be88d665e76b452c26fb2bdc3d54555c01226fba062b004ede780b190a50f9db"}, - {file = "mypy-0.990-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b8f4a8213b1fd4b751e26b59ae0e0c12896568d7e805861035c7a15ed6dc9eb"}, - {file = "mypy-0.990-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2b6f85c2ad378e3224e017904a051b26660087b3b76490d533b7344f1546d3ff"}, - {file = "mypy-0.990-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ee5f99817ee70254e7eb5cf97c1b11dda29c6893d846c8b07bce449184e9466"}, - {file = "mypy-0.990-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49082382f571c3186ce9ea0bd627cb1345d4da8d44a8377870f4442401f0a706"}, - {file = "mypy-0.990-cp38-cp38-win_amd64.whl", hash = "sha256:aba38e3dd66bdbafbbfe9c6e79637841928ea4c79b32e334099463c17b0d90ef"}, - {file = "mypy-0.990-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9d851c09b981a65d9d283a8ccb5b1d0b698e580493416a10942ef1a04b19fd37"}, - {file = "mypy-0.990-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d847dd23540e2912d9667602271e5ebf25e5788e7da46da5ffd98e7872616e8e"}, - {file = "mypy-0.990-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc6019808580565040cd2a561b593d7c3c646badd7e580e07d875eb1bf35c695"}, - {file = "mypy-0.990-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a3150d409609a775c8cb65dbe305c4edd7fe576c22ea79d77d1454acd9aeda8"}, - {file = "mypy-0.990-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3227f14fe943524f5794679156488f18bf8d34bfecd4623cf76bc55958d229c5"}, - {file = "mypy-0.990-cp39-cp39-win_amd64.whl", hash = "sha256:c76c769c46a1e6062a84837badcb2a7b0cdb153d68601a61f60739c37d41cc74"}, - {file = "mypy-0.990-py3-none-any.whl", hash = "sha256:8f1940325a8ed460ba03d19ab83742260fa9534804c317224e5d4e5aa588e2d6"}, - {file = "mypy-0.990.tar.gz", hash = "sha256:72382cb609142dba3f04140d016c94b4092bc7b4d98ca718740dc989e5271b8d"}, -] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -nodeenv = [ - {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, - {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, -] -ordered-set = [ - {file = "ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8"}, - {file = "ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562"}, -] -packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, -] -pexpect = [ - {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, - {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, -] -pkginfo = [ - {file = "pkginfo-1.8.3-py2.py3-none-any.whl", hash = "sha256:848865108ec99d4901b2f7e84058b6e7660aae8ae10164e015a6dcf5b242a594"}, - {file = "pkginfo-1.8.3.tar.gz", hash = "sha256:a84da4318dd86f870a9447a8c98340aa06216bfc6f2b7bdc4b8766984ae1867c"}, -] -pkgutil_resolve_name = [ - {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, - {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, -] -platformdirs = [ - {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, - {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -poetry-core = [ - {file = "poetry_core-1.4.0-py3-none-any.whl", hash = "sha256:5559ab80384ac021db329ef317086417e140ee1176bcfcb3a3838b544e213c8e"}, - {file = "poetry_core-1.4.0.tar.gz", hash = "sha256:514bd33c30e0bf56b0ed44ee15e120d7e47b61ad908b2b1011da68c48a84ada9"}, -] -poetry-plugin-export = [ - {file = "poetry_plugin_export-1.2.0-py3-none-any.whl", hash = "sha256:109fb43ebfd0e79d8be2e7f9d43ba2ae357c4975a18dfc0cfdd9597dd086790e"}, - {file = "poetry_plugin_export-1.2.0.tar.gz", hash = "sha256:9a1dd42765408931d7831738749022651d43a2968b67c988db1b7a567dfe41ef"}, -] -pre-commit = [ - {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"}, - {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"}, -] -psutil = [ - {file = "psutil-5.9.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:8f024fbb26c8daf5d70287bb3edfafa22283c255287cf523c5d81721e8e5d82c"}, - {file = "psutil-5.9.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:b2f248ffc346f4f4f0d747ee1947963613216b06688be0be2e393986fe20dbbb"}, - {file = "psutil-5.9.2-cp27-cp27m-win32.whl", hash = "sha256:b1928b9bf478d31fdffdb57101d18f9b70ed4e9b0e41af751851813547b2a9ab"}, - {file = "psutil-5.9.2-cp27-cp27m-win_amd64.whl", hash = "sha256:404f4816c16a2fcc4eaa36d7eb49a66df2d083e829d3e39ee8759a411dbc9ecf"}, - {file = "psutil-5.9.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:94e621c6a4ddb2573d4d30cba074f6d1aa0186645917df42c811c473dd22b339"}, - {file = "psutil-5.9.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:256098b4f6ffea6441eb54ab3eb64db9ecef18f6a80d7ba91549195d55420f84"}, - {file = "psutil-5.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:614337922702e9be37a39954d67fdb9e855981624d8011a9927b8f2d3c9625d9"}, - {file = "psutil-5.9.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39ec06dc6c934fb53df10c1672e299145ce609ff0611b569e75a88f313634969"}, - {file = "psutil-5.9.2-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3ac2c0375ef498e74b9b4ec56df3c88be43fe56cac465627572dbfb21c4be34"}, - {file = "psutil-5.9.2-cp310-cp310-win32.whl", hash = "sha256:e4c4a7636ffc47b7141864f1c5e7d649f42c54e49da2dd3cceb1c5f5d29bfc85"}, - {file = "psutil-5.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:f4cb67215c10d4657e320037109939b1c1d2fd70ca3d76301992f89fe2edb1f1"}, - {file = "psutil-5.9.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dc9bda7d5ced744622f157cc8d8bdd51735dafcecff807e928ff26bdb0ff097d"}, - {file = "psutil-5.9.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75291912b945a7351d45df682f9644540d564d62115d4a20d45fa17dc2d48f8"}, - {file = "psutil-5.9.2-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4018d5f9b6651f9896c7a7c2c9f4652e4eea53f10751c4e7d08a9093ab587ec"}, - {file = "psutil-5.9.2-cp36-cp36m-win32.whl", hash = "sha256:f40ba362fefc11d6bea4403f070078d60053ed422255bd838cd86a40674364c9"}, - {file = "psutil-5.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9770c1d25aee91417eba7869139d629d6328a9422ce1cdd112bd56377ca98444"}, - {file = "psutil-5.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:42638876b7f5ef43cef8dcf640d3401b27a51ee3fa137cb2aa2e72e188414c32"}, - {file = "psutil-5.9.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91aa0dac0c64688667b4285fa29354acfb3e834e1fd98b535b9986c883c2ce1d"}, - {file = "psutil-5.9.2-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fb54941aac044a61db9d8eb56fc5bee207db3bc58645d657249030e15ba3727"}, - {file = "psutil-5.9.2-cp37-cp37m-win32.whl", hash = "sha256:7cbb795dcd8ed8fd238bc9e9f64ab188f3f4096d2e811b5a82da53d164b84c3f"}, - {file = "psutil-5.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:5d39e3a2d5c40efa977c9a8dd4f679763c43c6c255b1340a56489955dbca767c"}, - {file = "psutil-5.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd331866628d18223a4265371fd255774affd86244fc307ef66eaf00de0633d5"}, - {file = "psutil-5.9.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b315febaebae813326296872fdb4be92ad3ce10d1d742a6b0c49fb619481ed0b"}, - {file = "psutil-5.9.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7929a516125f62399d6e8e026129c8835f6c5a3aab88c3fff1a05ee8feb840d"}, - {file = "psutil-5.9.2-cp38-cp38-win32.whl", hash = "sha256:561dec454853846d1dd0247b44c2e66a0a0c490f937086930ec4b8f83bf44f06"}, - {file = "psutil-5.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:67b33f27fc0427483b61563a16c90d9f3b547eeb7af0ef1b9fe024cdc9b3a6ea"}, - {file = "psutil-5.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3591616fa07b15050b2f87e1cdefd06a554382e72866fcc0ab2be9d116486c8"}, - {file = "psutil-5.9.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b29f581b5edab1f133563272a6011925401804d52d603c5c606936b49c8b97"}, - {file = "psutil-5.9.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4642fd93785a29353d6917a23e2ac6177308ef5e8be5cc17008d885cb9f70f12"}, - {file = "psutil-5.9.2-cp39-cp39-win32.whl", hash = "sha256:ed29ea0b9a372c5188cdb2ad39f937900a10fb5478dc077283bf86eeac678ef1"}, - {file = "psutil-5.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:68b35cbff92d1f7103d8f1db77c977e72f49fcefae3d3d2b91c76b0e7aef48b8"}, - {file = "psutil-5.9.2.tar.gz", hash = "sha256:feb861a10b6c3bb00701063b37e4afc754f8217f0f09c42280586bd6ac712b5c"}, -] -ptyprocess = [ - {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, - {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, -] -py = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] -pycparser = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, -] -pyparsing = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, -] -pyrsistent = [ - {file = "pyrsistent-0.18.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1"}, - {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26"}, - {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e"}, - {file = "pyrsistent-0.18.1-cp310-cp310-win32.whl", hash = "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6"}, - {file = "pyrsistent-0.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec"}, - {file = "pyrsistent-0.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b"}, - {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc"}, - {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22"}, - {file = "pyrsistent-0.18.1-cp37-cp37m-win32.whl", hash = "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8"}, - {file = "pyrsistent-0.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286"}, - {file = "pyrsistent-0.18.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6"}, - {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec"}, - {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c"}, - {file = "pyrsistent-0.18.1-cp38-cp38-win32.whl", hash = "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca"}, - {file = "pyrsistent-0.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a"}, - {file = "pyrsistent-0.18.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5"}, - {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045"}, - {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c"}, - {file = "pyrsistent-0.18.1-cp39-cp39-win32.whl", hash = "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc"}, - {file = "pyrsistent-0.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07"}, - {file = "pyrsistent-0.18.1.tar.gz", hash = "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96"}, -] -pytest = [ - {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"}, - {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"}, -] -pytest-cov = [ - {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, - {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, -] -pytest-forked = [ - {file = "pytest-forked-1.4.0.tar.gz", hash = "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e"}, - {file = "pytest_forked-1.4.0-py3-none-any.whl", hash = "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8"}, -] -pytest-github-actions-annotate-failures = [ - {file = "pytest-github-actions-annotate-failures-0.1.7.tar.gz", hash = "sha256:c6af8f9d13f1f09ef4c104a30875a4975db131ddbba979c8e48fdc456c8dde1f"}, - {file = "pytest_github_actions_annotate_failures-0.1.7-py2.py3-none-any.whl", hash = "sha256:c4a7346d1d95f731a6b53e9a45f10ca56593978149266dd7526876cce403ea38"}, -] -pytest-mock = [ - {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"}, - {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"}, -] -pytest-randomly = [ - {file = "pytest-randomly-3.12.0.tar.gz", hash = "sha256:d60c2db71ac319aee0fc6c4110a7597d611a8b94a5590918bfa8583f00caccb2"}, - {file = "pytest_randomly-3.12.0-py3-none-any.whl", hash = "sha256:f4f2e803daf5d1ba036cc22bf4fe9dbbf99389ec56b00e5cba732fb5c1d07fdd"}, -] -pytest-xdist = [ - {file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"}, - {file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"}, -] -pywin32-ctypes = [ - {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, - {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, -] -PyYAML = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, - {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, - {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, -] -rapidfuzz = [ - {file = "rapidfuzz-2.13.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91c049f7591d9e9f8bcc3c556c0c4b448223f564ad04511a8719d28f5d38daed"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:26e4b7f7941b92546a9b06ed75b40b5d7ceace8f3074d06cb3369349388d700d"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba2a8fbd21079093118c40e8e80068750c1619a5988e54220ea0929de48e7d65"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de707808f1997574014d9ba87c2d9f8a619688d615520e3dce958bf4398514c7"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba3f47a5b82de7304ae08e2a111ccc90a6ea06ecc3f25d7870d08be0973c94cb"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a181b6ef9b480b56b29bdc58dc50c198e93d33398d2f8e57da05cbddb095bd9e"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1e569953a2abe945f116a6c22b71e8fc02d7c27068af2af40990115f25c93e4"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:026f6ecd8948e168a89fc015ef34b6bcb200f30ac33f1480554d722181b38bea"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daf5e4f6b048c225a494c941a21463a0d397c39a080db8fece9b3136297ed240"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e39ae60598ed533f513db6d0370755685666024ab187a144fc688dd16cfa2d33"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e8d71f1611431c445ced872b303cd61f215551a11df0c7171e5993bed84867d5"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f5d07dca69bf5a9f1e1cd5756ded6c197a27e8d8f2d8a3d99565add37a3bd1ec"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ac95981911559c842e1e4532e2f89ca255531db1d87257e5e69cd8c0c0d585fc"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-win32.whl", hash = "sha256:b4162b96d0908cb0ca218513eab559e9a77c8a1d9705c9133813634d9db27f4f"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-win_amd64.whl", hash = "sha256:84fd3cfc1cb872019e60a3844b1deedb176de0b9ded11bf30147137ac65185f5"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a599cc5cec196c0776faf65b74ac957354bd036f878905a16be9e20884870d02"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dbad2b7dad98b854a468d2c6a0b11464f68ce841428aded2f24f201a17a144eb"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad78fb90540dc752b532345065146371acd3804a917c31fdd8a337951da9def2"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed0f99e0037b7f9f7117493e8723851c9eece4629906b2d5da21d3ef124149a2"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9abdffc590ef08d27dfd14d32e571f4a0f5f797f433f00c5faf4cf56ab62792a"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:352c920e166e838bc560014885ba979df656938fcc29a12c73ff06dc76b150d8"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c40acbadc965e72f1b44b3c665a59ec78a5e959757e52520bf73687c84ce6854"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a4053d5b62cedec83ff67d55e50da35f7736bed0a3b2af51fa6143f5fef3785"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0c324d82871fe50471f7ba38a21c3e68167e868f541f57ac0ef23c053bbef6e6"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cb4bd75518838b141dab8fe663de988c4d08502999068dc0b3949d43bd86ace6"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4b785ffbc16795fca27c9e899993df7721d886249061689c48dbfe60fa7d02a1"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:1f363bf95d79dbafa8eac17697965e02e74da6f21b231b3fb808b2185bfed337"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f7cfc25d8143a7570f5e4c9da072a1e1c335d81a6926eb10c1fd3f637fa3c022"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-win32.whl", hash = "sha256:580f32cda7f911fef8266c7d811e580c18734cd12308d099b9975b914f33fcaf"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:98be3e873c8f9d90a982891b2b061521ba4e5e49552ba2d3c1b0806dd5677f88"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:de8ec700127b645b0e2e28e694a2bba6dcb6a305ef080ad312f3086d47fb6973"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0ec73e6d3ad9442cfb5b94c137cf4241fff2860d81a9ee8be8c3d987bb400c0"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da5b7f35fc824cff36a2baa62486d5b427bf0fd7714c19704b5a7df82c2950b4"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f186b3a32d78af7a805584a7e1c2fdf6f6fd62939936e4f3df869158c147a55"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68f2e23eec59fc77bef164157889a2f7fb9800c47d615c58ee3809e2be3c8509"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4291a8c02d32aa6ebdffe63cf91abc2846383de95ae04a275f036c4e7a27f9ba"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a2eeee09ff716c8ff75942c1b93f0bca129590499f1127cbeb1b5cefbdc0c3d5"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2345656b30d7e18d18a4df5b765e4059111860a69bf3a36608a7d625e92567e6"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1e1dd1a328464dd2ae70f0e31ec403593fbb1b254bab7ac9f0cd08ba71c797d0"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:54fe1835f96c1033cdb7e4677497e784704c81d028c962d2222239ded93d978b"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6b68b6a12411cfacca16ace22d42ae8e9946315d79f49c6c97089789c235e795"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-win32.whl", hash = "sha256:9a740ddd3f7725c80e500f16b1b02b83a58b47164c0f3ddd9379208629c8c4b5"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-win_amd64.whl", hash = "sha256:378554acdcf8370cc5c777b1312921a2a670f68888e999ea1305599c55b67f5d"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa96955f2878116239db55506fe825f574651a8893d07a83de7b3c76a2f0386e"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b4df886481ca27a6d53d30a73625fb86dd308cf7d6d99d32e0dfbfcc8e8a75b9"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c66f3b8e93cdc3063ffd7224cad84951834d9434ffd27fa3fabad2e942ddab7"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d6d5ab0f12f2d7ae6aad77af67ae6253b6c1d54c320484f1acd2fce38b39ac2"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f0574d5d97722cfaf51b7dd667c8c836fa9fdf5a7d8158a787b98ee2788f6c5"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:83ff31d33c1391a0a6b23273b7f839dc8f7b5fb75ddca59ce4f334b83ca822bb"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94d8c65f48665f82064bea8a48ff185409a309ba396f5aec3a846831cbe36e6d"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c065a83883af2a9a0303b6c06844a700af0db97ff6dc894324f656ad8efe405"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:773c60a5368a361253efea194552ff9ed6879756f6feb71b61b514723f8cb726"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:12ece1a4d024297afa4b76d2ce71c2c65fc7eaa487a9ae9f6e17c160253cfd23"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2b491f2fac36718247070c3343f53aadbbe8684f3e0cf3b6cce1bd099e1d05cb"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:31370273787dca851e2df6f32f1ec8c61f86e9bbeb1cc42787020b6dfff952fd"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:47b5b227dc0bd53530dda55f344e1b24087fa99bb1bd7fceb6f5a2b1e2831ad4"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-win32.whl", hash = "sha256:8f09a16ae84b1decb9df9a7e393ec84a0b2a11da6356c3eedcf86da8cabe3071"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-win_amd64.whl", hash = "sha256:e038e187270cbb987cf7c5d4b574fce7a32bc3d9593e9346d129874a7dc08dc3"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:aee5dce78e157e503269121ad6f886acab4b1ab3e3956bcdf0549d54596eab57"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80073e897af0669f496d23899583b5c2f0decc2ec06aa7c36a3b8fb16eda5e0e"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ce40c2a68fe28e05a4f66229c11885ef928086fbcd2eff086decdacfe5254da9"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd268701bf930bbb2d12f6f7f75c681e16fee646ea1663d258e825bf919ca7a1"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5d93e77881497f76e77056feea4c375732d27151151273d6e4cb8a1defbf17a"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b27c3e2b1789a635b9df1d74838ae032dc2dbc596ece5d89f9de2c37ba0a6dfe"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e49f412fe58c793af61b04fb5536534dfc95000b6c2bf0bfa42fcf7eb1453d42"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27bbdee91718019e251d315c6e9b03aa5b7663b90e4228ac1ddb0a567ff3634b"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b51d45cb9ed81669206e338413ba224c06a8900ab0cc9106f4750ac73dc687bb"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3479a2fd88504cc41eb707650e81fd7ce864f2418fee24f7224775b539536b39"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7adb4327453c1550f51d6ba13d718a84091f82230c1d0daca6db628e57d0fa5a"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3a4e87aae287d757d9c5b045c819c985b02b38dea3f75630cc24d53826e640be"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e175b1643306558a3d7604789c4a8c217a64406fe82bf1a9e52efb5dea53ae"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-win32.whl", hash = "sha256:fb896fafa206db4d55f4412135c3ae28fbc56b8afc476970d0c5f29d2ce50948"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-win_amd64.whl", hash = "sha256:37a9a8f5737b8e429291148be67d2dd8ba779a69a87ad95d2785bb3d80fd1df7"}, - {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d6cb51a8459e7160366c6c7b31e8f9a671f7d617591c0ad305f2697707061da2"}, - {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:343fe1fcbbf55c994b22962bfb46f6b6903faeac5a2671b2f0fa5e3664de3e66"}, - {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d9d081cd8e0110661c8a3e728d7b491a903bb54d34de40b17d19144563bd5f6"}, - {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f93a6740fef239a8aca6521cc1891d448664115b53528a3dd7f95c1781a5fa6"}, - {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:deaf26cc23cfbf90650993108de888533635b981a7157a0234b4753527ac6e5c"}, - {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b6a0617ba60f81a8df3b9ddca09f591a0a0c8269402169825fcd50daa03e5c25"}, - {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bee1065d55edfeabdb98211bb673cb44a8b118cded42d743f7d59c07b05a80d"}, - {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e5afd5477332ceeb960e2002d5bb0b04ad00b40037a0ab1de9916041badcf00"}, - {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eead76c172ba08d49ea621016cf84031fff1ee33d7db751d7003e491e55e66af"}, - {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:83b1e8aca6c3fad058d8a2b7653b7496df0c4aca903d589bb0e4184868290767"}, - {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:41610c3a9be4febcbcac2b69b2f45d0da33e39d1194e5ffa3dd3a104d5a67a70"}, - {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3aacc4eb58d6bccf6ec571619bee35861d4103961b9873d9b0829d347ca8a63e"}, - {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:791d90aa1c68b5485f6340a8dc485aba7e9bcb729572449174ded0692e7e7ad0"}, - {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d4f94b408c9f9218d61e8af55e43c8102f813eea2cf82de10906b032ddcb9aa"}, - {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ac6a8a34f858f3862798383f51012788df6be823e2874fa426667a4da94ded7e"}, - {file = "rapidfuzz-2.13.2.tar.gz", hash = "sha256:1c67007161655c59e13bba130a2db29d7c9e5c81bcecb8846a3dd7386065eb24"}, -] -requests = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, -] -requests-toolbelt = [ - {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, - {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, -] -SecretStorage = [ - {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, - {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, -] -setuptools = [ - {file = "setuptools-65.4.1-py3-none-any.whl", hash = "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012"}, - {file = "setuptools-65.4.1.tar.gz", hash = "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"}, -] -shellingham = [ - {file = "shellingham-1.5.0-py2.py3-none-any.whl", hash = "sha256:a8f02ba61b69baaa13facdba62908ca8690a94b8119b69f5ec5873ea85f7391b"}, - {file = "shellingham-1.5.0.tar.gz", hash = "sha256:72fb7f5c63103ca2cb91b23dee0c71fe8ad6fbfd46418ef17dbe40db51592dad"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] -tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] -tomlkit = [ - {file = "tomlkit-0.11.5-py3-none-any.whl", hash = "sha256:f2ef9da9cef846ee027947dc99a45d6b68a63b0ebc21944649505bf2e8bc5fe7"}, - {file = "tomlkit-0.11.5.tar.gz", hash = "sha256:571854ebbb5eac89abcb4a2e47d7ea27b89bf29e09c35395da6f03dd4ae23d1c"}, -] -trove-classifiers = [ - {file = "trove-classifiers-2022.9.26.tar.gz", hash = "sha256:4fda647dabbc8e2ae3e0188c91823f57a7040fa436ebdefa8c9b41180bea243e"}, - {file = "trove_classifiers-2022.9.26-py3-none-any.whl", hash = "sha256:9b552c49617946de9df75687cfd556bce346aead331477a3a41a26ab66d6a1fe"}, -] -typed-ast = [ - {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, - {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, - {file = "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"}, - {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, - {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, - {file = "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"}, - {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, - {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, - {file = "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"}, - {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, - {file = "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"}, - {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, - {file = "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"}, - {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, - {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, -] -types-html5lib = [ - {file = "types-html5lib-1.1.11.tar.gz", hash = "sha256:2b67bbaf3125b840720dc5890f243c3661583a503f0ed33166acf31c67e53717"}, - {file = "types_html5lib-1.1.11-py3-none-any.whl", hash = "sha256:dda54159be6ef58a67bf10bdd6fe5b4559e55e1df6bb18c47915281a8be0e5fd"}, -] -types-jsonschema = [ - {file = "types-jsonschema-4.16.1.tar.gz", hash = "sha256:95e31d2b90da218faf3d8fa34fa33ae55fc52c79b2cb7308755cc2d7d71b1096"}, - {file = "types_jsonschema-4.16.1-py3-none-any.whl", hash = "sha256:21ca9a227185b83655c71755b5834c36d66ca43f9de77c018d61c4f917f851ab"}, -] -types-requests = [ - {file = "types-requests-2.28.11.2.tar.gz", hash = "sha256:fdcd7bd148139fb8eef72cf4a41ac7273872cad9e6ada14b11ff5dfdeee60ed3"}, - {file = "types_requests-2.28.11.2-py3-none-any.whl", hash = "sha256:14941f8023a80b16441b3b46caffcbfce5265fd14555844d6029697824b5a2ef"}, -] -types-urllib3 = [ - {file = "types-urllib3-1.26.25.tar.gz", hash = "sha256:5aef0e663724eef924afa8b320b62ffef2c1736c1fa6caecfc9bc6c8ae2c3def"}, - {file = "types_urllib3-1.26.25-py3-none-any.whl", hash = "sha256:c1d78cef7bd581e162e46c20a57b2e1aa6ebecdcf01fd0713bb90978ff3e3427"}, -] -typing-extensions = [ +files = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] -urllib3 = [ - {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, - {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, + +[[package]] +name = "urllib3" +version = "1.26.13" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, + {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, ] -virtualenv = [ - {file = "virtualenv-20.16.5-py3-none-any.whl", hash = "sha256:d07dfc5df5e4e0dbc92862350ad87a36ed505b978f6c39609dc489eadd5b0d27"}, - {file = "virtualenv-20.16.5.tar.gz", hash = "sha256:227ea1b9994fdc5ea31977ba3383ef296d7472ea85be9d6732e42a91c04e80da"}, + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "virtualenv" +version = "20.16.7" +description = "Virtual Python Environment builder" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "virtualenv-20.16.7-py3-none-any.whl", hash = "sha256:efd66b00386fdb7dbe4822d172303f40cd05e50e01740b19ea42425cbe653e29"}, + {file = "virtualenv-20.16.7.tar.gz", hash = "sha256:8691e3ff9387f743e00f6bb20f70121f5e4f596cae754531f2b3b3a1b1ac696e"}, ] -webencodings = [ + +[package.dependencies] +distlib = ">=0.3.6,<1" +filelock = ">=3.4.1,<4" +importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""} +platformdirs = ">=2.4,<3" + +[package.extras] +docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] +testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, ] -xattr = [ + +[[package]] +name = "xattr" +version = "0.10.0" +description = "Python wrapper for extended filesystem attributes" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "xattr-0.10.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:5afb5cf3fe728b1e54a8796076787b1dbe192c87eb775233aa02dc26f3495664"}, {file = "xattr-0.10.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:a1e6fb1567a2bfea8096d84de68670cedba94e0a128b89007f35d5415da82805"}, {file = "xattr-0.10.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:a0baed7d2848c020aac6c1d5a6474bd86f43d109d80f73fd269e058612a2b08d"}, @@ -1774,7 +1784,27 @@ xattr = [ {file = "xattr-0.10.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f1d087b76fa325b6051677641da015d6d3af7e1e2f8afb28b869b81c3e33dc"}, {file = "xattr-0.10.0.tar.gz", hash = "sha256:722652d2a5324e17891c416d4c76d91ccf98830a8f516a0de8533ce867f3acaf"}, ] -zipp = [ - {file = "zipp-3.9.0-py3-none-any.whl", hash = "sha256:972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980"}, - {file = "zipp-3.9.0.tar.gz", hash = "sha256:3a7af91c3db40ec72dd9d154ae18e008c69efe8ca88dde4f9a731bb82fe2f9eb"}, + +[package.dependencies] +cffi = ">=1.0" + +[[package]] +name = "zipp" +version = "3.10.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zipp-3.10.0-py3-none-any.whl", hash = "sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1"}, + {file = "zipp-3.10.0.tar.gz", hash = "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8"}, ] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.7" +content-hash = "728bc093f740faad6db2b3a1c7a61ea05ca6c6a998b1f144f071cdea5a3b2771" From b2e20458f85b7bea231faa4fad32099952e074f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Fri, 25 Nov 2022 14:14:06 +0100 Subject: [PATCH 018/151] chore: restrict virtualenv for Python 3.9 on Windows due to issues with the embedded pip --- poetry.lock | 23 ++++++++++++++++++++++- pyproject.toml | 7 ++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7b8db67ea57..62a3d4b01b0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1686,6 +1686,27 @@ brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +[[package]] +name = "virtualenv" +version = "20.16.5" +description = "Virtual Python Environment builder" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "virtualenv-20.16.5-py3-none-any.whl", hash = "sha256:d07dfc5df5e4e0dbc92862350ad87a36ed505b978f6c39609dc489eadd5b0d27"}, + {file = "virtualenv-20.16.5.tar.gz", hash = "sha256:227ea1b9994fdc5ea31977ba3383ef296d7472ea85be9d6732e42a91c04e80da"}, +] + +[package.dependencies] +distlib = ">=0.3.5,<1" +filelock = ">=3.4.1,<4" +platformdirs = ">=2.4,<3" + +[package.extras] +docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"] +testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] + [[package]] name = "virtualenv" version = "20.16.7" @@ -1807,4 +1828,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "728bc093f740faad6db2b3a1c7a61ea05ca6c6a998b1f144f071cdea5a3b2771" +content-hash = "67df799e05fb4afd82e52395a1f0d5caa51cc33232d4311e6b7a4c8991f946a9" diff --git a/pyproject.toml b/pyproject.toml index a34a609aacd..5bd4332266d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,9 +70,14 @@ shellingham = "^1.5" tomli = { version = "^2.0.1", python = "<3.11" } # exclude 0.11.2 and 0.11.3 due to https://github.com/sdispater/tomlkit/issues/225 tomlkit = ">=0.11.1,<1.0.0,!=0.11.2,!=0.11.3" +# trove-classifiers uses calver, so version is unclamped trove-classifiers = ">=2022.5.19" # exclude 20.4.5 - 20.4.6 due to https://github.com/pypa/pip/issues/9953 -virtualenv = "^20.4.3,!=20.4.5,!=20.4.6" +virtualenv = [ + { version = "^20.4.3,!=20.4.5,!=20.4.6", markers = "sys_platform != 'win32' or python_version != '3.9'" }, + # see https://github.com/python-poetry/poetry/pull/6950 for details + { version = "^20.4.3,!=20.4.5,!=20.4.6,<20.16.6", markers = "sys_platform == 'win32' and python_version == '3.9'" }, +] xattr = { version = "^0.10.0", markers = "sys_platform == 'darwin'" } urllib3 = "^1.26.0" From f594246c9fd70ce9c9379afb45c63c19f9123fa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Mon, 5 Dec 2022 16:55:39 +0100 Subject: [PATCH 019/151] env: installing into `MockEnv` should not have side effects on another env (#7141) MockEnv should not use paths of SystemEnv for paths that may be written to --- src/poetry/utils/env.py | 11 +++++++++++ tests/masonry/builders/test_editable_builder.py | 10 ---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index e521ea0c526..74f1ed61b99 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -2039,6 +2039,17 @@ def sys_path(self) -> list[str]: return self._sys_path + @property + def paths(self) -> dict[str, str]: + if self._paths is None: + self._paths = self.get_paths() + self._paths["platlib"] = str(self._path / "platlib") + self._paths["purelib"] = str(self._path / "purelib") + self._paths["scripts"] = str(self._path / "scripts") + self._paths["data"] = str(self._path / "data") + + return self._paths + def get_marker_env(self) -> dict[str, Any]: if self._mock_marker_env is not None: return self._mock_marker_env diff --git a/tests/masonry/builders/test_editable_builder.py b/tests/masonry/builders/test_editable_builder.py index 13abe598e2b..020c9cc13ae 100644 --- a/tests/masonry/builders/test_editable_builder.py +++ b/tests/masonry/builders/test_editable_builder.py @@ -291,16 +291,6 @@ def test_builder_should_execute_build_scripts( mocker: MockerFixture, extended_without_setup_poetry: Poetry, tmp_path: Path ): env = MockEnv(path=tmp_path / "foo") - site_packages_dir = tmp_path / "site-packages" - site_packages_dir.mkdir(parents=True, exist_ok=True) - mocker.patch.object( - env, - "get_paths", - return_value={ - "purelib": str(site_packages_dir), - "platlib": str(site_packages_dir), - }, - ) mocker.patch( "poetry.masonry.builders.editable.build_environment" ).return_value.__enter__.return_value = env From 0ca8b7e9e711af751ff7ceb29fd4d5d6c93d5c12 Mon Sep 17 00:00:00 2001 From: George Waters Date: Wed, 7 Dec 2022 11:24:07 -0500 Subject: [PATCH 020/151] Calculate and store hash for url dependencies (#7121) --- src/poetry/installation/executor.py | 5 +- src/poetry/puzzle/provider.py | 10 ++- src/poetry/utils/helpers.py | 11 +++ .../with-same-version-url-dependencies.test | 9 ++- .../fixtures/with-url-dependency.test | 4 +- tests/utils/test_helpers.py | 76 +++++++++++++++++++ 6 files changed, 107 insertions(+), 8 deletions(-) diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index 0a01fee2b98..6df976f7c45 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -15,7 +15,6 @@ from typing import Any from cleo.io.null_io import NullIO -from poetry.core.packages.file_dependency import FileDependency from poetry.core.packages.utils.link import Link from poetry.core.pyproject.toml import PyProjectTOML @@ -28,6 +27,7 @@ from poetry.utils.authenticator import Authenticator from poetry.utils.env import EnvCommandError from poetry.utils.helpers import atomic_open +from poetry.utils.helpers import get_file_hash from poetry.utils.helpers import pluralize from poetry.utils.helpers import remove_directory from poetry.utils.pip import pip_install @@ -666,8 +666,7 @@ def _download_link(self, operation: Install | Update, link: Link) -> Path: @staticmethod def _validate_archive_hash(archive: Path, package: Package) -> str: - file_dep = FileDependency(package.name, archive) - archive_hash: str = "sha256:" + file_dep.hash() + archive_hash: str = "sha256:" + get_file_hash(archive) known_hashes = {f["hash"] for f in package.files} if archive_hash not in known_hashes: diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index 9c7457f7e7c..cfb50d43b4a 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -33,6 +33,7 @@ from poetry.puzzle.exceptions import OverrideNeeded from poetry.repositories.exceptions import PackageNotFound from poetry.utils.helpers import download_file +from poetry.utils.helpers import get_file_hash from poetry.vcs.git import Git @@ -396,7 +397,10 @@ def _search_for_file(self, dependency: FileDependency) -> Package: package.root_dir = dependency.base package.files = [ - {"file": dependency.path.name, "hash": "sha256:" + dependency.hash()} + { + "file": dependency.path.name, + "hash": "sha256:" + get_file_hash(dependency.full_path), + } ] return package @@ -453,6 +457,10 @@ def get_package_from_url(cls, url: str) -> Package: download_file(url, dest) package = cls.get_package_from_file(dest) + package.files = [ + {"file": file_name, "hash": "sha256:" + get_file_hash(dest)} + ] + package._source_type = "url" package._source_url = url diff --git a/src/poetry/utils/helpers.py b/src/poetry/utils/helpers.py index c4ef660d547..4d6ff50e28a 100644 --- a/src/poetry/utils/helpers.py +++ b/src/poetry/utils/helpers.py @@ -1,5 +1,7 @@ from __future__ import annotations +import hashlib +import io import os import shutil import stat @@ -252,3 +254,12 @@ def get_real_windows_path(path: str | Path) -> Path: path = path.resolve() return path + + +def get_file_hash(path: Path, hash_name: str = "sha256") -> str: + h = hashlib.new(hash_name) + with path.open("rb") as fp: + for content in iter(lambda: fp.read(io.DEFAULT_BUFFER_SIZE), b""): + h.update(content) + + return h.hexdigest() diff --git a/tests/installation/fixtures/with-same-version-url-dependencies.test b/tests/installation/fixtures/with-same-version-url-dependencies.test index e80c72a3d03..bc509936da6 100644 --- a/tests/installation/fixtures/with-same-version-url-dependencies.test +++ b/tests/installation/fixtures/with-same-version-url-dependencies.test @@ -5,7 +5,9 @@ description = "" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [] +files = [ + {file = "demo-0.1.0-py2.py3-none-any.whl", hash = "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a"} +] [package.source] type = "url" @@ -25,8 +27,9 @@ description = "" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [] - +files = [ + {file = "demo-0.1.0.tar.gz", hash = "sha256:72e8531e49038c5f9c4a837b088bfcb8011f4a9f76335c8f0654df6ac539b3d6"} +] [package.source] type = "url" url = "https://python-poetry.org/distributions/demo-0.1.0.tar.gz" diff --git a/tests/installation/fixtures/with-url-dependency.test b/tests/installation/fixtures/with-url-dependency.test index 13cab35bc46..e6878546942 100644 --- a/tests/installation/fixtures/with-url-dependency.test +++ b/tests/installation/fixtures/with-url-dependency.test @@ -5,7 +5,9 @@ description = "" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [] +files = [ + {file = "demo-0.1.0-py2.py3-none-any.whl", hash = "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a"} +] [package.source] type = "url" diff --git a/tests/utils/test_helpers.py b/tests/utils/test_helpers.py index b5ab3cfebdc..835298fa405 100644 --- a/tests/utils/test_helpers.py +++ b/tests/utils/test_helpers.py @@ -1,7 +1,18 @@ from __future__ import annotations +from pathlib import Path +from typing import TYPE_CHECKING + +import pytest + from poetry.core.utils.helpers import parse_requires +from poetry.utils.helpers import get_file_hash + + +if TYPE_CHECKING: + from tests.types import FixtureDirGetter + def test_parse_requires(): requires = """\ @@ -57,3 +68,68 @@ def test_parse_requires(): ] # fmt: on assert result == expected + + +def test_default_hash(fixture_dir: FixtureDirGetter) -> None: + root_dir = Path(__file__).parent.parent.parent + file_path = root_dir / fixture_dir("distributions/demo-0.1.0.tar.gz") + sha_256 = "72e8531e49038c5f9c4a837b088bfcb8011f4a9f76335c8f0654df6ac539b3d6" + assert get_file_hash(file_path) == sha_256 + + +try: + from hashlib import algorithms_guaranteed +except ImportError: + algorithms_guaranteed = {"md5", "sha1", "sha224", "sha256", "sha384", "sha512"} + + +@pytest.mark.parametrize( + "hash_name,expected", + [ + (hash_name, value) + for hash_name, value in [ + ("sha224", "972d02f36539a98599aed0566bc8aaf3e6701f4e895dd797d8f5248e"), + ( + "sha3_512", + "c04ee109ae52d6440445e24dbd6d244a1d0f0289ef79cb7ba9bc3c139c0237169af9a8f61cd1cf4fc17f853ddf84f97c475ac5bb6c91a4aff0b825b884d4896c", # noqa: E501 + ), + ( + "blake2s", + "c336ecbc9d867c9d860accfba4c3723c51c4b5c47a1e0a955e1c8df499e36741", + ), + ( + "sha3_384", + "d4abb2459941369aabf8880c5287b7eeb80678e14f13c71b9ecf64c772029dc3f93939590bea9ecdb51a1d1a74fefc5a", # noqa: E501 + ), + ( + "blake2b", + "48e70abac547ab38e2330e6e6743a0c0f6274dcaa6df2c98135a78a9dd5b04a072d551fc3851b34da03eb0bf50dd71c7f32a8c36956e99fd6c66491bc7844800", # noqa: E501 + ), + ( + "sha256", + "72e8531e49038c5f9c4a837b088bfcb8011f4a9f76335c8f0654df6ac539b3d6", + ), + ( + "sha512", + "e08a00a4b86358e49a318e7e3ba7a3d2fabdd17a2fef95559a0af681ea07ab1296b0b8e11e645297da296290661dc07ae3c8f74eab66bd18a80dce0c0ccb355b", # noqa: E501 + ), + ( + "sha384", + "aa3144e28c6700a83247e8ec8711af5d3f5f75997990d48ec41e66bd275b3d0e19ee6f2fe525a358f874aa717afd06a9", # noqa: E501 + ), + ("sha3_224", "64bfc6e4125b4c6d67fd88ad1c7d1b5c4dc11a1970e433cd576c91d4"), + ("sha1", "4c057579005ac3e68e951a11ffdc4b27c6ae16af"), + ( + "sha3_256", + "ba3d2a964b0680b6dc9565a03952e29c294c785d5a2307d3e2d785d73b75ed7e", + ), + ] + if hash_name in algorithms_guaranteed + ], +) +def test_guaranteed_hash( + hash_name: str, expected: str, fixture_dir: FixtureDirGetter +) -> None: + root_dir = Path(__file__).parent.parent.parent + file_path = root_dir / fixture_dir("distributions/demo-0.1.0.tar.gz") + assert get_file_hash(file_path, hash_name) == expected From 528111863d5447c25b96f402e0d6ed39f2c8eec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sat, 3 Dec 2022 13:57:18 +0100 Subject: [PATCH 021/151] chore: pin poetry-core for release --- poetry.lock | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 62a3d4b01b0..49c84d8f5b0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1828,4 +1828,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "67df799e05fb4afd82e52395a1f0d5caa51cc33232d4311e6b7a4c8991f946a9" +content-hash = "c1c21f02e493ffbfd622bec8546c83edad08325b957851dd3f9a287867ce24a4" diff --git a/pyproject.toml b/pyproject.toml index 5bd4332266d..c05a4ed049a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ generate-setup-file = false [tool.poetry.dependencies] python = "^3.7" -poetry-core = "^1.4.0" +poetry-core = "1.4.0" poetry-plugin-export = "^1.2.0" "backports.cached-property" = { version = "^1.0.2", python = "<3.8" } cachecontrol = { version = "^0.12.9", extras = ["filecache"] } From aa48815d2253e318d6e70c9a15fc4def0b48c870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sat, 3 Dec 2022 13:58:47 +0100 Subject: [PATCH 022/151] release: bump version to 1.3.0 --- CHANGELOG.md | 86 +++++++++++++++++++++++++++++++++++++++++++++----- pyproject.toml | 2 +- 2 files changed, 79 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3635a702289..27e120ce710 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,74 @@ # Change Log + +## [1.3.0] - 2022-12-09 + +### Added + +- Mark the lock file with an `@generated` comment as used by common tooling ([#2773](https://github.com/python-poetry/poetry/pull/2773)). +- `poetry check` validates trove classifiers and warns for deprecations ([#2881](https://github.com/python-poetry/poetry/pull/2881)). +- Introduce a top level `-C, --directory` option to set the working path ([#6810](https://github.com/python-poetry/poetry/pull/6810)). + +### Changed +- **New lock file format (version 2.0)** ([#6393](https://github.com/python-poetry/poetry/pull/6393)). +- Path dependency metadata is unconditionally re-locked ([#6843](https://github.com/python-poetry/poetry/pull/6843)). +- URL dependency hashes are locked ([#7121](https://github.com/python-poetry/poetry/pull/7121)). +- `poetry update` and `poetry lock` should now resolve dependencies more similarly ([#6477](https://github.com/python-poetry/poetry/pull/6477)). +- `poetry publish` will report more useful errors when a file does not exist ([#4417](https://github.com/python-poetry/poetry/pull/4417)). +- `poetry add` will check for duplicate entries using canonical names ([#6832](https://github.com/python-poetry/poetry/pull/6832)). +- Wheels are preferred to source distributions when gathering metadata ([#6547](https://github.com/python-poetry/poetry/pull/6547)). +- Git dependencies of extras are only fetched if the extra is requested ([#6615](https://github.com/python-poetry/poetry/pull/6615)). +- Invoke `pip` with `--no-input` to prevent hanging without feedback ([#6724](https://github.com/python-poetry/poetry/pull/6724), [#6966](https://github.com/python-poetry/poetry/pull/6966)). +- Invoke `pip` with `--isolated` to prevent the influence of user configuration ([#6531](https://github.com/python-poetry/poetry/pull/6531)). +- Interrogate environments with Python in isolated (`-I`) mode ([#6628](https://github.com/python-poetry/poetry/pull/6628)). +- Raise an informative error when multiple version constraints overlap and are incompatible ([#7098](https://github.com/python-poetry/poetry/pull/7098)). + +### Fixed + +- **Fix an issue where concurrent instances of Poetry would corrupt the artifact cache** ([#6186](https://github.com/python-poetry/poetry/pull/6186)). +- **Fix an issue where Poetry can hang after being interrupted due to stale locking in cache** ([#6471](https://github.com/python-poetry/poetry/pull/6471)). +- Fix an issue where the output of commands executed with `--dry-run` contained duplicate entries ([#4660](https://github.com/python-poetry/poetry/pull/4660)). +- Fix an issue where `requests`'s pool size did not match the number of installer workers ([#6805](https://github.com/python-poetry/poetry/pull/6805)). +- Fix an issue where `poetry show --outdated` failed with a runtime error related to direct origin dependencies ([#6016](https://github.com/python-poetry/poetry/pull/6016)). +- Fix an issue where only the last command of an `ApplicationPlugin` is registered ([#6304](https://github.com/python-poetry/poetry/pull/6304)). +- Fix an issue where git dependencies were fetched unnecessarily when running `poetry lock --no-update` ([#6131](https://github.com/python-poetry/poetry/pull/6131)). +- Fix an issue where stdout was polluted with messages that should go to stderr ([#6429](https://github.com/python-poetry/poetry/pull/6429)). +- Fix an issue with `poetry shell` activation and zsh ([#5795](https://github.com/python-poetry/poetry/pull/5795)). +- Fix an issue where a url dependencies were shown as outdated ([#6396](https://github.com/python-poetry/poetry/pull/6396)). +- Fix an issue where the `source` field of a dependency with extras was ignored ([#6472](https://github.com/python-poetry/poetry/pull/6472)). +- Fix an issue where a package from the wrong source was installed for a multiple-constraints dependency with different sources ([#6747](https://github.com/python-poetry/poetry/pull/6747)). +- Fix an issue where dependencies from different sources where merged during dependency resolution ([#6679](https://github.com/python-poetry/poetry/pull/6679)). +- Fix an issue where `experimental.system-git-client` could not be used via environment variable ([#6783](https://github.com/python-poetry/poetry/pull/6783)). +- Fix an issue where Poetry fails with an `AssertionError` due to `distribution.files` being `None` ([#6788](https://github.com/python-poetry/poetry/pull/6788)). +- Fix an issue where `poetry env info` did not respect `virtualenvs.prefer-active-python` ([#6986](https://github.com/python-poetry/poetry/pull/6986)). +- Fix an issue where `poetry env list` does not list the in-project environment ([#6979](https://github.com/python-poetry/poetry/pull/6979)). +- Fix an issue where `poetry env remove` removed the wrong environment ([#6195](https://github.com/python-poetry/poetry/pull/6195)). +- Fix an issue where the return code of a script was not relayed as exit code ([#6824](https://github.com/python-poetry/poetry/pull/6824)). +- Fix an issue where the solver could silently swallow `ValueError` ([#6790](https://github.com/python-poetry/poetry/pull/6790)). + +### Docs + +- Improve documentation of package sources ([#5605](https://github.com/python-poetry/poetry/pull/5605)). +- Correct the default cache path on Windows ([#7012](https://github.com/python-poetry/poetry/pull/7012)). + +### poetry-core ([`1.4.0`](https://github.com/python-poetry/poetry-core/releases/tag/1.4.0)) + +- The PEP 517 `metadata_directory` is now respected as an input to the `build_wheel` hook ([#487](https://github.com/python-poetry/poetry-core/pull/487)). +- `ParseConstraintError` is now raised on version and constraint parsing errors, and includes information on the package that caused the error ([#514](https://github.com/python-poetry/poetry-core/pull/514)). +- Fix an issue where invalid PEP 508 requirements were generated due to a missing space before semicolons ([#510](https://github.com/python-poetry/poetry-core/pull/510)). +- Fix an issue where relative paths were encoded into package requirements, instead of a file:// URL as required by PEP 508 ([#512](https://github.com/python-poetry/poetry-core/pull/512)). + +### poetry-plugin-export ([`^1.2.0`](https://github.com/python-poetry/poetry-plugin-export/releases/tag/1.2.0)) + +- Ensure compatibility with Poetry 1.3.0. No functional changes. + +### cleo ([`^2.0.0`](https://github.com/python-poetry/poetry-core/releases/tag/2.0.0)) + +- Fix an issue where shell completions had syntax errors ([#247](https://github.com/python-poetry/cleo/pull/247)). +- Fix an issue where not reading all the output of a command resulted in a "Broken pipe" error ([#165](https://github.com/python-poetry/cleo/pull/165)). +- Fix an issue where errors were not shown in non-verbose mode ([#166](https://github.com/python-poetry/cleo/pull/166)). + + ## [1.2.2] - 2022-10-10 ### Added @@ -83,25 +152,25 @@ ### Docs - Added note about how to add a git dependency with a subdirectory ([#6218](https://github.com/python-poetry/poetry/pull/6218)) -- Fixed several style issues in the docs ([#6255](https://github.com/python-poetry/poetry/pull/6255)) -- Fixed outdated info about `--only` parameter ([#6264](https://github.com/python-poetry/poetry/pull/6264)) +- Fixed several style issues in the docs ([#6254](https://github.com/python-poetry/poetry/pull/6254)) +- Fixed outdated info about `--only` parameter ([#6263](https://github.com/python-poetry/poetry/pull/6263)) ## [1.2.0rc2] - 2022-08-26 ### Fixed -- Fixed an issue where virtual environments were created unnecessarily when running `poetry self` commands ([#6226](https://github.com/python-poetry/poetry/pull/6226)) -- Ensure that packages' `pretty_name` are written to the lock file ([#6243](https://github.com/python-poetry/poetry/pull/6243)) +- Fixed an issue where virtual environments were created unnecessarily when running `poetry self` commands ([#6225](https://github.com/python-poetry/poetry/pull/6225)) +- Ensure that packages' `pretty_name` are written to the lock file ([#6237](https://github.com/python-poetry/poetry/pull/6237)) ### Improvements -- Improved the consistency of `Pool().remove_repository()` to make it easier to write poetry plugins ([#6231](https://github.com/python-poetry/poetry/pull/6231)) +- Improved the consistency of `Pool().remove_repository()` to make it easier to write poetry plugins ([#6214](https://github.com/python-poetry/poetry/pull/6214)) ### Docs -- Removed mentions of Python 2.7 from docs ([#6235](https://github.com/python-poetry/poetry/pull/6235)) -- Added note about the difference between groups and extras ([#6232](https://github.com/python-poetry/poetry/pull/6232)) +- Removed mentions of Python 2.7 from docs ([#6234](https://github.com/python-poetry/poetry/pull/6234)) +- Added note about the difference between groups and extras ([#6230](https://github.com/python-poetry/poetry/pull/6230)) ## [1.2.0rc1] - 2022-08-22 @@ -1612,7 +1681,8 @@ Initial release -[Unreleased]: https://github.com/python-poetry/poetry/compare/1.2.2...master +[Unreleased]: https://github.com/python-poetry/poetry/compare/1.3.0...master +[1.3.0]: https://github.com/python-poetry/poetry/releases/tag/1.3.0 [1.2.2]: https://github.com/python-poetry/poetry/releases/tag/1.2.2 [1.2.1]: https://github.com/python-poetry/poetry/releases/tag/1.2.1 [1.2.0]: https://github.com/python-poetry/poetry/releases/tag/1.2.0 diff --git a/pyproject.toml b/pyproject.toml index c05a4ed049a..59b21880ee1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "poetry" -version = "1.3.0.dev0" +version = "1.3.0" description = "Python dependency management and packaging made easy." authors = [ "Sébastien Eustace ", From 1b35296229bf006813e16829e3b6dcae2915606e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sat, 10 Dec 2022 18:40:37 +0100 Subject: [PATCH 023/151] chore: bump version to 1.4.0.dev0 (#7168) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 59b21880ee1..4504c94708c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "poetry" -version = "1.3.0" +version = "1.4.0.dev0" description = "Python dependency management and packaging made easy." authors = [ "Sébastien Eustace ", From e4dad2295f77e4845520ac6a141ac2fc9899e686 Mon Sep 17 00:00:00 2001 From: Tom Forbes Date: Sun, 11 Dec 2022 10:51:59 +0000 Subject: [PATCH 024/151] Add lockfile as an explicit dependency (#7169) --- poetry.lock | 2 +- pyproject.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 49c84d8f5b0..b5d1d5dd642 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1828,4 +1828,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "c1c21f02e493ffbfd622bec8546c83edad08325b957851dd3f9a287867ce24a4" +content-hash = "7b52d7192276e1e1ab4667723a0911fbecd54bbf3ed1cd2621a654bb121eb49f" diff --git a/pyproject.toml b/pyproject.toml index 4504c94708c..4bc5cbb99b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ html5lib = "^1.0" importlib-metadata = { version = "^4.4", python = "<3.10" } jsonschema = "^4.10.0" keyring = "^23.9.0" +lockfile = "^0.12.2" # packaging uses calver, so version is unclamped packaging = ">=20.4" pexpect = "^4.7.0" From 0c8e5b43d30d86d6ba7dc9b3068db26042e9cc53 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 11 Dec 2022 11:54:07 +0000 Subject: [PATCH 025/151] tests: make tests forward compatible with simplified marker simplification (#7136) --- tests/repositories/test_pypi_repository.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/repositories/test_pypi_repository.py b/tests/repositories/test_pypi_repository.py index 7396324af5a..4d4e59e2539 100644 --- a/tests/repositories/test_pypi_repository.py +++ b/tests/repositories/test_pypi_repository.py @@ -139,11 +139,18 @@ def test_package() -> None: win_inet = package.extras["socks"][0] assert win_inet.name == "win-inet-pton" assert win_inet.python_versions == "~2.7 || ~2.6" - assert ( - str(win_inet.marker) - == 'sys_platform == "win32" and (python_version == "2.7"' - ' or python_version == "2.6") and extra == "socks"' + + # Different versions of poetry-core simplify the following marker differently, + # either is fine. + marker1 = ( + 'sys_platform == "win32" and (python_version == "2.7" or python_version ==' + ' "2.6") and extra == "socks"' + ) + marker2 = ( + 'sys_platform == "win32" and python_version == "2.7" and extra == "socks" or' + ' sys_platform == "win32" and python_version == "2.6" and extra == "socks"' ) + assert str(win_inet.marker) in {marker1, marker2} @pytest.mark.parametrize( From 128c528f392cd79b3f19f0dbf09b6e4c74809e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Mon, 12 Dec 2022 05:51:50 +0100 Subject: [PATCH 026/151] solver: fix a special case with unconditional dependency of an extra (#7175) Co-authored-by: David Hotham --- src/poetry/puzzle/provider.py | 18 ++++++---------- src/poetry/puzzle/solver.py | 10 ++++++++- tests/puzzle/test_solver.py | 39 +++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index cfb50d43b4a..69302a94acb 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -20,6 +20,7 @@ from poetry.core.constraints.version import Version from poetry.core.packages.utils.utils import get_python_constraint_from_marker from poetry.core.version.markers import AnyMarker +from poetry.core.version.markers import EmptyMarker from poetry.core.version.markers import MarkerUnion from poetry.inspection.info import PackageInfo @@ -946,20 +947,13 @@ def _merge_dependencies_by_constraint( for dep in dependencies: by_constraint[dep.constraint].append(dep) for constraint, _deps in by_constraint.items(): - new_markers = [] - for dep in _deps: - marker = dep.marker.without_extras() - if marker.is_any(): - # No marker or only extras - continue - - new_markers.append(marker) - - if not new_markers: - continue + new_markers = [dep.marker for dep in _deps] dep = _deps[0] - dep.marker = dep.marker.union(MarkerUnion(*new_markers)) + + # Union with EmptyMarker is to make sure we get the benefit of marker + # simplifications. + dep.marker = MarkerUnion(*new_markers).union(EmptyMarker()) by_constraint[constraint] = [dep] return [value[0] for value in by_constraint.values()] diff --git a/src/poetry/puzzle/solver.py b/src/poetry/puzzle/solver.py index d38a2e0c277..66e1aca6a5b 100644 --- a/src/poetry/puzzle/solver.py +++ b/src/poetry/puzzle/solver.py @@ -181,8 +181,16 @@ def _solve(self) -> tuple[list[Package], list[int]]: if _package.name == dep.name: continue - if dep not in _package.requires: + try: + index = _package.requires.index(dep) + except ValueError: _package.add_dependency(dep) + else: + _dep = _package.requires[index] + if _dep.marker != dep.marker: + # marker of feature package is more accurate + # because it includes relevant extras + _dep.marker = dep.marker else: final_packages.append(package) depths.append(results[package]) diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 04f2dd70b35..0018da79da5 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -791,6 +791,45 @@ def test_solver_merge_extras_into_base_package_multiple_repos_fixes_5727( assert len(ops[0].package.requires) == 0, "a should not require itself" +def test_solver_returns_extras_if_excluded_by_markers_without_extras( + solver: Solver, repo: Repository, package: ProjectPackage +): + package.add_dependency( + Factory.create_dependency("A", {"version": "*", "extras": ["foo"]}) + ) + + package_a = get_package("A", "1.0") + package_b = get_package("B", "1.0") + + # mandatory dependency with marker + dep = get_dependency("B", "^1.0") + dep.marker = parse_marker("sys_platform != 'linux'") + package_a.add_dependency(dep) + + # optional dependency with same constraint and no marker except for extra + dep = get_dependency("B", "^1.0", optional=True) + dep.marker = parse_marker("extra == 'foo'") + package_a.extras = {"foo": [dep]} + package_a.add_dependency(dep) + + repo.add_package(package_a) + repo.add_package(package_b) + + transaction = solver.solve() + + ops = check_solver_result( + transaction, + [ + {"job": "install", "package": package_b}, + {"job": "install", "package": package_a}, + ], + ) + assert ( + str(ops[1].package.requires[0].marker) + == 'sys_platform != "linux" or extra == "foo"' + ) + + def test_solver_returns_prereleases_if_requested( solver: Solver, repo: Repository, package: ProjectPackage ): From b95ee16d5b455be80985d56e1fc6e6f4efca22b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Thu, 15 Dec 2022 20:06:48 +0100 Subject: [PATCH 027/151] ci: check lock file --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index be4d61dde3f..07d6b32a62b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -83,6 +83,9 @@ jobs: # Using `timeout` is a safeguard against the Poetry command hanging for some reason. timeout 10s poetry run pip --version || rm -rf .venv + - name: Check lock file + run: poetry lock --check + - name: Install dependencies run: poetry install --with github-actions From c0b1e379fa492abae020cb6781526769db0ebb8a Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 4 Sep 2022 14:33:22 +0100 Subject: [PATCH 028/151] set lookup is more efficient than list lookup --- src/poetry/installation/chooser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/poetry/installation/chooser.py b/src/poetry/installation/chooser.py index a5432cdd386..bca81274b39 100644 --- a/src/poetry/installation/chooser.py +++ b/src/poetry/installation/chooser.py @@ -120,7 +120,7 @@ def _get_links(self, package: Package) -> list[Link]: repository = self._pool.repository("pypi") links = repository.find_links_for_package(package) - hashes = [f["hash"] for f in package.files] + hashes = {f["hash"] for f in package.files} if not hashes: return links From 1667392525444afc82c8f279b58c5c205b5bf998 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 23 Oct 2022 13:11:07 +0100 Subject: [PATCH 029/151] more precise exception handling --- src/poetry/console/commands/init.py | 2 +- src/poetry/console/commands/version.py | 3 ++- src/poetry/inspection/info.py | 13 ++++++++++--- src/poetry/repositories/link_sources/base.py | 3 ++- src/poetry/version/version_selector.py | 13 ------------- 5 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index 329afc4d374..7b032aada66 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -413,7 +413,7 @@ def _find_best_version_for_package( # TODO: find similar raise ValueError(f"Could not find a matching version of package {name}") - return package.pretty_name, selector.find_recommended_require_version(package) + return package.pretty_name, f"^{package.version.to_string()}" def _parse_requirements(self, requirements: list[str]) -> list[dict[str, Any]]: from poetry.core.pyproject.exceptions import PyProjectException diff --git a/src/poetry/console/commands/version.py b/src/poetry/console/commands/version.py index c494879c4df..b6953c99c87 100644 --- a/src/poetry/console/commands/version.py +++ b/src/poetry/console/commands/version.py @@ -5,6 +5,7 @@ from cleo.helpers import argument from cleo.helpers import option +from poetry.core.version.exceptions import InvalidVersion from tomlkit.toml_document import TOMLDocument from poetry.console.commands.command import Command @@ -95,7 +96,7 @@ def increment_version(self, version: str, rule: str) -> Version: try: parsed = Version.parse(version) - except ValueError: + except InvalidVersion: raise ValueError("The project's version doesn't seem to follow semver") if rule in {"major", "premajor"}: diff --git a/src/poetry/inspection/info.py b/src/poetry/inspection/info.py index 54372b54176..90028c0d355 100644 --- a/src/poetry/inspection/info.py +++ b/src/poetry/inspection/info.py @@ -21,6 +21,7 @@ from poetry.core.utils.helpers import parse_requires from poetry.core.utils.helpers import temporary_directory from poetry.core.version.markers import InvalidMarker +from poetry.core.version.requirements import InvalidRequirement from poetry.utils.env import EnvCommandError from poetry.utils.env import ephemeral_environment @@ -201,12 +202,18 @@ def to_package( dependency = Dependency.create_from_pep_508(req, relative_to=root_dir) except InvalidMarker: # Invalid marker, We strip the markers hoping for the best + logger.warning( + "Stripping invalid marker (%s) found in %s-%s dependencies", + req, + package.name, + package.version, + ) req = req.split(";")[0] dependency = Dependency.create_from_pep_508(req, relative_to=root_dir) - except ValueError: - # Likely unable to parse constraint so we skip it + except InvalidRequirement: + # Unable to parse requirement so we skip it logger.warning( - "Invalid constraint (%s) found in %s-%s dependencies, skipping", + "Invalid requirement (%s) found in %s-%s dependencies, skipping", req, package.name, package.version, diff --git a/src/poetry/repositories/link_sources/base.py b/src/poetry/repositories/link_sources/base.py index 5f9a26c3048..975553eec40 100644 --- a/src/poetry/repositories/link_sources/base.py +++ b/src/poetry/repositories/link_sources/base.py @@ -9,6 +9,7 @@ from poetry.core.constraints.version import Version from poetry.core.packages.package import Package +from poetry.core.version.exceptions import InvalidVersion from poetry.utils._compat import cached_property from poetry.utils.patterns import sdist_file_re @@ -84,7 +85,7 @@ def link_package_data(cls, link: Link) -> Package | None: if version_string: try: version = Version.parse(version_string) - except ValueError: + except InvalidVersion: logger.debug( "Skipping url (%s) due to invalid version (%s)", link.url, version ) diff --git a/src/poetry/version/version_selector.py b/src/poetry/version/version_selector.py index d00e52f3c7c..195e83bd1a8 100644 --- a/src/poetry/version/version_selector.py +++ b/src/poetry/version/version_selector.py @@ -2,8 +2,6 @@ from typing import TYPE_CHECKING -from poetry.core.constraints.version import Version - if TYPE_CHECKING: from poetry.core.packages.package import Package @@ -56,14 +54,3 @@ def find_best_candidate( package = candidate return package - - def find_recommended_require_version(self, package: Package) -> str: - version = package.version - - return self._transform_version(version.text, package.pretty_version) - - def _transform_version(self, version: str, pretty_version: str) -> str: - try: - return f"^{Version.parse(version).to_string()}" - except ValueError: - return pretty_version From b48c025d90395bc5bf380f56a083e7ad17feaf66 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 13 Nov 2022 19:37:09 +0000 Subject: [PATCH 030/151] remove dead code per TODO --- src/poetry/json/__init__.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/poetry/json/__init__.py b/src/poetry/json/__init__.py index 1e237794380..1a47e0fab7b 100644 --- a/src/poetry/json/__init__.py +++ b/src/poetry/json/__init__.py @@ -42,13 +42,9 @@ def validate_object(obj: dict[str, Any]) -> list[str]: Path(CORE_SCHEMA_DIR, "poetry-schema.json").read_text(encoding="utf-8") ) - if core_schema["additionalProperties"]: - # TODO: make this un-conditional once core update to >1.1.0b2 - properties = {*schema["properties"].keys(), *core_schema["properties"].keys()} - additional_properties = set(obj.keys()) - properties - for key in additional_properties: - errors.append( - f"Additional properties are not allowed ('{key}' was unexpected)" - ) + properties = {*schema["properties"].keys(), *core_schema["properties"].keys()} + additional_properties = set(obj.keys()) - properties + for key in additional_properties: + errors.append(f"Additional properties are not allowed ('{key}' was unexpected)") return errors From 2a5fe375b1a41d449a62ab2845781450d70b5e5f Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sat, 3 Dec 2022 14:39:37 +0000 Subject: [PATCH 031/151] update pep517 dependencies --- src/poetry/inspection/info.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/poetry/inspection/info.py b/src/poetry/inspection/info.py index 90028c0d355..6b6fc2d86ad 100644 --- a/src/poetry/inspection/info.py +++ b/src/poetry/inspection/info.py @@ -41,7 +41,7 @@ PEP517_META_BUILD = """\ import build import build.env -import pep517 +import pyproject_hooks source = '{source}' dest = '{dest}' @@ -51,14 +51,14 @@ srcdir=source, scripts_dir=env.scripts_dir, python_executable=env.executable, - runner=pep517.quiet_subprocess_runner, + runner=pyproject_hooks.quiet_subprocess_runner, ) env.install(builder.build_system_requires) env.install(builder.get_requires_for_build('wheel')) builder.metadata_path(dest) """ -PEP517_META_BUILD_DEPS = ["build===0.7.0", "pep517==0.12.0"] +PEP517_META_BUILD_DEPS = ["build==0.9.0", "pyproject_hooks==1.0.0"] class PackageInfoError(ValueError): From e4643b0432935d03ad097bca2a059f27b7ab8909 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sat, 3 Dec 2022 15:36:02 +0000 Subject: [PATCH 032/151] update dependencies --- poetry.lock | 524 +++++++++++++++++++++++-------------------------- pyproject.toml | 6 +- 2 files changed, 250 insertions(+), 280 deletions(-) diff --git a/poetry.lock b/poetry.lock index b5d1d5dd642..c688dab5f3b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -70,14 +70,14 @@ redis = ["redis (>=3.3.6,<4.0.0)"] [[package]] name = "certifi" -version = "2022.9.24" +version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, - {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, ] [[package]] @@ -292,38 +292,38 @@ files = [ [[package]] name = "cryptography" -version = "38.0.3" +version = "38.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "cryptography-38.0.3-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:984fe150f350a3c91e84de405fe49e688aa6092b3525f407a18b9646f6612320"}, - {file = "cryptography-38.0.3-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:ed7b00096790213e09eb11c97cc6e2b757f15f3d2f85833cd2d3ec3fe37c1722"}, - {file = "cryptography-38.0.3-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:bbf203f1a814007ce24bd4d51362991d5cb90ba0c177a9c08825f2cc304d871f"}, - {file = "cryptography-38.0.3-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:554bec92ee7d1e9d10ded2f7e92a5d70c1f74ba9524947c0ba0c850c7b011828"}, - {file = "cryptography-38.0.3-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b52c9e5f8aa2b802d48bd693190341fae201ea51c7a167d69fc48b60e8a959"}, - {file = "cryptography-38.0.3-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:728f2694fa743a996d7784a6194da430f197d5c58e2f4e278612b359f455e4a2"}, - {file = "cryptography-38.0.3-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dfb4f4dd568de1b6af9f4cda334adf7d72cf5bc052516e1b2608b683375dd95c"}, - {file = "cryptography-38.0.3-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5419a127426084933076132d317911e3c6eb77568a1ce23c3ac1e12d111e61e0"}, - {file = "cryptography-38.0.3-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9b24bcff7853ed18a63cfb0c2b008936a9554af24af2fb146e16d8e1aed75748"}, - {file = "cryptography-38.0.3-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:25c1d1f19729fb09d42e06b4bf9895212292cb27bb50229f5aa64d039ab29146"}, - {file = "cryptography-38.0.3-cp36-abi3-win32.whl", hash = "sha256:7f836217000342d448e1c9a342e9163149e45d5b5eca76a30e84503a5a96cab0"}, - {file = "cryptography-38.0.3-cp36-abi3-win_amd64.whl", hash = "sha256:c46837ea467ed1efea562bbeb543994c2d1f6e800785bd5a2c98bc096f5cb220"}, - {file = "cryptography-38.0.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06fc3cc7b6f6cca87bd56ec80a580c88f1da5306f505876a71c8cfa7050257dd"}, - {file = "cryptography-38.0.3-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:65535bc550b70bd6271984d9863a37741352b4aad6fb1b3344a54e6950249b55"}, - {file = "cryptography-38.0.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5e89468fbd2fcd733b5899333bc54d0d06c80e04cd23d8c6f3e0542358c6060b"}, - {file = "cryptography-38.0.3-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6ab9516b85bebe7aa83f309bacc5f44a61eeb90d0b4ec125d2d003ce41932d36"}, - {file = "cryptography-38.0.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:068147f32fa662c81aebab95c74679b401b12b57494872886eb5c1139250ec5d"}, - {file = "cryptography-38.0.3-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:402852a0aea73833d982cabb6d0c3bb582c15483d29fb7085ef2c42bfa7e38d7"}, - {file = "cryptography-38.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b1b35d9d3a65542ed2e9d90115dfd16bbc027b3f07ee3304fc83580f26e43249"}, - {file = "cryptography-38.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6addc3b6d593cd980989261dc1cce38263c76954d758c3c94de51f1e010c9a50"}, - {file = "cryptography-38.0.3-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:be243c7e2bfcf6cc4cb350c0d5cdf15ca6383bbcb2a8ef51d3c9411a9d4386f0"}, - {file = "cryptography-38.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78cf5eefac2b52c10398a42765bfa981ce2372cbc0457e6bf9658f41ec3c41d8"}, - {file = "cryptography-38.0.3-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:4e269dcd9b102c5a3d72be3c45d8ce20377b8076a43cbed6f660a1afe365e436"}, - {file = "cryptography-38.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8d41a46251bf0634e21fac50ffd643216ccecfaf3701a063257fe0b2be1b6548"}, - {file = "cryptography-38.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:785e4056b5a8b28f05a533fab69febf5004458e20dad7e2e13a3120d8ecec75a"}, - {file = "cryptography-38.0.3.tar.gz", hash = "sha256:bfbe6ee19615b07a98b1d2287d6a6073f734735b49ee45b11324d85efc4d5cbd"}, + {file = "cryptography-38.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:2fa36a7b2cc0998a3a4d5af26ccb6273f3df133d61da2ba13b3286261e7efb70"}, + {file = "cryptography-38.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:1f13ddda26a04c06eb57119caf27a524ccae20533729f4b1e4a69b54e07035eb"}, + {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2ec2a8714dd005949d4019195d72abed84198d877112abb5a27740e217e0ea8d"}, + {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50a1494ed0c3f5b4d07650a68cd6ca62efe8b596ce743a5c94403e6f11bf06c1"}, + {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a10498349d4c8eab7357a8f9aa3463791292845b79597ad1b98a543686fb1ec8"}, + {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:10652dd7282de17990b88679cb82f832752c4e8237f0c714be518044269415db"}, + {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bfe6472507986613dc6cc00b3d492b2f7564b02b3b3682d25ca7f40fa3fd321b"}, + {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ce127dd0a6a0811c251a6cddd014d292728484e530d80e872ad9806cfb1c5b3c"}, + {file = "cryptography-38.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:53049f3379ef05182864d13bb9686657659407148f901f3f1eee57a733fb4b00"}, + {file = "cryptography-38.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:8a4b2bdb68a447fadebfd7d24855758fe2d6fecc7fed0b78d190b1af39a8e3b0"}, + {file = "cryptography-38.0.4-cp36-abi3-win32.whl", hash = "sha256:1d7e632804a248103b60b16fb145e8df0bc60eed790ece0d12efe8cd3f3e7744"}, + {file = "cryptography-38.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:8e45653fb97eb2f20b8c96f9cd2b3a0654d742b47d638cf2897afbd97f80fa6d"}, + {file = "cryptography-38.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca57eb3ddaccd1112c18fc80abe41db443cc2e9dcb1917078e02dfa010a4f353"}, + {file = "cryptography-38.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:c9e0d79ee4c56d841bd4ac6e7697c8ff3c8d6da67379057f29e66acffcd1e9a7"}, + {file = "cryptography-38.0.4-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0e70da4bdff7601b0ef48e6348339e490ebfb0cbe638e083c9c41fb49f00c8bd"}, + {file = "cryptography-38.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:998cd19189d8a747b226d24c0207fdaa1e6658a1d3f2494541cb9dfbf7dcb6d2"}, + {file = "cryptography-38.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67461b5ebca2e4c2ab991733f8ab637a7265bb582f07c7c88914b5afb88cb95b"}, + {file = "cryptography-38.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:4eb85075437f0b1fd8cd66c688469a0c4119e0ba855e3fef86691971b887caf6"}, + {file = "cryptography-38.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3178d46f363d4549b9a76264f41c6948752183b3f587666aff0555ac50fd7876"}, + {file = "cryptography-38.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6391e59ebe7c62d9902c24a4d8bcbc79a68e7c4ab65863536127c8a9cd94043b"}, + {file = "cryptography-38.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:78e47e28ddc4ace41dd38c42e6feecfdadf9c3be2af389abbfeef1ff06822285"}, + {file = "cryptography-38.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fb481682873035600b5502f0015b664abc26466153fab5c6bc92c1ea69d478b"}, + {file = "cryptography-38.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:4367da5705922cf7070462e964f66e4ac24162e22ab0a2e9d31f1b270dd78083"}, + {file = "cryptography-38.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b4cad0cea995af760f82820ab4ca54e5471fc782f70a007f31531957f43e9dee"}, + {file = "cryptography-38.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:80ca53981ceeb3241998443c4964a387771588c4e4a5d92735a493af868294f9"}, + {file = "cryptography-38.0.4.tar.gz", hash = "sha256:175c1a818b87c9ac80bb7377f5520b7f31b3ef2a0004e2420319beadedb67290"}, ] [package.dependencies] @@ -339,21 +339,21 @@ test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0 [[package]] name = "deepdiff" -version = "5.8.1" -description = "Deep Difference and Search of any Python object/data." +version = "6.2.2" +description = "Deep Difference and Search of any Python object/data. Recreate objects by adding adding deltas to each other." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "deepdiff-5.8.1-py3-none-any.whl", hash = "sha256:e9aea49733f34fab9a0897038d8f26f9d94a97db1790f1b814cced89e9e0d2b7"}, - {file = "deepdiff-5.8.1.tar.gz", hash = "sha256:8d4eb2c4e6cbc80b811266419cb71dd95a157094a3947ccf937a94d44943c7b8"}, + {file = "deepdiff-6.2.2-py3-none-any.whl", hash = "sha256:dea62316741f86c1d8e946f47c4c21386788457c898a495a5e6b0ccdcd76d9b6"}, + {file = "deepdiff-6.2.2.tar.gz", hash = "sha256:d04d997a68bf8bea01f8a97395877314ef5c2131d8f57bba2295f3adda725282"}, ] [package.dependencies] -ordered-set = ">=4.1.0,<4.2.0" +ordered-set = ">=4.0.2,<4.2.0" [package.extras] -cli = ["clevercsv (==0.7.1)", "click (==8.0.3)", "pyyaml (==5.4.1)", "toml (==0.10.2)"] +cli = ["click (==8.1.3)", "pyyaml (==6.0)"] [[package]] name = "distlib" @@ -481,19 +481,19 @@ testing = ["pre-commit"] [[package]] name = "filelock" -version = "3.8.0" +version = "3.8.2" description = "A platform independent file lock." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"}, - {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"}, + {file = "filelock-3.8.2-py3-none-any.whl", hash = "sha256:8df285554452285f79c035efb0c861eb33a4bcfa5b7a137016e32e6a90f9792c"}, + {file = "filelock-3.8.2.tar.gz", hash = "sha256:7565f628ea56bfcd8e54e42bdc55da899c85c1abfe1b5bcfd147e9188cebb3b2"}, ] [package.extras] -docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"] -testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"] +docs = ["furo (>=2022.9.29)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +testing = ["covdefaults (>=2.2.2)", "coverage (>=6.5)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] [[package]] name = "flatdict" @@ -541,14 +541,14 @@ files = [ [[package]] name = "identify" -version = "2.5.9" +version = "2.5.10" description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "identify-2.5.9-py2.py3-none-any.whl", hash = "sha256:a390fb696e164dbddb047a0db26e57972ae52fbd037ae68797e5ae2f4492485d"}, - {file = "identify-2.5.9.tar.gz", hash = "sha256:906036344ca769539610436e40a684e170c3648b552194980bb7b617a8daeb9f"}, + {file = "identify-2.5.10-py2.py3-none-any.whl", hash = "sha256:fb7c2feaeca6976a3ffa31ec3236a6911fbc51aec9acc111de2aed99f244ade2"}, + {file = "identify-2.5.10.tar.gz", hash = "sha256:dce9e31fee7dbc45fea36a9e855c316b8fbf807e65a862f160840bb5a2bf5dfd"}, ] [package.extras] @@ -568,14 +568,14 @@ files = [ [[package]] name = "importlib-metadata" -version = "4.13.0" +version = "5.1.0" description = "Read metadata from Python packages" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "importlib_metadata-4.13.0-py3-none-any.whl", hash = "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116"}, - {file = "importlib_metadata-4.13.0.tar.gz", hash = "sha256:dd0173e8f150d6815e098fd354f6414b0f079af4644ddfe90c71e2fc6174346d"}, + {file = "importlib_metadata-5.1.0-py3-none-any.whl", hash = "sha256:d84d17e21670ec07990e1044a99efe8d615d860fd176fc29ef5c306068fda313"}, + {file = "importlib_metadata-5.1.0.tar.gz", hash = "sha256:d5059f9f1e8e41f80e9c56c2ee58811450c31984dfa625329ffd7c0dad88a73b"}, ] [package.dependencies] @@ -589,14 +589,14 @@ testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packag [[package]] name = "importlib-resources" -version = "5.10.0" +version = "5.10.1" description = "Read resources from Python packages" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "importlib_resources-5.10.0-py3-none-any.whl", hash = "sha256:ee17ec648f85480d523596ce49eae8ead87d5631ae1551f913c0100b5edd3437"}, - {file = "importlib_resources-5.10.0.tar.gz", hash = "sha256:c01b1b94210d9849f286b86bb51bcea7cd56dde0600d8db721d7b81330711668"}, + {file = "importlib_resources-5.10.1-py3-none-any.whl", hash = "sha256:c09b067d82e72c66f4f8eb12332f5efbebc9b007c0b6c40818108c9870adc363"}, + {file = "importlib_resources-5.10.1.tar.gz", hash = "sha256:32bb095bda29741f6ef0e5278c42df98d135391bee5f932841efc0041f748dc3"}, ] [package.dependencies] @@ -655,14 +655,14 @@ trio = ["async_generator", "trio"] [[package]] name = "jsonschema" -version = "4.17.1" +version = "4.17.3" description = "An implementation of JSON Schema validation for Python" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "jsonschema-4.17.1-py3-none-any.whl", hash = "sha256:410ef23dcdbca4eaedc08b850079179883c2ed09378bd1f760d4af4aacfa28d7"}, - {file = "jsonschema-4.17.1.tar.gz", hash = "sha256:05b2d22c83640cde0b7e0aa329ca7754fbd98ea66ad8ae24aa61328dfe057fa3"}, + {file = "jsonschema-4.17.3-py3-none-any.whl", hash = "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"}, + {file = "jsonschema-4.17.3.tar.gz", hash = "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d"}, ] [package.dependencies] @@ -882,19 +882,16 @@ dev = ["black", "mypy", "pytest"] [[package]] name = "packaging" -version = "21.3" +version = "22.0" description = "Core utilities for Python packages" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, + {file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"}, + {file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"}, ] -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" - [[package]] name = "pexpect" version = "4.8.0" @@ -912,18 +909,18 @@ ptyprocess = ">=0.5" [[package]] name = "pkginfo" -version = "1.8.3" +version = "1.9.2" description = "Query metadatdata from sdists / bdists / installed packages." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.6" files = [ - {file = "pkginfo-1.8.3-py2.py3-none-any.whl", hash = "sha256:848865108ec99d4901b2f7e84058b6e7660aae8ae10164e015a6dcf5b242a594"}, - {file = "pkginfo-1.8.3.tar.gz", hash = "sha256:a84da4318dd86f870a9447a8c98340aa06216bfc6f2b7bdc4b8766984ae1867c"}, + {file = "pkginfo-1.9.2-py3-none-any.whl", hash = "sha256:d580059503f2f4549ad6e4c106d7437356dbd430e2c7df99ee1efe03d75f691e"}, + {file = "pkginfo-1.9.2.tar.gz", hash = "sha256:ac03e37e4d601aaee40f8087f63fc4a2a6c9814dda2c8fa6aab1b1829653bdfa"}, ] [package.extras] -testing = ["coverage", "nose"] +testing = ["pytest", "pytest-cov"] [[package]] name = "pkgutil-resolve-name" @@ -939,14 +936,14 @@ files = [ [[package]] name = "platformdirs" -version = "2.5.4" +version = "2.6.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, - {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, + {file = "platformdirs-2.6.0-py3-none-any.whl", hash = "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca"}, + {file = "platformdirs-2.6.0.tar.gz", hash = "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e"}, ] [package.extras] @@ -1063,18 +1060,6 @@ files = [ {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] - [[package]] name = "pycparser" version = "2.21" @@ -1087,21 +1072,6 @@ files = [ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] -[[package]] -name = "pyparsing" -version = "3.0.9" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "main" -optional = false -python-versions = ">=3.6.8" -files = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, -] - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - [[package]] name = "pyrsistent" version = "0.19.2" @@ -1178,22 +1148,6 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] -[[package]] -name = "pytest-forked" -version = "1.4.0" -description = "run tests in isolated forked subprocesses" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pytest-forked-1.4.0.tar.gz", hash = "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e"}, - {file = "pytest_forked-1.4.0-py3-none-any.whl", hash = "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8"}, -] - -[package.dependencies] -py = "*" -pytest = ">=3.10" - [[package]] name = "pytest-github-actions-annotate-failures" version = "0.1.7" @@ -1245,21 +1199,20 @@ pytest = "*" [[package]] name = "pytest-xdist" -version = "2.5.0" -description = "pytest xdist plugin for distributed testing and loop-on-failing modes" +version = "3.1.0" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"}, - {file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"}, + {file = "pytest-xdist-3.1.0.tar.gz", hash = "sha256:40fdb8f3544921c5dfcd486ac080ce22870e71d82ced6d2e78fa97c2addd480c"}, + {file = "pytest_xdist-3.1.0-py3-none-any.whl", hash = "sha256:70a76f191d8a1d2d6be69fc440cdf85f3e4c03c08b520fd5dc5d338d6cf07d89"}, ] [package.dependencies] execnet = ">=1.1" psutil = {version = ">=3.0", optional = true, markers = "extra == \"psutil\""} pytest = ">=6.2.0" -pytest-forked = "*" [package.extras] psutil = ["psutil (>=3.0)"] @@ -1330,101 +1283,101 @@ files = [ [[package]] name = "rapidfuzz" -version = "2.13.2" +version = "2.13.6" description = "rapid fuzzy string matching" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "rapidfuzz-2.13.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91c049f7591d9e9f8bcc3c556c0c4b448223f564ad04511a8719d28f5d38daed"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:26e4b7f7941b92546a9b06ed75b40b5d7ceace8f3074d06cb3369349388d700d"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba2a8fbd21079093118c40e8e80068750c1619a5988e54220ea0929de48e7d65"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de707808f1997574014d9ba87c2d9f8a619688d615520e3dce958bf4398514c7"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba3f47a5b82de7304ae08e2a111ccc90a6ea06ecc3f25d7870d08be0973c94cb"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a181b6ef9b480b56b29bdc58dc50c198e93d33398d2f8e57da05cbddb095bd9e"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1e569953a2abe945f116a6c22b71e8fc02d7c27068af2af40990115f25c93e4"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:026f6ecd8948e168a89fc015ef34b6bcb200f30ac33f1480554d722181b38bea"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daf5e4f6b048c225a494c941a21463a0d397c39a080db8fece9b3136297ed240"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e39ae60598ed533f513db6d0370755685666024ab187a144fc688dd16cfa2d33"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e8d71f1611431c445ced872b303cd61f215551a11df0c7171e5993bed84867d5"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f5d07dca69bf5a9f1e1cd5756ded6c197a27e8d8f2d8a3d99565add37a3bd1ec"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ac95981911559c842e1e4532e2f89ca255531db1d87257e5e69cd8c0c0d585fc"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-win32.whl", hash = "sha256:b4162b96d0908cb0ca218513eab559e9a77c8a1d9705c9133813634d9db27f4f"}, - {file = "rapidfuzz-2.13.2-cp310-cp310-win_amd64.whl", hash = "sha256:84fd3cfc1cb872019e60a3844b1deedb176de0b9ded11bf30147137ac65185f5"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a599cc5cec196c0776faf65b74ac957354bd036f878905a16be9e20884870d02"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dbad2b7dad98b854a468d2c6a0b11464f68ce841428aded2f24f201a17a144eb"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad78fb90540dc752b532345065146371acd3804a917c31fdd8a337951da9def2"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed0f99e0037b7f9f7117493e8723851c9eece4629906b2d5da21d3ef124149a2"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9abdffc590ef08d27dfd14d32e571f4a0f5f797f433f00c5faf4cf56ab62792a"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:352c920e166e838bc560014885ba979df656938fcc29a12c73ff06dc76b150d8"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c40acbadc965e72f1b44b3c665a59ec78a5e959757e52520bf73687c84ce6854"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a4053d5b62cedec83ff67d55e50da35f7736bed0a3b2af51fa6143f5fef3785"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0c324d82871fe50471f7ba38a21c3e68167e868f541f57ac0ef23c053bbef6e6"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cb4bd75518838b141dab8fe663de988c4d08502999068dc0b3949d43bd86ace6"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4b785ffbc16795fca27c9e899993df7721d886249061689c48dbfe60fa7d02a1"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:1f363bf95d79dbafa8eac17697965e02e74da6f21b231b3fb808b2185bfed337"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f7cfc25d8143a7570f5e4c9da072a1e1c335d81a6926eb10c1fd3f637fa3c022"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-win32.whl", hash = "sha256:580f32cda7f911fef8266c7d811e580c18734cd12308d099b9975b914f33fcaf"}, - {file = "rapidfuzz-2.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:98be3e873c8f9d90a982891b2b061521ba4e5e49552ba2d3c1b0806dd5677f88"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:de8ec700127b645b0e2e28e694a2bba6dcb6a305ef080ad312f3086d47fb6973"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0ec73e6d3ad9442cfb5b94c137cf4241fff2860d81a9ee8be8c3d987bb400c0"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da5b7f35fc824cff36a2baa62486d5b427bf0fd7714c19704b5a7df82c2950b4"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f186b3a32d78af7a805584a7e1c2fdf6f6fd62939936e4f3df869158c147a55"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68f2e23eec59fc77bef164157889a2f7fb9800c47d615c58ee3809e2be3c8509"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4291a8c02d32aa6ebdffe63cf91abc2846383de95ae04a275f036c4e7a27f9ba"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a2eeee09ff716c8ff75942c1b93f0bca129590499f1127cbeb1b5cefbdc0c3d5"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2345656b30d7e18d18a4df5b765e4059111860a69bf3a36608a7d625e92567e6"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1e1dd1a328464dd2ae70f0e31ec403593fbb1b254bab7ac9f0cd08ba71c797d0"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:54fe1835f96c1033cdb7e4677497e784704c81d028c962d2222239ded93d978b"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6b68b6a12411cfacca16ace22d42ae8e9946315d79f49c6c97089789c235e795"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-win32.whl", hash = "sha256:9a740ddd3f7725c80e500f16b1b02b83a58b47164c0f3ddd9379208629c8c4b5"}, - {file = "rapidfuzz-2.13.2-cp37-cp37m-win_amd64.whl", hash = "sha256:378554acdcf8370cc5c777b1312921a2a670f68888e999ea1305599c55b67f5d"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa96955f2878116239db55506fe825f574651a8893d07a83de7b3c76a2f0386e"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b4df886481ca27a6d53d30a73625fb86dd308cf7d6d99d32e0dfbfcc8e8a75b9"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c66f3b8e93cdc3063ffd7224cad84951834d9434ffd27fa3fabad2e942ddab7"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d6d5ab0f12f2d7ae6aad77af67ae6253b6c1d54c320484f1acd2fce38b39ac2"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f0574d5d97722cfaf51b7dd667c8c836fa9fdf5a7d8158a787b98ee2788f6c5"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:83ff31d33c1391a0a6b23273b7f839dc8f7b5fb75ddca59ce4f334b83ca822bb"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94d8c65f48665f82064bea8a48ff185409a309ba396f5aec3a846831cbe36e6d"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c065a83883af2a9a0303b6c06844a700af0db97ff6dc894324f656ad8efe405"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:773c60a5368a361253efea194552ff9ed6879756f6feb71b61b514723f8cb726"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:12ece1a4d024297afa4b76d2ce71c2c65fc7eaa487a9ae9f6e17c160253cfd23"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2b491f2fac36718247070c3343f53aadbbe8684f3e0cf3b6cce1bd099e1d05cb"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:31370273787dca851e2df6f32f1ec8c61f86e9bbeb1cc42787020b6dfff952fd"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:47b5b227dc0bd53530dda55f344e1b24087fa99bb1bd7fceb6f5a2b1e2831ad4"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-win32.whl", hash = "sha256:8f09a16ae84b1decb9df9a7e393ec84a0b2a11da6356c3eedcf86da8cabe3071"}, - {file = "rapidfuzz-2.13.2-cp38-cp38-win_amd64.whl", hash = "sha256:e038e187270cbb987cf7c5d4b574fce7a32bc3d9593e9346d129874a7dc08dc3"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:aee5dce78e157e503269121ad6f886acab4b1ab3e3956bcdf0549d54596eab57"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80073e897af0669f496d23899583b5c2f0decc2ec06aa7c36a3b8fb16eda5e0e"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ce40c2a68fe28e05a4f66229c11885ef928086fbcd2eff086decdacfe5254da9"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd268701bf930bbb2d12f6f7f75c681e16fee646ea1663d258e825bf919ca7a1"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5d93e77881497f76e77056feea4c375732d27151151273d6e4cb8a1defbf17a"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b27c3e2b1789a635b9df1d74838ae032dc2dbc596ece5d89f9de2c37ba0a6dfe"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e49f412fe58c793af61b04fb5536534dfc95000b6c2bf0bfa42fcf7eb1453d42"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27bbdee91718019e251d315c6e9b03aa5b7663b90e4228ac1ddb0a567ff3634b"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b51d45cb9ed81669206e338413ba224c06a8900ab0cc9106f4750ac73dc687bb"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3479a2fd88504cc41eb707650e81fd7ce864f2418fee24f7224775b539536b39"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7adb4327453c1550f51d6ba13d718a84091f82230c1d0daca6db628e57d0fa5a"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3a4e87aae287d757d9c5b045c819c985b02b38dea3f75630cc24d53826e640be"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e175b1643306558a3d7604789c4a8c217a64406fe82bf1a9e52efb5dea53ae"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-win32.whl", hash = "sha256:fb896fafa206db4d55f4412135c3ae28fbc56b8afc476970d0c5f29d2ce50948"}, - {file = "rapidfuzz-2.13.2-cp39-cp39-win_amd64.whl", hash = "sha256:37a9a8f5737b8e429291148be67d2dd8ba779a69a87ad95d2785bb3d80fd1df7"}, - {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d6cb51a8459e7160366c6c7b31e8f9a671f7d617591c0ad305f2697707061da2"}, - {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:343fe1fcbbf55c994b22962bfb46f6b6903faeac5a2671b2f0fa5e3664de3e66"}, - {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d9d081cd8e0110661c8a3e728d7b491a903bb54d34de40b17d19144563bd5f6"}, - {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f93a6740fef239a8aca6521cc1891d448664115b53528a3dd7f95c1781a5fa6"}, - {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:deaf26cc23cfbf90650993108de888533635b981a7157a0234b4753527ac6e5c"}, - {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b6a0617ba60f81a8df3b9ddca09f591a0a0c8269402169825fcd50daa03e5c25"}, - {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bee1065d55edfeabdb98211bb673cb44a8b118cded42d743f7d59c07b05a80d"}, - {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e5afd5477332ceeb960e2002d5bb0b04ad00b40037a0ab1de9916041badcf00"}, - {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eead76c172ba08d49ea621016cf84031fff1ee33d7db751d7003e491e55e66af"}, - {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:83b1e8aca6c3fad058d8a2b7653b7496df0c4aca903d589bb0e4184868290767"}, - {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:41610c3a9be4febcbcac2b69b2f45d0da33e39d1194e5ffa3dd3a104d5a67a70"}, - {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3aacc4eb58d6bccf6ec571619bee35861d4103961b9873d9b0829d347ca8a63e"}, - {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:791d90aa1c68b5485f6340a8dc485aba7e9bcb729572449174ded0692e7e7ad0"}, - {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d4f94b408c9f9218d61e8af55e43c8102f813eea2cf82de10906b032ddcb9aa"}, - {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ac6a8a34f858f3862798383f51012788df6be823e2874fa426667a4da94ded7e"}, - {file = "rapidfuzz-2.13.2.tar.gz", hash = "sha256:1c67007161655c59e13bba130a2db29d7c9e5c81bcecb8846a3dd7386065eb24"}, + {file = "rapidfuzz-2.13.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5c4dabaa257efe1eec1cafa59612dbc142e9c81356d345ad5828046cb745304b"}, + {file = "rapidfuzz-2.13.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:34c34bb779238196690fca8c413ab9047481e3c8b10e5a99877d6c3769459764"}, + {file = "rapidfuzz-2.13.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c693b9c7a8b34cae92868c2c0c42d6fd87c3b8abd9586b7e880bb0a01e85659b"}, + {file = "rapidfuzz-2.13.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e7ab10f6cbcf3b2b48f843a52d5dcd0a490b3dc76d5db80489032a853573a2f"}, + {file = "rapidfuzz-2.13.6-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2afc8811fbdb7b8ca43fd34007d96a4e90ea42dd15afd22220c5072e022fca71"}, + {file = "rapidfuzz-2.13.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12ccc0633bc6f95d3cc10acec9ab61aabc0b8c7742f0e829c29afa1cd465b434"}, + {file = "rapidfuzz-2.13.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d1cd7a7f36f1dfa0923d520390b168e09e9ac2ff14e2f33637c9240f20b3c8d"}, + {file = "rapidfuzz-2.13.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e791d829c2251a721aae9d5b99d4efa4a3f7d7fbe32564ddd9631188a241826f"}, + {file = "rapidfuzz-2.13.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee32c175c41f02805b02b04818f3a0b4d7145043ad1b6788b5927e43675491b0"}, + {file = "rapidfuzz-2.13.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bff8930ffcf6fe973b6ac08884e4553e8e7505e67de4047eeda46cb4973eb88d"}, + {file = "rapidfuzz-2.13.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c6de2e6bdf16ae2acf4b49892b19e87fcf09b428b9d2343e58537ca0ae1320ae"}, + {file = "rapidfuzz-2.13.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:63ccbe0231b3f0c31db5532d7e9b15b9d7ee4bd6fba18b1beb68ee7d4f05d1e0"}, + {file = "rapidfuzz-2.13.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fa37236d6a65b757145c4df7ed34b4ada7706ea5683c985c2024532b2e4f8649"}, + {file = "rapidfuzz-2.13.6-cp310-cp310-win32.whl", hash = "sha256:ba8770c9436d08300a56781cacf84526912841927265a8feb61f0bc7547869b9"}, + {file = "rapidfuzz-2.13.6-cp310-cp310-win_amd64.whl", hash = "sha256:b4e6ca42666119350811fdf119aa82ad0539fd21f3daa6533a478bc0df650b3f"}, + {file = "rapidfuzz-2.13.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a94ee9e7cd72111e69bff72e024c08bac53813e8e7f1f5bc8b0795e8f8995191"}, + {file = "rapidfuzz-2.13.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a52177ee3d48c1d27bae3d010c0e96890deabf65435c2654006907946e29d97f"}, + {file = "rapidfuzz-2.13.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6da83fe0ea10fb21988b80ea38949f1d5a968b0591c112c30a9c763d188b8c45"}, + {file = "rapidfuzz-2.13.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42a0c8589f1842486c1d7f2c9a58b2e30a723094e069123984fe0b05efcfa616"}, + {file = "rapidfuzz-2.13.6-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d62fffd06f6dd29873e2d901f5fcc3e2391dbaa3c8f38fe351556288004baa2c"}, + {file = "rapidfuzz-2.13.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49d10f5e330f3fd8871f19ccfafeeaa595f7f8b74adb669bf637c619f2795810"}, + {file = "rapidfuzz-2.13.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4a5cf45f5ced9a02d04a850d71b584b3ab248e72c560d8cea9eb1ac67c51172"}, + {file = "rapidfuzz-2.13.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86767725208a2b76a0d8e338643260765435b9e7241896b5fb66d3dc1dace79b"}, + {file = "rapidfuzz-2.13.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1aa89d2a02af1552370d64aaa971094d458c1cf53dbe3e4b09b68c8811312433"}, + {file = "rapidfuzz-2.13.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ea5282cc6939c7f1b75d2d95906cd6b0f90c9171e08f74ff7ecee806da2e4f61"}, + {file = "rapidfuzz-2.13.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:79812801626291f9ca6abbf89dfb1ca17e8165b5cf8af4656985be30142b758f"}, + {file = "rapidfuzz-2.13.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:1a7a1069a81c1fae5903d4d7ff80de723f3a492635a0e40699491460a5ba84fe"}, + {file = "rapidfuzz-2.13.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:52f2c6ea7c6ebc09b7a09f707273e7c8cb590f89093bc1de9c93ebe7f4ecfd95"}, + {file = "rapidfuzz-2.13.6-cp311-cp311-win32.whl", hash = "sha256:96c93f15b578e0332345a32035836034b66e3a8f10e62b6cca79d9ba25bf0bd2"}, + {file = "rapidfuzz-2.13.6-cp311-cp311-win_amd64.whl", hash = "sha256:ea23efde72c7d9a6170c2fe772a5be9ea1fafcf88cb11d935fbc665d2c48a36c"}, + {file = "rapidfuzz-2.13.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:15bad0c24a08ef7b87138317a1c0c9b54c577ad2c2671a82d89d5f2e35a7b372"}, + {file = "rapidfuzz-2.13.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5721bfa91ccd4d2e48787913a64a084ded44a7e488f17f728bb0b1dc2c7f1acd"}, + {file = "rapidfuzz-2.13.6-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0dfe6bf7b193db6b88c49124c065834a051d3a007da7d752b7cb488473965eb0"}, + {file = "rapidfuzz-2.13.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b101b1eddededbfe31e9eaed63ad64fa099f14ad6bc9850d4a337ffbb0c4566"}, + {file = "rapidfuzz-2.13.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74b498cac37ba66d72f77425cdadb0dd269495b28543bce8f8090f28086faaf5"}, + {file = "rapidfuzz-2.13.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5091b4139849971d9b101fe844d4a4306f1ac314371215db3ce652772811ecbe"}, + {file = "rapidfuzz-2.13.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5378b8711ae9ec8ba4eb5476afcc599e4bdfe73374084544abc9b6f984c013d3"}, + {file = "rapidfuzz-2.13.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ad87d5accb542d19dc755a73cf1b3d332d639743b18d62c694c566229235d71a"}, + {file = "rapidfuzz-2.13.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8e4b8e1c686737a229a53df21486dc040a5b62453a0f6bf3839d92d5f21a0d25"}, + {file = "rapidfuzz-2.13.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5823b99277cf51ff7abb668d02bfa1b97a77e77317a06a0bca431a4a85f3bc26"}, + {file = "rapidfuzz-2.13.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:881f1a3ad1bdab5e907ff0b33a8c0c8f8155ff364018fe82c09f6eddebaf9ba8"}, + {file = "rapidfuzz-2.13.6-cp37-cp37m-win32.whl", hash = "sha256:cda615c49e8bc990972b47cb7788fae8230b07449a38ba99192d74ebd525d5e7"}, + {file = "rapidfuzz-2.13.6-cp37-cp37m-win_amd64.whl", hash = "sha256:b18fcc28123040a867f980f4e41431b0227e1b861b8ea7303c92d3210f2f9561"}, + {file = "rapidfuzz-2.13.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:35a8a0cde5d46f0e81771ef7723c235993ea2f3da53ab91c35b82101437f4b61"}, + {file = "rapidfuzz-2.13.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0d1d22fed68976377a376afa8fdbc0f2353e4b16f3c490574e2ee2b0f90d1d86"}, + {file = "rapidfuzz-2.13.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83174ddf6b692ec762ad51afa097fe8fbb3e28ce72a1e1a835f196dbabdf26d5"}, + {file = "rapidfuzz-2.13.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14ffbe50d2f61d6071d696cf9282d2961596885a4c5a27355bf78b296a66230e"}, + {file = "rapidfuzz-2.13.6-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eae455a63882a95b976180ddb14588d42c78641047bf3f61f445e7be39a85508"}, + {file = "rapidfuzz-2.13.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80f6651db489e9ecd2b3ff9aee966b10cfc8f69591450694b2f362e13a5fe085"}, + {file = "rapidfuzz-2.13.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aee1deaec703a51fdf17d3d349c6b714b43bf56fe8d4c2157ebd985710f0eda"}, + {file = "rapidfuzz-2.13.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fea337f4face310917dbf1e321eb4b1392c02267af6edb0e9bba7fa04cb8711d"}, + {file = "rapidfuzz-2.13.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:23284face05d384e2c273da005e6dee9efebbb5c51d5e924b9114bafbb53d3db"}, + {file = "rapidfuzz-2.13.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:88476cfeaa74e63cfdc8de9f1b28b7a76d951ce663c077a0f03076b9865052c6"}, + {file = "rapidfuzz-2.13.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f162ab51e912d7615b5352ee79f1c1fd009a7f6a20ecc44238e2d594cab8c4cb"}, + {file = "rapidfuzz-2.13.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:cdfed7427c459be870ab13b4b17c254598500074079cec32475a46249d57b18b"}, + {file = "rapidfuzz-2.13.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:09367abf1490bbd7138fb883df9d811b12b2c409b50ae62360e44df84a4d515a"}, + {file = "rapidfuzz-2.13.6-cp38-cp38-win32.whl", hash = "sha256:1ae63571508ea8f8e85616f99ac6c843070d75de4dbefdb87d49fb24b441e2c1"}, + {file = "rapidfuzz-2.13.6-cp38-cp38-win_amd64.whl", hash = "sha256:7e0cd251529781d94703f8448fa8f43a051357c4f75839332b878ed13dccab19"}, + {file = "rapidfuzz-2.13.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2b9ec944bf3bc916a70845e83063926b91709c399041d564a50d68e62c606e74"}, + {file = "rapidfuzz-2.13.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:845c5394cfc7e7522f66aa68e740115e81e86530010107aabfbd611eee17b7d1"}, + {file = "rapidfuzz-2.13.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4414cb19bdaec7ee52bd17cac1b47baf4c1bb3e3e1ad6aead032296fdfa14603"}, + {file = "rapidfuzz-2.13.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:920c332777bac28b82be2a3d9e2c204c2b64783f15ba263c3c2551b4aa389624"}, + {file = "rapidfuzz-2.13.6-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb08f48c11d37c0f6121d862e9e9567d3ba6dc22ff2f3aa6c20c8c5f38d6aa9f"}, + {file = "rapidfuzz-2.13.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65379a26e0a042f3b5935d6178aaed499f434f73affb01893b9890c91d771a7d"}, + {file = "rapidfuzz-2.13.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d0d68bf0c53ff954e03a2da457989e065da52069ca649960f9d6830172fc607"}, + {file = "rapidfuzz-2.13.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:260be139930db7e864e47518853476f92994ec78a83a1236805e527c52bcae55"}, + {file = "rapidfuzz-2.13.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2d25ba295cc3c27c43fe4cbba6010407da70e240c18df979012cb6ebd70b8ab8"}, + {file = "rapidfuzz-2.13.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:16dd88b64a4e11b1a8fc84a9db7a017f7175620a3cf7304dfad59add4ef85c23"}, + {file = "rapidfuzz-2.13.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cb7809423c7146479a1d77d5a74cb4fe1b6dffbb6423c9a1a49f69906552929c"}, + {file = "rapidfuzz-2.13.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:fa0d33447bac6280c7deb02186349546404f387906fb4408186079852545c8de"}, + {file = "rapidfuzz-2.13.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cade0fd49d2b04fd21736e9dc20b10047cd5eb9e842f950c7ebc00531c595145"}, + {file = "rapidfuzz-2.13.6-cp39-cp39-win32.whl", hash = "sha256:ba102a2fb37b1db698016aa7a8b9bfe1c7e610d333a6e69737edc296c1a40b91"}, + {file = "rapidfuzz-2.13.6-cp39-cp39-win_amd64.whl", hash = "sha256:905c0f4304ea168cdc470e99496541dca1fdff084c22acb91a85ecdaa97986bf"}, + {file = "rapidfuzz-2.13.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:db38d0fb8a2f317b34adf0d418d1d0f3ef0eeb3543bfda0058abf34a506992c5"}, + {file = "rapidfuzz-2.13.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a422aee665b096bd5b02883ae1528d3e5e484069925d54e99db7233852fd1de"}, + {file = "rapidfuzz-2.13.6-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5179cbdcb97638d8ad4b2a638f76fc20bdef54c3f32879756bf56ed38f9199ad"}, + {file = "rapidfuzz-2.13.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19d872bb8fc106cfabe5f0e8fc2e23911e336823d4c326f4186f2b0a7f5ec0a4"}, + {file = "rapidfuzz-2.13.6-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:699ad346ca8e462808eb48493f9b28d82447e329bc68f6bea1f1b41615578a03"}, + {file = "rapidfuzz-2.13.6-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f2799e9b7fea1c42f36d1132ba2bf265724161a57f037b085aee91fe851d0ae"}, + {file = "rapidfuzz-2.13.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a19a644f8abf265a30b7787bd16b2845b75f6925c21ef9a1745415ec79f740a"}, + {file = "rapidfuzz-2.13.6-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df794809b916ab868b3fb0fc8ffa14ad8ae9deb0fbcc7f697ea900512024b4c8"}, + {file = "rapidfuzz-2.13.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2c2d05ed5ae8bf7b57366255f85bdea5420ce5ce32c898bf44bfaf55c043ece"}, + {file = "rapidfuzz-2.13.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6569a14ed01dc2ee2f915b97bbbf9290d924461e15f31df9e770dfab9c64b42e"}, + {file = "rapidfuzz-2.13.6-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:24709358f4bec9a5a1cb0417edc8fc8ec8161b86d546b98c6cecc647bce967cf"}, + {file = "rapidfuzz-2.13.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0350caf368aa58b598276e85b5109bb4d3262a7089beddcf45d9e2bdc219415"}, + {file = "rapidfuzz-2.13.6-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7812ad1ff06a570a6e0e5f1aceee673e7dd5db2c304d34aeebac0e5828291be"}, + {file = "rapidfuzz-2.13.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f85b751e0c0a5d5fbe9d327e53446c2cfae0e77b16600ff4854671f4f9237a5"}, + {file = "rapidfuzz-2.13.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e5f5e3eeb6816f98973428582b27a8d709e2e4f240165c59b9bfaf564a4d94c4"}, + {file = "rapidfuzz-2.13.6.tar.gz", hash = "sha256:948445a054d9fb30a93597c325d8836232bd68e61443a88779a57702aa35a007"}, ] [package.extras] @@ -1562,14 +1515,14 @@ files = [ [[package]] name = "trove-classifiers" -version = "2022.10.19" +version = "2022.12.1" description = "Canonical source for classifiers on PyPI (pypi.org)." category = "main" optional = false python-versions = "*" files = [ - {file = "trove-classifiers-2022.10.19.tar.gz", hash = "sha256:3a58b43b5149a0e125037e7e875d35e4471435a6a5a5b7e6b68cbee7afd23739"}, - {file = "trove_classifiers-2022.10.19-py3-none-any.whl", hash = "sha256:887261694f96a6af04a6d09566d4fd46b70edd5f1025b9ffc35cb4961218e8b0"}, + {file = "trove-classifiers-2022.12.1.tar.gz", hash = "sha256:8eccd9c075038ef2ec73276e2422d0dbf4d632f9133f029632d0df35374caf77"}, + {file = "trove_classifiers-2022.12.1-py3-none-any.whl", hash = "sha256:20ce6feeadd5793718b2106f0c832f821f29838aec792143e620b0a0a0a810b8"}, ] [[package]] @@ -1620,14 +1573,14 @@ files = [ [[package]] name = "types-jsonschema" -version = "4.17.0.1" +version = "4.17.0.2" description = "Typing stubs for jsonschema" category = "dev" optional = false python-versions = "*" files = [ - {file = "types-jsonschema-4.17.0.1.tar.gz", hash = "sha256:62625d492e4930411a431909ac32301aeab6180500e70ee222f81d43204cfb3c"}, - {file = "types_jsonschema-4.17.0.1-py3-none-any.whl", hash = "sha256:77badbe3881cbf79ac9561be2be2b1f37ab104b13afd2231840e6dd6e94e63c2"}, + {file = "types-jsonschema-4.17.0.2.tar.gz", hash = "sha256:8b9e1140d4d780f0f19b5cab1b8a3732e8dd5e49dbc1f174cc0b499125ca6f6c"}, + {file = "types_jsonschema-4.17.0.2-py3-none-any.whl", hash = "sha256:8fd2f9aea4da54f9a811baa6963aac10fd680c18baa6237392c079b97d152738"}, ] [[package]] @@ -1709,14 +1662,14 @@ testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7 [[package]] name = "virtualenv" -version = "20.16.7" +version = "20.17.1" description = "Virtual Python Environment builder" category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "virtualenv-20.16.7-py3-none-any.whl", hash = "sha256:efd66b00386fdb7dbe4822d172303f40cd05e50e01740b19ea42425cbe653e29"}, - {file = "virtualenv-20.16.7.tar.gz", hash = "sha256:8691e3ff9387f743e00f6bb20f70121f5e4f596cae754531f2b3b3a1b1ac696e"}, + {file = "virtualenv-20.17.1-py3-none-any.whl", hash = "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4"}, + {file = "virtualenv-20.17.1.tar.gz", hash = "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"}, ] [package.dependencies] @@ -1743,67 +1696,84 @@ files = [ [[package]] name = "xattr" -version = "0.10.0" +version = "0.10.1" description = "Python wrapper for extended filesystem attributes" category = "main" optional = false python-versions = "*" files = [ - {file = "xattr-0.10.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:5afb5cf3fe728b1e54a8796076787b1dbe192c87eb775233aa02dc26f3495664"}, - {file = "xattr-0.10.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:a1e6fb1567a2bfea8096d84de68670cedba94e0a128b89007f35d5415da82805"}, - {file = "xattr-0.10.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:a0baed7d2848c020aac6c1d5a6474bd86f43d109d80f73fd269e058612a2b08d"}, - {file = "xattr-0.10.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:b6fae8ec7eb650f025f774dc74211c4edd5526515f8fe8ab92b692c9a0f1abb8"}, - {file = "xattr-0.10.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:141fbf351aa386ab94a7a81935fd97cf8806e34498e3bc592e5b35769dcd8032"}, - {file = "xattr-0.10.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7c6d6d4615d4fa754431eb617c17da5d52ca6546d30dcbef52265ab64b317080"}, - {file = "xattr-0.10.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:259669d6cf188e92d5250b877fd59dde080dc0a118c579949e8e453baf8a54ea"}, - {file = "xattr-0.10.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5ebe4c6afe64e98e3f8cb41d3d2ed4c9a14d0e8d73a95531dbdb00b0d0ef36f3"}, - {file = "xattr-0.10.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:52e10b9737018a2e3b902c9a082334857ddd727d60a26648909dc81c07202d48"}, - {file = "xattr-0.10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d9adb5a02e86fa35aa89111dd2be8b2431af31a871c0224b84d9571724efa35b"}, - {file = "xattr-0.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cf8f0a47ea7c4cdb08d6ad317db242fb3694300452740727b01e72b8c9033a1"}, - {file = "xattr-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b041c05dae9aaddb6443f4dec63733726d235e01fd7dfae6904a98ef10d7fc78"}, - {file = "xattr-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ad9f3788f947eaca37be8db48c6219c1be3957f037e42b5b248b4019a88d985"}, - {file = "xattr-0.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1311e165f09864c5f4d98559f95f284d15f0a4ca724a6e8357b3f802200a96ee"}, - {file = "xattr-0.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d619781f2fd8deb0834a291a27d21bcdbed4c73b3ea8ade67e36ebb95e883067"}, - {file = "xattr-0.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:26a994e9e97ba9f70ec3a0ef7fd94917719515b2e878afea428205964a727174"}, - {file = "xattr-0.10.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cfbc457b6d8507c0a316dca986696ed02f7639ead19f169125b0c5d6202d132a"}, - {file = "xattr-0.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59de456b3003ec8e06345a8b17b42cdadb35321084aa5799de65ec216cbfdaa8"}, - {file = "xattr-0.10.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:829e570a1c164e34b3c3af7b8523c11fc74ca8320d522bddceed963ddc684d8f"}, - {file = "xattr-0.10.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c5b1094c12a55881e28c78d1c53c0c2fff188a169f5d3112573ca9fa6bf925"}, - {file = "xattr-0.10.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b78d28f8907665d9522bd50649a7c78215bd968021a0a847163a209b2e4cce63"}, - {file = "xattr-0.10.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b226ed1ba18bf4f6f209f97ba26daca2d07523dc897ffcaa0254e47a6544c1ea"}, - {file = "xattr-0.10.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:cce32a7173403ee7c065a1c935b7cd39092125ed020821b2811abebf7670a6e1"}, - {file = "xattr-0.10.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:eed4dc5218e3bbcf465df5deccde8b4cae16a418577ad3373e856918aa3f6d5f"}, - {file = "xattr-0.10.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:86d59fc0379a031979d522301a499ae82161bbb3c78e35b933748bb5e3fdfa8d"}, - {file = "xattr-0.10.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d5619fb7a9e6296b44ccfe1f87374e4f3453d44e966734ea4cf2659b05d1d333"}, - {file = "xattr-0.10.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a18004bdf3c27f8a5044082a3b6dd7df51620fd681d390a69cc7eddc31373316"}, - {file = "xattr-0.10.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2f3e2c7870173fe3c089eccc53166d6bdf6eb9cc28491b8a8c2825755dfbc61"}, - {file = "xattr-0.10.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a387986a8afa841955525604c773b711e88986b9904822d4749f882e584e98f9"}, - {file = "xattr-0.10.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:29dffb448ffdcaf5e978e4e5f10056fb916ab0a3292e8596258ac12a58195c22"}, - {file = "xattr-0.10.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1c040c3e91a7134da40350725945d98695ece694e962b9b2a292b523e560fe6a"}, - {file = "xattr-0.10.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:124102756c8fca42135302269d2ad2177833954307174daf80fbb980febfaa0b"}, - {file = "xattr-0.10.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:838b5d2bc180769096c700d80e31ba60e364d493f5adf4ee80bcf76c51dd6929"}, - {file = "xattr-0.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:19418ac900f6c59bab3fbf0b5a43904d614a20533555cec84658a1af7c256ba4"}, - {file = "xattr-0.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9dfd0c0982ef30c56212c6ebcaf5e1b4be202893bf101917a2ffe2ddd58e6cf6"}, - {file = "xattr-0.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d4d9c0d9b7634cc0a93ebd4e7952935445a653c3b274bbaf3dd421a51e2dc7a"}, - {file = "xattr-0.10.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f8244a90dbbc4a53f0e5788f2036bd1457ff8407644328119e48a7f52e62fcc"}, - {file = "xattr-0.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e35b27a1e2b229b3222490b2c690a439d942c900af0ac6e1c371180be200c8f"}, - {file = "xattr-0.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d632b083e74fbd6959b94a4002d533718037570342777837b0e6d10b55907da"}, - {file = "xattr-0.10.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2096c2afaa626b5295808d9b638a80c8bd62f3f23d745341165523a4c0ce9b00"}, - {file = "xattr-0.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:52def67ef8b795aa5dc49642b43f0f071f4e6819f250f4d60bba271b16d178d8"}, - {file = "xattr-0.10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:63900a3ab56880d9a02b9b216a147488d69e3d3e25546e8c6060cf20107050e5"}, - {file = "xattr-0.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a16ec341f506da2b1ce8420ca5621423af54530d69b07376d018cfb2a5420831"}, - {file = "xattr-0.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:097baf3bfbd0671e3bf335baed801b024e6e498c93502dd076d6384c80b460c7"}, - {file = "xattr-0.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d358adfd61a759df3ea392899bde2093974f679583bb51fef3d7bc4a7fe8419"}, - {file = "xattr-0.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3933071f26fd769751d9904aa607ff32d24042b940ae5b5384230ee3fae8a740"}, - {file = "xattr-0.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be912d30f44a2decac99d3844670dbed6e74498d38d40bfab196856afa14dc09"}, - {file = "xattr-0.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b8f631001af54a5c110cf05c0010864e9f0a94ae344c31fa4e2ba2581e866b87"}, - {file = "xattr-0.10.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:361684ae711932d155106a77bb88c5f5b4e10261ebaa88051a0e3e27cf259b0b"}, - {file = "xattr-0.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9c2c5ea869f048812b65de50d40788414eb60cd5e18766006fc742c56454c574"}, - {file = "xattr-0.10.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2f9448a987a4a03b97bda9cba666c0851a7661b6da0e1636534dcaabaeed093c"}, - {file = "xattr-0.10.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3462e018b18685d1e259d567a8796614922e828db002b00967ed328a6f01fa2a"}, - {file = "xattr-0.10.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:875c72e2e05322698008447ca017b9ebdcd8f9c3cb48d61f3c17181051842a35"}, - {file = "xattr-0.10.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f1d087b76fa325b6051677641da015d6d3af7e1e2f8afb28b869b81c3e33dc"}, - {file = "xattr-0.10.0.tar.gz", hash = "sha256:722652d2a5324e17891c416d4c76d91ccf98830a8f516a0de8533ce867f3acaf"}, + {file = "xattr-0.10.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:16a660a883e703b311d1bbbcafc74fa877585ec081cd96e8dd9302c028408ab1"}, + {file = "xattr-0.10.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1e2973e72faa87ca29d61c23b58c3c89fe102d1b68e091848b0e21a104123503"}, + {file = "xattr-0.10.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:13279fe8f7982e3cdb0e088d5cb340ce9cbe5ef92504b1fd80a0d3591d662f68"}, + {file = "xattr-0.10.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1dc9b9f580ef4b8ac5e2c04c16b4d5086a611889ac14ecb2e7e87170623a0b75"}, + {file = "xattr-0.10.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:485539262c2b1f5acd6b6ea56e0da2bc281a51f74335c351ea609c23d82c9a79"}, + {file = "xattr-0.10.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:295b3ab335fcd06ca0a9114439b34120968732e3f5e9d16f456d5ec4fa47a0a2"}, + {file = "xattr-0.10.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:a126eb38e14a2f273d584a692fe36cff760395bf7fc061ef059224efdb4eb62c"}, + {file = "xattr-0.10.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:b0e919c24f5b74428afa91507b15e7d2ef63aba98e704ad13d33bed1288dca81"}, + {file = "xattr-0.10.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:e31d062cfe1aaeab6ba3db6bd255f012d105271018e647645941d6609376af18"}, + {file = "xattr-0.10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:209fb84c09b41c2e4cf16dd2f481bb4a6e2e81f659a47a60091b9bcb2e388840"}, + {file = "xattr-0.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c4120090dac33eddffc27e487f9c8f16b29ff3f3f8bcb2251b2c6c3f974ca1e1"}, + {file = "xattr-0.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3e739d624491267ec5bb740f4eada93491de429d38d2fcdfb97b25efe1288eca"}, + {file = "xattr-0.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2677d40b95636f3482bdaf64ed9138fb4d8376fb7933f434614744780e46e42d"}, + {file = "xattr-0.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40039f1532c4456fd0f4c54e9d4e01eb8201248c321c6c6856262d87e9a99593"}, + {file = "xattr-0.10.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:148466e5bb168aba98f80850cf976e931469a3c6eb11e9880d9f6f8b1e66bd06"}, + {file = "xattr-0.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0aedf55b116beb6427e6f7958ccd80a8cbc80e82f87a4cd975ccb61a8d27b2ee"}, + {file = "xattr-0.10.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c3024a9ff157247c8190dd0eb54db4a64277f21361b2f756319d9d3cf20e475f"}, + {file = "xattr-0.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f1be6e733e9698f645dbb98565bb8df9b75e80e15a21eb52787d7d96800e823b"}, + {file = "xattr-0.10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7880c8a54c18bc091a4ce0adc5c6d81da1c748aec2fe7ac586d204d6ec7eca5b"}, + {file = "xattr-0.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:89c93b42c3ba8aedbc29da759f152731196c2492a2154371c0aae3ef8ba8301b"}, + {file = "xattr-0.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b905e808df61b677eb972f915f8a751960284358b520d0601c8cbc476ba2df6"}, + {file = "xattr-0.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ef954d0655f93a34d07d0cc7e02765ec779ff0b59dc898ee08c6326ad614d5"}, + {file = "xattr-0.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:199b20301b6acc9022661412346714ce764d322068ef387c4de38062474db76c"}, + {file = "xattr-0.10.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec0956a8ab0f0d3f9011ba480f1e1271b703d11542375ef73eb8695a6bd4b78b"}, + {file = "xattr-0.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffcb57ca1be338d69edad93cf59aac7c6bb4dbb92fd7bf8d456c69ea42f7e6d2"}, + {file = "xattr-0.10.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f0563196ee54756fe2047627d316977dc77d11acd7a07970336e1a711e934db"}, + {file = "xattr-0.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc354f086f926a1c7f04886f97880fed1a26d20e3bc338d0d965fd161dbdb8ab"}, + {file = "xattr-0.10.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c0cd2d02ef2fb45ecf2b0da066a58472d54682c6d4f0452dfe7ae2f3a76a42ea"}, + {file = "xattr-0.10.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49626096ddd72dcc1654aadd84b103577d8424f26524a48d199847b5d55612d0"}, + {file = "xattr-0.10.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ceaa26bef8fcb17eb59d92a7481c2d15d20211e217772fb43c08c859b01afc6a"}, + {file = "xattr-0.10.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8c014c371391f28f8cd27d73ea59f42b30772cd640b5a2538ad4f440fd9190b"}, + {file = "xattr-0.10.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:46c32cd605673606b9388a313b0050ee7877a0640d7561eea243ace4fa2cc5a6"}, + {file = "xattr-0.10.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:772b22c4ff791fe5816a7c2a1c9fcba83f9ab9bea138eb44d4d70f34676232b4"}, + {file = "xattr-0.10.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:183ad611a2d70b5a3f5f7aadef0fcef604ea33dcf508228765fd4ddac2c7321d"}, + {file = "xattr-0.10.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8068df3ebdfa9411e58d5ae4a05d807ec5994645bb01af66ec9f6da718b65c5b"}, + {file = "xattr-0.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bc40570155beb85e963ae45300a530223d9822edfdf09991b880e69625ba38a"}, + {file = "xattr-0.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:436e1aaf23c07e15bed63115f1712d2097e207214fc6bcde147c1efede37e2c5"}, + {file = "xattr-0.10.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7298455ccf3a922d403339781b10299b858bb5ec76435445f2da46fb768e31a5"}, + {file = "xattr-0.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:986c2305c6c1a08f78611eb38ef9f1f47682774ce954efb5a4f3715e8da00d5f"}, + {file = "xattr-0.10.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5dc6099e76e33fa3082a905fe59df766b196534c705cf7a2e3ad9bed2b8a180e"}, + {file = "xattr-0.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:042ad818cda6013162c0bfd3816f6b74b7700e73c908cde6768da824686885f8"}, + {file = "xattr-0.10.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9d4c306828a45b41b76ca17adc26ac3dc00a80e01a5ba85d71df2a3e948828f2"}, + {file = "xattr-0.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a606280b0c9071ef52572434ecd3648407b20df3d27af02c6592e84486b05894"}, + {file = "xattr-0.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5b49d591cf34cda2079fd7a5cb2a7a1519f54dc2e62abe3e0720036f6ed41a85"}, + {file = "xattr-0.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8705ac6791426559c1a5c2b88bb2f0e83dc5616a09b4500899bfff6a929302"}, + {file = "xattr-0.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5ea974930e876bc5c146f54ac0f85bb39b7b5de2b6fc63f90364712ae368ebe"}, + {file = "xattr-0.10.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f55a2dd73a12a1ae5113c5d9cd4b4ab6bf7950f4d76d0a1a0c0c4264d50da61d"}, + {file = "xattr-0.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:475c38da0d3614cc5564467c4efece1e38bd0705a4dbecf8deeb0564a86fb010"}, + {file = "xattr-0.10.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:925284a4a28e369459b2b7481ea22840eed3e0573a4a4c06b6b0614ecd27d0a7"}, + {file = "xattr-0.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa32f1b45fed9122bed911de0fcc654da349e1f04fa4a9c8ef9b53e1cc98b91e"}, + {file = "xattr-0.10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c5d3d0e728bace64b74c475eb4da6148cd172b2d23021a1dcd055d92f17619ac"}, + {file = "xattr-0.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8faaacf311e2b5cc67c030c999167a78a9906073e6abf08eaa8cf05b0416515c"}, + {file = "xattr-0.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc6b8d5ca452674e1a96e246a3d2db5f477aecbc7c945c73f890f56323e75203"}, + {file = "xattr-0.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3725746a6502f40f72ef27e0c7bfc31052a239503ff3eefa807d6b02a249be22"}, + {file = "xattr-0.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:789bd406d1aad6735e97b20c6d6a1701e1c0661136be9be862e6a04564da771f"}, + {file = "xattr-0.10.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9a7a807ab538210ff8532220d8fc5e2d51c212681f63dbd4e7ede32543b070f"}, + {file = "xattr-0.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3e5825b5fc99ecdd493b0cc09ec35391e7a451394fdf623a88b24726011c950d"}, + {file = "xattr-0.10.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80638d1ce7189dc52f26c234cee3522f060fadab6a8bc3562fe0ddcbe11ba5a4"}, + {file = "xattr-0.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3ff0dbe4a6ce2ce065c6de08f415bcb270ecfd7bf1655a633ddeac695ce8b250"}, + {file = "xattr-0.10.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5267e5f9435c840d2674194150b511bef929fa7d3bc942a4a75b9eddef18d8d8"}, + {file = "xattr-0.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b27dfc13b193cb290d5d9e62f806bb9a99b00cd73bb6370d556116ad7bb5dc12"}, + {file = "xattr-0.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:636ebdde0277bce4d12d2ef2550885804834418fee0eb456b69be928e604ecc4"}, + {file = "xattr-0.10.1-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d60c27922ec80310b45574351f71e0dd3a139c5295e8f8b19d19c0010196544f"}, + {file = "xattr-0.10.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b34df5aad035d0343bd740a95ca30db99b776e2630dca9cc1ba8e682c9cc25ea"}, + {file = "xattr-0.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f24a7c04ff666d0fe905dfee0a84bc899d624aeb6dccd1ea86b5c347f15c20c1"}, + {file = "xattr-0.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3878e1aff8eca64badad8f6d896cb98c52984b1e9cd9668a3ab70294d1ef92d"}, + {file = "xattr-0.10.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4abef557028c551d59cf2fb3bf63f2a0c89f00d77e54c1c15282ecdd56943496"}, + {file = "xattr-0.10.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0e14bd5965d3db173d6983abdc1241c22219385c22df8b0eb8f1846c15ce1fee"}, + {file = "xattr-0.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f9be588a4b6043b03777d50654c6079af3da60cc37527dbb80d36ec98842b1e"}, + {file = "xattr-0.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bc4ae264aa679aacf964abf3ea88e147eb4a22aea6af8c6d03ebdebd64cfd6"}, + {file = "xattr-0.10.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:827b5a97673b9997067fde383a7f7dc67342403093b94ea3c24ae0f4f1fec649"}, + {file = "xattr-0.10.1.tar.gz", hash = "sha256:c12e7d81ffaa0605b3ac8c22c2994a8e18a9cf1c59287a1b7722a2289c952ec5"}, ] [package.dependencies] @@ -1811,14 +1781,14 @@ cffi = ">=1.0" [[package]] name = "zipp" -version = "3.10.0" +version = "3.11.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "zipp-3.10.0-py3-none-any.whl", hash = "sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1"}, - {file = "zipp-3.10.0.tar.gz", hash = "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8"}, + {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, + {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, ] [package.extras] @@ -1828,4 +1798,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "7b52d7192276e1e1ab4667723a0911fbecd54bbf3ed1cd2621a654bb121eb49f" +content-hash = "c9064502080fd8c4c3195efac3b59202d51d7ca3498a4c92e9e7173fbc46480a" diff --git a/pyproject.toml b/pyproject.toml index 4bc5cbb99b2..566eb97e52c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ crashtest = "^0.4.1" dulwich = "^0.20.46" filelock = "^3.8.0" html5lib = "^1.0" -importlib-metadata = { version = "^4.4", python = "<3.10" } +importlib-metadata = { version = ">=4.4,<6", python = "<3.10" } jsonschema = "^4.10.0" keyring = "^23.9.0" lockfile = "^0.12.2" @@ -88,14 +88,14 @@ pre-commit = "^2.6" [tool.poetry.group.test.dependencies] # Cachy frozen to test backwards compatibility for `poetry.utils.cache`. cachy = "0.3.0" -deepdiff = "^5.0" +deepdiff = "^6.2" flatdict = "^4.0.1" httpretty = "^1.0" pytest = "^7.1" pytest-cov = "^4.0" pytest-mock = "^3.9" pytest-randomly = "^3.12" -pytest-xdist = { version = "^2.5", extras = ["psutil"] } +pytest-xdist = { version = "^3.1", extras = ["psutil"] } zipp = { version = "^3.4", python = "<3.8" } [tool.poetry.group.typing.dependencies] From cfdba43923b7ec386c44583055077cb697ced7bd Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 11 Dec 2022 11:26:03 +0000 Subject: [PATCH 033/151] tidy up mypy overrides --- pyproject.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 566eb97e52c..1748c65fbf8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -163,8 +163,6 @@ enable_error_code = [ [[tool.mypy.overrides]] module = [ 'poetry.console.commands.self.show.plugins', - 'poetry.installation.executor', - 'poetry.mixology.version_solver', 'poetry.plugins.plugin_manager', 'poetry.repositories.installed_repository', 'poetry.utils.env', From 6e84663dd76707af1337321a6d2c10c3d2491013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Tue, 20 Dec 2022 12:18:56 +0100 Subject: [PATCH 034/151] repository: according to PEP 440 pre-releases should be considered "if the only available version that satisfies the version specifier is a pre-release" (no special handling of "*" constraint) --- src/poetry/repositories/repository.py | 4 +--- tests/repositories/test_legacy_repository.py | 9 +-------- tests/repositories/test_pypi_repository.py | 2 +- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/poetry/repositories/repository.py b/src/poetry/repositories/repository.py index 46ecaac7475..274ece2ae65 100644 --- a/src/poetry/repositories/repository.py +++ b/src/poetry/repositories/repository.py @@ -50,9 +50,7 @@ def find_packages(self, dependency: Dependency) -> list[Package]: and not allow_prereleases and not package.is_direct_origin() ): - if constraint.is_any(): - # we need this when all versions of the package are pre-releases - ignored_pre_release_packages.append(package) + ignored_pre_release_packages.append(package) continue packages.append(package) diff --git a/tests/repositories/test_legacy_repository.py b/tests/repositories/test_legacy_repository.py index bde7b563fc5..9503897a978 100644 --- a/tests/repositories/test_legacy_repository.py +++ b/tests/repositories/test_legacy_repository.py @@ -213,7 +213,7 @@ def test_find_packages_no_prereleases() -> None: @pytest.mark.parametrize( - ["constraint", "count"], [("*", 1), (">=1", 0), (">=19.0.0a0", 1)] + ["constraint", "count"], [("*", 1), (">=1", 1), ("<=18", 0), (">=19.0.0a0", 1)] ) def test_find_packages_only_prereleases(constraint: str, count: int) -> None: repo = MockRepository() @@ -228,13 +228,6 @@ def test_find_packages_only_prereleases(constraint: str, count: int) -> None: assert package.source_url == repo.url -def test_find_packages_only_prereleases_empty_when_not_any() -> None: - repo = MockRepository() - packages = repo.find_packages(Factory.create_dependency("black", ">=1")) - - assert len(packages) == 0 - - @pytest.mark.parametrize( ["constraint", "expected"], [ diff --git a/tests/repositories/test_pypi_repository.py b/tests/repositories/test_pypi_repository.py index 4d4e59e2539..5ab69c77f2b 100644 --- a/tests/repositories/test_pypi_repository.py +++ b/tests/repositories/test_pypi_repository.py @@ -87,7 +87,7 @@ def test_find_packages_does_not_select_prereleases_if_not_allowed() -> None: @pytest.mark.parametrize( - ["constraint", "count"], [("*", 1), (">=1", 0), (">=19.0.0a0", 1)] + ["constraint", "count"], [("*", 1), (">=1", 1), ("<=18", 0), (">=19.0.0a0", 1)] ) def test_find_packages_only_prereleases(constraint: str, count: int) -> None: repo = MockRepository() From 7b40c844f523246738850b5ec9537dcf58ac7043 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 31 Dec 2022 01:21:42 +0100 Subject: [PATCH 035/151] [pre-commit.ci] pre-commit autoupdate (#7118) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 13 ++--- get-poetry.py | 48 +++++++++++-------- install-poetry.py | 16 ++++--- src/poetry/console/commands/add.py | 6 ++- src/poetry/console/commands/env/remove.py | 6 ++- src/poetry/console/commands/init.py | 18 ++++--- src/poetry/console/commands/install.py | 24 ++++++---- src/poetry/console/commands/lock.py | 6 ++- src/poetry/console/commands/remove.py | 12 +++-- src/poetry/console/commands/self/update.py | 6 ++- src/poetry/console/commands/show.py | 6 ++- src/poetry/console/commands/source/add.py | 8 ++-- src/poetry/console/commands/update.py | 12 +++-- src/poetry/installation/chooser.py | 12 +++-- src/poetry/installation/executor.py | 10 ++-- src/poetry/locations.py | 8 ++-- src/poetry/mixology/failure.py | 14 ++++-- .../python_requirement_solution_provider.py | 6 ++- src/poetry/repositories/cached.py | 7 ++- src/poetry/repositories/http.py | 6 ++- .../repositories/installed_repository.py | 8 ++-- src/poetry/repositories/pool.py | 8 ++-- src/poetry/repositories/pypi_repository.py | 6 ++- src/poetry/utils/env.py | 12 +++-- src/poetry/vcs/git/backend.py | 19 +++++--- tests/console/commands/test_init.py | 26 +++++----- 26 files changed, 206 insertions(+), 117 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 045dd8aec78..68891d76ae6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ ci: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -47,7 +47,7 @@ repos: - flake8-pie==0.16.0 - repo: https://github.com/asottile/pyupgrade - rev: v3.2.2 + rev: v3.3.1 hooks: - id: pyupgrade args: [--py37-plus] @@ -60,7 +60,7 @@ repos: args: [--all] - repo: https://github.com/pycqa/isort - rev: 5.10.1 + rev: 5.11.4 hooks: - id: isort name: "isort (python)" @@ -77,17 +77,18 @@ repos: args: [--lines-after-imports, "-1"] - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 22.12.0 hooks: - id: black - repo: https://github.com/pycqa/flake8 - rev: 5.0.4 + # freeze to commit rev to prevent automatic updates, since newer versions of flake8 are not compatible with plugins + rev: 6027577d325b0dd8bf1e465ebd29b71b5f0d005b hooks: - id: flake8 additional_dependencies: *flake8_deps - repo: https://github.com/pre-commit/pre-commit - rev: v2.20.0 + rev: v2.21.0 hooks: - id: validate_manifest diff --git a/get-poetry.py b/get-poetry.py index 7c3788b156a..99a33cb6869 100644 --- a/get-poetry.py +++ b/get-poetry.py @@ -446,13 +446,16 @@ def _is_supported(x): print( colorize( "deprecation", - "This installer is deprecated, and scheduled for removal from the" - " Poetry repository on or after January 1, 2023.\nSee" - " https://github.com/python-poetry/poetry/issues/6377 for" - " details.\n\nYou should migrate to https://install.python-poetry.org" - " instead, which supports all versions of Poetry, and allows for `self" - " update` of versions 1.1.7 or newer.\nInstructions are available at" - " https://python-poetry.org/docs/#installation.\n", + ( + "This installer is deprecated, and scheduled for removal from the" + " Poetry repository on or after January 1, 2023.\nSee" + " https://github.com/python-poetry/poetry/issues/6377 for" + " details.\n\nYou should migrate to" + " https://install.python-poetry.org instead, which supports all" + " versions of Poetry, and allows for `self update` of versions" + " 1.1.7 or newer.\nInstructions are available at" + " https://python-poetry.org/docs/#installation.\n" + ), ) ) @@ -460,14 +463,17 @@ def _is_supported(x): print( colorize( "deprecation", - "Without an explicit version, this installer will attempt to" - " install the latest version of Poetry.\nThis installer cannot" - " install Poetry 1.2.0a1 or newer (and installs will be unable to" - " `self update` to 1.2.0a1 or newer).\n\nTo continue to use this" - " deprecated installer, you must specify an explicit version with" - " --version or POETRY_VERSION=.\nAlternatively," - " if you wish to force this deprecated installer to use the latest" - " installable release, set GET_POETRY_IGNORE_DEPRECATION=1.\n", + ( + "Without an explicit version, this installer will attempt to" + " install the latest version of Poetry.\nThis installer cannot" + " install Poetry 1.2.0a1 or newer (and installs will be unable" + " to `self update` to 1.2.0a1 or newer).\n\nTo continue to use" + " this deprecated installer, you must specify an explicit" + " version with --version or" + " POETRY_VERSION=.\nAlternatively, if you wish to" + " force this deprecated installer to use the latest installable" + " release, set GET_POETRY_IGNORE_DEPRECATION=1.\n" + ), ) ) @@ -817,8 +823,10 @@ def add_to_fish_path(self): print( colorize( "warning", - "\nUnable to get the PATH value. It will not be updated" - " automatically.", + ( + "\nUnable to get the PATH value. It will not be updated" + " automatically." + ), ) ) self._modify_path = False @@ -853,8 +861,10 @@ def add_to_windows_path(self): print( colorize( "warning", - "Unable to get the PATH value. It will not be updated" - " automatically", + ( + "Unable to get the PATH value. It will not be updated" + " automatically" + ), ) ) self._modify_path = False diff --git a/install-poetry.py b/install-poetry.py index a5e56fd1243..2fbabbc16c3 100644 --- a/install-poetry.py +++ b/install-poetry.py @@ -493,9 +493,11 @@ def _is_self_upgrade_supported(x): self._write( colorize( "warning", - f"You are installing {version}. When using the current installer," - " this version does not support updating using the 'self update'" - " command. Please use 1.1.7 or later.", + ( + f"You are installing {version}. When using the current" + " installer, this version does not support updating using the" + " 'self update' command. Please use 1.1.7 or later." + ), ) ) if not self._accept_all: @@ -900,9 +902,11 @@ def main(): sys.stdout.write( colorize( "warning", - "The canonical source for Poetry's installation script is now" - " https://install.python-poetry.org. Please update your usage to reflect" - " this.\n", + ( + "The canonical source for Poetry's installation script is now" + " https://install.python-poetry.org. Please update your usage to" + " reflect this.\n" + ), ) ) sys.exit(main()) diff --git a/src/poetry/console/commands/add.py b/src/poetry/console/commands/add.py index eae854347b4..05bdc21bf77 100644 --- a/src/poetry/console/commands/add.py +++ b/src/poetry/console/commands/add.py @@ -63,8 +63,10 @@ class AddCommand(InstallerCommand, InitCommand): option( "dry-run", None, - "Output the operations but do not execute anything (implicitly enables" - " --verbose).", + ( + "Output the operations but do not execute anything (implicitly enables" + " --verbose)." + ), ), option("lock", None, "Do not perform operations (only update the lockfile)."), ] diff --git a/src/poetry/console/commands/env/remove.py b/src/poetry/console/commands/env/remove.py index d23fafe5526..d7029b96ac3 100644 --- a/src/poetry/console/commands/env/remove.py +++ b/src/poetry/console/commands/env/remove.py @@ -13,8 +13,10 @@ class EnvRemoveCommand(Command): arguments = [ argument( "python", - "The python executables associated with, or names of the virtual" - " environments which are to be removed.", + ( + "The python executables associated with, or names of the virtual" + " environments which are to be removed." + ), optional=True, multiple=True, ) diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index 7b032aada66..92cd249f084 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -42,16 +42,20 @@ class InitCommand(Command): option( "dependency", None, - "Package to require, with an optional version constraint, " - "e.g. requests:^2.10.0 or requests=2.11.1.", + ( + "Package to require, with an optional version constraint, " + "e.g. requests:^2.10.0 or requests=2.11.1." + ), flag=False, multiple=True, ), option( "dev-dependency", None, - "Package to require for development, with an optional version constraint, " - "e.g. requests:^2.10.0 or requests=2.11.1.", + ( + "Package to require for development, with an optional version" + " constraint, e.g. requests:^2.10.0 or requests=2.11.1." + ), flag=False, multiple=True, ), @@ -318,8 +322,10 @@ def _determine_requirements( choices.append("") package = self.choice( - "\nEnter package # to add, or the complete package name if it" - " is not listed", + ( + "\nEnter package # to add, or the complete package name if" + " it is not listed" + ), choices, attempts=3, default=len(choices) - 1, diff --git a/src/poetry/console/commands/install.py b/src/poetry/console/commands/install.py index 5503e917d01..b499970aac9 100644 --- a/src/poetry/console/commands/install.py +++ b/src/poetry/console/commands/install.py @@ -14,14 +14,18 @@ class InstallCommand(InstallerCommand): option( "no-dev", None, - "Do not install the development dependencies." - " (Deprecated)", + ( + "Do not install the development dependencies." + " (Deprecated)" + ), ), option( "sync", None, - "Synchronize the environment with the locked packages and the specified" - " groups.", + ( + "Synchronize the environment with the locked packages and the specified" + " groups." + ), ), option( "no-root", None, "Do not install the root package (the current project)." @@ -29,14 +33,18 @@ class InstallCommand(InstallerCommand): option( "dry-run", None, - "Output the operations but do not execute anything " - "(implicitly enables --verbose).", + ( + "Output the operations but do not execute anything " + "(implicitly enables --verbose)." + ), ), option( "remove-untracked", None, - "Removes packages not present in the lock file." - " (Deprecated)", + ( + "Removes packages not present in the lock file." + " (Deprecated)" + ), ), option( "extras", diff --git a/src/poetry/console/commands/lock.py b/src/poetry/console/commands/lock.py index 57c9ed74f77..d1009a265e8 100644 --- a/src/poetry/console/commands/lock.py +++ b/src/poetry/console/commands/lock.py @@ -16,8 +16,10 @@ class LockCommand(InstallerCommand): option( "check", None, - "Check that the poetry.lock file corresponds to the current" - " version of pyproject.toml.", + ( + "Check that the poetry.lock file corresponds to the current" + " version of pyproject.toml." + ), ), ] diff --git a/src/poetry/console/commands/remove.py b/src/poetry/console/commands/remove.py index 29e3021684b..88ea83880af 100644 --- a/src/poetry/console/commands/remove.py +++ b/src/poetry/console/commands/remove.py @@ -21,14 +21,18 @@ class RemoveCommand(InstallerCommand): option( "dev", "D", - "Remove a package from the development dependencies." - " (Deprecated)", + ( + "Remove a package from the development dependencies." + " (Deprecated)" + ), ), option( "dry-run", None, - "Output the operations but do not execute anything " - "(implicitly enables --verbose).", + ( + "Output the operations but do not execute anything " + "(implicitly enables --verbose)." + ), ), ] diff --git a/src/poetry/console/commands/self/update.py b/src/poetry/console/commands/self/update.py index f42a3d477d8..6cd94d71402 100644 --- a/src/poetry/console/commands/self/update.py +++ b/src/poetry/console/commands/self/update.py @@ -23,8 +23,10 @@ class SelfUpdateCommand(SelfCommand): option( "dry-run", None, - "Output the operations but do not execute anything " - "(implicitly enables --verbose).", + ( + "Output the operations but do not execute anything " + "(implicitly enables --verbose)." + ), ), ] help = """\ diff --git a/src/poetry/console/commands/show.py b/src/poetry/console/commands/show.py index 3c92574bc32..0fc9af3b46b 100644 --- a/src/poetry/console/commands/show.py +++ b/src/poetry/console/commands/show.py @@ -48,8 +48,10 @@ class ShowCommand(GroupCommand, EnvCommand): option( "why", None, - "When showing the full list, or a --tree for a single package," - " also display why it's included.", + ( + "When showing the full list, or a --tree for a single" + " package, also display why it's included." + ), ), option("latest", "l", "Show the latest version."), option( diff --git a/src/poetry/console/commands/source/add.py b/src/poetry/console/commands/source/add.py index e5750d77472..2848ce8a3be 100644 --- a/src/poetry/console/commands/source/add.py +++ b/src/poetry/console/commands/source/add.py @@ -25,9 +25,11 @@ class SourceAddCommand(Command): option( "default", "d", - "Set this source as the default (disable PyPI). A " - "default source will also be the fallback source if " - "you add other sources.", + ( + "Set this source as the default (disable PyPI). A " + "default source will also be the fallback source if " + "you add other sources." + ), ), option("secondary", "s", "Set this source as secondary."), ] diff --git a/src/poetry/console/commands/update.py b/src/poetry/console/commands/update.py index b880f5a89db..714a924e5bd 100644 --- a/src/poetry/console/commands/update.py +++ b/src/poetry/console/commands/update.py @@ -20,14 +20,18 @@ class UpdateCommand(InstallerCommand): option( "no-dev", None, - "Do not update the development dependencies." - " (Deprecated)", + ( + "Do not update the development dependencies." + " (Deprecated)" + ), ), option( "dry-run", None, - "Output the operations but do not execute anything " - "(implicitly enables --verbose).", + ( + "Output the operations but do not execute anything " + "(implicitly enables --verbose)." + ), ), option("lock", None, "Do not perform operations (only update the lockfile)."), ] diff --git a/src/poetry/installation/chooser.py b/src/poetry/installation/chooser.py index bca81274b39..821c09b38c8 100644 --- a/src/poetry/installation/chooser.py +++ b/src/poetry/installation/chooser.py @@ -80,8 +80,10 @@ def choose_for(self, package: Package) -> Link: if link.is_wheel: if not self._no_binary_policy.allows(package.name): logger.debug( - "Skipping wheel for %s as requested in no binary policy for" - " package (%s)", + ( + "Skipping wheel for %s as requested in no binary policy for" + " package (%s)" + ), link.filename, package.name, ) @@ -89,8 +91,10 @@ def choose_for(self, package: Package) -> Link: if not Wheel(link.filename).is_supported_by_environment(self._env): logger.debug( - "Skipping wheel %s as this is not supported by the current" - " environment", + ( + "Skipping wheel %s as this is not supported by the current" + " environment" + ), link.filename, ) continue diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index 6df976f7c45..dd72729a66d 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -318,10 +318,12 @@ def _do_execute_operation(self, operation: Operation) -> int: if self.supports_fancy_output(): self._write( operation, - f" • {operation_message}: " - "Skipped " - "for the following reason: " - f"{operation.skip_reason}", + ( + f" • {operation_message}: " + "Skipped " + "for the following reason: " + f"{operation.skip_reason}" + ), ) self._skipped[operation.job_type] += 1 diff --git a/src/poetry/locations.py b/src/poetry/locations.py index 0e3b884eb5b..d60eff0735b 100644 --- a/src/poetry/locations.py +++ b/src/poetry/locations.py @@ -33,9 +33,11 @@ if any(file.exists() for file in (auth_toml, config_toml)): logger.warning( - "Configuration file exists at %s, reusing this directory.\n\nConsider" - " moving configuration to %s, as support for the legacy directory will be" - " removed in an upcoming release.", + ( + "Configuration file exists at %s, reusing this directory.\n\nConsider" + " moving configuration to %s, as support for the legacy directory will" + " be removed in an upcoming release." + ), _LEGACY_CONFIG_DIR, CONFIG_DIR, ) diff --git a/src/poetry/mixology/failure.py b/src/poetry/mixology/failure.py index 448d39786bb..6d13ee2ca56 100644 --- a/src/poetry/mixology/failure.py +++ b/src/poetry/mixology/failure.py @@ -144,8 +144,10 @@ def _visit( self._visit(without_line) self._write( incompatibility, - f"{conjunction} because {with_line!s} ({line})," - f" {incompatibility_string}.", + ( + f"{conjunction} because {with_line!s} ({line})," + f" {incompatibility_string}." + ), numbered=numbered, ) else: @@ -170,9 +172,11 @@ def _visit( self._write( incompatibility, - f"{conjunction} because" - f" {cause.conflict!s} ({self._line_numbers[cause.conflict]})," - f" {incompatibility_string}", + ( + f"{conjunction} because {cause.conflict!s}" + f" ({self._line_numbers[cause.conflict]})," + f" {incompatibility_string}" + ), numbered=numbered, ) elif isinstance(cause.conflict.cause, ConflictCause) or isinstance( diff --git a/src/poetry/mixology/solutions/providers/python_requirement_solution_provider.py b/src/poetry/mixology/solutions/providers/python_requirement_solution_provider.py index b7d6e83bed2..ef278abb57b 100644 --- a/src/poetry/mixology/solutions/providers/python_requirement_solution_provider.py +++ b/src/poetry/mixology/solutions/providers/python_requirement_solution_provider.py @@ -19,8 +19,10 @@ def can_solve(self, exception: Exception) -> bool: return False m = re.match( - "^The current project's Python requirement (.+) is not compatible " - "with some of the required packages Python requirement", + ( + "^The current project's Python requirement (.+) is not compatible " + "with some of the required packages Python requirement" + ), str(exception), ) diff --git a/src/poetry/repositories/cached.py b/src/poetry/repositories/cached.py index 8a9443966de..2686cf7db90 100644 --- a/src/poetry/repositories/cached.py +++ b/src/poetry/repositories/cached.py @@ -8,8 +8,11 @@ ) warnings.warn( - "Module poetry.repositories.cached is renamed and scheduled for removal in poetry" - " release 1.4.0. Please migrate to poetry.repositories.cached_repository.", + ( + "Module poetry.repositories.cached is renamed and scheduled for removal in" + " poetry release 1.4.0. Please migrate to" + " poetry.repositories.cached_repository." + ), DeprecationWarning, stacklevel=2, ) diff --git a/src/poetry/repositories/http.py b/src/poetry/repositories/http.py index 3de1408a0ce..94e89980b1f 100644 --- a/src/poetry/repositories/http.py +++ b/src/poetry/repositories/http.py @@ -8,8 +8,10 @@ ) warnings.warn( - "Module poetry.repositories.http is renamed and scheduled for removal in poetry" - " release 1.4.0. Please migrate to poetry.repositories.http_repository.", + ( + "Module poetry.repositories.http is renamed and scheduled for removal in poetry" + " release 1.4.0. Please migrate to poetry.repositories.http_repository." + ), DeprecationWarning, stacklevel=2, ) diff --git a/src/poetry/repositories/installed_repository.py b/src/poetry/repositories/installed_repository.py index 228df3533f7..80353bd5de2 100644 --- a/src/poetry/repositories/installed_repository.py +++ b/src/poetry/repositories/installed_repository.py @@ -270,9 +270,11 @@ def load(cls, env: Env, with_dependencies: bool = False) -> InstalledRepository: name = canonicalize_name(distribution.metadata["name"]) except TypeError: logger.warning( - "Project environment contains an invalid distribution" - " (%s). Consider removing it manually or recreate the" - " environment.", + ( + "Project environment contains an invalid distribution" + " (%s). Consider removing it manually or recreate" + " the environment." + ), path, ) skipped.add(path) diff --git a/src/poetry/repositories/pool.py b/src/poetry/repositories/pool.py index 5f67f1768a5..48ccac36e75 100644 --- a/src/poetry/repositories/pool.py +++ b/src/poetry/repositories/pool.py @@ -18,9 +18,11 @@ def __init__( ignore_repository_names: bool = False, ) -> None: warnings.warn( - "Object Pool from poetry.repositories.pool is renamed and scheduled for" - " removal in poetry release 1.4.0. Please migrate to RepositoryPool from" - " poetry.repositories.repository_pool.", + ( + "Object Pool from poetry.repositories.pool is renamed and scheduled for" + " removal in poetry release 1.4.0. Please migrate to RepositoryPool" + " from poetry.repositories.repository_pool." + ), DeprecationWarning, stacklevel=2, ) diff --git a/src/poetry/repositories/pypi_repository.py b/src/poetry/repositories/pypi_repository.py index 236dabadfda..541c7ea81fb 100644 --- a/src/poetry/repositories/pypi_repository.py +++ b/src/poetry/repositories/pypi_repository.py @@ -86,8 +86,10 @@ def search(self, query: str) -> list[Package]: results.append(package) except InvalidVersion: self._log( - f'Unable to parse version "{version}" for the {name} package,' - " skipping", + ( + f'Unable to parse version "{version}" for the {name} package,' + " skipping" + ), level="debug", ) diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 74f1ed61b99..614ca092f21 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -541,8 +541,10 @@ def _detect_active_python(self) -> str | None: try: self._io.write_error_line( - "Trying to detect current active python executable as specified in the" - " config.", + ( + "Trying to detect current active python executable as specified in" + " the config." + ), verbosity=Verbosity.VERBOSE, ) executable = self._full_python_path("python") @@ -551,8 +553,10 @@ def _detect_active_python(self) -> str | None: ) except CalledProcessError: self._io.write_error_line( - "Unable to detect the current active python executable. Falling back to" - " default.", + ( + "Unable to detect the current active python executable. Falling" + " back to default." + ), verbosity=Verbosity.VERBOSE, ) return executable diff --git a/src/poetry/vcs/git/backend.py b/src/poetry/vcs/git/backend.py index 786a9689b93..20d7a1223af 100644 --- a/src/poetry/vcs/git/backend.py +++ b/src/poetry/vcs/git/backend.py @@ -295,8 +295,10 @@ def _clone(cls, url: str, refspec: GitRefSpec, target: Path) -> Repo: if isinstance(e, KeyError): # the local copy is at a bad state, lets remove it logger.debug( - "Removing local clone (%s) of repository as it is in a" - " broken state.", + ( + "Removing local clone (%s) of repository as it is in a" + " broken state." + ), local.path, ) remove_directory(local.path, force=True) @@ -305,8 +307,11 @@ def _clone(cls, url: str, refspec: GitRefSpec, target: Path) -> Repo: raise logger.debug( - "\nRequested ref (%s) was not fetched to local copy and cannot" - " be used. The following error was raised:\n\n\t%s", + ( + "\nRequested ref (%s) was not fetched to local copy and" + " cannot be used. The following error was" + " raised:\n\n\t%s" + ), refspec.key, e, ) @@ -433,8 +438,10 @@ def clone( # without additional configuration or changes for existing projects that # use http basic auth credentials. logger.debug( - "Unable to fetch from private repository '%s', falling back to" - " system git", + ( + "Unable to fetch from private repository '%s', falling back to" + " system git" + ), url, ) diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py index 935e58e636e..f78e9a64d17 100644 --- a/tests/console/commands/test_init.py +++ b/tests/console/commands/test_init.py @@ -844,13 +844,15 @@ def test_predefined_all_options(tester: CommandTester, repo: TestRepository): ] tester.execute( - "--name my-package " - "--description 'This is a description' " - "--author 'Foo Bar ' " - "--python '^3.8' " - "--license MIT " - "--dependency pendulum " - "--dev-dependency pytest", + ( + "--name my-package " + "--description 'This is a description' " + "--author 'Foo Bar ' " + "--python '^3.8' " + "--license MIT " + "--dependency pendulum " + "--dev-dependency pytest" + ), inputs="\n".join(inputs), ) @@ -941,10 +943,12 @@ def test_init_non_interactive_existing_pyproject_add_dependency( repo.add_package(get_package("foo", "1.19.2")) tester.execute( - "--author 'Your Name ' " - "--name 'my-package' " - "--python '^3.6' " - "--dependency foo", + ( + "--author 'Your Name ' " + "--name 'my-package' " + "--python '^3.6' " + "--dependency foo" + ), interactive=False, ) From 7105971f6c400f3a5b8e7467ba9777e7b253e5c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sat, 31 Dec 2022 01:35:09 +0100 Subject: [PATCH 036/151] docs: add advice for maintaining a plugin (#6977) --- docs/plugins.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/plugins.md b/docs/plugins.md index 1db8c065f0f..da1bb5ad55e 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -249,3 +249,18 @@ If you want to uninstall a plugin, you can run: ```shell $POETRY_HOME/bin/pip uninstall poetry-plugin ``` + + +## Maintaining a plugin + +When writing a plugin, you will probably access internals of Poetry, since there is no +stable public API. Although we try our best to deprecate methods first, before +removing them, sometimes the signature of an internal method has to be changed. + +As the author of a plugin, you are probably testing your plugin +against the latest release of Poetry. +Additionally, you should consider testing against the latest release branch and the +master branch of Poetry and schedule a CI job that runs regularly even if you did not +make any changes to your plugin. +This way, you will notice internal changes that break your plugin immediately +and can prepare for the next Poetry release. From d222411ae9d01a04ec8eda348a65aa83852c37d0 Mon Sep 17 00:00:00 2001 From: finswimmer Date: Sun, 1 Jan 2023 09:49:09 +0100 Subject: [PATCH 037/151] fix: as_uri() can only be applied to an absolute path (#7266) --- src/poetry/masonry/builders/editable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/poetry/masonry/builders/editable.py b/src/poetry/masonry/builders/editable.py index 1b38c2bee03..d9725145814 100644 --- a/src/poetry/masonry/builders/editable.py +++ b/src/poetry/masonry/builders/editable.py @@ -249,7 +249,7 @@ def _add_dist_info(self, added_files: list[Path]) -> None: json.dumps( { "dir_info": {"editable": True}, - "url": self._poetry.file.path.parent.as_uri(), + "url": self._poetry.file.path.parent.absolute().as_uri(), } ) ) From 82eb9346e0b8c9523067a926230d67bccaa59b50 Mon Sep 17 00:00:00 2001 From: Bartek Sokorski Date: Sat, 31 Dec 2022 02:53:46 +0100 Subject: [PATCH 038/151] Remove get-poetry.py script --- get-poetry.py | 1171 ------------------------------------------------- 1 file changed, 1171 deletions(-) delete mode 100644 get-poetry.py diff --git a/get-poetry.py b/get-poetry.py deleted file mode 100644 index 99a33cb6869..00000000000 --- a/get-poetry.py +++ /dev/null @@ -1,1171 +0,0 @@ -""" -This script will install Poetry and its dependencies -in isolation from the rest of the system. - -It does, in order: - - - Downloads the latest stable (or pre-release) version of poetry. - - Downloads all its dependencies in the poetry/_vendor directory. - - Copies it and all extra files in $POETRY_HOME. - - Updates the PATH in a system-specific way. - -There will be a `poetry` script that will be installed in $POETRY_HOME/bin -which will act as the poetry command but is slightly different in the sense -that it will use the current Python installation. - -What this means is that one Poetry installation can serve for multiple -Python versions. -""" -import argparse -import hashlib -import json -import os -import platform -import re -import shutil -import stat -import subprocess -import sys -import tarfile -import tempfile - -from contextlib import closing -from contextlib import contextmanager -from functools import cmp_to_key -from gzip import GzipFile -from io import UnsupportedOperation -from io import open - - -try: - from urllib.error import HTTPError - from urllib.request import Request - from urllib.request import urlopen -except ImportError: - from urllib2 import HTTPError - from urllib2 import Request - from urllib2 import urlopen - -try: - input = raw_input -except NameError: - pass - - -try: - try: - import winreg - except ImportError: - import _winreg as winreg -except ImportError: - winreg = None - -try: - u = unicode -except NameError: - u = str - -SHELL = os.getenv("SHELL", "") -WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") - - -FOREGROUND_COLORS = { - "black": 30, - "red": 31, - "green": 32, - "yellow": 33, - "blue": 34, - "magenta": 35, - "cyan": 36, - "white": 37, -} - -BACKGROUND_COLORS = { - "black": 40, - "red": 41, - "green": 42, - "yellow": 43, - "blue": 44, - "magenta": 45, - "cyan": 46, - "white": 47, -} - -OPTIONS = {"bold": 1, "underscore": 4, "blink": 5, "reverse": 7, "conceal": 8} - - -def style(fg, bg, options): - codes = [] - - if fg: - codes.append(FOREGROUND_COLORS[fg]) - - if bg: - codes.append(BACKGROUND_COLORS[bg]) - - if options: - if not isinstance(options, (list, tuple)): - options = [options] - - for option in options: - codes.append(OPTIONS[option]) - - return "\033[{}m".format(";".join(map(str, codes))) - - -STYLES = { - "info": style("green", None, None), - "comment": style("yellow", None, None), - "error": style("red", None, None), - "warning": style("yellow", None, None), - "deprecation": style("magenta", None, None), -} - - -def is_decorated(): - if platform.system().lower() == "windows": - return ( - os.getenv("ANSICON") is not None - or os.getenv("ConEmuANSI") == "ON" - or os.getenv("Term") == "xterm" - ) - - if not hasattr(sys.stdout, "fileno"): - return False - - try: - return os.isatty(sys.stdout.fileno()) - except UnsupportedOperation: - return False - - -def is_interactive(): - if not hasattr(sys.stdin, "fileno"): - return False - - try: - return os.isatty(sys.stdin.fileno()) - except UnsupportedOperation: - return False - - -def colorize(style, text): - if not is_decorated(): - return text - - return "{}{}\033[0m".format(STYLES[style], text) - - -@contextmanager -def temporary_directory(*args, **kwargs): - try: - from tempfile import TemporaryDirectory - except ImportError: - name = tempfile.mkdtemp(*args, **kwargs) - - yield name - - shutil.rmtree(name) - else: - with TemporaryDirectory(*args, **kwargs) as name: - yield name - - -def string_to_bool(value): - value = value.lower() - - return value in {"true", "1", "y", "yes"} - - -def expanduser(path): - """ - Expand ~ and ~user constructions. - - Includes a workaround for http://bugs.python.org/issue14768 - """ - expanded = os.path.expanduser(path) - if path.startswith("~/") and expanded.startswith("//"): - expanded = expanded[1:] - - return expanded - - -HOME = expanduser("~") -POETRY_HOME = os.environ.get("POETRY_HOME") or os.path.join(HOME, ".poetry") -POETRY_BIN = os.path.join(POETRY_HOME, "bin") -POETRY_ENV = os.path.join(POETRY_HOME, "env") -POETRY_LIB = os.path.join(POETRY_HOME, "lib") -POETRY_LIB_BACKUP = os.path.join(POETRY_HOME, "lib-backup") - - -BIN = """# -*- coding: utf-8 -*- -import glob -import sys -import os - -lib = os.path.normpath(os.path.join(os.path.realpath(__file__), "../..", "lib")) -vendors = os.path.join(lib, "poetry", "_vendor") -current_vendors = os.path.join( - vendors, "py{}".format(".".join(str(v) for v in sys.version_info[:2])) -) - -sys.path.insert(0, lib) -sys.path.insert(0, current_vendors) - -if __name__ == "__main__": - from poetry.console import main - - main() -""" - -BAT = u('@echo off\r\n{python_executable} "{poetry_bin}" %*\r\n') - - -PRE_MESSAGE = """# Welcome to {poetry}! - -This will download and install the latest version of {poetry}, -a dependency and package manager for Python. - -It will add the `poetry` command to {poetry}'s bin directory, located at: - -{poetry_home_bin} - -{platform_msg} - -You can uninstall at any time by executing this script with the --uninstall option, -and these changes will be reverted. -""" - -PRE_UNINSTALL_MESSAGE = """# We are sorry to see you go! - -This will uninstall {poetry}. - -It will remove the `poetry` command from {poetry}'s bin directory, located at: - -{poetry_home_bin} - -This will also remove {poetry} from your system's PATH. -""" - - -PRE_MESSAGE_UNIX = """This path will then be added to your `PATH` environment variable by -modifying the profile file{plural} located at: - -{rcfiles}""" - - -PRE_MESSAGE_FISH = """This path will then be added to your `PATH` environment variable by -modifying the `fish_user_paths` universal variable.""" - -PRE_MESSAGE_WINDOWS = """This path will then be added to your `PATH` environment variable by -modifying the `HKEY_CURRENT_USER/Environment/PATH` registry key.""" - -PRE_MESSAGE_NO_MODIFY_PATH = """This path needs to be in your `PATH` environment variable, -but will not be added automatically.""" - -POST_MESSAGE_UNIX = """{poetry} ({version}) is installed now. Great! - -To get started you need {poetry}'s bin directory ({poetry_home_bin}) in your `PATH` -environment variable. Next time you log in this will be done -automatically. - -To configure your current shell run `source {poetry_home_env}` -""" - -POST_MESSAGE_FISH = """{poetry} ({version}) is installed now. Great! - -{poetry}'s bin directory ({poetry_home_bin}) has been added to your `PATH` -environment variable by modifying the `fish_user_paths` universal variable. -""" - -POST_MESSAGE_WINDOWS = """{poetry} ({version}) is installed now. Great! - -To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH` -environment variable. Future applications will automatically have the -correct environment, but you may need to restart your current shell. -""" - -POST_MESSAGE_UNIX_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great! - -To get started you need {poetry}'s bin directory ({poetry_home_bin}) in your `PATH` -environment variable. - -To configure your current shell run `source {poetry_home_env}` -""" - -POST_MESSAGE_FISH_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great! - -To get started you need {poetry}'s bin directory ({poetry_home_bin}) -in your `PATH` environment variable, which you can add by running -the following command: - - set -U fish_user_paths {poetry_home_bin} $fish_user_paths -""" - -POST_MESSAGE_WINDOWS_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great! - -To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH` -environment variable. This has not been done automatically. -""" - - -class Installer: - CURRENT_PYTHON = sys.executable - CURRENT_PYTHON_VERSION = sys.version_info[:2] - METADATA_URL = "https://pypi.org/pypi/poetry/json" - VERSION_REGEX = re.compile( - r"v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?" - "(" - "[._-]?" - r"(?:(stable|beta|b|rc|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*)?)?" - "([.-]?dev)?" - ")?" - r"(?:\+[^\s]+)?" - ) - - REPOSITORY_URL = "https://github.com/python-poetry/poetry" - BASE_URL = REPOSITORY_URL + "/releases/download/" - FALLBACK_BASE_URL = "https://github.com/sdispater/poetry/releases/download/" - - def __init__( - self, - version=None, - preview=False, - force=False, - modify_path=True, - accept_all=False, - file=None, - base_url=BASE_URL, - ): - self._version = version - self._preview = preview - self._force = force - self._modify_path = modify_path - self._accept_all = accept_all - self._offline_file = file - self._base_url = base_url - - def allows_prereleases(self): - return self._preview - - def run(self): - version, current_version = self.get_version() - - if version is None: - return 1 - - self.customize_install() - self.display_pre_message() - self.ensure_home() - - try: - self.install( - version, upgrade=current_version is not None, file=self._offline_file - ) - except subprocess.CalledProcessError as e: - print(colorize("error", "An error has occurred: {}".format(str(e)))) - print(e.output.decode()) - - return e.returncode - - self.display_post_message(version) - - return 0 - - def uninstall(self): - self.display_pre_uninstall_message() - - if not self.customize_uninstall(): - return - - self.remove_home() - self.remove_from_path() - - def get_version(self): - current_version = None - if os.path.exists(POETRY_LIB): - with open( - os.path.join(POETRY_LIB, "poetry", "__version__.py"), encoding="utf-8" - ) as f: - version_content = f.read() - - current_version_re = re.match( - '(?ms).*__version__ = "(.+)".*', version_content - ) - if not current_version_re: - print( - colorize( - "warning", - "Unable to get the current Poetry version. Assuming None", - ) - ) - else: - current_version = current_version_re.group(1) - - # Skip retrieving online release versions if install file is specified - if self._offline_file is not None: - if current_version is not None and not self._force: - print("There is a version of Poetry already installed.") - return None, current_version - - return "from an offline file", current_version - - print(colorize("info", "Retrieving Poetry metadata")) - - metadata = json.loads(self._get(self.METADATA_URL).decode()) - - def _compare_versions(x, y): - mx = self.VERSION_REGEX.match(x) - my = self.VERSION_REGEX.match(y) - - vx = tuple(int(p) for p in mx.groups()[:3]) + (mx.group(5),) - vy = tuple(int(p) for p in my.groups()[:3]) + (my.group(5),) - - if vx < vy: - return -1 - elif vx > vy: - return 1 - - return 0 - - print("") - releases = sorted( - metadata["releases"].keys(), key=cmp_to_key(_compare_versions) - ) - - if self._version and self._version not in releases: - print(colorize("error", "Version {} does not exist.".format(self._version))) - - return None, None - - def _is_supported(x): - mx = self.VERSION_REGEX.match(x) - vx = tuple(int(p) for p in mx.groups()[:3]) + (mx.group(5),) - return vx < (1, 2, 0) - - print( - colorize( - "deprecation", - ( - "This installer is deprecated, and scheduled for removal from the" - " Poetry repository on or after January 1, 2023.\nSee" - " https://github.com/python-poetry/poetry/issues/6377 for" - " details.\n\nYou should migrate to" - " https://install.python-poetry.org instead, which supports all" - " versions of Poetry, and allows for `self update` of versions" - " 1.1.7 or newer.\nInstructions are available at" - " https://python-poetry.org/docs/#installation.\n" - ), - ) - ) - - if not os.environ.get("GET_POETRY_IGNORE_DEPRECATION") and not self._version: - print( - colorize( - "deprecation", - ( - "Without an explicit version, this installer will attempt to" - " install the latest version of Poetry.\nThis installer cannot" - " install Poetry 1.2.0a1 or newer (and installs will be unable" - " to `self update` to 1.2.0a1 or newer).\n\nTo continue to use" - " this deprecated installer, you must specify an explicit" - " version with --version or" - " POETRY_VERSION=.\nAlternatively, if you wish to" - " force this deprecated installer to use the latest installable" - " release, set GET_POETRY_IGNORE_DEPRECATION=1.\n" - ), - ) - ) - - version = self._version - if not version: - for release in reversed(releases): - m = self.VERSION_REGEX.match(release) - if m.group(5) and not self.allows_prereleases(): - continue - - if not os.environ.get("GET_POETRY_IGNORE_DEPRECATION"): - version = release - break - else: - if _is_supported(release): - print( - colorize( - "warning", - "Version {release} will be installed as it is the" - " latest version supported by this installer.\n\nThis" - " is not the latest version of Poetry! If you wish to" - " install the latest version, you must move to the new" - " installer as described above!\n".format( - release=release - ), - ) - ) - version = release - break - else: - print( - colorize( - "warning", - "Version {release} is available but is not supported by" - " this installer!".format(release=release), - ) - ) - - if not _is_supported(version): - print( - colorize( - "error", - "Version {version} is not supported by this installer! Please" - " specify a version prior to 1.2.0a1 to continue!".format( - version=version - ), - ) - ) - - return None, None - - current_version = None - if os.path.exists(POETRY_LIB): - with open( - os.path.join(POETRY_LIB, "poetry", "__version__.py"), encoding="utf-8" - ) as f: - version_content = f.read() - - current_version_re = re.match( - '(?ms).*__version__ = "(.+)".*', version_content - ) - if not current_version_re: - print( - colorize( - "warning", - "Unable to get the current Poetry version. Assuming None", - ) - ) - else: - current_version = current_version_re.group(1) - - if current_version == version and not self._force: - print("Latest version already installed.") - return None, current_version - - return version, current_version - - def customize_install(self): - if not self._accept_all: - print("Before we start, please answer the following questions.") - print("You may simply press the Enter key to leave unchanged.") - - modify_path = input("Modify PATH variable? ([y]/n) ") or "y" - if modify_path.lower() in {"n", "no"}: - self._modify_path = False - - print("") - - def customize_uninstall(self): - if not self._accept_all: - print() - - uninstall = ( - input("Are you sure you want to uninstall Poetry? (y/[n]) ") or "n" - ) - if uninstall.lower() not in {"y", "yes"}: - return False - - print("") - - return True - - def ensure_home(self): - """ - Ensures that $POETRY_HOME exists or create it. - """ - if not os.path.exists(POETRY_HOME): - os.mkdir(POETRY_HOME, 0o755) - - def remove_home(self): - """ - Removes $POETRY_HOME. - """ - if not os.path.exists(POETRY_HOME): - return - - shutil.rmtree(POETRY_HOME) - - def install(self, version, upgrade=False, file=None): - """ - Installs Poetry in $POETRY_HOME. - """ - if file is not None: - print("Attempting to install from file: " + colorize("info", file)) - else: - print("Installing version: " + colorize("info", version)) - - self.make_lib(version) - self.make_bin() - self.make_env() - self.update_path() - - return 0 - - def make_lib(self, version): - """ - Packs everything into a single lib/ directory. - """ - if os.path.exists(POETRY_LIB_BACKUP): - shutil.rmtree(POETRY_LIB_BACKUP) - - # Backup the current installation - if os.path.exists(POETRY_LIB): - shutil.copytree(POETRY_LIB, POETRY_LIB_BACKUP) - shutil.rmtree(POETRY_LIB) - - try: - self._make_lib(version) - except Exception: - if not os.path.exists(POETRY_LIB_BACKUP): - raise - - shutil.copytree(POETRY_LIB_BACKUP, POETRY_LIB) - shutil.rmtree(POETRY_LIB_BACKUP) - - raise - finally: - if os.path.exists(POETRY_LIB_BACKUP): - shutil.rmtree(POETRY_LIB_BACKUP) - - def _make_lib(self, version): - # Check if an offline installer file has been specified - if self._offline_file is not None: - try: - self.extract_lib(self._offline_file) - return - except Exception: - raise RuntimeError("Could not install from offline file.") - - # We get the payload from the remote host - platform = sys.platform - if platform == "linux2": - platform = "linux" - - url = self._base_url + "{}/".format(version) - name = "poetry-{}-{}.tar.gz".format(version, platform) - checksum = "poetry-{}-{}.sha256sum".format(version, platform) - - try: - r = urlopen(url + "{}".format(checksum)) - except HTTPError as e: - if e.code == 404: - raise RuntimeError("Could not find {} file".format(checksum)) - - raise - - checksum = r.read().decode() - - try: - r = urlopen(url + "{}".format(name)) - except HTTPError as e: - if e.code == 404: - raise RuntimeError("Could not find {} file".format(name)) - - raise - - meta = r.info() - size = int(meta["Content-Length"]) - current = 0 - block_size = 8192 - - print( - " - Downloading {} ({:.2f}MB)".format( - colorize("comment", name), size / 1024 / 1024 - ) - ) - - sha = hashlib.sha256() - with temporary_directory(prefix="poetry-installer-") as dir_: - tar = os.path.join(dir_, name) - with open(tar, "wb") as f: - while True: - buffer = r.read(block_size) - if not buffer: - break - - current += len(buffer) - f.write(buffer) - sha.update(buffer) - - # Checking hashes - if checksum != sha.hexdigest(): - raise RuntimeError( - "Hashes for {} do not match: {} != {}".format( - name, checksum, sha.hexdigest() - ) - ) - - self.extract_lib(tar) - - def extract_lib(self, filename): - gz = GzipFile(filename, mode="rb") - try: - with tarfile.TarFile(filename, fileobj=gz, format=tarfile.PAX_FORMAT) as f: - f.extractall(POETRY_LIB) - finally: - gz.close() - - def _which_python(self): - """Decides which python executable we'll embed in the launcher script.""" - allowed_executables = ["python3", "python"] - if WINDOWS: - allowed_executables += ["py.exe -3", "py.exe -2"] - - # \d in regex ensures we can convert to int later - version_matcher = re.compile(r"^Python (?P\d+)\.(?P\d+)\..+$") - fallback = None - for executable in allowed_executables: - try: - raw_version = subprocess.check_output( - executable + " --version", stderr=subprocess.STDOUT, shell=True - ).decode("utf-8") - except subprocess.CalledProcessError: - continue - - match = version_matcher.match(raw_version.strip()) - if match: - return executable - - if fallback is None: - # keep this one as the fallback; it was the first valid executable we - # found. - fallback = executable - - if fallback is None: - raise RuntimeError( - "No python executable found in shell environment. Tried: " - + str(allowed_executables) - ) - - return fallback - - def make_bin(self): - if not os.path.exists(POETRY_BIN): - os.mkdir(POETRY_BIN, 0o755) - - python_executable = self._which_python() - - if WINDOWS: - with open(os.path.join(POETRY_BIN, "poetry.bat"), "w") as f: - f.write( - u( - BAT.format( - python_executable=python_executable, - poetry_bin=os.path.join(POETRY_BIN, "poetry").replace( - os.environ["USERPROFILE"], "%USERPROFILE%" - ), - ) - ) - ) - - with open(os.path.join(POETRY_BIN, "poetry"), "w", encoding="utf-8") as f: - if WINDOWS: - python_executable = "python" - - f.write(u("#!/usr/bin/env {}\n".format(python_executable))) - f.write(u(BIN)) - - if not WINDOWS: - # Making the file executable - st = os.stat(os.path.join(POETRY_BIN, "poetry")) - os.chmod(os.path.join(POETRY_BIN, "poetry"), st.st_mode | stat.S_IEXEC) - - def make_env(self): - if WINDOWS: - return - - with open(os.path.join(POETRY_HOME, "env"), "w") as f: - f.write(u(self.get_export_string())) - - def update_path(self): - """ - Tries to update the $PATH automatically. - """ - if not self._modify_path: - return - - if "fish" in SHELL: - return self.add_to_fish_path() - - if WINDOWS: - return self.add_to_windows_path() - - # Updating any profile we can on UNIX systems - export_string = self.get_export_string() - - addition = "\n{}\n".format(export_string) - - profiles = self.get_unix_profiles() - for profile in profiles: - if not os.path.exists(profile): - continue - - with open(profile, "r") as f: - content = f.read() - - if addition not in content: - with open(profile, "a") as f: - f.write(u(addition)) - - def add_to_fish_path(self): - """ - Ensure POETRY_BIN directory is on Fish shell $PATH - """ - current_path = os.environ.get("PATH", None) - if current_path is None: - print( - colorize( - "warning", - ( - "\nUnable to get the PATH value. It will not be updated" - " automatically." - ), - ) - ) - self._modify_path = False - - return - - if POETRY_BIN not in current_path: - fish_user_paths = subprocess.check_output( - ["fish", "-c", "echo $fish_user_paths"] - ).decode("utf-8") - if POETRY_BIN not in fish_user_paths: - cmd = "set -U fish_user_paths {} $fish_user_paths".format(POETRY_BIN) - set_fish_user_path = ["fish", "-c", "{}".format(cmd)] - subprocess.check_output(set_fish_user_path) - else: - print( - colorize( - "warning", - "\nPATH already contains {} and thus was not modified.".format( - POETRY_BIN - ), - ) - ) - - def add_to_windows_path(self): - try: - old_path = self.get_windows_path_var() - except WindowsError: - old_path = None - - if old_path is None: - print( - colorize( - "warning", - ( - "Unable to get the PATH value. It will not be updated" - " automatically" - ), - ) - ) - self._modify_path = False - - return - - new_path = POETRY_BIN - if POETRY_BIN in old_path: - old_path = old_path.replace(POETRY_BIN + ";", "") - - if old_path: - new_path += ";" - new_path += old_path - - self.set_windows_path_var(new_path) - - def get_windows_path_var(self): - with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as root: - with winreg.OpenKey(root, "Environment", 0, winreg.KEY_ALL_ACCESS) as key: - path, _ = winreg.QueryValueEx(key, "PATH") - - return path - - def set_windows_path_var(self, value): - import ctypes - - with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as root: - with winreg.OpenKey(root, "Environment", 0, winreg.KEY_ALL_ACCESS) as key: - winreg.SetValueEx(key, "PATH", 0, winreg.REG_EXPAND_SZ, value) - - # Tell other processes to update their environment - HWND_BROADCAST = 0xFFFF - WM_SETTINGCHANGE = 0x1A - - SMTO_ABORTIFHUNG = 0x0002 - - result = ctypes.c_long() - SendMessageTimeoutW = ctypes.windll.user32.SendMessageTimeoutW - SendMessageTimeoutW( - HWND_BROADCAST, - WM_SETTINGCHANGE, - 0, - "Environment", - SMTO_ABORTIFHUNG, - 5000, - ctypes.byref(result), - ) - - def remove_from_path(self): - if "fish" in SHELL: - return self.remove_from_fish_path() - - elif WINDOWS: - return self.remove_from_windows_path() - - return self.remove_from_unix_path() - - def remove_from_fish_path(self): - fish_user_paths = subprocess.check_output( - ["fish", "-c", "echo $fish_user_paths"] - ).decode("utf-8") - if POETRY_BIN in fish_user_paths: - cmd = "set -U fish_user_paths (string match -v {} $fish_user_paths)".format( - POETRY_BIN - ) - set_fish_user_path = ["fish", "-c", "{}".format(cmd)] - subprocess.check_output(set_fish_user_path) - - def remove_from_windows_path(self): - path = self.get_windows_path_var() - - poetry_path = POETRY_BIN - if poetry_path in path: - path = path.replace(POETRY_BIN + ";", "") - - if poetry_path in path: - path = path.replace(POETRY_BIN, "") - - self.set_windows_path_var(path) - - def remove_from_unix_path(self): - # Updating any profile we can on UNIX systems - export_string = self.get_export_string() - - addition = "{}\n".format(export_string) - - profiles = self.get_unix_profiles() - for profile in profiles: - if not os.path.exists(profile): - continue - - with open(profile, "r") as f: - content = f.readlines() - - if addition not in content: - continue - - new_content = [] - for line in content: - if line == addition: - if new_content and not new_content[-1].strip(): - new_content = new_content[:-1] - - continue - - new_content.append(line) - - with open(profile, "w") as f: - f.writelines(new_content) - - def get_export_string(self): - path = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME") - export_string = 'export PATH="{}:$PATH"'.format(path) - - return export_string - - def get_unix_profiles(self): - profiles = [os.path.join(HOME, ".profile")] - - if "zsh" in SHELL: - zdotdir = os.getenv("ZDOTDIR", HOME) - profiles.append(os.path.join(zdotdir, ".zshrc")) - - bash_profile = os.path.join(HOME, ".bash_profile") - if os.path.exists(bash_profile): - profiles.append(bash_profile) - - return profiles - - def display_pre_message(self): - if WINDOWS: - home = POETRY_BIN.replace(os.getenv("USERPROFILE", ""), "%USERPROFILE%") - else: - home = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME") - - kwargs = { - "poetry": colorize("info", "Poetry"), - "poetry_home_bin": colorize("comment", home), - } - - if not self._modify_path: - kwargs["platform_msg"] = PRE_MESSAGE_NO_MODIFY_PATH - else: - if "fish" in SHELL: - kwargs["platform_msg"] = PRE_MESSAGE_FISH - elif WINDOWS: - kwargs["platform_msg"] = PRE_MESSAGE_WINDOWS - else: - profiles = [ - colorize("comment", p.replace(os.getenv("HOME", ""), "$HOME")) - for p in self.get_unix_profiles() - ] - kwargs["platform_msg"] = PRE_MESSAGE_UNIX.format( - rcfiles="\n".join(profiles), plural="s" if len(profiles) > 1 else "" - ) - - print(PRE_MESSAGE.format(**kwargs)) - - def display_pre_uninstall_message(self): - home_bin = POETRY_BIN - if WINDOWS: - home_bin = home_bin.replace(os.getenv("USERPROFILE", ""), "%USERPROFILE%") - else: - home_bin = home_bin.replace(os.getenv("HOME", ""), "$HOME") - - kwargs = { - "poetry": colorize("info", "Poetry"), - "poetry_home_bin": colorize("comment", home_bin), - } - - print(PRE_UNINSTALL_MESSAGE.format(**kwargs)) - - def display_post_message(self, version): - print("") - - kwargs = { - "poetry": colorize("info", "Poetry"), - "version": colorize("comment", version), - } - - if WINDOWS: - message = POST_MESSAGE_WINDOWS - if not self._modify_path: - message = POST_MESSAGE_WINDOWS_NO_MODIFY_PATH - - poetry_home_bin = POETRY_BIN.replace( - os.getenv("USERPROFILE", ""), "%USERPROFILE%" - ) - elif "fish" in SHELL: - message = POST_MESSAGE_FISH - if not self._modify_path: - message = POST_MESSAGE_FISH_NO_MODIFY_PATH - - poetry_home_bin = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME") - else: - message = POST_MESSAGE_UNIX - if not self._modify_path: - message = POST_MESSAGE_UNIX_NO_MODIFY_PATH - - poetry_home_bin = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME") - kwargs["poetry_home_env"] = colorize( - "comment", POETRY_ENV.replace(os.getenv("HOME", ""), "$HOME") - ) - - kwargs["poetry_home_bin"] = colorize("comment", poetry_home_bin) - - print(message.format(**kwargs)) - - def call(self, *args): - return subprocess.check_output(args, stderr=subprocess.STDOUT) - - def _get(self, url): - request = Request(url, headers={"User-Agent": "Python Poetry"}) - - with closing(urlopen(request)) as r: - return r.read() - - -def main(): - parser = argparse.ArgumentParser( - description="Installs the latest (or given) version of poetry" - ) - parser.add_argument( - "-p", - "--preview", - help="install preview version", - dest="preview", - action="store_true", - default=False, - ) - parser.add_argument("--version", help="install named version", dest="version") - parser.add_argument( - "-f", - "--force", - help="install on top of existing version", - dest="force", - action="store_true", - default=False, - ) - parser.add_argument( - "--no-modify-path", - help="do not modify $PATH", - dest="no_modify_path", - action="store_true", - default=False, - ) - parser.add_argument( - "-y", - "--yes", - help="accept all prompts", - dest="accept_all", - action="store_true", - default=False, - ) - parser.add_argument( - "--uninstall", - help="uninstall poetry", - dest="uninstall", - action="store_true", - default=False, - ) - parser.add_argument( - "--file", - dest="file", - action="store", - help=( - "Install from a local file instead of fetching the latest version " - "of Poetry available online." - ), - ) - - args = parser.parse_args() - - base_url = Installer.BASE_URL - - if args.file is None: - try: - urlopen(Installer.REPOSITORY_URL) - except HTTPError as e: - if e.code == 404: - base_url = Installer.FALLBACK_BASE_URL - else: - raise - - installer = Installer( - version=args.version or os.getenv("POETRY_VERSION"), - preview=args.preview or string_to_bool(os.getenv("POETRY_PREVIEW", "0")), - force=args.force, - modify_path=not args.no_modify_path, - accept_all=args.accept_all - or string_to_bool(os.getenv("POETRY_ACCEPT", "0")) - or not is_interactive(), - file=args.file, - base_url=base_url, - ) - - if args.uninstall or string_to_bool(os.getenv("POETRY_UNINSTALL", "0")): - return installer.uninstall() - - return installer.run() - - -if __name__ == "__main__": - sys.exit(main()) From a013c5c02565dc3011ae95230f699628b6abcdeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Thu, 22 Dec 2022 11:55:10 +0100 Subject: [PATCH 039/151] performance: cache pages of PyPI repository in the same way as for legacy repository to fix performance regression caused by replacing cachy --- src/poetry/repositories/http_repository.py | 7 +++- src/poetry/repositories/legacy_repository.py | 15 +++----- src/poetry/repositories/pypi_repository.py | 10 ++--- .../repositories/single_page_repository.py | 8 +++- tests/installation/test_chooser.py | 2 +- ...al_yank.html => futures-partial-yank.html} | 0 tests/repositories/test_legacy_repository.py | 38 +++++++++---------- .../test_single_page_repository.py | 7 +++- 8 files changed, 47 insertions(+), 40 deletions(-) rename tests/repositories/fixtures/legacy/{futures_partial_yank.html => futures-partial-yank.html} (100%) diff --git a/src/poetry/repositories/http_repository.py b/src/poetry/repositories/http_repository.py index bcb0e0fa2b6..8077f9ce57e 100644 --- a/src/poetry/repositories/http_repository.py +++ b/src/poetry/repositories/http_repository.py @@ -30,8 +30,11 @@ if TYPE_CHECKING: + from packaging.utils import NormalizedName + from poetry.config.config import Config from poetry.inspection.info import PackageInfo + from poetry.repositories.link_sources.base import LinkSource from poetry.utils.authenticator import RepositoryCertificateConfig @@ -293,8 +296,8 @@ def _get_response(self, endpoint: str) -> requests.Response | None: ) return response - def _get_page(self, endpoint: str) -> HTMLPage | None: - response = self._get_response(endpoint) + def _get_page(self, name: NormalizedName) -> LinkSource | None: + response = self._get_response(f"/{name}/") if not response: return None return HTMLPage(response.url, response.text) diff --git a/src/poetry/repositories/legacy_repository.py b/src/poetry/repositories/legacy_repository.py index 73bde91a664..89e5221a706 100644 --- a/src/poetry/repositories/legacy_repository.py +++ b/src/poetry/repositories/legacy_repository.py @@ -72,7 +72,7 @@ def package( return package def find_links_for_package(self, package: Package) -> list[Link]: - page = self.get_page(f"/{package.name}/") + page = self.get_page(package.name) if page is None: return [] @@ -90,12 +90,9 @@ def _find_packages( if not constraint.is_any(): key = f"{key}:{constraint!s}" - page = self.get_page(f"/{name}/") + page = self.get_page(name) if page is None: - self._log( - f"No packages found for {name}", - level="debug", - ) + self._log(f"No packages found for {name}", level="debug") return [] versions = [ @@ -119,7 +116,7 @@ def _find_packages( def _get_release_info( self, name: NormalizedName, version: Version ) -> dict[str, Any]: - page = self.get_page(f"/{name}/") + page = self.get_page(name) if page is None: raise PackageNotFound(f'No package named "{name}"') @@ -141,8 +138,8 @@ def _get_release_info( ), ) - def _get_page(self, endpoint: str) -> SimpleRepositoryPage | None: - response = self._get_response(endpoint) + def _get_page(self, name: NormalizedName) -> SimpleRepositoryPage | None: + response = self._get_response(f"/{name}/") if not response: return None return SimpleRepositoryPage(response.url, response.text) diff --git a/src/poetry/repositories/pypi_repository.py b/src/poetry/repositories/pypi_repository.py index 541c7ea81fb..35c8c99e1ca 100644 --- a/src/poetry/repositories/pypi_repository.py +++ b/src/poetry/repositories/pypi_repository.py @@ -111,13 +111,11 @@ def _find_packages( Find packages on the remote server. """ try: - json_page = self.get_json_page(name) + json_page = self.get_page(name) except PackageNotFound: - self._log( - f"No packages found for {name}", - level="debug", - ) + self._log(f"No packages found for {name}", level="debug") return [] + assert isinstance(json_page, SimpleJsonPage) versions: list[tuple[Version, str | bool]] @@ -226,7 +224,7 @@ def _get_release_info( return data.asdict() - def get_json_page(self, name: NormalizedName) -> SimpleJsonPage: + def _get_page(self, name: NormalizedName) -> SimpleJsonPage: source = self._base_url + f"simple/{name}/" info = self.get_package_info(name) return SimpleJsonPage(source, info) diff --git a/src/poetry/repositories/single_page_repository.py b/src/poetry/repositories/single_page_repository.py index e8de0b141f8..1cd77a4d3a1 100644 --- a/src/poetry/repositories/single_page_repository.py +++ b/src/poetry/repositories/single_page_repository.py @@ -1,11 +1,17 @@ from __future__ import annotations +from typing import TYPE_CHECKING + from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.link_sources.html import SimpleRepositoryPage +if TYPE_CHECKING: + from packaging.utils import NormalizedName + + class SinglePageRepository(LegacyRepository): - def _get_page(self, endpoint: str | None = None) -> SimpleRepositoryPage | None: + def _get_page(self, name: NormalizedName) -> SimpleRepositoryPage | None: """ Single page repositories only have one page irrespective of endpoint. """ diff --git a/tests/installation/test_chooser.py b/tests/installation/test_chooser.py index 0c82faacf61..a50338489a4 100644 --- a/tests/installation/test_chooser.py +++ b/tests/installation/test_chooser.py @@ -98,7 +98,7 @@ def callback( parts = uri.rsplit("/") name = parts[-2] - fixture = LEGACY_FIXTURES / (name + "_partial_yank" + ".html") + fixture = LEGACY_FIXTURES / (name + "-partial-yank" + ".html") with fixture.open(encoding="utf-8") as f: return [200, headers, f.read()] diff --git a/tests/repositories/fixtures/legacy/futures_partial_yank.html b/tests/repositories/fixtures/legacy/futures-partial-yank.html similarity index 100% rename from tests/repositories/fixtures/legacy/futures_partial_yank.html rename to tests/repositories/fixtures/legacy/futures-partial-yank.html diff --git a/tests/repositories/test_legacy_repository.py b/tests/repositories/test_legacy_repository.py index 9503897a978..c17f42047fc 100644 --- a/tests/repositories/test_legacy_repository.py +++ b/tests/repositories/test_legacy_repository.py @@ -30,6 +30,7 @@ import httpretty from _pytest.monkeypatch import MonkeyPatch + from packaging.utils import NormalizedName from poetry.config.config import Config @@ -45,16 +46,13 @@ class MockRepository(LegacyRepository): def __init__(self) -> None: super().__init__("legacy", url="http://legacy.foo.bar", disable_cache=True) - def _get_page(self, endpoint: str) -> SimpleRepositoryPage | None: - parts = endpoint.split("/") - name = parts[1] - + def _get_page(self, name: NormalizedName) -> SimpleRepositoryPage | None: fixture = self.FIXTURES / (name + ".html") if not fixture.exists(): return None with fixture.open(encoding="utf-8") as f: - return SimpleRepositoryPage(self._url + endpoint, f.read()) + return SimpleRepositoryPage(self._url + f"/{name}/", f.read()) def _download(self, url: str, dest: Path) -> None: filename = urlparse.urlparse(url).path.rsplit("/")[-1] @@ -73,7 +71,7 @@ def test_packages_property_returns_empty_list() -> None: def test_page_relative_links_path_are_correct() -> None: repo = MockRepository() - page = repo.get_page("/relative") + page = repo.get_page("relative") assert page is not None for link in page.links: @@ -84,7 +82,7 @@ def test_page_relative_links_path_are_correct() -> None: def test_page_absolute_links_path_are_correct() -> None: repo = MockRepository() - page = repo.get_page("/absolute") + page = repo.get_page("absolute") assert page is not None for link in page.links: @@ -95,7 +93,7 @@ def test_page_absolute_links_path_are_correct() -> None: def test_page_clean_link() -> None: repo = MockRepository() - page = repo.get_page("/relative") + page = repo.get_page("relative") assert page is not None cleaned = page.clean_link('https://legacy.foo.bar/test /the"/cleaning\0') @@ -105,7 +103,7 @@ def test_page_clean_link() -> None: def test_page_invalid_version_link() -> None: repo = MockRepository() - page = repo.get_page("/invalid-version") + page = repo.get_page("invalid-version") assert page is not None links = list(page.links) @@ -123,7 +121,7 @@ def test_page_invalid_version_link() -> None: def test_sdist_format_support() -> None: repo = MockRepository() - page = repo.get_page("/relative") + page = repo.get_page("relative") assert page is not None bz2_links = list(filter(lambda link: link.ext == ".tar.bz2", page.links)) assert len(bz2_links) == 1 @@ -434,8 +432,8 @@ def test_package_yanked( def test_package_partial_yank(): class SpecialMockRepository(MockRepository): - def _get_page(self, endpoint: str) -> SimpleRepositoryPage | None: - return super()._get_page(f"/{endpoint.strip('/')}_partial_yank/") + def _get_page(self, name: NormalizedName) -> SimpleRepositoryPage | None: + return super()._get_page(canonicalize_name(f"{name}-partial-yank")) repo = MockRepository() package = repo.package("futures", Version.parse("3.2.0")) @@ -481,31 +479,31 @@ def __init__( def test_get_200_returns_page(http: type[httpretty.httpretty]) -> None: - repo = MockHttpRepository({"/foo": 200}, http) + repo = MockHttpRepository({"/foo/": 200}, http) - assert repo.get_page("/foo") + assert repo.get_page("foo") @pytest.mark.parametrize("status_code", [401, 403, 404]) def test_get_40x_and_returns_none( http: type[httpretty.httpretty], status_code: int ) -> None: - repo = MockHttpRepository({"/foo": status_code}, http) + repo = MockHttpRepository({"/foo/": status_code}, http) - assert repo.get_page("/foo") is None + assert repo.get_page("foo") is None def test_get_5xx_raises(http: type[httpretty.httpretty]) -> None: - repo = MockHttpRepository({"/foo": 500}, http) + repo = MockHttpRepository({"/foo/": 500}, http) with pytest.raises(RepositoryError): - repo.get_page("/foo") + repo.get_page("foo") def test_get_redirected_response_url( http: type[httpretty.httpretty], monkeypatch: MonkeyPatch ) -> None: - repo = MockHttpRepository({"/foo": 200}, http) + repo = MockHttpRepository({"/foo/": 200}, http) redirect_url = "http://legacy.redirect.bar" def get_mock( @@ -517,7 +515,7 @@ def get_mock( return response monkeypatch.setattr(repo.session, "get", get_mock) - page = repo.get_page("/foo") + page = repo.get_page("foo") assert page is not None assert page._url == "http://legacy.redirect.bar/foo/" diff --git a/tests/repositories/test_single_page_repository.py b/tests/repositories/test_single_page_repository.py index 54e7069feeb..081a58d6f32 100644 --- a/tests/repositories/test_single_page_repository.py +++ b/tests/repositories/test_single_page_repository.py @@ -3,6 +3,7 @@ import re from pathlib import Path +from typing import TYPE_CHECKING from poetry.core.packages.dependency import Dependency @@ -10,6 +11,10 @@ from poetry.repositories.single_page_repository import SinglePageRepository +if TYPE_CHECKING: + from packaging.utils import NormalizedName + + class MockSinglePageRepository(SinglePageRepository): FIXTURES = Path(__file__).parent / "fixtures" / "single-page" @@ -20,7 +25,7 @@ def __init__(self, page: str) -> None: disable_cache=True, ) - def _get_page(self, endpoint: str = None) -> SimpleRepositoryPage | None: + def _get_page(self, name: NormalizedName) -> SimpleRepositoryPage | None: fixture = self.FIXTURES / self.url.rsplit("/", 1)[-1] if not fixture.exists(): return From bfd490c2ca3a8ff0d73b71ba355454df88868627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Thu, 22 Dec 2022 14:28:44 +0100 Subject: [PATCH 040/151] refactor(repository): always raise PackageNotFound in _get_page instead of returning None; remove unsuccessful attempt to get pretty name via json-based simple API --- src/poetry/repositories/http_repository.py | 4 ++-- src/poetry/repositories/legacy_repository.py | 22 +++++++------------ src/poetry/repositories/pypi_repository.py | 14 +----------- .../repositories/single_page_repository.py | 5 +++-- tests/repositories/test_legacy_repository.py | 9 ++++---- tests/repositories/test_pypi_repository.py | 8 ------- .../test_single_page_repository.py | 5 +++-- 7 files changed, 22 insertions(+), 45 deletions(-) diff --git a/src/poetry/repositories/http_repository.py b/src/poetry/repositories/http_repository.py index 8077f9ce57e..298fc1e815b 100644 --- a/src/poetry/repositories/http_repository.py +++ b/src/poetry/repositories/http_repository.py @@ -296,8 +296,8 @@ def _get_response(self, endpoint: str) -> requests.Response | None: ) return response - def _get_page(self, name: NormalizedName) -> LinkSource | None: + def _get_page(self, name: NormalizedName) -> LinkSource: response = self._get_response(f"/{name}/") if not response: - return None + raise PackageNotFound(f"Package [{name}] not found.") return HTMLPage(response.url, response.text) diff --git a/src/poetry/repositories/legacy_repository.py b/src/poetry/repositories/legacy_repository.py index 89e5221a706..7654d64d921 100644 --- a/src/poetry/repositories/legacy_repository.py +++ b/src/poetry/repositories/legacy_repository.py @@ -72,8 +72,9 @@ def package( return package def find_links_for_package(self, package: Package) -> list[Link]: - page = self.get_page(package.name) - if page is None: + try: + page = self.get_page(package.name) + except PackageNotFound: return [] return list(page.links_for_version(package.name, package.version)) @@ -84,14 +85,9 @@ def _find_packages( """ Find packages on the remote server. """ - versions: list[tuple[Version, str | bool]] - - key: str = name - if not constraint.is_any(): - key = f"{key}:{constraint!s}" - - page = self.get_page(name) - if page is None: + try: + page = self.get_page(name) + except PackageNotFound: self._log(f"No packages found for {name}", level="debug") return [] @@ -117,8 +113,6 @@ def _get_release_info( self, name: NormalizedName, version: Version ) -> dict[str, Any]: page = self.get_page(name) - if page is None: - raise PackageNotFound(f'No package named "{name}"') links = list(page.links_for_version(name, version)) yanked = page.yanked(name, version) @@ -138,8 +132,8 @@ def _get_release_info( ), ) - def _get_page(self, name: NormalizedName) -> SimpleRepositoryPage | None: + def _get_page(self, name: NormalizedName) -> SimpleRepositoryPage: response = self._get_response(f"/{name}/") if not response: - return None + raise PackageNotFound(f"Package [{name}] not found.") return SimpleRepositoryPage(response.url, response.text) diff --git a/src/poetry/repositories/pypi_repository.py b/src/poetry/repositories/pypi_repository.py index 35c8c99e1ca..14f0b0fc3f8 100644 --- a/src/poetry/repositories/pypi_repository.py +++ b/src/poetry/repositories/pypi_repository.py @@ -115,13 +115,6 @@ def _find_packages( except PackageNotFound: self._log(f"No packages found for {name}", level="debug") return [] - assert isinstance(json_page, SimpleJsonPage) - - versions: list[tuple[Version, str | bool]] - - key: str = name - if not constraint.is_any(): - key = f"{key}:{constraint!s}" versions = [ (version, json_page.yanked(name, version)) @@ -129,12 +122,7 @@ def _find_packages( if constraint.allows(version) ] - pretty_name = json_page.content["name"] - packages = [ - Package(pretty_name, version, yanked=yanked) for version, yanked in versions - ] - - return packages + return [Package(name, version, yanked=yanked) for version, yanked in versions] def _get_package_info(self, name: str) -> dict[str, Any]: headers = {"Accept": "application/vnd.pypi.simple.v1+json"} diff --git a/src/poetry/repositories/single_page_repository.py b/src/poetry/repositories/single_page_repository.py index 1cd77a4d3a1..7bdc469bbf0 100644 --- a/src/poetry/repositories/single_page_repository.py +++ b/src/poetry/repositories/single_page_repository.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING +from poetry.repositories.exceptions import PackageNotFound from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.link_sources.html import SimpleRepositoryPage @@ -11,11 +12,11 @@ class SinglePageRepository(LegacyRepository): - def _get_page(self, name: NormalizedName) -> SimpleRepositoryPage | None: + def _get_page(self, name: NormalizedName) -> SimpleRepositoryPage: """ Single page repositories only have one page irrespective of endpoint. """ response = self._get_response("") if not response: - return None + raise PackageNotFound(f"Package [{name}] not found.") return SimpleRepositoryPage(response.url, response.text) diff --git a/tests/repositories/test_legacy_repository.py b/tests/repositories/test_legacy_repository.py index c17f42047fc..bdb68935894 100644 --- a/tests/repositories/test_legacy_repository.py +++ b/tests/repositories/test_legacy_repository.py @@ -46,10 +46,10 @@ class MockRepository(LegacyRepository): def __init__(self) -> None: super().__init__("legacy", url="http://legacy.foo.bar", disable_cache=True) - def _get_page(self, name: NormalizedName) -> SimpleRepositoryPage | None: + def _get_page(self, name: NormalizedName) -> SimpleRepositoryPage: fixture = self.FIXTURES / (name + ".html") if not fixture.exists(): - return None + raise PackageNotFound(f"Package [{name}] not found.") with fixture.open(encoding="utf-8") as f: return SimpleRepositoryPage(self._url + f"/{name}/", f.read()) @@ -432,7 +432,7 @@ def test_package_yanked( def test_package_partial_yank(): class SpecialMockRepository(MockRepository): - def _get_page(self, name: NormalizedName) -> SimpleRepositoryPage | None: + def _get_page(self, name: NormalizedName) -> SimpleRepositoryPage: return super()._get_page(canonicalize_name(f"{name}-partial-yank")) repo = MockRepository() @@ -490,7 +490,8 @@ def test_get_40x_and_returns_none( ) -> None: repo = MockHttpRepository({"/foo/": status_code}, http) - assert repo.get_page("foo") is None + with pytest.raises(PackageNotFound): + repo.get_page("foo") def test_get_5xx_raises(http: type[httpretty.httpretty]) -> None: diff --git a/tests/repositories/test_pypi_repository.py b/tests/repositories/test_pypi_repository.py index 5ab69c77f2b..bff1a4fb5d5 100644 --- a/tests/repositories/test_pypi_repository.py +++ b/tests/repositories/test_pypi_repository.py @@ -332,14 +332,6 @@ def test_urls() -> None: assert repository.authenticated_url == "https://pypi.org/simple/" -def test_use_pypi_pretty_name() -> None: - repo = MockRepository(fallback=True) - - package = repo.find_packages(Factory.create_dependency("twisted", "*")) - assert len(package) == 1 - assert package[0].pretty_name == "Twisted" - - def test_find_links_for_package_of_supported_types(): repo = MockRepository() package = repo.find_packages(Factory.create_dependency("hbmqtt", "0.9.6")) diff --git a/tests/repositories/test_single_page_repository.py b/tests/repositories/test_single_page_repository.py index 081a58d6f32..804fa3d0125 100644 --- a/tests/repositories/test_single_page_repository.py +++ b/tests/repositories/test_single_page_repository.py @@ -7,6 +7,7 @@ from poetry.core.packages.dependency import Dependency +from poetry.repositories.exceptions import PackageNotFound from poetry.repositories.link_sources.html import SimpleRepositoryPage from poetry.repositories.single_page_repository import SinglePageRepository @@ -25,10 +26,10 @@ def __init__(self, page: str) -> None: disable_cache=True, ) - def _get_page(self, name: NormalizedName) -> SimpleRepositoryPage | None: + def _get_page(self, name: NormalizedName) -> SimpleRepositoryPage: fixture = self.FIXTURES / self.url.rsplit("/", 1)[-1] if not fixture.exists(): - return + raise PackageNotFound(f"Package [{name}] not found.") with fixture.open(encoding="utf-8") as f: return SimpleRepositoryPage(self._url, f.read()) From 7c86992909257caa4f51a50c001f5894bfe5065e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sun, 1 Jan 2023 23:47:48 +0100 Subject: [PATCH 041/151] fix: PEP 440 pre-release handling (part 2) Remove implicit "allow-prereleases" in repository, consider explicit allow-prereleases in show, make search_for test in provider forward-compatible. --- src/poetry/console/commands/show.py | 13 ++++++++++--- src/poetry/repositories/repository.py | 23 ++--------------------- tests/puzzle/test_provider.py | 21 ++++++++++++--------- 3 files changed, 24 insertions(+), 33 deletions(-) diff --git a/src/poetry/console/commands/show.py b/src/poetry/console/commands/show.py index 0fc9af3b46b..b0248f7df77 100644 --- a/src/poetry/console/commands/show.py +++ b/src/poetry/console/commands/show.py @@ -522,18 +522,25 @@ def find_latest_package( from poetry.version.version_selector import VersionSelector # find the latest version allowed in this pool + requires = root.all_requires if package.is_direct_origin(): - requires = root.all_requires - for dep in requires: if dep.name == package.name and dep.source_type == package.source_type: provider = Provider(root, self.poetry.pool, NullIO()) return provider.search_for_direct_origin_dependency(dep) + allow_prereleases = False + for dep in requires: + if dep.name == package.name: + allow_prereleases = dep.allows_prereleases() + break + name = package.name selector = VersionSelector(self.poetry.pool) - return selector.find_best_candidate(name, f">={package.pretty_version}") + return selector.find_best_candidate( + name, f">={package.pretty_version}", allow_prereleases + ) def get_update_status(self, latest: Package, package: Package) -> str: from poetry.core.constraints.version import parse_constraint diff --git a/src/poetry/repositories/repository.py b/src/poetry/repositories/repository.py index 274ece2ae65..c9dd4b85969 100644 --- a/src/poetry/repositories/repository.py +++ b/src/poetry/repositories/repository.py @@ -6,7 +6,6 @@ from packaging.utils import canonicalize_name from poetry.core.constraints.version import Version -from poetry.core.constraints.version import VersionRange from poetry.repositories.abstract_repository import AbstractRepository from poetry.repositories.exceptions import PackageNotFound @@ -34,11 +33,10 @@ def packages(self) -> list[Package]: def find_packages(self, dependency: Dependency) -> list[Package]: packages = [] - constraint, allow_prereleases = self._get_constraints_from_dependency( - dependency - ) ignored_pre_release_packages = [] + constraint = dependency.constraint + allow_prereleases = dependency.allows_prereleases() for package in self._find_packages(dependency.name, constraint): if package.yanked and not isinstance(constraint, Version): # PEP 592: yanked files are always ignored, unless they are the only @@ -92,23 +90,6 @@ def search(self, query: str) -> list[Package]: return results - @staticmethod - def _get_constraints_from_dependency( - dependency: Dependency, - ) -> tuple[VersionConstraint, bool]: - constraint = dependency.constraint - - allow_prereleases = dependency.allows_prereleases() - if isinstance(constraint, VersionRange) and ( - constraint.max is not None - and constraint.max.is_unstable() - or constraint.min is not None - and constraint.min.is_unstable() - ): - allow_prereleases = True - - return constraint, allow_prereleases - def _find_packages( self, name: NormalizedName, constraint: VersionConstraint ) -> list[Package]: diff --git a/tests/puzzle/test_provider.py b/tests/puzzle/test_provider.py index a5126a47337..5e96d952832 100644 --- a/tests/puzzle/test_provider.py +++ b/tests/puzzle/test_provider.py @@ -67,15 +67,7 @@ def provider(root: ProjectPackage, pool: RepositoryPool) -> Provider: (Dependency("foo", "<2"), [Package("foo", "1")]), (Dependency("foo", "<2", extras=["bar"]), [Package("foo", "1")]), (Dependency("foo", ">=1"), [Package("foo", "2"), Package("foo", "1")]), - ( - Dependency("foo", ">=1a"), - [ - Package("foo", "3a"), - Package("foo", "2"), - Package("foo", "2a"), - Package("foo", "1"), - ], - ), + (Dependency("foo", ">=1a"), [Package("foo", "2"), Package("foo", "1")]), ( Dependency("foo", ">=1", allows_prereleases=True), [ @@ -101,6 +93,17 @@ def test_search_for( repository.add_package(foo2a) repository.add_package(foo2) repository.add_package(foo3a) + # TODO: remove workaround when poetry-core with + # https://github.com/python-poetry/poetry-core/pull/543 is available + if str(dependency.constraint) == ">=1a": + result = provider.search_for(dependency) + assert result == expected or result == [ + Package("foo", "3a"), + Package("foo", "2"), + Package("foo", "2a"), + Package("foo", "1"), + ] + return assert provider.search_for(dependency) == expected From 68e143363fda1d35d45b79954806dc0684487e17 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 3 Jan 2023 22:36:55 +0200 Subject: [PATCH 042/151] docs: add clarification to `virtualenvs.path` Co-authored-by: Bjorn Neergaard --- docs/configuration.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 719f7f10d95..af537772277 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -356,6 +356,10 @@ Applies on virtualenv creation. Directory where virtual environments will be created. +{{% note %}} +This setting controls the global virtual environment storage path. It most likely will not be useful at the local level. To store virtual environments in the project root, see `virtualenvs.in-project`. +{{% /note %}} + ### `virtualenvs.prefer-active-python` (experimental) **Type**: `boolean` From 47a329b9bc344985e30f840c7445723cdba8f7c3 Mon Sep 17 00:00:00 2001 From: Bjorn Neergaard Date: Tue, 3 Jan 2023 13:54:26 -0700 Subject: [PATCH 043/151] ci: update skip job for 3.11 GA --- .github/workflows/skip.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/skip.yml b/.github/workflows/skip.yml index 40225d5adfd..be69a320bbb 100644 --- a/.github/workflows/skip.yml +++ b/.github/workflows/skip.yml @@ -28,6 +28,6 @@ jobs: strategy: matrix: os: [Ubuntu, macOS, Windows] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] steps: - run: exit 0 From ef89e90fc1305e07a62e9e715c91a66c8d7425f7 Mon Sep 17 00:00:00 2001 From: Bjorn Neergaard Date: Tue, 3 Jan 2023 12:11:19 -0700 Subject: [PATCH 044/151] docs: update for get-poetry.py removal --- docs/_index.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/_index.md b/docs/_index.md index c383fdac017..05e1a9394a4 100644 --- a/docs/_index.md +++ b/docs/_index.md @@ -46,9 +46,13 @@ The script can be executed directly (i.e. 'curl python') or downloaded and then (e.g. in a CI environment). {{% warning %}} -The previous `get-poetry.py` and `install-poetry.py` installers are deprecated. Any installs performed -using `get-poetry.py` should be uninstalled and reinstalled using `install.python-poetry.org` to ensure -in-place upgrades are possible. +The `get-poetry.py` installer has been deprecated and removed. If you installed +Poetry using `get-poetry.py`, please uninstall (as documented in the relevant +step below), and then follow these installation instructions. + +The previous `install-poetry.py` script as included in the Poetry repository +is deprecated and frozen. Please migrate to the standalone version described +above, as the in-tree version is [scheduled to be removed](https://github.com/python-poetry/poetry/issues/6676). {{% /warning %}} **Linux, macOS, Windows (WSL)** @@ -182,11 +186,13 @@ curl -sSL https://install.python-poetry.org | POETRY_UNINSTALL=1 python3 - ``` {{% warning %}} -If you installed using the deprecated `get-poetry.py` script, you should use it to uninstall instead: +If you installed using the deprecated `get-poetry.py` script, you should remove the path it uses manually, e.g. ```bash -curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python3 - --uninstall +rm -rf "${POETRY_HOME:-~/.poetry}" ``` + +Also remove ~/.poetry/bin from your `$PATH` in your shell configuration, if it is present. {{% /warning %}} {{< /step >}} From b69019b7dd43efa82445d7725ebde28daadbbfb9 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Thu, 5 Jan 2023 19:41:41 +0000 Subject: [PATCH 045/151] pkginfo has published types, fix resulting errors includes a change to the format of cached information held about individual packages --- poetry.lock | 16 ++++++++-------- pyproject.toml | 3 +-- src/poetry/inspection/info.py | 7 +------ src/poetry/repositories/cached_repository.py | 2 +- src/poetry/repositories/legacy_repository.py | 1 - src/poetry/repositories/pypi_repository.py | 1 - 6 files changed, 11 insertions(+), 19 deletions(-) diff --git a/poetry.lock b/poetry.lock index c688dab5f3b..7f5a98bdbc7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -909,14 +909,14 @@ ptyprocess = ">=0.5" [[package]] name = "pkginfo" -version = "1.9.2" +version = "1.9.4" description = "Query metadatdata from sdists / bdists / installed packages." category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "pkginfo-1.9.2-py3-none-any.whl", hash = "sha256:d580059503f2f4549ad6e4c106d7437356dbd430e2c7df99ee1efe03d75f691e"}, - {file = "pkginfo-1.9.2.tar.gz", hash = "sha256:ac03e37e4d601aaee40f8087f63fc4a2a6c9814dda2c8fa6aab1b1829653bdfa"}, + {file = "pkginfo-1.9.4-py3-none-any.whl", hash = "sha256:7fc056f8e6b93355925083373b0f6bfbabe84ee3f29854fd155c9d6a4211267d"}, + {file = "pkginfo-1.9.4.tar.gz", hash = "sha256:e769fd353593d43e0c9f47e17e25f09a8efcddcdf9a71674ea3ba444ff31bb44"}, ] [package.extras] @@ -1455,14 +1455,14 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( [[package]] name = "shellingham" -version = "1.5.0" +version = "1.5.0.post1" description = "Tool to Detect Surrounding Shell" category = "main" optional = false -python-versions = ">=3.4" +python-versions = ">=3.7" files = [ - {file = "shellingham-1.5.0-py2.py3-none-any.whl", hash = "sha256:a8f02ba61b69baaa13facdba62908ca8690a94b8119b69f5ec5873ea85f7391b"}, - {file = "shellingham-1.5.0.tar.gz", hash = "sha256:72fb7f5c63103ca2cb91b23dee0c71fe8ad6fbfd46418ef17dbe40db51592dad"}, + {file = "shellingham-1.5.0.post1-py2.py3-none-any.whl", hash = "sha256:368bf8c00754fd4f55afb7bbb86e272df77e4dc76ac29dbcbb81a59e9fc15744"}, + {file = "shellingham-1.5.0.post1.tar.gz", hash = "sha256:823bc5fb5c34d60f285b624e7264f4dda254bc803a3774a147bf99c0e3004a28"}, ] [[package]] @@ -1798,4 +1798,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "c9064502080fd8c4c3195efac3b59202d51d7ca3498a4c92e9e7173fbc46480a" +content-hash = "bbc71952916c7ee1c404e4a85e611189347955d63d5560b5bdb23073a07afde6" diff --git a/pyproject.toml b/pyproject.toml index 1748c65fbf8..88a46ec4ecf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ lockfile = "^0.12.2" # packaging uses calver, so version is unclamped packaging = ">=20.4" pexpect = "^4.7.0" -pkginfo = "^1.5" +pkginfo = "^1.9.4" platformdirs = "^2.5.2" requests = "^2.18" requests-toolbelt = ">=0.9.1,<0.11.0" @@ -174,7 +174,6 @@ module = [ 'cachecontrol.*', 'lockfile.*', 'pexpect.*', - 'pkginfo.*', 'requests_toolbelt.*', 'shellingham.*', 'virtualenv.*', diff --git a/src/poetry/inspection/info.py b/src/poetry/inspection/info.py index 6b6fc2d86ad..fe7ce6864e1 100644 --- a/src/poetry/inspection/info.py +++ b/src/poetry/inspection/info.py @@ -74,7 +74,6 @@ def __init__( name: str | None = None, version: str | None = None, summary: str | None = None, - platform: str | None = None, requires_dist: list[str] | None = None, requires_python: str | None = None, files: list[dict[str, str]] | None = None, @@ -84,7 +83,6 @@ def __init__( self.name = name self.version = version self.summary = summary - self.platform = platform self.requires_dist = requires_dist self.requires_python = requires_python self.files = files or [] @@ -102,7 +100,6 @@ def update(self, other: PackageInfo) -> PackageInfo: self.name = other.name or self.name self.version = other.version or self.version self.summary = other.summary or self.summary - self.platform = other.platform or self.platform self.requires_dist = other.requires_dist or self.requires_dist self.requires_python = other.requires_python or self.requires_python self.files = other.files or self.files @@ -117,7 +114,6 @@ def asdict(self) -> dict[str, Any]: "name": self.name, "version": self.version, "summary": self.summary, - "platform": self.platform, "requires_dist": self.requires_dist, "requires_python": self.requires_python, "files": self.files, @@ -262,7 +258,6 @@ def _from_distribution( name=dist.name, version=dist.version, summary=dist.summary, - platform=dist.supported_platforms, requires_dist=requirements, requires_python=dist.requires_python, ) @@ -423,6 +418,7 @@ def from_metadata(cls, path: Path) -> PackageInfo | None: else: directories = list(cls._find_dist_info(path=path)) + dist: pkginfo.BDist | pkginfo.SDist | pkginfo.Wheel for directory in directories: try: if directory.suffix == ".egg-info": @@ -460,7 +456,6 @@ def from_package(cls, package: Package) -> PackageInfo: name=package.name, version=str(package.version), summary=package.description, - platform=package.platform, requires_dist=list(requires), requires_python=package.python_versions, files=package.files, diff --git a/src/poetry/repositories/cached_repository.py b/src/poetry/repositories/cached_repository.py index b593feb44a2..a04fb4d56f0 100644 --- a/src/poetry/repositories/cached_repository.py +++ b/src/poetry/repositories/cached_repository.py @@ -22,7 +22,7 @@ class CachedRepository(Repository, ABC): - CACHE_VERSION = parse_constraint("1.1.0") + CACHE_VERSION = parse_constraint("2.0.0") def __init__( self, name: str, disable_cache: bool = False, config: Config | None = None diff --git a/src/poetry/repositories/legacy_repository.py b/src/poetry/repositories/legacy_repository.py index 7654d64d921..2c23a9e281d 100644 --- a/src/poetry/repositories/legacy_repository.py +++ b/src/poetry/repositories/legacy_repository.py @@ -123,7 +123,6 @@ def _get_release_info( name=name, version=version.text, summary="", - platform=None, requires_dist=[], requires_python=None, files=[], diff --git a/src/poetry/repositories/pypi_repository.py b/src/poetry/repositories/pypi_repository.py index 14f0b0fc3f8..763e25e94bb 100644 --- a/src/poetry/repositories/pypi_repository.py +++ b/src/poetry/repositories/pypi_repository.py @@ -162,7 +162,6 @@ def _get_release_info( name=info["name"], version=info["version"], summary=info["summary"], - platform=info["platform"], requires_dist=info["requires_dist"], requires_python=info["requires_python"], files=info.get("files", []), From 3fe8838659415f7495d415503ff64eea1aa0bf57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Em=C3=ADdio=20S=2ES?= Date: Sun, 8 Jan 2023 17:26:15 -0300 Subject: [PATCH 046/151] Fix: Missing bash key formatter in code block (#7318) --- docs/_index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_index.md b/docs/_index.md index 05e1a9394a4..ebe15c88b62 100644 --- a/docs/_index.md +++ b/docs/_index.md @@ -239,11 +239,11 @@ poetry@preview --version Finally, `pipx` can install any valid [pip requirement spec](https://pip.pypa.io/en/stable/cli/pip_install/), which allows for installations of the development version from `git`, or even for local testing of pull requests: -``` +```bash pipx install --suffix @master git+https://github.com/python-poetry/poetry.git@master pipx install --suffix @pr1234 git+https://github.com/python-poetry/poetry.git@refs/pull/1234/head - ``` + {{< /step >}} {{< step >}} **Update Poetry** From d8b5181b7d2eb13be4daef4b90a510760b804541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Tue, 10 Jan 2023 17:45:40 +0100 Subject: [PATCH 047/151] chore(release): merge changelog from 1.3 branch --- CHANGELOG.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27e120ce710..a97e31cbe48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,26 @@ # Change Log +## [1.3.2] - 2022-01-10 + +### Fixed + +- Fix a performance regression when locking dependencies from PyPI ([#7232](https://github.com/python-poetry/poetry/pull/7232)). +- Fix an issue where passing a relative path via `-C, --directory` fails ([#7266](https://github.com/python-poetry/poetry/pull/7266)). + +### Docs + +- Update docs to reflect the removal of the deprecated `get-poetry.py` installer from the repository ([#7288](https://github.com/python-poetry/poetry/pull/7288)). +- Add clarifications for `virtualenvs.path` settings ([#7286](https://github.com/python-poetry/poetry/pull/7286)). + + +## [1.3.1] - 2022-12-12 + +### Fixed + +- Fix an issue where an explicit dependency on `lockfile` was missing, resulting in a broken Poetry in rare circumstances ([7169](https://github.com/python-poetry/poetry/pull/7169)). + + ## [1.3.0] - 2022-12-09 ### Added @@ -1681,7 +1701,9 @@ Initial release -[Unreleased]: https://github.com/python-poetry/poetry/compare/1.3.0...master +[Unreleased]: https://github.com/python-poetry/poetry/compare/1.3.2...master +[1.3.2]: https://github.com/python-poetry/poetry/releases/tag/1.3.2 +[1.3.1]: https://github.com/python-poetry/poetry/releases/tag/1.3.1 [1.3.0]: https://github.com/python-poetry/poetry/releases/tag/1.3.0 [1.2.2]: https://github.com/python-poetry/poetry/releases/tag/1.2.2 [1.2.1]: https://github.com/python-poetry/poetry/releases/tag/1.2.1 From df9d3c91bf6ebf95bf951bea320c8723383f9ca1 Mon Sep 17 00:00:00 2001 From: Junpei Kawamoto Date: Wed, 11 Jan 2023 16:35:59 -0500 Subject: [PATCH 048/151] Docs: whitelist_externals was replaced by allowlist_externals (#7341) --- docs/faq.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 75c08cb6473..5280072037b 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -81,7 +81,7 @@ Thus, dependencies are resolved by `pip`. isolated_build = true [testenv] -whitelist_externals = poetry +allowlist_externals = poetry commands_pre = poetry install --no-root --sync commands = @@ -99,7 +99,7 @@ isolated_build = true [testenv] skip_install = true -whitelist_externals = poetry +allowlist_externals = poetry commands_pre = poetry install commands = From a5c3846625540eef567b7f4131a0b2eff3042449 Mon Sep 17 00:00:00 2001 From: Beryl S Date: Sun, 15 Jan 2023 06:11:32 -0600 Subject: [PATCH 049/151] uploader: fix HTTP status code handling for redirects (#7160) --- src/poetry/publishing/uploader.py | 2 +- tests/publishing/test_uploader.py | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/poetry/publishing/uploader.py b/src/poetry/publishing/uploader.py index 7b1e8e19048..1f3a6bd81b9 100644 --- a/src/poetry/publishing/uploader.py +++ b/src/poetry/publishing/uploader.py @@ -266,7 +266,7 @@ def _upload_file( f" - Uploading {file.name} %percent%%" ) bar.finish() - elif resp.status_code == 301: + elif 300 <= resp.status_code < 400: if self._io.output.is_decorated(): self._io.overwrite( f" - Uploading {file.name} FAILED" diff --git a/tests/publishing/test_uploader.py b/tests/publishing/test_uploader.py index be6256052a8..f0721d8f12d 100644 --- a/tests/publishing/test_uploader.py +++ b/tests/publishing/test_uploader.py @@ -65,6 +65,32 @@ def test_uploader_properly_handles_nonstandard_errors( assert str(e.value) == f"HTTP Error 400: Bad Request | {content}" +@pytest.mark.parametrize( + "status, body", + [ + (308, "Permanent Redirect"), + (307, "Temporary Redirect"), + (304, "Not Modified"), + (303, "See Other"), + (302, "Found"), + (301, "Moved Permanently"), + (300, "Multiple Choices"), + ], +) +def test_uploader_properly_handles_redirects( + http: type[httpretty.httpretty], uploader: Uploader, status: int, body: str +): + http.register_uri(http.POST, "https://foo.com", status=status, body=body) + + with pytest.raises(UploadError) as e: + uploader.upload("https://foo.com") + + assert ( + str(e.value) + == "Redirects are not supported. Is the URL missing a trailing slash?" + ) + + def test_uploader_properly_handles_301_redirects( http: type[httpretty.httpretty], uploader: Uploader ): From 4c29f211acbc48cb0110b9e2489c57802c670b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sun, 15 Jan 2023 18:19:34 +0100 Subject: [PATCH 050/151] chore: remove unused duplicated default values for config settings (#7355) --- src/poetry/console/commands/config.py | 79 ++++++--------------------- 1 file changed, 17 insertions(+), 62 deletions(-) diff --git a/src/poetry/console/commands/config.py b/src/poetry/console/commands/config.py index 03773a5ef0d..898312adc77 100644 --- a/src/poetry/console/commands/config.py +++ b/src/poetry/console/commands/config.py @@ -16,7 +16,6 @@ from poetry.config.config import boolean_validator from poetry.config.config import int_normalizer from poetry.console.commands.command import Command -from poetry.locations import DEFAULT_CACHE_DIR if TYPE_CHECKING: @@ -52,74 +51,31 @@ class ConfigCommand(Command): LIST_PROHIBITED_SETTINGS = {"http-basic", "pypi-token"} @property - def unique_config_values(self) -> dict[str, tuple[Any, Any, Any]]: + def unique_config_values(self) -> dict[str, tuple[Any, Any]]: unique_config_values = { - "cache-dir": ( - str, - lambda val: str(Path(val)), - str(DEFAULT_CACHE_DIR / "virtualenvs"), - ), - "virtualenvs.create": (boolean_validator, boolean_normalizer, True), - "virtualenvs.in-project": (boolean_validator, boolean_normalizer, False), - "virtualenvs.options.always-copy": ( - boolean_validator, - boolean_normalizer, - False, - ), + "cache-dir": (str, lambda val: str(Path(val))), + "virtualenvs.create": (boolean_validator, boolean_normalizer), + "virtualenvs.in-project": (boolean_validator, boolean_normalizer), + "virtualenvs.options.always-copy": (boolean_validator, boolean_normalizer), "virtualenvs.options.system-site-packages": ( boolean_validator, boolean_normalizer, - False, - ), - "virtualenvs.options.no-pip": ( - boolean_validator, - boolean_normalizer, - False, ), + "virtualenvs.options.no-pip": (boolean_validator, boolean_normalizer), "virtualenvs.options.no-setuptools": ( boolean_validator, boolean_normalizer, - False, - ), - "virtualenvs.path": ( - str, - lambda val: str(Path(val)), - str(DEFAULT_CACHE_DIR / "virtualenvs"), - ), - "virtualenvs.prefer-active-python": ( - boolean_validator, - boolean_normalizer, - False, - ), - "experimental.new-installer": ( - boolean_validator, - boolean_normalizer, - True, - ), - "experimental.system-git-client": ( - boolean_validator, - boolean_normalizer, - False, - ), - "installer.parallel": ( - boolean_validator, - boolean_normalizer, - True, - ), - "installer.max-workers": ( - lambda val: int(val) > 0, - int_normalizer, - None, - ), - "virtualenvs.prompt": ( - str, - lambda val: str(val), - "{project_name}-py{python_version}", ), + "virtualenvs.path": (str, lambda val: str(Path(val))), + "virtualenvs.prefer-active-python": (boolean_validator, boolean_normalizer), + "experimental.new-installer": (boolean_validator, boolean_normalizer), + "experimental.system-git-client": (boolean_validator, boolean_normalizer), + "installer.parallel": (boolean_validator, boolean_normalizer), + "installer.max-workers": (lambda val: int(val) > 0, int_normalizer), + "virtualenvs.prompt": (str, lambda val: str(val)), "installer.no-binary": ( PackageFilterPolicy.validator, PackageFilterPolicy.normalize, - None, ), } @@ -196,8 +152,7 @@ def handle(self) -> int: values: list[str] = self.argument("value") - unique_config_values = self.unique_config_values - if setting_key in unique_config_values: + if setting_key in self.unique_config_values: if self.option("unset"): config.config_source.remove_property(setting_key) return 0 @@ -205,7 +160,7 @@ def handle(self) -> int: return self._handle_single_value( config.config_source, setting_key, - unique_config_values[setting_key], + self.unique_config_values[setting_key], values, ) @@ -310,10 +265,10 @@ def _handle_single_value( self, source: ConfigSource, key: str, - callbacks: tuple[Any, Any, Any], + callbacks: tuple[Any, Any], values: list[Any], ) -> int: - validator, normalizer, _ = callbacks + validator, normalizer = callbacks if len(values) > 1: raise RuntimeError("You can only pass one value.") From 4e04830882e6a8761418f8481249a47e87c54d71 Mon Sep 17 00:00:00 2001 From: Bartosz Sokorski Date: Sun, 15 Jan 2023 19:12:25 +0100 Subject: [PATCH 051/151] ci: Python 3.11 in Cirrus/FreeBSD --- .cirrus.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.cirrus.yml b/.cirrus.yml index 450fe74a08a..2ce67499911 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -16,6 +16,7 @@ test_task: - PYTHON: python3.8 - PYTHON: python3.9 - PYTHON: python3.10 + - PYTHON: python3.11 install_prereqs_script: - V=$(printf '%s' $PYTHON | tr -d '.[:alpha:]') - pkg install -y python${V} py${V}-sqlite3 From 7714eda24fd2c87345c8d2df0b8e0140a749d953 Mon Sep 17 00:00:00 2001 From: Bartosz Sokorski Date: Mon, 16 Jan 2023 06:43:51 +0100 Subject: [PATCH 052/151] Update workflows (#7312) --- .github/workflows/main.yml | 2 +- .github/workflows/release.yml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 07d6b32a62b..a7d2ee447ba 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -54,7 +54,7 @@ jobs: - name: Bootstrap poetry run: | - curl -sL https://install.python-poetry.org | python - -y + curl -sSL https://install.python-poetry.org | python - -y - name: Update PATH if: ${{ matrix.os != 'Windows' }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a060c22216..0556a26f086 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,8 @@ jobs: python-version: "3.10" - name: Install Poetry - run: python install-poetry.py -y + run: | + curl -sSL https://install.python-poetry.org | python - -y - name: Update PATH run: echo "$HOME/.local/bin" >> $GITHUB_PATH From 2d54ec97b25062d7ee1cab0bc30c53d44edf5226 Mon Sep 17 00:00:00 2001 From: Martin Miglio Date: Wed, 18 Jan 2023 05:59:48 -0500 Subject: [PATCH 053/151] poetry check command accepts --directory argument (#7241) --- src/poetry/console/commands/check.py | 4 +--- tests/console/commands/test_check.py | 15 ++++++++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/poetry/console/commands/check.py b/src/poetry/console/commands/check.py index 30812883523..12bfc09343f 100644 --- a/src/poetry/console/commands/check.py +++ b/src/poetry/console/commands/check.py @@ -1,7 +1,5 @@ from __future__ import annotations -from pathlib import Path - from poetry.console.commands.command import Command @@ -61,7 +59,7 @@ def handle(self) -> int: from poetry.factory import Factory # Load poetry config and display errors, if any - poetry_file = Factory.locate(Path.cwd()) + poetry_file = self.poetry.file.path config = PyProjectTOML(poetry_file).poetry_config check_result = Factory.validate(config, strict=True) diff --git a/tests/console/commands/test_check.py b/tests/console/commands/test_check.py index 1c21a80f3e5..fcdfa5d9a19 100644 --- a/tests/console/commands/test_check.py +++ b/tests/console/commands/test_check.py @@ -29,12 +29,17 @@ def test_check_valid(tester: CommandTester): def test_check_invalid(mocker: MockerFixture, tester: CommandTester): + from poetry.core.toml import TOMLFile + mocker.patch( - "poetry.factory.Factory.locate", - return_value=Path(__file__).parent.parent.parent - / "fixtures" - / "invalid_pyproject" - / "pyproject.toml", + "poetry.poetry.Poetry.file", + return_value=TOMLFile( + Path(__file__).parent.parent.parent + / "fixtures" + / "invalid_pyproject" + / "pyproject.toml" + ), + new_callable=mocker.PropertyMock, ) tester.execute() From 7279a07386d58a0145cc9f3d8793ac9724903662 Mon Sep 17 00:00:00 2001 From: johnthagen Date: Thu, 19 Jan 2023 20:14:03 -0500 Subject: [PATCH 054/151] Support `Private ::` trove classifiers (#7271) --- src/poetry/console/commands/check.py | 4 ++++ tests/console/commands/test_check.py | 18 ++++++++++++++++++ .../fixtures/private_pyproject/pyproject.toml | 17 +++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 tests/fixtures/private_pyproject/pyproject.toml diff --git a/src/poetry/console/commands/check.py b/src/poetry/console/commands/check.py index 12bfc09343f..8a53cd5bc09 100644 --- a/src/poetry/console/commands/check.py +++ b/src/poetry/console/commands/check.py @@ -31,6 +31,10 @@ def validate_classifiers( unrecognized = sorted( project_classifiers - set(classifiers) - set(deprecated_classifiers) ) + # Allow "Private ::" classifiers as recommended on PyPI and the packaging guide + # to allow users to avoid accidentally publishing private packages to PyPI. + # https://pypi.org/classifiers/ + unrecognized = [u for u in unrecognized if not u.startswith("Private ::")] if unrecognized: errors.append(f"Unrecognized classifiers: {unrecognized!r}.") diff --git a/tests/console/commands/test_check.py b/tests/console/commands/test_check.py index fcdfa5d9a19..dbf665b6227 100644 --- a/tests/console/commands/test_check.py +++ b/tests/console/commands/test_check.py @@ -59,3 +59,21 @@ def test_check_invalid(mocker: MockerFixture, tester: CommandTester): """ assert tester.io.fetch_error() == expected + + +def test_check_private(mocker: MockerFixture, tester: CommandTester): + mocker.patch( + "poetry.factory.Factory.locate", + return_value=Path(__file__).parent.parent.parent + / "fixtures" + / "private_pyproject" + / "pyproject.toml", + ) + + tester.execute() + + expected = """\ +All set! +""" + + assert tester.io.fetch_output() == expected diff --git a/tests/fixtures/private_pyproject/pyproject.toml b/tests/fixtures/private_pyproject/pyproject.toml new file mode 100644 index 00000000000..a572e83c8ff --- /dev/null +++ b/tests/fixtures/private_pyproject/pyproject.toml @@ -0,0 +1,17 @@ +[tool.poetry] +name = "private" +version = "0.1.0" +description = "" +authors = ["Your Name "] +readme = "README.md" +classifiers = [ + "Private :: Do Not Upload", +] + + +[tool.poetry.dependencies] +python = "^3.7" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" From 2cab0744059360c32904ab54a7661ef85c93e92e Mon Sep 17 00:00:00 2001 From: Yeger Date: Fri, 20 Jan 2023 19:04:31 +0200 Subject: [PATCH 055/151] Order root extra dependencies (#7375) --- src/poetry/packages/locker.py | 2 +- tests/packages/test_locker.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/poetry/packages/locker.py b/src/poetry/packages/locker.py index 7fd19669457..7fa0e6ffce9 100644 --- a/src/poetry/packages/locker.py +++ b/src/poetry/packages/locker.py @@ -244,7 +244,7 @@ def set_lock_data(self, root: Package, packages: list[Package]) -> bool: if root.extras: lock["extras"] = { - extra: [dep.pretty_name for dep in deps] + extra: sorted(dep.pretty_name for dep in deps) for extra, deps in sorted(root.extras.items()) } diff --git a/tests/packages/test_locker.py b/tests/packages/test_locker.py index 268658a3ff1..23022d41c66 100644 --- a/tests/packages/test_locker.py +++ b/tests/packages/test_locker.py @@ -737,6 +737,41 @@ def test_locker_should_raise_an_error_if_lock_version_is_newer_and_not_allowed( _ = locker.lock_data +def test_root_extras_dependencies_are_ordered(locker: Locker, root: ProjectPackage): + root_dir = Path(__file__).parent.parent.joinpath("fixtures") + Factory.create_dependency("B", "1.0.0", root_dir=root_dir) + Factory.create_dependency("C", "1.0.0", root_dir=root_dir) + package_first = Factory.create_dependency("first", "1.0.0", root_dir=root_dir) + package_second = Factory.create_dependency("second", "1.0.0", root_dir=root_dir) + package_third = Factory.create_dependency("third", "1.0.0", root_dir=root_dir) + + root.extras = { + "C": [package_third, package_second, package_first], + "B": [package_first, package_second, package_third], + } + locker.set_lock_data(root, []) + + expected = f"""\ +# {GENERATED_COMMENT} +package = [] + +[extras] +B = ["first", "second", "third"] +C = ["first", "second", "third"] + +[metadata] +lock-version = "2.0" +python-versions = "*" +content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" +""" # noqa: E800 + + with locker.lock.open(encoding="utf-8") as f: + content = f.read() + + print(content) + assert content == expected + + def test_extras_dependencies_are_ordered(locker: Locker, root: ProjectPackage): package_a = get_package("A", "1.0.0") package_a.add_dependency( From 66d36520e984509d4adddf6446529b60924d089f Mon Sep 17 00:00:00 2001 From: June Oh Date: Sat, 21 Jan 2023 02:20:57 +0900 Subject: [PATCH 056/151] Fix neglected subdirectory in dependency lock (#7367) --- src/poetry/packages/locker.py | 4 ++++ tests/packages/test_locker.py | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/poetry/packages/locker.py b/src/poetry/packages/locker.py index 7fa0e6ffce9..57765ffa440 100644 --- a/src/poetry/packages/locker.py +++ b/src/poetry/packages/locker.py @@ -378,6 +378,10 @@ def _dump_package(self, package: Package) -> dict[str, Any]: constraint["tag"] = dependency.tag elif dependency.rev: constraint["rev"] = dependency.rev + + if dependency.directory: + constraint["subdirectory"] = dependency.directory + else: constraint["version"] = str(dependency.pretty_constraint) diff --git a/tests/packages/test_locker.py b/tests/packages/test_locker.py index 23022d41c66..1595b40e6ce 100644 --- a/tests/packages/test_locker.py +++ b/tests/packages/test_locker.py @@ -865,6 +865,15 @@ def test_locker_dumps_dependency_information_correctly( "F", {"git": "https://github.com/python-poetry/poetry.git", "branch": "foo"} ) ) + package_a.add_dependency( + Factory.create_dependency( + "G", + { + "git": "https://github.com/python-poetry/poetry.git", + "subdirectory": "bar", + }, + ) + ) packages = [package_a] @@ -891,6 +900,7 @@ def test_locker_dumps_dependency_information_correctly( D = {{path = "distributions/demo-0.1.0.tar.gz"}} E = {{url = "https://python-poetry.org/poetry-1.2.0.tar.gz"}} F = {{git = "https://github.com/python-poetry/poetry.git", branch = "foo"}} +G = {{git = "https://github.com/python-poetry/poetry.git", subdirectory = "bar"}} [metadata] lock-version = "2.0" From ccd09a294c4a43a10cbfd2694d599646d7a42280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sat, 21 Jan 2023 10:56:57 +0100 Subject: [PATCH 057/151] chore(tests): avoid unnecessary parsing --- tests/packages/test_locker.py | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/tests/packages/test_locker.py b/tests/packages/test_locker.py index 1595b40e6ce..24728c5d9ed 100644 --- a/tests/packages/test_locker.py +++ b/tests/packages/test_locker.py @@ -12,7 +12,6 @@ from typing import TYPE_CHECKING import pytest -import tomlkit from poetry.core.constraints.version import Version from poetry.core.packages.package import Package @@ -231,9 +230,8 @@ def test_locker_properly_loads_extras(locker: Locker): content-hash = "c3d07fca33fba542ef2b2a4d75bf5b48d892d21a830e2ad9c952ba5123a52f77" """ # noqa: E800 - data = tomlkit.parse(content) with open(locker.lock, "w", encoding="utf-8") as f: - f.write(data.as_string()) + f.write(content) packages = locker.locked_repository().packages @@ -296,9 +294,8 @@ def test_locker_properly_loads_nested_extras(locker: Locker): content-hash = "123456789" """ # noqa: E800 - data = tomlkit.parse(content) with open(locker.lock, "w", encoding="utf-8") as f: - f.write(data.as_string()) + f.write(content) repository = locker.locked_repository() assert len(repository.packages) == 3 @@ -363,9 +360,8 @@ def test_locker_properly_loads_extras_legacy(locker: Locker): content-hash = "123456789" """ # noqa: E800 - data = tomlkit.parse(content) with open(locker.lock, "w", encoding="utf-8") as f: - f.write(data.as_string()) + f.write(content) repository = locker.locked_repository() assert len(repository.packages) == 2 @@ -405,9 +401,8 @@ def test_locker_properly_loads_subdir(locker: Locker) -> None: python-versions = "*" content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" """ - data = tomlkit.parse(content) with open(locker.lock, "w", encoding="utf-8") as f: - f.write(data.as_string()) + f.write(content) repository = locker.locked_repository() assert len(repository.packages) == 1 @@ -503,9 +498,8 @@ def test_locker_properly_assigns_metadata_files(locker: Locker) -> None: {file = "demo-1.0-py3-none-any.whl", hash = "sha256"}, ] """ - data = tomlkit.parse(content) with open(locker.lock, "w", encoding="utf-8") as f: - f.write(data.as_string()) + f.write(content) repository = locker.locked_repository() assert len(repository.packages) == 5 @@ -697,9 +691,8 @@ def test_locker_should_emit_warnings_if_lock_version_is_newer_but_allowed( """ caplog.set_level(logging.WARNING, logger="poetry.packages.locker") - data = tomlkit.parse(content) with open(locker.lock, "w", encoding="utf-8") as f: - f.write(data.as_string()) + f.write(content) _ = locker.lock_data @@ -729,9 +722,8 @@ def test_locker_should_raise_an_error_if_lock_version_is_newer_and_not_allowed( """ # noqa: E800 caplog.set_level(logging.WARNING, logger="poetry.packages.locker") - data = tomlkit.parse(content) with open(locker.lock, "w", encoding="utf-8") as f: - f.write(data.as_string()) + f.write(content) with pytest.raises(RuntimeError, match="^The lock file is not compatible"): _ = locker.lock_data @@ -824,9 +816,8 @@ def test_locker_should_neither_emit_warnings_nor_raise_error_for_lower_compatibl """ caplog.set_level(logging.WARNING, logger="poetry.packages.locker") - data = tomlkit.parse(content) with open(locker.lock, "w", encoding="utf-8") as f: - f.write(data.as_string()) + f.write(content) _ = locker.lock_data @@ -1031,9 +1022,8 @@ def test_locked_repository_uses_root_dir_of_package( content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" """ # noqa: E800 - data = tomlkit.parse(content) with open(locker.lock, "w", encoding="utf-8") as f: - f.write(data.as_string()) + f.write(content) create_dependency_patch = mocker.patch( "poetry.factory.Factory.create_dependency", autospec=True From f99198c3e824b6e474a9cc1240100273a16e84f6 Mon Sep 17 00:00:00 2001 From: Pascal F Date: Wed, 11 Jan 2023 16:46:08 +0100 Subject: [PATCH 058/151] Add poetry version used to generate lock file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Randy Döring <30527984+radoering@users.noreply.github.com> --- src/poetry/packages/locker.py | 5 +++-- tests/packages/test_locker.py | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/poetry/packages/locker.py b/src/poetry/packages/locker.py index 57765ffa440..ab7454d4fa9 100644 --- a/src/poetry/packages/locker.py +++ b/src/poetry/packages/locker.py @@ -25,6 +25,7 @@ from tomlkit import inline_table from tomlkit import table +from poetry.__version__ import __version__ from poetry.utils._compat import tomllib @@ -40,8 +41,8 @@ logger = logging.getLogger(__name__) _GENERATED_IDENTIFIER = "@" + "generated" GENERATED_COMMENT = ( - f"This file is automatically {_GENERATED_IDENTIFIER} by Poetry and should not be" - " changed by hand." + f"This file is automatically {_GENERATED_IDENTIFIER} by Poetry" + f" {__version__} and should not be changed by hand." ) diff --git a/tests/packages/test_locker.py b/tests/packages/test_locker.py index 24728c5d9ed..a86b5d770ea 100644 --- a/tests/packages/test_locker.py +++ b/tests/packages/test_locker.py @@ -17,6 +17,7 @@ from poetry.core.packages.package import Package from poetry.core.packages.project_package import ProjectPackage +from poetry.__version__ import __version__ from poetry.factory import Factory from poetry.packages.locker import GENERATED_COMMENT from poetry.packages.locker import Locker @@ -1169,3 +1170,29 @@ def test_lock_file_resolves_file_url_symlinks(root: ProjectPackage): """ # noqa: E800 assert content == expected + + +def test_lockfile_is_not_rewritten_if_only_poetry_version_changed( + locker: Locker, root: ProjectPackage +) -> None: + generated_comment_old_version = GENERATED_COMMENT.replace(__version__, "1.3.2") + assert generated_comment_old_version != GENERATED_COMMENT + old_content = f"""\ +# {generated_comment_old_version} +package = [] + +[metadata] +lock-version = "2.0" +python-versions = "*" +content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" +""" # noqa: E800 + + with open(locker.lock, "w", encoding="utf-8") as f: + f.write(old_content) + + assert not locker.set_lock_data(root, []) + + with locker.lock.open(encoding="utf-8") as f: + content = f.read() + + assert content == old_content From fa29e769467068d78dbaa5abc2c6889487999234 Mon Sep 17 00:00:00 2001 From: Makarov Andrey Date: Sat, 21 Jan 2023 16:43:42 +0300 Subject: [PATCH 059/151] [docs] export command supports constraints.txt format (#7383) --- docs/cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli.md b/docs/cli.md index 9a71e0e1b92..a4c682a757d 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -691,7 +691,7 @@ group defined in `tool.poetry.dependencies` when used without specifying any opt ### Options * `--format (-f)`: The format to export to (default: `requirements.txt`). - Currently, only `requirements.txt` is supported. + Currently, only `constraints.txt` and `requirements.txt` are supported. * `--output (-o)`: The name of the output file. If omitted, print to standard output. * `--dev`: Include development dependencies. (**Deprecated**, use `--with dev` instead) From be8cd1fbed82ce4fd8f911063276068b2be92fd2 Mon Sep 17 00:00:00 2001 From: finswimmer Date: Mon, 19 Dec 2022 13:00:37 +0100 Subject: [PATCH 060/151] fix(env): get full python path for appropriate executable --- src/poetry/utils/env.py | 2 +- tests/utils/test_env.py | 26 ++++++++++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 614ca092f21..6c4ccef0a6c 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -995,7 +995,7 @@ def create_venv( self._io.write_error_line( f"Using {python} ({python_patch})" ) - executable = python + executable = self._full_python_path(python) python_minor = ".".join(python_patch.split(".")[:2]) break diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 8df2abb175f..1b65c8a052e 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -18,6 +18,7 @@ from poetry.repositories.installed_repository import InstalledRepository from poetry.utils._compat import WINDOWS from poetry.utils.env import GET_BASE_PREFIX +from poetry.utils.env import GET_PYTHON_VERSION_ONELINER from poetry.utils.env import EnvCommandError from poetry.utils.env import EnvManager from poetry.utils.env import GenericEnv @@ -1002,7 +1003,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py3.7", - executable="python3", + executable="/usr/bin/python3", flags={ "always-copy": False, "system-site-packages": False, @@ -1027,7 +1028,9 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific poetry.package.python_versions = "^3.6" mocker.patch("sys.version_info", (2, 7, 16)) - mocker.patch("subprocess.check_output", side_effect=["3.5.3", "3.9.0"]) + mocker.patch( + "subprocess.check_output", side_effect=["3.5.3", "3.9.0", "/usr/bin/python3.9"] + ) m = mocker.patch( "poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: "" ) @@ -1036,7 +1039,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py3.9", - executable="python3.9", + executable="/usr/bin/python3.9", flags={ "always-copy": False, "system-site-packages": False, @@ -1459,11 +1462,18 @@ def test_create_venv_accepts_fallback_version_w_nonzero_patchlevel( poetry.package.python_versions = "~3.5.1" + def mock_check_output(cmd: str, *args: Any, **kwargs: Any) -> str: + if GET_PYTHON_VERSION_ONELINER in cmd: + if "python3.5" in cmd: + return "3.5.12" + else: + return "3.7.1" + else: + return "/usr/bin/python3.5" + check_output = mocker.patch( "subprocess.check_output", - side_effect=lambda cmd, *args, **kwargs: str( - "3.5.12" if "python3.5" in cmd else "3.7.1" - ), + side_effect=mock_check_output, ) m = mocker.patch( "poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: "" @@ -1474,7 +1484,7 @@ def test_create_venv_accepts_fallback_version_w_nonzero_patchlevel( assert check_output.called m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py3.5", - executable="python3.5", + executable="/usr/bin/python3.5", flags={ "always-copy": False, "system-site-packages": False, @@ -1582,7 +1592,7 @@ def test_create_venv_project_name_empty_sets_correct_prompt( m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py3.7", - executable="python3", + executable="/usr/bin/python3", flags={ "always-copy": False, "system-site-packages": False, From ed5441f5659bd66b56ce475504be98031907621c Mon Sep 17 00:00:00 2001 From: finswimmer Date: Mon, 19 Dec 2022 13:29:52 +0100 Subject: [PATCH 061/151] fix(env): catch EnvCommandError instead of CalledProcessError when detecting active python --- src/poetry/utils/env.py | 2 +- tests/utils/test_env.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 6c4ccef0a6c..408d0c0f232 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -551,7 +551,7 @@ def _detect_active_python(self) -> str | None: self._io.write_error_line( f"Found: {executable}", verbosity=Verbosity.VERBOSE ) - except CalledProcessError: + except EnvCommandError: self._io.write_error_line( ( "Unable to detect the current active python executable. Falling" diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 1b65c8a052e..23cbbda3e7f 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -1601,3 +1601,15 @@ def test_create_venv_project_name_empty_sets_correct_prompt( }, prompt="virtualenv-py3.7", ) + + +def test_fallback_on_detect_active_python(poetry: Poetry, mocker: MockerFixture): + m = mocker.patch( + "subprocess.check_output", + side_effect=subprocess.CalledProcessError(1, "some command"), + ) + env_manager = EnvManager(poetry) + active_python = env_manager._detect_active_python() + + assert active_python is None + assert m.call_count == 1 From 33b8b5a480de739e29938503209e404e0fa2a2a8 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 22 Jan 2023 14:05:10 +0000 Subject: [PATCH 062/151] don't set package pretty_version (#7305) --- src/poetry/packages/locker.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/poetry/packages/locker.py b/src/poetry/packages/locker.py index ab7454d4fa9..2f4a2dcfda4 100644 --- a/src/poetry/packages/locker.py +++ b/src/poetry/packages/locker.py @@ -119,7 +119,6 @@ def locked_repository(self) -> LockfileRepository: package = Package( name, info["version"], - info["version"], source_type=source_type, source_url=url, source_reference=source.get("reference"), From bd85ae6db4a2b7cd6fe778870c422f93006b6551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jelmer=20Vernoo=C4=B3?= Date: Sun, 22 Jan 2023 17:11:57 +0000 Subject: [PATCH 063/151] Bump dulwich to 0.21. --- poetry.lock | 126 +++++++++++++++++++++++-------------------------- pyproject.toml | 2 +- 2 files changed, 61 insertions(+), 67 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7f5a98bdbc7..ec0d8b80035 100644 --- a/poetry.lock +++ b/poetry.lock @@ -369,78 +369,72 @@ files = [ [[package]] name = "dulwich" -version = "0.20.50" +version = "0.21.2" description = "Python Git Library" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "dulwich-0.20.50-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:97f02f8d500d4af08dc022d697c56e8539171acc3f575c2fe9acf3b078e5c8c9"}, - {file = "dulwich-0.20.50-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7301773e5cc16d521bc6490e73772a86a4d1d0263de506f08b54678cc4e2f061"}, - {file = "dulwich-0.20.50-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b70106580ed11f45f4c32d2831d0c9c9f359bc2415fff4a6be443e3a36811398"}, - {file = "dulwich-0.20.50-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f9c4f2455f966cad94648278fa9972e4695b35d04f82792fa58e1ea15dd83f0"}, - {file = "dulwich-0.20.50-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9163fbb021a8ad9c35a0814a5eedf45a8eb3a0b764b865d7016d901fc5a947fc"}, - {file = "dulwich-0.20.50-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:322ff8ff6aa4d6d36294cd36de1c84767eb1903c7db3e7b4475ad091febf5363"}, - {file = "dulwich-0.20.50-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5d3290a45651c8e534f8e83ae2e30322aefdd162f0f338bae2e79a6ee5a87513"}, - {file = "dulwich-0.20.50-cp310-cp310-win32.whl", hash = "sha256:80ab07131a6e68594441f5c4767e9e44e87fceafc3e347e541c928a18c679bd8"}, - {file = "dulwich-0.20.50-cp310-cp310-win_amd64.whl", hash = "sha256:eefe786a6010f8546baac4912113eeed4e397ddb8c433a345b548a04d4176496"}, - {file = "dulwich-0.20.50-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:df3562dde3079d57287c233d45b790bc967c5aae975c9a7b07ca30e60e055512"}, - {file = "dulwich-0.20.50-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e1ae18d5805f0c0c5dac65795f8d48660437166b12ee2c0ffea95bfdbf9c1051"}, - {file = "dulwich-0.20.50-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d2f7df39bd1378d3b0bfb3e7fc930fd0191924af1f0ef587bcd9946afe076c06"}, - {file = "dulwich-0.20.50-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:731e7f319b34251fadeb362ada1d52cc932369d9cdfa25c0e41150cda28773d0"}, - {file = "dulwich-0.20.50-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4d11d44176e5d2fa8271fc86ad1e0a8731b9ad8f77df64c12846b30e16135eb"}, - {file = "dulwich-0.20.50-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7aaabb8e4beadd53f75f853a981caaadef3ef130e5645c902705704eaf136daa"}, - {file = "dulwich-0.20.50-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c3dc9f97ec8d3db08d9723b9fd06f3e52c15b84c800d153cfb59b0a3dc8b8d40"}, - {file = "dulwich-0.20.50-cp311-cp311-win32.whl", hash = "sha256:3b1964fa80cafd5a1fd71615b0313daf6f3295c6ab05656ea0c1d2423539904a"}, - {file = "dulwich-0.20.50-cp311-cp311-win_amd64.whl", hash = "sha256:a24a3893108f3b97beb958670d5f3f2a3bec73a1fe18637a572a85abd949a1c4"}, - {file = "dulwich-0.20.50-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6d409a282f8848fd6c8d7c7545ad2f75c16de5d5977de202642f1d50fdaac554"}, - {file = "dulwich-0.20.50-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5411d0f1092152e1c0bb916ae490fe181953ae1b8d13f4e68661253e10b78dbb"}, - {file = "dulwich-0.20.50-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6343569f998ce429e2a5d813c56768ac51b496522401db950f0aa44240bfa901"}, - {file = "dulwich-0.20.50-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a405cd236766060894411614a272cfb86fe86cde5ca73ef264fc4fa5a715fff4"}, - {file = "dulwich-0.20.50-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ee0f9b02019c0ea84cdd31c00a0c283669b771c85612997a911715cf84e33d99"}, - {file = "dulwich-0.20.50-cp36-cp36m-win32.whl", hash = "sha256:2644466270267270f2157ea6f1c0aa224f6f3bf06a307fc39954e6b4b3d82bae"}, - {file = "dulwich-0.20.50-cp36-cp36m-win_amd64.whl", hash = "sha256:d4629635a97e3af1b5da48071e00c8e70fad85f3266fadabe1f5a8f49172c507"}, - {file = "dulwich-0.20.50-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0e4862f318d99cc8a500e3622a89613a88c07d957a0f628cdc2ed86addff790f"}, - {file = "dulwich-0.20.50-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c96e3fb9d48c0454dc242c7accc7819780c9a7f29e441a9eff12361ed0fa35f9"}, - {file = "dulwich-0.20.50-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cc6092a4f0bbbff2e553e87a9c6325955b64ea43fca21297c8182e19ae8a43c"}, - {file = "dulwich-0.20.50-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:519b627d49d273e2fd01c79d09e578675ca6cd05193c1787e9ef165c9a1d66ea"}, - {file = "dulwich-0.20.50-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a75cab01b909c4c683c2083e060e378bc01701b7366b5a7d9846ef6d3b9e3d5"}, - {file = "dulwich-0.20.50-cp37-cp37m-win32.whl", hash = "sha256:ea8ffe26d91dbcd5580dbd5a07270a12ea57b091604d77184da0a0d9fad50ed3"}, - {file = "dulwich-0.20.50-cp37-cp37m-win_amd64.whl", hash = "sha256:8f3af857f94021cae1322d86925bfc0dd31e501e885ab5db275473bfac0bb39d"}, - {file = "dulwich-0.20.50-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3fb35cedb1243bc420d885ef5b4afd642c6ac8f07ddfc7fdbca1becf9948bf7e"}, - {file = "dulwich-0.20.50-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4bb23a9cec63e16c0e432335f068169b73dd44fa9318dd7cd7a4ca83607ff367"}, - {file = "dulwich-0.20.50-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5267619b34ddaf8d9a6b841492cd17a971fd25bf9a5657f2de928385c3a08b94"}, - {file = "dulwich-0.20.50-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9091f1d53a3c0747cbf0bd127c64e7f09b770264d8fb53e284383fcdf69154e7"}, - {file = "dulwich-0.20.50-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6ec7c8fea2b44187a3b545e6c11ab9947ffb122647b07abcdb7cc3aaa770c0e"}, - {file = "dulwich-0.20.50-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:11b180b80363b4fc70664197028181a17ae4c52df9965a29b62a6c52e40c2dbe"}, - {file = "dulwich-0.20.50-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c83e7840d9d0a94d7033bc109efe0c22dfcdcd816bcd4469085e42809e3bf5ba"}, - {file = "dulwich-0.20.50-cp38-cp38-win32.whl", hash = "sha256:c075f69c2de19d9fd97e3b70832d2b42c6a4a5d909b3ffd1963b67d86029f95f"}, - {file = "dulwich-0.20.50-cp38-cp38-win_amd64.whl", hash = "sha256:06775c5713cfeda778c7c67d4422b5e7554d3a7f644f1dde646cdf486a30285a"}, - {file = "dulwich-0.20.50-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:49f66f1c057c18d7d60363f461f4ab8329320fbe1f02a7a33c255864a7d3c942"}, - {file = "dulwich-0.20.50-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4e541cd690a5e3d55082ed51732d755917e933cddeb4b0204f2a5ec5d5d7b60b"}, - {file = "dulwich-0.20.50-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:80e8750ee2fa0ab2784a095956077758e5f6107de27f637c4b9d18406652c22c"}, - {file = "dulwich-0.20.50-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fbb6368f18451dc44c95c55e1a609d1a01d3821f7ed480b22b2aea1baca0f4a7"}, - {file = "dulwich-0.20.50-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3ee45001411b638641819b7b3b33f31f13467c84066e432256580fcab7d8815"}, - {file = "dulwich-0.20.50-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4842e22ed863a776b36ef8ffe9ed7b772eb452b42c8d02975c29d27e3bc50ab4"}, - {file = "dulwich-0.20.50-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:790e4a641284a7fb4d56ebdaf8b324a5826fbbb9c54307c06f586f9f6a5e56db"}, - {file = "dulwich-0.20.50-cp39-cp39-win32.whl", hash = "sha256:f08406b6b789dea5c95ba1130a0801d8748a67f18be940fe7486a8b481fde875"}, - {file = "dulwich-0.20.50-cp39-cp39-win_amd64.whl", hash = "sha256:78c388ad421199000fb7b5ed5f0c7b509b3e31bd7cad303786a4d0bf89b82f60"}, - {file = "dulwich-0.20.50-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cb194c53109131bcbcd1ca430fcd437cdaf2d33e204e45fbe121c47eaa43e9af"}, - {file = "dulwich-0.20.50-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7542a72c5640dd0620862d6df8688f02a6c336359b5af9b3fcfe11b7fa6652f"}, - {file = "dulwich-0.20.50-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4aa1d0861517ebbbe0e0084cc9ab4f7ab720624a3eda2bd10e45f774ab858db8"}, - {file = "dulwich-0.20.50-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:583c6bbc27f13fe2e41a19f6987a42681c6e4f6959beae0a6e5bb033b8b081a8"}, - {file = "dulwich-0.20.50-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0c61c193d02c0e1e0d758cdd57ae76685c368d09a01f00d704ba88bd96767cfe"}, - {file = "dulwich-0.20.50-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2edbff3053251985f10702adfafbee118298d383ef5b5b432a5f22d1f1915df"}, - {file = "dulwich-0.20.50-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a344230cadfc5d315752add6ce9d4cfcfc6c85e36bbf57fce9444bcc7c6ea8fb"}, - {file = "dulwich-0.20.50-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:57bff9bde0b6b05b00c6acbb1a94357caddb2908ed7026a48c715ff50d220335"}, - {file = "dulwich-0.20.50-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e29a3c2037761fa816aa556e78364dfc8e3f44b873db2d17aed96f9b06ac83a3"}, - {file = "dulwich-0.20.50-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2aa2a4a84029625bf9c63771f8a628db1f3be2d2ea3cb8b17942cd4317797152"}, - {file = "dulwich-0.20.50-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd9fa00971ecf059bb358085a942ecac5be4ff71acdf299f44c8cbc45c18659f"}, - {file = "dulwich-0.20.50-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:af4adac92fb95671ea3a24f2f8e5e5e8f638711ce9c33a3ca6cd68bf1ff7d99f"}, - {file = "dulwich-0.20.50.tar.gz", hash = "sha256:50a941796b2c675be39be728d540c16b5b7ce77eb9e1b3f855650ece6832d2be"}, + {file = "dulwich-0.21.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:42c459742bb1802a7686b90f15b7e6f0475ab2a6a8b60b1b01fe8309d6182962"}, + {file = "dulwich-0.21.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:60db3151c7b21d4d30cb39f92a324f6b4296c4226077a9845a297b28062566ac"}, + {file = "dulwich-0.21.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7eacdee555ad5b24774e633b9976b0fd3721f87c4583c0d5644f66427a005276"}, + {file = "dulwich-0.21.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78bf7d82e0fb65a89b6f1cfe88bb46664056f852bed945d07b47b1d56fc76334"}, + {file = "dulwich-0.21.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578e1adec5105643a9c77875e6e6f02627da58a549fab4730584c7bacf0555f9"}, + {file = "dulwich-0.21.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2ea866332f6ed48738e62d2ba57fbad0f0c172191730e1ca61a460ae6603ff3d"}, + {file = "dulwich-0.21.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aaf539fb5d0dcdeab11b0f09f63e6e6851023b1fcdd92d66c51e3c1c45f60843"}, + {file = "dulwich-0.21.2-cp310-cp310-win32.whl", hash = "sha256:86203171f36d5524baa2d04c8b491fd83e2e840b1b3c587535604309b1022ffd"}, + {file = "dulwich-0.21.2-cp310-cp310-win_amd64.whl", hash = "sha256:d6dac3a7453a2de1c1f910794cf7147571771998d2325fea21abd3878a7e91ae"}, + {file = "dulwich-0.21.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09c90e5b4fe9adb0fc2989b67f7eee37a4ad205e2e32550cec7d0af388ffe7ab"}, + {file = "dulwich-0.21.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:37ea1dd93df7e8d8202be4a114633db832b2839793e3a43ab57adf5f5d9c1715"}, + {file = "dulwich-0.21.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b94140249d09976aa60dda88964695903b03956a1b3b37d0ced7f0ca27c249ce"}, + {file = "dulwich-0.21.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d4f7c53fe5910b68cc750b1ffff009fde808df5c3ae2906d2102d1d4290bbd6"}, + {file = "dulwich-0.21.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0ea27192f2c07a84765b06202dbb467ae0da6c93e7824e4e5022ca214e01f7c"}, + {file = "dulwich-0.21.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:acfc3650b8f67306afbc15dae22e9507fab8a0bdf5986c711c047a134a127321"}, + {file = "dulwich-0.21.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0448f4ae5753c08c4ab5e090a89f4d30974847bd41e352854b9a30c0dca00971"}, + {file = "dulwich-0.21.2-cp311-cp311-win32.whl", hash = "sha256:9eae7e44086b2e3bab3f74bfb28d69a6ae52d2fdc83e2d64191485063baf5102"}, + {file = "dulwich-0.21.2-cp311-cp311-win_amd64.whl", hash = "sha256:f3d364192572ade997e40dcc618fedb29176f89685ba07fae2a23d18811a848f"}, + {file = "dulwich-0.21.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2ae925e9501f26e7e925fd9141f606be1cdff164c2a6a0d93e2c68980ce00f9a"}, + {file = "dulwich-0.21.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9752c4eb4625209f61f8315b089709fb26dc4a593dade714e14d4c275d3658a5"}, + {file = "dulwich-0.21.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6380ffbb6048eb7bef3ed24ac684fb515061a3e881df12e8f8542854829215"}, + {file = "dulwich-0.21.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e3291ecffdeb4c1ae5453d698ee9d5ec9149e2c31c69e2056214e7a150422921"}, + {file = "dulwich-0.21.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1b26f54a735543468609d8120271cb0353d56bbbcb594daef38a880feb86a5d6"}, + {file = "dulwich-0.21.2-cp37-cp37m-win32.whl", hash = "sha256:bc8429e417ff5bce7f622beaa8dbdd660093b80a8d6ccb0040149cdf38c61c45"}, + {file = "dulwich-0.21.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7808c558589bea127e991a2a82a8ce3a317bed84d509ed54d46ee5d6324957c3"}, + {file = "dulwich-0.21.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:34e59d348461c81553ca520c3f95c85d62cf6baaa297abe5e80cea0d404bab82"}, + {file = "dulwich-0.21.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fddeed845ad99743cce8b43683b12fe2e164e920baa0c3baff6094b7d6a1831b"}, + {file = "dulwich-0.21.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4c0deea0a9447539db8e41ada9813353b1f572d2a56a7c2eb73178759eefc06"}, + {file = "dulwich-0.21.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e04411a8f2f6ad8dd3b18cc464e3cf996813e832b910d0b41c2e8f0aa4b2860"}, + {file = "dulwich-0.21.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8121fac495a79b946dfb2612ef48c644d5dd9c55278cc023f1337aa4c1dddbe9"}, + {file = "dulwich-0.21.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:06b5ea5a31372446375bb30304d8d3ce90795200f148df470faf41fa66061dc6"}, + {file = "dulwich-0.21.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a68393f843518818fd98e34191820ae92e5c09a0ae2c9cac6f474d69b3a243ab"}, + {file = "dulwich-0.21.2-cp38-cp38-win32.whl", hash = "sha256:48d047e8945348830e576170ef9e5f6f1dbf81567331db54caa3c4d67c93770b"}, + {file = "dulwich-0.21.2-cp38-cp38-win_amd64.whl", hash = "sha256:3023933c1bf35e149f74d94e42944fce9903c8daea6560cd0657da75f5653e80"}, + {file = "dulwich-0.21.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a911565cec4f38689d4888298d5cdd3c41151f15e8c2ca0238432a1194d55741"}, + {file = "dulwich-0.21.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c80b558cc5d9fbd0bc8609d5100b6b57d7a2f164ad1a2caf158179629fc0ef40"}, + {file = "dulwich-0.21.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:adcc225256f63e44dc8d81f66bd0bfdf1d1b161ed399a0923a434937adf64e54"}, + {file = "dulwich-0.21.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b96f96c02490181297fc5431d71679d448bf2b3c5aee579e546d1779a36567d"}, + {file = "dulwich-0.21.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb8a812d32400b12a0b70d603edd2b2fce4f5b0867062e2089f81db2e770c6e7"}, + {file = "dulwich-0.21.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:82873d5248dc9bfe2aae4a4bf8518633c4e12144cf6ecba56f2996abc8e4c53e"}, + {file = "dulwich-0.21.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e35981b025483f56a615a1c6440a235c675ec65cea8575b2260b636136197fa0"}, + {file = "dulwich-0.21.2-cp39-cp39-win32.whl", hash = "sha256:72ea83a8e6bb5e9f0f86bae262e2ff867d15073895030ac042a652cf64e52d44"}, + {file = "dulwich-0.21.2-cp39-cp39-win_amd64.whl", hash = "sha256:b13fe5509bc4a365788999ea1915ac2ab59617301f7c6bbb06b25cd5f0725f60"}, + {file = "dulwich-0.21.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2efa033a4477cc1f6f0056b39a970eb9ac19a10f1a51683a590a1068c5c3c084"}, + {file = "dulwich-0.21.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6fe1563d1efd3d6c4fcfda210d313d449e50ba5371500dc68a0fd3afb0a6ec3"}, + {file = "dulwich-0.21.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2a774c06d10700e66610bc021050bee6b81ebce141b2e695b5448e2e70466df"}, + {file = "dulwich-0.21.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ae82a1f0c9abe9a3719ebc611cbc2a60fcb9b02a567f750280ff5f783e21abfb"}, + {file = "dulwich-0.21.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:86e83c9b713323d47796b3b95ddf95f491ffaa07050ed6145ac2f3249b67bd06"}, + {file = "dulwich-0.21.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f24a7de7e5ec0889f674a4975b61d5e5a455313cb6d32bddb0981f42c0edd789"}, + {file = "dulwich-0.21.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc265def2b50a10bfdd9539a5d5d6a313e45493c1e20303f4c18ba045b1d555b"}, + {file = "dulwich-0.21.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7097a5a6e06d9339c52b636ea90f93b498441ab962075bc21279396c25e1ff7b"}, + {file = "dulwich-0.21.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c7b698ef8bf9ee7de13a282dc6f196ffcfd679c36434059f6c9e0be67b4c51a0"}, + {file = "dulwich-0.21.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:929b720a80c758f29b48ad9b25d3b16e2276767fadfbcdb1d910b9565f4dcc82"}, + {file = "dulwich-0.21.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7385eae5cc0b4777c5021d6b556eca9a0ebfc8e767329ebf5d55c45f931dbd3"}, + {file = "dulwich-0.21.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7efd37d32e924f97beb3ee4a8cf7fb00210db954f7c971d4c67970d8bbe9508b"}, + {file = "dulwich-0.21.2.tar.gz", hash = "sha256:d865ae7fd9497d64ce345a6784ff1775b01317fba9632ef9d2dfd7978f1b0d4f"}, ] [package.dependencies] +typing-extensions = {version = "*", markers = "python_version <= \"3.7\""} urllib3 = ">=1.25" [package.extras] @@ -1798,4 +1792,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "bbc71952916c7ee1c404e4a85e611189347955d63d5560b5bdb23073a07afde6" +content-hash = "0ec7c852f1ac947add292b5e4d33f8d7ad79358ca6da3661480c94608fd4c4a6" diff --git a/pyproject.toml b/pyproject.toml index 88a46ec4ecf..0a482489584 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ poetry-plugin-export = "^1.2.0" cachecontrol = { version = "^0.12.9", extras = ["filecache"] } cleo = "^2.0.0" crashtest = "^0.4.1" -dulwich = "^0.20.46" +dulwich = "^0.21.2" filelock = "^3.8.0" html5lib = "^1.0" importlib-metadata = { version = ">=4.4,<6", python = "<3.10" } From d3156964ffd564d576b29c705edb201b34d4cbc6 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sat, 21 Jan 2023 15:39:52 +0000 Subject: [PATCH 064/151] remove strange VENDORS handling in installed repository --- src/poetry/repositories/installed_repository.py | 16 ---------------- tests/repositories/test_installed_repository.py | 13 ++----------- 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/src/poetry/repositories/installed_repository.py b/src/poetry/repositories/installed_repository.py index 80353bd5de2..2a3496ac5ba 100644 --- a/src/poetry/repositories/installed_repository.py +++ b/src/poetry/repositories/installed_repository.py @@ -20,15 +20,6 @@ from poetry.utils.env import Env -_VENDORS = Path(__file__).parent.parent.joinpath("_vendor") - - -try: - FileNotFoundError -except NameError: - FileNotFoundError = OSError - - logger = logging.getLogger(__name__) @@ -283,13 +274,6 @@ def load(cls, env: Env, with_dependencies: bool = False) -> InstalledRepository: if name in seen: continue - try: - path.relative_to(_VENDORS) - except ValueError: - pass - else: - continue - package = cls.create_package_from_distribution(distribution, env) if with_dependencies: diff --git a/tests/repositories/test_installed_repository.py b/tests/repositories/test_installed_repository.py index 87dfe183c8a..5d71bcd7497 100644 --- a/tests/repositories/test_installed_repository.py +++ b/tests/repositories/test_installed_repository.py @@ -22,14 +22,12 @@ SITE_PURELIB = ENV_DIR / "lib" / "python3.7" / "site-packages" SITE_PLATLIB = ENV_DIR / "lib64" / "python3.7" / "site-packages" SRC = ENV_DIR / "src" -VENDOR_DIR = ENV_DIR / "vendor" / "py3.7" INSTALLED_RESULTS = [ metadata.PathDistribution(SITE_PURELIB / "cleo-0.7.6.dist-info"), metadata.PathDistribution(SRC / "pendulum" / "pendulum.egg-info"), metadata.PathDistribution( zipfile.Path(str(SITE_PURELIB / "foo-0.1.0-py3.8.egg"), "EGG-INFO") ), - metadata.PathDistribution(VENDOR_DIR / "attrs-19.3.0.dist-info"), metadata.PathDistribution(SITE_PURELIB / "standard-1.2.3.dist-info"), metadata.PathDistribution(SITE_PURELIB / "editable-2.3.4.dist-info"), metadata.PathDistribution(SITE_PURELIB / "editable-with-import-2.3.4.dist-info"), @@ -80,11 +78,6 @@ def mock_git_info(mocker: MockerFixture) -> None: ) -@pytest.fixture(autouse=True) -def mock_installed_repository_vendors(mocker: MockerFixture) -> None: - mocker.patch("poetry.repositories.installed_repository._VENDORS", str(VENDOR_DIR)) - - @pytest.fixture def repository(mocker: MockerFixture, env: MockEnv) -> InstalledRepository: mocker.patch( @@ -104,7 +97,7 @@ def get_package_from_repository( def test_load_successful(repository: InstalledRepository): - assert len(repository.packages) == len(INSTALLED_RESULTS) - 1 + assert len(repository.packages) == len(INSTALLED_RESULTS) def test_load_successful_with_invalid_distribution( @@ -118,9 +111,7 @@ def test_load_successful_with_invalid_distribution( ) repository_with_invalid_distribution = InstalledRepository.load(env) - assert ( - len(repository_with_invalid_distribution.packages) == len(INSTALLED_RESULTS) - 1 - ) + assert len(repository_with_invalid_distribution.packages) == len(INSTALLED_RESULTS) assert len(caplog.messages) == 1 message = caplog.messages[0] From 196d6660d2c250f9b2a3fec6304986ef1145e2cc Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sat, 21 Jan 2023 15:22:03 +0000 Subject: [PATCH 065/151] remove empty vendor directory --- src/poetry/_vendor/.gitignore | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 src/poetry/_vendor/.gitignore diff --git a/src/poetry/_vendor/.gitignore b/src/poetry/_vendor/.gitignore deleted file mode 100644 index d6b7ef32c84..00000000000 --- a/src/poetry/_vendor/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore From 10957d5a19bb2636b8825ebde8a0281030d8584c Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 22 Jan 2023 12:57:27 +0000 Subject: [PATCH 066/151] Don't uninstall packages that should be kept --- src/poetry/puzzle/transaction.py | 14 ++++++++++---- tests/puzzle/test_transaction.py | 25 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/poetry/puzzle/transaction.py b/src/poetry/puzzle/transaction.py index 5c5dbd061c9..74d0d6c5e61 100644 --- a/src/poetry/puzzle/transaction.py +++ b/src/poetry/puzzle/transaction.py @@ -74,6 +74,7 @@ def calculate_operations( operations.append(Install(result_package, priority=priority)) if with_uninstalls: + uninstalls: set[str] = set() for current_package in self._current_packages: found = any( current_package.name == result_package.name @@ -83,11 +84,12 @@ def calculate_operations( if not found: for installed_package in self._installed_packages: if installed_package.name == current_package.name: + uninstalls.add(installed_package.name) operations.append(Uninstall(current_package)) if synchronize: - current_package_names = { - current_package.name for current_package in self._current_packages + result_package_names = { + result_package.name for result_package, _ in self._result_packages } # We preserve pip/setuptools/wheel when not managed by poetry, this is # done to avoid externally managed virtual environments causing @@ -96,9 +98,12 @@ def calculate_operations( "pip", "setuptools", "wheel", - } - current_package_names + } - result_package_names for installed_package in self._installed_packages: + if installed_package.name in uninstalls: + continue + if ( self._root_package and installed_package.name == self._root_package.name @@ -108,7 +113,8 @@ def calculate_operations( if installed_package.name in preserved_package_names: continue - if installed_package.name not in current_package_names: + if installed_package.name not in result_package_names: + uninstalls.add(installed_package.name) operations.append(Uninstall(installed_package)) return sorted( diff --git a/tests/puzzle/test_transaction.py b/tests/puzzle/test_transaction.py index ae4093f5b12..67d3499183a 100644 --- a/tests/puzzle/test_transaction.py +++ b/tests/puzzle/test_transaction.py @@ -121,6 +121,31 @@ def test_it_should_remove_installed_packages_if_required(): ) +def test_it_should_not_remove_installed_packages_that_are_in_result(): + transaction = Transaction( + [], + [ + (Package("a", "1.0.0"), 1), + (Package("b", "2.0.0"), 2), + (Package("c", "3.0.0"), 0), + ], + installed_packages=[ + Package("a", "1.0.0"), + Package("b", "2.0.0"), + Package("c", "3.0.0"), + ], + ) + + check_operations( + transaction.calculate_operations(synchronize=True), + [ + {"job": "install", "package": Package("a", "1.0.0"), "skipped": True}, + {"job": "install", "package": Package("b", "2.0.0"), "skipped": True}, + {"job": "install", "package": Package("c", "3.0.0"), "skipped": True}, + ], + ) + + def test_it_should_update_installed_packages_if_sources_are_different(): transaction = Transaction( [Package("a", "1.0.0")], From 86308b6b516e9893a5c4f9cf47c181387883bacb Mon Sep 17 00:00:00 2001 From: Martin Mokry Date: Sun, 22 Jan 2023 20:13:35 +0100 Subject: [PATCH 067/151] drop flatdict dependency --- poetry.lock | 13 +------------ pyproject.toml | 1 - tests/config/test_config.py | 5 ++--- tests/helpers.py | 33 ++++++++++++++++++++++++++++++++- tests/test_helpers.py | 23 +++++++++++++++++++++++ 5 files changed, 58 insertions(+), 17 deletions(-) create mode 100644 tests/test_helpers.py diff --git a/poetry.lock b/poetry.lock index ec0d8b80035..123acd0cf75 100644 --- a/poetry.lock +++ b/poetry.lock @@ -489,17 +489,6 @@ files = [ docs = ["furo (>=2022.9.29)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] testing = ["covdefaults (>=2.2.2)", "coverage (>=6.5)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] -[[package]] -name = "flatdict" -version = "4.0.1" -description = "Python module for interacting with nested dicts as a single level dict with delimited keys." -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "flatdict-4.0.1.tar.gz", hash = "sha256:cd32f08fd31ed21eb09ebc76f06b6bd12046a24f77beb1fd0281917e47f26742"}, -] - [[package]] name = "html5lib" version = "1.1" @@ -1792,4 +1781,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "0ec7c852f1ac947add292b5e4d33f8d7ad79358ca6da3661480c94608fd4c4a6" +content-hash = "39120ecbb0a8005ab2bab602a5f36ca178588521343d56c87ea2596f14c1c88a" diff --git a/pyproject.toml b/pyproject.toml index 0a482489584..729e68c0a54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,7 +89,6 @@ pre-commit = "^2.6" # Cachy frozen to test backwards compatibility for `poetry.utils.cache`. cachy = "0.3.0" deepdiff = "^6.2" -flatdict = "^4.0.1" httpretty = "^1.0" pytest = "^7.1" pytest-cov = "^4.0" diff --git a/tests/config/test_config.py b/tests/config/test_config.py index ce691470585..0d4f3ba7a2b 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -8,11 +8,10 @@ import pytest -from flatdict import FlatDict - from poetry.config.config import Config from poetry.config.config import boolean_normalizer from poetry.config.config import int_normalizer +from tests.helpers import flatten_dict if TYPE_CHECKING: @@ -21,7 +20,7 @@ def get_options_based_on_normalizer(normalizer: Callable) -> str: - flattened_config = FlatDict(Config.default_config, delimiter=".") + flattened_config = flatten_dict(obj=Config.default_config, delimiter=".") for k in flattened_config: if Config._get_normalizer(k) == normalizer: diff --git a/tests/helpers.py b/tests/helpers.py index 9ccb9e7730e..feef37728c0 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -9,7 +9,6 @@ from pathlib import Path from typing import TYPE_CHECKING -from typing import Any from poetry.core.packages.package import Package from poetry.core.packages.utils.link import Link @@ -28,6 +27,8 @@ if TYPE_CHECKING: from collections.abc import Iterator + from typing import Any + from typing import Mapping from poetry.core.constraints.version import Version from poetry.core.packages.dependency import Dependency @@ -284,3 +285,33 @@ def mock_metadata_entry_points( "entry_points", return_value=[make_entry_point_from_plugin(name, cls, dist)], ) + + +def flatten_dict(obj: Mapping[str, Any], delimiter: str = ".") -> Mapping[str, Any]: + """ + Flatten a nested dict. + + A flatdict replacement. + + :param obj: A nested dict to be flattened + :delimiter str: A delimiter used in the key path + :return: Flattened dict + """ + + def recurse_keys(obj: Mapping[str, Any]) -> Iterator[tuple[list[str], Any]]: + """ + A recursive generator to yield key paths and their values + + :param obj: A nested dict to be flattened + :return: dict + """ + if isinstance(obj, dict): + for key in obj.keys(): + for leaf in recurse_keys(obj[key]): + leaf_path, leaf_value = leaf + leaf_path.insert(0, key) + yield (leaf_path, leaf_value) + else: + yield ([], obj) + + return {delimiter.join(path): value for path, value in recurse_keys(obj)} diff --git a/tests/test_helpers.py b/tests/test_helpers.py new file mode 100644 index 00000000000..7db5580b460 --- /dev/null +++ b/tests/test_helpers.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from tests.helpers import flatten_dict + + +def test_flatten_dict() -> None: + orig_dict = { + "a": 1, + "b": 2, + "c": { + "x": 8, + "y": 9, + }, + } + + flattened_dict = { + "a": 1, + "b": 2, + "c:x": 8, + "c:y": 9, + } + + assert flattened_dict == flatten_dict(orig_dict, delimiter=":") From e1504eda6a7a8f2871ec08325731146e3be383b0 Mon Sep 17 00:00:00 2001 From: Chip Pate Date: Sun, 22 Jan 2023 16:54:23 -0500 Subject: [PATCH 068/151] fix(cli): make config relocation instructions more explicit (#7140) --- src/poetry/locations.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/poetry/locations.py b/src/poetry/locations.py index d60eff0735b..7d10f7d11d8 100644 --- a/src/poetry/locations.py +++ b/src/poetry/locations.py @@ -34,9 +34,12 @@ if any(file.exists() for file in (auth_toml, config_toml)): logger.warning( ( - "Configuration file exists at %s, reusing this directory.\n\nConsider" - " moving configuration to %s, as support for the legacy directory will" - " be removed in an upcoming release." + ( + "Configuration file exists at %s, reusing this" + " directory.\n\nConsider moving TOML configuration files to %s, as" + " support for the legacy directory will be removed in an upcoming" + " release." + ), ), _LEGACY_CONFIG_DIR, CONFIG_DIR, From 33242c22906ec84c7f526537bfd9a98415192d13 Mon Sep 17 00:00:00 2001 From: Wagner Macedo Date: Mon, 23 Jan 2023 06:32:49 +0100 Subject: [PATCH 069/151] Fix incorrect sys.argv[0] path when calling project scripts (#6737) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Christopher Dignam Co-authored-by: Randy Döring <30527984+radoering@users.noreply.github.com> --- src/poetry/console/commands/run.py | 21 ++++++++++- tests/console/commands/test_run.py | 37 +++++++++++++++++++ tests/fixtures/scripts/pyproject.toml | 1 + tests/fixtures/scripts/scripts/check_argv0.py | 23 ++++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/scripts/scripts/check_argv0.py diff --git a/src/poetry/console/commands/run.py b/src/poetry/console/commands/run.py index 50e1ae118f6..63af286d3b1 100644 --- a/src/poetry/console/commands/run.py +++ b/src/poetry/console/commands/run.py @@ -5,6 +5,7 @@ from cleo.helpers import argument from poetry.console.commands.env_command import EnvCommand +from poetry.utils._compat import WINDOWS if TYPE_CHECKING: @@ -44,7 +45,25 @@ def _module(self) -> Module: return module - def run_script(self, script: str | dict[str, str], args: str) -> int: + def run_script(self, script: str | dict[str, str], args: list[str]) -> int: + """Runs an entry point script defined in the section ``[tool.poetry.scripts]``. + + When a script exists in the venv bin folder, i.e. after ``poetry install``, + then ``sys.argv[0]`` must be set to the full path of the executable, so + ``poetry run foo`` and ``poetry shell``, ``foo`` have the same ``sys.argv[0]`` + that points to the full path. + + Otherwise (when an entry point script does not exist), ``sys.argv[0]`` is the + script name only, i.e. ``poetry run foo`` has ``sys.argv == ['foo']``. + """ + for script_dir in self.env.script_dirs: + script_path = script_dir / args[0] + if WINDOWS: + script_path = script_path.with_suffix(".cmd") + if script_path.exists(): + args = [str(script_path), *args[1:]] + break + if isinstance(script, dict): script = script["callable"] diff --git a/tests/console/commands/test_run.py b/tests/console/commands/test_run.py index 60bc94e74a7..12a4081cf1e 100644 --- a/tests/console/commands/test_run.py +++ b/tests/console/commands/test_run.py @@ -146,3 +146,40 @@ def test_run_script_exit_code( ) assert tester.execute("exit-code") == 42 assert tester.execute("return-code") == 42 + + +@pytest.mark.parametrize( + "installed_script", [False, True], ids=["not installed", "installed"] +) +def test_run_script_sys_argv0( + installed_script: bool, + poetry_with_scripts: Poetry, + command_tester_factory: CommandTesterFactory, + tmp_venv: VirtualEnv, + mocker: MockerFixture, +) -> None: + """ + If RunCommand calls an installed script defined in pyproject.toml, + sys.argv[0] must be set to the full path of the script. + """ + mocker.patch("poetry.utils.env.EnvManager.get", return_value=tmp_venv) + mocker.patch( + "os.execvpe", + lambda file, args, env: subprocess.call([file] + args[1:], env=env), + ) + + install_tester = command_tester_factory( + "install", + poetry=poetry_with_scripts, + environment=tmp_venv, + ) + assert install_tester.execute() == 0 + if not installed_script: + for path in tmp_venv.script_dirs[0].glob("check-argv0*"): + path.unlink() + + tester = command_tester_factory( + "run", poetry=poetry_with_scripts, environment=tmp_venv + ) + argv1 = "absolute" if installed_script else "relative" + assert tester.execute(f"check-argv0 {argv1}") == 0 diff --git a/tests/fixtures/scripts/pyproject.toml b/tests/fixtures/scripts/pyproject.toml index a53f36b930e..06880366cc9 100644 --- a/tests/fixtures/scripts/pyproject.toml +++ b/tests/fixtures/scripts/pyproject.toml @@ -9,6 +9,7 @@ readme = "README.md" python = "^3.7" [tool.poetry.scripts] +check-argv0 = "scripts.check_argv0:main" exit-code = "scripts.exit_code:main" return-code = "scripts.return_code:main" diff --git a/tests/fixtures/scripts/scripts/check_argv0.py b/tests/fixtures/scripts/scripts/check_argv0.py new file mode 100644 index 00000000000..e1dbc79d343 --- /dev/null +++ b/tests/fixtures/scripts/scripts/check_argv0.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +import sys + +from pathlib import Path + + +def main() -> int: + path = Path(sys.argv[0]) + if sys.argv[1] == "absolute": + if not path.is_absolute(): + raise RuntimeError(f"sys.argv[0] is not an absolute path: {path}") + if not path.exists(): + raise RuntimeError(f"sys.argv[0] does not exist: {path}") + else: + if path.is_absolute(): + raise RuntimeError(f"sys.argv[0] is an absolute path: {path}") + + return 0 + + +if __name__ == "__main__": + raise sys.exit(main()) From daa87b2db053319ebbef7709fd0fd7721a46eb5b Mon Sep 17 00:00:00 2001 From: wxgeo Date: Mon, 23 Jan 2023 22:18:46 +0100 Subject: [PATCH 070/151] Change my-token to in documentation. (#7393) --- docs/repositories.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/repositories.md b/docs/repositories.md index f27c92ab0fa..8d52a10994f 100644 --- a/docs/repositories.md +++ b/docs/repositories.md @@ -342,7 +342,7 @@ when uploading packages to PyPI. Once you have created a new token, you can tell Poetry to use it: ```bash -poetry config pypi-token.pypi my-token +poetry config pypi-token.pypi ``` If you still want to use your username and password, you can do so with the following From 9a0b4af51effde0ddba53085e85e9f16e070f85a Mon Sep 17 00:00:00 2001 From: Bartosz Sokorski Date: Tue, 24 Jan 2023 07:17:24 +0100 Subject: [PATCH 071/151] ci: update flake8 plugins (#7391) Dropped flake8-broken-line. We follow black for formatting anyway and this was blocking us from updating to newest flake8 version. --- .pre-commit-config.yaml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 68891d76ae6..1322d6cc17f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,19 +32,18 @@ repos: hooks: - id: yesqa additional_dependencies: &flake8_deps - - flake8-annotations==2.9.0 - - flake8-broken-line==0.5.0 - - flake8-bugbear==22.7.1 - - flake8-comprehensions==3.10.0 - - flake8-eradicate==1.3.0 - - flake8-quotes==3.3.1 + - flake8-annotations==3.0.0 + - flake8-bugbear==23.1.20 + - flake8-comprehensions==3.10.1 + - flake8-eradicate==1.4.0 + - flake8-pie==0.16.0 + - flake8-quotes==3.3.2 - flake8-simplify==0.19.3 - flake8-tidy-imports==4.8.0 - - flake8-type-checking==2.2.0 - - flake8-typing-imports==1.12.0 + - flake8-type-checking==2.3.0 + - flake8-typing-imports==1.14.0 - flake8-use-fstring==1.4 - - pep8-naming==0.13.1 - - flake8-pie==0.16.0 + - pep8-naming==0.13.3 - repo: https://github.com/asottile/pyupgrade rev: v3.3.1 From cb17220c2e8cc5265ea8a209a012b57f2a8a4533 Mon Sep 17 00:00:00 2001 From: finswimmer Date: Sun, 29 Jan 2023 20:25:56 +0100 Subject: [PATCH 072/151] fix(dependencies): remove upper boundary for importlib-metadata --- poetry.lock | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 123acd0cf75..1aeb0977301 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1781,4 +1781,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "39120ecbb0a8005ab2bab602a5f36ca178588521343d56c87ea2596f14c1c88a" +content-hash = "c1c3d959c9e5ede769d788c0fe4767bdc9a444aabb9ebfcb47c3a393d8a5ef2d" diff --git a/pyproject.toml b/pyproject.toml index 729e68c0a54..13bc4a260ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ crashtest = "^0.4.1" dulwich = "^0.21.2" filelock = "^3.8.0" html5lib = "^1.0" -importlib-metadata = { version = ">=4.4,<6", python = "<3.10" } +importlib-metadata = { version = ">=4.4", python = "<3.10" } jsonschema = "^4.10.0" keyring = "^23.9.0" lockfile = "^0.12.2" From 68770a67ef7e93fee86b64986dcd517e106b1994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sat, 28 Jan 2023 13:18:42 +0100 Subject: [PATCH 073/151] chore: update required poetry-core and poetry-plugin-export and relock dependencies --- .pre-commit-config.yaml | 2 +- poetry.lock | 766 ++++++++++++++++++++++++---------------- pyproject.toml | 15 +- src/poetry/factory.py | 2 +- 4 files changed, 471 insertions(+), 314 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1322d6cc17f..c0d132dd9a5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -59,7 +59,7 @@ repos: args: [--all] - repo: https://github.com/pycqa/isort - rev: 5.11.4 + rev: 5.12.0 hooks: - id: isort name: "isort (python)" diff --git a/poetry.lock b/poetry.lock index 1aeb0977301..def850e4f87 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,21 +2,22 @@ [[package]] name = "attrs" -version = "22.1.0" +version = "22.2.0" description = "Classes Without Boilerplate" category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, + {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, + {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, ] [package.extras] -dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] -docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] -tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] +cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] +tests = ["attrs[tests-no-zope]", "zope.interface"] +tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] [[package]] name = "backports-cached-property" @@ -171,19 +172,102 @@ files = [ [[package]] name = "charset-normalizer" -version = "2.1.1" +version = "3.0.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=3.6.0" +python-versions = "*" files = [ - {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, - {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, + {file = "charset-normalizer-3.0.1.tar.gz", hash = "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-win32.whl", hash = "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-win32.whl", hash = "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-win32.whl", hash = "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-win32.whl", hash = "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-win32.whl", hash = "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59"}, + {file = "charset_normalizer-3.0.1-py3-none-any.whl", hash = "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24"}, ] -[package.extras] -unicode-backport = ["unicodedata2"] - [[package]] name = "cleo" version = "2.0.1" @@ -214,62 +298,63 @@ files = [ [[package]] name = "coverage" -version = "6.5.0" +version = "7.1.0" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, - {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, - {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, - {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, - {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, - {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, - {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, - {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, - {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, - {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, - {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, - {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, - {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, - {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, - {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, - {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, + {file = "coverage-7.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b946bbcd5a8231383450b195cfb58cb01cbe7f8949f5758566b881df4b33baf"}, + {file = "coverage-7.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec8e767f13be637d056f7e07e61d089e555f719b387a7070154ad80a0ff31801"}, + {file = "coverage-7.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a5a5879a939cb84959d86869132b00176197ca561c664fc21478c1eee60d75"}, + {file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b643cb30821e7570c0aaf54feaf0bfb630b79059f85741843e9dc23f33aaca2c"}, + {file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32df215215f3af2c1617a55dbdfb403b772d463d54d219985ac7cd3bf124cada"}, + {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:33d1ae9d4079e05ac4cc1ef9e20c648f5afabf1a92adfaf2ccf509c50b85717f"}, + {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:29571503c37f2ef2138a306d23e7270687c0efb9cab4bd8038d609b5c2393a3a"}, + {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:63ffd21aa133ff48c4dff7adcc46b7ec8b565491bfc371212122dd999812ea1c"}, + {file = "coverage-7.1.0-cp310-cp310-win32.whl", hash = "sha256:4b14d5e09c656de5038a3f9bfe5228f53439282abcab87317c9f7f1acb280352"}, + {file = "coverage-7.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:8361be1c2c073919500b6601220a6f2f98ea0b6d2fec5014c1d9cfa23dd07038"}, + {file = "coverage-7.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:da9b41d4539eefd408c46725fb76ecba3a50a3367cafb7dea5f250d0653c1040"}, + {file = "coverage-7.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5b15ed7644ae4bee0ecf74fee95808dcc34ba6ace87e8dfbf5cb0dc20eab45a"}, + {file = "coverage-7.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d12d076582507ea460ea2a89a8c85cb558f83406c8a41dd641d7be9a32e1274f"}, + {file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2617759031dae1bf183c16cef8fcfb3de7617f394c813fa5e8e46e9b82d4222"}, + {file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4e4881fa9e9667afcc742f0c244d9364d197490fbc91d12ac3b5de0bf2df146"}, + {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9d58885215094ab4a86a6aef044e42994a2bd76a446dc59b352622655ba6621b"}, + {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ffeeb38ee4a80a30a6877c5c4c359e5498eec095878f1581453202bfacc8fbc2"}, + {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3baf5f126f30781b5e93dbefcc8271cb2491647f8283f20ac54d12161dff080e"}, + {file = "coverage-7.1.0-cp311-cp311-win32.whl", hash = "sha256:ded59300d6330be27bc6cf0b74b89ada58069ced87c48eaf9344e5e84b0072f7"}, + {file = "coverage-7.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:6a43c7823cd7427b4ed763aa7fb63901ca8288591323b58c9cd6ec31ad910f3c"}, + {file = "coverage-7.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a726d742816cb3a8973c8c9a97539c734b3a309345236cd533c4883dda05b8d"}, + {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc7c85a150501286f8b56bd8ed3aa4093f4b88fb68c0843d21ff9656f0009d6a"}, + {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5b4198d85a3755d27e64c52f8c95d6333119e49fd001ae5798dac872c95e0f8"}, + {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddb726cb861c3117a553f940372a495fe1078249ff5f8a5478c0576c7be12050"}, + {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:51b236e764840a6df0661b67e50697aaa0e7d4124ca95e5058fa3d7cbc240b7c"}, + {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7ee5c9bb51695f80878faaa5598040dd6c9e172ddcf490382e8aedb8ec3fec8d"}, + {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c31b75ae466c053a98bf26843563b3b3517b8f37da4d47b1c582fdc703112bc3"}, + {file = "coverage-7.1.0-cp37-cp37m-win32.whl", hash = "sha256:3b155caf3760408d1cb903b21e6a97ad4e2bdad43cbc265e3ce0afb8e0057e73"}, + {file = "coverage-7.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2a60d6513781e87047c3e630b33b4d1e89f39836dac6e069ffee28c4786715f5"}, + {file = "coverage-7.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2cba5c6db29ce991029b5e4ac51eb36774458f0a3b8d3137241b32d1bb91f06"}, + {file = "coverage-7.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beeb129cacea34490ffd4d6153af70509aa3cda20fdda2ea1a2be870dfec8d52"}, + {file = "coverage-7.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c45948f613d5d18c9ec5eaa203ce06a653334cf1bd47c783a12d0dd4fd9c851"}, + {file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef382417db92ba23dfb5864a3fc9be27ea4894e86620d342a116b243ade5d35d"}, + {file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c7c0d0827e853315c9bbd43c1162c006dd808dbbe297db7ae66cd17b07830f0"}, + {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e5cdbb5cafcedea04924568d990e20ce7f1945a1dd54b560f879ee2d57226912"}, + {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9817733f0d3ea91bea80de0f79ef971ae94f81ca52f9b66500c6a2fea8e4b4f8"}, + {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:218fe982371ac7387304153ecd51205f14e9d731b34fb0568181abaf7b443ba0"}, + {file = "coverage-7.1.0-cp38-cp38-win32.whl", hash = "sha256:04481245ef966fbd24ae9b9e537ce899ae584d521dfbe78f89cad003c38ca2ab"}, + {file = "coverage-7.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:8ae125d1134bf236acba8b83e74c603d1b30e207266121e76484562bc816344c"}, + {file = "coverage-7.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2bf1d5f2084c3932b56b962a683074a3692bce7cabd3aa023c987a2a8e7612f6"}, + {file = "coverage-7.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:98b85dd86514d889a2e3dd22ab3c18c9d0019e696478391d86708b805f4ea0fa"}, + {file = "coverage-7.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38da2db80cc505a611938d8624801158e409928b136c8916cd2e203970dde4dc"}, + {file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3164d31078fa9efe406e198aecd2a02d32a62fecbdef74f76dad6a46c7e48311"}, + {file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db61a79c07331e88b9a9974815c075fbd812bc9dbc4dc44b366b5368a2936063"}, + {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ccb092c9ede70b2517a57382a601619d20981f56f440eae7e4d7eaafd1d1d09"}, + {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:33ff26d0f6cc3ca8de13d14fde1ff8efe1456b53e3f0273e63cc8b3c84a063d8"}, + {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d47dd659a4ee952e90dc56c97d78132573dc5c7b09d61b416a9deef4ebe01a0c"}, + {file = "coverage-7.1.0-cp39-cp39-win32.whl", hash = "sha256:d248cd4a92065a4d4543b8331660121b31c4148dd00a691bfb7a5cdc7483cfa4"}, + {file = "coverage-7.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7ed681b0f8e8bcbbffa58ba26fcf5dbc8f79e7997595bf071ed5430d8c08d6f3"}, + {file = "coverage-7.1.0-pp37.pp38.pp39-none-any.whl", hash = "sha256:755e89e32376c850f826c425ece2c35a4fc266c081490eb0a841e7c1cb0d3bda"}, + {file = "coverage-7.1.0.tar.gz", hash = "sha256:10188fe543560ec4874f974b5305cd1a8bdcfa885ee00ea3a03733464c4ca265"}, ] [package.dependencies] @@ -292,47 +377,44 @@ files = [ [[package]] name = "cryptography" -version = "38.0.4" +version = "39.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "cryptography-38.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:2fa36a7b2cc0998a3a4d5af26ccb6273f3df133d61da2ba13b3286261e7efb70"}, - {file = "cryptography-38.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:1f13ddda26a04c06eb57119caf27a524ccae20533729f4b1e4a69b54e07035eb"}, - {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2ec2a8714dd005949d4019195d72abed84198d877112abb5a27740e217e0ea8d"}, - {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50a1494ed0c3f5b4d07650a68cd6ca62efe8b596ce743a5c94403e6f11bf06c1"}, - {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a10498349d4c8eab7357a8f9aa3463791292845b79597ad1b98a543686fb1ec8"}, - {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:10652dd7282de17990b88679cb82f832752c4e8237f0c714be518044269415db"}, - {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bfe6472507986613dc6cc00b3d492b2f7564b02b3b3682d25ca7f40fa3fd321b"}, - {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ce127dd0a6a0811c251a6cddd014d292728484e530d80e872ad9806cfb1c5b3c"}, - {file = "cryptography-38.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:53049f3379ef05182864d13bb9686657659407148f901f3f1eee57a733fb4b00"}, - {file = "cryptography-38.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:8a4b2bdb68a447fadebfd7d24855758fe2d6fecc7fed0b78d190b1af39a8e3b0"}, - {file = "cryptography-38.0.4-cp36-abi3-win32.whl", hash = "sha256:1d7e632804a248103b60b16fb145e8df0bc60eed790ece0d12efe8cd3f3e7744"}, - {file = "cryptography-38.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:8e45653fb97eb2f20b8c96f9cd2b3a0654d742b47d638cf2897afbd97f80fa6d"}, - {file = "cryptography-38.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca57eb3ddaccd1112c18fc80abe41db443cc2e9dcb1917078e02dfa010a4f353"}, - {file = "cryptography-38.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:c9e0d79ee4c56d841bd4ac6e7697c8ff3c8d6da67379057f29e66acffcd1e9a7"}, - {file = "cryptography-38.0.4-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0e70da4bdff7601b0ef48e6348339e490ebfb0cbe638e083c9c41fb49f00c8bd"}, - {file = "cryptography-38.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:998cd19189d8a747b226d24c0207fdaa1e6658a1d3f2494541cb9dfbf7dcb6d2"}, - {file = "cryptography-38.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67461b5ebca2e4c2ab991733f8ab637a7265bb582f07c7c88914b5afb88cb95b"}, - {file = "cryptography-38.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:4eb85075437f0b1fd8cd66c688469a0c4119e0ba855e3fef86691971b887caf6"}, - {file = "cryptography-38.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3178d46f363d4549b9a76264f41c6948752183b3f587666aff0555ac50fd7876"}, - {file = "cryptography-38.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6391e59ebe7c62d9902c24a4d8bcbc79a68e7c4ab65863536127c8a9cd94043b"}, - {file = "cryptography-38.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:78e47e28ddc4ace41dd38c42e6feecfdadf9c3be2af389abbfeef1ff06822285"}, - {file = "cryptography-38.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fb481682873035600b5502f0015b664abc26466153fab5c6bc92c1ea69d478b"}, - {file = "cryptography-38.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:4367da5705922cf7070462e964f66e4ac24162e22ab0a2e9d31f1b270dd78083"}, - {file = "cryptography-38.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b4cad0cea995af760f82820ab4ca54e5471fc782f70a007f31531957f43e9dee"}, - {file = "cryptography-38.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:80ca53981ceeb3241998443c4964a387771588c4e4a5d92735a493af868294f9"}, - {file = "cryptography-38.0.4.tar.gz", hash = "sha256:175c1a818b87c9ac80bb7377f5520b7f31b3ef2a0004e2420319beadedb67290"}, + {file = "cryptography-39.0.0-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52a1a6f81e738d07f43dab57831c29e57d21c81a942f4602fac7ee21b27f288"}, + {file = "cryptography-39.0.0-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:80ee674c08aaef194bc4627b7f2956e5ba7ef29c3cc3ca488cf15854838a8f72"}, + {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:887cbc1ea60786e534b00ba8b04d1095f4272d380ebd5f7a7eb4cc274710fad9"}, + {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f97109336df5c178ee7c9c711b264c502b905c2d2a29ace99ed761533a3460f"}, + {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a6915075c6d3a5e1215eab5d99bcec0da26036ff2102a1038401d6ef5bef25b"}, + {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:76c24dd4fd196a80f9f2f5405a778a8ca132f16b10af113474005635fe7e066c"}, + {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bae6c7f4a36a25291b619ad064a30a07110a805d08dc89984f4f441f6c1f3f96"}, + {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:875aea1039d78557c7c6b4db2fe0e9d2413439f4676310a5f269dd342ca7a717"}, + {file = "cryptography-39.0.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f6c0db08d81ead9576c4d94bbb27aed8d7a430fa27890f39084c2d0e2ec6b0df"}, + {file = "cryptography-39.0.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f3ed2d864a2fa1666e749fe52fb8e23d8e06b8012e8bd8147c73797c506e86f1"}, + {file = "cryptography-39.0.0-cp36-abi3-win32.whl", hash = "sha256:f671c1bb0d6088e94d61d80c606d65baacc0d374e67bf895148883461cd848de"}, + {file = "cryptography-39.0.0-cp36-abi3-win_amd64.whl", hash = "sha256:e324de6972b151f99dc078defe8fb1b0a82c6498e37bff335f5bc6b1e3ab5a1e"}, + {file = "cryptography-39.0.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:754978da4d0457e7ca176f58c57b1f9de6556591c19b25b8bcce3c77d314f5eb"}, + {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ee1fd0de9851ff32dbbb9362a4d833b579b4a6cc96883e8e6d2ff2a6bc7104f"}, + {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:fec8b932f51ae245121c4671b4bbc030880f363354b2f0e0bd1366017d891458"}, + {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:407cec680e811b4fc829de966f88a7c62a596faa250fc1a4b520a0355b9bc190"}, + {file = "cryptography-39.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7dacfdeee048814563eaaec7c4743c8aea529fe3dd53127313a792f0dadc1773"}, + {file = "cryptography-39.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ad04f413436b0781f20c52a661660f1e23bcd89a0e9bb1d6d20822d048cf2856"}, + {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50386acb40fbabbceeb2986332f0287f50f29ccf1497bae31cf5c3e7b4f4b34f"}, + {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:e5d71c5d5bd5b5c3eebcf7c5c2bb332d62ec68921a8c593bea8c394911a005ce"}, + {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:844ad4d7c3850081dffba91cdd91950038ee4ac525c575509a42d3fc806b83c8"}, + {file = "cryptography-39.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e0a05aee6a82d944f9b4edd6a001178787d1546ec7c6223ee9a848a7ade92e39"}, + {file = "cryptography-39.0.0.tar.gz", hash = "sha256:f964c7dcf7802d133e8dbd1565914fa0194f9d683d82411989889ecd701e8adf"}, ] [package.dependencies] cffi = ">=1.12" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1,!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"] docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] -pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +pep8test = ["black", "ruff"] sdist = ["setuptools-rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] @@ -355,6 +437,25 @@ ordered-set = ">=4.0.2,<4.2.0" [package.extras] cli = ["click (==8.1.3)", "pyyaml (==6.0)"] +[[package]] +name = "deepdiff" +version = "6.2.3" +description = "Deep Difference and Search of any Python object/data. Recreate objects by adding adding deltas to each other." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "deepdiff-6.2.3-py3-none-any.whl", hash = "sha256:d83b06e043447d6770860a635abecb46e849b0494c43ced2ecafda7628c7ce72"}, + {file = "deepdiff-6.2.3.tar.gz", hash = "sha256:a02aaa8171351eba675cff5f795ec7a90987f86ad5449553308d4e18df57dc3d"}, +] + +[package.dependencies] +ordered-set = ">=4.0.2,<4.2.0" +orjson = "*" + +[package.extras] +cli = ["click (==8.1.3)", "pyyaml (==6.0)"] + [[package]] name = "distlib" version = "0.3.6" @@ -445,14 +546,14 @@ pgp = ["gpg"] [[package]] name = "exceptiongroup" -version = "1.0.4" +version = "1.1.0" description = "Backport of PEP 654 (exception groups)" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"}, - {file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"}, + {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, + {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, ] [package.extras] @@ -475,19 +576,19 @@ testing = ["pre-commit"] [[package]] name = "filelock" -version = "3.8.2" +version = "3.9.0" description = "A platform independent file lock." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "filelock-3.8.2-py3-none-any.whl", hash = "sha256:8df285554452285f79c035efb0c861eb33a4bcfa5b7a137016e32e6a90f9792c"}, - {file = "filelock-3.8.2.tar.gz", hash = "sha256:7565f628ea56bfcd8e54e42bdc55da899c85c1abfe1b5bcfd147e9188cebb3b2"}, + {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"}, + {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"}, ] [package.extras] -docs = ["furo (>=2022.9.29)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] -testing = ["covdefaults (>=2.2.2)", "coverage (>=6.5)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] +docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] [[package]] name = "html5lib" @@ -524,14 +625,14 @@ files = [ [[package]] name = "identify" -version = "2.5.10" +version = "2.5.17" description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "identify-2.5.10-py2.py3-none-any.whl", hash = "sha256:fb7c2feaeca6976a3ffa31ec3236a6911fbc51aec9acc111de2aed99f244ade2"}, - {file = "identify-2.5.10.tar.gz", hash = "sha256:dce9e31fee7dbc45fea36a9e855c316b8fbf807e65a862f160840bb5a2bf5dfd"}, + {file = "identify-2.5.17-py2.py3-none-any.whl", hash = "sha256:7d526dd1283555aafcc91539acc061d8f6f59adb0a7bba462735b0a318bff7ed"}, + {file = "identify-2.5.17.tar.gz", hash = "sha256:93cc61a861052de9d4c541a7acb7e3dcc9c11b398a2144f6e52ae5285f5f4f06"}, ] [package.extras] @@ -551,14 +652,14 @@ files = [ [[package]] name = "importlib-metadata" -version = "5.1.0" +version = "6.0.0" description = "Read metadata from Python packages" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "importlib_metadata-5.1.0-py3-none-any.whl", hash = "sha256:d84d17e21670ec07990e1044a99efe8d615d860fd176fc29ef5c306068fda313"}, - {file = "importlib_metadata-5.1.0.tar.gz", hash = "sha256:d5059f9f1e8e41f80e9c56c2ee58811450c31984dfa625329ffd7c0dad88a73b"}, + {file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"}, + {file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"}, ] [package.dependencies] @@ -566,39 +667,39 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] [[package]] name = "importlib-resources" -version = "5.10.1" +version = "5.10.2" description = "Read resources from Python packages" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "importlib_resources-5.10.1-py3-none-any.whl", hash = "sha256:c09b067d82e72c66f4f8eb12332f5efbebc9b007c0b6c40818108c9870adc363"}, - {file = "importlib_resources-5.10.1.tar.gz", hash = "sha256:32bb095bda29741f6ef0e5278c42df98d135391bee5f932841efc0041f748dc3"}, + {file = "importlib_resources-5.10.2-py3-none-any.whl", hash = "sha256:7d543798b0beca10b6a01ac7cafda9f822c54db9e8376a6bf57e0cbd74d486b6"}, + {file = "importlib_resources-5.10.2.tar.gz", hash = "sha256:e4a96c8cc0339647ff9a5e0550d9f276fc5a01ffa276012b58ec108cfd7b8484"}, ] [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [[package]] name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] @@ -662,24 +763,26 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- [[package]] name = "keyring" -version = "23.11.0" +version = "23.13.1" description = "Store and access your passwords safely." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "keyring-23.11.0-py3-none-any.whl", hash = "sha256:3dd30011d555f1345dec2c262f0153f2f0ca6bca041fb1dc4588349bb4c0ac1e"}, - {file = "keyring-23.11.0.tar.gz", hash = "sha256:ad192263e2cdd5f12875dedc2da13534359a7e760e77f8d04b50968a821c2361"}, + {file = "keyring-23.13.1-py3-none-any.whl", hash = "sha256:771ed2a91909389ed6148631de678f82ddc73737d85a927f382a8a1b157898cd"}, + {file = "keyring-23.13.1.tar.gz", hash = "sha256:ba2e15a9b35e21908d0aaf4e0a47acc52d6ae33444df0da2b49d41a46ef6d678"}, ] [package.dependencies] importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} +importlib-resources = {version = "*", markers = "python_version < \"3.9\""} "jaraco.classes" = "*" jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} -pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""} +pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] +completion = ["shtab"] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] @@ -863,16 +966,70 @@ files = [ [package.extras] dev = ["black", "mypy", "pytest"] +[[package]] +name = "orjson" +version = "3.8.5" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "orjson-3.8.5-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:143639b9898b094883481fac37733231da1c2ae3aec78a1dd8d3b58c9c9fceef"}, + {file = "orjson-3.8.5-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:31f43e63e0d94784c55e86bd376df3f80b574bea8c0bc5ecd8041009fa8ec78a"}, + {file = "orjson-3.8.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c802ea6d4a0d40f096aceb5e7ef0a26c23d276cb9334e1cadcf256bb090b6426"}, + {file = "orjson-3.8.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf298b55b371c2772420c5ace4d47b0a3ea1253667e20ded3c363160fd0575f6"}, + {file = "orjson-3.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68cb4a8501a463771d55bb22fc72795ec7e21d71ab083e000a2c3b651b6fb2af"}, + {file = "orjson-3.8.5-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:4f1427952b3bd92bfb63a61b7ffc33a9f54ec6de296fa8d924cbeba089866acb"}, + {file = "orjson-3.8.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c0a9f329468c8eb000742455b83546849bcd69495d6baa6e171c7ee8600a47bd"}, + {file = "orjson-3.8.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6535d527aa1e4a757a6ce9b61f3dd74edc762e7d2c6991643aae7c560c8440bd"}, + {file = "orjson-3.8.5-cp310-none-win_amd64.whl", hash = "sha256:2eee64c028adf6378dd714c8debc96d5b92b6bb4862debb65ca868e59bac6c63"}, + {file = "orjson-3.8.5-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:f5745ff473dd5c6718bf8c8d5bc183f638b4f3e03c7163ffcda4d4ef453f42ff"}, + {file = "orjson-3.8.5-cp311-cp311-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:544f1240b295083697027a5093ec66763218ff16f03521d5020e7a436d2e417b"}, + {file = "orjson-3.8.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c85c9c6bab97a831e7741089057347d99901b4db2451a076ca8adedc7d96297f"}, + {file = "orjson-3.8.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9bae7347764e7be6dada980fd071e865544c98317ab61af575c9cc5e1dc7e3fe"}, + {file = "orjson-3.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c67f6f6e9d26a06b63126112a7bc8d8529df048d31df2a257a8484b76adf3e5d"}, + {file = "orjson-3.8.5-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:758238364142fcbeca34c968beefc0875ffa10aa2f797c82f51cfb1d22d0934e"}, + {file = "orjson-3.8.5-cp311-none-win_amd64.whl", hash = "sha256:cc7579240fb88a626956a6cb4a181a11b62afbc409ce239a7b866568a2412fa2"}, + {file = "orjson-3.8.5-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:79aa3e47cbbd4eedbbde4f988f766d6cf38ccb51d52cfabfeb6b8d1b58654d25"}, + {file = "orjson-3.8.5-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:2544cd0d089faa862f5a39f508ee667419e3f9e11f119a6b1505cfce0eb26601"}, + {file = "orjson-3.8.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2be0025ca7e460bcacb250aba8ce0239be62957d58cf34045834cc9302611d3"}, + {file = "orjson-3.8.5-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b57bf72902d818506906e49c677a791f90dbd7f0997d60b14bc6c1ce4ce4cf9"}, + {file = "orjson-3.8.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ae9832a11c6a9efa8c14224e5caf6e35046efd781de14e59eb69ab4e561cf3"}, + {file = "orjson-3.8.5-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:0e28330cc6d51741cad0edd1b57caf6c5531aff30afe41402acde0a03246b8ed"}, + {file = "orjson-3.8.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:155954d725627b5480e6cc1ca488afb4fa685099a4ace5f5bf21a182fabf6706"}, + {file = "orjson-3.8.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ece1b6ef9312df5d5274ca6786e613b7da7de816356e36bcad9ea8a73d15ab71"}, + {file = "orjson-3.8.5-cp37-none-win_amd64.whl", hash = "sha256:6f58d1f0702332496bc1e2d267c7326c851991b62cf6395370d59c47f9890007"}, + {file = "orjson-3.8.5-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:933f4ab98362f46a59a6d0535986e1f0cae2f6b42435e24a55922b4bc872af0c"}, + {file = "orjson-3.8.5-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:47a7ca236b25a138a74b2cb5169adcdc5b2b8abdf661de438ba65967a2cde9dc"}, + {file = "orjson-3.8.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b573ca942c626fcf8a86be4f180b86b2498b18ae180f37b4180c2aced5808710"}, + {file = "orjson-3.8.5-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a9bab11611d5452efe4ae5315f5eb806f66104c08a089fb84c648d2e8e00f106"}, + {file = "orjson-3.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee2f5f6476617d01ca166266d70fd5605d3397a41f067022ce04a2e1ced4c8d"}, + {file = "orjson-3.8.5-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:ec0b0b6cd0b84f03537f22b719aca705b876c54ab5cf3471d551c9644127284f"}, + {file = "orjson-3.8.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:df3287dc304c8c4556dc85c4ab89eb333307759c1863f95e72e555c0cfce3e01"}, + {file = "orjson-3.8.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:09f40add3c2d208e20f8bf185df38f992bf5092202d2d30eced8f6959963f1d5"}, + {file = "orjson-3.8.5-cp38-none-win_amd64.whl", hash = "sha256:232ec1df0d708f74e0dd1fccac1e9a7008cd120d48fe695e8f0c9d80771da430"}, + {file = "orjson-3.8.5-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:8fba3e7aede3e88a01e94e6fe63d4580162b212e6da27ae85af50a1787e41416"}, + {file = "orjson-3.8.5-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:85e22c358cab170c8604e9edfffcc45dd7b0027ce57ed6bcacb556e8bfbbb704"}, + {file = "orjson-3.8.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeab1d8247507a75926adf3ca995c74e91f5db1f168815bf3e774f992ba52b50"}, + {file = "orjson-3.8.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:daaaef15a41e9e8cadc7677cefe00065ae10bce914eefe8da1cd26b3d063970b"}, + {file = "orjson-3.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ccc9f52cf46bd353c6ae1153eaf9d18257ddc110d135198b0cd8718474685ce"}, + {file = "orjson-3.8.5-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:d48c182c7ff4ea0787806de8a2f9298ca44fd0068ecd5f23a4b2d8e03c745cb6"}, + {file = "orjson-3.8.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1848e3b4cc09cc82a67262ae56e2a772b0548bb5a6f9dcaee10dcaaf0a5177b7"}, + {file = "orjson-3.8.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38480031bc8add58effe802291e4abf7042ef72ae1a4302efe9a36c8f8bfbfcc"}, + {file = "orjson-3.8.5-cp39-none-win_amd64.whl", hash = "sha256:0e9a1c2e649cbaed410c882cedc8f3b993d8f1426d9327f31762d3f46fe7cc88"}, + {file = "orjson-3.8.5.tar.gz", hash = "sha256:77a3b2bd0c4ef7723ea09081e3329dac568a62463aed127c1501441b07ffc64b"}, +] + [[package]] name = "packaging" -version = "22.0" +version = "23.0" description = "Core utilities for Python packages" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"}, - {file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"}, + {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, + {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, ] [[package]] @@ -892,14 +1049,14 @@ ptyprocess = ">=0.5" [[package]] name = "pkginfo" -version = "1.9.4" -description = "Query metadatdata from sdists / bdists / installed packages." +version = "1.9.6" +description = "Query metadata from sdists / bdists / installed packages." category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "pkginfo-1.9.4-py3-none-any.whl", hash = "sha256:7fc056f8e6b93355925083373b0f6bfbabe84ee3f29854fd155c9d6a4211267d"}, - {file = "pkginfo-1.9.4.tar.gz", hash = "sha256:e769fd353593d43e0c9f47e17e25f09a8efcddcdf9a71674ea3ba444ff31bb44"}, + {file = "pkginfo-1.9.6-py3-none-any.whl", hash = "sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546"}, + {file = "pkginfo-1.9.6.tar.gz", hash = "sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046"}, ] [package.extras] @@ -919,19 +1076,22 @@ files = [ [[package]] name = "platformdirs" -version = "2.6.0" +version = "2.6.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-2.6.0-py3-none-any.whl", hash = "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca"}, - {file = "platformdirs-2.6.0.tar.gz", hash = "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e"}, + {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, + {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""} + [package.extras] -docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] -test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" @@ -954,14 +1114,14 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "poetry-core" -version = "1.4.0" +version = "1.5.0" description = "Poetry PEP 517 Build Backend" category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "poetry_core-1.4.0-py3-none-any.whl", hash = "sha256:5559ab80384ac021db329ef317086417e140ee1176bcfcb3a3838b544e213c8e"}, - {file = "poetry_core-1.4.0.tar.gz", hash = "sha256:514bd33c30e0bf56b0ed44ee15e120d7e47b61ad908b2b1011da68c48a84ada9"}, + {file = "poetry_core-1.5.0-py3-none-any.whl", hash = "sha256:e216b70f013c47b82a72540d34347632c5bfe59fd54f5fe5d51f6a68b19aaf84"}, + {file = "poetry_core-1.5.0.tar.gz", hash = "sha256:253521bb7104e1df81f64d7b49ea1825057c91fa156d7d0bd752fefdad6f8c7a"}, ] [package.dependencies] @@ -969,30 +1129,30 @@ importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} [[package]] name = "poetry-plugin-export" -version = "1.2.0" +version = "1.3.0" description = "Poetry plugin to export the dependencies to various formats" category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "poetry_plugin_export-1.2.0-py3-none-any.whl", hash = "sha256:109fb43ebfd0e79d8be2e7f9d43ba2ae357c4975a18dfc0cfdd9597dd086790e"}, - {file = "poetry_plugin_export-1.2.0.tar.gz", hash = "sha256:9a1dd42765408931d7831738749022651d43a2968b67c988db1b7a567dfe41ef"}, + {file = "poetry_plugin_export-1.3.0-py3-none-any.whl", hash = "sha256:6e5919bf84afcb08cdd419a03f909f490d8671f00633a3c6df8ba09b0820dc2f"}, + {file = "poetry_plugin_export-1.3.0.tar.gz", hash = "sha256:61ae5ec1db233aba947a48e1ce54c6ff66afd0e1c87195d6bce64c73a5ae658c"}, ] [package.dependencies] -poetry = ">=1.2.2,<2.0.0" +poetry = ">=1.3.0,<2.0.0" poetry-core = ">=1.3.0,<2.0.0" [[package]] name = "pre-commit" -version = "2.20.0" +version = "2.21.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"}, - {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"}, + {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, + {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, ] [package.dependencies] @@ -1001,8 +1161,7 @@ identify = ">=1.0.0" importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} nodeenv = ">=0.11.1" pyyaml = ">=5.1" -toml = "*" -virtualenv = ">=20.0.8" +virtualenv = ">=20.10.0" [[package]] name = "psutil" @@ -1057,46 +1216,51 @@ files = [ [[package]] name = "pyrsistent" -version = "0.19.2" +version = "0.19.3" description = "Persistent/Functional/Immutable data structures" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "pyrsistent-0.19.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d6982b5a0237e1b7d876b60265564648a69b14017f3b5f908c5be2de3f9abb7a"}, - {file = "pyrsistent-0.19.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:187d5730b0507d9285a96fca9716310d572e5464cadd19f22b63a6976254d77a"}, - {file = "pyrsistent-0.19.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:055ab45d5911d7cae397dc418808d8802fb95262751872c841c170b0dbf51eed"}, - {file = "pyrsistent-0.19.2-cp310-cp310-win32.whl", hash = "sha256:456cb30ca8bff00596519f2c53e42c245c09e1a4543945703acd4312949bfd41"}, - {file = "pyrsistent-0.19.2-cp310-cp310-win_amd64.whl", hash = "sha256:b39725209e06759217d1ac5fcdb510e98670af9e37223985f330b611f62e7425"}, - {file = "pyrsistent-0.19.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2aede922a488861de0ad00c7630a6e2d57e8023e4be72d9d7147a9fcd2d30712"}, - {file = "pyrsistent-0.19.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879b4c2f4d41585c42df4d7654ddffff1239dc4065bc88b745f0341828b83e78"}, - {file = "pyrsistent-0.19.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c43bec251bbd10e3cb58ced80609c5c1eb238da9ca78b964aea410fb820d00d6"}, - {file = "pyrsistent-0.19.2-cp37-cp37m-win32.whl", hash = "sha256:d690b18ac4b3e3cab73b0b7aa7dbe65978a172ff94970ff98d82f2031f8971c2"}, - {file = "pyrsistent-0.19.2-cp37-cp37m-win_amd64.whl", hash = "sha256:3ba4134a3ff0fc7ad225b6b457d1309f4698108fb6b35532d015dca8f5abed73"}, - {file = "pyrsistent-0.19.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a178209e2df710e3f142cbd05313ba0c5ebed0a55d78d9945ac7a4e09d923308"}, - {file = "pyrsistent-0.19.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e371b844cec09d8dc424d940e54bba8f67a03ebea20ff7b7b0d56f526c71d584"}, - {file = "pyrsistent-0.19.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111156137b2e71f3a9936baf27cb322e8024dac3dc54ec7fb9f0bcf3249e68bb"}, - {file = "pyrsistent-0.19.2-cp38-cp38-win32.whl", hash = "sha256:e5d8f84d81e3729c3b506657dddfe46e8ba9c330bf1858ee33108f8bb2adb38a"}, - {file = "pyrsistent-0.19.2-cp38-cp38-win_amd64.whl", hash = "sha256:9cd3e9978d12b5d99cbdc727a3022da0430ad007dacf33d0bf554b96427f33ab"}, - {file = "pyrsistent-0.19.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f1258f4e6c42ad0b20f9cfcc3ada5bd6b83374516cd01c0960e3cb75fdca6770"}, - {file = "pyrsistent-0.19.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21455e2b16000440e896ab99e8304617151981ed40c29e9507ef1c2e4314ee95"}, - {file = "pyrsistent-0.19.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd880614c6237243ff53a0539f1cb26987a6dc8ac6e66e0c5a40617296a045e"}, - {file = "pyrsistent-0.19.2-cp39-cp39-win32.whl", hash = "sha256:71d332b0320642b3261e9fee47ab9e65872c2bd90260e5d225dabeed93cbd42b"}, - {file = "pyrsistent-0.19.2-cp39-cp39-win_amd64.whl", hash = "sha256:dec3eac7549869365fe263831f576c8457f6c833937c68542d08fde73457d291"}, - {file = "pyrsistent-0.19.2-py3-none-any.whl", hash = "sha256:ea6b79a02a28550c98b6ca9c35b9f492beaa54d7c5c9e9949555893c8a9234d0"}, - {file = "pyrsistent-0.19.2.tar.gz", hash = "sha256:bfa0351be89c9fcbcb8c9879b826f4353be10f58f8a677efab0c017bf7137ec2"}, + {file = "pyrsistent-0.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a"}, + {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64"}, + {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf"}, + {file = "pyrsistent-0.19.3-cp310-cp310-win32.whl", hash = "sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a"}, + {file = "pyrsistent-0.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da"}, + {file = "pyrsistent-0.19.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9"}, + {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393"}, + {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19"}, + {file = "pyrsistent-0.19.3-cp311-cp311-win32.whl", hash = "sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3"}, + {file = "pyrsistent-0.19.3-cp311-cp311-win_amd64.whl", hash = "sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-win32.whl", hash = "sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b"}, + {file = "pyrsistent-0.19.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8"}, + {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a"}, + {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c"}, + {file = "pyrsistent-0.19.3-cp38-cp38-win32.whl", hash = "sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c"}, + {file = "pyrsistent-0.19.3-cp38-cp38-win_amd64.whl", hash = "sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7"}, + {file = "pyrsistent-0.19.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc"}, + {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2"}, + {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3"}, + {file = "pyrsistent-0.19.3-cp39-cp39-win32.whl", hash = "sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2"}, + {file = "pyrsistent-0.19.3-cp39-cp39-win_amd64.whl", hash = "sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98"}, + {file = "pyrsistent-0.19.3-py3-none-any.whl", hash = "sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64"}, + {file = "pyrsistent-0.19.3.tar.gz", hash = "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440"}, ] [[package]] name = "pytest" -version = "7.2.0" +version = "7.2.1" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, - {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, + {file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"}, + {file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"}, ] [package.dependencies] @@ -1133,14 +1297,14 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "pytest-github-actions-annotate-failures" -version = "0.1.7" +version = "0.1.8" description = "pytest plugin to annotate failed tests with a workflow command for GitHub Actions" category = "dev" optional = false -python-versions = "*" +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ - {file = "pytest-github-actions-annotate-failures-0.1.7.tar.gz", hash = "sha256:c6af8f9d13f1f09ef4c104a30875a4975db131ddbba979c8e48fdc456c8dde1f"}, - {file = "pytest_github_actions_annotate_failures-0.1.7-py2.py3-none-any.whl", hash = "sha256:c4a7346d1d95f731a6b53e9a45f10ca56593978149266dd7526876cce403ea38"}, + {file = "pytest-github-actions-annotate-failures-0.1.8.tar.gz", hash = "sha256:2d6e6cb5f8d0aae4a27a20cc4e20fabd3199a121c57f44bc48fe28e372e0be23"}, + {file = "pytest_github_actions_annotate_failures-0.1.8-py2.py3-none-any.whl", hash = "sha256:6a882ff21672fa79deae8d917eb965a6bde2b25191e7632e1adfc23ffac008ab"}, ] [package.dependencies] @@ -1266,101 +1430,101 @@ files = [ [[package]] name = "rapidfuzz" -version = "2.13.6" +version = "2.13.7" description = "rapid fuzzy string matching" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "rapidfuzz-2.13.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5c4dabaa257efe1eec1cafa59612dbc142e9c81356d345ad5828046cb745304b"}, - {file = "rapidfuzz-2.13.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:34c34bb779238196690fca8c413ab9047481e3c8b10e5a99877d6c3769459764"}, - {file = "rapidfuzz-2.13.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c693b9c7a8b34cae92868c2c0c42d6fd87c3b8abd9586b7e880bb0a01e85659b"}, - {file = "rapidfuzz-2.13.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e7ab10f6cbcf3b2b48f843a52d5dcd0a490b3dc76d5db80489032a853573a2f"}, - {file = "rapidfuzz-2.13.6-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2afc8811fbdb7b8ca43fd34007d96a4e90ea42dd15afd22220c5072e022fca71"}, - {file = "rapidfuzz-2.13.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12ccc0633bc6f95d3cc10acec9ab61aabc0b8c7742f0e829c29afa1cd465b434"}, - {file = "rapidfuzz-2.13.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d1cd7a7f36f1dfa0923d520390b168e09e9ac2ff14e2f33637c9240f20b3c8d"}, - {file = "rapidfuzz-2.13.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e791d829c2251a721aae9d5b99d4efa4a3f7d7fbe32564ddd9631188a241826f"}, - {file = "rapidfuzz-2.13.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee32c175c41f02805b02b04818f3a0b4d7145043ad1b6788b5927e43675491b0"}, - {file = "rapidfuzz-2.13.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bff8930ffcf6fe973b6ac08884e4553e8e7505e67de4047eeda46cb4973eb88d"}, - {file = "rapidfuzz-2.13.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c6de2e6bdf16ae2acf4b49892b19e87fcf09b428b9d2343e58537ca0ae1320ae"}, - {file = "rapidfuzz-2.13.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:63ccbe0231b3f0c31db5532d7e9b15b9d7ee4bd6fba18b1beb68ee7d4f05d1e0"}, - {file = "rapidfuzz-2.13.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fa37236d6a65b757145c4df7ed34b4ada7706ea5683c985c2024532b2e4f8649"}, - {file = "rapidfuzz-2.13.6-cp310-cp310-win32.whl", hash = "sha256:ba8770c9436d08300a56781cacf84526912841927265a8feb61f0bc7547869b9"}, - {file = "rapidfuzz-2.13.6-cp310-cp310-win_amd64.whl", hash = "sha256:b4e6ca42666119350811fdf119aa82ad0539fd21f3daa6533a478bc0df650b3f"}, - {file = "rapidfuzz-2.13.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a94ee9e7cd72111e69bff72e024c08bac53813e8e7f1f5bc8b0795e8f8995191"}, - {file = "rapidfuzz-2.13.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a52177ee3d48c1d27bae3d010c0e96890deabf65435c2654006907946e29d97f"}, - {file = "rapidfuzz-2.13.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6da83fe0ea10fb21988b80ea38949f1d5a968b0591c112c30a9c763d188b8c45"}, - {file = "rapidfuzz-2.13.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42a0c8589f1842486c1d7f2c9a58b2e30a723094e069123984fe0b05efcfa616"}, - {file = "rapidfuzz-2.13.6-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d62fffd06f6dd29873e2d901f5fcc3e2391dbaa3c8f38fe351556288004baa2c"}, - {file = "rapidfuzz-2.13.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49d10f5e330f3fd8871f19ccfafeeaa595f7f8b74adb669bf637c619f2795810"}, - {file = "rapidfuzz-2.13.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4a5cf45f5ced9a02d04a850d71b584b3ab248e72c560d8cea9eb1ac67c51172"}, - {file = "rapidfuzz-2.13.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86767725208a2b76a0d8e338643260765435b9e7241896b5fb66d3dc1dace79b"}, - {file = "rapidfuzz-2.13.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1aa89d2a02af1552370d64aaa971094d458c1cf53dbe3e4b09b68c8811312433"}, - {file = "rapidfuzz-2.13.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ea5282cc6939c7f1b75d2d95906cd6b0f90c9171e08f74ff7ecee806da2e4f61"}, - {file = "rapidfuzz-2.13.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:79812801626291f9ca6abbf89dfb1ca17e8165b5cf8af4656985be30142b758f"}, - {file = "rapidfuzz-2.13.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:1a7a1069a81c1fae5903d4d7ff80de723f3a492635a0e40699491460a5ba84fe"}, - {file = "rapidfuzz-2.13.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:52f2c6ea7c6ebc09b7a09f707273e7c8cb590f89093bc1de9c93ebe7f4ecfd95"}, - {file = "rapidfuzz-2.13.6-cp311-cp311-win32.whl", hash = "sha256:96c93f15b578e0332345a32035836034b66e3a8f10e62b6cca79d9ba25bf0bd2"}, - {file = "rapidfuzz-2.13.6-cp311-cp311-win_amd64.whl", hash = "sha256:ea23efde72c7d9a6170c2fe772a5be9ea1fafcf88cb11d935fbc665d2c48a36c"}, - {file = "rapidfuzz-2.13.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:15bad0c24a08ef7b87138317a1c0c9b54c577ad2c2671a82d89d5f2e35a7b372"}, - {file = "rapidfuzz-2.13.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5721bfa91ccd4d2e48787913a64a084ded44a7e488f17f728bb0b1dc2c7f1acd"}, - {file = "rapidfuzz-2.13.6-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0dfe6bf7b193db6b88c49124c065834a051d3a007da7d752b7cb488473965eb0"}, - {file = "rapidfuzz-2.13.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b101b1eddededbfe31e9eaed63ad64fa099f14ad6bc9850d4a337ffbb0c4566"}, - {file = "rapidfuzz-2.13.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74b498cac37ba66d72f77425cdadb0dd269495b28543bce8f8090f28086faaf5"}, - {file = "rapidfuzz-2.13.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5091b4139849971d9b101fe844d4a4306f1ac314371215db3ce652772811ecbe"}, - {file = "rapidfuzz-2.13.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5378b8711ae9ec8ba4eb5476afcc599e4bdfe73374084544abc9b6f984c013d3"}, - {file = "rapidfuzz-2.13.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ad87d5accb542d19dc755a73cf1b3d332d639743b18d62c694c566229235d71a"}, - {file = "rapidfuzz-2.13.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8e4b8e1c686737a229a53df21486dc040a5b62453a0f6bf3839d92d5f21a0d25"}, - {file = "rapidfuzz-2.13.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5823b99277cf51ff7abb668d02bfa1b97a77e77317a06a0bca431a4a85f3bc26"}, - {file = "rapidfuzz-2.13.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:881f1a3ad1bdab5e907ff0b33a8c0c8f8155ff364018fe82c09f6eddebaf9ba8"}, - {file = "rapidfuzz-2.13.6-cp37-cp37m-win32.whl", hash = "sha256:cda615c49e8bc990972b47cb7788fae8230b07449a38ba99192d74ebd525d5e7"}, - {file = "rapidfuzz-2.13.6-cp37-cp37m-win_amd64.whl", hash = "sha256:b18fcc28123040a867f980f4e41431b0227e1b861b8ea7303c92d3210f2f9561"}, - {file = "rapidfuzz-2.13.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:35a8a0cde5d46f0e81771ef7723c235993ea2f3da53ab91c35b82101437f4b61"}, - {file = "rapidfuzz-2.13.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0d1d22fed68976377a376afa8fdbc0f2353e4b16f3c490574e2ee2b0f90d1d86"}, - {file = "rapidfuzz-2.13.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83174ddf6b692ec762ad51afa097fe8fbb3e28ce72a1e1a835f196dbabdf26d5"}, - {file = "rapidfuzz-2.13.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14ffbe50d2f61d6071d696cf9282d2961596885a4c5a27355bf78b296a66230e"}, - {file = "rapidfuzz-2.13.6-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eae455a63882a95b976180ddb14588d42c78641047bf3f61f445e7be39a85508"}, - {file = "rapidfuzz-2.13.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80f6651db489e9ecd2b3ff9aee966b10cfc8f69591450694b2f362e13a5fe085"}, - {file = "rapidfuzz-2.13.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aee1deaec703a51fdf17d3d349c6b714b43bf56fe8d4c2157ebd985710f0eda"}, - {file = "rapidfuzz-2.13.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fea337f4face310917dbf1e321eb4b1392c02267af6edb0e9bba7fa04cb8711d"}, - {file = "rapidfuzz-2.13.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:23284face05d384e2c273da005e6dee9efebbb5c51d5e924b9114bafbb53d3db"}, - {file = "rapidfuzz-2.13.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:88476cfeaa74e63cfdc8de9f1b28b7a76d951ce663c077a0f03076b9865052c6"}, - {file = "rapidfuzz-2.13.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f162ab51e912d7615b5352ee79f1c1fd009a7f6a20ecc44238e2d594cab8c4cb"}, - {file = "rapidfuzz-2.13.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:cdfed7427c459be870ab13b4b17c254598500074079cec32475a46249d57b18b"}, - {file = "rapidfuzz-2.13.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:09367abf1490bbd7138fb883df9d811b12b2c409b50ae62360e44df84a4d515a"}, - {file = "rapidfuzz-2.13.6-cp38-cp38-win32.whl", hash = "sha256:1ae63571508ea8f8e85616f99ac6c843070d75de4dbefdb87d49fb24b441e2c1"}, - {file = "rapidfuzz-2.13.6-cp38-cp38-win_amd64.whl", hash = "sha256:7e0cd251529781d94703f8448fa8f43a051357c4f75839332b878ed13dccab19"}, - {file = "rapidfuzz-2.13.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2b9ec944bf3bc916a70845e83063926b91709c399041d564a50d68e62c606e74"}, - {file = "rapidfuzz-2.13.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:845c5394cfc7e7522f66aa68e740115e81e86530010107aabfbd611eee17b7d1"}, - {file = "rapidfuzz-2.13.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4414cb19bdaec7ee52bd17cac1b47baf4c1bb3e3e1ad6aead032296fdfa14603"}, - {file = "rapidfuzz-2.13.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:920c332777bac28b82be2a3d9e2c204c2b64783f15ba263c3c2551b4aa389624"}, - {file = "rapidfuzz-2.13.6-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb08f48c11d37c0f6121d862e9e9567d3ba6dc22ff2f3aa6c20c8c5f38d6aa9f"}, - {file = "rapidfuzz-2.13.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65379a26e0a042f3b5935d6178aaed499f434f73affb01893b9890c91d771a7d"}, - {file = "rapidfuzz-2.13.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d0d68bf0c53ff954e03a2da457989e065da52069ca649960f9d6830172fc607"}, - {file = "rapidfuzz-2.13.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:260be139930db7e864e47518853476f92994ec78a83a1236805e527c52bcae55"}, - {file = "rapidfuzz-2.13.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2d25ba295cc3c27c43fe4cbba6010407da70e240c18df979012cb6ebd70b8ab8"}, - {file = "rapidfuzz-2.13.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:16dd88b64a4e11b1a8fc84a9db7a017f7175620a3cf7304dfad59add4ef85c23"}, - {file = "rapidfuzz-2.13.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cb7809423c7146479a1d77d5a74cb4fe1b6dffbb6423c9a1a49f69906552929c"}, - {file = "rapidfuzz-2.13.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:fa0d33447bac6280c7deb02186349546404f387906fb4408186079852545c8de"}, - {file = "rapidfuzz-2.13.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cade0fd49d2b04fd21736e9dc20b10047cd5eb9e842f950c7ebc00531c595145"}, - {file = "rapidfuzz-2.13.6-cp39-cp39-win32.whl", hash = "sha256:ba102a2fb37b1db698016aa7a8b9bfe1c7e610d333a6e69737edc296c1a40b91"}, - {file = "rapidfuzz-2.13.6-cp39-cp39-win_amd64.whl", hash = "sha256:905c0f4304ea168cdc470e99496541dca1fdff084c22acb91a85ecdaa97986bf"}, - {file = "rapidfuzz-2.13.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:db38d0fb8a2f317b34adf0d418d1d0f3ef0eeb3543bfda0058abf34a506992c5"}, - {file = "rapidfuzz-2.13.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a422aee665b096bd5b02883ae1528d3e5e484069925d54e99db7233852fd1de"}, - {file = "rapidfuzz-2.13.6-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5179cbdcb97638d8ad4b2a638f76fc20bdef54c3f32879756bf56ed38f9199ad"}, - {file = "rapidfuzz-2.13.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19d872bb8fc106cfabe5f0e8fc2e23911e336823d4c326f4186f2b0a7f5ec0a4"}, - {file = "rapidfuzz-2.13.6-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:699ad346ca8e462808eb48493f9b28d82447e329bc68f6bea1f1b41615578a03"}, - {file = "rapidfuzz-2.13.6-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f2799e9b7fea1c42f36d1132ba2bf265724161a57f037b085aee91fe851d0ae"}, - {file = "rapidfuzz-2.13.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a19a644f8abf265a30b7787bd16b2845b75f6925c21ef9a1745415ec79f740a"}, - {file = "rapidfuzz-2.13.6-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df794809b916ab868b3fb0fc8ffa14ad8ae9deb0fbcc7f697ea900512024b4c8"}, - {file = "rapidfuzz-2.13.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2c2d05ed5ae8bf7b57366255f85bdea5420ce5ce32c898bf44bfaf55c043ece"}, - {file = "rapidfuzz-2.13.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6569a14ed01dc2ee2f915b97bbbf9290d924461e15f31df9e770dfab9c64b42e"}, - {file = "rapidfuzz-2.13.6-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:24709358f4bec9a5a1cb0417edc8fc8ec8161b86d546b98c6cecc647bce967cf"}, - {file = "rapidfuzz-2.13.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0350caf368aa58b598276e85b5109bb4d3262a7089beddcf45d9e2bdc219415"}, - {file = "rapidfuzz-2.13.6-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7812ad1ff06a570a6e0e5f1aceee673e7dd5db2c304d34aeebac0e5828291be"}, - {file = "rapidfuzz-2.13.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f85b751e0c0a5d5fbe9d327e53446c2cfae0e77b16600ff4854671f4f9237a5"}, - {file = "rapidfuzz-2.13.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e5f5e3eeb6816f98973428582b27a8d709e2e4f240165c59b9bfaf564a4d94c4"}, - {file = "rapidfuzz-2.13.6.tar.gz", hash = "sha256:948445a054d9fb30a93597c325d8836232bd68e61443a88779a57702aa35a007"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b75dd0928ce8e216f88660ab3d5c5ffe990f4dd682fd1709dba29d5dafdde6de"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:24d3fea10680d085fd0a4d76e581bfb2b1074e66e78fd5964d4559e1fcd2a2d4"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8109e0324d21993d5b2d111742bf5958f3516bf8c59f297c5d1cc25a2342eb66"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f705652360d520c2de52bee11100c92f59b3e3daca308ebb150cbc58aecdad"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7496e8779905b02abc0ab4ba2a848e802ab99a6e20756ffc967a0de4900bd3da"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:24eb6b843492bdc63c79ee4b2f104059b7a2201fef17f25177f585d3be03405a"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:467c1505362823a5af12b10234cb1c4771ccf124c00e3fc9a43696512bd52293"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53dcae85956853b787c27c1cb06f18bb450e22cf57a4ad3444cf03b8ff31724a"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46b9b8aa09998bc48dd800854e8d9b74bc534d7922c1d6e1bbf783e7fa6ac29c"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1fbad8fb28d98980f5bff33c7842efef0315d42f0cd59082108482a7e6b61410"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:43fb8cb030f888c3f076d40d428ed5eb4331f5dd6cf1796cfa39c67bf0f0fc1e"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:b6bad92de071cbffa2acd4239c1779f66851b60ffbbda0e4f4e8a2e9b17e7eef"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d00df2e4a81ffa56a6b1ec4d2bc29afdcb7f565e0b8cd3092fece2290c4c7a79"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-win32.whl", hash = "sha256:2c836f0f2d33d4614c3fbaf9a1eb5407c0fe23f8876f47fd15b90f78daa64c34"}, + {file = "rapidfuzz-2.13.7-cp310-cp310-win_amd64.whl", hash = "sha256:c36fd260084bb636b9400bb92016c6bd81fd80e59ed47f2466f85eda1fc9f782"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b34e8c0e492949ecdd5da46a1cfc856a342e2f0389b379b1a45a3cdcd3176a6e"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:875d51b3497439a72e2d76183e1cb5468f3f979ab2ddfc1d1f7dde3b1ecfb42f"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ae33a72336059213996fe4baca4e0e4860913905c2efb7c991eab33b95a98a0a"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5585189b3d90d81ccd62d4f18530d5ac8972021f0aaaa1ffc6af387ff1dce75"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42085d4b154a8232767de8296ac39c8af5bccee6b823b0507de35f51c9cbc2d7"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:585206112c294e335d84de5d5f179c0f932837752d7420e3de21db7fdc476278"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f891b98f8bc6c9d521785816085e9657212621e93f223917fb8e32f318b2957e"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08590905a95ccfa43f4df353dcc5d28c15d70664299c64abcad8721d89adce4f"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b5dd713a1734574c2850c566ac4286594bacbc2d60b9170b795bee4b68656625"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:988f8f6abfba7ee79449f8b50687c174733b079521c3cc121d65ad2d38831846"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b3210869161a864f3831635bb13d24f4708c0aa7208ef5baac1ac4d46e9b4208"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f6fe570e20e293eb50491ae14ddeef71a6a7e5f59d7e791393ffa99b13f1f8c2"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6120f2995f5154057454c5de99d86b4ef3b38397899b5da1265467e8980b2f60"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-win32.whl", hash = "sha256:b20141fa6cee041917801de0bab503447196d372d4c7ee9a03721b0a8edf5337"}, + {file = "rapidfuzz-2.13.7-cp311-cp311-win_amd64.whl", hash = "sha256:ec55a81ac2b0f41b8d6fb29aad16e55417036c7563bad5568686931aa4ff08f7"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7d005e058d86f2a968a8d28ca6f2052fab1f124a39035aa0523261d6baf21e1f"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe59a0c21a032024edb0c8e43f5dee5623fef0b65a1e3c1281836d9ce199af3b"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdfc04f7647c29fb48da7a04082c34cdb16f878d3c6d098d62d5715c0ad3000c"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68a89bb06d5a331511961f4d3fa7606f8e21237467ba9997cae6f67a1c2c2b9e"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:effe182767d102cb65dfbbf74192237dbd22d4191928d59415aa7d7c861d8c88"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25b4cedf2aa19fb7212894ce5f5219010cce611b60350e9a0a4d492122e7b351"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3a9bd02e1679c0fd2ecf69b72d0652dbe2a9844eaf04a36ddf4adfbd70010e95"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5e2b3d020219baa75f82a4e24b7c8adcb598c62f0e54e763c39361a9e5bad510"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:cf62dacb3f9234f3fddd74e178e6d25c68f2067fde765f1d95f87b1381248f58"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:fa263135b892686e11d5b84f6a1892523123a00b7e5882eff4fbdabb38667347"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fa4c598ed77f74ec973247ca776341200b0f93ec3883e34c222907ce72cb92a4"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-win32.whl", hash = "sha256:c2523f8180ebd9796c18d809e9a19075a1060b1a170fde3799e83db940c1b6d5"}, + {file = "rapidfuzz-2.13.7-cp37-cp37m-win_amd64.whl", hash = "sha256:5ada0a14c67452358c1ee52ad14b80517a87b944897aaec3e875279371a9cb96"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ca8a23097c1f50e0fdb4de9e427537ca122a18df2eead06ed39c3a0bef6d9d3a"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9be02162af0376d64b840f2fc8ee3366794fc149f1e06d095a6a1d42447d97c5"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af4f7c3c904ca709493eb66ca9080b44190c38e9ecb3b48b96d38825d5672559"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f50d1227e6e2a0e3ae1fb1c9a2e1c59577d3051af72c7cab2bcc430cb5e18da"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c71d9d512b76f05fa00282227c2ae884abb60e09f08b5ca3132b7e7431ac7f0d"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b52ac2626945cd21a2487aeefed794c14ee31514c8ae69b7599170418211e6f6"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca00fafd2756bc9649bf80f1cf72c647dce38635f0695d7ce804bc0f759aa756"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d248a109699ce9992304e79c1f8735c82cc4c1386cd8e27027329c0549f248a2"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c88adbcb933f6b8612f6c593384bf824e562bb35fc8a0f55fac690ab5b3486e5"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c8601a66fbfc0052bb7860d2eacd303fcde3c14e87fdde409eceff516d659e77"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:27be9c63215d302ede7d654142a2e21f0d34ea6acba512a4ae4cfd52bbaa5b59"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3dcffe1f3cbda0dc32133a2ae2255526561ca594f15f9644384549037b355245"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8450d15f7765482e86ef9be2ad1a05683cd826f59ad236ef7b9fb606464a56aa"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-win32.whl", hash = "sha256:460853983ab88f873173e27cc601c5276d469388e6ad6e08c4fd57b2a86f1064"}, + {file = "rapidfuzz-2.13.7-cp38-cp38-win_amd64.whl", hash = "sha256:424f82c35dbe4f83bdc3b490d7d696a1dc6423b3d911460f5493b7ffae999fd2"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c3fbe449d869ea4d0909fc9d862007fb39a584fb0b73349a6aab336f0d90eaed"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:16080c05a63d6042643ae9b6cfec1aefd3e61cef53d0abe0df3069b9d4b72077"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dbcf5371ea704759fcce772c66a07647751d1f5dbdec7818331c9b31ae996c77"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:114810491efb25464016fd554fdf1e20d390309cecef62587494fc474d4b926f"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99a84ab9ac9a823e7e93b4414f86344052a5f3e23b23aa365cda01393ad895bd"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81642a24798851b118f82884205fc1bd9ff70b655c04018c467824b6ecc1fabc"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3741cb0bf9794783028e8b0cf23dab917fa5e37a6093b94c4c2f805f8e36b9f"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:759a3361711586a29bc753d3d1bdb862983bd9b9f37fbd7f6216c24f7c972554"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1333fb3d603d6b1040e365dca4892ba72c7e896df77a54eae27dc07db90906e3"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:916bc2e6cf492c77ad6deb7bcd088f0ce9c607aaeabc543edeb703e1fbc43e31"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:23524635840500ce6f4d25005c9529a97621689c85d2f727c52eed1782839a6a"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:ebe303cd9839af69dd1f7942acaa80b1ba90bacef2e7ded9347fbed4f1654672"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fe56659ccadbee97908132135de4b875543353351e0c92e736b7c57aee298b5a"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-win32.whl", hash = "sha256:3f11a7eff7bc6301cd6a5d43f309e22a815af07e1f08eeb2182892fca04c86cb"}, + {file = "rapidfuzz-2.13.7-cp39-cp39-win_amd64.whl", hash = "sha256:e8914dad106dacb0775718e54bf15e528055c4e92fb2677842996f2d52da5069"}, + {file = "rapidfuzz-2.13.7-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f7930adf84301797c3f09c94b9c5a9ed90a9e8b8ed19b41d2384937e0f9f5bd"}, + {file = "rapidfuzz-2.13.7-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c31022d9970177f6affc6d5dd757ed22e44a10890212032fabab903fdee3bfe7"}, + {file = "rapidfuzz-2.13.7-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f42b82f268689f429def9ecfb86fa65ceea0eaf3fed408b570fe113311bf5ce7"}, + {file = "rapidfuzz-2.13.7-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b477b43ced896301665183a5e0faec0f5aea2373005648da8bdcb3c4b73f280"}, + {file = "rapidfuzz-2.13.7-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d63def9bbc6b35aef4d76dc740301a4185867e8870cbb8719ec9de672212fca8"}, + {file = "rapidfuzz-2.13.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c66546e30addb04a16cd864f10f5821272a1bfe6462ee5605613b4f1cb6f7b48"}, + {file = "rapidfuzz-2.13.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f799d1d6c33d81e983d3682571cc7d993ae7ff772c19b3aabb767039c33f6d1e"}, + {file = "rapidfuzz-2.13.7-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d82f20c0060ffdaadaf642b88ab0aa52365b56dffae812e188e5bdb998043588"}, + {file = "rapidfuzz-2.13.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:042644133244bfa7b20de635d500eb9f46af7097f3d90b1724f94866f17cb55e"}, + {file = "rapidfuzz-2.13.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75c45dcd595f8178412367e302fd022860ea025dc4a78b197b35428081ed33d5"}, + {file = "rapidfuzz-2.13.7-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3d8b081988d0a49c486e4e845a547565fee7c6e7ad8be57ff29c3d7c14c6894c"}, + {file = "rapidfuzz-2.13.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16ffad751f43ab61001187b3fb4a9447ec2d1aedeff7c5bac86d3b95f9980cc3"}, + {file = "rapidfuzz-2.13.7-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:020858dd89b60ce38811cd6e37875c4c3c8d7fcd8bc20a0ad2ed1f464b34dc4e"}, + {file = "rapidfuzz-2.13.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cda1e2f66bb4ba7261a0f4c2d052d5d909798fca557cbff68f8a79a87d66a18f"}, + {file = "rapidfuzz-2.13.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b6389c50d8d214c9cd11a77f6d501529cb23279a9c9cafe519a3a4b503b5f72a"}, + {file = "rapidfuzz-2.13.7.tar.gz", hash = "sha256:8d3e252d4127c79b4d7c2ae47271636cbaca905c8bb46d80c7930ab906cf4b5c"}, ] [package.extras] @@ -1368,19 +1532,19 @@ full = ["numpy"] [[package]] name = "requests" -version = "2.28.1" +version = "2.28.2" description = "Python HTTP for Humans." category = "main" optional = false python-versions = ">=3.7, <4" files = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, + {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, + {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, ] [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" +charset-normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<1.27" @@ -1421,18 +1585,18 @@ jeepney = ">=0.6" [[package]] name = "setuptools" -version = "65.6.3" +version = "67.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, - {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, + {file = "setuptools-67.0.0-py3-none-any.whl", hash = "sha256:9d790961ba6219e9ff7d9557622d2fe136816a264dd01d5997cfc057d804853d"}, + {file = "setuptools-67.0.0.tar.gz", hash = "sha256:883131c5b6efa70b9101c7ef30b2b7b780a4283d5fc1616383cdf22c83cbefe6"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] @@ -1460,18 +1624,6 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] - [[package]] name = "tomli" version = "2.0.1" @@ -1498,14 +1650,14 @@ files = [ [[package]] name = "trove-classifiers" -version = "2022.12.1" +version = "2023.1.20" description = "Canonical source for classifiers on PyPI (pypi.org)." category = "main" optional = false python-versions = "*" files = [ - {file = "trove-classifiers-2022.12.1.tar.gz", hash = "sha256:8eccd9c075038ef2ec73276e2422d0dbf4d632f9133f029632d0df35374caf77"}, - {file = "trove_classifiers-2022.12.1-py3-none-any.whl", hash = "sha256:20ce6feeadd5793718b2106f0c832f821f29838aec792143e620b0a0a0a810b8"}, + {file = "trove-classifiers-2023.1.20.tar.gz", hash = "sha256:ed3fd4e1d2ea77ca9ed379380582566e6003a54d70a3e5521a9b2a7896609362"}, + {file = "trove_classifiers-2023.1.20-py3-none-any.whl", hash = "sha256:e4fb91e744a1b8a4e416226dbd4c3a58717295f54608e412cee526287d5d14d0"}, ] [[package]] @@ -1544,38 +1696,38 @@ files = [ [[package]] name = "types-html5lib" -version = "1.1.11.10" +version = "1.1.11.11" description = "Typing stubs for html5lib" category = "dev" optional = false python-versions = "*" files = [ - {file = "types-html5lib-1.1.11.10.tar.gz", hash = "sha256:90eeffad04010bf67bcccf77b9cc6cf4783eec13cbd22ebbaaf20f24d5f2ca11"}, - {file = "types_html5lib-1.1.11.10-py3-none-any.whl", hash = "sha256:18c34ce51eac062321b224365b3aee5b5cdc1613872ed8469c9c0c4ccd3e02e9"}, + {file = "types-html5lib-1.1.11.11.tar.gz", hash = "sha256:8cf7fb57dcaf3e612806e9bc25ae366ff7ca71a3418ae829f5b1a9c52cbb4960"}, + {file = "types_html5lib-1.1.11.11-py3-none-any.whl", hash = "sha256:7456a07a4d162bb8c42c2a088c60cca7c63d06cf2c409a8de39536a6cdbbccc2"}, ] [[package]] name = "types-jsonschema" -version = "4.17.0.2" +version = "4.17.0.3" description = "Typing stubs for jsonschema" category = "dev" optional = false python-versions = "*" files = [ - {file = "types-jsonschema-4.17.0.2.tar.gz", hash = "sha256:8b9e1140d4d780f0f19b5cab1b8a3732e8dd5e49dbc1f174cc0b499125ca6f6c"}, - {file = "types_jsonschema-4.17.0.2-py3-none-any.whl", hash = "sha256:8fd2f9aea4da54f9a811baa6963aac10fd680c18baa6237392c079b97d152738"}, + {file = "types-jsonschema-4.17.0.3.tar.gz", hash = "sha256:746aa466ffed9a1acc7bdbd0ac0b5e068f00be2ee008c1d1e14b0944a8c8b24b"}, + {file = "types_jsonschema-4.17.0.3-py3-none-any.whl", hash = "sha256:c8d5b26b7c8da6a48d7fb1ce029b97e0ff6e74db3727efb968c69f39ad013685"}, ] [[package]] name = "types-requests" -version = "2.28.11.5" +version = "2.28.11.8" description = "Typing stubs for requests" category = "dev" optional = false python-versions = "*" files = [ - {file = "types-requests-2.28.11.5.tar.gz", hash = "sha256:a7df37cc6fb6187a84097da951f8e21d335448aa2501a6b0a39cbd1d7ca9ee2a"}, - {file = "types_requests-2.28.11.5-py3-none-any.whl", hash = "sha256:091d4a5a33c1b4f20d8b1b952aa8fa27a6e767c44c3cf65e56580df0b05fd8a9"}, + {file = "types-requests-2.28.11.8.tar.gz", hash = "sha256:e67424525f84adfbeab7268a159d3c633862dafae15c5b19547ce1b55954f0a3"}, + {file = "types_requests-2.28.11.8-py3-none-any.whl", hash = "sha256:61960554baca0008ae7e2db2bd3b322ca9a144d3e80ce270f5fb640817e40994"}, ] [package.dependencies] @@ -1607,14 +1759,14 @@ files = [ [[package]] name = "urllib3" -version = "1.26.13" +version = "1.26.14" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, - {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, + {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, + {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, ] [package.extras] @@ -1764,21 +1916,21 @@ cffi = ">=1.0" [[package]] name = "zipp" -version = "3.11.0" +version = "3.12.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, - {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, + {file = "zipp-3.12.0-py3-none-any.whl", hash = "sha256:9eb0a4c5feab9b08871db0d672745b53450d7f26992fd1e4653aa43345e97b86"}, + {file = "zipp-3.12.0.tar.gz", hash = "sha256:73efd63936398aac78fd92b6f4865190119d6c91b531532e798977ea8dd402eb"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "c1c3d959c9e5ede769d788c0fe4767bdc9a444aabb9ebfcb47c3a393d8a5ef2d" +content-hash = "89dc4e56ed4a5f8713e1b6c74e3b9f6a6c9c5143350908feb39d71101b937f84" diff --git a/pyproject.toml b/pyproject.toml index 13bc4a260ba..c2411adf9b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,8 +47,8 @@ generate-setup-file = false [tool.poetry.dependencies] python = "^3.7" -poetry-core = "1.4.0" -poetry-plugin-export = "^1.2.0" +poetry-core = "1.5.0" +poetry-plugin-export = "^1.3.0" "backports.cached-property" = { version = "^1.0.2", python = "<3.8" } cachecontrol = { version = "^0.12.9", extras = ["filecache"] } cleo = "^2.0.0" @@ -75,9 +75,9 @@ tomlkit = ">=0.11.1,<1.0.0,!=0.11.2,!=0.11.3" trove-classifiers = ">=2022.5.19" # exclude 20.4.5 - 20.4.6 due to https://github.com/pypa/pip/issues/9953 virtualenv = [ - { version = "^20.4.3,!=20.4.5,!=20.4.6", markers = "sys_platform != 'win32' or python_version != '3.9'" }, + { version = "^20.4.3,!=20.4.5,!=20.4.6" }, # see https://github.com/python-poetry/poetry/pull/6950 for details - { version = "^20.4.3,!=20.4.5,!=20.4.6,<20.16.6", markers = "sys_platform == 'win32' and python_version == '3.9'" }, + { version = "<20.16.6", markers = "sys_platform == 'win32' and python_version == '3.9'" }, ] xattr = { version = "^0.10.0", markers = "sys_platform == 'darwin'" } urllib3 = "^1.26.0" @@ -88,7 +88,12 @@ pre-commit = "^2.6" [tool.poetry.group.test.dependencies] # Cachy frozen to test backwards compatibility for `poetry.utils.cache`. cachy = "0.3.0" -deepdiff = "^6.2" +deepdiff = [ + { version = "^6.2" }, + # avoid orjson, which is required by deepdiff 6.2.3, on FreeBSD + # because it requires a rust compiler + { version = "<6.2.3", markers = "platform_system == 'FreeBSD'"}, +] httpretty = "^1.0" pytest = "^7.1" pytest-cov = "^4.0" diff --git a/src/poetry/factory.py b/src/poetry/factory.py index c20f224272c..002fdcf4779 100644 --- a/src/poetry/factory.py +++ b/src/poetry/factory.py @@ -107,7 +107,7 @@ def create_poetry( @classmethod def get_package(cls, name: str, version: str) -> ProjectPackage: - return ProjectPackage(name, version, version) + return ProjectPackage(name, version) @classmethod def configure_sources( From 0b1d869a3ac21b287bac5dc5342249d140f268fc Mon Sep 17 00:00:00 2001 From: Charles Brunet Date: Mon, 30 Jan 2023 13:43:56 -0500 Subject: [PATCH 074/151] Make test locale independent (#7411) --- tests/console/commands/test_run.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/tests/console/commands/test_run.py b/tests/console/commands/test_run.py index 12a4081cf1e..6b5bd95a728 100644 --- a/tests/console/commands/test_run.py +++ b/tests/console/commands/test_run.py @@ -65,26 +65,23 @@ def test_run_keeps_options_passed_before_command( def test_run_has_helpful_error_when_command_not_found( app_tester: ApplicationTester, env: MockEnv, capfd: pytest.CaptureFixture[str] ): + nonexistent_command = "nonexistent-command" env._execute = True - app_tester.execute("run nonexistent-command") + app_tester.execute(f"run {nonexistent_command}") - assert env.executed == [["nonexistent-command"]] + assert env.executed == [[nonexistent_command]] assert app_tester.status_code == 1 if WINDOWS: # On Windows we use a shell to run commands which provides its own error # message when a command is not found that is not captured by the # ApplicationTester but is captured by pytest, and we can access it via capfd. - # The expected string in this assertion assumes Command Prompt (cmd.exe) is the - # shell used. - assert capfd.readouterr().err.splitlines() == [ - ( - "'nonexistent-command' is not recognized as an internal or external" - " command," - ), - "operable program or batch file.", - ] + # The exact error message depends on the system language. Thus, we check only + # for the name of the command. + assert nonexistent_command in capfd.readouterr().err else: - assert app_tester.io.fetch_error() == "Command not found: nonexistent-command\n" + assert ( + app_tester.io.fetch_error() == f"Command not found: {nonexistent_command}\n" + ) @pytest.mark.skipif( From 651d82db3d8d08070b452a8e2cf933aba7e52759 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Wed, 1 Feb 2023 08:49:12 -0800 Subject: [PATCH 075/151] Fail with comprehensible error message if path dependencies do not exist (#6844) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Randy Döring <30527984+radoering@users.noreply.github.com> --- src/poetry/puzzle/provider.py | 8 +++ tests/console/commands/test_install.py | 38 +++++++++++++ tests/console/commands/test_lock.py | 57 +++++++++++++++++++ tests/console/commands/test_show.py | 31 ++++++++++ .../deleted_directory_dependency/poetry.lock | 20 +++++++ .../pyproject.toml | 10 ++++ .../deleted_file_dependency/poetry.lock | 20 +++++++ .../deleted_file_dependency/pyproject.toml | 10 ++++ .../missing_directory_dependency/poetry.lock | 20 +++++++ .../pyproject.toml | 13 +++++ .../missing_file_dependency/poetry.lock | 20 +++++++ .../missing_file_dependency/pyproject.toml | 13 +++++ 12 files changed, 260 insertions(+) create mode 100644 tests/fixtures/deleted_directory_dependency/poetry.lock create mode 100644 tests/fixtures/deleted_directory_dependency/pyproject.toml create mode 100644 tests/fixtures/deleted_file_dependency/poetry.lock create mode 100644 tests/fixtures/deleted_file_dependency/pyproject.toml create mode 100644 tests/fixtures/missing_directory_dependency/poetry.lock create mode 100644 tests/fixtures/missing_directory_dependency/pyproject.toml create mode 100644 tests/fixtures/missing_file_dependency/poetry.lock create mode 100644 tests/fixtures/missing_file_dependency/pyproject.toml diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index 69302a94acb..ed88b57d977 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -50,6 +50,7 @@ from poetry.core.packages.directory_dependency import DirectoryDependency from poetry.core.packages.file_dependency import FileDependency from poetry.core.packages.package import Package + from poetry.core.packages.path_dependency import PathDependency from poetry.core.packages.url_dependency import URLDependency from poetry.core.packages.vcs_dependency import VCSDependency from poetry.core.version.markers import BaseMarker @@ -390,6 +391,7 @@ def get_package_from_vcs( ) def _search_for_file(self, dependency: FileDependency) -> Package: + dependency.validate(raise_error=True) package = self.get_package_from_file(dependency.full_path) self.validate_package_for_dependency(dependency=dependency, package=package) @@ -420,6 +422,7 @@ def get_package_from_file(cls, file_path: Path) -> Package: return package def _search_for_directory(self, dependency: DirectoryDependency) -> Package: + dependency.validate(raise_error=True) package = self.get_package_from_directory(dependency.full_path) self.validate_package_for_dependency(dependency=dependency, package=package) @@ -652,6 +655,11 @@ def complete_package( if locked is not None and locked.package.is_same_package_as(dep): continue self.search_for_direct_origin_dependency(dep) + else: + for dep in _dependencies: + if dep.is_file() or dep.is_directory(): + dep = cast("PathDependency", dep) + dep.validate(raise_error=True) dependencies = self._get_dependencies_with_overrides( _dependencies, dependency_package diff --git a/tests/console/commands/test_install.py b/tests/console/commands/test_install.py index a2bda6a4651..9e4e59d1b5e 100644 --- a/tests/console/commands/test_install.py +++ b/tests/console/commands/test_install.py @@ -14,6 +14,7 @@ from poetry.poetry import Poetry from tests.types import CommandTesterFactory + from tests.types import FixtureDirGetter from tests.types import ProjectFactory @@ -69,6 +70,22 @@ def tester( return command_tester_factory("install") +def _project_factory( + fixture_name: str, + project_factory: ProjectFactory, + fixture_dir: FixtureDirGetter, +) -> Poetry: + source = fixture_dir(fixture_name) + pyproject_content = (source / "pyproject.toml").read_text(encoding="utf-8") + poetry_lock_content = (source / "poetry.lock").read_text(encoding="utf-8") + return project_factory( + name="foobar", + pyproject_content=pyproject_content, + poetry_lock_content=poetry_lock_content, + source=source, + ) + + @pytest.mark.parametrize( ("options", "groups"), [ @@ -291,3 +308,24 @@ def test_install_logs_output_decorated(tester: CommandTester, mocker: MockerFixt ) assert tester.status_code == 0 assert tester.io.fetch_output() == expected + + +@pytest.mark.parametrize("options", ["", "--without dev"]) +@pytest.mark.parametrize( + "project", ["missing_directory_dependency", "missing_file_dependency"] +) +def test_install_path_dependency_does_not_exist( + command_tester_factory: CommandTesterFactory, + project_factory: ProjectFactory, + fixture_dir: FixtureDirGetter, + project: str, + options: str, +): + poetry = _project_factory(project, project_factory, fixture_dir) + poetry.locker.locked(True) + tester = command_tester_factory("install", poetry=poetry) + if options: + tester.execute(options) + else: + with pytest.raises(ValueError, match="does not exist"): + tester.execute(options) diff --git a/tests/console/commands/test_lock.py b/tests/console/commands/test_lock.py index a8ef7c2aac9..506f82a1776 100644 --- a/tests/console/commands/test_lock.py +++ b/tests/console/commands/test_lock.py @@ -204,6 +204,63 @@ def test_lock_no_update_path_dependencies( assert {p.name for p in packages} == {"quix", "sampleproject"} +@pytest.mark.parametrize("update", [True, False]) +@pytest.mark.parametrize( + "project", ["missing_directory_dependency", "missing_file_dependency"] +) +def test_lock_path_dependency_does_not_exist( + command_tester_factory: CommandTesterFactory, + project_factory: ProjectFactory, + fixture_dir: FixtureDirGetter, + project: str, + update: bool, +): + poetry = _project_factory(project, project_factory, fixture_dir) + locker = Locker( + lock=poetry.pyproject.file.path.parent / "poetry.lock", + local_config=poetry.locker._local_config, + ) + poetry.set_locker(locker) + options = "" if update else "--no-update" + + tester = command_tester_factory("lock", poetry=poetry) + if update or "directory" in project: + # directory dependencies are always updated + with pytest.raises(ValueError, match="does not exist"): + tester.execute(options) + else: + tester.execute(options) + + +@pytest.mark.parametrize("update", [True, False]) +@pytest.mark.parametrize( + "project", ["deleted_directory_dependency", "deleted_file_dependency"] +) +def test_lock_path_dependency_deleted_from_pyproject( + command_tester_factory: CommandTesterFactory, + project_factory: ProjectFactory, + fixture_dir: FixtureDirGetter, + project: str, + update: bool, +): + poetry = _project_factory(project, project_factory, fixture_dir) + locker = Locker( + lock=poetry.pyproject.file.path.parent / "poetry.lock", + local_config=poetry.locker._local_config, + ) + poetry.set_locker(locker) + + tester = command_tester_factory("lock", poetry=poetry) + if update: + tester.execute("") + else: + tester.execute("--no-update") + + packages = locker.locked_repository().packages + + assert {p.name for p in packages} == set() + + @pytest.mark.parametrize("is_no_update", [False, True]) def test_lock_with_incompatible_lockfile( command_tester_factory: CommandTesterFactory, diff --git a/tests/console/commands/test_show.py b/tests/console/commands/test_show.py index a927cee5bf3..8f7eadb2d3f 100644 --- a/tests/console/commands/test_show.py +++ b/tests/console/commands/test_show.py @@ -8,6 +8,7 @@ from poetry.core.packages.dependency_group import DependencyGroup from poetry.factory import Factory +from poetry.utils._compat import tomllib from tests.helpers import MOCK_DEFAULT_GIT_REVISION from tests.helpers import get_package @@ -2140,3 +2141,33 @@ def test_url_dependency_is_not_outdated_by_repository_package( # version in the repository. tester.execute("--outdated") assert tester.io.fetch_output() == "" + + +@pytest.mark.parametrize( + ("project_directory", "required_fixtures"), + [ + ( + "deleted_directory_dependency", + [], + ), + ], +) +def test_show_outdated_missing_directory_dependency( + tester: CommandTester, + poetry: Poetry, + installed: Repository, + repo: TestRepository, +): + with (poetry.pyproject.file.path.parent / "poetry.lock").open(mode="rb") as f: + data = tomllib.load(f) + poetry.locker.mock_lock_data(data) + + poetry.package.add_dependency( + Factory.create_dependency( + "missing", + {"path": data["package"][0]["source"]["url"]}, + ) + ) + + with pytest.raises(ValueError, match="does not exist"): + tester.execute("") diff --git a/tests/fixtures/deleted_directory_dependency/poetry.lock b/tests/fixtures/deleted_directory_dependency/poetry.lock new file mode 100644 index 00000000000..de0370101b8 --- /dev/null +++ b/tests/fixtures/deleted_directory_dependency/poetry.lock @@ -0,0 +1,20 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "missing" +version = "1.2.3" +description = "" +category = "main" +optional = false +python-versions = "*" +files = [] +develop = false + +[package.source] +type = "directory" +url = "missing" + +[metadata] +lock-version = "2.0" +python-versions = "*" +content-hash = "bec78476925e4cda6b22e91551ce4337264bdc3394c4f8297ad238f67a436d0e" diff --git a/tests/fixtures/deleted_directory_dependency/pyproject.toml b/tests/fixtures/deleted_directory_dependency/pyproject.toml new file mode 100644 index 00000000000..74151f00969 --- /dev/null +++ b/tests/fixtures/deleted_directory_dependency/pyproject.toml @@ -0,0 +1,10 @@ +[tool.poetry] +name = "project-with-missing-directory-dependency" +version = "1.2.3" +description = "This is a description" +authors = ["Your Name "] +license = "MIT" +packages = [] + +[tool.poetry.dependencies] +python = "*" diff --git a/tests/fixtures/deleted_file_dependency/poetry.lock b/tests/fixtures/deleted_file_dependency/poetry.lock new file mode 100644 index 00000000000..d5bb9f16541 --- /dev/null +++ b/tests/fixtures/deleted_file_dependency/poetry.lock @@ -0,0 +1,20 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "missing" +version = "1.2.3" +description = "" +category = "main" +optional = false +python-versions = "*" +files = [] +develop = false + +[package.source] +type = "file" +url = "missing-0.1.0-py2.py3-none-any.whl" + +[metadata] +lock-version = "2.0" +python-versions = "*" +content-hash = "bec78476925e4cda6b22e91551ce4337264bdc3394c4f8297ad238f67a436d0e" diff --git a/tests/fixtures/deleted_file_dependency/pyproject.toml b/tests/fixtures/deleted_file_dependency/pyproject.toml new file mode 100644 index 00000000000..74151f00969 --- /dev/null +++ b/tests/fixtures/deleted_file_dependency/pyproject.toml @@ -0,0 +1,10 @@ +[tool.poetry] +name = "project-with-missing-directory-dependency" +version = "1.2.3" +description = "This is a description" +authors = ["Your Name "] +license = "MIT" +packages = [] + +[tool.poetry.dependencies] +python = "*" diff --git a/tests/fixtures/missing_directory_dependency/poetry.lock b/tests/fixtures/missing_directory_dependency/poetry.lock new file mode 100644 index 00000000000..671a7060742 --- /dev/null +++ b/tests/fixtures/missing_directory_dependency/poetry.lock @@ -0,0 +1,20 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "missing" +version = "1.2.3" +description = "" +category = "dev" +optional = false +python-versions = "*" +files = [] +develop = false + +[package.source] +type = "directory" +url = "missing" + +[metadata] +lock-version = "2.0" +python-versions = "*" +content-hash = "bec78476925e4cda6b22e91551ce4337264bdc3394c4f8297ad238f67a436d0e" diff --git a/tests/fixtures/missing_directory_dependency/pyproject.toml b/tests/fixtures/missing_directory_dependency/pyproject.toml new file mode 100644 index 00000000000..570ca5debc3 --- /dev/null +++ b/tests/fixtures/missing_directory_dependency/pyproject.toml @@ -0,0 +1,13 @@ +[tool.poetry] +name = "project-with-missing-directory-dependency" +version = "1.2.3" +description = "This is a description" +authors = ["Your Name "] +license = "MIT" +packages = [] + +[tool.poetry.dependencies] +python = "*" + +[tool.poetry.dev-dependencies] +missing = { path = "./missing" } diff --git a/tests/fixtures/missing_file_dependency/poetry.lock b/tests/fixtures/missing_file_dependency/poetry.lock new file mode 100644 index 00000000000..76c3027db45 --- /dev/null +++ b/tests/fixtures/missing_file_dependency/poetry.lock @@ -0,0 +1,20 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "missing" +version = "1.2.3" +description = "" +category = "dev" +optional = false +python-versions = "*" +files = [] +develop = false + +[package.source] +type = "file" +url = "missing-0.1.0-py2.py3-none-any.whl" + +[metadata] +lock-version = "2.0" +python-versions = "*" +content-hash = "bec78476925e4cda6b22e91551ce4337264bdc3394c4f8297ad238f67a436d0e" diff --git a/tests/fixtures/missing_file_dependency/pyproject.toml b/tests/fixtures/missing_file_dependency/pyproject.toml new file mode 100644 index 00000000000..0be727e2104 --- /dev/null +++ b/tests/fixtures/missing_file_dependency/pyproject.toml @@ -0,0 +1,13 @@ +[tool.poetry] +name = "project-with-missing-directory-dependency" +version = "1.2.3" +description = "This is a description" +authors = ["Your Name "] +license = "MIT" +packages = [] + +[tool.poetry.dependencies] +python = "*" + +[tool.poetry.dev-dependencies] +missing = { file = "missing-0.1.0-py2.py3-none-any.whl" } From 98568d493ddf1a935c4688a88a8bc3e990a2b1b5 Mon Sep 17 00:00:00 2001 From: Wagner Macedo Date: Fri, 3 Feb 2023 15:17:27 +0100 Subject: [PATCH 076/151] Add tests for pyproject with multiple readme files (#6678) --- tests/console/commands/test_build.py | 44 +++++++++++++++++ .../with_multiple_readme_files/README-1.rst | 2 + .../with_multiple_readme_files/README-2.rst | 2 + .../my_package/__init__.py | 6 +++ .../with_multiple_readme_files/pyproject.toml | 19 ++++++++ .../masonry/builders/test_editable_builder.py | 47 +++++++++++++++++++ 6 files changed, 120 insertions(+) create mode 100644 tests/console/commands/test_build.py create mode 100644 tests/fixtures/with_multiple_readme_files/README-1.rst create mode 100644 tests/fixtures/with_multiple_readme_files/README-2.rst create mode 100644 tests/fixtures/with_multiple_readme_files/my_package/__init__.py create mode 100644 tests/fixtures/with_multiple_readme_files/pyproject.toml diff --git a/tests/console/commands/test_build.py b/tests/console/commands/test_build.py new file mode 100644 index 00000000000..6bb71aace71 --- /dev/null +++ b/tests/console/commands/test_build.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +import shutil +import tarfile + +from pathlib import Path +from typing import TYPE_CHECKING + +from poetry.factory import Factory + + +if TYPE_CHECKING: + from poetry.utils.env import VirtualEnv + from tests.types import CommandTesterFactory + + +def test_build_with_multiple_readme_files( + tmp_path: Path, tmp_venv: VirtualEnv, command_tester_factory: CommandTesterFactory +): + source_dir = ( + Path(__file__).parent.parent.parent / "fixtures" / "with_multiple_readme_files" + ) + target_dir = tmp_path / "project" + shutil.copytree(str(source_dir), str(target_dir)) + + poetry = Factory().create_poetry(target_dir) + tester = command_tester_factory("build", poetry, environment=tmp_venv) + + tester.execute() + + build_dir = target_dir / "dist" + assert build_dir.exists() + + sdist_file = build_dir / "my_package-0.1.tar.gz" + assert sdist_file.exists() + assert sdist_file.stat().st_size > 0 + + (wheel_file,) = build_dir.glob("my_package-0.1-*.whl") + assert wheel_file.exists() + assert wheel_file.stat().st_size > 0 + + sdist_content = tarfile.open(sdist_file).getnames() + assert "my_package-0.1/README-1.rst" in sdist_content + assert "my_package-0.1/README-2.rst" in sdist_content diff --git a/tests/fixtures/with_multiple_readme_files/README-1.rst b/tests/fixtures/with_multiple_readme_files/README-1.rst new file mode 100644 index 00000000000..265d70d6a73 --- /dev/null +++ b/tests/fixtures/with_multiple_readme_files/README-1.rst @@ -0,0 +1,2 @@ +Single Python +============= diff --git a/tests/fixtures/with_multiple_readme_files/README-2.rst b/tests/fixtures/with_multiple_readme_files/README-2.rst new file mode 100644 index 00000000000..a5693d97336 --- /dev/null +++ b/tests/fixtures/with_multiple_readme_files/README-2.rst @@ -0,0 +1,2 @@ +Changelog +========= diff --git a/tests/fixtures/with_multiple_readme_files/my_package/__init__.py b/tests/fixtures/with_multiple_readme_files/my_package/__init__.py new file mode 100644 index 00000000000..ceb22ae175a --- /dev/null +++ b/tests/fixtures/with_multiple_readme_files/my_package/__init__.py @@ -0,0 +1,6 @@ +"""Example module""" + +from __future__ import annotations + + +__version__ = "0.1" diff --git a/tests/fixtures/with_multiple_readme_files/pyproject.toml b/tests/fixtures/with_multiple_readme_files/pyproject.toml new file mode 100644 index 00000000000..7178a33c579 --- /dev/null +++ b/tests/fixtures/with_multiple_readme_files/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "my-package" +version = "0.1" +description = "Some description." +authors = [ + "Your Name " +] +license = "MIT" + +readme = [ + "README-1.rst", + "README-2.rst" +] + +homepage = "https://python-poetry.org" + + +[tool.poetry.dependencies] +python = "^2.7" diff --git a/tests/masonry/builders/test_editable_builder.py b/tests/masonry/builders/test_editable_builder.py index 020c9cc13ae..529f2d03e22 100644 --- a/tests/masonry/builders/test_editable_builder.py +++ b/tests/masonry/builders/test_editable_builder.py @@ -68,6 +68,15 @@ def extended_without_setup_poetry() -> Poetry: return poetry +@pytest.fixture +def with_multiple_readme_files() -> Poetry: + poetry = Factory().create_poetry( + Path(__file__).parent.parent.parent / "fixtures" / "with_multiple_readme_files" + ) + + return poetry + + @pytest.fixture() def env_manager(simple_poetry: Poetry) -> EnvManager: return EnvManager(simple_poetry) @@ -287,6 +296,44 @@ def test_builder_installs_proper_files_when_packages_configured( assert len(paths) == len(expected) +def test_builder_generates_proper_metadata_when_multiple_readme_files( + with_multiple_readme_files: Poetry, tmp_venv: VirtualEnv +): + builder = EditableBuilder(with_multiple_readme_files, tmp_venv, NullIO()) + + builder.build() + + dist_info = "my_package-0.1.dist-info" + assert tmp_venv.site_packages.exists(dist_info) + + dist_info = tmp_venv.site_packages.find(dist_info)[0] + assert dist_info.joinpath("METADATA").exists() + + metadata = """\ +Metadata-Version: 2.1 +Name: my-package +Version: 0.1 +Summary: Some description. +Home-page: https://python-poetry.org +License: MIT +Author: Your Name +Author-email: you@example.com +Requires-Python: >=2.7,<3.0 +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Description-Content-Type: text/x-rst + +Single Python +============= + +Changelog +========= + +""" + assert dist_info.joinpath("METADATA").read_text(encoding="utf-8") == metadata + + def test_builder_should_execute_build_scripts( mocker: MockerFixture, extended_without_setup_poetry: Poetry, tmp_path: Path ): From f57cdc31c3e08eb48eae839081b196300a0ec2ea Mon Sep 17 00:00:00 2001 From: finswimmer Date: Sat, 26 Nov 2022 09:34:44 +0100 Subject: [PATCH 077/151] feat: turn EnvManager method into staticmethod which detect active python --- src/poetry/utils/env.py | 42 +++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 408d0c0f232..0877a9a17eb 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -20,7 +20,6 @@ from subprocess import CalledProcessError from typing import TYPE_CHECKING from typing import Any -from typing import cast import packaging.tags import tomlkit @@ -521,7 +520,8 @@ def __init__(self, poetry: Poetry, io: None | IO = None) -> None: self._poetry = poetry self._io = io or NullIO() - def _full_python_path(self, python: str) -> str: + @staticmethod + def _full_python_path(python: str) -> str: try: executable = decode( subprocess.check_output( @@ -536,23 +536,23 @@ def _full_python_path(self, python: str) -> str: return executable - def _detect_active_python(self) -> str | None: + @staticmethod + def _detect_active_python(io: None | IO = None) -> str | None: + io = io or NullIO() executable = None try: - self._io.write_error_line( + io.write_error_line( ( "Trying to detect current active python executable as specified in" " the config." ), verbosity=Verbosity.VERBOSE, ) - executable = self._full_python_path("python") - self._io.write_error_line( - f"Found: {executable}", verbosity=Verbosity.VERBOSE - ) + executable = EnvManager._full_python_path("python") + io.write_error_line(f"Found: {executable}", verbosity=Verbosity.VERBOSE) except EnvCommandError: - self._io.write_error_line( + io.write_error_line( ( "Unable to detect the current active python executable. Falling" " back to default." @@ -561,11 +561,16 @@ def _detect_active_python(self) -> str | None: ) return executable - def _get_python_version(self) -> tuple[int, int, int]: - version_info = tuple(sys.version_info[:3]) + @staticmethod + def get_python_version( + precious: int = 3, + prefer_active_python: bool = False, + io: None | IO = None, + ) -> Version: + version = ".".join(str(v) for v in sys.version_info[:precious]) - if self._poetry.config.get("virtualenvs.prefer-active-python"): - executable = self._detect_active_python() + if prefer_active_python: + executable = EnvManager._detect_active_python(io) if executable: python_patch = decode( @@ -577,9 +582,9 @@ def _get_python_version(self) -> tuple[int, int, int]: ).strip() ) - version_info = tuple(int(v) for v in python_patch.split(".")[:3]) + version = ".".join(str(v) for v in python_patch.split(".")[:precious]) - return cast("tuple[int, int, int]", version_info) + return Version.parse(version) def activate(self, python: str) -> Env: venv_path = self._poetry.config.virtualenvs_path @@ -692,7 +697,12 @@ def get(self, reload: bool = False) -> Env: if self._env is not None and not reload: return self._env - python_minor = ".".join([str(v) for v in self._get_python_version()[:2]]) + prefer_active_python = self._poetry.config.get( + "virtualenvs.prefer-active-python" + ) + python_minor = self.get_python_version( + precious=2, prefer_active_python=prefer_active_python, io=self._io + ).to_string() venv_path = self._poetry.config.virtualenvs_path From 8949bfdccb15383b50b33979f53c15ad0d593671 Mon Sep 17 00:00:00 2001 From: finswimmer Date: Sat, 26 Nov 2022 09:39:44 +0100 Subject: [PATCH 078/151] feat: take `virtualenvs.prefer-active-python` into account on `poetry init` --- src/poetry/console/commands/init.py | 17 +++++++---- tests/console/commands/test_init.py | 46 +++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index 92cd249f084..ee068e9ca31 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -1,7 +1,5 @@ from __future__ import annotations -import sys - from pathlib import Path from typing import TYPE_CHECKING from typing import Any @@ -78,8 +76,9 @@ def handle(self) -> int: from poetry.core.pyproject.toml import PyProjectTOML from poetry.core.vcs.git import GitConfig + from poetry.config.config import Config from poetry.layouts import layout - from poetry.utils.env import SystemEnv + from poetry.utils.env import EnvManager project_path = Path.cwd() @@ -161,10 +160,16 @@ def handle(self) -> int: python = self.option("python") if not python: - current_env = SystemEnv(Path(sys.executable)) - default_python = "^" + ".".join( - str(v) for v in current_env.version_info[:2] + config = Config.create() + default_python = ( + "^" + + EnvManager.get_python_version( + precious=2, + prefer_active_python=config.get("virtualenvs.prefer-active-python"), + io=self.io, + ).to_string() ) + question = self.create_question( f"Compatible Python versions [{default_python}]: ", default=default_python, diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py index f78e9a64d17..c4d0171f43a 100644 --- a/tests/console/commands/test_init.py +++ b/tests/console/commands/test_init.py @@ -2,10 +2,12 @@ import os import shutil +import subprocess import sys from pathlib import Path from typing import TYPE_CHECKING +from typing import Any import pytest @@ -26,6 +28,7 @@ from poetry.core.packages.package import Package from pytest_mock import MockerFixture + from poetry.config.config import Config from poetry.poetry import Poetry from tests.helpers import TestRepository from tests.types import FixtureDirGetter @@ -1061,3 +1064,46 @@ def test_package_include( f'python = "^3.10"\n' ) assert expected in tester.io.fetch_output() + + +@pytest.mark.parametrize( + ["prefer_active", "python"], + [ + (True, "1.1"), + (False, f"{sys.version_info[0]}.{sys.version_info[1]}"), + ], +) +def test_respect_prefer_active_on_init( + prefer_active: bool, + python: str, + config: Config, + mocker: MockerFixture, + tester: CommandTester, + source_dir: Path, +): + from poetry.utils.env import GET_PYTHON_VERSION_ONELINER + + orig_check_output = subprocess.check_output + + def mock_check_output(cmd: str, *_: Any, **__: Any) -> str: + if GET_PYTHON_VERSION_ONELINER in cmd: + return "1.1.1" + + return orig_check_output(cmd, *_, **__) + + mocker.patch("subprocess.check_output", side_effect=mock_check_output) + + config.config["virtualenvs"]["prefer-active-python"] = prefer_active + pyproject_file = source_dir / "pyproject.toml" + + tester.execute( + "--author 'Your Name ' --name 'my-package'", + interactive=False, + ) + + expected = f"""\ +[tool.poetry.dependencies] +python = "^{python}" +""" + + assert expected in pyproject_file.read_text() From d9b563b350dafd8eea8b87d3f73681410b3b1c95 Mon Sep 17 00:00:00 2001 From: finswimmer Date: Sat, 26 Nov 2022 09:40:08 +0100 Subject: [PATCH 079/151] feat: take `virtualenvs.prefer-active-python` into account on `poetry new` --- src/poetry/console/commands/init.py | 2 +- src/poetry/console/commands/new.py | 18 ++++++++--- src/poetry/utils/env.py | 8 ++--- tests/console/commands/test_new.py | 50 +++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 10 deletions(-) diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index ee068e9ca31..b1c400e1042 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -164,7 +164,7 @@ def handle(self) -> int: default_python = ( "^" + EnvManager.get_python_version( - precious=2, + precision=2, prefer_active_python=config.get("virtualenvs.prefer-active-python"), io=self.io, ).to_string() diff --git a/src/poetry/console/commands/new.py b/src/poetry/console/commands/new.py index 968a3a2a90b..492a7258a94 100644 --- a/src/poetry/console/commands/new.py +++ b/src/poetry/console/commands/new.py @@ -1,7 +1,5 @@ from __future__ import annotations -import sys - from contextlib import suppress from cleo.helpers import argument @@ -31,8 +29,9 @@ def handle(self) -> int: from poetry.core.vcs.git import GitConfig + from poetry.config.config import Config from poetry.layouts import layout - from poetry.utils.env import SystemEnv + from poetry.utils.env import EnvManager if self.io.input.option("directory"): self.line_error( @@ -71,8 +70,17 @@ def handle(self) -> int: if author_email: author += f" <{author_email}>" - current_env = SystemEnv(Path(sys.executable)) - default_python = "^" + ".".join(str(v) for v in current_env.version_info[:2]) + poetry_config = Config.create() + default_python = ( + "^" + + EnvManager.get_python_version( + precision=2, + prefer_active_python=poetry_config.get( + "virtualenvs.prefer-active-python" + ), + io=self.io, + ).to_string() + ) layout_ = layout_cls( name, diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 0877a9a17eb..7a3584a9e98 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -563,11 +563,11 @@ def _detect_active_python(io: None | IO = None) -> str | None: @staticmethod def get_python_version( - precious: int = 3, + precision: int = 3, prefer_active_python: bool = False, io: None | IO = None, ) -> Version: - version = ".".join(str(v) for v in sys.version_info[:precious]) + version = ".".join(str(v) for v in sys.version_info[:precision]) if prefer_active_python: executable = EnvManager._detect_active_python(io) @@ -582,7 +582,7 @@ def get_python_version( ).strip() ) - version = ".".join(str(v) for v in python_patch.split(".")[:precious]) + version = ".".join(str(v) for v in python_patch.split(".")[:precision]) return Version.parse(version) @@ -701,7 +701,7 @@ def get(self, reload: bool = False) -> Env: "virtualenvs.prefer-active-python" ) python_minor = self.get_python_version( - precious=2, prefer_active_python=prefer_active_python, io=self._io + precision=2, prefer_active_python=prefer_active_python, io=self._io ).to_string() venv_path = self._poetry.config.virtualenvs_path diff --git a/tests/console/commands/test_new.py b/tests/console/commands/test_new.py index 79f09e20f27..58560b01e89 100644 --- a/tests/console/commands/test_new.py +++ b/tests/console/commands/test_new.py @@ -1,7 +1,11 @@ from __future__ import annotations +import subprocess +import sys + from pathlib import Path from typing import TYPE_CHECKING +from typing import Any import pytest @@ -10,7 +14,9 @@ if TYPE_CHECKING: from cleo.testers.command_tester import CommandTester + from pytest_mock import MockerFixture + from poetry.config.config import Config from poetry.poetry import Poetry from tests.types import CommandTesterFactory @@ -170,3 +176,47 @@ def test_command_new_with_readme(fmt: str | None, tester: CommandTester, tmp_dir poetry = verify_project_directory(path, package, package, None) assert poetry.local_config.get("readme") == f"README.{fmt or 'md'}" + + +@pytest.mark.parametrize( + ["prefer_active", "python"], + [ + (True, "1.1"), + (False, f"{sys.version_info[0]}.{sys.version_info[1]}"), + ], +) +def test_respect_prefer_active_on_new( + prefer_active: bool, + python: str, + config: Config, + mocker: MockerFixture, + tester: CommandTester, + tmp_dir: str, +): + from poetry.utils.env import GET_PYTHON_VERSION_ONELINER + + orig_check_output = subprocess.check_output + + def mock_check_output(cmd: str, *_: Any, **__: Any) -> str: + if GET_PYTHON_VERSION_ONELINER in cmd: + return "1.1.1" + + return orig_check_output(cmd, *_, **__) + + mocker.patch("subprocess.check_output", side_effect=mock_check_output) + + config.config["virtualenvs"]["prefer-active-python"] = prefer_active + + package = "package" + path = Path(tmp_dir) / package + options = [path.as_posix()] + tester.execute(" ".join(options)) + + pyproject_file = path / "pyproject.toml" + + expected = f"""\ +[tool.poetry.dependencies] +python = "^{python}" +""" + + assert expected in pyproject_file.read_text() From 55127c893db5bf8ab33b494e0c4adc8c9b09c458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Sat, 20 Aug 2022 01:17:26 +0200 Subject: [PATCH 080/151] Improve sources management --- src/poetry/console/commands/source/add.py | 12 +++--- src/poetry/factory.py | 47 ++++++++++++++--------- tests/console/commands/self/conftest.py | 20 ++++++++++ 3 files changed, 54 insertions(+), 25 deletions(-) diff --git a/src/poetry/console/commands/source/add.py b/src/poetry/console/commands/source/add.py index 2848ce8a3be..f258e404af0 100644 --- a/src/poetry/console/commands/source/add.py +++ b/src/poetry/console/commands/source/add.py @@ -36,7 +36,6 @@ class SourceAddCommand(Command): def handle(self) -> int: from poetry.factory import Factory - from poetry.repositories import RepositoryPool from poetry.utils.source import source_to_table name = self.argument("name") @@ -84,13 +83,14 @@ def handle(self) -> int: self.line(f"Adding source with name {name}.") sources.append(source_to_table(new_source)) + self.poetry.config.merge( + {"sources": {source["name"]: source for source in sources}} + ) + # ensure new source is valid. eg: invalid name etc. - self.poetry._pool = RepositoryPool() try: - Factory.configure_sources( - self.poetry, sources, self.poetry.config, NullIO() - ) - self.poetry.pool.repository(name) + pool = Factory.create_pool(self.poetry.config, NullIO()) + pool.repository(name) except ValueError as e: self.line_error( f"Failed to validate addition of {name}: {e}" diff --git a/src/poetry/factory.py b/src/poetry/factory.py index 002fdcf4779..ff03718b48d 100644 --- a/src/poetry/factory.py +++ b/src/poetry/factory.py @@ -29,10 +29,10 @@ from poetry.core.packages.package import Package from tomlkit.toml_document import TOMLDocument + from poetry.repositories import RepositoryPool from poetry.repositories.legacy_repository import LegacyRepository from poetry.utils.dependency_specification import DependencySpec - logger = logging.getLogger(__name__) @@ -90,13 +90,15 @@ def create_poetry( ) # Configuring sources - self.configure_sources( - poetry, - poetry.local_config.get("source", []), - config, - io, - disable_cache=disable_cache, + config.merge( + { + "sources": { + source["name"]: source + for source in poetry.local_config.get("source", []) + } + } ) + poetry.set_pool(self.create_pool(config, io, disable_cache=disable_cache)) plugin_manager = PluginManager(Plugin.group, disable_plugins=disable_plugins) plugin_manager.load_plugins() @@ -110,23 +112,28 @@ def get_package(cls, name: str, version: str) -> ProjectPackage: return ProjectPackage(name, version) @classmethod - def configure_sources( + def create_pool( cls, - poetry: Poetry, - sources: list[dict[str, str]], config: Config, - io: IO, + io: IO | None = None, disable_cache: bool = False, - ) -> None: + ) -> RepositoryPool: + from poetry.repositories import RepositoryPool + + if io is None: + io = NullIO() + if disable_cache: logger.debug("Disabling source caches") - for source in sources: + pool = RepositoryPool() + + for source in config.get("sources", {}).values(): repository = cls.create_package_source( source, config, disable_cache=disable_cache ) - is_default = bool(source.get("default", False)) - is_secondary = bool(source.get("secondary", False)) + is_default = source.get("default", False) + is_secondary = source.get("secondary", False) if io.is_debug(): message = f"Adding repository {repository.name} ({repository.url})" if is_default: @@ -136,22 +143,24 @@ def configure_sources( io.write_line(message) - poetry.pool.add_repository(repository, is_default, secondary=is_secondary) + pool.add_repository(repository, is_default, secondary=is_secondary) # Put PyPI last to prefer private repositories # unless we have no default source AND no primary sources # (default = false, secondary = false) - if poetry.pool.has_default(): + if pool.has_default(): if io.is_debug(): io.write_line("Deactivating the PyPI repository") else: from poetry.repositories.pypi_repository import PyPiRepository - default = not poetry.pool.has_primary_repositories() - poetry.pool.add_repository( + default = not pool.has_primary_repositories() + pool.add_repository( PyPiRepository(disable_cache=disable_cache), default, not default ) + return pool + @classmethod def create_package_source( cls, source: dict[str, str], auth_config: Config, disable_cache: bool = False diff --git a/tests/console/commands/self/conftest.py b/tests/console/commands/self/conftest.py index e6fa5a052bf..68aedf1ac21 100644 --- a/tests/console/commands/self/conftest.py +++ b/tests/console/commands/self/conftest.py @@ -1,12 +1,14 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing import Callable import pytest from poetry.core.packages.package import Package from poetry.__version__ import __version__ +from poetry.factory import Factory from poetry.repositories import RepositoryPool from poetry.utils.env import EnvManager @@ -14,8 +16,10 @@ if TYPE_CHECKING: import httpretty + from cleo.io.io import IO from pytest_mock import MockerFixture + from poetry.config.config import Config from poetry.repositories.repository import Repository from poetry.utils.env import VirtualEnv from tests.helpers import TestRepository @@ -38,6 +42,20 @@ def pool(repo: TestRepository) -> RepositoryPool: return RepositoryPool([repo]) +def create_pool_factory( + repo: Repository, +) -> Callable[[Config, IO, bool], RepositoryPool]: + def _create_pool( + config: Config, io: IO, disable_cache: bool = False + ) -> RepositoryPool: + pool = RepositoryPool() + pool.add_repository(repo) + + return pool + + return _create_pool + + @pytest.fixture(autouse=True) def setup_mocks( mocker: MockerFixture, @@ -45,6 +63,7 @@ def setup_mocks( installed: Repository, pool: RepositoryPool, http: type[httpretty.httpretty], + repo: Repository, ) -> None: mocker.patch.object(EnvManager, "get_system_env", return_value=tmp_venv) mocker.patch( @@ -59,3 +78,4 @@ def setup_mocks( "poetry.installation.installer.Installer._get_installed", return_value=installed, ) + mocker.patch.object(Factory, "create_pool", side_effect=create_pool_factory(repo)) From 444fe0783bc3e4a539ebcfe860ec21f484466899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Sat, 20 Aug 2022 17:27:10 +0200 Subject: [PATCH 081/151] Add a wheel installer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Randy Döring <30527984+radoering@users.noreply.github.com> --- poetry.lock | 14 ++- pyproject.toml | 1 + src/poetry/installation/wheel_installer.py | 102 +++++++++++++++++++++ src/poetry/utils/env.py | 13 ++- tests/conftest.py | 4 +- tests/installation/test_executor.py | 3 +- tests/installation/test_wheel_installer.py | 61 ++++++++++++ tests/utils/test_env.py | 9 +- 8 files changed, 199 insertions(+), 8 deletions(-) create mode 100644 src/poetry/installation/wheel_installer.py create mode 100644 tests/installation/test_wheel_installer.py diff --git a/poetry.lock b/poetry.lock index def850e4f87..8039a6cd566 100644 --- a/poetry.lock +++ b/poetry.lock @@ -702,6 +702,18 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "installer" +version = "0.6.0" +description = "A library for installing Python wheels." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "installer-0.6.0-py3-none-any.whl", hash = "sha256:ae7c62d1d6158b5c096419102ad0d01fdccebf857e784cee57f94165635fe038"}, + {file = "installer-0.6.0.tar.gz", hash = "sha256:f3bd36cd261b440a88a1190b1becca0578fee90b4b62decc796932fdd5ae8839"}, +] + [[package]] name = "jaraco-classes" version = "3.2.3" @@ -1933,4 +1945,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "89dc4e56ed4a5f8713e1b6c74e3b9f6a6c9c5143350908feb39d71101b937f84" +content-hash = "47c828b086d203975e5d1e6deecc4821d51d09eb8dea20141f08fdd1cb280e03" diff --git a/pyproject.toml b/pyproject.toml index c2411adf9b5..ac90ba82196 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,7 @@ dulwich = "^0.21.2" filelock = "^3.8.0" html5lib = "^1.0" importlib-metadata = { version = ">=4.4", python = "<3.10" } +installer = "^0.6.0" jsonschema = "^4.10.0" keyring = "^23.9.0" lockfile = "^0.12.2" diff --git a/src/poetry/installation/wheel_installer.py b/src/poetry/installation/wheel_installer.py new file mode 100644 index 00000000000..78cecb39c98 --- /dev/null +++ b/src/poetry/installation/wheel_installer.py @@ -0,0 +1,102 @@ +from __future__ import annotations + +import os +import platform +import sys + +from pathlib import Path +from typing import TYPE_CHECKING + +from installer import install +from installer.destinations import SchemeDictionaryDestination +from installer.sources import WheelFile + +from poetry.__version__ import __version__ +from poetry.utils._compat import WINDOWS + + +if TYPE_CHECKING: + from typing import BinaryIO + + from installer.records import RecordEntry + from installer.scripts import LauncherKind + from installer.utils import Scheme + + from poetry.utils.env import Env + + +class WheelDestination(SchemeDictionaryDestination): + """ """ + + def write_to_fs( + self, + scheme: Scheme, + path: Path | str, + stream: BinaryIO, + is_executable: bool, + ) -> RecordEntry: + from installer.records import Hash + from installer.records import RecordEntry + from installer.utils import copyfileobj_with_hashing + from installer.utils import make_file_executable + + target_path = Path(self.scheme_dict[scheme]) / path + if target_path.exists(): + # Contrary to the base library we don't raise an error + # here since it can break namespace packages (like Poetry's) + pass + + parent_folder = target_path.parent + if not parent_folder.exists(): + # Due to the parallel installation it can happen + # that two threads try to create the directory. + os.makedirs(parent_folder, exist_ok=True) + + with open(target_path, "wb") as f: + hash_, size = copyfileobj_with_hashing(stream, f, self.hash_algorithm) + + if is_executable: + make_file_executable(target_path) + + return RecordEntry(str(path), Hash(self.hash_algorithm, hash_), size) + + def for_source(self, source: WheelFile) -> WheelDestination: + scheme_dict = self.scheme_dict.copy() + + scheme_dict["headers"] = str(Path(scheme_dict["headers"]) / source.distribution) + + return self.__class__( + scheme_dict, interpreter=self.interpreter, script_kind=self.script_kind + ) + + +class WheelInstaller: + def __init__(self, env: Env) -> None: + self._env = env + + script_kind: LauncherKind + if not WINDOWS: + script_kind = "posix" + else: + if platform.uname()[4].startswith("arm"): + script_kind = "win-arm64" if sys.maxsize > 2**32 else "win-arm" + else: + script_kind = "win-amd64" if sys.maxsize > 2**32 else "win-ia32" + + schemes = self._env.paths + schemes["headers"] = schemes["include"] + + self._destination = WheelDestination( + schemes, interpreter=self._env.python, script_kind=script_kind + ) + + def install(self, wheel: Path) -> None: + with WheelFile.open(Path(wheel.as_posix())) as source: + install( + source=source, + destination=self._destination.for_source(source), + # Additional metadata that is generated by the installation tool. + additional_metadata={ + "INSTALLER": f"Poetry {__version__}".encode(), + }, + ) diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 7a3584a9e98..70f4b9b4772 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -59,7 +59,6 @@ from poetry.poetry import Poetry - GET_SYS_TAGS = f""" import importlib.util import json @@ -84,7 +83,6 @@ ) """ - GET_ENVIRONMENT_INFO = """\ import json import os @@ -155,7 +153,6 @@ def _version_nodot(version): print(json.dumps(env)) """ - GET_BASE_PREFIX = """\ import sys @@ -1438,6 +1435,16 @@ def paths(self) -> dict[str, str]: if self._paths is None: self._paths = self.get_paths() + if self.is_venv(): + # We copy pip's logic here for the `include` path + self._paths["include"] = str( + self.path.joinpath( + "include", + "site", + f"python{self.version_info[0]}.{self.version_info[1]}", + ) + ) + return self._paths @property diff --git a/tests/conftest.py b/tests/conftest.py index 86e5dfd2b36..12f89a9f9b2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -279,12 +279,12 @@ def http() -> Iterator[type[httpretty.httpretty]]: yield httpretty -@pytest.fixture +@pytest.fixture(scope="session") def fixture_base() -> Path: return Path(__file__).parent / "fixtures" -@pytest.fixture +@pytest.fixture(scope="session") def fixture_dir(fixture_base: Path) -> FixtureDirGetter: def _fixture_dir(name: str) -> Path: return fixture_base / name diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 6b8de09546b..4e1d5d6f0bc 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -222,8 +222,9 @@ def test_execute_prints_warning_for_yanked_package( "(black-21.11b0-py3-none-any.whl) is yanked. Reason for being yanked: " "Broken regex dependency. Use 21.11b1 instead." ) + output = io.fetch_output() error = io.fetch_error() - assert return_code == 0 + assert return_code == 0, f"\noutput: {output}\nerror: {error}\n" assert "pytest" not in error if has_warning: assert expected in error diff --git a/tests/installation/test_wheel_installer.py b/tests/installation/test_wheel_installer.py new file mode 100644 index 00000000000..42dcec76292 --- /dev/null +++ b/tests/installation/test_wheel_installer.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +import re + +from pathlib import Path +from typing import TYPE_CHECKING + +import pytest + +from poetry.core.constraints.version import parse_constraint + +from poetry.installation.wheel_installer import WheelInstaller +from poetry.utils.env import MockEnv + + +if TYPE_CHECKING: + from _pytest.tmpdir import TempPathFactory + + from tests.types import FixtureDirGetter + + +@pytest.fixture +def env(tmp_path: Path) -> MockEnv: + return MockEnv(path=tmp_path) + + +@pytest.fixture(scope="module") +def demo_wheel(fixture_dir: FixtureDirGetter) -> Path: + return fixture_dir("distributions/demo-0.1.0-py2.py3-none-any.whl") + + +@pytest.fixture(scope="module") +def default_installation(tmp_path_factory: TempPathFactory, demo_wheel: Path) -> Path: + env = MockEnv(path=tmp_path_factory.mktemp("default_install")) + installer = WheelInstaller(env) + installer.install(demo_wheel) + return Path(env.paths["purelib"]) + + +def test_default_installation_source_dir_content(default_installation: Path) -> None: + source_dir = default_installation / "demo" + assert source_dir.exists() + assert (source_dir / "__init__.py").exists() + + +def test_default_installation_dist_info_dir_content(default_installation: Path) -> None: + dist_info_dir = default_installation / "demo-0.1.0.dist-info" + assert dist_info_dir.exists() + assert (dist_info_dir / "INSTALLER").exists() + assert (dist_info_dir / "METADATA").exists() + assert (dist_info_dir / "RECORD").exists() + assert (dist_info_dir / "WHEEL").exists() + + +def test_installer_file_contains_valid_version(default_installation: Path) -> None: + installer_file = default_installation / "demo-0.1.0.dist-info" / "INSTALLER" + with open(installer_file) as f: + installer_content = f.read() + match = re.match(r"Poetry (?P.*)", installer_content) + assert match + parse_constraint(match.group("version")) # must not raise an error diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 23cbbda3e7f..1d9f6c3ba63 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -1155,7 +1155,8 @@ def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable( del os.environ["VIRTUAL_ENV"] version = Version.from_parts(*sys.version_info[:3]) - poetry.package.python_versions = f"~{version.major}.{version.minor-1}.0" + poetry.package.python_versions = f"~{version.major}.{version.minor - 1}.0" + venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) check_output = mocker.patch( "subprocess.check_output", @@ -1266,6 +1267,7 @@ def test_system_env_has_correct_paths(): assert paths.get("platlib") is not None assert paths.get("scripts") is not None assert env.site_packages.path == Path(paths["purelib"]) + assert paths["include"] is not None @pytest.mark.parametrize( @@ -1287,6 +1289,11 @@ def test_venv_has_correct_paths(tmp_venv: VirtualEnv): assert paths.get("platlib") is not None assert paths.get("scripts") is not None assert tmp_venv.site_packages.path == Path(paths["purelib"]) + assert paths["include"] == str( + tmp_venv.path.joinpath( + f"include/site/python{tmp_venv.version_info[0]}.{tmp_venv.version_info[1]}" + ) + ) def test_env_system_packages(tmp_path: Path, poetry: Poetry): From ab087af13bc91f3223de7d2d60a1996d892ba0ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Sat, 20 Aug 2022 16:56:52 +0200 Subject: [PATCH 082/151] Add a wheel builder --- poetry.lock | 44 ++++- pyproject.toml | 24 +-- src/poetry/config/config.py | 8 +- src/poetry/installation/chef.py | 151 ++++++++++++++++++ src/poetry/installation/executor.py | 84 ++++++++-- tests/console/commands/self/test_update.py | 16 ++ tests/console/commands/test_add.py | 2 + tests/console/commands/test_config.py | 3 + .../fixtures/distributions/demo-0.1.0.tar.gz | Bin 961 -> 1003 bytes .../demo-0.1.2-py2.py3-none-any.whl | Bin 0 -> 1537 bytes .../fixtures/extended_with_no_setup/README.md | 2 + .../fixtures/extended_with_no_setup/build.py | 32 ++++ .../extended/__init__.py | 0 .../extended/extended.c | 58 +++++++ .../extended_with_no_setup/pyproject.toml | 26 +++ .../with-same-version-url-dependencies.test | 2 +- tests/installation/test_chef.py | 64 +++++++- tests/installation/test_executor.py | 150 ++++++++++++----- .../dists/black-21.11b0-py3-none-any.whl | Bin 2822 -> 14949 bytes .../dists/cleo-1.0.0a5-py3-none-any.whl | Bin 0 -> 78701 bytes .../dists/poetry_core-1.5.0-py3-none-any.whl | Bin 0 -> 464992 bytes .../dists/pytest-3.5.0-py2.py3-none-any.whl | Bin 0 -> 3691 bytes .../dists/pytest-3.5.1-py2.py3-none-any.whl | Bin 0 -> 3693 bytes .../fixtures/pypi.org/json/cleo/1.0.0a5.json | 90 +++++++++++ .../pypi.org/json/importlib-metadata.json | 27 ++++ .../json/importlib-metadata/1.7.0.json | 140 ++++++++++++++++ .../fixtures/pypi.org/json/poetry-core.json | 27 ++++ .../pypi.org/json/poetry-core/1.5.0.json | 96 +++++++++++ .../fixtures/pypi.org/json/zipp.json | 27 ++++ .../fixtures/pypi.org/json/zipp/3.5.0.json | 142 ++++++++++++++++ tests/repositories/test_pypi_repository.py | 11 ++ tests/utils/test_helpers.py | 24 +-- 32 files changed, 1173 insertions(+), 77 deletions(-) create mode 100644 tests/fixtures/distributions/demo-0.1.2-py2.py3-none-any.whl create mode 100644 tests/fixtures/extended_with_no_setup/README.md create mode 100644 tests/fixtures/extended_with_no_setup/build.py create mode 100644 tests/fixtures/extended_with_no_setup/extended/__init__.py create mode 100644 tests/fixtures/extended_with_no_setup/extended/extended.c create mode 100644 tests/fixtures/extended_with_no_setup/pyproject.toml create mode 100644 tests/repositories/fixtures/pypi.org/dists/cleo-1.0.0a5-py3-none-any.whl create mode 100644 tests/repositories/fixtures/pypi.org/dists/poetry_core-1.5.0-py3-none-any.whl create mode 100644 tests/repositories/fixtures/pypi.org/dists/pytest-3.5.0-py2.py3-none-any.whl create mode 100644 tests/repositories/fixtures/pypi.org/dists/pytest-3.5.1-py2.py3-none-any.whl create mode 100644 tests/repositories/fixtures/pypi.org/json/cleo/1.0.0a5.json create mode 100644 tests/repositories/fixtures/pypi.org/json/importlib-metadata.json create mode 100644 tests/repositories/fixtures/pypi.org/json/importlib-metadata/1.7.0.json create mode 100644 tests/repositories/fixtures/pypi.org/json/poetry-core.json create mode 100644 tests/repositories/fixtures/pypi.org/json/poetry-core/1.5.0.json create mode 100644 tests/repositories/fixtures/pypi.org/json/zipp.json create mode 100644 tests/repositories/fixtures/pypi.org/json/zipp/3.5.0.json diff --git a/poetry.lock b/poetry.lock index 8039a6cd566..2bae3a2b2a2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -31,6 +31,31 @@ files = [ {file = "backports.cached_property-1.0.2-py3-none-any.whl", hash = "sha256:baeb28e1cd619a3c9ab8941431fe34e8490861fb998c6c4590693d50171db0cc"}, ] +[[package]] +name = "build" +version = "0.10.0" +description = "A simple, correct Python build frontend" +category = "main" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "build-0.10.0-py3-none-any.whl", hash = "sha256:af266720050a66c893a6096a2f410989eeac74ff9a68ba194b3f6473e8e26171"}, + {file = "build-0.10.0.tar.gz", hash = "sha256:d5b71264afdb5951d6704482aac78de887c80691c52b88a9ad195983ca2c9269"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "os_name == \"nt\""} +importlib-metadata = {version = ">=0.22", markers = "python_version < \"3.8\""} +packaging = ">=19.0" +pyproject_hooks = "*" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["furo (>=2021.08.31)", "sphinx (>=4.0,<5.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)"] +test = ["filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "toml (>=0.10.0)", "wheel (>=0.36.0)"] +typing = ["importlib-metadata (>=5.1)", "mypy (==0.991)", "tomli", "typing-extensions (>=3.7.4.3)"] +virtualenv = ["virtualenv (>=20.0.35)"] + [[package]] name = "cachecontrol" version = "0.12.11" @@ -288,7 +313,7 @@ rapidfuzz = ">=2.2.0,<3.0.0" name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "dev" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -1226,6 +1251,21 @@ files = [ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] +[[package]] +name = "pyproject-hooks" +version = "1.0.0" +description = "Wrappers to call pyproject.toml-based build backend hooks." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyproject_hooks-1.0.0-py3-none-any.whl", hash = "sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8"}, + {file = "pyproject_hooks-1.0.0.tar.gz", hash = "sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5"}, +] + +[package.dependencies] +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + [[package]] name = "pyrsistent" version = "0.19.3" @@ -1945,4 +1985,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "47c828b086d203975e5d1e6deecc4821d51d09eb8dea20141f08fdd1cb280e03" +content-hash = "077b0abda1e0e5ffc7286738a63098606e3bd4650f7bb2569efe16cc106e055e" diff --git a/pyproject.toml b/pyproject.toml index ac90ba82196..c45c01a2e67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,7 @@ python = "^3.7" poetry-core = "1.5.0" poetry-plugin-export = "^1.3.0" "backports.cached-property" = { version = "^1.0.2", python = "<3.8" } +build = "^0.10.0" cachecontrol = { version = "^0.12.9", extras = ["filecache"] } cleo = "^2.0.0" crashtest = "^0.4.1" @@ -66,6 +67,7 @@ packaging = ">=20.4" pexpect = "^4.7.0" pkginfo = "^1.9.4" platformdirs = "^2.5.2" +pyproject-hooks = "^1.0.0" requests = "^2.18" requests-toolbelt = ">=0.9.1,<0.11.0" shellingham = "^1.5" @@ -167,22 +169,22 @@ enable_error_code = [ # warning. [[tool.mypy.overrides]] module = [ - 'poetry.console.commands.self.show.plugins', - 'poetry.plugins.plugin_manager', - 'poetry.repositories.installed_repository', - 'poetry.utils.env', + 'poetry.console.commands.self.show.plugins', + 'poetry.plugins.plugin_manager', + 'poetry.repositories.installed_repository', + 'poetry.utils.env', ] warn_unused_ignores = false [[tool.mypy.overrides]] module = [ - 'cachecontrol.*', - 'lockfile.*', - 'pexpect.*', - 'requests_toolbelt.*', - 'shellingham.*', - 'virtualenv.*', - 'xattr.*', + 'cachecontrol.*', + 'lockfile.*', + 'pexpect.*', + 'requests_toolbelt.*', + 'shellingham.*', + 'virtualenv.*', + 'xattr.*', ] ignore_missing_imports = true diff --git a/src/poetry/config/config.py b/src/poetry/config/config.py index 7cbcba3fc10..98d37fcb117 100644 --- a/src/poetry/config/config.py +++ b/src/poetry/config/config.py @@ -100,7 +100,6 @@ def validator(cls, policy: str) -> bool: logger = logging.getLogger(__name__) - _default_config: Config | None = None @@ -124,7 +123,11 @@ class Config: "prefer-active-python": False, "prompt": "{project_name}-py{python_version}", }, - "experimental": {"new-installer": True, "system-git-client": False}, + "experimental": { + "new-installer": True, + "system-git-client": False, + "wheel-installer": True, + }, "installer": {"parallel": True, "max-workers": None, "no-binary": None}, } @@ -267,6 +270,7 @@ def _get_normalizer(name: str) -> Callable[[str], Any]: "virtualenvs.options.prefer-active-python", "experimental.new-installer", "experimental.system-git-client", + "experimental.wheel-installer", "installer.parallel", }: return boolean_normalizer diff --git a/src/poetry/installation/chef.py b/src/poetry/installation/chef.py index fa3eb267f00..ec294b52d3a 100644 --- a/src/poetry/installation/chef.py +++ b/src/poetry/installation/chef.py @@ -2,28 +2,179 @@ import hashlib import json +import tarfile +import tempfile +import zipfile +from contextlib import redirect_stdout +from io import StringIO from pathlib import Path from typing import TYPE_CHECKING +from typing import Callable +from typing import Collection + +from build import BuildBackendException +from build import ProjectBuilder +from build.env import IsolatedEnv as BaseIsolatedEnv +from poetry.core.utils.helpers import temporary_directory +from pyproject_hooks import quiet_subprocess_runner # type: ignore[import] from poetry.installation.chooser import InvalidWheelName from poetry.installation.chooser import Wheel +from poetry.utils.env import ephemeral_environment if TYPE_CHECKING: + from contextlib import AbstractContextManager + from poetry.core.packages.utils.link import Link from poetry.config.config import Config from poetry.utils.env import Env +class ChefError(Exception): + ... + + +class ChefBuildError(ChefError): + ... + + +class IsolatedEnv(BaseIsolatedEnv): + def __init__(self, env: Env, config: Config) -> None: + self._env = env + self._config = config + + @property + def executable(self) -> str: + return str(self._env.python) + + @property + def scripts_dir(self) -> str: + return str(self._env._bin_dir) + + def install(self, requirements: Collection[str]) -> None: + from cleo.io.null_io import NullIO + from poetry.core.packages.dependency import Dependency + from poetry.core.packages.project_package import ProjectPackage + + from poetry.config.config import Config + from poetry.factory import Factory + from poetry.installation.installer import Installer + from poetry.packages.locker import Locker + from poetry.repositories.installed_repository import InstalledRepository + + # We build Poetry dependencies from the requirements + package = ProjectPackage("__root__", "0.0.0") + package.python_versions = ".".join(str(v) for v in self._env.version_info[:3]) + for requirement in requirements: + dependency = Dependency.create_from_pep_508(requirement) + package.add_dependency(dependency) + + pool = Factory.create_pool(self._config) + installer = Installer( + NullIO(), + self._env, + package, + Locker(self._env.path.joinpath("poetry.lock"), {}), + pool, + Config.create(), + InstalledRepository.load(self._env), + ) + installer.update(True) + installer.run() + + class Chef: def __init__(self, config: Config, env: Env) -> None: + self._config = config self._env = env self._cache_dir = ( Path(config.get("cache-dir")).expanduser().joinpath("artifacts") ) + def prepare(self, archive: Path, output_dir: Path | None = None) -> Path: + if not self._should_prepare(archive): + return archive + + if archive.is_dir(): + tmp_dir = tempfile.mkdtemp(prefix="poetry-chef-") + + return self._prepare(archive, Path(tmp_dir)) + + return self._prepare_sdist(archive, destination=output_dir) + + def _prepare(self, directory: Path, destination: Path) -> Path: + with ephemeral_environment(self._env.python) as venv: + env = IsolatedEnv(venv, self._config) + builder = ProjectBuilder( + directory, + python_executable=env.executable, + scripts_dir=env.scripts_dir, + runner=quiet_subprocess_runner, + ) + env.install(builder.build_system_requires) + env.install( + builder.build_system_requires | builder.get_requires_for_build("wheel") + ) + + stdout = StringIO() + with redirect_stdout(stdout): + try: + return Path( + builder.build( + "wheel", + destination.as_posix(), + ) + ) + except BuildBackendException as e: + raise ChefBuildError(str(e)) + + def _prepare_sdist(self, archive: Path, destination: Path | None = None) -> Path: + from poetry.core.packages.utils.link import Link + + suffix = archive.suffix + context: Callable[ + [str], AbstractContextManager[zipfile.ZipFile | tarfile.TarFile] + ] + if suffix == ".zip": + context = zipfile.ZipFile + else: + context = tarfile.open + + with temporary_directory() as tmp_dir: + with context(archive.as_posix()) as archive_archive: + archive_archive.extractall(tmp_dir) + + archive_dir = Path(tmp_dir) + + elements = list(archive_dir.glob("*")) + + if len(elements) == 1 and elements[0].is_dir(): + sdist_dir = elements[0] + else: + sdist_dir = archive_dir / archive.name.rstrip(suffix) + if not sdist_dir.is_dir(): + sdist_dir = archive_dir + + if destination is None: + destination = self.get_cache_directory_for_link(Link(archive.as_uri())) + + destination.mkdir(parents=True, exist_ok=True) + + return self._prepare( + sdist_dir, + destination, + ) + + def _should_prepare(self, archive: Path) -> bool: + return archive.is_dir() or not self._is_wheel(archive) + + @classmethod + def _is_wheel(cls, archive: Path) -> bool: + return archive.suffix == ".whl" + def get_cached_archive_for_link(self, link: Link) -> Path | None: archives = self.get_cached_archives_for_link(link) if not archives: diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index dd72729a66d..f84148a9e1a 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -16,13 +16,13 @@ from cleo.io.null_io import NullIO from poetry.core.packages.utils.link import Link -from poetry.core.pyproject.toml import PyProjectTOML from poetry.installation.chef import Chef from poetry.installation.chooser import Chooser from poetry.installation.operations import Install from poetry.installation.operations import Uninstall from poetry.installation.operations import Update +from poetry.installation.wheel_installer import WheelInstaller from poetry.utils._compat import decode from poetry.utils.authenticator import Authenticator from poetry.utils.env import EnvCommandError @@ -60,6 +60,8 @@ def __init__( self._dry_run = False self._enabled = True self._verbose = False + self._wheel_installer = WheelInstaller(self._env) + self._use_wheel_installer = config.get("experimental.wheel-installer", True) if parallel is None: parallel = config.get("installer.parallel", True) @@ -471,18 +473,21 @@ def _execute_uninstall(self, operation: Uninstall) -> int: message = f" • {op_msg}: Removing..." self._write(operation, message) - return self._remove(operation) + return self._remove(operation.package) def _install(self, operation: Install | Update) -> int: package = operation.package if package.source_type == "directory": + if not self._use_wheel_installer: + return self._install_directory_without_wheel_installer(operation) + return self._install_directory(operation) if package.source_type == "git": return self._install_git(operation) if package.source_type == "file": - archive = self._prepare_file(operation) + archive = self._prepare_archive(operation) elif package.source_type == "url": assert package.source_url is not None archive = self._download_link(operation, Link(package.source_url)) @@ -495,14 +500,18 @@ def _install(self, operation: Install | Update) -> int: " Installing..." ) self._write(operation, message) - return self.pip_install(archive, upgrade=operation.job_type == "update") + + if not self._use_wheel_installer: + return self.pip_install(archive, upgrade=operation.job_type == "update") + + self._wheel_installer.install(archive) + + return 0 def _update(self, operation: Install | Update) -> int: return self._install(operation) - def _remove(self, operation: Uninstall) -> int: - package = operation.package - + def _remove(self, package: Package) -> int: # If we have a VCS package, remove its source directory if package.source_type == "git": src_dir = self._env.path / "src" / package.name @@ -517,7 +526,7 @@ def _remove(self, operation: Uninstall) -> int: raise - def _prepare_file(self, operation: Install | Update) -> Path: + def _prepare_archive(self, operation: Install | Update) -> Path: package = operation.package operation_message = self.get_operation_message(operation) @@ -532,9 +541,54 @@ def _prepare_file(self, operation: Install | Update) -> Path: if not Path(package.source_url).is_absolute() and package.root_dir: archive = package.root_dir / archive - return archive + return self._chef.prepare(archive) def _install_directory(self, operation: Install | Update) -> int: + package = operation.package + operation_message = self.get_operation_message(operation) + + message = ( + f" • {operation_message}:" + " Building..." + ) + self._write(operation, message) + + assert package.source_url is not None + if package.root_dir: + req = package.root_dir / package.source_url + else: + req = Path(package.source_url).resolve(strict=False) + + if package.source_subdirectory: + req /= package.source_subdirectory + + if package.develop: + # Editable installations are currently not supported + # for PEP-517 build systems so we defer to pip. + # TODO: Remove this workaround once either PEP-660 or PEP-662 is accepted + return self.pip_install(req, editable=True) + + archive = self._prepare_archive(operation) + + try: + if operation.job_type == "update": + # Uninstall first + # TODO: Make an uninstaller and find a way to rollback in case + # the new package can't be installed + assert isinstance(operation, Update) + self._remove(operation.initial_package) + + self._wheel_installer.install(archive) + finally: + archive.unlink() + + return 0 + + def _install_directory_without_wheel_installer( + self, operation: Install | Update + ) -> int: + from poetry.core.pyproject.toml import PyProjectTOML + from poetry.factory import Factory package = operation.package @@ -644,6 +698,7 @@ def _download(self, operation: Install | Update) -> Path: def _download_link(self, operation: Install | Update, link: Link) -> Path: package = operation.package + output_dir = self._chef.get_cache_directory_for_link(link) archive = self._chef.get_cached_archive_for_link(link) if archive is None: # No cached distributions was found, so we download and prepare it @@ -659,7 +714,16 @@ def _download_link(self, operation: Install | Update, link: Link) -> Path: raise - if package.files: + if archive.suffix != ".whl": + message = ( + f" • {self.get_operation_message(operation)}:" + " Preparing..." + ) + self._write(operation, message) + + archive = self._chef.prepare(archive, output_dir=output_dir) + + if package.files and archive.name in {f["file"] for f in package.files}: archive_hash = self._validate_archive_hash(archive, package) self._hashes[package.name] = archive_hash diff --git a/tests/console/commands/self/test_update.py b/tests/console/commands/self/test_update.py index 9cc7e523d23..09c3a21b501 100644 --- a/tests/console/commands/self/test_update.py +++ b/tests/console/commands/self/test_update.py @@ -10,10 +10,13 @@ from poetry.__version__ import __version__ from poetry.factory import Factory +from poetry.installation.executor import Executor +from poetry.installation.wheel_installer import WheelInstaller if TYPE_CHECKING: from cleo.testers.command_tester import CommandTester + from pytest_mock import MockerFixture from tests.helpers import TestRepository from tests.types import CommandTesterFactory @@ -21,6 +24,19 @@ FIXTURES = Path(__file__).parent.joinpath("fixtures") +@pytest.fixture() +def setup(mocker: MockerFixture, fixture_dir: Path): + mocker.patch.object( + Executor, + "_download", + return_value=fixture_dir("distributions").joinpath( + "demo-0.1.2-py2.py3-none-any.whl" + ), + ) + + mocker.patch.object(WheelInstaller, "install") + + @pytest.fixture() def tester(command_tester_factory: CommandTesterFactory) -> CommandTester: return command_tester_factory("self update") diff --git a/tests/console/commands/test_add.py b/tests/console/commands/test_add.py index 9bc238aeb7a..a654e835579 100644 --- a/tests/console/commands/test_add.py +++ b/tests/console/commands/test_add.py @@ -787,6 +787,7 @@ def test_add_constraint_with_platform( ): platform = sys.platform env._platform = platform + env._marker_env = None cachy2 = get_package("cachy", "0.2.0") @@ -1824,6 +1825,7 @@ def test_add_constraint_with_platform_old_installer( ): platform = sys.platform env._platform = platform + env._marker_env = None cachy2 = get_package("cachy", "0.2.0") diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index 052b726c2fd..547ae31fa6f 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -53,6 +53,7 @@ def test_list_displays_default_value_if_not_set( expected = f"""cache-dir = {cache_dir} experimental.new-installer = true experimental.system-git-client = false +experimental.wheel-installer = true installer.max-workers = null installer.no-binary = null installer.parallel = true @@ -82,6 +83,7 @@ def test_list_displays_set_get_setting( expected = f"""cache-dir = {cache_dir} experimental.new-installer = true experimental.system-git-client = false +experimental.wheel-installer = true installer.max-workers = null installer.no-binary = null installer.parallel = true @@ -135,6 +137,7 @@ def test_list_displays_set_get_local_setting( expected = f"""cache-dir = {cache_dir} experimental.new-installer = true experimental.system-git-client = false +experimental.wheel-installer = true installer.max-workers = null installer.no-binary = null installer.parallel = true diff --git a/tests/fixtures/distributions/demo-0.1.0.tar.gz b/tests/fixtures/distributions/demo-0.1.0.tar.gz index 133b64421f86ad26448e0b0f27be33df6808c14c..37349b770e5a082866c033457affc12da95e0ce3 100644 GIT binary patch literal 1003 zcmV)^L4m12aJUF46-VM_7$P_0 z8BCKnI8I?y@j-kCKgPC`w3%tEG*V9`!0RQ>_VV^+@4ez|Xu%!N^;3!?Vf;>3@<#oV7q5wtx&2!M{~V~(E#O)ZJQ_@t(nq?U}9kMMxD-9FOyFUICB z{;K&-l3;rL!ypJv{9gjc@lR7$GMT1swI3V$WT*fyqUiDX2a6Lw483q3Eg%md{__w_ z*c)^F9QdEdKTWTxs2MNQ6fd!@Gr>kseeCM1P9*& z(0_ob|ChjbRFVym#QCw6fdmJxkIqR!6RdIr-P(1Mptns?5V21%`id9SsmKn9=e(ro z(?DT;tnYGYL0F#P+mrr;w*4;S=m+{+!vw9Ji#>2*y0c~A#;5qb zW$1eDtZn#xBj_8UJ42u4q^{YPQJDNf@SPwONyI(dmJfpWIjd!Yvz+peFjf6{xx^MwtYc0^ z%T1Fv1%7q5^xepfULP|gFY+rUhmK%=%Y2)j6$AaR_Lbnj(oDMQ=*I?5z=-~Pap1%K zKUB7PyolBPF9(3*AZuSURz=kkS}1+zi$@ zvraMBYa=U}L~u|}ekJ?*yj;$&P-&PSKwLs>ZVgfw;(ky;e}LevOEsz zzoxROp7X!2$4LGAs@T0dsQX_W1*ZOA0`FsI=HQHP;3kmZMsA%Cl?81DFYpFF#EoQm zt@8}CqT)hgohJxYkc@C^P@PQ*1+lYMUxhJ%?Ujdi6UIJLUfdVSufq4S4N3Pg`53 z2+G|4_;&Vz%&ETKobz&*9`>o5Z`a#Oq2k{cLi>P|>Wi>qJ|vGlw-Nc_1pR@7wQnBw z=rh0T)vq1n_}qbYZ0eYY=MKC>LqDU|=%G9N$rpCmAVjGR)2rER7_Qe-<;%dpz`(%3 Zz`(%3z`(%3z`$Sx{{Vc=>LCCq002sR2!Q|q literal 961 zcmV;y13vs8iwFpsLE~Ej|72xtZ!It`F)lDJbYXG;?N{Ay+cp^WHJ^ghF4P(kBhj*) zXmik^>(*gg8ni%fGEZP_#^WN18bxI>((Qfr9DA{Tq~yeQ(jgvF@25OBCJ#UUisyVZ z*(du?PT1vh&SqToJ_y1V@B0nIL3~p-d=$k)+QXN<9iXW-Q!xEQZ}L9^dVmWp3OLwZOCG)j_$qMpP59y4CZAS4k9_f2s>%S0)3mXT`lp!!eZ`2G5)$%g(1NtE<( z==A@=k!2TL)k2n8hR4{qH^Fzx|7bv5{&)Gm&Hr(f&@iGQIT(gA2Ca7cuhi8CiZiR=smw_Sd4e`*7 z*Ha}b-LW2Gjs8ih8Y-vWt3UsE!)h&fiJzHMopStn%{%G|Zz&?=kvBnTbzU;#6)$JJ zoC;n~P_=+D?fAb_GQw});vWYj_K=Bb@}@8?N8^aZhMeYH0&%sn*1orgrClS-p{`ZK ze?#UP$+zOEV#C;0;r@+Kqe1mM(%@+v2rZfCt-wv;5cTzvjBB z9`fCPPyI)fh8y~)bQC)M?*gCVpda9=oQZOt;zpkZ2ZljsN)~t~YTam&*JLUc$D*jD z(%9Y;geo>YXLD|}{{uSp%|-Gj*K{^xn%Sl+-%s(W-}fgVEm6X8=Xs|1FO5*wotM(? zWNpT2b+-)cMgrB@!8+CXlRfN%FfC_1m4sq+maz^XXtHb$6V{q#|{K**Hjr7|>v@FnNVLmJG0 zJw^zkE(9dJ;F-w*DTefWi-Yug^YOlf2SbWYCjP9=8+Z)wT?p|uW3C_bkGh=ycr&8Q j@v8&D{GQ7Y4h{|u4h{|u4h{|ue+%yb(`BL(04M+eDE0Bh diff --git a/tests/fixtures/distributions/demo-0.1.2-py2.py3-none-any.whl b/tests/fixtures/distributions/demo-0.1.2-py2.py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..a01175c144ef549ae2f4e407eb436e3a1270106d GIT binary patch literal 1537 zcmWIWW@Zs#W&nbgx+_5-8U|Q_jFi;eeEk5NN<^VbbPeKF$iQ%niGe{Dmlj{w5Jwls5XY$#?|L0}5NLh>UGrF6rlI%bFlKGdE-{vD z9kH)fvwR*WnMHqhf1~_UX5WnH+g>ZecUsT+Ja=Z}PW5(GtDbo$BUVZ?a`s2|b+JrY zV)FYlyUjDs+0{!gtYO^{y;dVCQS`v_KYH~QCvS#`UAZx>@af&vW=zlS$8&G-3t|?| zmlfO6trVn@%{Z;>r)qAoo@w=xa(&*L8Qb`BEE4Ca+Ss-(&N;!pXTRvjIF)L{C$(j< zi?2sx@&z08XzS6F9 zI9P4|xdjcZv+C~!ZJ$@JyjD2i$`btsGs_goFHD!LQ^KxH+4qgB?|9@H_nML-4~w<0 z%wOJWKJ-uSHzO<_I2c^BZU+6Y{*Zeb7?|9^fR)A-s6npI{y{FeXS|D=3>X+L>`nI+ z@c6moMbj(=zJ(%PEO$~wxLj6d`bOC=FOS*rS@yx!`1?htc3NntlcBIyxSsEWbwz_``EVG?liw(qI1|RYN3fpyPsdA z3rB6s#LzE?x(lxMYZRZ>OIhW5_1ee7uFUownPst;_vvSzQu*-rd7@Jn1H0=7i-*sq zx9!%scS_^9{*1Gx>mPsI?6k_J?f>&Ejm)=eF08n2`$6Y(<*dn-vyvIq%t2wm1x~IE z42m8OQ+e1UfPM#IDO_O??&0d{6P}Tpnxh+*T2!2wpJ%0DsAs_Co|>0hlvt8qWTjA$ zpITB>sQ^sHMtWvkLHYS5x}L?l0i{K$IhjdT3MECQsazq6=|F{*MtTL6#=3d=d8xXI zd6ir!?qO)+DtsvhbO#6vK;5GsAD@|*SrQ)))EFOM25~`ryn?NQIw;o|r~}PlWU^<* zl?hdV=74}e!+%E*4bGBW3<@AN10#b3L&IFV(%Y%m8DTU-kICuHnb(2I1-G%H49LbR z15E-Mi;;~1gk8B;o8Q&a`ui!L>udrnUgmn##tAQBv`cg~vfb#$a^m1eiv#3|D$XwroAHXv1n5IQ5~}bz}EWIhTu!3@{qIU1(_uVb_vI pdtmwprvjvug<^w*V8it1D}ZSOO2chnWdkJ|HXzIZy0;mej{sQf-3I^w literal 0 HcmV?d00001 diff --git a/tests/fixtures/extended_with_no_setup/README.md b/tests/fixtures/extended_with_no_setup/README.md new file mode 100644 index 00000000000..a7508bd515e --- /dev/null +++ b/tests/fixtures/extended_with_no_setup/README.md @@ -0,0 +1,2 @@ +Module 1 +======== diff --git a/tests/fixtures/extended_with_no_setup/build.py b/tests/fixtures/extended_with_no_setup/build.py new file mode 100644 index 00000000000..3ef8cfae13a --- /dev/null +++ b/tests/fixtures/extended_with_no_setup/build.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +import os +import shutil + +from distutils.command.build_ext import build_ext +from distutils.core import Distribution +from distutils.core import Extension + + +extensions = [Extension("extended.extended", ["extended/extended.c"])] + + +def build(): + distribution = Distribution({"name": "extended", "ext_modules": extensions}) + distribution.package_dir = "extended" + + cmd = build_ext(distribution) + cmd.ensure_finalized() + cmd.run() + + # Copy built extensions back to the project + for output in cmd.get_outputs(): + relative_extension = os.path.relpath(output, cmd.build_lib) + shutil.copyfile(output, relative_extension) + mode = os.stat(relative_extension).st_mode + mode |= (mode & 0o444) >> 2 + os.chmod(relative_extension, mode) + + +if __name__ == "__main__": + build() diff --git a/tests/fixtures/extended_with_no_setup/extended/__init__.py b/tests/fixtures/extended_with_no_setup/extended/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/fixtures/extended_with_no_setup/extended/extended.c b/tests/fixtures/extended_with_no_setup/extended/extended.c new file mode 100644 index 00000000000..25a028eb11e --- /dev/null +++ b/tests/fixtures/extended_with_no_setup/extended/extended.c @@ -0,0 +1,58 @@ +#include + + +static PyObject *hello(PyObject *self) { + return PyUnicode_FromString("Hello"); +} + + +static PyMethodDef module_methods[] = { + { + "hello", + (PyCFunction) hello, + NULL, + PyDoc_STR("Say hello.") + }, + {NULL} +}; + +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "extended", + NULL, + -1, + module_methods, + NULL, + NULL, + NULL, + NULL, +}; +#endif + +PyMODINIT_FUNC +#if PY_MAJOR_VERSION >= 3 +PyInit_extended(void) +#else +init_extended(void) +#endif +{ + PyObject *module; + +#if PY_MAJOR_VERSION >= 3 + module = PyModule_Create(&moduledef); +#else + module = Py_InitModule3("extended", module_methods, NULL); +#endif + + if (module == NULL) +#if PY_MAJOR_VERSION >= 3 + return NULL; +#else + return; +#endif + +#if PY_MAJOR_VERSION >= 3 + return module; +#endif +} diff --git a/tests/fixtures/extended_with_no_setup/pyproject.toml b/tests/fixtures/extended_with_no_setup/pyproject.toml new file mode 100644 index 00000000000..779fb1bd9dc --- /dev/null +++ b/tests/fixtures/extended_with_no_setup/pyproject.toml @@ -0,0 +1,26 @@ +[tool.poetry] +name = "extended" +version = "0.1" +description = "Some description." +authors = [ + "Sébastien Eustace " +] +license = "MIT" + +readme = "README.md" + +homepage = "https://python-poetry.org/" + +include = [ + # C extensions must be included in the wheel distributions + {path = "extended/*.so", format = "wheel"}, + {path = "extended/*.pyd", format = "wheel"}, +] + +[tool.poetry.build] +script = "build.py" +generate-setup-file = false + +[build-system] +requires = ["poetry-core>=1.5.0"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/installation/fixtures/with-same-version-url-dependencies.test b/tests/installation/fixtures/with-same-version-url-dependencies.test index bc509936da6..52a3690e887 100644 --- a/tests/installation/fixtures/with-same-version-url-dependencies.test +++ b/tests/installation/fixtures/with-same-version-url-dependencies.test @@ -28,7 +28,7 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "demo-0.1.0.tar.gz", hash = "sha256:72e8531e49038c5f9c4a837b088bfcb8011f4a9f76335c8f0654df6ac539b3d6"} + {file = "demo-0.1.0.tar.gz", hash = "sha256:9fa123ad707a5c6c944743bf3e11a0e80d86cb518d3cf25320866ca3ef43e2ad"} ] [package.source] type = "url" diff --git a/tests/installation/test_chef.py b/tests/installation/test_chef.py index 6af8f682eea..bd730880f6b 100644 --- a/tests/installation/test_chef.py +++ b/tests/installation/test_chef.py @@ -8,8 +8,12 @@ from packaging.tags import Tag from poetry.core.packages.utils.link import Link +from poetry.factory import Factory from poetry.installation.chef import Chef +from poetry.repositories import RepositoryPool +from poetry.utils.env import EnvManager from poetry.utils.env import MockEnv +from tests.repositories.test_pypi_repository import MockRepository if TYPE_CHECKING: @@ -18,6 +22,20 @@ from tests.conftest import Config +@pytest.fixture() +def pool() -> RepositoryPool: + pool = RepositoryPool() + + pool.add_repository(MockRepository()) + + return pool + + +@pytest.fixture(autouse=True) +def setup(mocker: MockerFixture, pool: RepositoryPool) -> None: + mocker.patch.object(Factory, "create_pool", return_value=pool) + + @pytest.mark.parametrize( ("link", "cached"), [ @@ -82,7 +100,7 @@ def test_get_cached_archives_for_link(config: Config, mocker: MockerFixture): ) assert archives - assert set(archives) == {Path(path) for path in distributions.glob("demo-0.1.0*")} + assert set(archives) == set(distributions.glob("demo-0.1.*")) def test_get_cache_directory_for_link(config: Config, config_cache_dir: Path): @@ -103,3 +121,47 @@ def test_get_cache_directory_for_link(config: Config, config_cache_dir: Path): ) assert directory == expected + + +def test_prepare_sdist(config: Config, config_cache_dir: Path) -> None: + chef = Chef(config, EnvManager.get_system_env()) + + archive = ( + Path(__file__) + .parent.parent.joinpath("fixtures/distributions/demo-0.1.0.tar.gz") + .resolve() + ) + + destination = chef.get_cache_directory_for_link(Link(archive.as_uri())) + + wheel = chef.prepare(archive) + + assert wheel.parent == destination + assert wheel.name == "demo-0.1.0-py3-none-any.whl" + + +def test_prepare_directory(config: Config, config_cache_dir: Path): + chef = Chef(config, EnvManager.get_system_env()) + + archive = Path(__file__).parent.parent.joinpath("fixtures/simple_project").resolve() + + wheel = chef.prepare(archive) + + assert wheel.name == "simple_project-1.2.3-py2.py3-none-any.whl" + + +def test_prepare_directory_with_extensions( + config: Config, config_cache_dir: Path +) -> None: + env = EnvManager.get_system_env() + chef = Chef(config, env) + + archive = ( + Path(__file__) + .parent.parent.joinpath("fixtures/extended_with_no_setup") + .resolve() + ) + + wheel = chef.prepare(archive) + + assert wheel.name == f"extended-0.1-{env.supported_tags[0]}.whl" diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 4e1d5d6f0bc..0be2fb8ae78 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -8,6 +8,7 @@ from pathlib import Path from typing import TYPE_CHECKING from typing import Any +from urllib.parse import urlparse import pytest @@ -16,11 +17,13 @@ from poetry.core.packages.package import Package from poetry.core.packages.utils.link import Link +from poetry.installation.chef import Chef as BaseChef from poetry.installation.executor import Executor from poetry.installation.operations import Install from poetry.installation.operations import Uninstall from poetry.installation.operations import Update -from poetry.repositories.repository_pool import RepositoryPool +from poetry.installation.wheel_installer import WheelInstaller +from poetry.repositories.pool import RepositoryPool from poetry.utils.env import MockEnv from tests.repositories.test_pypi_repository import MockRepository @@ -37,6 +40,29 @@ from tests.types import FixtureDirGetter +class Chef(BaseChef): + _directory_wheel = None + _sdist_wheel = None + + def set_directory_wheel(self, wheel: Path) -> None: + self._directory_wheel = wheel + + def set_sdist_wheel(self, wheel: Path) -> None: + self._sdist_wheel = wheel + + def _prepare_sdist(self, archive: Path, destination: Path | None = None) -> Path: + if self._sdist_wheel is not None: + return self._sdist_wheel + + return super()._prepare_sdist(archive) + + def _prepare(self, directory: Path, destination: Path) -> Path: + if self._directory_wheel is not None: + return self._directory_wheel + + return super()._prepare(directory, destination) + + @pytest.fixture def env(tmp_dir: str) -> MockEnv: path = Path(tmp_dir) / ".venv" @@ -85,12 +111,18 @@ def mock_file_downloads(http: type[httpretty.httpretty]) -> None: def callback( request: HTTPrettyRequest, uri: str, headers: dict[str, Any] ) -> list[int | dict[str, Any] | str]: + name = Path(urlparse(uri).path).name + fixture = Path(__file__).parent.parent.joinpath( - "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl" + "repositories/fixtures/pypi.org/dists/" + name ) - with fixture.open("rb") as f: - return [200, headers, f.read()] + if not fixture.exists(): + fixture = Path(__file__).parent.parent.joinpath( + "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl" + ) + + return [200, headers, fixture.read_bytes()] http.register_uri( http.GET, @@ -99,6 +131,20 @@ def callback( ) +@pytest.fixture() +def wheel(tmp_dir: Path) -> Path: + shutil.copyfile( + Path(__file__) + .parent.parent.joinpath( + "fixtures/distributions/demo-0.1.2-py2.py3-none-any.whl" + ) + .as_posix(), + Path(tmp_dir).joinpath("demo-0.1.2-py2.py3-none-any.whl").as_posix(), + ) + + return Path(tmp_dir).joinpath("demo-0.1.2-py2.py3-none-any.whl") + + def test_execute_executes_a_batch_of_operations( mocker: MockerFixture, config: Config, @@ -107,12 +153,18 @@ def test_execute_executes_a_batch_of_operations( tmp_dir: str, mock_file_downloads: None, env: MockEnv, + wheel: Path, ): - pip_install = mocker.patch("poetry.installation.executor.pip_install") + wheel_install = mocker.patch.object(WheelInstaller, "install") config.merge({"cache-dir": tmp_dir}) + chef = Chef(config, env) + chef.set_directory_wheel(wheel) + chef.set_sdist_wheel(wheel) + executor = Executor(env, pool, config, io) + executor._chef = chef file_package = Package( "demo", @@ -171,11 +223,10 @@ def test_execute_executes_a_batch_of_operations( expected = set(expected.splitlines()) output = set(io.fetch_output().splitlines()) assert output == expected - assert len(env.executed) == 1 + assert wheel_install.call_count == 4 + # One pip uninstall and one pip editable install + assert len(env.executed) == 2 assert return_code == 0 - assert pip_install.call_count == 5 - assert pip_install.call_args.kwargs.get("upgrade", False) - assert pip_install.call_args.kwargs.get("editable", False) @pytest.mark.parametrize( @@ -290,7 +341,6 @@ def test_execute_should_show_errors( def test_execute_works_with_ansi_output( - mocker: MockerFixture, config: Config, pool: RepositoryPool, io_decorated: BufferedIO, @@ -302,24 +352,19 @@ def test_execute_works_with_ansi_output( executor = Executor(env, pool, config, io_decorated) - install_output = ( - "some string that does not contain a keyb0ard !nterrupt or cance11ed by u$er" - ) - mocker.patch.object(env, "_run", return_value=install_output) return_code = executor.execute( [ - Install(Package("pytest", "3.5.1")), + Install(Package("cleo", "1.0.0a5")), ] ) - env._run.assert_called_once() # fmt: off expected = [ "\x1b[39;1mPackage operations\x1b[39;22m: \x1b[34m1\x1b[39m install, \x1b[34m0\x1b[39m updates, \x1b[34m0\x1b[39m removals", # noqa: E501 - "\x1b[34;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mpytest\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m3.5.1\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mPending...\x1b[39m", # noqa: E501 - "\x1b[34;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mpytest\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m3.5.1\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mDownloading...\x1b[39m", # noqa: E501 - "\x1b[34;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mpytest\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m3.5.1\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mInstalling...\x1b[39m", # noqa: E501 - "\x1b[32;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mpytest\x1b[39m\x1b[39m (\x1b[39m\x1b[32m3.5.1\x1b[39m\x1b[39m)\x1b[39m", # finished # noqa: E501 + "\x1b[34;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mcleo\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m1.0.0a5\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mPending...\x1b[39m", # noqa: E501 + "\x1b[34;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mcleo\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m1.0.0a5\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mDownloading...\x1b[39m", # noqa: E501 + "\x1b[34;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mcleo\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m1.0.0a5\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mInstalling...\x1b[39m", # noqa: E501 + "\x1b[32;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mcleo\x1b[39m\x1b[39m (\x1b[39m\x1b[32m1.0.0a5\x1b[39m\x1b[39m)\x1b[39m", # finished # noqa: E501 ] # fmt: on @@ -344,21 +389,16 @@ def test_execute_works_with_no_ansi_output( executor = Executor(env, pool, config, io_not_decorated) - install_output = ( - "some string that does not contain a keyb0ard !nterrupt or cance11ed by u$er" - ) - mocker.patch.object(env, "_run", return_value=install_output) return_code = executor.execute( [ - Install(Package("pytest", "3.5.1")), + Install(Package("cleo", "1.0.0a5")), ] ) - env._run.assert_called_once() expected = """ Package operations: 1 install, 0 updates, 0 removals - • Installing pytest (3.5.1) + • Installing cleo (1.0.0a5) """ expected = set(expected.splitlines()) output = set(io_not_decorated.fetch_output().splitlines()) @@ -540,14 +580,26 @@ def test_executor_should_write_pep610_url_references_for_files( def test_executor_should_write_pep610_url_references_for_directories( - tmp_venv: VirtualEnv, pool: RepositoryPool, config: Config, io: BufferedIO + tmp_venv: VirtualEnv, + pool: RepositoryPool, + config: Config, + io: BufferedIO, + wheel: Path, ): - url = Path(__file__).parent.parent.joinpath("fixtures/simple_project").resolve() + url = ( + Path(__file__) + .parent.parent.joinpath("fixtures/git/github.com/demo/demo") + .resolve() + ) package = Package( - "simple-project", "1.2.3", source_type="directory", source_url=url.as_posix() + "demo", "0.1.2", source_type="directory", source_url=url.as_posix() ) + chef = Chef(config, tmp_venv) + chef.set_directory_wheel(wheel) + executor = Executor(tmp_venv, pool, config, io) + executor._chef = chef executor.execute([Install(package)]) verify_installed_distribution( tmp_venv, package, {"dir_info": {}, "url": url.as_uri()} @@ -555,18 +607,30 @@ def test_executor_should_write_pep610_url_references_for_directories( def test_executor_should_write_pep610_url_references_for_editable_directories( - tmp_venv: VirtualEnv, pool: RepositoryPool, config: Config, io: BufferedIO + tmp_venv: VirtualEnv, + pool: RepositoryPool, + config: Config, + io: BufferedIO, + wheel: Path, ): - url = Path(__file__).parent.parent.joinpath("fixtures/simple_project").resolve() + url = ( + Path(__file__) + .parent.parent.joinpath("fixtures/git/github.com/demo/demo") + .resolve() + ) package = Package( - "simple-project", - "1.2.3", + "demo", + "0.1.2", source_type="directory", source_url=url.as_posix(), develop=True, ) + chef = Chef(config, tmp_venv) + chef.set_directory_wheel(wheel) + executor = Executor(tmp_venv, pool, config, io) + executor._chef = chef executor.execute([Install(package)]) verify_installed_distribution( tmp_venv, package, {"dir_info": {"editable": True}, "url": url.as_uri()} @@ -600,6 +664,7 @@ def test_executor_should_write_pep610_url_references_for_git( config: Config, io: BufferedIO, mock_file_downloads: None, + wheel: Path, ): package = Package( "demo", @@ -610,7 +675,11 @@ def test_executor_should_write_pep610_url_references_for_git( source_url="https://github.com/demo/demo.git", ) + chef = Chef(config, tmp_venv) + chef.set_directory_wheel(wheel) + executor = Executor(tmp_venv, pool, config, io) + executor._chef = chef executor.execute([Install(package)]) verify_installed_distribution( tmp_venv, @@ -632,10 +701,11 @@ def test_executor_should_write_pep610_url_references_for_git_with_subdirectories config: Config, io: BufferedIO, mock_file_downloads: None, + wheel: Path, ): package = Package( - "two", - "2.0.0", + "demo", + "0.1.2", source_type="git", source_reference="master", source_resolved_reference="123456", @@ -643,7 +713,11 @@ def test_executor_should_write_pep610_url_references_for_git_with_subdirectories source_subdirectory="two", ) + chef = Chef(config, tmp_venv) + chef.set_directory_wheel(wheel) + executor = Executor(tmp_venv, pool, config, io) + executor._chef = chef executor.execute([Install(package)]) verify_installed_distribution( tmp_venv, @@ -723,7 +797,7 @@ def test_executor_should_be_initialized_with_correct_workers( assert executor._max_workers == expected_workers -def test_executer_fallback_on_poetry_create_error( +def test_executor_fallback_on_poetry_create_error_without_wheel_installer( mocker: MockerFixture, config: Config, pool: RepositoryPool, @@ -741,7 +815,7 @@ def test_executer_fallback_on_poetry_create_error( "poetry.factory.Factory.create_poetry", side_effect=RuntimeError ) - config.merge({"cache-dir": tmp_dir}) + config.merge({"cache-dir": tmp_dir, "experimental": {"wheel-installer": False}}) executor = Executor(env, pool, config, io) diff --git a/tests/repositories/fixtures/pypi.org/dists/black-21.11b0-py3-none-any.whl b/tests/repositories/fixtures/pypi.org/dists/black-21.11b0-py3-none-any.whl index f0e3956e20f5855ef5b7c52a9e93fb38c60cc930..50daa412ff79d3c9ee6fe7dbd37634117a358ab0 100644 GIT binary patch literal 14949 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 diff --git a/tests/repositories/fixtures/pypi.org/dists/cleo-1.0.0a5-py3-none-any.whl b/tests/repositories/fixtures/pypi.org/dists/cleo-1.0.0a5-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..42ec0d1f0d1cd8c39c57cdcebc857e8c0d3c8aae GIT binary patch literal 78701 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 z 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

6EZ{uu=V@oxREStNSYFZp% z)ffG7ZQutK50b;y#+mN&OB!CLwzgK+xxBp0>}M901<-n)kA{l<=7TL0#sW*D_(WsQ9H6VaGeW;6*izv>omb0Hfy0ghyI- zwB>0c6!mbXhI0ZC>NM$STHDC9+Y~YkAH9XIR}E&Qd8I32N?|aLks_>#$4BuYf8_Imhp*<-9EZy}UW< zv;L|R^^u;ne{tSs3tiQ+&ujQt3*9Vkp5%(cR;D3q4D8vs<~Bx^z)I%&n=e zn88%Gg0VMnCLOLX%#lJ}<>cGB=x{6^uP-O cV#(q1Nka%Cv|!pCH`A{!tBG*bY6 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

%fryW(W?noROt!1btQUXoa; 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

YE`kG zTyg~~df7mcVFl{5U*0Cx&;lZ8oy5{8OZ(TH9z3TZ$CqF4FBPsqCN|m}rEH3WBBA8U zjN7D9dpt0XIYV}frOu99GMS|xphY$=pLlzuzwjX#q^@To;5)vc%zPI84l%@HO5Fn0 z!Hc=1m3ey$v|NC?BS-={$tS(Ui!=w+a2b$s0-xj;R0REOihAI#zgnKMNQOqA9f!<_ zoKpQ>A)$$i3d}C^8H^IZdm?tPG3XhqU$tLrk&{FSdg@xn9{6zbuL$=;T^{D)aD6=O zv{43&ePNQ^HU5=f1LeiMq8b!6G=rI+i6Zi_-@co_Cj0$hEL@%C`l<-tT=(6*?p- z9w2!~?a26~%U_r!jfIM@rAl9@OQM1XlD zHx3jc55^3&{3j9L)Mb2rQ6HkYQ~(|Rrr(bOtl>`%6H-87HzM|_5y1HKE|1OHu*Ylp zM0aKP$YDPA?sWkBsA?4priiWQaKKpZ0I8!E@{bq^S`D;htcr#|`IdDL7T~6Hn{ori za!>lsW5j$ts=oj@gPy`}ZK14Mqf21c6`w^Sn8W(7A{3;Tk5N2{J}Tf_G)5Y=I`bQ^ ze~suv7G``E<`oEknKwD=r$|)-W{ynAud~A__&4qf$!r{1s&UV5z;1*}eC{5b4=18H zup~^NSQZ6g;%U?o5GkNWbLvLM5V*8c??qGlVwAcV!z;R$vmor1v~<=bVpOL`*0RKok?gf!9c)7 z!C1^-MBf78KO5g-{Ib}WFx;3)v$DOy89pD`251iq@fimvUnfM8NUT&7LQW^JFD*>Z zO-by@CPEJ-bvaM23sn4xdB)P@gjAIvZMfPhdej3vbpC9=^0|Aasmt~7TbKrE1M(uM zGvDi5XNaCPqL^lA!^#F&AK*F_?bh4k)|D~`8zVNfu%XbKIO&o<4$ZwNg z!WiE`?6n><)vTGd(xpu2C|!!tFX=a%e?!|E9WW#5AWS>;9^G5Yk!jLDm1+Cy6=4$q zseD0RB=Gd0N~3`Hk8$uv&((`=aJpP^>lWVDtz!zEtkq{nTRflQv_+K(s}+{HsiErO z6D#1Ua4~jR5RU^z$?-+gr{D_LOe@%u(WnbIlFtJGRL7NyP983bxt9`)wQG@09u9HR z0YgYI))Bi$55Uq;3(N6$H(N;(eM+d@lpGi+rp!fF(bw?{F40w`mu(=RqDMxT7zrT@ z^m^C@g-AwO@&eW#&3)AP)0;+D1ja$HeWWZaPNyV|W-!2RUg#y^MjA;L>InEcAjB=Z z9t)^cde`)~`!Sz`^AC?%wp$S&r1VYoOI4~`p$kO#4cU(M^#Be-=Y!I~H{dvUzsUXp zzpnk;gSFMEmHFUj*#tO+=)0{_NiWEa)CwA|Nt=rtCw+&ifqYR_=2BaF%L`#J$idvSlElDP+KYi5TfMKr zD#~HUF9S3O2tM;)xf&%vNU8LW=`$nJZiFkpMxPXD+N93*07G=`N7(oMv?dBi>$i*x zIq4KcOb~tywe{GCN}F0i5?%2jvF#L||NRp5(Yc4>jU&ASQkUo*8B&v*22&82e~;UQ zvh~u|yc}zn#rttm^j}X{Csyk;-#I(OU9m3kRUQI9ne-&5SQQxE#9aT0kg~cUdqAC| zP*WZsVmU~5)7I1HV15)65$k?VE>a_2aL@ASdc2Kye%PBJ&X&JlV=kDAU2$Xd5(!-K zI?y}O5rM>_;!7aP?lR__^@3cI*r1$+q_kWO#=LRQaxogxGl1|!i!M{Au

7YW}1K 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

A4uihBTX=Di4etf04OlX4B6pFcCjN~U&Q zy?UCs(Zdt_K{4s*NYkyMFWE9<@%wjnH7}JjSCf-DYZ-2Sev*n)3dpQ)6`>Jeq2D9) zKGRgd*_A#Ii%IQsds@vx$vAmF(X?(n#tVBe$xe;~Q+XwtJW1b`FziJdo<pu zZ$|$!09ts_l7W#cC(^>JNHur)_w#tKzO1$9VCcGoa}QM{B?R`$-ewsF%hp6mrD2kK zztXDGSG7P=ifU3nvgT}!`MD_PBB-}Xx>9|R&Z3bF-VfhO&YL)|qMDMs(h0#juQ+l+ z`yDfcfEoC|IlbAL5?c*3Ms-;hw_Oc#OoFM7a`|});cOW*o@4!{G@F6s-REYY27)jZK`ibH&h zr$?xKhR`bg)<73=ghb{;+I_;(pJIQ&VptqP0U8xDH>dWEs0;|5s@Lq-Yr}!2gT&92 z$GODg6tcBwxR2|Fx=3Oqh zXay0nO_eH_sXN<(@uqhI@i5aKHm>sC<9ra_VVIkrhn&P@l<+>kDqU>j@DC|V$~t7kDa z3uCu*gCMh&(fEid$i0HmDlcxJoD4Se{gM3Q1fZrAengUnZF8?|^mFR^^& zyl;K-0B`v|rL5{H=Mzw1Nn)N@W9vK^#y3fw3CQG|*yq7V^V(%)=D#$yN}C>o%+k8{ z0EU;2S@g`)bJ~N4HBqjmF4Dy^j(Mn%Tt+7#l`f;S31zZ!<{t+5)!P;__a(#C6A;4c zGuTh6o=eL~$Lq{Fm9>yu7MC_^j2|5abHY!njV5z-sy2dhXg{=n=4&sG)KnXAJYLYGal+ag_zinv}R3q0!%$Km}F-{52HU2wiC# zDOuG)3gMh4EhKNKFjZpI-1B2z>emNs^!lFf_Zd6pf+%1nPmk6^@Z?=>rr8}ao}CS_ zz`O;%fe#>Qt8dd3Q;Ozj#CQ7x62_mOxR~&w^L&nGn*5cow@=Oz=-}-2Av?Vi1e`QY zQGuaP#5KSskQ8M0)__6zxkaeb+sx?DJ@ipiU=q!gLsU)HN$N~r>+xZ}@J+(?K90U^ctK&y zUgMwKhI$0`K5#7S#!csmFOpzUi8n-<7c(LGbv zVlWmXHGyU;gqMYGt9q%;SS9%)*sD~s)~%@cm35dGn4_hNsk(z(E{W5B(-7F*?;ob; z_h)U$P=Qt1N|6uCa@F(Cu2Lf}r4(Ho<-}&~u2`dK@1xy)F;xrMT9MCaAnyD4II#-7 zbPc$33pZsJc>^K7MNtO2sx)@Rs!M<1rZ+*a#NOmZj7CZhBEcr_zs_w0lsh{kPBWnJ zwlWa_AmmU=0YK6kObu}5W0|+q%_0QR6`VG(@4kz*cw*E?%-WuMwLv^$tT<6sLuD52 z*ro}g)=gE!v4{8Q?Pw4K+GS5gDC<1&jwkW;AQmK7bI^V!a?BWB41{7MS!-IX*Gm#tfXpXsLOs4SO&WWtN77|mG5WoJ7gtkchR zQ{pVZfIEC4YAQ_@p;g-oPt|)DTNBuPpxO?P7n?XG4(6zwV%90eknKwCP9=n)(;-Y9UDETxdH4t(*2xA*FAo(5UtO&L^I!>d8mzx z6e}QdWcIwx(nmv<@$_ha)_2BA17bnh5Zm#u=b48RfE(V}PNeKqR8Uff|EeO=K_HhA zEAcW%mhHCuweQKJ_PGn4F@P6lQ&NIDjn zHJ6u6bm5j?%W096s2tnVr2jCgA_*SW1~Yn$S73{c5JYmE;^r%ZuYO){V_@d`8$NAr z|LsZZgJ7!s^g3C7LGXr@HIN%dUV#0l1tb(;?Q5>5XaA%pX|d(6U~UdU@MISw+2#{l zy!PT{HV=M!J+6b()gIJ185n;hRX<7y@bNNyyc~yufu|P@HnIU;8a&Z{=uJaMe069Y1wH77uO)j>5mpMGWkRu+wuoz%n{W zn!SpE-Lw|53Tet8U<9d2G$DwqhkuqJkcb6d8oAy|7XiVi6k76Nmf`*}_~2{LGVCFF zQ6aK=Q{+3;z5L01bQfti-TDLVKE`0&6JEw!|8N%$S7IOhi@xjs0XMv)6GjlmcAp8{ z|8fbIAOVH}dy_v0b7RB9+5nQQ{)vn3viorommbgcJ)*L&=0Z>OE# z-gW`+Bt9g8vAf-bw-4`2t_+~`oY9D(k3aJsj%Wq8)7JM2wBJiMq!n=45jeqA0-_^g z$9Ck_1(_Qo>Wemi*JU_bhLiV&)cThjD8p)+D+GRXFtU&!@Uv-FN_6le1^L z4}RtdB8))Ft^Ty+k{jT|*$;CKd2AluiIl#q7Swdt5pqe?aVXwg<)T zzqtlL`oq2T!ZI&8mCz-DJBDv8R3vOAm~G~p4IY7i!hQN^_Z^&*xAMv#t3L(1(BA;s zmzOt&7Y-WxmdwKp$RACesF4-X%t^ecu5QRNuVC5c57?481rVA*N;ja$@!4h$mwPhU zU&RjxWa7Kn_p@$o0~*3Fw=MD4orR*~4jEONd^W`v+tyz0L?k(56OQB1YePxC*#AUp zw3aUA!l_Wg`TGcmG%bkWQxHr>A?$AlyU`JH@w+QqixcGa7w~`EjVXKw%18b(cA;H-9{{QoR{?qFjnH&6v?^BaBWjnxt5PahisUk}tNmZLt4ib($t2#RXI>uxmq>4e_ z09$US_cL3Z;7K9rBW`#z{rW&sprk9e2x?yp@g!32VW$IL$aur6ufkIl>uY2$9>Q|H>|Bk?h{(e zr1yH$3@@12C{ug-r0Sb%cE^w{q#!SMv}f5KsfN4_r`Y-MYQT=UP zy2ov)ujeu$P{)b6NGiACZ=(uJL8&Py(G^IrMpbpZvnRL8R)wHM@Ww3us#iq+-}CwP zLwGLxdxG2kHk#jNZ)f7{=s|B}=V(H&=W1eWZ0AV-U-M~fXXIjIV*4L*vK00Bj0(lr z%+%O4&79Qq47J4Mq!g{<=p^kZJ(YreD5SlE1L+9~8aj3G>&JWBA$i(y>2ZpAUkPhX z8;ZYxgao8CcVG%-vB1(x&Kaw90qbQ*w2&Qn9VKS&8Ycb*0e7~v3^-6LE2Et#~{g}#P*W2q(Tn}Wy z#FxWrtX^sW|HyB9g}i%TSh;}q{660Q+}fyGfh7F6&fQ3)%+=2^j?w6O&tYr8^F(L>BUmr5=UJQmd$O0a!VrHF zdrUQCF3>~QFPMe=X1G7lz7ErpvYH z8biI53e&>tBg#f#f1_dtN!c~%I)9{=*bveJQ;)K?{{?c6yZo~IKK2Ke9qm|WUE(m^hCWP_H$qF6TbcF>yZ_3`SEW{uc6 zF@@{MCUxNorrO(NFVy)Pz4d4V7uHq&&}+=7k!@rJ=S!wmw9_oZM5Rbj?PTCZ9sTw( zgArubG2P^G7j)V89h{_T`3FE-OZv+kBtuCDxcDG5^+FNF2+dc8kkTD%W$Y5hL~nqJ zQ3z7a?RhVQ70uKw@d}CG-RD>h9nqC|?_`zUMUmL^;m=7O?FbQW_9xhXFTsbDNy^n< z?)3vB008a(6dE4(7Pe-3ChpEAwoVpywoY`?5<()fN+MgDT2A|-Xudakb3+rW%?-ou z`y@$=%c(F8HJUKhAj}y)$g}=TfGoLfjhM)0}TiR(vyTIf#J%VyqwFWfC% zFM)G?KP2F-j?OpZ`u5!Kr?0iIAC3Hkp&N!7WRA$kj+qsI<(V-_8`P;P257d&pFdvG z+Ow^XbTFqB-!RDLooXSb7 zOqj%@6SXa|w+{_-M8;S&!|GwAc*bckhhMemQkkd-9I(=uQzlf>t&dnj66cC(3Q=f| zH98V0D-t&Z-67X}Y%n<=Es~Std!ilNB^qat=$LbZut#HMGiw%TNNgQ*O$%p!1d;WQ zZb`cKNfi+H&WKq!E*`&#Yb6>+rHgN%Qd4?}d_S3rV1kz%&D8`@hvg zI2Xs4ipB|`SF)!9F7lH{7d+}AnM-6mAzJUx@9!I!p!v*}Vh9&dw9R%=$}Uhv<@;r&0XwstjV-cB1zxvh%ub-8{-|8)68epTYE}04AQn6!ejLn7(&~{#S zUp?l49Mh=Q3-9meY%^eua?@~vjN-8&E~yg;LT@CpIlD<|V;2+~lQ#M(?TltK$fxVUYx@D6Eg69en}fy8tgd(i+1R4q|p z1*^X>_^8{5l%bH)H=*6o|HiRgWelH)tMr0j(d!PJgDiS9Lo^<<#x)YeKJ!vNY!D`s zU*FzUReNj4i6W8SeFRBVX27$ARoF>1Mv3`z5A8@U$NohtU*IMeH zA>+x{(UWOz`R8@Me$~v^?yuUyF0yqpU?Sr67vZSj+vMjBAB{}VA9JGLv$IKJyjbn| z2=FXcC8>$AidxazvNeyqMi1AVW~O#J=9G-J>b^D7ED5h)+plrKGwu~-ep~~=OJL($ zYv=}Ly~<}j6WzS7NBHB~|M}c_oqBp;52XF3V)I!fFvBreb+gnWAfx;qKUg3gZ<38u zN+q|8GM-nkvVlSY`zf@6o2?Fr*_)9D+GRXk1JtZjUI35X!k_h6Ga}E0aQT6^11xvtUX}E-Eo~)R!v( zagbLYU-2jSEX_af$`qE!d6vL<*tMjqLMq=vIO;4M3spNl1t|dp*aSZz1STvjVHVNJ zPFP1cDuN1GgJ}b&M4%X8N&>Z}q`$VZsYg?89M4Xkre=<(qMVRRqr9eU9@!I>HF}Ts z-a#0}VNq5u#iqJfrTvhRb=FE3xQt}Uy-VvHv1Yg!vCcP&`u&MfsbXE!qyc>vLWGzbhY!K@}1j$w%_jj+fZidY^B?ouXLf#OZ$kLQvS zu%LPnT9G3!W(0SsO1cf}GBF*>AYuL+i=jNTR2eCt2<$*`TZ4(btncuy8lZBybxPy8 z|7>IVs)3SYR0ixcky7NVdiDv#rfiF0LWqQdUoi`eQZG>di2y+Aeaoy)9UpeT)cSu$gMn=klLH9 zvn{k_v`jVGl-e-cWLa!TZH>bf1pV zp_n9^U=lf zc|;PmxJh6`a?e?%fV!>1VKlam&~}EMb)uK7=F;Kltu&(v@0l=~iY!iV4%tRIZUd`~ zm+jXPa0y?01Lwmo(BtGRD~2W(`IPfL>I5v+!9Qo^Nz)Z(FyqvzZ~p+fa|;#vkrY%` zOKLYvY>!PC!{?rv(o7FtBIMkizu?3}N0J9e!VbcG0={yyc`)35=ZVLAe^kT%`(>=( z1RN$bmj9mXAopNmZxtUdzWdJ9`6k-k#6pZr0RUgm3LpF>mMnpt?$XiLvla)oQn0@v9CU>T=@^{bp*dE!_rPULRKH&T)RpHQ z{6<~`_(jjCs~>sI1uvUBEoTS2Z$p*<=an4eMf5W2&r0VPNC_<@daO76_Hn0~R4r-r zXYGX-{_ZY3-P=Dm@YCuocw0Ts*7-AE0G(FNh(KWA8TSouXftoEr4jN9TjZgx;H3G; z#Iq^|N%>f);x*PGyg-z=LIyPCroC_vW1&j95lJ{>AF~O*V~h}1dLCTVEP6ty6ig*l zcz0UCmsgfXkeB;^wwN^@HkJj6lqpb?KF)cMtA*aTPFMlxCYel1E;(feWmuXgq;J`G zAlsO-j*!KQ&5`$;yOgzz{WTO!(t6TX{;c7L+!?{tIbgdp{%40NBjrTMX4xt! z-K&RSvwy==h5nyKTf^H^xKP`jYZ$y(eVR-%)4)PlZ&>Fl47I~gKL(5s+?VidDBKn~ z5bv1SkLnUTh#f#VtG{IPZ`qaH0WWs+CJ2Sw#ZmHH;pIq1NCXsuYY}&wiMV1ty**vF zE3fk2sC+y)Z1r*QdO#tL_$ZOQ@(>9u4Juc5IlXwK*ERHfu)tOCeTzdyfA0*#u!k<} zi+EyxJBK2kz8FJ%cDAj@&e?+`)-|p{`jng9@e@(c+>qa zOoV;;MZHj>OfA;BexyFD;n2Du417c&i~-fM(YH*414?P$Lc`2_bFEyH**KM-vDhOOsCE%$`iM(}4&-{&tnMSXjV-T8*HWVwL^9bOb=1R$pQE-72C}lDezd+ysl@*b|W*Sdczr=xwzi3WF<&9 zLikz7$l_@gA$LKKax2e>+{`h9@Q-huXT0v8XT+AA#Gug^@~k5Y_Q|)C#ADC3#8b6% zSy;vG-g9wL3?3bk&=&#$5B9Um9dK;fvnlotK%Y@jK_zAXqbIeo&iNtG^6C&X9kWa` zd@v)@WFf+;ysm1)uBoKB3WfJU%M+dR68D;y$&6jFP$xGik@75z6t%38K{i495`^8z zDCryq;r77Js($nd-(QDbkd~&5T!JP!_lqNR~_fX<4 zmj7~@5Q&k6zO*8bH76hOy$L(_?{h@k&;-ZM>$S0*dk=J)wdtaScE?Uzt2TfuF6+vYZ^d%t!z}JzM|CBo3DNvFRJU#qBzJaFmZK}6&|7& z*RvBWTZD~J_*oZn6l^mnrJN?R=>fU~aOaL1``y+2BQQ@;t12G>fCp zJo50|oZkUD@CEU6%tSopZ(Pu}OLTN$CMYeO?khrrGT)(O~OvL&RM@8Z1PxXeX zKGfDO^{SFoHEqt#_hRbzKyT&mCV{ug~-gx)@I4FM^iuPybk% zfqG|54Wy8YewbBG8PrPKHJFvk_$Lew?$)~l(D-;-Rh>-Hf#YOjFw&m%TmEznIFR1U z)b>_R`%a!LG!niDtR?>IYtk}7q}=cwu+=zabq+71Dsvvt!w4u5CCoinAwv0 zcQ=a9KMK|XtiN6$?q55{3ow}j9M6gckzbQvulZXHh3ytWoq(CVM#2vS1}TljA`bIw z+!u(6`S%%A#0p1^cnmArQiUGk12KlV4BoI!(GH^(5 zJd(N=fGdT@u@jM&#K5sqx@8fquCXjgRBS%lx-g^fck2|^shx)P@7nQSvIFU-9iER$ z^4j?4YpHobXVq^+#7_;TY2?)C{VB=;m%E56eYm}-lIHbIsY^ZnfP_)o9o+3RF^PX`W~r*s_RW^JWk64Kq6gUog|S5;~GS%Ah9;?nE)TqBqWdeRwu@lHt_QS@>aUd4g|oB4|ronH%x_8H<)45lN4qm~M> z3erbr!N6x=OP(f@q)m_&c5lKsgPczV&^(<5whFFKnqejrd)t=dGFRQ`>dVQPQ_wra z6o4wZb)>srHVsuC@pzA8P&&~M`*_F}fL8za8ozp|e^fw_LS20L3P6%NO(UteS;HDm>E9dnFCtq)i`&0fU7WtL^s-=I3N$p>E(?L;dFE`v@PwHkfCV*v)T%*{cjd;R2Ny_71ln} zLV=DNNArD-gVUQPs32rpTXkc}kjm>BpM! z!Vbyj)d=!SsJh()3-_=J_v`1f+vP`>7tf$I^OLs)*D6y2QB8z9Bl6VBqk%=(>op4| zt4|jxAII0JEUvd|T>H+r7GzP3$T?S_ABbILH_HT?31?P@Fc8bPLOvU)Me-?4WJ_Ra z3hW>1rAuP%ed|zHM|tBE>0bgWo=g%wz^!AuM%L=&q9S1KbMd%^B!_#Mxu#zY`Ybm_ zbc|7&ipPr;>aB&H;?S3aE==L)ss2ytUtp?b!>G}a^s?Q3%PVzWuoGlQvs7&%7GA!k z1!F>GOImN-+g4SD_8EopcMt^~I_LU*-{50`fa>6USuNIE2;k=rzhPMDcBBibGthe! zVmeOiEOLsquF7Q(phVbFR)TIM9goLlX?wMqfSZEd1{-y2K*Av;Lz`G=cz+UWiOcQ8 zQ)tlivD4jR=8dykV~#>*B@Y<}GH|^SNgvvwo@tr--l51ZnaD3%EgL6Fcc9g{X6U#u zBMiWp2J1M5$A^yn?REz?m^u)ir@nt`An3i$`hFTsHb=eLoBa>)A)==|79>)90YY*i zjS7%R)QOZlA`*$}(S`Je!b**N>XO-|usB{L6hdIh;k5G^Gr!oDs#H$B3xLnUS0BbLFYlvEN8Ch+@1(971Qj^(T6=jyT|)J_?Ooi zO|Q z#S&?m#c60b)R1IYzZI6rZ>WMXmcT~a`7^!W`vbU` z8N%!5y6U}=8!8rx$jw4kzW&UL9qH(uk@x=uB@PB_%t;{#E~{oIfdUz{C)>vbZ7N*lseaY459VFaRFw;VvYbZY$sq@Pv38YMT zTS5;|{)I9y3?)V|cmi~;Dw@Uz1Vlj}Jr@u~*jfOZIU|kr;wFa5c>FUTc+pR%?Dz5L zC#%=}x-%0;1H|X``Fh94ng}18B(&LV;UN^~uzF#b8StoDxg}2{JSb zix$=faGpqh*<5aIi;=`Im`>_i(VmP1A=jL_Khz_`UGsVaNXmxKY8CVs!xB)tS6 z{pgk`%rY!Ye4*~a`@mw`9z0970yVxTfgcS(`&k>I;DM*zdPBzPaWAa#N`!e$dr(wM zw(@;{5Xg1u^FWU8{WXHuA1T}UObV=+L+kJ%66LK;`BL_zreS=C%3`g35cN?>Ux0V} zda2TB!dm)r6O|OpF{`}B0L`!`{ZidYIzZ1@W}B2PH*4DH-yjfN=wx4#m>*nh#5;3_ zG|^P|`m~}oli)6I-%I|rPi9}JC2^o#>{Xqh?5eR&M)Tr3K;w)s-Z72<6}$3PB9yP~#$)rG)x zpwC<_m%>bz+MCuUWzu8C8Lol-B}jumoqkFW9UD#UIw0#1)S=3%WdJOo8-mJ}%G7bY z1VEo16bUKMBfB#OcsY-6a-P2mOI77i5LZJMfMkwYlvq>mM6`C5tdm%1IZvKVl#0~* z%Curc-x&Q?&_{5pXzKOt6bn>&!-={I?%t!%#P_S8BhU#;#r5dQT8mC z63waYe9w+8$hLiE+>LTHIP6Zgh|v=nRea3J9N*}~=@IS=f{pp2FlOd#@7wI=-53T; zpi#nFBRg@54%JS^6lf|wG+tXp!l2`@q?{b4FYr|F08^?+NdMeyhCFu z+R8I^v1br2N_jCiz@Y~Udpx$oxqv{>zra?xxpXZTPt9DbAoTujDN8e$)YXEzjJGgb zz}t)Rx$pj5CfgjR$B^C6RGm!QMg}ywfZM3By6*dXQyrSt0eDH+B%p^dw<*epRfUri z=ixM+t*x!f+pwL}^aAV6Csa&nQ(Ve3$Tbbk8)HoTcRoKFYoK*Hwn?w!LbOjeg zH2x}VKy<|L^=GiXYOAWL9gxf%r&;ch^G=?WY9;kFuAOWLXBpUS9FT2%#*T}(NH6qa?`9e;0Q7hXNLTiFN!dZ9j^ui3 zHAVjOS!Cbdv4^-f3<-pAH2}8ZI~@i6mZDdy7?zFTSMc>Xb(Cgw+xwG;fs-rC1LYp> zf=5OI#CqDUI&H!KDK?F3o#|;A{Z`8zq@eM*_@)LBOnFU6VYSWpWpEISB>e!ksuMGw zgvyeb$wlZOvl<^CyV!$vIZ?r{Fmp>gm6Vki2%l;yu-)W5GJ)+kPL4TWpzepwVP4~1 zhP@lBr+V~Pq@Jsy#U=<(74rjVp3ld>jCiLh%zm#+S$QK=hkIqHL;ns&yucuiY-a{} z=}a!kWE#&9wxQf=(Y=8bh22*9R(Z62_b~C`?0UD4E2QK2stEYvxxyOYcrM_CqSEqW z1Uxs&q8fn;Xf$G>yEqTL=cDY}-`5wxffF_pl8S;{VXN3@RBVA?I``ic(z=gwxFoM5 z!jcf+>nmb_3Xx&cK1ZwZMnfvUVm^U8LTuSyE2y*zq*~B+hOfzT^D%^{L)zJ&s#A zAM-b7TMmZZKBb~YPzwhk)q4z{HK(Tr(gL}fqQ=`HPHSz|+2iBs+#V*bmpn;OGp6M~ z|Hyi*^^bH=p)ck3H8LRu78qbka9prRMcf;d2}NuKAaH_(Ut(Ae&}NoaD|Esqa7n#` z;qg4_;Ryjy*K}QiA36T5)D-rS%7|ztQY&aY@+#3qOaENiAmwd;g*t2n@0#8GYxJKo zGxLw!R>$OmSg~QCxZ3+mmmK;#Gx)!S^$!v%yRCOJ&$$!jUVHI+Ef%WEo?jtS`85^_ zOsw{}S51R*tBqdh7&MGS8ucy!wmBL2HaRCsyP;{F`ZSg8S1qT6bXDig9-(4<4>{H| zU-8ite?oeDuDT$e96`f6ZY9>WFmAkL?E14(D7JwPA)L_ zVVa9ADVJ5NrDWz75%@7@?l#ilnTX@&KT_LVvUJT>8oe`Vzk6&nuL_T_z!?wg4o@%P z;ES_?Pfa1^KpMf0%&CCx^VY3kPvH$Gza0B`5c#Pbvj}u}N9kKYlkrCffrG;hO*RiI zja78~r5l5FhB*L800N{qa_e3PB> z7Fo5-SK{I?EbL?1dBuAGiAo}p3bJ!?V((ZuWM#*h7qu~zz39oH2BJudOlW*G27~t@ z7LO_F#5;b1U!n?c8Z3O=LdW$ zH&QBzelw-)8Nxkyc%!0F3LAM%d^9&Mq-a|WR1;Mk>beKl1Rd&XaN%m@%KI_4vh-pr zGmm|Ja|N+x^Fxz0<0zmb`WC+S6H~Cev4DMjd8NUy7D}(XY7x0v$pQD}>c(NBQK6uk z^LFK4=ds_eyJqy(RBfJw;H271oXz3fh-q6$TD8@xXAPf)m)BNs!hNh6`C{ZInv z_PFO)g)EPK%S2u)C>=#SuS7VJ3=TdB(jtB%AyK?AYBv5>rLZ5gV@un=iS$#7`%)_7 z1Z|x{pAcSE)gkOe=UK~Uid3o7@CGS<@mFiZO$~py#0YPLjz-OTOFx|gL;U58q{Vs% z-^pj^%f@A?LcIinDBIEuOY6B1G(9jmaYgoy<7&=GE^&pDMJQ>9UeZIPX}byv;ty(H z9_Xd2tIzohNZpn&SU7|$?GmHdWoxzC2M-qov|f&N%gq`q^_~6a45;8C*Y9^ke(ac| zNU;;B1nj3qH>@)GS0j8TkGXzAN*}L(>Q~@I0Nb+V{R!}4`aD7*;PDGm!+PLyP%|E~ zY4(0UcOxv|Q3*swnj)sGf$T-JV21Uq>6RYZ%dJV|39&yD8e^P6)hJf4PS; zax<@lud%II|6Gl}cP(73$dU;9$R2`nnZ4|Wt-Faq&oI1 z7ymOCC%_{6T`UljXj$)(xpIO-j8m0XcZ4~9#}fT$n_%|htOS=4$Xi$P96p9J&A4)T zZl=1Z7#GUb)S;P;+rNuXBWW3e5tF?;SDo0dANYEEp!r1Cm(ziN!v4udHOgvz;oX38 zKJ^wA_T^pNPVU?`murB#aNC7)J9Y)|k%ghK?GD4;u@&+FOd9b3@YF&}ieZ5C4MUs? z-F@IwBuUN=)xrVsW8jEG#ME|cz+(v|eX;X<3TgH2~)QAkhrX5 z@50bsH^K}(^8ZN*;)f^{pyC)xqx2{d_56qpD#1KHy3<+eVc=8oc%h($b$Nn81jfF= z_Qc@b`S^2Z7*QP0hbfb~5T(KzBv~d@^oE;D=qeYsW*vLZe__JOb76M>0UInCD*PqL zhK|T-FRQ+K4W-{Eu}#~#XBO{`H-1;pRm#vAM|imdoFNM=4g~5c9|*^0FK6oPa}xjlzrX zN_bU-73rp<6>OeAjB5~v(9~q(G5oWI4w~}6BZ<_`4vXkc+8vwA&2u1MG8zkOYZvI7J22}`IX}j*@Ql_dICgaz)nElb-%V|)?m>75M&j6b|5Yk2X> z=;A%@+O*9m=~khQ)s_|RmX+f^@+_>Zi8t8ImU}dNwQ_I>6z?14HebF_wBaB1P+e~W z7NKxy)mh`dn&lHa^gQW!|WPS{*&pwPa4mzdm66P~=r|*l|TEl|`Uje!@xY zwwyelJP^o+_@Wcfa-0?pagdIo#^nkMixdiMxZDr?!k+89O$zXb9X!L>rJP|V%3=eohgw zrLB>q<5l^Xq0VxW2e~{g2MG0Qv1i9n%msj8igdX=BJi5`pWR{xLMfRdF*sq#Myxrz z7HT6GFi`8Z$zkOG$Jjf@h!TBkpKaT=`?PJ_w%vW&wr$(CZQFMDY1`KHoyj}z{O@EY zH>uQzN~)5*_NukE*6$ga(eYDU+j3Y*Wu>uky~bhnz)uqvgyiw8q9S-x zxpKI$q+9n0j=~|G$B=>B{qtk?`GO?Wz4?oq`%<8SU#SeW2|VJm3P$p3M@i?tU+DRfk z8g`UZ_BVL}94efR;9^9HtOP;IPcCBA)?8V1T%YH^LxxUjN+^{s=8*{jIrC_?xEn{S zi89R+D#LwF%5k|a1eln&Pu89+csd>@`RmRHo-f6FsI_NZuA{S z_k-O4d|lEbf>8UH51TrUav1v0$8GD|qOofm;v1JN^lF^iyaM5$g@@M%M-F9mrsyc3 zRUHCaXf!*AfrsN)czq#ir}-jq_!*u|?o6~O_Q<27tU)4s`#A!(z@+&X07I`59e#6vUWUL=@k_ujV7JRg?vo_i_M)mU(dG zMQFcTdV;ZTga7iQY(7M28#J3Qw@2Lp)pdk*G2U>x-x%)AldB9!hDbC5>UD)Ofl3Ro z#mY(ir+26aGLOC*vN7tTyu>PWy4rzucU9ye*&OtmWVTBZV@4z_T-D!K|K9G%;_MzR zcDB5IB`G{&CWy~9u7blYZ_f)C02a1Hh}d0YSl~XHlQJh>2f45ozY8qKd}+U_U+qo= z9%^J4g0n4M%cpKt!5lbdQ_xvOB}01xsR*5e`e(>$pMz|aVelLPp!^wT6G4-<0||IO z%CG~kX}GA6o6{id?$b zU~D~NKLLmVY|ZR|sM+J*^VcMbsZ!}73WH|p1BXf`!Nk4T;>jZE7lhF~fPS4j*&>Tq z?1Ai)tGdqo=O#>6@{)o5@wvj|AzDK>4=Om@hNvJFc8ZR}WC2rE3o`R*9vCjE3|^^7 zWDtJsBw?RU@3r{{TrwRQl+LT{Y6QeKY~%+uMJPT%wBKO>{=!VLSo4Y2MdPZsg+kty z7|Q7^qLG!L6Y9pb-CX2-@nV{gK2Y~#q$5-7#>81g_6w-^=)*PrZs=>K+>c@&1x=fm zS8`+Yz}Owa_pM>?>NFOot4xJVqv*6B_pALZs+T(=_{X}rQ5P~%5Y85`3$jCrwVV$Q zpIEddC!s`qm#5VmWjH~vV6{Z}0lPi3(xZ9%kDq$r#okvVLc4V_S2uE&S^_{xAq2l| z6c?ZeDQ+Jx5_4TZIHvR5$#1Q7{r9#5z)R2gcCc0Z?wG38TRd1^p%1JM0!pT~V)`(w zaYYPHj!aK|XXYjACaS&Xh9!roh&<}d<%jYPG@mE&SeepCR}S;?0*kZlLZ>>R1>Z)( z!uAOE1Et5!h`6|i@AeJT`R;b-nkehJ8xS zj>y+Ub$RfG*5M6e2D_N^k?go3K2!fcY}XI^13{God` z)w~3>sq2y@l-Z$N*Tqr?OmL(h(9ft##$ZpPyGoHQ(qo1U>#Va2So_$~Ku7~@Dy{&e z#e|Dh=wN^X@rf>yRISRT(oE#AAfwKztMQ?+PUb36dLT*o^!Sxe)>FOlXx}uR&*r(` zt)*^Tn>!f*73gr24OZ5J@{s#=RAZ_FoqGCAYgt#Cy&D$YVU0!1ah^L{TI2oKD`M@? zi#FO|5T;y@G|dW8(AzVF$nng<#p{6z>!ccr72O(Uth7-PgBusOB)=|}?%*!@ZE8jT z%f5&TO-O5!(t{Oz-xT;QG|IQQT9%3OtPdWE9$~y#t@`+%{rx`uy() zlp-7P3~EKwN7P#*fQgvyswhfaN1DC$SU7%)VW~jem=SKlkHObsA)Am;#urv&l@tX^ z&afAz8B<%#)o?NzC-og*wxYO4c6FWz5O{SIIlmo; zKIBr1qytLbV4+dlp3=*QON4kzk3C7KcSWx=iV1hb3h`c`l6O`S{5=l;te@`-=#Q(` zE}WsHkRyP7-_e?y|m6C$jcALH1@wj6yjQqwuaTuFQ$SI94! z!W^gWPv6F@V3c~xf#dPTHsT*|@Tf7bjIY$-83vD#wAVOB+-#93En(O^ev?K3%X&%B zQD&v(dLu9;?sGY3;c&I02*-17)>Tt4qF}uazlCFzqLGDSM}%O(zLyjLxIYU-8bvf& zM!>A?%C8cwsxKbYUw`*pIbO1{H^jV$u83YI1fM$uL08S-^ae}0Pn#fBPaS$3mT-xs z0xZ>2ZF=5xN5i&cPgPDoB@sQj^B=VJlo%HS^Nji5-NG>0fVHdrz}n59HHmp282f^( zC$(|a9V{GwUlfiEH-(xs4r@VT#~gB$pVJx&&aA!PW#9%1#Xi{&fb}fAc2rfgzpErY zEW5-{_QeiH8q*FjS%Xi>Y8aTw2uUIxT?8&SBu^(AHkuiWK$&>UVyWasCFHs(7_5rWGM$$eGrgC)|kK^I6Y|dpLGNi?t{7tdf-FMJG-v+QpfK z_P@2*_)RLl>hIsyb!)K;O=`%=i4S^@+$p5bXXj+W&g(KNPsQfU&%3()gNZ$B394ru zr|;vMupW%hwHUj~zqrg$Iju6pXw>IGgWkf(Yyn(oHx;G}y0(#~CoA(;g0y2rJXpKL zL@X*i1SwAfK0M9cgf{iR7ht?({!65ZF)sFj7j~NnnyyX)Ty@dhLnKTnD-{Emhl-^N zv|lnY(Th+tz-_+=C)1?I{d0B(Kbt5PTNc{*$>^!H2f9$K!*)5^Um3y_m;eDGWdLn9 z!sc$~P+Li3;F(?xt?$T^*l*&Te8?aAuOYX0`s5Z>PJ?(xd@sg;;Bl6Ybe7pvy}tz; zt%`z7JRPI38+()(u6C?N>*Q3B!E)_ZGBl$_+5QxcQ$$D5yz*AIsWRa zf@4qY7_5R8%2T6;|q-soJ;69ah59fiNlwlX2t}G>_RzTn7qu zuq!^rolzg)nH?!}&=?atwvRrv6J~Th&xXe_$zJ3|K626-_Ro`v!;E+-m)#}jo`D$d zq;^KTonHZ-n!oN?=puC>9%;cy0s*7{*)oo2ptIq5bZ2&t*s(KU4sBuk^7KI49XG|P zWihj5?04${Uhco6y^-L+;)-{>r7JEGaOIxkNmn<$`LDgx$T|V@y4q!J)GQ^u2LY!9)w~O@Y z{k0%h1-e?cuqd>hn$L}q6V;}n;I%2Ym$ekuKUEIFUb3ya9G--l%mSyC*81)%IfmB^ zlKsD$gZBgy6rzh&pvP46c=KRl{ru%ngAV@!AL;@YMfx*M5)6^a7)v?j?0rn6oDT80 zup*sezk8Z;-H|S>oZgCoiOVHB=18o7fR%865&~KA+yfYkwOO$4S`2C4+i|BkZiby= z0^0ZDidy)PLC-p~1_Zgd=i;q`$#K#u7SpF-$k-V=dONnPU zxl2pl(H03%l?D4rqDc6~0}0ONCXjm$Q98Pnv^gip&m-*N&9kj5fE-oB0E?Ul!+%Pb zrKs?G@;e56QcOYctEJr!H|@iWE3vu_e)0;TeA5KVd{a*aZ!5f393-PYi=z0Efz?Mc zQ9q1`47ZJ@x$StI;0)a=jCQgjz0I{?*cUWs>+5OO)+AhL2+#(@$Ig5P@cgqrX}iNS z{MyEpv$rrjT4%RKvRrC2-R8_+6I&kR+VGVm|G+h~;B=8vrbN*OJT_7-|H61^!sHcj`we?m2a-AmjJ?G*V@7a}px zSCD}_jA-pjD=3^fn@?;!oUH@k#Uc-lO-5uP!GD=TV=zTg^Q-xb8QpItX|$)~?ZMB9 zQu-`rQkYdbe9Jk!x2GA)AAAJ208NFgO@6r~AG8bTG`r)b&+ych)potwFJ9ck>miCu zI5cN)MCVS;34*JtTB2%_2N%V>@-iNo%Oxvq(_t^j;5!P5IX^=7&{8KtLGGnkewIwa zD&a#9p2t|)NaQlhPF(LiGxmrJDGa-EOzrC+YX5WOs%P(@9d{ArZ6ZA?0hPCqoe#ot z$rD?^Kw>AR0Mp$qX5lizK^O{M!!P$9?=Ofj1GU1z!Ienp=NAaa5lkXF~Oe4tD|oW8`UZ0YK8+5iyI@Pi$UgE2RFz-io8+63YpupNiboMCBwhC}Kg(^okdGd~sE0_7orU$IbP+@LY!`>Y#tQpO9a%q9QTziWQ7- zeR?!eGfTq4=C^Dl!FSpWci{9Xa;mr-ZJTYD&qj z!ULzWUfT{>P(^=7v*2h*c_2_SVQ?mbnx|?I;`)cjJ zy?b&ukSk40a++=L#j|C)>?@TFYN%8vxdb?=ii~ftgp-MYwOh$jk^Cr_mKvNy1=A!8iD2D~h7&T3*AuUHVcaA`-1V z@QBlx`OaB6?F}h+~N3R#nB-L6kJ zcKtX_jb|{47jMLLb7lEf#SfSr#J8{u=p|fgJZ?9~B;) zh#1*Qx)U{QBKP=ZIGm=I33P@2)jlYA0m^B7nILp4qizv@f~|tMJbiIqbMA1VL`>^U z{0ypPKZu%ZrdEFO`H3zIyh@qSg+pEyglNtC3`w#w`0tdH?lR*95assp)S28 zaz#bCAE5PYW0glVwhD$Nb;LRQ`m48Yu(^WDMQm84vUeulX_Cw=${R>UlFYHYr~W+X z=|MT%YQY6^!02SlhKbl<6kcl!sSt>8So?%{M|P{`9`*`{YjdeX7f+$`fVC^Jid1}! zOR1H2nP+`18PyRi)Q#*db=b8_@o58Prrhm|^}}ge<@uxBBc)>W5wJ9a7i?sLxMD* zXI~>aQJTH^Tti~!hzRoYcL$g+T{sm{kKx7iK4)@R#_o^9s1!!NmG!HlSY8{n)1bw< z%CYGI8t>$!m&RN(iRs;S)4zHThw+21t|DOzYEO&khUs;ccp-kD|`U<-rgE3gaz;% zgL88`QKJCojZ)k5C*{RBJ$-lq85hK`clgRv;Zmjzq70>gU}d98HcYKTcPAyL6A`KXrD9S#zLWvSUV?DMRQZ|K@`n zT80>`?^!wsXTGqDRypkbF%^vJ`OV-ntG*Hk$#ol^KlCL=&WG%SvgByV01sT->nKpp z>AfWGA$wujRK2Y2M@2Y&9<@dDl~Wr*1@c%r;18=ZyDjqQ+1L_Ib?XD$KqKlGDdQdn z_@pRQHXx_YtR?}!P`YXo?m)8NRV5$o>rHsg7R4@rT;>@Eh>d>s2h`H#+dB#FiFUz{ zS1S&Ct4O>ebl4+zI2e^Bz9La?#bc{j%e_&8*Ybsw%GI1krsBhrbOGmUV$vW5gg^Gt zNo+xKiu0$ipOmkf7UHdak*{|v?P{^M%5m*hqft;@v^JLdO6iwZZvzeJx3L1mQabav zq4I~qMo@oz2Qfn9UhrR`uXfndgsYEIyGp5kos?n*6jbg)dLY|%+XL*L;H~nsllGpq zfu9mGJJm~z`Gmk_-R9;wF9pZ^S{ftdjQJhKnU)?b%y%l?zC!a}a4@d>xy<*a5aPJ9 z!b66(0|&25-};z`btG(*hV6NoN~rD>rUn3`SYM22&3>Pi5jNYqm>TlM5`;K-?L|_t z>2elj%X-d^ug{EOKqTLH6?efcAo%F^(Ue{m%qv)VIrMr$W#L^=hb&p2tJQ39=G|-5 zGXlun>4znBn`!=b*ZD17&pv#dd`^In*N#{+I%rxa>?3he}|kc7RR=9AIbaV(nJ64QR_2Zlg8bZ(ql#=Z&l!Di; zwy6U25N;BBo#7hj^0*IS9ZOYs6oco6M>v|TeP7J=Am)ibi;D2&@_OJXnHSV|03{zM zmpwgM-q(p0$y_)Tz9`W+fAV`H@j_9-PEK4VW@jaOOHl2^MOd9epgfCNf>jwv!o?W$ z(cUL&sH4pMs|sc<dXc1jnwe=5ke{Vo3RHJ2?Uw=f2P~gl#DcdX z&me9h*ySpCzm>MhafMy|9l^)9@PXrx7BRa(Z)kX2ZO~4pcEg#dgyjz_wO=q8CI2y_1!P zJ+*cSZ9y88?H9D3S{R|8ku|NTgib3Z+&Na)p9(&=3~=e7sUn5!v*nVo0K3B?c%-Io zTz;a{^oGY78qsO3dyRQ9C9X%HWnpe`no>m-KVD9#0I7dq7G?pvw;zd&MU6w3RvOeL zYmP&hx$RRJ=9d|#)&e%&n}D#Go679^Gej2B>h7(`|qA$sto~G_+V9*CfU;gk=5B+agg8DL|iJ#;Y z?c)o;Nb~S}W8ecuo+j%C2rr0TSAZD3Vm6&J=wJM;KXIL7Jze%ux(i!8jb+7m@P|%lS-U0AStOO z%kUncC=5vJ9Gs4^;dY9#F=&hcIuiRe+yUVQyxP?ja z1BMt9Q}tBDZ@GeyH}qo#ttPU0i1lQQ?0BkeGM(047je%@uUNhUKB}KXc=Pi0d)RO- zk&B1&7<*Q{_|qMJ!lRgRo*oo1sY9u1hJqY|G=h^WNJ|{>j{^mI<&^FWcIVDqv;AQ2 zDOYrso)VNCYsZ7+ig!_sZ?@2|uK3lm+$+8S&x`pg2uz_G>q~lI%oD055O^yF@h9u9+R=Ygygpgv^ z0D}{R7Ss?|L%v>ei-US(B@0rxPm*e0Y20QthNdUOQ_B{T8{DaMI6MkEi3x@O#s*vy z{Pf2F1n+{xu%-nI`RrcX#oZpMx0Juxu#$c)TCpx$a(K!{27!2<& zw*tevXj8GpR3pY~OZ5ujD{hGunZ0&iO+|K(loUul!_-!Idh2=lYd=$}-A`d}^6Zb9 z5IIymly6HNSrW{A_c%5=tT@gyOIVWbD7>+f?TEIN^~s z!?VAvdk{u0k~nQrOX3+N3A@GX5XyZ~&$i}U)t-6Yq25N9&R&2BKMFm#TWu_;W>5(= zzKU!H6#`1r=7wp>?e7gAokcx)Ee)a7tU2zM_`QR7A=O0*1Ko8$ZN zxh`=}|8^dF(`J(Gs>}YUA4oTd zIgBU7hHVrzilHTD=k(XH=LwaRQ zpP8+4BZWaaHSTpNl$^dD4tx9|=-%ceVjuzj`?*bmUfLYs{PUN2mF)OkQ3sWp+?B9D zZo!7GU*r^{2@>x*?;O)(CZ_EmMkceXHwh3xOly(!sV|ZhI87cCcm9-?4amVk zT|k~T{nFd_7mdv*xOJWFijhgq4YK0*$0Y0sePNyFYem7~68T)sJTLsJpBjRBfQ$T6 z^7PkDYl#*L_X2WG<>#$^unJeuYF>}9wP@p>)GRka@|EKbj_75@%-&VCjo2#I!n0cY zYEQLZ;pOIWg?chJ7*RrFLkIWd7M^*x1~VMdVt*S8cv<1XOGJPczr84ctsTuAiG}C; zxMb41x;6u-CC#skw&JgDbF)D6FJb993{Fk8#ON1nmSPkBVg^AidPmKe^g ze0_T!bx54n8w5;Eq{qQ?Km+r+bVL8?X|ku9ZQbTCBcb!heTZAFR_D{S~dJZ~~D?XI}M zPfmNsq})b3Ra)sk0+Qv6py4s+i=5-red|Q7pzc#1f91mGKUVqv_l@?E8$Ze2-hP`4g&J3H=25^+W zxB3sjW8C{UJ(T>;lfa=91KHbD`pE)nf2=J;{+MNhdrq#@7MTR4O+I(!n0*ymE%xJw z!1Lw-HQAd}yz@G<61-eSqe5SUd|K!Z+bQDG6&V+mNBPw3tkz7X^Tru zHO~b}1j5ln)6kZCVRV($%(hX>o1tCk@ejyzK^Ge>`}!aOT>Lst5K35lX}H`L59 zgS#=rmD$WOR_2|j4IVC40bf>!1S3I%GF1Xy_!0#a<>;z~If$cINh7$Rxx~H1jO{r((_`j4;cJ;e3DJg+V7UHP9 zHHj_e_27w?3kNzK-d9@;+8J z4maG5->&c`#pc?@uiIB06iVzxf#Y$5HA#?Y=QCP*K^PbPn0S`!<7H~#8iTZ1)u%zW z+}2CPqp(Xtq`C*@%b8A`!I%NvgfPXDx_0VOtk(lZWkqT+!hGLn35GYLwBm04Em((Jy)3POGs4+fOuH9XRBnfVgcdJ5)rU23jdq{jttG z!(%xVPgS*S1d-854j+^mvMZG5w>Gp!N>`GB>n-Zdc1XE- zxs&n+in|3w>6@?E@lw#cr}UyQ!3gA783bcl<#9NP2&DkWzx%sgB-y`XAUvkYet0a_ z$H$TRi7{Hj#|d2WNyB7(;!tZ%cETkcSVGlvLKBk5j_i~TD``H*p{iW4r7FE*6ut?^ zlmXkG`?Lw{R3F=lqoP*9Uix&j1A%_w9H`;rO;O$<{;qm)tIstQ9VjY;;lXsT`#UrC+J64^g}btHs$_&^ zgg9SsS}{OVzf`PH)cHpb$9yGnTXt6EDErHZE{96Odq#DXcqCcax4AowHFuFM&VmoK zGOY5}_Lv&D$_^#6FolnI%ZQ2n&IlTXtt`ncgEgp_sy)en^>hmsy`H4I88>p17mf9> z6-T6)%A_-1$*nFiJcU!T*WHn{*XIvfnoR~$zo(3SFr4xd=yawWi}ag@SSK``rnI=) z;|w>ncfR)d$5%|M$?6tg!cg#??&-KaeDIddZ(LZTGJ!SG}!%duUsdXZQx=@pslR-y2 zxQOBYZQFJASR+zg@YfvLWx{p4AF%gytXit&nH^xc4r86mIO-IPG`@m9DFW=^bfEJ0 zy_+;cFgMWx^tQ8Jsru|G@R9#9fL5NdqhW)0t6aWfcT-VHcwlLJQglJY^ZaJ&a=If| zpxj}eiw3o=u>>Mz0kmcDikIW?l{eH@=KhzXE7uj{#Nc(p=Am-^Br%6~0MC2TzyP?l224z{>SE&)9B*I9;KsGci$7hS*bS*1IRQ=?3SUI4 zpvYD%A)=qo9((aj-8gtuHu5B=&i@C!A|| z;e?=VUMBLzBQ=s+iJm*s5j35<1sM(}Lp?QG*O>LL4xrxaX_~OnCPg?V18xa`^9lfG z-}4r8q>gZc^?QS#^Hw91uT6coD|kUP_GI@G2Ixe&pW0qe!+I4|xHr+&<7FNi5@~*Y zVgN^5(M@zFKxb?sYC3I$+I|*>n{i~vlo~cSq&`IuYq~WZmSb3(Y{i?G*2BKCP~UYV zr`TB@eH2ixpYIOZoC?Tjv0Id5RtTJiy6E$I>BrUya|JGePK3a%(hMZSou-aRzDa_! zN&)dP*fJJUt*QUHaAZ0t*>(MhEWm)~IefFlHv}G^;Hg@gp!imZo7QB)YMsEp5n|Ll zjjX6h98A9UKk@o5ek~YAhR7nX5{@p%0<_}zk9AcWp%9&@7Ewe?7`J2(8>U*e6(J7A zh<_FT{)3PqBEHgERpyRnc2{ffJPM9CZ2L^hGmqXWn8gg$lc?LH znDRrJ{#Qh&Ab8p@v}l1da*TTU^N@l&x?_x@)A=!1E2uU%T~^=b!|!wl`j`}xqj|pnBU`G&pIR^um~nebT{|rO&!tT(vt-#3{H`m1NLLj`fPx&y4hqy!{rI5( zblg*c*|^%Y{;P1@ESCQKDowfg@SjV1bxJIK9C+&MBEykNSlc}o+s>en`5z1H)tTk1QNy122YI|6*?60w=Be3Qf-IGO!Y5(+j#8zh1znPuvD$a_Hm^!a8CK7e-y22Rhd84i_+-8^hQ6;P?}=Hk#MNi z#w>MzYu1YwV4=%)(Kdje2htntM1_`-Zc(?X`u$c+>pvA#g2_2>Oa;=Uj5U~zICR#k z=?-pA1YDJg(e(cYe~mvR!rSM0Y%}o^D%SKFb8as!n=nxOiAZmE$8-<+MeGDKcuTT`=<=la`)R&EsI~G3cc>#Yd(6Bw%9<2^B z;y`R^&7U<>-9&80k#rLM3v;vRv~b3x*yfC+6LTc5-s*n2)U*-!!*C!J&@GZWtNrk+ zBn6)UUw4$#9OI2Nn~a-@&8Vufu<7C1v1QlPoHQ9z&v`9dddoH@=8mt}0D{nQEwDEotb=jXgk2Fh5pqOQX?=6z3_mQ zTmfKuKH$_gZDYeikLB*Dhbkv+b7&Mpq|uxjSd$qtT4hMlfK<-Y7WVoNOTPd2bQL~! zN~|4PPLcz0Yl%Mwi1aEC02&NT!m}#S1HoHP!;UvVX{!n~l*5UjHQcVqvSN zIlG>#T$P>AD^m`rjGu(GLX2wiWwoo%*PuGT;Dhhz!w41ma@Z(i zRm7v+#<8sd14diWyTLixY~eLLs{aLsujPDYT95@sr2Nph&O*_6cPW`YP9YNABcV<5 z9WBnM?|*We-`VDAOMZFHyubKxy#Ehw^Z!LQr*(33%8nO+8z4XsdC3W=yD3VtG;->S zAWUw8z=hB^vZagWa&5@^JUsegqdG}L=!D}mUk?tZna#Pbj2U+`WHZ(t=u0mcwRJJ+ z3QfV!8CSwG>!ujO8v@bsqaCW=qO_U_qX~1Y&b>r8n?@(vQ-)j@L|i&`J!=HF_|ed#zW8t0sozHZ-;yiD z|E+~azYuCC2LlTmCr3IxJqsHPCq2DiE;=y4e;P@TD2Bi5_tn{7BT@hVe8SAc#>BzG z=>Pet9Vc1nW{-u1N^zf~K8 zP@bP8L``&~xHgb_LJ^JINQm-56;W2lPC*2^n_#)W*NbAF{7ZI_#Haf+jsfS-9_zf4 zhYCYBk&Q|I zbtjxrdh>L*ho#$GlMBq#Iewd4=R#N^L^;vm8~+ zYWUi~F`e+Rd#bBTcpoOrzD@uFv16+dBS>*Az*)PgqJk4tr43s0C5IkTr``k<4^{+% z@-A~GS+NVKCB@iXvD5DYwJLoPuL*jVx}RxN=^ELDN_099V$w??o4ycVsUh`+axIfq z6p4!EYtlz{E%JjsK)AfcViRV#oBmD?u92Obvt0<9JEZ6lSg5@hr%@5;Z|MumczXAPn*Ujj{jV&g@9{znuJkKl^p?X|-{i&0jf%3u<#6BESkb9xS{0 zX;2UI3?Lmi5yH893I*y!C=qh{g-_4WB~itM)+T;fl9muA+ZHyGWXK*ui3IB-RsMuH zFC_;;NRucjD#sbBSu5i%5CKy3s|Oy`KW3|Cag=chfhe8qM?kmul|^|Ri7H;OFv2+_ zNmmKFJDeWFF1DQe_WabE{SXUQD7jFenTjzY&B_kIQx6LPs;Flo{#UeFo*;bqwFyV$ z4X2W*gVxl&+8GXDIlAI=St^ip$RL8j;J*8_R%v0c&<_SH+}gDGI$Ee48OGbnXTnI!pTDKvy_%^N~H2J{rtvVMixL6MQn>c&aU;0TRM;1n-fMb5sKobILHT)rjYEskW@bkxsYPf-iBp8M!s%dXpN$Nt0tno=N{iAH~XuZCOHttFB1MYRJT{^#C{6tiM(8@?fn zqjXU-p3Oo7<)g=Pc$h}eFptR$+WxPr4kJ7hbw%+=Rs+hvC2CBcyOY8^=INv2ULDH6 z|6LD5)&~$LBKg9v-a2%je zlQub|I87$0K*3uq5=wi5{PpR^LJwRXy!1U!ww(Y+xC~WUOoe|?eEs@STrTYXP{AT< z=ARFGjy1akFxS!pgnTWgmd@I$TecY{guFAS^1*lfrwiSmXM3JoHc4o3!yt54TzT+V z2JxeQachN}&ob)YNzItoVZ^h*_%!uK`rpaF9XHmuerz2Vw4KbJBQKWSMbQ~9=FN5s z<+^xoxa7{UJm*gc&~U{b35)5T)Xa0)Bk7lr-49{wm*!}M(fJz<&kHVaQgR223Rx?F z(RS~V39x#c#+<^3%capWkrG^gjrvCf0UN|2@|w{-3!n;X0ig;6Rs|M$-!&r)8#GKBYQH~tC#fWMkOmBi?L{jr@yzBjC zE=3CyF)+?)XCa2zr3~1;=PG4DS9p2%U7yAh!Zt#7H|Rmg5z(S?>3`bP3)UBW@WniA zC^d9H1)FVx`O?0F#gF%Gk%lrZ2QpETw87#mKayG@s#_AgM&RVBm%U>oS?OF>FrW@B z+bh&W-UPJiJszn!+NT991h(SWsi++}T_nTDTf}~t|XHaqAWlS zQFA)e!9*NJ+D8!%WQAo*j+Stemx}?=eSFeX@k%Ae?*K3$G;n=dEkBG=IYC{U%je2} zJ7!3Y5BmHsn0$DXC(IZy3d+hHD92k%6rDbE)}W`+5c|!5NlTI#D;oa9%O^WJm{hgV zhnXF|6Ti)1I*EVHAvN~OMI@~fI1Gbo&`NY^j}#5eSGhA=FU_r_2@5ugbjfEq zNfwJ-GbWMotVl6lQR%;xvP}PbkRALY)idblt`~wk&K_WM+I2We>@N5=IhV1M~-B4m1PW8O9tL?~z2fBU^ZB=!8qj zjknWH?`@CSzlW-u&2O3*c%&Xn{4@S;C{(u4;0XAyc}FH3j$>MjP~KBQ+d}E=)o;Xt z$PfqwBq--5)92FRyMse@u=jTjAXA^}aYh1EjSeyNrZo~^3yabx6OV#Zy- zGx-bAcvu=K1ixoK?NvvhGa*UZr)nLAm9C-Gt1~-y1|NuU=l5I^g!WY=>C$=X#?5wZ z_x$H(R@&MS1K`(QFM8Tls7%~?XQ z$8^Mh6A|4&DQN;0%WVB!n2p!FwK1ENTi?c*pLiL2JAz(cVVzuFm%aJ$_L}*Gg;aAk zr*%)_siom7G?OjuThHfO?J3=@>c~8V;hR33cywE8PGK$}TKAPFjGO>;N!66FX6X~R z&J0JH%WnfyKZ*ApU@a22+sbE?Q70s__#DmAXkY(rK*a6Ldh78!?dbyt0HFLI#6mj* z2S=0t(lAR_*|puENAQ`|GicylixM@8Eh8*I+?XR#t-dq}V3rIVWF&Pl&MY^-^xeh0 zp9JK@qKtR!`MB23j(QO&Sy8|mL=TPABO^Q2WN<%)NwzR-dssCcLzy?6$l6-x1rOqvcEMXOc=P9A_~-CW3yzORsU4+}!M+O*ABuJ62h@}(8{9CIOz>nS3$=wQ6SRFz8V zMmWRvEV*kzLEhi!SRR6X&so|gAPdX9^dhp?O@A)!RD2*oSz-2I(vI!Ziy&yt zoGviCdCK!j&W6$+RD`q$jIkmqWf9o@9cg)VCJMFloHF{j)X-inK#uGP5uu}93XSv%D3W?XBkqvzBM%#Ta3tpt_gNmxJa6r(!y#iDjdO4PQeQsJT^3E{O^_LbPIg{2W9URW!cwd4~A{raE3E%TN$?P4BNJi4BNJC+h&Gs ztK+Ngs;=MHJ!;hd;XK@N9_}7z@3GfhbIrA8=5Xr%wB6yCM0X}7X#ltZz3r(xJez!g zjNDpo9|Y39Cjao6#&WM2MrYbln<_3dzN8l6vYCi;mbgcmUNb|BoBYG$38!*jr^Z3~Db4W-d4LM~6mKN4`o|V3t11wt)=Xpfkor0)UVo)v1-$RxX{1OaMG)w4mB)@3%(LnfT%!UK8 zE$GW%ld6YWSQd%kTcR{75gA;EW0ar8*!pKya8yM*}9%aLrB)^1|4N!sJ=rtb7>nyiJywa*A z>uo6-u_h5*-W)Gr>O!KN&f2rDb$P0idwBNF1#4uk@-rC(sq~ zkl|q;yE};gyg9^k+Tj+zLmvP4?eRace$Gbr|8leY@2p>Dd@oEt0bKAEZ_s&zzzNPd z^CL*W1!M3?jj*Y*0q*@paZ}Y`)67&rJl|+Crh=a}g<+x)q(1|tW z41)-4V3nr@(x(EzPco?K9jb=60n(JlZY&4Sm;j0GAQ zMR()YE&ge{Js4#jme)opxMF(>EN-qf<9EfFJIZR&j_#4d{ zGZ#c!RI0%iC#sV^3!zUCzGexJ1;N7*py}XE{p3(zT4t1BJuaI!Jm~#KG!W`zxX-Tz zLAxt~kV*Nu7vZmL{Z)OMB))SiS@<=|1E?_f!P~UjASHrYhghzneJz zzf6|{-GA+=tD%vt<9|3g+PdL|*TVSH!+}3}3X6PztJ=BP!wYDTH;B7Qe{KJj9=5~U zQX`p85hK#lZ~-e%4H2=!`E{>?7hp3ec0>tG1Hjl>`BBOA(S|<|(WUbGZv=d->O*Gd zcQ#J`9mM}96#AbF`!5uFp{8xK&Wh&sRIRtj8yZZ^uAS{qE^vyaSwr~Ub};D!q?u4k~s^Bo-+1i$<3W+Bk8+48K8S?zjEZ;aR ztX(_ir%!)$!lgzf1pcs0O73bSA>QxId4 zJw?{L-y9)ZUtWI~u{s|2(?oTCwVYsb%ICxGHC!xtejE{hs2aregDBy)Q<3rw{~>*P za=B`=fE@)Tug`;BWpf_NM<;uWTMO~%vNE}QZIMQrwq)-2s6PBvz+Vs{f`5Lj^!I{zw`o202g zlN%zw2L}k2dS}hA&G(xaL%kiM;!2BIFEAJAZht>X_|}NhT(?nu&T_o1g79JD8KxS?gwpDt9BME4z& zXlp%_-&ODT1v9iXjvJ$fxTqXg22~5SIB3wlsAI4!o%FOm;l#7gO^F$LbZR5mFiMVv z>?ZpZ3RMlU9I|}dP#(dxtRXIj2SGP9F{DXCId;E|yBl(ecE4S83hESY&{7ak7u)O` z$dVqsEBnibf)(^i#SJEghT(MLQa$w5_+DO~{K+__VpZ?{d>{3+_CrZ+rg2{L0ltLk zaR_p)s&~x|J5BGaS$qX;yjGd&F>)bF!F|9B$+^>N&@_>On|f?Tgl82TZA}lh_D^Og z3#QkHQT}5?^26ZbX34gx16a$t=h>-G_B7fA>?tVX=f4G9Kn4?Q#&^Jt`(MHlTYX1I zBl~ZY-S5g(kJJjL3C739WJR!L$IMnghsR?UE)r1()~Zt3)RSJ+xG~nm5Qsa~K=6AjnM%j& z2zHfvqjCnqBJ16Xg}|21%WL?aW0IfD9vIKv z@iG$a;LZ6NCJM{sjjGibKR)jACGhaA(BkpOf=6gFcmL~@CF(_D?NwYdUAm`>ep!?M zsry!q*t}y74{{!_xdOdIHUHOS@vb)(eB9oxR7v<(N!fgxKBh>GfGO_UMph7;G0y%%`=k$~ zAtL!VP0T9RJknQyzpfW`IFUMh=@IhI1Ldp3rFx7}a*s%1$xbA7@M}a7$ziY;X#Bh#fMFh6Z{o$y#fAfG@{Ix@wYD#v! zg!6WB>g;8ZD)K#BuNLih4pBf$k(`>OWp=vYiaf6lD~2!&d)J{7DB%-8=IhD{#h))X zf4=EnxZXo&(SI(~&4Hu7>?Sw7J^Mu@Bt&v+1jf~F1Lh5m2KV@#T2iox-SS)G4f`kq z%2fZ*?>OVw{l(ix|4}xWhUWhBHzDWbp9J%C-mSRI&(uB@>!z((-MZ*fv$gxIsk?n@ zpJ%_(;0vyV?$|{O%mL3o5qdT@j5aBB8K1BLkc9EP1ShGI5DYrYf{8c$mi0@RSC)9P zkX=ni%bkO8ZrI%3v}-0@Ht8ypB{+}A|3sDpO?Ah!(v~(OSY8a_d^ugwCipm|sv}l2>j7w3SdbsdznZTzP0zP!V^Z z4-EUYvL)xa2SU%7_Fli}HEv-QdI$bS&So8H>T42qPU*%{hni1go$2uQ+FI)fKne6u zURckrTzyz%k=XQw&B*Y?d?t(3n#UL^Nh=S-9H!k9ABIM-a%71#Qh^oPWgpL9W0+aHFExJ|~ zEKhNMytkVjX#*~Oy0?Jm5L#CjK%gMMOYyo3CM>~Q;zqG;RGx4wnq!pVO6QO(92A1) z$s=CQH~5KUklQjJ^)@wI6H%4_-3OG~Sa1b^z%vLkusMUlOn^DIkjjPcFpPUiKUKB>?OOQsf278|S7r=^UQRqocd@%HVIBKT67`Z@Aw9fiMlAJw}@M7 z5Ll)rE*u1s;-fsb7H>+WKG(`@{m-%N^j~w)@Gct@$XZHNHGKo8cHiE(N41;Ec#2%e zc$T;dwpgAhLE>x~yYIX7`zT=R5Cf7L)9Q|(v-*WGH;vQR@~vzHNC++irOvVJMtWk7 zC`;pHvYfYUw?zC-yU^#wYS}s$(J*Xfu7EFsaN@R*K6Y(owrzs{h&R>X2#Jmon-cUuL=$Fu+;N+DKd|#c7 zwY0amBq>_2)y*NP=x1j4I8ci`g)`V4=7rP=U70c;?K=}KEXFQ8*u&vk_FMV8(Ej*JKfiS&2Xh46l2s}QGHWt5sGls-0{9t$&jU^RwiHbt%t zOv#B@84Qn>M1`ai4e|DO$JMldAPiuxz{2 zQvKgimgRqc*Zq?f9!yv_2Vp=8zIvh%tVXLqBoKxnszfVDP#`aavvbL#SpA(INki1- zVIpgesE}+vIrt;qr;BftB)>xv+&680gV=Y*?2sGUtddxKZ52tFa(P6&<3|SAEn^Xy z*Iol`F9K!Pb@uTUS`VVDYF*holAFFIDI^!up*1w*T{?&KgCZh&-q!IHe8xD=h}XQkx9J)}io#NU&TvtG_)>vMwv>m82Xr9?*Ow zpwd^QanMayDub!Bk%AZ`yf$Ax4NGbk<)_e9rcLbuot{~YKRjm)2fI4<-j28Jwih%qsu zhV*g+$DHEqvv0rY?hN=HhC3h}_JAhL5%6^wC+FG;aBbf_Zs6*Etp0K--XJXfe<1A%Os#Har7wXznLhI-ZrPDs-)snV!_UYk<9hX^wxJdU z|8Ju;;Ot4+3LPPJk<7{XjO9kdIB=^hqx!%oF+%z}qCa*UYN*v3`zK`|Hp%F!5b+<` zA94Wy%0;?}U?0rF(Qpzz%U>1Om*b3(;WlnMiX>2?f3Dw0WSuPKhu$QVh%h<;X!G&f z8|2be8j<(0MZTxE1>E7r9ZKA4<#7R}PaTOFi{XF=7!+Z3yyf`qBZFEJ z($&B>6z+9XVL7^O47{oMJDPlz=!4-zhJ+K7iTCZ$x3?hqR zCH|14uxN{&wj-FsJ7LBjY3YeI0&8U*9n`AiP^pYnmaMf&K>39qK+%&9PRivdh@H3P zJ5!jRslP*=7h|oJ2(*vdj*K90Wu+_0*%$*tlc(v-v;( z`=mdd%c!%s>+U=(qQ$lD^)h%~H}J5{Oy%}_6z}yhAZFJYP}TLO=ya_>6Ri8YO}#Td z+jZLP;~dP?YZa|23xlVTMODRwAPOT)joepRlSs_lNJ3i;?ZX~B?bO>$jCrjxsJtRY z4bY!br@16Zr`&k^u=xxAxTvuhRzwoC%E=EuZ_a78K?0$d)nf%4hDyv8qH>y!n=jNw zSy?5*@zdc(En&D15t^F<`cXdDJN!_Ruk1eQ?nNX~H;ONy2Bf@IADr#;u|^@vK9Q7> z4}Ks?x)5}tTxR5G!}I1kAcUV9g@g39quoc5{E_vV3?c?_fZ0=bMfJ#{sGs*fw65~f zEJU+Pf%;Np?Ax;@><73V$2Nqsp;|hoI5Bv|B~$k2!kC>nChJ5V>w|Z8y7e^Z;p4h} zH#+^53$TFtXuxKe^aVz%y6NBrVZG7A2hyf|>M_k5=3|8^`Qs>rR|ZGd=zqz~VFd2i zkGYGZoh5eUNLg}M%W7bgAW)(wuLz3N7s?~PY334M(8C~4x5QH5Rp3x~O7@$>F4$BC z?}hh*5=5{BrVoo`TWD)3hP-0h{i19)+-I;08Jdq@vc0SJQ`@K z`j-$=p8ncogJ_tK`bn`o>AR*Rj>@+_NoDeOi{oZYJmgseIi7g?%;v!-qggz4v6SsN z_)6ED;@X8{cgQl4`-bCCp&%pMQP2;??{|Dr&Oh}7s{6uUV$KZ?I1GJp%aWUsk~et& z%0!uOh_I=r^~uD5pWm%WlHnO`R^D{rwWtz&mT9jf{qFul%GQ)ay`3%1t-z&!$GRk0 zNMVRm)3Y0fkM_#MXiGoqtnsrf+haML|z{(dYl`-Hup&;;PODmJlhlRbfG zfC`Ybc&Bsxbab7zn0XL|?j*8qLTQG~QOFY}Y?gNCGIg6WBi6(#NvR2r_Upu~*73bn zia&73a}s@24dOM>F1E;5Sn`dhOpc1)YmX)=M2vfhsbt`1|$ zk_`5p<=73{bOBum4x@2QF>RHc@+c$-8k?t%o;?1G?b zUy|bytLjHo)IXsOoh5xQHt1E)->Lm^W!N;#1~^P95Mjc_HG*Jp7Xf{ObuM3Qjrbnh zBFfZrT^yDcHp14}R~M^phxEB*T=dhHvcEq>N3mS>&p6K@J=O{WXr($Lu=~U1Ku3Pj5HrsZI(FTQn|X5a^~%&->|pz?wv3KuA%}%03M%uV*Xu+K1%F8w(5Cj zCYZoyt5lCgU>^n@bKNzb0E{L(@Z=$HS)qKA(9+p)XXTfy@wu@OPceelAbKB_}rXRKe@hqM+u7)6vX9MEA91B7P-eV{lo)lUuy|s{3;8VC^$c z^d_jP+gG|+$@1c~=Na>TVQ_npW;R@KFx2Id#@S47JF$Z?yim2zw^waKoS!Z3QJu|# zZ$FMZ`Bo!89aP1Z#^2B;*X+h z?$}zJa;%HC1dGkbOKZ*C`Z^C`*M#GN>w+T+vn)6`m;EOof9w-zTl@y(vfmJn>;DwK z{S%gVCTvUe)1wVt-J#uBgKvV4;P$2XAO(j)=>%2qY!aC%#4F)FUuQL)Ej5&DvNdAR zTyPvcv21gQTRIA2MKC2}=|zTzBd2+zqVG+J4?mEEGzqhdev8kUpLArmFj+oJrkbp7)SARvXB3jz3zI}>47$lrY| zMMci=5~K$EJh@3$8VHsUU}_%88sK|B`#1Zm2;D@X?h6f#GC>_|g2}O}@tv#i_0*m6 z0GGD#vj)ya4`zmA(Hi=&&T!88c&FwlNJxtry-DBlhS+yqHF1Lymd!R!{1lTe|Bpj| z;lmm0Z9a{G$w~bvC*`mQB~IR1wXj>|i7TV<;GBdS&78d_1z`LSO*Ohpl27^U?2pY2B0-8a^&Yc@j; zPZ~{s;~yj4HW&XGZ>Im$D;$^&0zZ88% z>ZAyA3;)m4;_^OJ91izgcINm$1N;9W?fC}#7dRF%>q9qhAK_8&gbTvM8>`z0DzSaG zX*ngDXyW05a?n{VoU=t@L6TV&oS#>jshbD96em-xGXQkB?XZmHDG!&&C98TP^`^`A zt^@UH)&(6*lW2$vOPKw!ll#tqkT`mZ< zS!hDO%x4?j)Ln?Yb}E4cDYh@$gp9XLHL7o3%6D(v1{S8FT6GW7zfr;6YnJH9PYgKT z@aJu$5!)1PDLPu(vBvTw%|2H+2ODU92jv671tUiC_UnJZ&iC{jK{Ta{YRO|HXKjfwVT> z3Z6-M(h(k=S{Pkx98q=m<@E;vC|#%?Eq{DlX|)PGb$d1JW2`EVY{=pa9k_0QPm>jJDd5?x{uy5&@vf@9b_Yob=ycH?Ap^QQg|g)XWn zQ7C%^dw-$+f_-TI+HNssENOGcI`d#pzB*aCL?G<#5(+oV`;V)&te9B^TDVTiD^8ue zzmLb?E>jVO0_Ga)%VQc8)1hJbwy>ZHNksXcu~ORP??9N(W;S7k*OIwB^b7^YGOoWy z;4KWcC#;YY+@|&rhCrX#3H`1@5rPCFnYS-owbj6if-x#k7^{sSLxSz;=nQ|-dh6iF z2bG(9GDoeL>D6b5V6C3jwQ?K!^yW;Lm#tb5gc?KX;|SHj+i;-5uRBPATEhMqbETE& z3Ho-+PK!gkYcIOOgf!vdx9(Li98Io^1mNunJBN_bG?!G4mw_IF3+3^@3LK!<4Yd3K zNftKbo;uD2?Q`f|txnm{X||gGc{WP-(B8JXtZ!s8P#gZ~ ziT1pbz~(z4q&BCT>DBOGC~`Y=K0guCv~FExuU_QBu4>mJN+Sbt+7B70AEzI3CBvg{ z;%aejGp_pAt33UTNCW}+5}^?OWq4=kTKZ{SEZVZr&MF%ToXrjm~uyM2>F%|4mc-z#AwU`(r(=XG_N4P*trF= z_b#86q+<$nSGF4ctJ@qruSSLr>pAlvhHlw>X_LN!5Xu@OaL(KY)T3SvyRch`otgoY z`Rk1NJe$a?C4@I$Ji5*48EC9m$s(jGy-Ay!#jRgZLJ=i{HRQ_{D`$Gi&-i$bmxm-P z$tm+`v{y5<__%M<39CPwi666DcambUNev7G?q{KVB@$~we&o;(Nyo^cvS;g@V7GYN=tMpaXEHT_; z;wP9-Sg^JxH`JbVbj3>pBjRfC5Rjl$)pi;yklFWsghQAhdcrb+L3kYc{EWqO$4yaE zR(K?N3f_AZJFi4=4XYc!P$e6;2xe`b>5jSZXSibw`Wm-iuT)5fbI#RUWClDX;>;)| zo~RdzNN1B;=z(Wc{hX|W^1|PzK|f+W-Tw{w-M~x9l^dO5VKl!q-#qe^)|RF@JJMa% zvoMDFmlnq!Or2T=hIMdnP>9WgY%R5iC%|`BkEL{v%md*Aut6U-po3`-g5B=5s=RkkPGo&$r0s&@)l z1QU(99w9QrRSk;3JVndgAO`Lw%!k`A?6)XlN;PWYt`5R??+1s2mkN+dm3YOIIpvTG zx`hbJSuL)@>+oWlLhCUzQBXpPT+Rsrm>V#%CnkFwx31bXfU(1K*7crZqDek3W@H5> zo~KIiHRwhP01>^W-9UL|CH*!f4<$9+w%Ks+Kp|2kYL^BgV@VE(8wTRYvEjc3aB4l)igPsDIb z4nQFQY^1};(qNaZRFUaU@_A-@XZ|h3Kf#-iIUSmOS`kU4x~h_J?kF)Thu7^K&%q;w zRw}Gld{}YH#47TuP5wntht8+54!d;&utz9X6cbYxVhs-3*~_JEbC-xp^X~JI1YsP= zsvA*txc$*IJz5Eld2|Wn%|?uo)LdHQKb9_A1=-cGk<`H|e9EJQL>m1UVZfP2H&Lmm z8STbp11tvm1OL2c#-%kj0uP%!`uN&CDL%7hOkLHg4Ih8%>4R`Y1g`Dp+iJLY?ev8x_7MSi8CFqJCCRLP<;?NBtgneNy z3)q5$dw=9eQV<_*Op89=f)!iA2S+K@bys=NX!)ILm*9UvRq&h!Y0VOkSlHinD(!Z= zt^*b30m*rJA$zb8z#>YfwfV9^Qa*;ZVTWK={vs9%xWU~*@*bx`@a7jr2rA@PI2g$d zQGI9BzH9`kkGw=RRyWsa=Q@mpI%RTsMQqmI!Xxlsh;KW(`z+-i1?e-lT^JJ_Wf~CP zKi7cCv(Yv`udSlpcpMK9+8B|LP+ajKvCh!`9>2bB&MYF}j9!+B*$|nebl6;n(}w<4 zIbBZ9LE&eOQAIq*tGKLpV@|b=s8aMHryGGsuaUf?qX2ebai0_j*GMMNuolb-{mPt# zNGH1+!tKaQ&OMio7<)Vj*cWTB;@jVb@T>?}#-Cg5IJ=RFX4HqmlrOy1tF#Kj_J=Q( z25qBEANnhJQH=)6eQT?y1%s`|#0gI8A_}p=2c^_G7yIxbz8nfg#(2#>q{jMV5OZ`w zCoSJK&(nC$Hct?36U7F^+C^=>lIq|!|BA75|+t&hZ%1sRjianPd9_!Om zmRDQ4(c&$u$WkuE)adr=F3(r2_oXkPJi3E7S=Hde5bC{dc%SwBsw!p|n-@xF0fvrv zSxnL508#jpPqP^<7^Eq}I%!%~2?><(R`z;70s-HFh>cPY_G0E!M(vFackR3255>5< zYAcFVwkAD2K22H^uv>8Z%^Qz-`N4VpdFbJ1(~Dz)HF7+%z8Kj+sc10UuCt0ib>t_| zS(#G%W-B1m0-A2Z?NJ*OpKSlkYX*&S>~+4g-@xyO=-;wnqyLgx{&$_R)da)8G?ziU zJjLWca@cH9TwLPhBw@hU6PUp}J2h+_ytm&1Y#Bpy%}@oI8PEYNv?eOWaSq^6)f7^c z$q&@OPwtgoOSDn@l=f@Xw{QeJFge4I|4dURr%>hSMhi79bvS{0}f zSsIEm$0<4qr%Evy8X$4=9`!U8Ef{_|Af*xTe=GFNLVPj5!UF**3jzUsi$VV}g}_+f z(9y=;?fc(1|CQmf-oAap2)-b-Iw@_ag3{88{n4=#$0-m;gv=&1C3UY9N%G8V!1*r3 zyiHycc73cAgzP|p-NZBX@7l6wU`4ZfH50paVTIHdyUCnxcAY^gG+42r32io>pGrKk zY)(pVZfR*-A(`mHtrvBvK-uidGSK5b0u{b)f8AY-zYlKfbUnYx@_k`~xNiLr+~iw0 zKsxo&uGjlYH;C~G4(4sBfn%^$y{Cz!X9@I?J)__Qv~*eGo*Mg;M>JWS)tR31nQj5N z%6hms??Woi-6@gkXsjL^mP0o$+pNuaL7Y`*nQGlNl(^E8wYg@TNAwBlca=M94_aD? zX1K!*pc9PHH|Lx3tlUxtI36;h(zfq6Tb`ibLpRAv&fhLwG!R~%qeKhhHK#$@YjdIeaX*8{hAE&RqRw) zvpg*5Coh%#1VH9*4$2?Y(2;hVzPT zON8I7O07XqXx#d`J$zUUq(Qoq-Xs`qNvJrHP&0;sO_SDJS>8!SZMQ;}oSmY97i1c( z30(feLrB-u@E#g`1eg$3Ms&v_jo#-mGGro`VMn?(kf~1fXR%o{nz>kT&5N z!O5<~i)E&eEI$P1$F}iA#EW07ldh5kauYw{KJMyT*&6`|Q1<6~ur&#F=F=hnl-{x_ z-QXnxFg?CQ5VE8+C(*e82aYtGr=?PKENjb)6H66abA!M8nnG8ioJ%dC!jed;oL50$!cJ6@1mMThH zGagw$&_IxQ5Orp!m21oAq&soL9C>h{IyKsIqIF-g=*1l;2Lvb>=qDnpNRU_X_8K`G z+x96GBoKNxTkkXo)W*;$a7_CZbHp#RMiR^fmFBy*03E2v!0k|Rh7bewo^RS?;>MUf9b{J3BpcJ8r)^Zu%-)uo?xr@8ro)ykf1KaMaN9{HO^dKeV z)FF7dtQg4rzUE~$Id2iCNGs%Ml#^irOM5Bv?L^exv!2d(CT(D{8v-xZ2eP=EKyTGN z&d**M4&UN^%RVEWO8ccer%zs%bdSpJQ+0rx$^ZT-a78q|yyL4Bo%QKa;gQ-M@BmX# z12E^#7@brrc3)A!HsS?4Nrcb@Om#$7z7uM#H7UUL(STq>BCwLTFL! z5;yL;qCFDITl;JG7r1YQKMcqlMgl11D_Rs6O1rr=q1F-Kz<^6OFdQb@BkN*|2BH+K2*H>*(An$@G6`!mWqL z=~om?O>1!h2eFa6{nV%Gs<%gL*}~WL3LQHPi9Mf;xF=Lj2|k>Z(3k7#niRWm4@xN( z={YbaG73t*5Tnp8R;0_Kto#L_pS1PHebImF6L7IIjU0%g+a47{DWSbcZlj7JD@Ihw zeod;W9)bdTj|j<}pn7R2;Y9{5jxGjaVi??EyJn7U)ux?E&{*e$avqT=77N1(H}4&% zOH#G`HD&I_8}tqhowp_^HGOs`P%h?DyQ{A8tyw-xk|A5<4KZqiRzHAhRcE}A@z`mA z8S%HihdRLcJTzsm44Ro!;Fu;}g?|!XOy@Xi)&cQ#cRoMhdKZ-aHefYIBktf}@Bvda z&H25>EH@Wdnfj+=Srpf*YYBNhHf?nsDQM1f1>%Y#X^HJmwO@on5KbSv#E1hCGN16- z-|_d_fF(50EiFPfE%w>~P>tGZ_o9)7`l9GDl24vAl zD{P>ll2U5SVLOf5XZ#rHojEu!h2=iqugu68LURD4y6B%*#MYGoB8LDPpLOmrzs8Bq zPJ~dpk|YEn_jCZ^pJ@1pgQ6cYlq0AO^tH#sS+D>r_52!q%jHH?i zF=~LlB-YKXJ>f^$Q@{{FKPTinMj>$p*5L!fAe52mF8;KQ*f>X;)R=;QN=i5Gpe^~C zt|`c^!?q|1kwN_Eq*}HOP9D~+=}M7u+jo&DVz9H{k7f6}-S-U%kS&6VMbmopP;C~A z#lVu_!TO^fnDTS%d5xrd5Wb9ZD~>h-o%7R-Ne)YwVuxmy-tc_ z7{;_y`;|R-QjC@*3Yf|IS35#W$I<+ZDMMrtd4>WMm8O^MS^*%8(R*n?53?&^`;X$g0myFi{ zo=3ov{2T{73$m6$%pZ%FyhU&~igCVGqZx0l$GWm2hn;x9xaL_XGWjfb!`b`J3+S2oYv zuMa2CFO3^>+PR0ln!gBS2;509i1{~nDU}%rX|g*s!Nuu3GPA68CMF|e0a~~<2ZlI9nxolSki|D#7Bb-GZL?~}mWsA= zzPDVd=9#w5dJ%|bDmsg13Y}G|SZZ8DhB;Jr~_|;M{1c~O!vH8 zr}wo!mSAp=NaY90YPJgffH4s7QAv)dt8saBao!K z?X|wQdBS(8~&xXN_{ zrnn1nzn&p^^XGcDY8MXW1V^)_s&^ud8gMo1b)xgybRaO|;tH=Tn%`h>Q>nPCNEew8 z$7O0}XZ0u|T3&pX%Kql~5nqQJ1?dpDv$T8~96a?@J5bYbQ@G9aPr@hJqkW2LjbgYy zV2#zt=^rwj#*EG=upB6CrPB^>HE5s5YM$%Bn;I%W90%uDu9euIQ#&r^3nl%9N;j^4 z-7Wn)ob-W-p6}~^?GoO)qZxGQ&#=JOw}E>Lxa#&;}(r zMvCYX*em2L4^=HaqHSG%(nYe)!y>py-ZAI{FLP+pmqDxB1)x{g`PyIq zP1GKTVsp0lT~|K*?wP^;w`|ee;h#ObMhemq|ItIC3U?_CF}$3_4$q$5Z# z!7P!m79Y27B{<@9b=>dlTpm%GTKtrCFQPFov_3W)16T(r@nN3%Sy*vDjhU*VwK_YC z$bJQnG2%Wf3!LmZo{Q^7^F?aP)8w3r>Z*!(*oLB z@{@j(ZpE6jKgh7Ib_Bef(Lsu>fr*vu-LmyQ+ktWD=lOaA=@6ptdAa+5d=5?UIA~p}f^zd8f4u7k@=OWCGA|@|oNj@u}d99+QtBQJ#59wcOG{ zAbwa4tnjP3o?9+v>c%j|cg5`1`%-bB6uE;4r?X1Q;357?ifcGZ^{CF{mh0{b2ebC{p#DUb^4iolQSHGFlRI$f> zU)Aavb2_7&)6-MY-rL63HhoW*MEj1!vt*wx#^o+G?J_40UZloj2r@)h(FtKU>U&}5 zFJke6*5LUBi81?(OXgg7S|mo34%aF}sfK}>%MnxhnoMPa`EBtLz#Hzw3<;r05Y9ni&esuOSBIQL37lIr`#quTGUAhh1A4pa3=WeHw;i%>6g z6E7Wa7-eJ*&LFI2`?S-Q^R^k^uNRFEO`{e8k+xa>S5pc#FrrRx=nWU+GyaTGBXvgb z!3hFo75Gsl^<5Y!`zH-B6B}SljA@g|v9>bDn4Zu7h_wpSx2`dTMq&?iD%1Ri=FT_(4 zAc)83C=NznA^~W<9TT0A^R+25!kXx!BdoU)D2?ueXyxvoixu3vBA}&BWZX6o4P@s) z*kV^q{#hj(?I%K{A8G#=WA7LoX~1^t#x^>(Z6_1kww*~b;l#FW+qP}nwkLLS@|}IK z_gnStbAELH?5eJQ`mP&mt!vs=^qy3Of5SY<&zE4OhnNVGED%sITSkW@#jFN2gPe5A zl?YJsN{%!YOA$@G)aR7?r@1QX4bl$DG`4KTfqe9~2dejdi#-EXL(X;(|rYSSS zJwIrusdxG-W4$iQLu7NqUJan!1U60|OQGnnVi!1{Y#T>A)DvCm9L^dE#>hwlZz?Wa zjYx()?OAiSc_*Z$FQ8r5UuvvgTJiTUV!6Csx0PtcUw3f55wwuK=)^6xRwND0=|X~X ze+^7nDB;!ZDfj)c9a2vL6^0A8LgXji8ZOI|$L0d)V?v~@Vn_XXT0Y%{^O0qcRq+Zt zRYAtijQzrz-zV0FrAY2I)TBiA$#IQaX6e@Mv=mjBtdrCQ9UO;MEcF9pb?O)+z=@dt zm(~Jy* z&8EpjgeBCR2}%-w71Z06>LbouwnxY3n$shz$yaN6KeMLh>3|Y21cy!BG02~t>qp_s z?-+pOQEQJ1x~sU2M=fJ?sdsGKiL7eM0hr5Rw?UV%jPn&ZEs8M`X2R&~SvbRIavXo) zII<%!Qi6$0Zv`awn2ermJJapP&wMSR%kQLy=8Gftk{`R(Z_5XXI3@M)KkCIwyOTF` z+}Ba#n{vQsy_w^{m#f^IAST9P8-hqN?6V#^za9idp{(@SSR zv95WjZUO6rgjH(opG}6pVZaZ|0-8lgr!~5@8DJg6kNT049_itfUK4@@g$%SX_wy!6 z=?reujz%y1Gn56^G^E0a9q>toJl+5}Xv+hXC5mNbIu%^)RZMQdbYxKTE zTV9yM1+_?h3Nw$+R{@&1(y_45s|(mxFmlZPqVJm}HHM6}nz{B+hlF)o{=d=(Ya&Y} zlue4@;Hx0=#rw`!_WnG}VQd05P<-nxnDjXshPXQNaxBn42nOWFJX-2g$taRQ z9UDMqb#NgY`?DIbLguxKP^$v_3$nXOBod<+1jEcDFuQ)f<39Tyq-JOnS6*cyV2eKwMhEBe< zb4R#8Vf6X0H-3i6je=#-ml5hN+dAf&Xs4*W+Qdw_dqbOFT+=iHu#oQX4#hr>`vRV+ z|1OVwX7s);JIyAmA;kX*ZrIef*qILk?tTO^R*s!RZNN@XSmrs%sEdC%_Mq2468JEC zHCO$pLhs+POA@jiyS$#t&1>E^Zp*W5FBjML&*tJlr*^c;yDxF>Pr0w~S@CDR=GUIJ z22WY8Z+rJkHVzCQGk?cGE<5J);cI-*zAttlZCmX4?s8fhh-OT2GbdwaXmPslnT#cs2$i zRY)q}BdUv>X;QJxK0->QX6B`xPw_oxbwvx-Xi~w2LJ@oax)8XyQj6L;Q|YrEn%-}^ zgZBtLxO_*Mvza2Kt}ur=?{!v${!l1ne*0^HQGl8jdAI#4%~}mjG)N_{ zL5Q!R16t6A>2R!Kxr5Y}4OdD@|H>C{HIh`;3 zt=atl40t{_?guCMnPJ>vfPk?7n{RDsXZc^+MpfD0oB!nmSBKk(fRDC7%p(r^({Yi>cn;neq%0-&klYzZhXW?jGmz(b3dhJn?kc&-E0*d>Eql>V>_*~F(V zIJ6`g@sU?rU~%Z|C@sEx={y?yi5o9r1L}+CY z=VAbJ%ldj3sWq*l0kTLaJs^@Ox0;;EpSxQ9a{h_Z6b^joXPemF;gc@y3ENJC1g;Ey z`-uLSkx)QgRiSGjQiw2};6bAxk{Se5@;jZ(5H>-|eBR#hkV?(tAbGo&T_h~fE9+Js zPMX(=R{_$Bu$2Oo?m@FpRdX#G+{aENc>okm6r}3r{x^hiJL`Xtvp8E~ws6zB0v3%s zkb)vfmtp7KNY+{_A=nP9Hll4oS=WW;Xee~f8w(iDoG~&`i+}TC`-+O8d^&?RqP2FV z5w^ubU~Dc0AAW#p8!l|TBJ6fv!s*@qu;WVZ;?{Q7RCZKns{qxYax{y+vsGOIL0Wjb zP_ulUa;nsLvKUtqt-r5@&JZq55ZZFatGNg0E+2*=S1vUEFaeGbefmBoU8lV3k=YRm z!X5*(#aJ}(4k4nOlApdMu+5{Gw<~uD3z6<(SCs5xE>*gEc9u-A1_PtW>rRvNp^12a zy&e)Y%-tB8h`#QCAcdylUf_%=k0g9TGv%V^vq{AJ*^7ea;1M_ZZ?nRj+0(wWo!cKb z;`n)HO(=WD|Bisyd=v0Wal{z#k?{4MjL5Cvb9~cu8c6hYszU*$>3Ts2nL`;2{Zp+b zA?>cQ2WtZD4li2!JWgxMW=@&A#waAKxh9?NQ3(X5~k%h4AGOBr&}!#^m}*ocH!Er{_f z*kY@#as|_ZBQ)v;7h*}588*MgIpW!%KNfVzX}xE67a0V-P@ALE@f-`$id(fD_J(&A z{h(=8coT@ZZTQ`MtBBWNtX3`z^!~PXr0oT|lUgkcN+k>LN&tA4<#8Q~xH!8hCEiqk#z5gSh z^rsm9KXeDm|E4njUq9jh7V+T9620g9qbYy?K^y#M@Wy{U3XTl_?=!K5{eLb3euM^U zRM=FSG+o|0nelJ9J7lfAk%Mi`b~;&V`G1|~E*|WxIbk3EJls5{I0Ykyp_Oe-Zpafg)Ji@~Z$D=zhIgwINlt;8zQf~|iMIJ4gB_!dzt}DDQZa3ZC zIxZ#xfl5%JL|5>ZFp-c@QLsk6bxPN_9xysD+T2^yoiOYs$ve5Q#l|0`_;he0&gf<4 ze7@XGA;Kb2FNLVVg7C}LQLJ$7E=+}!zB zvbFD}kEZAdDNN_HPUpLB|U+B^e=7H?VcgKB-2 z{jhQ!T~l2ZL8m+BE;)MlA$iyfqsp-sh+7*YWWeq4kjg_B-XhpMqO?lNl`1f(D5hu# z(fCO>xy4(N%u5c^`l)$VW;cX597NEJdxDLi*o(=)*I7I74k%4%6xdaG!WKmK&q1fW z1qv|~W<${yI_Hi1eiv1IBi$qkg*sLlwocg>=YEux)CutV(xU~JK$)c4xx3Wk`W%;W zAW;zZz`XN|?*Y*P)BNELu-h~X@ENs%-}t`OO|uG2K;#O#MulvBGl>sjPD|o3Dse0X z(vkV}rTJZ0ztJCP69y%YC0Pi72sczhlAD@RUj&L7h&~DAU?D(2IEG(&iY5g5m^N&H z)Ug^v*@{wZ+01l9)Ko^-uY;6p*UiH+LOh|6C&V;T+_WEW1Q=n;(Qu;c5Hr-P?e{;x z)_(bAeSMCIU*W#Ev+TrRRZ=Qibp2qVQyy}fkr6%PP$M8)n(+EFM_W@%NlUZSV}X@I z$&j!<*A{GRUc)+h`PKG9oX%vDy~DYyXlcXGQ$uSA?r?z9CLBf+9~^ybdxVrB$dOGE z{5*#UZ?W<0I@q0qTqwBL3gLKit{gt7x|G%70#|TuJy3U1ATUUB8CMijRxBGSge;*B zx2DdM1w@)zY0;`U$|11{jl3g8G;0Cm97c9Jqu5I44{4T}DKizLzoOQvJJ_%|AU6)9 z5>>mZ{zR?-W@k`XKD52>qER5z#_|I?qF>(prin@fo-~<9Yp&r)ABbrM8ZOu~OfnBt zWp;^5!9pi`B*=b}_AT>5HXgRC>YjGG{lk_qBZ=dF0fF!7ogb}@Hglol zl2JF-9kWWYWkQ z>zULydZnAS(DJ>;I%L%pu~;_BB?Pu;3il7={|;xxK?XepRTI&4Cb!u%aSlN-cdZs% zPaoDy7Yqn%#@Z0G0QSCW5VOPysVd(@Vf4=7+1;5_F#^b&Ep8wwk~t0RA}cXwA8SM>4tj;c zkOq^B%YVd^_9`eF4Aq+RiMb1GFS|0K8+M4@7qS@#A+M={KNVz1Y9@vMLXbG`1WHAP z0Nt|{-14H$8iMnJ($tcRXcqfS@}0GSZS+dypX-=WO=$Fx_@)$OT}QDexGlYDR&5A* zpn}}J*b#U@SU_0&To**3z_kuWq^M890ZPRCnDbii#P@@fBk?17#a*;MWG~rDT-BQ< z#_2!4L9kZ3HvEti$kwrwpra)>af+^N8Tc(eITBUhBQR-iq|cZQuh8ov!t^@3Q5}W} zy9yCT*R)G|?O6*28rc%E5#-CuB`&c$$|z$7U3FCn;YfOH8ogCI1B`?TESey@bJ&1g z_cOX(q13ea^HMKMioBtp)qJ^n35XB!z5csSf+EVT=TPY-ti%>HN=*B8d42x+VA4iu zpB*D2H_drEMusxiZyAk_Do=KbM^xE)&oYyRu8Ef`gP$;_q*Ut4cww=OjIM};B~1(` zKz1&KX?@+_?!UAeO@_>G+uPvaJvSmZmBa!H1>X4twjsz6fl-Wg4&;E zAvi!?eCfX*ietpddUyiuhAz&9%{j&Xu^&;f!7c63bN^9UAj-3KXc)rwHyt~GY<*_l z83Bb;XihV%jh1zsm;(LUFC* zUXINOd=Lcd1Ioc<(R*C>tPpu%p~;QYP#|q0QPQ4*EzE z;%-N8{Nv{9)H7*48Zs~wXme0Gy3`66+uD6vNfAHw4dU87S2uKdKERTI_r_rCoj9VS z8}9Jvtn?TW9h(PKClS@>!SZx!R_@-|on0gI7$CWM^m?_fNZ*0mM&IE@(2J3+q)D1m z5fMz`fkzvHlY`i0msWVu>%?`iLDsW%pNi=9zA^E_toeM*$gw%KWebp3Ci5kuthu_D zj2I9bkuhdcnV%JC%N#jpXmK`+#;0g#a4I!+v3itfRYsFQ`uM|N2udMJDfu%}^=Dm+ zeX%vvSaurZR%Vjw|5jIt11_lpNjzLnQK}599`Lewst=#{Z`?=mz>hHY?@tn^`ARy4 z-@|J2;Gax!=P7m<)f@DEoPc)xI?jUa1rDS;^-*F#K;)2VE5HP&JZYVqPl zJl$dAj5(h;u*H?O@KnJRqZ`jB+OS;%A9nTOa=z1Fkok#tw7i|?#wSVJ(#wQ`06EFE zTZm328`Mk=kUOjGOtz4N^qEa2?gwQ)Wr_l5WjUSwUv&{J)vFN|SI&mt?gK_fjiduR zafSJt3X!y@O?fR=-KLi8gh4aR#TUCB4w$2VYtMOc>|~_394C_`oIz}dpf4W~^^@*f zmkb61a$2KvEW8C#saeJaGx3DDyp(~XKRQ*r5OkIlQPEM8T|-iz+@?(iD0*ZR14YeH&>5~!++;rDAJNF>2vw3BuS;VVFBMax{4-$uARwdQPr zl!yPJf(;L55uRgcPBs(km$FpqDu)l!%7)R1E=P>HdX!*`@O!ExvVnOr%f;;3Zq8wm zK16U`H0Fn+Y(J<&*?QOM`mGSMsr+UwJK6xbzgG#aV@;0XxQZxVxfm}cw(2EQaM~lb zE%-|lXrd&@W95k%!gwtpy-@4N7aLxU$1hEFh~VST=dtB5Xzt#BoKR!b7}~_5^k>%W zWz-m+&A|+j6#=$L?4vUT@D1_5F2@Q>5auNifq*Kxfq=;VzlI@46H7;@pN&x!&Q<(I z^KDNyND&Xy*=*y27! zxompXO!;b5C)$|NPWu`hYthNIW9GGXG*rC6s5-LaY+AQWSZ(+-d4_1t(ESXY>D)QBoUSIXuigSHi+QsyTwkW0 zeaWJz{;%#jg88IsmI1Wl?w$lPTQ#G8=9N;IAh?@GDo*FN9+)*i4tB0-s9jvNM70_6 zks-W_F8Ij5kK{FpomqnB_e{A>7bfsAg>C&58>Xn1gqAp;Qp0|4j08No%UVr^7EqJj^>sw1T}KE-4|rZ3fJE#wbDX`6f82W7TUjL* zrFC85(A7-|pe-}4Yt?kDW$^CEy?J=ES-u9#?QA1LxhY7zVOWFz`pWByGQGf2fH?)? zS-e z1z_5@*^oHxpa6Zq*>LQNuV4PP_vmOHYp$)g#Zb!M3xF&(DF8pUsuO^OtS~C#)2%<) zidh5{o2*)5``P>%mw|eMnbMfythj zNHKp!%U_lrl>Ihll|2{S{{5d=^wvGS^rBR&ED-Bj7a|01$1|f|NR$Gx6my&sEmT43 z)kS8Ff6E1sUWd_)dJG-N;pOQ#%0I)vK@uPWF@ey5^T(Qst#^&I2`Lhf*TbE&`yB7D zm4p>gZeg9>BytC}aNcq6+FG$a8RDyR)(J;UDbQxoq!Kgn(VaMP*)ePNnFgiQbmijd zLXMhLAjskS*+Ha+-`*NYYu;Jrms{~&(pq{#TDQR@zNPPf2Ns&EI~W}%#T$Z)X~?1~ zkiAEzDRN(Yf#(jPA?_%j`c1ANOdIoC@u}B_4rbl%c%cf6(Gny$$Y#n_l&d`Ou=YVK zq@KxxEMBK|<=))1eT^|&Wvrld4QHzZpc;~@d?NE{frG4*supAn+?4^Glxs1=A;d

V#pitNPC3$vcM)>LfhDn5nF)J|43FWDymaL2c)3D^v^VI0U&+1Wd zmE3PL(!I9$&NtZn=TG7c1;@t}$(FUqHo$3Fjhda)&9C0lB)k6udkvH7Q`g}DI75cf zSPV;Pl9C5xEJuI%d+8d zaYm4O!?)1KIM$(p>;tIZjS!WGrUZ^&d`w{XhKM@eSGVs?dx7_YnQEsGh;SKUy(;mA z^jM@RqUgd~Rh&2h4jAWMr=%!x1eh4o!6bY> zOdcB>#?UIb^-=`LD?;smZ)DpEN(0sV_$Ym7F47r(4;CXmJ5-5?~YwA zA`6s_N355OR~wTXQts-MGaIbzLX5BE4$#78&UdQ(>L6%UqZTV7({JCG;zEPX+yj{DI4Cz`2MD*i{T05*3rhs zS~`#S*g(l@* zteLqyS7jm9FPm&I@K_W0Wi6ps%ZYi05s0Vthip`E9_z2a7BzOIFn zXbXnwE|Ncy;sNwEPVaNd@q<0mD3UAF?YhqO00XDSZ4)P;NZ%&Z3ltX6$?6hzXS7goM>{HtGr zrH*+00#<= z)1Ghu(&i2p7DZWwR2OZ64WN*jA&eN(T)U&?XAEO8?1AD2zCsz0mRS&__L35*zYGg< zLP3hu19xg>*M{68|Fe;=Tmqv8jXbF_zG`r#U`MMvI=~sAFXLkFnjH9ut|ru!I6ObC z;G0o#O_?HixEBVoj!`T_Rn~bfv;shC={jRe)Uh@0Qr38j77qi-Z8^k5SQJ#Alv03X z*BKVeGq>nuy@Z;XczAaST3y_=Pd!}`{u|*8u`UvI$8#ZYn=hue;ok$C8~&#iY1j48 zO=MpbF+YKdciw{fV6ke}w|DCi6J|snvVXI3tbqm;|FH~430tqu)=y#kDv1o=Ey{&R z=n~WB8~?p%{6aS7-qAFKD9|i>k1nbRctzKK_*vFPVBQ{Cuyjo)?_)x#WE)LE zL>Zu~5g}q%@+C*cB%`JkO@tV~%UouSFAb952--m8a$b#=Aq(%7@2NrRP(wX|&B^(`7R(av zT>m8wm8*_6-AYn?*z$-I(KK)thh_6Io@oqJ#$D9S*J|7B$T@T(1fUjaa`?C*mHi}F z9^cLy)a$e={ljhvr48b29_tNWVC8H=3mejuFv(D1zNT70es!4QDQq>C{7tbUK!g@c z*PE&7oVt88-IUquaRNf3fjJwuVCNvE2WG+vwK&~Ga#wh|Y9Q+I&qFpeA8NLE`p^Bp zBT6tnc~<@V+q_INmudmlkr%M-Jr^xwHR2~+SnLU;7(XHHl!dZJ^SZt07Yr^5-S4?> z2X94j3e`qCLsanK7?FIPmNN&IK9N^H1m)AJ&-4uCht6!pzJM{2vFgf|zoCo-Nr1S% zJwC*o;I2ZFp-ghpi261~4T|K+0?R$MKDh{;w_651HZ)Lh)n>b^ix|IH;}aB2kv-GU z{JYUbRhvi;<(cyI8k_qUOK&;rvKY11 zbxf+Qb)7Wpopp`T;xP0W_5Em1HoM3yP3R8z(!2L^BRi_+MT#5kNCy!<=X9N;AlkQchENcPma}yVio#aZOakn zC)`03KtMN)PL?O_z=|qs`RCRD&MehE)5bgUC0!L?4k$qjwbd|iWF-aTx+o>4DW?vS zq-fEwnD<+jcuZRaW?-ge`Jp?_9#zL#_WQH9I@k@Z)1JouVZ)}OKi@-0p7Z84#{47x z`iFeKfEtZU{~5_5fTFE4$HVahCDmiuH%4cN`IA!WR~3j9kZx~ZM?q~%Px*Zx6&}Co zYT_<*qOvx(OO)|DlAtZa$i-yJwchu`C*N}ar3duD*yu-d(@{?m-OjB`qZ?>T?VnkQ z_1zxBJGVi4p^vuEFHFv5Grh6u=*)=J5OT;!PiK;<(GtUPnpm@@IpF<3`>zZMZzt_uK(V9Yz2k)_8POwVsv^VI0h+>ncG%m6`g~v9g&8#QMqcpum{Jd} zKJaL3*su1~xKc)qQf(4PYM!TxYs|YjT)5dMQ~mRTmH!UI@zo$2q)J`&67jIuJT5Fr2l7T=JemAbRT&28`-^U|s$I0c^^hhxi;*#J!xQCuQ@{v{m#a%{Z>U*zw)F zM`dLA7WiWYn0i1`tFa83TM}4j+vYNTZ$A>CCrxwfp(2csx7H)5SMN0TiffozMfeKI zi}3#^1ibFiz|0vZ-Om`<$W-&ooDb<+|72^^by)!2quB1wy)K?{iR0@Ygb zl%985Vz-%^PdwZRUxCjv9m82a>_kflv(>(H{ zGl3^Mg4(Sw3e1jaFX_!ejIm|i?5aAmzQgp&yocoM;FU0Vb9&#-ByjA60LfviEd;Oh z_4U%G&d9WO=8nb}=KVGl7}P6#vGwKreeP9%$i{+X={{x!{2p5jius}(UR><97Ag{u zW{+M4E=|{K_b(YnuYV$cDgq8712SNb;QSFER5UrF_<*H*lM4_eK#HTL;@b;16zAM+ zp?EnTZ@Wej&EVEmB>ELsL`hENWaCIw2)E-WafKj#@1WU;)nG)FA+K_QBpC~nsHL~y zo@3@Vl}w2H4t+X1j!|3y?I$nKO8P6{BuZ2E2|{zPmj|u%BwFv4J?)Y`5U!R=caHX#&sTMbGWNh!yLyel~*L*2rA&}nhu=cdhlTI#9WXPLd5#M>-xC@vl zaNcd1U$bnT0dPAqK;I_*;fTzv8~{SihT|sd2<5gCOK$MLw6N4*af23`VUN!$ju7fQ zgi8)gaaumZYPra6X&jc=6MzpWwaT9qHLZa-vtXMOX9TdheF-a`S3FD|V(otuLUlCF z);R5yt9W*r)p>wSjM>j{K>yiJeXv<$o*LBft?XD|%iZHBeHnOMc!^|-MpJp3@XqEh zISPD+c(md}XYyzXU~1>(M%BJ)ffh57FJMPIZv?c=tmj}XK#>JLut3wkG$fS6CR6=y z+!2Fq!2v4>wb2lE88CtS)d0+TWP^|>tIr+DGXHJ(8E*TvdDz9@G;|X}n{>3#t4-ga zY5rTy8I9UNz9shCU#ffvtrHm;kUcz<5qwBpg>7#CIznjD5i>4h%Nj6`y!V5b4GfW7 zx8dy;^URP|5}|BujMmZ?6#c>!CEoh&M}9#xV$$XxvgL`$A*PF`!zV;v$0gNa1!`mM zp}+BIp?QG3ja!P{kqc&=L)jV?zWzD}!YD{(HKL-sxsD~dY^+cs|1~y&9<%huc&cc& zZrC-10F{OD)A&Q&S!lDCT{%R-1?EiW2I>M&i10qzM1$6L`S#SYcy*4O&0|O_vx;In zaYEOE1T*Bwt0;#zszGT0{NSkMjdew~-Xs1a8Y)Tr%=6Df?=% zaP~uTgc);^*>I&ZAoc1?n>pMm(y%XUb%{$NHM~|RBNsO++UIB8A`x}-N@Tuq*^BIW z0@P?4mx58J+c92|K-XhZE$IhE*gQ~-kXDVZFUhhUdjS;05tlXSx`2>}nuG(MOfTk# z`v|YxD@qI?0xz~#1D)ePbEuoYj8RDtX^HUT8W%-aqo+%uU1n_1gWyPtX2%Z=fsl7F zbh4MC1z)Y4lD&l0?(H3YB<+P07WQ`z2dkuGF!)^z4Q`@ys9x8BRo|#I?nH5yN;m|? zNjUe`kj8{~0&`CE1|17Su3^eFkUUxiwy5;ZtG53ohpO_W^7`WxH4s>I5vbnf;0@AM z6)HNMkYQ4l8~fRPK!*&aYf-Sy0q#(bhTed8fF%kzk)R`qe}!zk_-FS2Wv(|i5oW*| zbVLDGTOh^*LxkWT>*}GkgNtI& zrGg33hXMUEZ4dWCIeqtKxhxf_T}j0%yE_7(AUqu8qlA{A7js16w`|Rnv7JUbsIOuw zxlrQsYx{CY84@0dHguX@pJ0R{;XotJu-!h#Id7KP(v2AbHwraNnm~F-u-Yuz8tIXs zv!~JL7Rc%7hpZ!kkm@!sym^~wOt^?MQt8$E9ti*FT6@lT)ZEr%M-hFamm zr>486?f_w_u*yC-U_menL>=PEu+&2t~8}2HK-&iO<6mo(>UymC5PSSFm}7= z5PSiLk0AF}Aw@RhUp_jChcpj4F+@7UW>{us-*q0S$6PDN{MhXyIlSTg_8nscbhXQ1 zbp)beuO>aLX~F@u81y1?H+EWUrV2EPX-f!mKsySdwgyQ*TV zI~U`Se(0b2RunlEa)NWR52Q}nvI-v7rT;T)9gFCp@SULVrf1}0JwT7nF@a`0WLkw& z?~W% z_fRX3Gc7REM?8D(0rT6{Bh}`WdlH|5Hx-#dZV(HE_nc~|PKQ%Vje{JN%-+83RWWtM zyv1&g6}tIDWuko$I)PJnV1LD zVjDjbf)@szxcmjAGgLwPVNv0jU|EHR+U(H(>I8g?o>{6odQ_EoYPhPr3_ePyd;HH) zfw8JGu>CyByyJ8R{eX}+f)#Rji80xuz*++hE|@%_jQfrt{Ky%05jQmQJ)-l3**Iv) zhfp^+4(RcY`t-EIYM{mmD>x+6K*6?Z_fs&?MM1g6N-SWAU=Y_C|5NGBUWL-FkvP{u-sR49X-3IQ z$mebE<)ta_8h-J^fK4pfV_d7Dd<&HhIXEX)rrYzjCHQN6Xa`ThZ z{r2exw*03%q^)3uf+2b*6JNXw{<(IHI@mlIdFde;*alpL**p?vj1(MEvT%Q!@yxF- z3zBUy8$`SdbLRPp{+CW|2)_ol9TDS4!X|v68efl{dz^X_ObRdDa&?DXpJzXl<}g^Y zmk#iqT|06?0`IGP^>k2gYbfUJ*}$@&{cWN2671Z>;B_F2h%M3e--k<*S0 zojo~&6>!5{jGXC`4hH79^$1v(XLp40l^aQ2o_x1EdyiNoM_m8fj72x06(S+oV3`UR z9EEY(`b@lZxCsixfp~LonwMurU**`h7q$$vl5AvBq_kG!BrM{aIcSR{i&-b8No4T4 zZJ|NIcl8p-OxLbNUN>FRQu76U20A;3oE$eRgM*d6?c%HGi~tttGu)CHc7)lp3lgz< zY}*yj@w+v_w$3d1=t+L3%p%RFVS-1xG5t`~H1FuB7^T2qI`lSpQ}0K>LcFRI-Haka zbx7bADW{7QkJ~ZnvE#g%E$lStCbqtBo)!c6AFr_n#|rWMs((LsU!hS%*Vn=MNOi4# z?e)0QVa^l)szlCG~;FxDGe+HO$E-b9{$yZ z>Y*wuf34&$k8taKSHV*H3Gw|bZf^9wM2$|E(SylnvVS%4@U-HYyuP*BLXL@&X-w4I zGLzY0M+zMvZ0L{eEz!KljB)J5j?CCuepBDm`1@Lfrv9UbmN0cl&(7@Tb=#3WzOk_w z-Z^2MIuIWGIymc_XepUan=jA(%>x;1moK`t+yg41Vs~1Ye@>pzIcNgTTK=d?uE_B2 zf~R23_y?=}K3_LaWJMYYK7A+#F^887adc%MQ948lh=4wY!Qf*kSDazkq>!=Q2NBE+ z5UD#DvVcFOQIOI{UY=rdHaXALgi$kZreC+aMDvV|J${y&=+?)q1lM<=brc_vBQ$6x z3IjIDM@n2zl5doD605dy+E>n*?(w|4c}SRtZmkD!31@RGc`W$8xs(@0`D`sBdns=yk3Xo)mV5&VN%UA=&b&S z#JXB#{to{H@>?`~k_i`#+eD+;5gm zp+DmDLD>J^&0=HfWN2dOWcafmh*J9U@ZY@#b< zy^v**u}JESNASIs!D_kCh=-@~_jub;pQ5d#s?Y*ySha*ll1Z%6GH5Z>j-GW+#x998 zsqa*H;w9_3=lkUeM!&nW-ShMOEl9Z0-^y(imdJA3Qmw*M{vuiGD1emARM?)VwOGe^ zR3d@YaEWz;Uhv-;OZ+}fTR^?nubdOD(cr~P3c4CeP%1IcSwRBnr0@NJohSZ0J4Mu@ zS)6i7N!GIo_G^qU1h0XIQ;?#QS52!d#JHC1qmt$Ryf6B5$WI8+5+utn%~aS8qM{fPAwYi?dj*A{cj2?4zS?*)Y8J)yN7QPS& zByBjOJ#Om~AcXT!Y$r}i!nsT&qp@mo)&ft;?6sa9Ymm?ZIFIWFYtT$}qh3wVO@ipA zNyoyaMXCs9C~V&ERkg*D*|jBK&QKYsaKzl&Sor0kNL2Gg5Dh}hAfd$a2?$Phb*Ou= zMdRbeV*i;Hh>pD`^26lM9`(FV+vf44*BWOThgB51!LTf;NGLgjKn^N*GuV`r(}H4X z7+)S|&yWnn4L8z(1Dz?K}kUB7O*(NjXCxeb9I0)QnCj0`FUlo14=kFINWD@Va~K7H*^^bV zIA}UT@q%GgZCQLc*?PjUGUn-W0dloU8)M0e(bJ z9RN7C3c`6*?K8XOL0zI9A}Vj7#N6`sabQ&Z3er`K%^+Wqq2>g0OYIy)m|)VJTb5+H zJcJvMKgTKWLw3Q4LcXhkd*xC1!6VnAXiATk>F+$XR1dhI4MtC3o1$IRN!C2MUGNXK z1&Wdq@1jd0y;=W+HCh#W-@7ya5;?imHc^b_O&-ajn{bm@tsl2OHXv_GB~i;7tPTqrKhPSB$nf2nLmR#;NA=q_fL9}%;kfW4)xepu9ENo;T~)|9`dH&LFN^b)eI#c_BF`V97hWow|eJ7I-2;-O#CCpCuryDkTo<`|bVx>Ydl^bwhgI-(HG8 zDr7_<(a4m+;%Fn|Vj`y+kYMQ7#*F;^5ilR@erJT#T!j*)9JD;G`uA7X)1BJYW_o7=GK8bjW69J+0 zVyaQX%_(*VyG`MK?{x0N?@)RB^amXyZOG6>h5P(4Eg?P^hFL4uMz>%-zTRM`_htt1 z?bYIcRjNC@6O|EsUIDxq`DMBa~R)|UASW2LovkfyZ?s^~B{H@081~kr* z#jm>)^XQVt%uaO8V%ce1C6lk_UVp>$cqM`QzAmhK?$Nl{s0t7`mLnTJ9<<xWs$N&XMMzA3!3x5+lPZL7nMZQHhO+exQm+eXK> zZ709jHab3;dFJ`%{J%M8?)J^z7yGHI_pMcHt)js5GHu@foH$>g>uKxh@?7QFVjNDI z6sCcAAU9&@m#g$L;hRf-b25K0Ir{j&r5u|?A6c1G6v?W#B&CP5V-|gf|R_(1HW|eXsF@k$eie zR4hCKrw2CSLiC=vIc;6QbFK7LL2$(;T&H%miAiMW;f|jj$3>|R3DYqJ1_ULxciGnm z8%|823ET;$qPA8;^-(vv@swjSlE^mp}J^)aU!Q17>&;fAB+=Qes0HMUGD7KSdNFkz5>4Z?`|>q4 zt6mud-7kVH3O5#0Xi{*?pWJeE5`IE&S}oa#>&GQnH*DQ)R4L-|#+MvWwj~DzBJpLZ zSfATV3>~b&zIo0izNhogB-ve#Q_%i-CM36(AkffcEau+Lh8GLtLlFCvzrb7N77+Q1 zDsP};qIC%5BP=~t63J!l-UZZ47EUg6RTg`uGepJmr})R{p9RfD!)E_vt6wJ;Kyy88 zP>?^MlJY0={D4PLoj?&Pn}{zUNA&Fo(nwTLr4p>(O_Xt^+u{|vg`3X+ zz7VbHx0Qi!`*nkS4b*ayait2dOMH0JL7IQs!AL-2slq_b0gQWOe1#r1->$Z8W`n9O zDj_v}XigT;u_-6BO!&AfGK&rK4S{VHf?zA*weX4-(mZEH=rp@=@6$}Aq^W|AJ~F&G zge_M^<7)3V$r)4VJjA(JsxP29BcW69v?(PeW;-P@C4KOQUVF0`S#5YibH~9vHv?~A zRy*+w_GSq?V}aFhq|2Tnt=aqq5y7uRR+FG+KDF?ev=t80eV0e;T8AJc%dhT*5(an;$yF zJbJyj!}NX&zQBvNYFJ1Dtt&F0c#iQhE3V4SWC}ozNDY(>vSO|)#5a`bYOP_MOQMuo zZg!XK7aDC;plYd39+%zP4qZ!N8vBQ6Yp?hY7yIdS%uP(Q^)P8xX6eR!gufh1*3=18 z_MO$3_HUAZ4GL#lL+G7Atq}VKwRKs32;tr>b_}H^CperlzkSZz);O2-=^5qEZm6LL z`%@ULN@H+1FvNfYmO-FFQY9|(8wG;IR}XMFpH5_8S(<9!_yaGi#AjeXF7VxeojdS0 zg)nWdiaq1A8&s)@bP{c~(&O{RpbvACn2 zXQ{i?;hg5#$pJ6ZO>dX0HbSRACc?i9u4GE{sGzL1%Hi^bB!uA;g~SzYZ6}LO_c$*6 z|K6YGdprXMI<^V7zt*)IQKH&SAj^rY%Thovvki63W$--I`*z+>QWeqaxJ1TDgZN{` zrXm`gZan+Rx$roaa4^H=AE0>U=J}Pcbp@k=8T(`u$*!V5EO#EwbdptsuYQ)g^NwX% z6qd9!slc;xQD#}*M`<%0Fx=_E5P3hR(KcNy_I1>x7n!^b)7=Lst2Odr9eXqlJ=8`j z^e9vtTF#DE?6*AeQf@qai73~)e*OL_`*TTs5+Ztbf!7TdXtOCVfdx0Y*)3CWa4HYb z?O#G%Hvo;03$rsD@RLiD_X?mUDZ-k{80-5G;pzlZAciUpuF*)6bc>O2ObGO-*&fJ_ zxC$cEmL?$e;|2bCj3?k`?X?DK%L_z7fo8UJjMpZ_=esBoN2|?f$P3G>W>(V{?WAhk zTzk>V^7$#?`!tH0%4x_a@$mA*=lc1{(he^h!KH6ZcCrF_UeW0%Br^V)cJH`<$!fxM zP9RA+B$FrTX;{Z+Fwub4sA~G%O&tiv42VBrgM_tE(@J-3^A%B zk9(q^@!iO{yH0h-edG*EgbiVU=)TZ@#%Omk%TEN-4Q%6YhgBTgm*_!_hKcz?&wT&~UZW&nnIs z!2Gqtem+voID!?$%qSeZGVtDO*NE>qOno`*Yw&frsPDA0`FzX~4HY`1;!Y z*)c;5HtV|R`D=*>?mQRPDm^!I-V$``l6lx3(|vFq9sS>LX#aQnOZ0E4{a?xazjnUC zE`~U7-|xkImxq5Z4gY%I*xt_B#mUgp&gCE9%1+?79{7ngS5 z{!5q>S5XtC)LFROm!VFT#GI-Qf;8uE7x#8Y%wAsfsTcut^qieUyoNATe4s9zX1OLe z2sow}ayxSp2~)NfF7zQP6KZR3+a)6rr*>A(!0F@lBmEsXQ?-b?(Y5ei)23fk&Zd+b zrpYYL8d%cY>f6qfkmBVx^H^W=hboi(Oe$pYALV~}KKQUOKqhI+ z^!+PWLHGF&^_2-4k2p&o-QP&hy#7)mP$r&mI{hAfR>8|G7HiUq8{r)WOuw#MI9CKUO46 z;eB(BhHpFkhQMEmewM@+F=tCAGwm-rqR67E@2_6DpdrEqiK8$=5pf9oCOPkMf8Jt< zOPWUDg7u zNoK_w&@2vgvMuxMc6hrMwUEt4dkz!Dmitg`lb&9`{H;;zWk8hI=PQ3I!`-+4sK@6_ zWa{<^5dAkbcOpY9-k$rl`9yy4*I~F3XibA-nN>-^c!A}&m+aXYVg~zA4E0S4!iTL| zGjFIr4%>t15*#gHHtb2Ye72*&oP`~z$lxv=ta0m-(nr=CLMev?k0cUWG3AeicqY2- zIKXG2#kf@B{b6co;{8qi%geb9GTpVYYMCgY59eK}^JC4gB^&~?j8biuzjg{t{6&`4 z2H-gAF8UStgh;luU<9wH7Z^*CTAwngAt4K5g3_z{+XOgeV4ZdL z9u;JT&)U13L$mAP`xX-0@VjO2*x49E5oVaBA}2){uzfTvf%eCKsl)!U5_EA{RhBfQ zJ9s4ALI&!A5WJ9I(vf7#x?W*|O;(T6x!z{PtVsgy#RMXV;XH`Zh@gZqI2+ov>I7#) zYe_{or}Q0KFKYR`?KAZJGb=!1inI{yGsp+d)II=!07-l1ywmc>5MdjgweW~nAM8TM zyOJjF20^0R1{Y@b?+6g4>*iK1UY4o6H4KQ6vPoax(@>p>CifHhRG+94YoV`$u|6T_ z@&aq^T9Wu2*z@-CO6@PgkfGsXwtdOJ*q*PfU!K)-u}yvU8QK7_mr-N!HTiSO>)SYe(%tZSFFdJwFk{O*M)>dN)C|ex#UesfdTETL_0IY zYl@Xi>5U!`8tN1k5QD5pXm_@JmgEWSS;Z_=B63FUFP;u3mQF(1;7de=>AD2iuJXl5uUe^Y1{C(aGV!PlsT( z3a)f%Zu4kTo^83Q#4qB1*LM7zCVvE@Pfd^EsVnC_f5pI?bEpKx4l4b{@R7|cXk1&H zG~69;!^$-WHKs|C9|3Wek!M|864dS)7kDTvk%438C*u&!;i8d)9-QSw)f-zCkY!n&&P-LJ@{rN<8B=3v<<(pke~oip}^cmiDGzzvfuV+IxvOmh29k^&SMXVcW~Ig+@KgGO^%sL5-;JWNIjm0fs?hFB#J1WKV#5ZY6j zB@Y~6_wvi^xJR|1PWY-+L&9hP2=cOsXid(VM%7sybzBkvhK!vCTfUu|6ap9Bq2;)F5%> z!XhAnA_)`mmQ@4S7YMU0)MnrhmR|{ z9^$S1#ZJ?-Cul+N*&!hU^TN9b9}QOSv9bsOUG+fKgJF7`7UvQ*5eZJ#=7um#>~AS6 zN;r}TG4ni#Gtx7T)UZ+~Co2k`_F=h^`{MxwWuz1i5zkE?#&B(JPEw>whC5CSFQ@2+ zzD@;T&7P(GDN2OAU<1Uj<`ILYO9LvhA*e*x%mH}41dCjU#GyC-h=X>uzW8w`{d1wS zQ1^3Xre{NakjF@We`crKGA@9%bbA;EMuXbQAxld^VvUWGTRcWlvMDe!mvXJfcBu#s zqNsAQKW&PKn`MAcGtSk?m2&55o7_yHg?|U5M!b>x0^6GrG*0Nt7M5h=}^E^ zS^3aqp;j&q{S9nOR<&llK>x5dgl$ncHx&3asdFnRo&mUXqo55l#8xm(exib5#EQtZ zqg;yM;w8pJF)`s;G|~*6SWTy8>9?U>PDCUgD^iq zrnzpO*@=zP++0Vz+rRN7jY4m=iTw)6{gR^dLZu=1&AtS8R(#H>V<{mhhIOQ_Fb^MR znwnA#A1y3a1o${U1J?xQQ>;nQxwQ(V1^z5xu0 zRaHp`(;BZwoz4h_J5B$UZuyn6i(+ebMYcAOc9P947(pM6D6{Ix=)(Zbn_OMi#p!~R z^aSDtE3p3WVGInAZ;vcJOy8;^vI#!uhDtU-AL)-K9Tqmmt2^TcMw0vvJD~iVxZUmu zsEPTI3YxjV(fakwl0y8-@f_-LbUiExxv^p@vHde-zRk*OzFlz3QNJpJ2I#?r!-(>) zbhbl9cq!wlXmZKBTT(^Kw9Im@Kia{h%}k9iR;sw*Ln(s~Ec?o6Fgfx$uTt=tbIX+` z-0Q)C$E>z00|sK<6#6pQW-(KoNhs4wzb&(i+_`_5_TY(+eH#b zRq4lb2YdM2z^VF@Seq}<-y{r%eB_?)xaSKrai_Tk0+U6OgnD}cC>JD9%dsaSXu3U056v%O zOS}z9U9DS0O*%&$sEyezDXLi8;2HrBIS*2Q684^sO%;>FztPN8HgUA=K{2<;GidX1 zd_fy_sUjFoK$j5Q!d1GCkn&}=DeUKt%ze&a%Ly7Xk|5|WqPeJ5^T{_?XgGtTa3V5Kavn-*NJl=mI$!6wAKe3B-MZx_^#0 z*b+9%pX+fTrL$X&tMMR+FfeRAGUnHWDx<`cXm~+4N0nSxg@QAcBo+lxlOj0=alo#T zSak08`?yz6t{&n*PvwpXjMPOa*t$U>5bqr*N#sB=&@Y%wjUQFfvNHg{xpUr7s3QI* z-|yjswF6n4lC7N4Qz$RszIV<4r3S}iy&l0Ngg9go{(FxE?3hNzOrXgqN-mK$T@(hc z%l^c(_+h>lfBPlK0Ty_V?h3T?x>b?gm3H3Xp=7Z1-kmzB?;ZvYdsDseY9_5=r*DU1+DKp?^Gx4ON2zi{hlmK+$ zAEMt{m~d0%Bi>-r)P1fti|WXBtufADERWOJ2YmBE$$TEEx3&i?ecQ-CNrB;^V^QP- zkeX)xn7E%&{h?zwT#q>qf_fj^E**wl9G((~IE+#sC}%VwBxnWpptS$x{pY5(t3582 z>=PzzGHBrO?CkUr1bD|DuQw}Hy!JHs<_x9JD9~}7AEl=Hm-u})`SZ z!SUJjJe&`quIDWtkGN$QQwZ1}!TJ~Knp*5VngO7+{YheFYjuNClQTTqT}%>M)xHQT zFV7Xmp=2%f_@RX-Ra=9OHt{p7QJNN$gxhd8LL`Q>{iuCTi@oG$Q)ynd}xMblm)WHKw1lb%I;4?n3eM~d4Cgy zwXC@AE;}`>A)1>#My%RsO(rDlOS9$z6V)%a=_ zZC){0dLO0W=>m=cJn7B59FqCrw1G6L-#+ImR8i=%R4#_x@>`B@1*$nzRrnbD`uMLl zqfhtwfm~DW)oq&a`$W1Lx53xj_u+(Bn{?y*C7$lZI+ z4JsDi$LIOM!9hr3cFgt`<~mlYsJ8yQn4Yn%-|g}Xb)0B-{E@N)zcFOoDC8ZAqKqo* zjDH?$S}mVYh@GU3s({-5w-*B-{A6FW-xTA+>_Aq0gL!sA@_GLl!sP^US(I0w8#3}vt>?$BirM@KvQ+j!slz`}1F4lU@NicL3dsoB`#vC`a z$zxR@1kNVR4GT>{2Zy&sn?S<6WF75yqE1~Ow_HPS_a=5R#aPM<>t|sDh<9nm3StJA z84IKI#?)`db8!xk@b z^1id`vZ64dxwGi*fFD*0L{>?D3Lln7VnBOiem-TrSc0roc~#WFSh2^q>#P{ZbORN4>}45B_d?`^14jK=pK*3kH=o zJbX2rgRgu1Y=FhVPD61vyD&cZJa{L_Rk*$rv@90@&`aDZdSmm7kB})X?ogAXlvra~ z{{6*TS_hKFRR}aDgG7kzi=w6=WCQ~8=ls{EE;Tyhcu{-hp=2An%8M)p@g0_oFwIv# zlU~_%GLrDblkM4h7l+pH)M^1bx>D_LrzgaO#kWKE>H^rvN&h>GR3}0%BCr&kQf0@n z-J*z~I(^z7j_Pn=VSd@`FRGG;=^PXx$0lm1p1jvFZ;u$nV4oq#Nl)x2C=p=@&7KH1F}53ro!Nn}oFT%m0ewd^B@*dOE&9W6j#9adXa)TjAW! z`#0Zb`vu70u{UKyNZg?;g>EpacC#`o&nNdjN9>~;>=d!%@cq<%re%~>U|orOm*B8KqZT$~=Xuh; zT-h|m7@75JwUd-&0t>+*1YLUvw&ReRjp@&o z&ey){+4;Up&1jqsoDQqBd~Fnc+IjySZ_0y*oVtD{-qx=uRLoV-HhVtx2Ia$HIK*7LSb!*$ zFO8(BwY!yD4A|P~Dw69CEZypQ?m{oy$l?Qi=v#Qdfu=L0@!JM!noGc>DKNJcy>8F` zJi*3w>(I{54_MZ%LY3DapnrGZ{N-4%JAIfxF|1#^#2Uu#Mncmk95EzaRKn+i&SXQQ zT7Q{p%}hxc7c0~<`RrA;@f`a6s_}J|`uaUYe9YvtTuFOoJzX-ch+d*xPt_UlTiqM1 z1(Pj`z!S1GV4Fr!7$8mADAL*v9NgK=QqNj!PaCas5o&c}De3&vDJP3AF-v-(VQ!}6 z5K--Y@Xoavx(>gu9%)@02i+i%f5yW~d<6e2 z6{uhZ^fAQI%S$*LlEcmL%)GdnlF@GpDI-}f8g9}qDtWDmSoQTIs-$DeIvYw4H?Afj z?l41SfHF3+xVyxu%?OsE*0lT~3wUY?WD!TfmVX&K-PyiUFO9_hM~+$dTHl>gFYOEL zKL=vL`g_CP-|_tTT_peAZ}wkB(#+Dv^nZl%f7p9>zo|pz+N_=-Ss+4SuA8*L(-Aj9 z7t@K3K6%hpBu9~%H#ELnW52Ym%jR>f&hvE7u`_3^pWqu-UFnzyFp{|aVT*AnZrqo% zFfFu5V%=afy?VRM(Dl>N_iOKdxk>120v&*bE_AQ# zO9!QE0$#tnW8V%Mini7Orz?X|@l4aPSM*_(ChfGuAD$oyvhEet?)<2u;Yv!-mSz*` z4@wNFJq1z{jP^v4;uucQ+e-!4tT{^D;~xFxYKHt$t4U}ZZySB3l{q!(UogUOm%3pA zPIm1dS!d^q-a09f-z4%FChc9s&>BkI+k><-hyBUFt8{l}>pzL%rwdCVdCGYoaZMf9 z2s}q1=w1RA#v0C3%{b>+Mwk$f*rULzI8!QH_B84I1w911>GPMaf<cJ>rr*7FCI}c_M zgZNx4{U(l@N1}$BcQI?W2ah$gOTZp8<8(GiOFozXj^56vo&HYnhF#z|$%;#1BdQ}v zvFl^>H)nhKaUP)lP}Ec{MthNMFMN}@?82c>#uG5f5QA&F&N^z2=cUremp(>M;T?E= z)mViiE{>83lGsaE4yf(yHY!#_K$}(&1?wIcxzkm=g|jUeB#D@Pue;qg z)?ZyYCp>o%{msF5n(|rDX#IAD=eTgv=mURrRfobMjo*C3{LlC$9{FBMfeZw+qWC|V z%m0es|9bd7+c*C;Z9uWH}{v%?=Trm^Gx4I(@kF9HSv2P9>N?)u99 z`ur;0?v1#CTZG5G0BvrA+`DzP)oo{Q-zN&Fl1!m%{%9`N6dQjmvGh(@aa}m}U0d9o zi*IGOXXAe9D!v1lZY`U3fz^T+WU#x+>p!b{iiH_|)BS4h$!$3oDX!IVMwwd=5MMVT z;>O-azC50&yl<8YA^m;dAEXI;gG(w-@YWu9eK!`^_$w`LG`Ab|>O0_l(_4(hGQ*xO z>9g7CLch+c?eFWaIv#?W@sU!3**pPrGQfB&9K|)qE(3#ACcqv9aCx!oYE0$(>j(GF zNam>+F2d(Q%lEp?Hkp@V1uw6k`=x8CGd(X)Yoj}M!pDaNSEzzfm5G zje|GKh3dc?e#=`H{RL9^&!4Dqa6P*m#V-61vD4&X;RD&h;TcB-NInrQ9~$klw_Jb5 zIsb4|L-OsWegXrF`sTPlSm#sVxyzW%AMc%TWW+U?X=64>Xc#5o;ctFD{jIdmF`psy zeL1-XnmTCPNiwM1G1j$Ba#rY>?4Hf?wrc(f-ID#raLw=M^mR`eTW|ga|dL&E~h7A+q6(=;iVbJNXabOjP7S3dhfGZmkmch0n3vY-U08*Q*B5O zT4P7&`5>v`y1b>YnXx2>ZvP40=A3GnMcpMnck=-ZJB&XjDa#b{uu5xfPB$}>x?Lud zQcAWk?Sse?@-*at(dqGu3wI>gS9Gz0$oe%mr;m&{X{qTSn{fn2un;liR6_?MTcW5! z+P@}8U?sSUve1L69CJr;&7xnB>i=jqEP~EQeKe=ox*2pfxdbA#z4;!5E0$9Tti2^* zg^_KOI^ig*?zxoNkJQvuI zY~D8GP7FMv9YZuoT{~2yk!gPdnlaMOiSOO#B8o=`VT|DO=6i*)Oa*XjBL=Ah6a%hz zX9RJ~6zR`f1rZH5mk(pLx!9~V?y%eICltmUjp*-zU)E!1eQDO6r4D#eHY0Yv242t_ zAg2wQ_zdncyy9@M%hHYD;IsD-C+=ygE22n7$-8K2p0EXp%vRA-ve^Ir zElcrIqd6RGEY38g@jeEKaQH9QsN|fwim9sDyaLGiy#;R_v&kMDzha1-a`1KBT7M7= z)VT7xr?!*vZj?IWBj8giD65$`vY%Fcd~?DfESWTB3_u%bBNKeX~@H zTxMI9NfMk|H-}^*QzgunnWh6}_yEaz-!PoyN5&DT$|UaJcoj1zh4FgdCjR9>&+JX= z>JAP=kXlBT{Ec-uQ~|O)2tGUgG1gtSKxsuIluC55MC%r^eVd1+qNuFka%tE)VX~iW zh9p;6avunEG(dbaoqM--S<>zrG&80SL?NT zs^ctMbb`{+T=YoEdb8P@sqW|5HgSp=v0axSt$MG=%m-@3av8>Np>AjY;?`3$Y-MS? zwK(UVdejU@q*nt@XyZt%Qf9`q!rqp6I2!XG_oRLWxk8q=<=KVmbzEI_B z-s@;m57sdQgeVm=MldnQVt}cb#4$oAnR&@U#~2wHL+ulds$nfoM(?DUq-rs>X9LuP zMIQH?25{!O*xm$cl{iX#3eWwaJ7-mx{RIWGwnk4A$tw&BIpPImiZ@R5yRA8gaJL}> z&&*UjOa@Q5R8@RrH`-G|oj|gosr~Zg>eBr17Y_1KBJN<_+K}6!hV#4j!Dq9*K_Jy} z?-9b$+Qe!;yaNn|T#><$sj^AvDf?(^UP`inYzYXTr(^9YxN<56|IY5ktIeRok9WaWMRI%DSMdwU|wD7)}d#4Ks1l zBF#9D?5}UZ4#)wnhP26Eg~Jm(FzA4=cm#gznPhiw@MP?~eDYAh5@MJlvx3%O^(|Ky z6snV)djqw8E;^?tU5sr&jVxT2+^Drfe+3wPv0{S7?$RO*7T9w6!6z{ME~tBCtvNB; zMvr)b3cuA2ATXGrHnDEaWs(d$*q*+Fw#L1#InJXDz5>!J@`Tz%b;W{jAZ&Q5B>);e znF_r1=#VLZ5R-n2y;VJW=?||TUExZa@dPNpv!z?aS+8Y4FlJSg^gS*usDMYwABN$$ zPV*NlZF==y>w%eh*gz?vc^9XO&1Ex)L2~#5Pnnw3hR%_dz*A5`>l^_u#@gnSEYqCDS1DB#R!lUyx0o zIC|o@tXts_v6*!nrLqZ`v?G3u>STe9VdL|xTUFB6E-hj>hSVcu75l^E13|oT?2x@m z@kGiU&Qs*;@~8y|KmzW8g?5?6+cY-$KoIxBx#IdSqg)501_mCrYzPzzmgIcU5lJuk z=V)Sflh+QI$ve!Xois#K<4*R3HWrcK*ScG6I`n4AJ!(@D%&R&^;5Lxg+>+o|RRC-? zt9}_!R+}^(=oVG*RCVA=!O_E}3~o^Mfh6fSaN56qZ#SX3%Icvm<4qxs@mju2g;IbE z&f=%6ehx0Abk`jkM4p-d7EJ|Vi{9y^=xfZ#J)lYK6Js~OF1&<%5DS3b?ia=zQ5-Jh zePG(D!R2nlFXnKXro%z4GOwqE#-`$qx+s9Oo#~uXcED;AEtw|jlZFeq`0I<8yc`x3 zd;!AE&6@BSU!S862r{dW9I{b7xGM-mt)vlVyI9rl35=w#LA(6(H(2`5Wl)cXqDL|z z1wCg}I{K;)qHxo1V?8tkI!4EivZ^1i=M{Qk$g#FxZrRR`xZE5~&e+vwC%*~71xO@3t*LzBwT~Z~&3_<5rA4q6 zO(HBEh(_ zwQcnz+k55;XRXt?P0@I3Q0y@vm7_zcdp*+%bkpG|!vnl!*j}R;y77^Z^hbA;KP4bV zs>m#j>>uZF9voDGHSJgzaf}-AhI$xl>dmgln})0E!|{ZE(W(@rj~($7<+qHrLDX+k zkME{k5*1Qeu!7XkjLrpN9aRX3t<}=|m5c^LVdMP3&+mcPBTBHx6*1QiD}u?J1R&e{t)%@I3BLV;rQZ+&0)^(;c~rRF&J)5EfB$ zp$txzfdF6$5HgI?WN}Qy)cJwDFkD$va&)WVLi2(4HT&cPnZTg0i2tG4W1Cv#~F3;!ZwaO(*~d;h9j zd;Fck7JlQc_4h^e;la%%0RPfn7*g!S2NWM{dt141q;YW`R+vAK{hH%scF8st+ut@; z_UUU0XG?Ul5piJ9%fyU9sM2iBcCYv#$EkLt^AMDog(p@9FXMDps4(zNv5hj@u*z;C z_S$D#9EWuCCn?Vr`7N>QS30%eJogw#7ozUG-GzGhifQ z33)U7F*$_BSCFwYnRwfk6pG`Xo6vTW3X#?>)=$dn*!+6)$Vk9py4(m;eldM&TAVz}qi%Hw7YmZZach7~A!O zy}l=@p7hfb>-|T8sp&uJ9HGw^q}gs@R}k~Cb^(j3Oqu&Tf0(2YhYQhX5r|2z>JxQM zf3wXq3O{JC3R{94IEgBXhY4<86{RGZda~MBN_iV~iXL0HJOe2v6(hwMx6iwyKQ#Bu@aBiszbgvCC8Vpm78B4r!?MzU5$}{bj z1~-z?Fts-%4S7^4RKS?5)-((bjlc`nuNHaxMFGS{6J9BFV{H_OA|8bUCGg?|1I=8#)klRl;hGSm1h1@-iUZEl@Fp} zYfh1CVSl0Csb=>MC^09a0uBp$p)gF%0!=2&Sn`sp^cGfA4zhE+HzV>B@ngjCBvzAx zL_e6Gg?wraF%HucBg3lxB#tCFD~8j z^SjK>0BXXZh2fp8ITH>7HJwc(DyR%XU5uM{(Eqp)rrcT+I-oO{~7az(hH z?D!etcy^d%>p>M2pN)lBxIeTEB`OCa#OLhmmyn%a zQfxt5U(uNO9d5bEtC7I|K*Pa6uyhc?d=yLagl&wy;?f@^QQ-w%=`TR-)AkI}OglV- zvBSyt!K`{uSuGP?_65a3cdLpBrd+1;Y9R(?`$|^Mp97vAeRRrxwKi zRq5D;{{-qab`xmH_ku#G z!Rh3op@|WGO<0+>jTKotTpDn9XeWQV56c-*uDNN>2`5hOXWp_SvTI#J70a4)!zZD1 zTPHV%NS4z!=tw4#18gr9Sj4?|#82$@7SgYxy%PDo|DsDVZ9>~UiU@i(hCBxR=8P^+8J>W_vPT1W96Uzbc zBASF{9bwbtti)2_Om@wd^-Dgcc_fzlE3qnP7>BlIQ zs&-(f39y=vFcJxUO4_>Lwc110^ODf$SOX6^t=+_C_Ov@*6 zWTUM65MHJ8GouRK6KXWHIh)~{h~Urg*RKq2Q4kxQPV-vF0wC`4LNLq6J38!q&|}yX zeeiEvia5NR2^{;J_jXa|s}_&zS{7cOzDCLic38;o?fAg`q_ak2KjBUZB77N?b|H6L^sr^8D; zrf=@ySyuwfu{g z9@)cri90Z{|zewF)#vGe?~xQG>k$8W5)b zv_87Iy?%C=|MTutQlE3(bFWyC;#+4D%ne1OViI)WJ+1-{CR{)mHM}SbRxmG7O?mt9 z3fijd^v_pWoDCf66i=bR?^MGlrNW0Qc!;CyE#K{eGu=I{p z&$}_*wIPI2)w7H7a?loI0nVP$rXUn$_53}WyKG0fWdXFsJBSLKi-ezoRHt&V8IE~4 zcNZomf9JV}$@$$CV{97*DL6KO^m%1^=*{Ax0Rb5dcR8fu!;+(4vq(QCR#I-qv(w=L z^pZnT&Zid6;ny@2pT{+eJQ7xt91BNGP3{2`H9rC(5&$rtYXpApxxSW7@vesGD+4#T zM+1c8MWSd^TNg!8?^g9%@fU(AFywVnrRcf@80nr*Z-$>C8(RsbmEte^lazlo3(4*b zo<7n~nf^!*;V`>@YX1;V9|2PESBqfef5C8Qn-@ic2!P(sM6E~-p-Q&&JZ@u$Gkc0P$DzvI{ZBAu zm=i#?E~3C$L<*k@S(lsn2h&$59+d@K*XSm-jW1nEysqoD;^xmke#(~yImLhpxfpP? z72XQJYf`0?Z-VV8ZxFi54>iG`V(|$Xdd7v!gN$M;cw$xFyjvm7YxIP#@4zzyL}Bm4 zelAIt4`_EtPQE6k)o4SuCFDQ6T-Ad255gJ_jO{XEexwtswGEx%iQ24WxT{>(UOW>M z2@cS95QGZ>&3$pkHJ@*<@L~}E1r7Xy#tNV?!^GA!j;zOlxU#qj`YZTl>T0Xq?{wFX zW1jr3Og0Q^&wElxMn1HKbLl%tFJx0%;F8Uhn-UD^>gAd}TWX@f?8~|(85v6l<>2i~ z3piqMeUQ*Z;FHrPEX5rLMx3-BXO%ic{O4IpU0&iq3jqYQfb~C}WdAj3adt2@wluRe zHgvJHxBH&A6l(xt0BlIU&+4_)!gRg2kXWk)0vT+AfuaGdN@$VM0@9?J5i(q4$$t`$ zwz-8jB_i&~(SxEzr?c7+!>Jp&YFX&0wJjx-DGGqYVC;lkRPnu0>mBf^;RS`jW zbHTJ(N3i?%cxQKe-M=p7+FyVDLhfwJdua}#x<7@wZnF!B<#`))Z|$n6qLP(Pax5CLZ2dB(etuJ@s? zYVRvR{lN1ZHLq3e(?PRBOd%9>MmF+n&?gzF5rfL;e%H|HPZeJ5?!j`d+~>y4d9;zA zI(+BfV#!^|n&T&RZ*`+CL-~_|RolOPqH!ao`EOmaaQo_U5En)l=V7#lq4RV^sPzMK zRvY@UTKIwhWp%rlOc7SkeRa zYv|*bC1(OGnqxN|>X!Rj-M|fmt(9eR31WX-}LK8D4bUBFN@cdv#*CKa!j`bMlF zJfAF-HL@9g$xll@bS`4)FJ;t&OP5+Foa_ zqTxQsp2lAZKRO`0Vh|vZL67b|pWonP{~8(vEh5!ldb7zPT7d9VIR?TPo^(94c23%=q^QHMp!T$N#J{oAM4{;< zb4)+o&|S-O30=cGD$y&pM_F1{ta?y}Rj|Og(yZG1@)yV`IZLM4KaJzBUt4pV`J2vO zj1A(#qeXk}=X+0N2!OvEI0f(y`+aq9t)}dP7~rN5;8+zIkk$|vWFygH$&oZI+{t2r zN>KsUjPRQP#EZv~f+ED&873EG+1?e;lPLPWcV-o8iy-qxSb;Dri2Ymi*KPxUR`lJ9 zvdgzB!aJD!XkBkOSLoc@X4_`-85O#ce%g_sK>{23(T;sChCZxVX-@il0Luwj6;FYz zvdd%MRI%xd_XQ`*jDu_{$Ch>Ll1{<bgtYYZVA3V{S6beLyUW|r-sFwuCFc+- zA!TUhX0eZWeTxAr*r4^FPYWy&>%Rc~&WtSP((yUk6D@~rD*XcpzQAmIudTaU8)Rc>=C-w7Q$O}+#P{olBkmw_s|I{X-q9PgGOBCw#i;bo%OTaC zK6jldB{!V113rp?SP#Ep=8y^8uIvJiinr#>ibgEr0NXCm&Gb|rZ8zS+Z7`>hpF`)Vs=HIoOG zc3^5HDA2T&$N5nZwaih!Z@hjTWWcB^nI?H~E0?~1YoyEm)<^gCu=r{?6nV_n3iAJ9 z?46?{-MYQe*mlRZZQHhO+qTuQ({VbsZQD+#V<#Qm+V8=4_WsU$@BL%esH!nm)m-bD zYpu!O{4;pHCy1k_Qd6BTu?G9j3e(%?WMr6F|Fc;g8)q=!@7K+0vdk0{ATi|)keEXL z@5(vdoNfPZxdBqF5|$X@dY&~zj~D1ghQXoZmX`5>prM@v`^=i7nQJ;jCxOa&hXH0G2y~&%{^*ybcWRlJMUUsBEIM;W51nX58 zH@H=$uV85A`a=oz)(Ez1fD`DI6f{DusaSh1C8TKbKn~l~ShK$9=my8?2r- zXWAu?RfmUPW@vuMu@GXRg{)j3%Eh3w&AjKVD(^y_o~B(vDZGPt12PI&lPC^K+vh08 zpLbocGhw)beshGky_k~~lJXd#WbQ?spu4!)EPly1sH_L1=5?VraztUk2gr!Z@`es7ag=Q`cx%t5efIyLkD)ePB)8r z#h<#za+|62kh>9s{-a1w7Vdt%k1vbLN$?ze?=$ z7r;fifyyM}m=*Ty=O~?i1UGVc+&;(btiWM)yDgc^;yb~wZ{K+pP2WZLxAE-{@GS6>|^7(j+)5T51BBEFFpR}4*^~di+k*~oPU}u z06F{bF5drM=>ESjv~DSz2oeCA(r@hlmm=8z0MW9wvbO=KNy}Px00b?h&ntueNDzD*w$@m-i(wZ$`!9>(sH^8l1n=7rcvbuFcgZr*pZl5MnFKBAker`{Dld`PvyDK2AJi9QFrj zBJkM2ul_}gos%UGaGkmm4WxTAF22}`cyPUj;>_+3;gndsTy_dn^qWx#ec%TSbzrYv za!RAkA_qcEatz&UCd@)y4ccpA!Bw~}PS3MumrkPZW$gQ2U@LLD)%Mz`l zg3BY4^#*v#(6?!h!OFSH7W%5YhEtwdiKNYAZVlDmQ~2iWpB&^YSCzm93sGG*}T#6!4gw4Z?Jq6523%3>3}(m7PoW+&N<_Q7#38WZET(J&7} z-)i&kdLpyfgO>g@L5-R*A>UeqFpo4rFhHj@C;Gi5zrck zzu}j;F?WpF-4!VJ!Q&mYNY6iX$UK|3(LYNDK&tZ%qlLuj0KL0$y>hHlcRT$?HViw1 zF9hKn;!DxYfc$e(9@IAj`+=NP7rK-x*@>#fxiyTPx@TQ`9J!3OPS>7B6n2TY@E(k_ zn7q%v_+*>A)VRgTzm$u5fzSC|Fxoz}1$v6$aiRQ*j% zYj1X(G?1;5yV;;X&G0aj$AXJe0mEZ=*-Ye=CpD;xOJKhwj(Z8K1j%#`UpKdlGJCt2 z!hf`N>>#moEhZj*6|IBRI6Zd+h;`)O5jKx-d@DI%!jGR@8V|3LE;gaBNs5EMoNf)@ zu5F{$H*>_1K*~(;OPz`(pvKC1jGDpF?qXaFC#7qjc1*Xn)NUjCk6n=f#> zmUM@PJwV*;i^f-ueD?P4qO@BUL4WVqFyPLuof8X$!xfhwub2DP9#+7x=y$?uldY|B zjX1WvjNFWM(wck^9zIBiTq04SvCy4R?MF;TJy?t%SHW%H#<@q7ae|NzAalJ3->U4e zncTbB^T=$}@BPnhL$P_U}`VfTtS#n^ll zeD=Of&~q@Tv5vjFr6dGIUJOk|6gAm3Mv@xH`|OQht$ zY=$46gRyGqQ9^4z)Pwia7o~n#jG5VFwn)jz+B~;7HL?8-io^?Fud?TSq15GLjH7&u zZI9j6ZLDH=j?HYnbtX@D9Odw2$lM7VQhTZu*rc0?My{ zIMGAqMvs^PEo9E2D>G+$HE+bn&kxn{+}`SDYuh%|-*RzA8hmUhWN+j@NOII?+&c#WeGxXN1 zJ*~xgSw3WtItPmQSHg0D=Te6p<=o5bqgKM0Tq*!h+YAl~lVJw3Rf|$PCW{=Xmown? z@H~OSE_4Q;r-Wn&;q0PbM7zkY`^(Y&>FWL92-3t!bVA&v{IM`@!?m@g&#i=o#wCP* zKHlmH2QNuO!sl(lB>ahl=MZ66#}>V=i}WXWO-j)ij1+HK4vvpqiicGf`F(X@IT(ZN zc%es|NkdS}3oif0Mtgo-P2#i|@$=+VM2W@W2b5pjJ3VuUNFM&7LX@di`dN1xB)LbLncj)EXa)1W=hI5Be*>^}*d5f)CM`y%1nKS_F zr(6gADtQH2wtg~ zvILVTHB4S zf=Vt8O*_(kOx%D}dbDyU>U>wtv+pvu;prtS6FDsE=!#bw0ZC`CKU*j&d0KKbcqT*U zs9%=@46eH`17lwRJB%dK+h->9nM4!KGjqix`XQ>yO*6evCalv;RWLUu2mRAI*7c@` zHjPX&hLw0AOI=QOeF%=E@wy&;l&7OKS?J_fVszUW7Su-QBHYGU5_>aSLsKodDoMfC z9eSMG4WtYN1RgA>f;YTjr|E9ygR@&kO_OHJ1b@lFBCVN9g@B`+B5)I% zSzQzCxj;+n;aaaO`PZu%_x7vV@Au_oqhFGvC`Z8HRF<8jab%)b3M{?hGF@UxYVFce~?Bz#wa?Pl_rEY&*o5TIHPTiCzdz-b0e>Af)858 z*)k(A`k6b7`A6|NFlBt9oTnxA~RtCC*j6zHmo8~mwYGusTq@<%0PF#X( zI8tY1Cs{&XN7a;ob?EOtoAPK|!QpzNXsnFmwEAkII{CT~- zS=r2fW-8r5Hay-fPnl1Ck(gMb{`rNVfFnK!k6Ix5C-ETlQ_E9LSKQk59k=Uwu4||! z6BQAM5CFFFV{?9v($j}RrCT#_m(hzxPjWLbkKbrwNDA$~!E@;%1{tN_Rdz9uigy)l z6S*Q=_K3s^Icfcauu7TLd`fw9OB9@XBQm_u>0qvjJUPQ)v`7ZKTxm5-uh}3q+?I{l zI}&qcIMm@um-=+Jiu38b80go#V8mQPFoKu{ebAB%s1BSuGv+N^c55g)Z1^!@MPVH& z`&P_~gP}?>^2L}8HW++oREu?lHvSy34Syz924}xKxe8GNgXEr7l3m7hrahmesd2gA z?|Ix#_0IYfW|k}Dpu)<`Xwj{NQuOk8VVpJR*d(foeRuc<8}azXbPNyaL~qdA?Kb#G zOxbGA@0mT9Ek%=O#Z9?vw~DazLD{qtM$CKkx3zVP?z$-7_!PRda#qsq*m>T*DObeB zrG*n8QGUo95lFP2Cu6sI%4{hGQA68s zNz&Y9ZL8cKXB;HIE1EX6s``^%C&xu8l(6-^T+^>XS_4w&O^P9?qg!|B40b6V)Tu&+ z@)x9Wjg}>N(n+mhfw_R$97I?al~C_^5Vy{%SDoJzbgthu>*BU`e!K6?`0vx4N_R!E zSbyLiN?HT|N}9%}dUdSa9j7t>6?utStF>J2t?#FRaBa!tMH3j+m|B%XaR>O~Tw?&k;7&aT9 zr2EAP6kzta(3Vj6U&@&|6yihI>~}|&isx9xSWc4w>4?Lyk4~j*pZ3$UC@EB)K5|{02Dd2A{!~&1zRHv;yWBbhsiKQ4? z`e-il6&H!gZM)k(jiyzS+Iqlm|B!iB95917a?aQxNn(i~J*vCJ&SkG&ZiV+0vE$!b zX+lR3imQ-?yzn3Z4UW?AltZFWs>B%zt$nERtyoj(*>B!FYN&x_YlvoX#!dmM9D~~2 zG59t)JRg2k3UjlkAhpgq2BFty)h7lWoX%+$2(Dm$6NmwqDp9urGp2nuEp^f&bbWCY zi;H)elswEtfewia!}@*BN%xSQkP%7V5j*XToX}xtc%3u^4WFel9*B%@24f9)i2=c0 zGf3jfXlwx42-cVna?$6_M?C;+E}@IhCSi2mvYi)s9I4(vs*Xz->kLg!1el)kHgU8I| zoW+mGp}f(k6fXE#%u60YCsE#j-qpl6379tMEAeNfA5`rk$#TB2DE3GR8!HTf2#CtF zN2@h#ZvVqFLn&QDtG1rvOvC#OA(ZPMbj@etm%(4G+&z*Sh_46t(>+jQ3z+!=BbaBE zr!j6VAIb){3r_@3!#Bc(MAjw0RCG~^trM9vTct#fqhlqs%8 zfcWDXYu0N6NGl*(b_ky%Z);dUbN42v81f5`JVbvt2O^cPMDlw}9yV`3ig~OJo-giU z)J)%JjE8P_JzW~baI}KH;E`FPcrz0?aFW)p`z{p*ZN`X>^2#8W-{n%P4l^Fyb)8FN zdJ*d*Zwln}IO~GO{rK(I)JFkbTwxu_tq11MMru$I0;=MKLZZJ6!<%yO$PAPIj5JVN zmmyYADeNz@kmwbw7mky*q3}{tMaBiXlq(;(eF18hz}KZqV0sQ=vGtZUZU5vnRmaxj zky7|Z2wOg}=yAQ9+r#<&bZw(Zi#vEG`a9}v*@eH|GGEL%%%LLCaxS|6qzLn&Eu8ag zAF^#3wWl_(V^1IAb)Je=?Xq?VNW&0k0w7)w$Vnl5u9Kfnjg1nGJ&E|^81 z4kPwE_o@U}Y@dubx|tf$iQWt_b&*q7ZJ9NfY=jvh{nUbC3-c@pK~ zQO;WrB`@%V#1|_?WLD0W27Kkh-5(_z3Dr+7T(FMLd$$sqI|@cFL^tPWxSn9h=TBc) zAq64Sv1jGcA}?Dhq$?x6at@k5|0DL1lkWE@3&@4w(Xwj>h)*H|G;V9DFK#w@!p_n!Lh z)GzTvPTCdHg19NR8qS4KG%&Rp6P`qT49ncV70riRMg^#lY;#2*({WQdL!mK|(rI6( zTL=f?Qx$CsBoRI;acFLN8OAeHk4`%sLY4@A^iPny6sNWvYEXhpQl^s1{v-lzU$&vZ zk-X*9N~+*|(uw+%8e5ckQ>(+Pj#M>9xa&&V#0guRQ-J19POHR0mxg->;`| zdfFDL-NrUI#!hi{8=Ty+nUP9z5)u|TMZ+RDJ(&1Gx#Vw`%4Ir!x^QR`@@ zf~7d?s42Rw_7L~XHmpg6RlV^`;fynp-FgB)__+$Ebc=DMYV%SAr(g{xb0_t3@Q?;q z(@|?xD8y&k!onp(*U(ELNn7$17dSn>)~P@C*`;E8tYw>ic*1_8(=$qY98_7D|kl2>C*|>=h{M<{U7h#s^6Z14-@nS|6rW!^KQzvlfPFmhIqWh zp3TmKYc%RKCqqa1A$HPR6A8VPqQ4_~tqJ#R3V>Ww0(aPW{y?^#)nB;5oP{wc@~XSd zMHgbc-8Z}x3ytrriPLwHkjO@OI&f+`fbM~3mbwAvsyNnCcb2o>RLhRp;L{aR7erivCIN!~- zdH=xE_x6v}mu-hw-T(mjzZU@fAN`-PNCz`l=f8kVb5$ng0T{4dZ#30;C{#zF#OOhl zcSwrPzrcx1GnWt`M&ogxk%&I=1nwM#Ns#!5&)3&8x8cdlo|ffQrr?WRT7d$05F=r*=&IXqfB&s73t=0Sk4a*0`hrk!rE&pBrFeSGa}2|d$9 ztJvvEh^^vc5OtHuYtnov!o>9qB_H>G^$1~BxPAL(Ao0&l%R?p4GsYluEO-BBv zwP0z)M;dH&X-f$(T*kfGl6SX=)K=oGfIg+l-@dvzO$A!?9xs2?pRt2`iC_((>ESi?_ z%l0Q3ZPcq*M?PhNoMEqj{0iZ1()rX~w$%EtbJ+AF;GmwY&&nMC1wX?keo$t)paiSY~+VO$a!b`vGkcT&nO+9^QJ$@B|!B>K*=`KZLMxY00 z_f0PkFQd#f|A}ilVtjC$4 zMMmDA%1oMi6Dv2FW{6#LbEp;FhUdmum7|(oO|@ggH8yI_R5SZBisyoYQ0-OE)q+$)F`(Xt@9(smSgYu_~!e z!JilR0iosN>p*K&VWq+cKzkP2_PayuYl7Mujy|hoOD$JfaA}Z*9q=o3f@+5$;cC4< zLy}(nH5rBU4xMrx*h~*FJejcI21lSMs zmExu)m-bFw1)28kbQlWvd>e1F@|L_8ZU+$k_+U~sU+{}n=gbU^`$J&Gi;x$RoEY9V ze}Zl5wFbuj1UDL+5T z{Sf05;-jKDYL^uC?6yyOd-&<>FoNTfN1_Zxc9L5v%v4HEL)q7aO0!QfRn5=uNbIRy zJqzy#`h4N}X>_*eLNGP@&Pr>X8n2b}xF*7{yJQ<-;^AmtD`uTA>B*b#QZxt*`k@>} zrg0+%ZOsRk97J4Qu&dinq5dl`$cSJpm1h$o(8T>ff;hbp2R=igl%Nw6VwMGFOZp(S z=qVjn4Wo6kC*%q^dihnReO(LVMl(p^mpBdkh=Ff1&U>Ju7xmEX*h|IeLFtYMvTGz7 z+G6z#r-xe@L*B8*3hC(4x%2MtQsOL(jB&Wpka+64US=dW@!x6B_cvjca>|9iSF0&#?^?8>5T6jw`tZs?^KE8p_+cBoZxEAJaX#wiyJE}LPAKvx9#-Ze2kHCx_)bEMm4CRzYOrUszet+3kZLzZRW@Ni ziJ|+E&zk1}WFH;;gUbc*ctTzumRipmcTJs8IquEPWD?ll+o@i6QCe8ehtR$$K@%9a zAI*Ce4a;g>AI%du*cpu`#qp^L7Atk?DQRl(*@p@qD%Y5KH13~-1AUnGg`{KKWvG+< z<}Ol-_Nj{gbgYH_MDoxu?)B9bmCymwVrrj+F-9jR-5Zqe+hY7+ez0362&KAG?2f~;@;KI-{yl&{FSOW0fnl67@?vv4kOgq zUg^D>%kr#1bNi3JJake}QPF_?ISL^Bf9z8iGdp)P=l^*}%f)pg3^2llKJ$hxLzhs4 z^5Fao#3G?_QWCu<*`xzN2R2E1a{GDoWVfM18ELy3!2mm-#i7@&{3vf%bF#NkILbQT z4t$gPLWYEN7jYaF?y zv0c5{((a@bn~Pu{Q1+(zuDO{(5dnExBsA$%UpTWFUz#6d)`0omlXLRg~4S>JD z^cg|`IsQx4WiWAYHe>iN9{;AyOxMEYNj%jTB*%W^<>-5Dn${|;JUi}8|5D`$M`#iKG;N3NV{&{r&C!_zT zTVZaz91I8(T-Y<0*e`8wPz7R?fjQ|yKxZbFy~~>5F(~^jUeBBEeCrNkrETLz;ZQp~ z>mb^q{&`j00{+ZGvWq;-W@a(o*zC~L?hFN5M0SR!nOq2M_-2Nctq0ZQ9h*^UeG}VUZa7&Kj6`a<-bVF!kS&ktGvHn8YWG;le53Kd z&o4Z6zK?kQ@^}~i$E3l@eg)|PCLInSKu+BMCP;r}4gdt)ELI365FkR=eqv{!e?Se0 zQG!d9>!hSq`ge7ZZ}-d?SQ@2DJp-3&XnRTL#(!zqOR@iN-jgR;^|>Lg5Ejg^A9i)mO1^~ zsmwZv7EAoi0c|}{K#{5wzPei{y(`y_pnsQ-7LI3pLjXsi0Qx6r<$sFue{%KrlR<`N zVpc6OL4k&jicUH%L1S!+Zj6CiNpfm(l0k}gd}@~F>g>38hL4qDm_Eu4^Lq%#7C7<$m}G$}U$i8IQdc zo&@r!H;4>CZ_=dCAHLSKN&@TQP2Ey8Pnpl z!%P9p#RG^QK>y3p^h2&h*olcGmj2L@W*Ja*%dMYK8C5J`*V^HiAyyQY6!xUZyOe^0y+m*QQ8)S90s|q5P`UGT+Wpa3MEJ)63*hX(^I~!Txtxxsp8xa4{ijtNYoGBw6a@&V#_fNR zmA}UR)l{0n*2=`p-UVQDTDv&dH|w1zZ?-vq_9_Iuj7D+zSSCGQ~JRg_+bxt9y zpOgDmjQJ(|iT)Xmf;PsH4#f;-mB-9{3>>5UbntU{RtNis>?`W$9}X>FCM?+mtPon6 z7w*}%$T32>`%v+q_*614mbo#FB=-lNz}Oe~SxZUZ^es^7=yYpdH}9X`F#3WVwxgiT z@@qoG&J4Xwk!pnPP^6eaM?Ksu#N!T(k|!(e! zr7D~ce*v~g!kt+F&73=}I$O9!Q&3ZkWN29j1z#fTZ3o}tx`CN$@D6dRsmG}|A40WC zhk#CC0TC$P^fSK2!M1qMJM-IMNx+|7t&i3(5A;o4+_8Qm2}}unjH}pcMxrhoqVprWp@dEI z=eN>lxafIv8KmcLN=V^prgNCQbK0s(cH$)H=??Opb#?ge4aFT(8X=X4W)56ahY1K=YJui>-4QT1Gn-DZ&| z5|}HP+ea`U=j^a*UuPFT(xZ$~6_!1bcR?vkV-EMXW3P;E2aQiwK^66p9%~_kBT~JP zCW+>7`%i(~DD=-_iR%+HW8@$tkZg-zFdI9W4k<^dh-3y$7c3I5xBx3XFwu@G!~tMY zFrd@STsQkTmpY*RDH+oxmC`oQ!Q=WG(_N=6$qwpt;!8-uRC6@o8~ z5iIA?Kxt~Q;Z|dKdo!VRaz@0e(Z59o4(6mJt0k8)tJ^0p;`YEt%)G94Xh%R7i}{0Z zxf4sPxeSM#u2e!M2jhY>wpV0BlCwoPbe)Co&Y;6Y9*V4#lX=@i- zY`8hkFTZv$4Zh~yf)!U22MoM9!(LL~%tRG1w_WnC!{vl1>>r_Rtmx(5LXlP$$Ar$y zgRPQFv6CEVVIiaJwUL2*b73n^tB;3G1h zI0=sQ*h+0mdd$Ib)oV0ztK;~gag@O%Sn@{xzUPj7Pzef0-h3&{kz#9ek{ zbH<>a)k2uoZ-Y#+SjcOOmF$O;sS43RTav&$cbdY8Le(nROC$S1PDhd##YsM#i7a4e znLo)!NiISr*)Dx+A}%D8&Rd8L_ZJW0c{G&CjTfI>{aqf2(sDBP?fiU}B;BH7Ya2XZ zzy{%%C#DAn7&F=?7(^Q>;D`#nFtnDcA@!r|z=j&6<#Sk3rEi!V@o7_W^mT3Eg{;UX z$a3ea8|$3;?lL{hls4Jdf-KA5v-TSlQPLGXeQ@HaRRjwU%D5OIk!eS=(4yQxT9TKO znyB)3LEcQ9I7bS*DO6+@?D4u6uF-9K8!}TwM5}04YEjGtOKufcWHVYh^i@4%?kNyV zDbIx+(h{T|cgM~Ky|cyI9YsPz01pI+Xqcd4l9|G6&ZD4ua_52?N@E_n_6uu4@xz&Z;IaCVusXH$m})X9 zhI|fC5Ibf1^SI7-7OpzfWu^>h1$A^bz_#Xeo=`X{3rmYTqWmZNhp9L0c=7{^5HM>j zp4$&!k*EnIpXXDM*stgi|P- z!I-mu34vr;5F^dEWMhR?x^wK<vSYfNYRH2^k_jq$$=^&eJV^Vu{OLjrfKsL+VNnAU0BC6D>aKGKmr z5q^=TsQH$h;lU1)#fTN*V*DI4g~_y|cS*hjt!_%W&qTd%c0Plk9708;y$v@-xs6>+ z3}6sprMhlM8U-y9f7nk0;q4NZc|23Dz_i0Uvc+ltxE=3oBmEvVX2P24-HHHHa^uma z>s%xe8D?LtK}_D$Kno2{>yc?Z^9vsFD)qMnu{LOD;!izv;q>|D;u^;uIo@UgX&8JW zlR|C0Xt`4ip< zbx@a;LAwQ=wmd4Pzis(P8Ov^oa?Lyh6*jLL$4Y1{MK zz~wUm#PjBG#XQn_wK%r9`-#ibW0%;jPm@N4J9Hw;Cu(-cWMV3)+5YhG8E2dbIf<4b zd}ViOs&-z_QMZ&`w{Vc`(CA!VYEmrBqrW+8W?eJRswMLAmN-?F1_LgQllJP+3l&~+ z-xx4>T0u}1#2_Mp4p}i;s9zNX%CyKrErlCiV!|djR9_dK+CSA(0=RA+obR~4?i1Kx z$96wY1D5rw-$IPCpAcl&WnkGS1-_s>b0PyR0zQpqsSxg)SX${yRNZTIM(3xK)HGA; z^%%3O<3{3u*@qi1*P9xQ=U%(0tVFfA~yizRV2Xm?~^sOj&d^+KMr+ zugW4;=_F2_Vo;MpHlaqoP?>IyxDC1c-Z7(?8a%d5nRq`Lp77)~DDWN%5i5`FXYE#c zLYS>bHkcm&N^5O*lB}wL5$zYnACJB?{-%o?tX=&v&qMy54qt63I&@@jd_W@`Ld;f2 z6heC}bi4`E%7#&!TPra&W)%qml046ZmRa)AvX3rDlDZ{S?fXA>Zag}4{ z3`~99`DNSlVUdHl1h_q2cD-F3sXCZ(fT>QRMjta2!v~ z+LYVwJ6y)PDz8_2hrxBIBtJymaRkrqhIiVlS4;cv-vxmyi_%)%As{P> zcVj=4H83{MSMHvN9os6W8^ewUg9LD6=?%SM?MaZMvneKty0GM%-?oy87fr=e_Ez{| zv|?8_H1%V{OS4K1sMC$?#P5X#8hHM2vL z#m&k5_rdf79m1?W7nI*UqBy#`%0)hnQ42f?y1hw)6gP>OnrbR{Z6Fz+ zx-c6?b+zayCrdaNo(Pj2L;U;I_zz&e^?Yf~KPH94B~->_v*}bDe2HT1Tln1sv>05! z`DRBK5H-P!Xfcn^gEq<&g&m%eW zG3IxQ=xpX2+6+HJ&d74bgPSp@U`E?#upDq)yofR|QO^=oMlB^n9<)4N<*6BWZQZD@ zb5TcIE{?5E=vUJk+58D%Xx3=+`%~^kv<18Mc_XX^lTvzz7a4a96Cc3kG}A14>Ac{a z`HsuBdPU@8DS22&MWol18i@(?E@j7kdmkxVB$sDCj(W=?NpA@v-}s@|$KFWoc%F%i zy@|!})_C@w0-|e_w601YG_wx+3U&)SsB<`_B9S@^^4jOi#e2+xdKV{)9GU(&>%R6X z$|g_Q%+m@0RQix%+aUP3|G5kS(P}$7#iA1i%y#3rPJQzBjs zGlre=~NXovHD4bLF@W=UpJ+EIce=A$1i7YTc-Q* z$`)W^15LT(sw%uAo_11yPv(k!Yo-wcp${PAZx*ai$21ELHe*7E0jYvHckKmC-@jBu zZUsUv((aUnu{6e5^RE4K%3du8@=mRt+$QY7D`k!oJA@wun|7l(-G9-MNHi3FI=}`r z>rTZt(**OsAn>@C24NXJI{x8sn&oJk*Oz0z#nHIx76G=&S@k zW1LT^I0qzVn@hv!`~H&9-4_Iu;6JrmQs)h3?t(rcf-EETyTY}$`T-Fr3!baHUKi5$ zYM9Y5#{1mB<+SDMwmV~-MLmPX*dqR8{Ar|;>2=+``37?3hV4F3gO|JDlpp|Xvi2y`T_|LY| zdao|8d+3QQd`^vG0y<}eCBzYLByAi9-fx%D#v@DRIu5d@C^r|e@?YdPs!P~#-ackE z^m3ACp$8#Ez(0!Z)TvzQ5BaL+lLi#a;+|){nEDS z7phqpEUW$`3hS4;1U`Oo%yZ=ka9|Q8AIQ`)oL`LT%H*dr20j4#Y6~x}4PSS6BlY&l^H4EsK8v zmFtj53FJ=9R~?o$K(?qz6(y?i(a@L>jW~}j3Tb~MA z7|?L$UrJ!`I(BUEYGlg%IlPmcbcg~OQ*%Svs()ZDhb6^V+t7Y!ng77!nTi7TUT0nx zs=sD))wje|oG=4R7nY{wz!GultZ6%GzTE0eh7IY^xvih=qrtLOqzJK^1=Rm)(PSx)sVv#dmPXO{j@OJ@|aF32y1f-WiV*DEc z`%eeyU%3aGIt~Y%X#S@fnp3dlr95&w#M71wKi=B}k6Q%||+k;&CI?p6_;7 z-LG0ejXJxK2=cG*Z@ETCzk`xV)a?>Dr>ivWg8Irrn*otS8zmYgn-SrZj8Vm)N5My z;s=B?(5F%dVlSoR5x0#cjx0aY8aH;&KXY}>?=A}KaQxN?88T%v$*yA%hEepE{!1jE zYO-cmyh8?EE1Fi#)GdNm6njp+Ng3-?bOS0()ocr*gDz}p!IV0kIfn_iGQQ%tvS;&g z;82~TJ9SzTr8kr37ww6c@MbPz5JpafA%CN}KAcO^uwJ`eqR)N%h|)>Wfuq7~=pkQ4y|a#AImbQy zuDhn4yB1vmGk+d`+muy6y4FBC)QsAT_>b1ny)0j$Wn<>Ph&}5NRgV0TpvlDX=!Oda zE~8YO(_tG-CI)4Dzls&Hi~vk7Y-L?p9{&#Zj(UOOE6f_gl#yCwHOxXGF;;O(IZ(J& z9-F4=Sa{G`M@$I$1X)tvGWgLyERo8{FB+AFUEYO}#*r~`uBhgg&tiu(DV*_Jf1Ogk zf<2su@qJhTN1R$4-!~?E1PSWI@JJn+As)Y7QL~gTg11krzvJQsm(7d z!nto*0erIKPw*8>AQ($-j+-TK|3&-@T|hYmJPtt659B&y7l-_z@_qOGLOa_okvm_&pDF+G7PTpnXcn_zx$X}(7vcBjSy zuDo9%(`$_7a$FYAy53pf(H)O^%`2qg)+DV% z)<>y9Z4)ZRrejb_NuBYo8z^V2FUZ4;ENEwq4n5lJsEz0@^!gg4+UzGAz>Q;wgJR%R zbP`eT%!x0_Lq+I$xGgfOrMYS79OL0j$v>^26FNs#0^s<&s&;@yo#Jwz89?i&b{YSa zz9@;S57C=#ogOt^<+mZ@`|@v;PMI_cPS_2<$s@@OT~H6Wt;&xcwabBO8_w&5to0{W zdlBro4!}xGr{)G&SOSEp$s#j=!9C4y*n*HFm-2R=W0{K#^KIK2->)LavP9|<7|-2v zEVH`DKx;mR>x&T!4L^r|2;V2uW`hle2)wEs`4Z~IjtaYDx=QbhPS}zzxK51L2?bs1 z{;Ms#O{l8}{Jw0JzU|@vc-cDJePc$(4&S!W`=`u5m0CN$Z6POVo->JTiDGQnwr>Lv z|W8$)?9&VAC}IO9wHh~e|rh}I!qEI zy?71lF@rwkoY0(FGAISYdyifhBmsG5>L5n>POUbR@uhSBdb?AK%!D2h*_`EfH&E5R z?WvyCdpn>H)*X4Twhz#yW6BiF4*LRT_eBz>CnmgSf99q!`Y{k{@Dei<6jI?%J>xu` z*^dhF+kx(iaA-4^49@i!5Bqd?8Sc1enu!X@g_42TQ{Rh>ywd7%#?yoHIuoSSuH2sJ zUf~jxAtyz^ERuc+=B{80hlf-1Y~ep@B6m97S_}mbh`Kw7OS*j!4={~gQ3hZxSQ2=5 zEWpNXErrP}ap?yx>1P7J+mclp2SE^A%` za=o%FY&2eScXY-<@qfX2p0qswg`St#1yKEi=NEsEzj4Zcn0x;nr~Lnqm@iO|PEE{6 zRf@~f0+jojL)|<**wa#t(f8#N(=wBzs#jviObrgA;%BGC12?Ry5Nbf$1jI%CMv{(H zq(T1+LGO$CbPWEU<;M5;n+Ni5GyPYZ@Hg)Vh!H{LjdRd9^7lDlQ)1=Mh8ko@LTQqg z(ST8!mS%P^behNJtB<2VVyr##%z%hq1m^HOna~A`?VkYeOA1kY)MjF~Vy%9Io!XPd zhSLH82jr$?iLH(_4Gt}A9wl~a7yvCZNwWr(4IfjNOT6uP_WoR^r;OZ6B2O4$o&bvn zSpI*p;)|%)g1f$1@x0$^`dt(9UoiH6Tf#qW~eRamGzxJ%W%z|auNo7YN86* zhT_N?hM;GLG6d$X;P9g$n|ItGrhWM9c{abEo!eE8lF#nW%G2q;q@K+lk%?EjJcY}4 zt2EB_4xc|S&W_v`b$n*=qD%=vn#Q3TWAr-<)|O4qd*x-_88<-Fism+_oWnZA)ump@ zEP^z47uCRBd0r2*psjOYCj|Wqe(vO2s;(z%csShBvlk}VZjEDGZWGW}5n%CP$*r=; z3-?d~jU^zeb|hb=_&bhqo(=k zF!}(VMT*2@f$L*P%aWPkoKMHgjeuLWFU2f?P#^~acS?Y?GKZS&P@H37xS=`zsE%En zR3}f$=>=-_UI5v1Lva|9@EOt6uuI^3NDnMJ#4{S%Exr`hD_am0$2>xn#bEUiZw!At zAA6s{wRx7ko#(S}m*w`ZIzZ;mHnvu>fZmacOBaKO^uXh>F?v>`k4QlfF&P1D+J0ar zM-JR8k0OiR{mV`L7W01*0sbb<{Cf$I{}+Dz>50IS^!FbqeUHDj^FMxUXlwmVfBHT& z|6jE8cNnTF(2CYhme04X=!I$`=+Y68HIUHoQK?`qIa|Pb#pT!-kxJu! zo|o=@Z4S$f)9@cM2z4HKZmkWNM_xi6T^x%7XCSY@uW}G2%*t|_QfJgUeHf4KU+ZUImhmAr;2!!BC6F_pu|;+GBdZ+u`ikH!iV zrPX^sQ3<||uSgqlrz#|;>h#)h6sa#~%zKhTJzYj%A##g=Zc)22bI~lIy9BdTpQ(Tm zZ=+9nu-LC@rHC+bDJ%?_`96XEku9{5Vl4@MngmqGv|*ag);c)h{$rEhA3Ux3<3|{7 zi#q^|Z*X0L&2N;F<;I9A8xl82jml~jz4T)BN0z%{8rb@99kJqo!^iSEkdC&^KI?4z zrxsrvJV8hY_U$73?P@f-E_LaP{ts_M*~HTki0=3#PBM0o(JBo+GFLDKkA)_I(k*_! z#P*HHy;3w6B+k%qxNyY6N$Lpeg~+ufz7)E?YV{QSQ#FEY8FZg>E66hMfscRNxQK_P_G>u%ZH*1F zE0@_5iu4TdW0>L>dQzOwRxVCgL&*L{Owwb6m=VVpaPo;0V9l|BbU9!^CDL$9f6JP7&Xj~ZW~ zGiz1a2M4xIOYVK^dxp0$!!*)k7|Ezr}BPeeNAryV^(aB!|SL(CI1< zpUa-OnLC-zI(Qz}ewRQDh$Lq!%Moh$uFQe<6;dNI0NV@ECEsqmz{Ka@ED6uuaZoGu zOuqv(b&^zXxHd+9e*4=h;ee(N6s~B?(L*r%75gB#*s_{tw`Izv<*_;8A+kF-H9Jj| zkIKUp(1?jbcFDvzWT{=a{Od3BYJ=$~Pt~`fdH){&v!(iPJWG+N38+2>gn%peFnMm0 z)kDbn-_!MZ+oc$a?S+2t2`r5eyd4{h**q8Q_4U%o9fgW)LO4L;5{(yLAwM~f^21i! zvtpvrd=Z$M07ThVePQ@r<0&pTU67dtiyH|e;K?2+R}n8)z5Mn^`4qkEPMHMbeDp~lTc#s@K}XJ@Utx=e0qcRljXoiXI6Q2dS?_cU`aEMu(`Otd;|P-O=Kh^%m-h0b zU&rlPfaRXIvR@IT0_wQr*2&Qk%-D-xp|+vr40GJ_?DgS{plHHJmv$Zfm0(QBzk9g? z-&UH}815t=<61kMZhXZ`jg3CIY_R59T%p%Acz10#F;^AqP)!`|5 zjOlT&u#b$RqvZVn(?k;Y308qP3fHO_wL;_2jJp0=Z~| zZ+YG8K|P%`AF7sy4N~)dJfv!l%6A>5k=3!v>4h2yEc%IY!GR?D$I)((nB_oN6lhyg z2b{op3jx2{J=K3*nhcmEiD0Vz;ph7~NUtgrX0k4cmK-@ivyoQ$MhZ^8W07l6NRE{? zm*;EcIHeOBlVs6(0gvxIdI_U#vKvgeq3Lwlt|(46#pt;_ijHA~F!DvKnYNAouPTwh zI#AN}ojbit4FK>RPx}|o^dFg+n*ZZsTxcjbuCXEcELHRGu#g&tM$HAwvL@oP+pqmu zAH-ZUHlk}2gbWS+w;_1?^73hO5dM@mxaN#arC2&dRscW z>cj+yt;woi3QoFmmtSWxU}Ev3`-rohyl+pftog!UWkWVu_|oul=2xc|(e%3JJY-LC zxF_{K z;4|p)9J*xtgE2$wgT=(amSC2HM%3++vaP1MG@n{Y-N;CfB1}c_>f&Y0i>>1&#{|GL ztfn}fN#!1b>t~z<1av{S%<%jNMcfREg`Tzi1@#4D& z7e@moDr5k|)O+!G)NE>L4dQUFP;6Pzp1>cD1CH@Lz_}v+ImTG8h;Rvhe07E%hZ&Bx zw|geP2Xg7wYzC+8V`FUG!Yf5@{sJU%29QYa`aN3&lSgYX{2{%Knbr0SkOzxh#+arj z!Hx6vcfyo!0wzhO+!V^M!G##gqOlFyYww$VAysb2QMadxPBtwoSqAe^Q%YupNsi0J}|7{#^MA6U2{Q;13!s?V^^NGiEOlM8w7p^z=Pt#MH-$&($kjIruVBHqz;CQ zHj8wMw41J8T^6NktyP?HAx>P*k>*IIo)btAT)(KvN~&Z_BWF#%0>;IG$2Xn3?RDni zh#>%Sj#69#gj4o!kQ-`<=|;L&))9jWo1mm#bSN zAPhoiB)^Ve6g7~Ym1a@w-c&}f?(!}%haZu%$J$W_GkB(h8~OMxFaz3w8i^wi{VVdk znyx!bx@u9Y=Ae0sEIw=<3_>+kL$}3k=Xc=Mdj&$d;Bu-p4X7`uzBktea+jV2Q`;mo zhM{>%t_^WotugJ^!!uPVp&NNAAbsW_dP-j*mWiYyS~7Nf2aoa9a4Tgm9;X0R1$M8_=QpX+A`#$E9nn`#mF z^!n{goWYVx(iNnFO4prIKho6uKYf34ZIvXbnV5jjV|?c&YS2U)U9|NQrk@D6Q?Jtz z<>|YrLC!mm^~$J5(bc)ak8nZrDxd{2x1d9mF8!N6p_IjI-`gjQ4nHUX$}gyj-sQu< z>L!|+yvE9wPzYBV>g5IVc!YG{8b5pb=}i@iA|!s&sWaI-5XRbp{BGQs#Vz z6c?58+Ni~mz_H>Ev9ZX!o!+>(}Y|=I*%sIOQ3>>q|yn%zjIT=pQoW<=(Zvh>b<7G^2al4V9}?NrK`SNW#5mupUngv`}3;18T2)j#|W!v zW{{Q~ElbjDRHs%;KcR3UY~hOJB-IuiHo6dnKj;#c8rP~O#f`JtWNmQ{g2GISR!J|S z4%AYYc8w7EqLctJRliae;}&uwh8xd?!{&1}ekY(?IWR0l5jbZXT3mM<%4LL zgO$tC8Z0wZuP5C#$X)*`*9TI@ohY$E$S_>GaE0MJ!~0xhh09^^tI%B#ERPso7(~ia z)hgM`+n_QpAIAd2fw_Tz>?swQw+J3Dc@3+1mv5zOe6#$fE^+v!%+Mxy^f$x<0sI8W z0P`&}Mni?%vrZcluT0>H3S_pKF}pjv-Sd5iko0w{vKp9)Mo{&V4*o zfOmMviksCn7bKyuTYBcveUm$nEm? zbEogy@z#^tU_>Wk}Lw?12Zl(Tx6_N>gQKWF3s{ z-e6*x&F9VK=CC68KD($QSgf#Zmf|i*DHVa@3a%l!ge{wXlh%-dl(v^CCA~w6ItY*t zgHRk1v1)fltW}WX0R(TDo~#O{Fr64-jhL|++5=>}VTu&2L>#;CBijBkA1)xQ0LYUC zl_xynPtax>@IE7%-|aA~Itd15a?f0-y`Cj!GYll#AP&G&Nrz}E5(L*EhXasHcW)d# z8n=z}&q;_~2KwCLLL&zhuWax+Z-A;y(&9A1Q0o=@aCV@hK$Q+H0B^zk1BIGb^+cg# zT_Fs3V9)H-)0i|lAp(s|Y6M43B8vg^B=}jX&1tZ*cX0Fm(cnl0P@DVm9#4i8^n~1s z-CE`7cZ3cONl^GrnSC#1(_f|xU+e@O`CQv)2i8dggafbApgd}{$v0o)FDp{3of7?I zk|yrCMr#CFLgs@+_?9wV*^o8H`2IB~$y&B&U2;{`h?&=ZENu>@Q1CA}<%o~G(qRxx z?EvGT?!v|R0HLXKt{$6hV=S{@OvrVNn{!{!g|li|@N5vP8mwb=uSwUUlRRy?R|O={$mYFHM%%2unZQVy$LJvgU&r;9~~ z$DvaIQ31IN=-L}J=0c?qb;2uFc*(PS`$umW7Be`977EXEN`tg+Kh>)7M+;%^RCEcH zd=43kj?>HhVq3tBlX-w4)w}xjuO9!mlmB0FN&LS({(sNn`|mHYzjKs~A@F1WiA>6W z55oWD=l>FrhaF>t>mh*PpUG5WSfqBknucycnzb;<(*yAQd>u~Kl>tXuKplrdY+_sh zQWy5mFXaH>&fK_3iuUw$WZd#Gx-8c+u{Z%)pWx~JE2>_iTJ|3CJ%z>Z@pl5?f26F@ zn>y%QTkAXg%j42If)DyTgdz6i6*euAd`i^90Ya--z*?{o4FL&u7k&Tw!6TwDec?0g zIhBOM;hSks2j<%zqa;veC|xpB$iZlWFTLpYaCr+xA~UqC5L$%{1y{M-e&i^?ctqJ) zQBOy<3mz94FyR6H=ExTz+3>*;bDybpLzONqec{}y+)cGnEc!76bWD? zj`sV0A^-Ufx>EixoBLlY`d*ghx4h`AzQ4VdwB`I6P`t0ITiDdWnEhOLSxN?InAlSB z;YHI;T%yF+3Y|oe_iUT2U7 zoWM&40GK8?-hGkd%~M1r3WMf@=vJ=*IrbPjBNgKU_8`h0hQ?Lm<*~J&R(N3wivE6!P zR5qe6jjuCnqJyqR#Ex|(SS9PIQtABu>;-#d80%^%M4Zkrd^vF7?7}Mdwg%Qy)d{@t z)|To*3tEf_x3c(tV&?^6;0A#o3F|ShiwwgL`RC%1gxo>h6n0F`mi3e5^-iGo)d<_64g9qqcj7~ z|J54(ikd)91i4{YNJb=MgRH~2y(8MKWE;^?Z}?N3Tz7?hO;h@a?zt7Z#Q9W^dYU7k zNXqL#X2zcrYeF7Tk(lM$4HfQdW~Vvlr3 zg#h=joS}szwbCEIeIb1Ur4=8h6S`}j`OPKOV?h{su7JqD3OhGeP?0mdtG9tOm*LDT{5 zefws*6K{hL2j)yaeq?%cX8)=})UY>&V^I#;i*A@?Aj~XeUsofs5Ui%U<7C`APy%LU zP9n!8fB=epD9;A*onkIab+1TX3a<}$cW{1vSbG`7F;GjYFcRjQPBxOqG~~LEWbb0U zjjXlpc!+fEy4hs+aOZs;D%P3xfmDX2PB5Ne9!XeXOg{Esqc|x=a~U4Be?siY0?MK$ zgd{xpG9ae0a;s|f(~LbjzzhI^iEA1OSSl5+3f#CfJ{^D!rb86W1hW1qFx_fS4>_r* z=697M-Ku(!Q_55pR#uA_$;OR(AYaTF@#E(WM$|X~A+fzzuv;{H8@T{9kDtgK)HVY@ zCI(*`ZIu4}l#3$B6L%KG%?vCY^X<`O{$_0@(^JC{5|TEFg@n7!%f1Aq&pYuT0w(st z&DnLx?Ln2t?NT1^bUjhUW#-FdWwC%jdnnQ?^%7|r;pmrUg+ivN8h_gk zTCL6z&D27rObU0va6>HLxnN9MUbWL2wy}m|E8J*!N)I&$)wY{>B$$j5Hqy^0lct@M ztKIwvk=?lWpPsFBy19|wEIZ>nr+$A>z=tEDgMkU=>aYYMH>e1OUJOe7mG)D%#Rk(tldm3Z@9koy9-D=*ngN`t^ECp9?oU=zM&0M=~B)9}&DNt8X7t=Urq1;X@Y#Lgi*i)uy`XH&f&e@^PyGE0PRq$yRbm3|pezM`1!|;P-CLom zj+5pdNFzNCkR8L$aDI12=u_>_3lZ(}yL&1y`D`0a6IP2>Ut3;EXl;Da&P(0Sb%!2g zyD&&at2h=Ly}R}~dxZ{dB2889PC<)wdEJQnOoy~XR}g{*da^j*Z>`;mzm*lN#n za6?dRI9H~l3SCYjG|f7USSZ)$&i(Vns3w~U`>nZpv|Y+7@d8$)!?^NOO z^aBNVXf&=O46M>Ew@!jaF@%4aAESqE!E5o}GK)5-0wfgxNO}p-(`#Q~7o4T(@Z5MM z!@|XPJul=Dyc6xC%(vAWYu+4}e6X>H79rD5pPYiD#s9Z=vJq>up1hb#08&^Mi`Im{ z**IW&alHx9p=HQ-$#Pzx1yFeXO%q8~%8|jc&=>HU;6}pRN$y>IlNF09$YeIf;|oCG zw5#BDnmWtCXT)ADJ^+iF%Iy+-y-$0s8)MXAFx2To+9x8ej$XKY4zw_&;cM6d(vUMs<0!gNDLsy7Sff(#O622J&C`>{%|}iiQ(7jhcQLr3VhT z_zdrx4RdvWWG<28Cn3NYlqahsj4+Qg7<&Dvo@>)q{^hAvb=h2Z)QdS)U%!bT*2vxF{~8~LQMFE&_tW9?{XzTl1mwU85$I&H`Ous4{1+GQd*|2L z^B($MwAfxXA70G4g3zd8XK5Q@dx4HIcqKs@bK>zU)o(MkQn|1Xti&H5I$sqaE19b zS?r1qtY#ra!{aRzj9-UdqRrLgnjpR-ac~)WH9Q(&tg5;D6B*MFj_0}#{>IIy z{$#W9UT*Bi&9zv;nbOTd+s5mB#O>0?2K}2yLfzfmp;GCMG{nE8BrFA+tGK;}^I;xHC z<eO%R_JuusF_ zbMV;^-zv-%=pBlVNE&u*iO)tzlfZ5H4`h~fz2aW!{MN}{GzN$>ldI|(Zf%l5?mgLG zcFxRnw3_{<=ptOeZw|2bEg?OL=j{xI44r~0gh*YI^)T?dCCj+5O%`?Amv$h>GKv|R zV#XA_-FT;IWBTA({9`m9u(gXXWj6Z-Eas?03SG}s&Sd2F5Nu?D5wBq+hB)k|9|-*O zCYu#uWD^IF$z4m}e2B9RUuSMx5J6n4{zXyxnCVN$X|za|e3Z_0zqx657jY4%fTlWq zq8u>Kgtz$vrjZ9Y+uURA@mE;(N54DBj)GTb8_F!gr+CQ&2DR@H)C=HT&-F86P$t*#_J8tKsdjBoXJ$gfR#;)ege0ooShlwRGF(G$ zzbsxR57%w2!rM%N?DP6F&q9})d?^Os)n7~I^u#_fqNK5iTW+WR-iK6?jeSOEW6^rn z1fK}(n$TPILoM7p{bI7NcErG^Ve9MxLFQrFq=#i072if9?Vp_ zk5RvPBmBp9t9jY*skA{3g_MN7(~2uK1FlRu6q#mLz?q+QJ)H!VU0ym7#xhiC@SncI z11VPv*VmF67@Oy2Y(teAZO_uyOgc$MWpBnM7u7Ot#B%N#g_zvd7c4{)to7n(`%SYLo`_cZ;V|^KGW|;S0S>?vTsvqf?>@jzB%#h?8Skeu-E0jS3yM zn@QP#%9@1vn1=MMOqLV)O)QfVz?Z-c~q znFDV@_u(GT^Ms_1$_6BEal$%lJEkQX&56x=qZx=SinN|E7rnq|g@D<5#b=70NSgC} zf}C%-txfus(b0G>>BO254&TZ4DyQA1SL~uTl{J5-_>E!durzh2DJzF()FV#z`)LjR z-5HlczVggRsb`)(*~rHu=7Vpv^Vx`N*RdaWZ~T41EP^+tkE2k1tLCf>o5hN7ffGWphA zRwC&U!gH<)d3hSR3dFN(<$~bBhCl?i7mT@%(#}%0&6>nD$R5}rK*KoajK>KD&Nx2Y z;twa?z^}|GL+wVH3xl~_#;jjh1N?(Wqx0ifX76_)23{f7xZcNM5LP)Ozpfl(@M^-p zQ56^B5^Esl7$0nKzOBN+4zQKX#ByUYsz>h9YHesT65+%U8oGWi&GCD+2a8U#Ds9VI z1{4XeJ*g}fi+O6EGA&TFeN&osJvp(O#7=lPr=fIexU^NRM5I`idTQx9XHHmUGKtYm z|NAKmX;TiXskl=dM;i%3g6_Fvq8aozB-S8M1nhm|bWYBE1D$Zg#z2ne+wrioO>xV- zrx{#u@g?L2iHQlwbpKP~$#p6{KL6s!5l979)LIMHWGYq*=Sh8w)5j#r_R2gbJTM-N zau4Sq2DB{S>^qAmkX%qp`8fMFM6QfJId4E}DSxMMJquE+JfT8)Y*lj9l3_MPMae#4 z-xLkvweVo%FdjS4Ogts0T>HX?DC>SpV%!pISzB2&=@6WBCA_VW5=D!k3OQhD4+dT} zo=-$>&mtWbD>o+eBV{*Y>CKFOs>2wjB^fS5V^;XV8^ua7~r-n7a5um=g1iE_M&V*5DCqhomx#1>VQ2G__Zt@e#n@8Y!|-T zh;`>GR=;Fx0I{nkp*; z{G}jq*;8N3-CC7I!?Sr+6hqT*tU!Iq|Jq7D!lW{c|Kn%$63Xj?sWzzDM}p7Ym_0Q! zygD13%9r2xkgSARbDVwga4~-79^s!6Oxa+Y%yliZ)NV&VwVq$6q{znlp~lT=H52gd zc1gfo4Hu)%2OLfXzSV%J}njuk!k&$0a110pUs57Yvb=_P}!bn zTb;{q=yfyU&WS&oP%SK_${Ij==znTvx|XUogVCz}kWGv7Xk^V&=0Mv3U922ioQx^5 zgTo_{tFyA+@Ia@!RP1U>Cipohx_6yQwK5R*sW3{G%}2Gu?ZP%hyS0PX5SjQGzKU`w z5Vo4YP@Tqq<@pR>W}}o*0bLTbMk}W>usY(<1FeL2Rf{FS1 zINBX<22PzCiT$a)MSB^3`e`J&S!Wg_nLwFNkGm3i%#)ym?Utq15H)bNOaL2x98~l0 zn66sw6QbLh=>VBll)sO4j$E!tpYtl_hUc;4&(y47kad~TR8|#)K?T3?eY}mJmq=q$jZ&ma z$8-DiBKX!WX#RW-A0RNZrm{2CttXE`aUfMKN{{NwMOeLi=u-o|d!XuW?wXR?1<*>3 z48T!g%VD>GD%qB3ry@3zLA9XHy}u_6Y_p)^fnmgpwx$g!P3=Z6)jXY6e}!5Dor917 zQ&^s>vf@0)fd(GYf`EZi-4T7a;m6`{-Cb({IXz`;T+Z;+5J==9x~+m!fl$>@-415!xYVL`Kn@#p&qdazCP>>`}~#fpLge zIk|`=Wlr4*tTqD&CSc}_P(eW)SR1B5O0R~{#@uPa(5d&_fg??nY&N`Q1+U-nnQObu zU$g_kBli4Whnk_$v;%xKKGp)VVT*kjK$1)tTJ3<@nVAt!a|7XWBIfW zKN$%{T`|%b!ixaf0xP1|5r3%*z^G#4vpP`H1)w2bwkFI~E8VM;?nA<-(E-``7Gv-) z^L((IkdB0CXs(~vrsbmRZ2`e}*VSWovNoE(QJ@Y@x17aB&n&wx?Cj(EPJV!pvM~hc zwd^*F30}{~2BI)5r?%|dqca^vM1+SG?Qeug_a;>RWLH^mrH+_#>3RO6$717`LSUPc z`-?dl@|lqZ{#H7mM$6s^_O1e^pA8sKXB=ED#EQCk6X2aLBFmK)e*e!^yrWLEicDJR zFXw~340aFM{Znz_8CgD~Mg{=~x>L5ZoK)llb^*fH-gc>=5(SMlXXL(O2QVfLsjuE| z#Cc`Jw=2?h+W{hfdN7jzQ}0DK~9$RE#@ld&}6fQLC{&qnyb(yJ$9-tUKi96Y*b4Oe2?T zX+}w1M^=0_NP<4ySRbwpaxnl5`wKWiZ_W()0qF? z2)RawDj%_)9T{%`2(mP7Zm0_}*46dj!n(jD+^BFWc#=QwFKRdvPUSSPdo8>^mg%aP z4<2ZT5rVa)4rVPEN>$m%SADk`I-waXZ9q@gXU<|}Tvti`?JUtwN<8H0+cP=4ilboF z+e20bE++y}BF@RV5sd`@98<>BNEC7Atp~u(L zR=mHca5v2Wp`yC7#Q}@kIahce&kw z*eHRega+ittBYl@Q<3|mr#SlP0Z4ePo=A5Tpy>|;^|~Ph4#P`MlEQUvtMMOj zuO_@xj6bXw3zF*sCQq$nTg%D+70)L2Pvn98#3^RkUsA zzM<@?>KdW~(tKgii&8~I!!S`$CuM9IK|^7(PIKbf<+_bp$mc7qHAtD+gu;*m$_JjO zv*-QIqTklyIK`Tcs0hO(Yj$KPxgxzN%9zvzdkLmM-vY6F=&3hQ7RZ;YXXtW6*XhWX zrf&zpFO^)(%!2>!8A6fokmi+7%>wR9Kw?s*x3uHM*qSy}e@6jzGzT@w9~w-c4@4CV zaEZKKbUfy&xIV+JsiWDWF9-L2Zl&fvK?qK@Bkm@w3Ky5ihKb1ulARUHTN7{yt!AAg zpO(qnW!+}710z=+l^GI2oyWuO0KQMXhBjWi=G)4vlC}SHY}Q*e5JcZH8nj{?uV3#p zz}hXzpM0G@`=C@RtJxM@E+D+fGzId;%-gdOVpQ(K(1EivEg1bMe7f1sal=My5npQi zr~F`m)lnPdE_!pD=*7d(d@Eq1iIihaK*9jEyjpQ9IQ{5>uT{gTsEQZ6VyI??{utEt zX!z1X3)XHHRtMy*A#_g3#K#xoCiQmoTa#_WMj5sT_KyA-u$U z+0F`iox^yPcw-W3I3pO6DdvM#zq7aK$0<-88Nje6?h6SpGWP%oYYu)ule^WK-Sr1> z;kjB<0qxW8&J&f)=T|45!!+J$Us6x7*`3>I(3uM+SS&qnFy_I#g%8Iy9c9aFgYF5# zr0YC9ktzFpk`Vd0F)>Z|$uXFsJZ&~p$9o>^>YpM)7UTA4Y}+0QAe?V-eRtSkJU<&0 zrckXYI5^D4Z)TNqx1?CKt{|nb=vHYXAZ7!vjLG(xW-ak;@-&5X8!T6tSei)FNRh>J zh}1pFlB!c#Y(X3#WfbNmY+}WkLDWz3d=Q21&AD#TP!Pds?rrTVJFKP+%x<;izx*>;pTYwiueaO+k1z@$XeED5vV zki9_xWc>Hzrj{*U6im_rwuOzH^DZIi=0oFqs$qsnS&#S~D?eh7!*&1xMI4q$ zd-`di5RC~U&Pw#pH%aqz`@G@;=56WYuDy%zIk2mm?&Hp~1C?hcCX(xB_%p;@3EJ9C zAc3YP2CUhYn5Za}4ei$Wi#>00yLRVX{tE2l>^pfi`zmH<1<`E<_A<=$N3Vz&ir}=H z{l)HnoJ`uHf*X`c_xW`n8D|H7YmT3;IoRNoAw+qn1q5C?k%rh!gi{XGE9Xi%h(NSb%k#uL1e%)-S4n&t-T&L8wUl6+m&W0ZtGuNk63 z#f)Apy2O)4ZA20UD(2%4l0;E$8k`}^o$f&yq<#V10X^k-No1NX?FsDMc!+RjDLeuO z4VqffJrdp-1Rg=M>-^f7QWfVKUkH{wr!7?6t9j3>%vz}DaicnFn8x3Xt9wvWcVyrb z=1u3k#s~ooTRv9(94xIN%Kid|ZG!3NmH{6I6JIcL!!L$PDWv`E9%2sOq)K7gM{iizfsE?4=t)%21`|!1DxDc( zy7|eV7_cE;HP#IWE$=TEDapxG z9jd?mx{&~PY$RV@P@CkxaKPQsYbBZ%qYE`Lx4t+NqYH=lLzHUGlwAarO=u1V^)~dQ z>uB~6sia|^CSU1a>CF7N$^ChgbMDSMd$02?QC8)fQwta)lDod3hHFigFO}S}R4kxlhrwXn znhH5`deF%iGPl4qQVb{VsVDXoF|Lx`2YIokc-caVh(f5I-b&}&Kd$$}@8g!to9V-n zxp)3E=RGGpi*8IN{idKXx$Y&(7GL!2wp_M?_whR?VNdg=%U)IctZwQGrj+ko@tyoN znNV*mUGvFG@l7^@lg(Wbz%pK1)4Jrd0P1&J-oD(E{vk&LFs{B^_EMDxkh0kxk%M7Y zGnfJ4Bt5&$J-Dm2{_fhGJ#uUcZ@2k{8Uu*iNv23UCqli)aUt6^!CCF`4p|@yTYX+2 zb?DqhMnmsi^Qk+3qUvU~`$nj}%NiRv#$@#q!XEyM2on)_bA|sEDq0i(0L1@qvGPAP z*?$yRjT_qy7L4!g9{&=r(!_Fs>sr}iPqSGQ?SH~Ft*+Q`g7|a{#udrJQTg$$+CMKI zy7BR1Va*ARoJ^}wk_5QjJ#V*3_I)ix(hn-l{zc^E&CICrv*r8oor7CpfqIzu=d*#5|pD*VGUQZK46+T^Eo(GtAI#-O?HLt(k`>j8!vfAjF*J z{UrGFGQm|*Rd*v^)*1O7?eBrx3=6H%0ExxAz;F|G75RurD=qMf?&y^x z;=sB{R)hu3;QB4Po-&V#ygc3<+Sz=7FW~BsfRwmSF@(2|qN|(->>*0#>{C!}pQ#`z zWhOx>18S;(5+8d6LpNkA(+fNd`oHOTm9R>8;_mzmIjpTz9;~VRNtCN1>hccBQ6dACx8g}_Mq4oA69m?8|RRq*JVC|0V zM}3;;NjiT_)k;)iG>G51Vx$_qYkTB+gLLw@J!>-tKX*d5U z)6Ds8w{I7N8&H*(%*LG3{LcGpk*fG_;`#d$FZ$r(Z6~Dh=NQGPY|PgL{&3($5?02+ z48RU5%!_4PRMH7-%}u}aMB>AAhD!#0Rh5o34DQ$hbFy^&S)ksc``|}gt7C9;-xsT? zR(f?M=;}Eo@b&~I{G5Av6!6k1)s`V00nfsAKMldB^T#{&p`9&3*JX%2!|c^T(kZhB ze-$08wIlPEaD~e-Y0jgx6PZz;x6&*?j3>I!|KamCEHr>21jeEaw?Zbf+ia#2Fl*b- zkG-IVE7cVL&dwy3gP3W*f~h`J-v{8KoZ!K*bq?HagcT^OLNY-E0`*|;<=-vtq;MgZ zGogc~GBD8yMNm}`ZW^;F*K6&?l|$p`4eXpkw3W8qBM0FcanrJ$PYMe0SJDDFJ6%Gt z9>cI#^*to9O?*VZHsu2X9W-r|BpE7R7I-=i{Qwx=$rT z3j=lmH&+mZjZTjG(%e}z*ZK4GN}B44fb0k}KrS6Lk1h1i5syU#NV;&2*<2d@K#W`}E00FpLCesM)To1+tJan= z+5I@pXts^MVu=PQ@3N6Lo!or5H16#TE?iU5=Js!9cptgGh(QzCS+=f~T{bQ^j4*Nx z7xp}D0^`xXc18@ZRZgDsdCu%dfr=@~2#<@R(AIyGv$XpU*-Bmi5eQ0a0Sq*@6$PZP zIf}dj({M1KxKgwmaAPQmtDcC0-XzR8hhZ@KF#JnF?fN`(OeCh{4V zk2{d7R=^frVOmkt3OE0-n44+QN5GuM-U`38zJh3wOlxg(!VcvP;5JY z1z14gCro@>#&MF{^`p4s)~LZz6lcW)-$4`!!ME9N)ug#GrJB;tefg9w4v?V00yY^# zFirx;3PH_yFRw{n4bw^c{`_&Apc~5}KQ-+YwbLb~n(+%HwBQkY|iW{&5J3p{ZVM;SzJT!HClS7dr%rT%e*aV6YHY&D<$ zL5!I%?qM1lOKm>@%aW{Y2z1EBn78T-=@oR+Wvn3c ziL?BA-!gOUOWUV}aVd}0@5wWpJz!A=HY9&00z{ni(Z@g5197ofRzfh-!SF3T?n55&ELOmRJ#Mu;ev|S zbP@g+_^sG8cD(u|A7`;CE8++5vAfOy+USLQmnV@k&-FJCIHnn3{E;t?lMRKnQPtk@ zET+C7w|;~6k5$F5`ZqD6mnpnnJYW76H(o1xfF}>9l@;=9$mq|iTzA{nI ze@_!gFRza)&Ra)4X4zoGd#FZdZRV5h%{KBgU$L&?-Za_Q*^wAk4+ z0Pgzap+3G{E76p#c)O}Px&EuyzsjXnp~{Giap4AOM8#eWS*ao5^tM-ld07{Bgs!o>?`Aj;4>X;p``QE$`MAT`Gh@@5GVRAt8KMAl!hu6 zF$>xo-;a#p&t+Y>%5YmsL-r7&Douwk3h>sDKmG1(YVUHn-QBJ~+(YI$-gjy-?9DHp zh+Jz%&+{2S5%wdNmw?pKr28&c`G$VSOIo$H&uXMmIgdD^tS)%RUkZ73wkJh_Z?zj*(!a((sHTZB|&@>w(aX3x@d^~ zOjtIld=aZ+p-0uTMKDU{qh;#u?d>jDJ6oqdhv$1z=DYRPzKNZuqxJos3j)Y91Q0lD zb%~3y^6R@Z|3Muzxq|H1zF;XmeF#h|-0jzcnWqOnKj?E`4|pP99}U5isPN(wssFey z1bcsoJ1HX1%%B@5q6z)fY#~9ZxLa3Da7x0@x`0C!?rnCZ=8|CqQ5>yic`r}-rw5_- zPaZMtA8Gd#k?z1}UH9wADA@wN6CEyJm`uTpz)sPgIe7g>;@COL{0u8gb+R|O+p1tG z6rjcNJZ)QVUHlLSpqSG*nXzDKSjjq)Q7M^gR~;TNGK!`Eyn47vOuFza?L#@eH3)JT z!@A&lwWz1m6?M(6K+jhx8VX1rl;?j!avFMpp!j$0?Eb@#4=_V877Po4oIm3 zV@%2!Y1RU$<*`#dT~ZtjmIn)u%fRd%g-(a=-pHln7snn`S7~;jekm6PW8-nAMww(( zDrk=I(LD)*;7GMPgIy(C1jE8eqlDkN{vtN?X49DhiF_*!qaGgaiz4oufQ0xr(Z?h` zS+ri|J3T^~)%*tVc#bh6IHw=NrL&e~b1`peuCVd0IB*-9BD%z>_2cnr&P8HYh{=E? zoRWBB@p@BJ&Nve-Lw7!*`etR6Y1xK?oa9f1ypbT(b)>>4m@L;@z9kGgR^b2|LOPnv zCAxP8`D51bTremJKvaQ~>eH|><{ruo&je$emy9U7;6Ye*V`vD1JN^l$F^@rXf%%l_ z-^!5a@VX0Dl;c|ZFPkEtkBU?#UFddX-gk914ZlXN;iaujzuOXUW_P)xZ3x1W#S6+ zC>PD?)V3tHjX49aL-NSlB7+_^C6(_qO=_5UPrMhYiyDIk`5AJkt_B1xMsBWOR6HB* zyDR(9?>d1#J)~JQLBE@88F?h$LES9kzeyGN{Fmts&ax?W>ThSz=(o!JpC-l-u)uBPfHM>w-R%B<3u+Dle|V9X_FPAf`qPX4I7v6| zNu3%?9_f9ch4FHC*LXR*+Q!`6T-^- z@FK6nXqecMbC5<=S&OvwH^4!$32ICGL096W1n**4t;Q&B`OB*JyP-hbITbr{S2VQz z>w#n%m@A08{8~p~?K`MQcBh-&FkLcL9S4Z>TjS_tzAbt65}>PZnC&d;svCn;W>(U% zC@G3G{48uPdpvf!Jy>M?plZh4xrVY~S^Qovbr;#WN7YN)&5r?OxyHlELnHVgrK>Jt9pnOc0Y*7}2O>yhKhpoU*geB5O& zk!&6!k>+%4ldL%hV~t~~AsG>1SZ;P>Zx2x?BNepkSwg0jCkCtwp%(u&APU|`2n%<{ z|N4e^?Yyx?0H2J%J0i_f6q)IK_T)cr{|1xg5;tUO;DscXSV{Bp=@rq;iJ^zf!voh^ zK*Szg@vYG+s8h3|9tg8k5i1lJb)b6&sD4aH)*p#_2eoIeG^$9&hN1MY()@@fQo1_d zxhMUVEMQH7<1`59QU2aaDewdq1p+8htD37a?k7@zj$#RLk68lhY9vgsDfcrhX7TV( z4bi<}(QPp%jes-$cu+r2UJ$IIS{nkn=aQ@rH50!QFlEY|5|3|7bhnfY^Wv}@8K}7x zjCauNJzy#*w;csH;QMLSY<1p|C}oU6E%lJ3X2@RsIhWd?#)Zu;@Vh0TtSohL1 zo?{%Q#t3%svH*wql_O2jUG1M(H^mBeszQN%#+;cq_BN=03M=d}PaT8L5!lhY7Pb*9 z+Z{w0a0HzG-)sj5m4Bzr+!3NS4KIn@sw5+y;LagGhjA+s;#bfvgs2z1w*8$d&x{4j z)S~%)4pjuTNNS6RQ

5%?2s({ot$ivQMKuWTgG4Cg8PA>D^lLrG+azAq1?+H8BHy z-$jtvvvUK@tflM~PAFbXTD{y6;W!g%gR{9RRYNThGyuYOJRu2`q7y$y%AxNM@1Lm4 zvKP2Xqp%$<C;bh-Psaj^kNbLeZjj zLw*SW9Jy$`csNt_LqidR{G+g-$*uHAU}QWOoFqx#GIASmN6P073O6Uzmvib>1UupW z*~hT^p7gg34PC1mgi*VZLyJ&Ae1|_E`Gvy8H`k>+=aIXR7;R%x1my2Gg`!S~Hr@A4 z`0hAZV0TTa{m5IC`H12c%L}xBdOk%_&O(QQ7@e6}HNGqCuIvjWjY@0+*y^>j@F_cP%2x`(T~ zr+eV>%uUyGUpnIIWq=JVep8yvvd4u;Esi^5+7~4jDL2@Hv0Z8;po?$w%EFux0@mr> zpJ|hO2)mEmE3*;8x!HWJO&IQZR~HCWZgj0{K93uXia2{nl{eS(aOJTMR7{%` z674D5Sv;hD;cyR3`Gsc+S|LfqLpjB;am3ZQn{=(vB7$Re4fJt85~2N+T`oOR-K>WiXy*o9?}C}+;|DngFn>FEJIK$za327lR$wsd6gVl+ zZ^10cyC3}j)aJrYN5D91RSl^C+K6eCp(0S`E#TE`l`CM zQM*3DNDe9s@Q<--;g=yHfZ8f$;@b$bNF7^ZoyDhUbs5#}mxpIk5*5iQmbrFQmj$|0 zTF%iW(uxsN=u{Dv*TPmT0+;8b)ZVc1H541C6`uaxnmD9IeTq1ou>`f0{|f`35}BAu zBk7#fFXtxyMiSs}bIK@5=0}CJzdp5PGc}KRF8_HH9uclb+E$u4NI#IM@D*_FI>3+{ z&srCogd&D}ArvS^6k4&GM63dvqXVM4-)X(tk>Wpx)RqqM-TKM*4fLP(tXTR)S|-b@l|6`m+YG`Vs?hsQDj)K;z(bvg-cu8B2_X z;t8s6sS^eXXn5#4e6ph|qdQK0+SXY4`)vGf;VkQ)igZ@%%U-napU*iC6uE9=4f+#! z4UaJL@$C@7vy(ULyO`c8kV^vvf>VRbsDnvC)JA>B$K5Xork zS7e^%9;L_s>;m#2o~fM?N#>AQOyblFK_zIjDskGMngHTZ{`6XTueqau5iOZ10Uh3v zwBzf4VxHF0c0NO*A6~rwy^3Zf5l)KWsO^8(rHatOV_NyrOv)#5sGxBktrCqgICYtr z&SG-?4Wd!4YouYYGCsd6#)^c-D4K*EToOyuF2r$?%)vreotX>n=fCf;{7t)PDA-m1 zCW~Bfb|ibszlb6ouQ#n&(5Mq6V$IazC^rFf$sdc_{}&K|_5}N$)87SUpvFTFlAzNO z8ga+xI=gWyV)`kj-RU(XT2ztA^sM3rhim)@xYdgQ(`Bkg>v(jkl1}}RQ!#J3tuP&PlC|0{r9mlhdZb;dyMvOAhl>#cQ>vj9#)K@ly6r={@(p$= zPIp;X0}_b47m{#y2}O#3ojsF;CFq(^Vs3V}$;F8OU^h+fGbs}yfQ#o5x52>ZZ@X$6 zf3_v5d=}qwW+5kXP8bEQ!Rl_H_}f~p-AF7yR3?G(t_7HrqL;GLBf~fO5MFg=H&mOG zfws1~D^0~K;a&{lJ|eD}VrdOz^vhA}SRnM0@tneXpS18EIUiFLaOz=hOjRc)hzpG) z#-;Ww{PwPzI;fiBR9@-Xl)D29#+?K^d7qw}VlU8Bh}pb|LlJ0A_=v{S2xQCLZ8H#e zFPm##vuz6>n2&lye<%*RO+QBTg3FW!T|AJ-VN)IE(@$;0#si^yuu~MLsR@Z{$&EBzp}a4GQRWz-1EuP&gsmO2FOm3)ZYU-&SPNmhaHM6@?bafSf&~ z1q@-yS9ct?HpP_4GyoG2;n(%GjL{3$3=|11xtMy{u3S9q6 zahaPD&j1l6EONQ7VlLoi_g)<$+&P2T)?&VDwQHbUKJDl1>Wa1JzA?D4#{*Co`r3t9 z&^{yx45*F4IfSXLA*_X zg{51&E-IA%V1Zm>J{R!u5OP*yc?&@k#LwsS(WC;um$o}}KRlh*9Ocw#-NA}AdYXxr zZoGGoTnT9*(ZPH&4W|mlvTfGYVQW3$Is_6Kp4UY4TvS5$kIOKYoZL=mP5w9C&(4+j zY}d(ieHdl!YT|L)$2h>eUi3m-7-~_{Bz~j69@l$QUvFHo$s20_*Q2Dt$&}h(%aG*t zW#g=a=tJ)*L!<#5gorK9Ccj^=*^GK+L!JKO{{e%5snYwrNBJ?J0ugM}b z7r!skG0dTD43tPe@1Z`TnBHW-XamH4R86X+H@7t#MMS@bu#pslm8_G^CH=8zs<=i%>T{@Zuv4Azfq+_aFz|MY7Bwh!cdd# zyB@fr!#=>6nSC-^jPQE3^i-3of{6-8w6Y2Lef8$9exFG)T36Kk3QD=Y2{qMD{^%j| zAsKj99YYjXVqKtRyp_VT?a&8&hL3Ui~-@-`*7H!Z*R)Y915senOl_23M7;{iXn(UQUiSw`!ow zRLj_kuXo;Ze>;ME)$=tB$%z(722jWoj%V+bqz1KS7JXN^xsxEw!3r&*Imt7AlS0k2 zg2XvnFlU43|0xTpkKnrtA6Ty2mk5j}>3C%n=6`fmwod4PQQf9M(HIuZ2~1zelUGen z6W$d)yMsu8TlyT1yZw4F+eVg8>Pmg^#S>{g@n}Embn8i>vD_Rf)X|KI=7XHwT_C_K zmuIrykn2OT*2`9#47ScF5w4xKFQ4_BVjJ|52;s(hUNn_n^1Tf2o!ieV7xhPAe@~sm zy=1gVcH&T1qq+jY)`JFc213vnVaBa!PSY+Rf7C}r*fUpQnlvlv@U=Z_nq6vxbWd(- zuX%1W!U^~tQ}Mmuoa>{!w)mOoQB(IR!Ger?(gF@A~ zL9(%&bZ|Wyk4?bu`Tk7i>aIwYfu#5N+05<}jNq=vE9_>aHh44UezN8r42axxM9eD1 zW+{&R3c;+{cmC36cgrsW8@3l2l_J=scaAf5LWo}vX)XH4B>r)AH!{G8TxpzdW4s(r}6g9GyxnStlXBX@{biwC7 zqhx!Sj$}Kz-GjH(kvxV670?bgO7zc(2{}@kp#fBMRke8U#rWxuKaO2^{w2?-f%Ll^ zGLq81q)WtIbX(TC2=MmPL((bW;QlZ+UtAmy*6GscH_PJd$8SZ*b<-IwoUppF3e(#- zCPb)8qa563N6etUw7G}%kjR@Fw2x)AtoKJe~|rA^+8{tfL_kh&@6*2_eA_JsiA zy_w>gZpQZ6xc|4RGaXL$okKlqwRZ{ym<-bJgKK`3&hk2S(Q{- z8XDtK%Au4Tnupz@`XeLu@xkobB!e@sJtPGSzXf+|&?wmFaq985A3B{yhc?zSvb7s3 z5-aR8!O=At>>5>R)FK~mkhu1xTmc?*-vox;4C)lk?MD$=B`!=}cZ(UQM@+NP82x+=AG@ z?d?;cmSAnAek?XP@FVWKtaPs;72dfX#@RlnG>A^ePNK1PZ193+#K6Ys_kn%~y?J7t zP?fF+0+5+=O=lFPv#c$54E*ubV7yL1V|`1xZgQBFZo8{+{R9t7b!{Q?4&3w}>MBFb z3czKoP3gF=@&|{O25&Ii6kD|gOAKr)zUwqP;Mz$Bra_EQfF{*g#yq8Twh}c*VElV4 z`m2o369m0KU6HSG`!kfTpZBUCH@riuBEdm)sH|X~GrWPHF90t!` z-hy5wh&^K7VkeA4=_WZ8A9x3;eRing>X!p5{DkfslRD%ET3~<%m2hRbm1z|3#b$4D z1?e5H3nFr&ewa(=F^X@e`;{ciDq2Hm98b=f5ru4==Z?Mxf_ZM1S#8JVio#~$H8A;EqcS!Q-tzRcb@c6s*uvgCyWaGjuN%ZFPEq?p z8VR%31p(ZY8_nomM;`N8u=_N+JvP;kkM^3;y>`poC>~2muTSSrZE+S~WjG5j4f<27L5dq4Pmo}J+m1aka$N$H|AbOT9)iRXBnz$T-uZ|w95w=b40M@D

)u27yoXM zXIj}LnDSp(G=SgR8$_<69cf>?d$%ruMtg%$(fuHmGvio5V7_3pl_|!pw&D{ya7p;yOP?p^Q+ti? zWkfxSt-B({ zy@CTAB~#Lim_u#KY0Wy*zp$xg1^wn_`@Ty)fnO!LU8bwHbiYym4e<4{2N#X1T^^C` zP28`~z9`oCc)?SeF~)Q%C>A>Q24aN(^f6@WsGrnHpF~Qnh@|v0?x^9)mG%oT*cGrd zKBsH~Aq4RaAuO*-lj5+^JCx=>2+M!9e=cw6dHqc$oNjGo>QuG;8Ied;@HDCkQKKG?;Wmd!=fldYfrLUj()_GHhdT4%v8FQd}ZQw+y64@23 zsj)EVx`lA7Ovv^fRAjM_PV+3(T}TA5pauuq$`d9mb|>^SwsEW^(wi5Uhl>#=wc6;B}N;3m~Ws72Smn#PQG1n>Q)CvRhY$LkgBNVHhOb37a^sU2K+0c``8TBY+Vs0*X~ zSkbfbC`ZW3HkQE7v6OEESN#wOk_7AN1P(KFzEP#FjczTZ9nIu|6qpXqXj-HM3lAYd z)*F+^YzTiUn1suK__nubu79sl5H+q`BtfGYIN)uJd(>wv>^dF-G0=Xpd(30LcRO1T z<;Fvq#KOGX9rc)yf>4ST(rLrRoM4vj`pG;yY~Xb|65I^IawPBy!M>RBfjXj>Ggw|3 zUdN;0#KeIIUo=-P>pJ)F2_j+vFi-N0F{{qxe%wvPZ^}S8EOcN$&lKIR-yhno7fF%7n>;Uh42Co(3{@W;FKl`qYtGsZ;VVU;o45 zY?Jt|m3o*a39SjHH3H!Ke5PR1d?hJ6|D!Y&$uOJcKdeYQF1}R%!qC0ZKAeapVF( ze|vX_)AoCFy=vo+{7Wh8a3xP2XOy+7i)gj}oo$***wkkYy8R&4hXm-CJ7wN+bSn^7 z$Ov8Po)%y^`M^vCEc#Xiw&oRvtK2K5!?zF$xcZYkJJ=jjD%zjSU4w17Qlw=<#|~Pv zbF$#Ax6pW7K<{>fvs#f?y8DkVBDd|DwrTK{lEN4KaPAa8ni0vv!&C@0r5R4oKie3g zFS~u!DcFrRHCp1^k>ETDO4SdUeb#kegL=+-t4n8z%O9UAwk-z8jB8i*i7b5z zPRhK&jBz`Xsz=5$AD&WnI<#p2+CM-P&4uu9Fkfo%)B**}jMut##XLi$!t<_XVTC)< z$$olwYryW_BBRw&XN<_s<7bmiP_1URI~^p#xt3ZUlamL9<`&#=We4`;x7-x>KKbH^ zamVqzmqFVPAz{)LR@D(Cy&chS#~VB7Pa-I(aDhwOV}-byF_DVD!T7JadH^7wWw7vJ zgZ8~Eb!O!0f}iR*)Z-i05jv~RHp;!Vh{QIQisA<|!l%LgMOD^cXK0eRFt)c=*m*ln zt%Cf8-OOG%cUj`91T@jlH1j-8PlifZL0(x0n=ArO7ztjrM+D3}nPrCDqsBoqvbtR? z;0$aBc)G?gTBOeha)B&SLVvOtF@S8NUUL@0F6!4q8UBV>ECQ3H(}-^J&I z9?(nUO2_Z4J9JoP=6@9cewD2aKrLP)5wLj&97Gaf#d7S5a zFk6ytJBqb4%*+>lWV*%)!s)y$DY3N-p7n0h0VPvaE{dEV2g7KSH$hi~jg?vA;@mV9 zeh+%@T_-{Z0Dg08|LskX5yb`n1Ey@|-edNXl>-BJ{T4Wy<8gfFxF>D*2K_UUP+@!@ zs*IJ^w&(bnNfzs=)FGAdDQ_xG&ux_lfbo!)iRT{BHP@hfnj@Ir;KTw+kJk&eNn+7M zc)f7dn-qDyVALafOLv`iKy~}@=t}u~{52`-SmK@yI7HZ8%>jiQYYl<9kKV&d;y&T0 z^G}7hAoSFG>Y*Q~lkPOwcUvm;JJ36Fn@#)JFk#-DI!E(X5`c#o^hAc}={Vyx)`u>3oH)XOh)2E-m>!V}Z8>~QQgH6xw&p(G- zr&zL#-4wYG0)Q2)uF7=_G*;CO@?;aajT=wmZ{R+X;`me->(i) zxV()EB8AGfxw!NLJ!@T6z*74O_5B?Mp?S+)bS>l8*U zMMs&W#YBA~n&D|e3)cnuS-}&{!KA?Oon^W%T(dB z${A`6SkpYoS0*iXo)9Z!t=Mo^y#G9i@o`$H;R>PcyQDil1kJt~NRGv0I>Q84P87nX zp^6MbeZQV%ozWb^{ZNAVC;;eL&$!0Drse?_?X#Fhy7}XGN$!VWyiLrvz+@@X&T{fl ziqMa5`<0(w*}VSZM@V4Qm@aePf+BNw`u_F#^>C1qQOW629k-a!g>8h;*FcO-d}kr^ zaXDFR-kUrGTP?q!8T6Q7={a=BVSSq}p?Le8UZ7d}OOkpTp|O9h?KIf{Du=gG00xc> zRP@=rh$-`v9ebcGPoZjd<`FrOqJT#%X9^)w3BDpu`LmLUHVIo=w61!I)5ci#h6YCD zGl6ud{BnFtMa_2&Mpg-?6>3&-Y^Sn*qcz1A@JQ35Ssc@ce1kInOkFSpNA}B#qB4!iY)I)8HLcDt=k5`h6HfKfEo-TxVi9Zd`tWbTV%M#R}&j1lu z@x^OQ{1>@t&Qk3R>PEZ9OWutb2o+~j2$>x~S(HhydoIIy0jKFmx3r3%8bcsE5F(5k z-B;lMR>uDxf1ylOpD{E2t@WypweBKpCuQY>tC$D2I~t@F-EpIe9NvdxER1>of~5-r zFmenM1<`Q=F=BKLcD(y2%xS1|G+JpnxQ5d~DCSLkFWN8s`8~Ky*9@Hw??I-O$i0wv zl_6R2V8(THnw~_KKTT09wG(yh$$5R1USVh}=tU9UlkXbXF!MmkEnmAx49fltcS0J> zP582$m(Lix*InO3@FAG?G~(K1Q6Vej6Z6v~L`sKE%O{eyBcW1T%~Rz{tiqfYC@Lcs z$uefWRk@Q;-T-F3TT;W~DR`$*&*iv7RlFI|?ICY@>X|5DgG*_CaS3N{zmcUW!m*$p z%u=C%lw)zQQ&(kf^oS)5A_^j%qHSz`>gU)FHZ*ZWn5nTgD7FOxSZr`f(6epZH=wRSv(d1!S}+Q;kwOnA7{P4f|Q=0?zd zT66&MinSdBd73WfVpb4sJZiIBJ%=)39q&QOXvUwB3 zM`v_puApzt1-9s9QQl~%)k-J>9no=&A?01qC_4&2@^(pWxiWGb$t}+hXRe=~%1t{G z(oRxqv8^1|Jn^ruF0Ay0CCBRDDc3mGjP*;FyTE{;ME`2w*_(~(Uei>mgRN1eQ7$>0 zY#;Umw^rL6CPWEOQUFfCYz>RIjJV=ZijbXL9v*uk8xE{J2K2Y@ndw2PeZ`YnOn$h& zqoDku>SRvAmd_{f?T9WfmrH#w6y%50xMe5COQ{IN(V>WHI2Trs-Ety#Gi(jM=;Zym zzWC_^%wo9q-wR!xuJ~<4Zc5@Y7#ScF{=&3Oc~i~jk~K-ElnkSSd=Ji6zxH-N-XQ{B zeO{9e*3EHrCey%GTx<|3f%fU4Rs0%xXoV#9nwAi4BMh410kC0SVrpLe9+|^4a~~j6 z?i^sp8$3THdUgmL8A+$7$3`|8GKN-247q5@nvh8EkazfydfnL`n(gK=&pgfuM2wWV zaYD_VNS(;$>V-v=we4We6!{`-c(zwDoYIw(&xqV7UKRO|yLpAV$~6PgL!*6Q|IO01 z{><`SEg`MT34Tb0e7Lljmr=aWjHj4?2wa>A16fxY9{2?##0mj!8qqpqs7MY933eD} z1yq{`p|qUs_ugEBqB{M&+h$zZW?ZPCABe#YumE|`Fe9oi*l*~oMtok_K z0DWA5Eq)+nru)}8OIi;xnici5=Hv-e-EI(LqD;; zS@q&ATyp^D0S2&QA`_IRGM4w;n^=#Q@$29t&^Tz--~Wpm-?n?@|HF0M)@3>q&=C>~ z_w{%F#lfB!dWC=M5f5;1iB(wXuSU>qhs4eV`IqbD=NMIYZ*xKMWsWAcqYZ#FnY>OC zBDPhLxR}Ll6{;lq0%uD6+;J(>VbEWJ8_SwITK~egIjq#Hl#N=^u9m6#9F+9t0uK#r z^Chk5T<7x}U*eJKK`rVzz69VmGb+IvaU9UhmDee;poL~mfbCf}LXCcOGRt_*-p|^V zw29_6ML+iBc;#w+oaZv>Tj-w|-0YC9*sJu*X;g^}@Dn*;2dD;PnYhD#oEFJJ!xaQk zC(jNNvX~Oe)r_DTKhID1Jyowijyp7X;KtT@9X6=`>Z_XAE;tDZ+zfRM*Qci_J9D*gAs+vKEu3kPQubei%8D6#Ykj!M&_86QEhaKm^el}-*g0@-L zE(XvkI~UJ9t!^h0pO21+GHV<$IT9LkLsl^vY(h64?h0oPS4#SQ`hFgQzmc7jQDz3N zLgi7$w};}uz<_p`4Vz$>3qmM0u#ojCMG1)y7?pChJNm@WHBV=*z|c9$*gK;(+kG8e zIZC%Y39g)dIicndgpRK~3QAhocxy!c`4%&vu{)oi;^42NdXs|NBaO{SS1yako2~dQ8vXg;V*#8g^ zC9IUuuP|fxynPN;_T>6Z;&KffDvpcK5R%j)6j4%+k5G4I?^Pa!om&I$nhrm zQmsb>DSBGFO6S~b0(!WuX*X=y3vVI&Yu8)oKa%O019(Q3bPIfHJh~;u0qx);*D*+` z6uWRR+KB$3?Vw+WFLSa}W7h;3c555~xVKH(PH-vonjnnTi_6&*O>bcDU7A@6VT`&j z3Ipr3OQQbCKF|!IWe${m&4K1L-BXA#5ehEGtIy4aCs-$*Z;qpEu$e0Z)@RStw(*o>Fz|7`ag&-qp_cmuwRo2k-SfDU(VvYr8r z7)t#T9xrHi$j>50Um3-Sjc`Y+P}~eTV@X0Z69C!3oW(7}e*W`Wac-RijAE{gbd9!# zq*nv46$CD%A~I3SWI_LUN)qQN>je`{xEewD>QFTUrU>Ucb7R>hacysFB1J>6HZ~-) z$&G-5UM5pKs2wk)`U)HDvwQ+>qFXg`i@y~Ty7ji*S8#&BO?_9sFt0A7t&pFolmetr zq@{gQ8d&;em$ECm@J)AGQ+>V+tX*^{NmLMDU3*u(sDaMYa37?bPQVAH(0LP2+!22O z)~~DA%@l{_*+qZv$w1q-?bvMJpRsM7_{xFdH$M|JFROWNVn!%xvnvZf;GOGjF)ZK# z(ShDBiPu5Mi?4#Ir(-T z4RaZa;7u{qj6+I_cVDF!g|^tMlh7hU5p(U^A4N7b5OWjIr;Ar`_~s2Jh3vWK$UP-F z!@&jrh>>c)0dhJ#$X7t%I9E+ zL1xkK=$TLN1Euai2cy1;nnAn2n-FMUB1pL}O3TXw<}pC8E)&1l#*xU{~idckcQ< z@0ZJ}U--?LGiS~@b7t<|mjwrM$G~Q zH^NsA`$AS>UFPAgPs@C{EPB3Iqd_@Cn$|mDEPbfb)t{R9T)Lfcw^Ea4wNB2;bAFzG zqRAIen;wc<@vzky?U*ZBT=%?IcZM_{+n~qk>V~>ay^}xxzR##JFSj+{lvn57KWPhA zX3JcBU9UZ=`s(Cnhvu&ujoiK}tXmb+xNW63)M@0X{Mxm8$nCNJW}BPmU%a&MMnIjR zY09b*{W4~TUTDy&O_0jDMf1G+PWzUPuet7VX6J{7E>9Yk9zXHNhrg}z%-0X@cHzH< zcA;5GT>G!rTY^$|q&CZ}b>Wfz_-`X?9sc|Gwy~>D$L8i&d~@pL4r5wCjoZ$VzGMG> zmfbQWwC$=6i$``F^lOcc6G0bj@Qeb&uv!MYeh!qHJ4xC2@UXa>Yv|l zg~$1oZz9g*W-j#XsTnd=5n-78<=dP8Hp|}>yxzBK(0KP>MsNA1rbFELd56o*KJ$3v z8n02hU5|Ud>{uatPRIR~dav5_Ywqdz8d=BZuMMhw;oi!_-0%H!pRMRLCfs#!uN}v3 z552lV(QE&*zjuCJuxZBPHwVjQekFfb`rY%ayJhCiO?&t1V8i%w*~6Z$Jl=c2$umQ0 zeOqQrq~||4OLVx=kw+?hn!%A9Fy z9K#+rI5n!JOIyv2u8R^)1$%GLzjWe=!;9T+2Tr`{7@gv{xQvtk+Erf7p1Qi_4L*=o z>EN{bOB&70>(M(fCFkt(K`G9D6K8Mu`>(KD0n6U~`!2jssnWH|)WR9p3b@_4z74j2 z^AvxYf$tToi%;uiG4p@YX_ZEOvJd~D(1%|uCWPGEG&8O~{&yb0pQ5#17IQOT2}q(+ z6Rp+z$aKki1D-j;mk`O`e^rK%`|wH1@uB!~uw~_oo?Cj)ohHJS8;yX=R2r30Cc~F+ z2#LdLG)#=dlTdG~ah$6ahfj;S3I3HPX%SJ8prg>PZFoIB6S8%{`%d_y?$*m<{-F|+ zrcJ>6(kZ|`w@M8OL)Y4)(KzSM~!o>mj1ilqRL<%JjyRpz+!?TQXIzn9PjHXsg+ zF=-V1w?hf_mt`K>Oar5yw7Z~DtSWeUTGz`ao z%`BWonPim3Dm4_O(4)u6sGVEGrMSucw@cRCAMY=tnL7%c^x<{dtJH=by4s#^5r zTJN!)W;3t~tp;Et{}Q&6%Jd)j%i5KAQer$zi2uSa>t!)_NM|sKcjpNQXl(Sx)e~Vs zRg0g#c+YfRz+mEq_98;^0LWN)kt6>5LIzNm>}^ceDWiq*z3Cf&yAgOvm~NznH(tcR z8I(p5`DX1p95kgm$JNH`21zBREMWi>ojv*tm_K~N&u=Wgr7nFgQY z$617Z?qqgVt;Cp21dg=w8XH(Rh97I~)+RF)W6~3Mv!cN%{An>i+sHyP?a_H(m1=b8 zq)jX+(;(aB`8b6@w(ht+2uZ7Cv!M1_q&q>(U=qbl($jcmBVEEACQsZZr+ynXjIPL$ zn^}Ek8YOr7^Vez^r5mh>H1r1|&@l$B#-NB(Cdhrd_YWT$)~}b)GFJs9MV5uLoC#-1 z2JWRl&;|W~$uQ47vVG}Q1NP~hmr;UVV72xp{oAGA}N8>7`H zP{Xc6Rde7|sK6i0wuD73KPHV;D`Vw~WEpet9v>bU(!U(X`65%`m14at=0DCzx4?T+ zS^ycoZ_R+672)iGB&nufrKu*PN=>7BXJu?%i=1I#XO8o-3W`sQ*>FRe%2dO%`R$7to8YXoB~k*2`jk z=lC&A@D0m~e1XiUG#F(DlgdcT$kbi#eYG7X)zeKFtNf}+HmFdmD1Eq{-hRC^CT(ad zj>CVp%VKT`?a^|^57fkrEn9D{cBzg``>RGA*V4x2Ep@(!Bw44|8np^7bqY943N!h^ z*Zzws3s+;iEatzwCBa2%HuyyA8|x?oDcKygrfXxJ>iuU+VO@NxXMRDQFdMe?iQ z4=TaW4Ei{J0F}S~ZhlQC#K4PKVUhYd)$!x_fmHsLh#UG<(0>nP`F#ledtp{fn7`GQ z6158XXcOM!tWuJOqTWHV$$_phLZrAM-G>lY|U%m@lo$>RE z8H>@~R}LJ3UPpKNG_EhvZKr{spk0$jiJ5GY8%q{L zUU#X|X(ZKw3>)+bS{8R8ud-^5YwpT%4HpPgGS?xJ4Kp)*^R{yvzCxFLu}Vb)whJj) z5}k~o7B5ygiBxqO?uKEx2O?QK2p`X&F;=EazRb%?hg92flhJG+x|-)2#S{d(RuzN^ zdabV*4akx6i!JyzEZrD44#>C25Zwuxul|RDRA>_tw6qeeZ0)**2P$*i-WI}2MS3cO z$4vD@7p=W^4l*jRkZ3~4xBxy{RIjmmc>ter6ZCSh2hT2t&lgm?r)B%H$qoSLBN4v!(V(U1(G^@Q|TL|7wof_A`tbDf7GK z_JDQC#yA)Q4Lxi|;P8Rab1ZJ}B83J!q3grO-wzsq7`E@>*?yn#;o6NC#2# zdj?yjiAL&L__89QhW(RU{BqlctV zy;3O~ZBnVDsS@ohuDutzKfz0Mf$veb9Ga&Fs_a5W3uwv zB+(9hu}#b58o4?d`^l6Rw(dV9{|5`SMAUXk{XCv%M+<7XTF=7o9iocZ1l4EP!Zt9W ze!m&^;#IsWk}Hg=M5WC3k}IKK@~Bv*#%*Y-HELb3OWS2Jw@`m7NM3t0}a~ z@|p)u+AWZGBf=r+ugC3?1{>v?Xt`Rep^;ZCbahKdjCqQ%?P}96KWv7hEHWY&#bKy= z?L6@W6U~S-37!P4`&kK^4BJcCZ=xg{~}atQ6QNkEL0{CS6FYfvr~b0|K``AtC}@z`_|dJ>bQqXA zT71p*-H7?RV&DO~?UoFM->NP|5M5(p*(Sg2^Z!O$`|? zhrN33g+uGzK$tk6{Yjdv8!e}uT^(@#&FbZFW_Ozk!`nGj0;!q9dyA9l6l%GFHsvmj zef;)_BhH*++{j?O^{O4%XuOSu7BvI&V|Ru?S|vOW>3a9&+JRN$Ff=7n%-s62jvrjk z$S|Q|L!a6~O_0-$EnjS$ku?F~GO3}^z7CWtE3H_5xucJ!2)m_i>A|It<90#vA@0E)XeCo_&d%Iy2O_eHep=`-(|Zj z=HI(XFvVhe`x9Ba7h=ku!<2Qm0eLY4L)l2Vj&>%iY3t9k+F+W#Gzf>hN(7t7@6qFU zCN=$hRBA)T%@`dGmiVM=O^sz!nR`EN7DP@lV8kn&5-v)=RI{--HnHc@o7zz+&+sPOVA{^UP!Qg4d{QluKyX`q{bEX>HMrclW4TiHZ|)}&X^ zf>gxY|Ag~T`Zxkh&h$q+*+_nF$`or5}CMr#!& z{^e#AAEKC^o6`we?NY)s4ccP|S!99xY{AdXFJo>#L#dNodtBZpLM?Q1GCXyn)EEj~ zv&d2Zux>uJ`Xg4KX;(_1;8&;ZMVH(~DhNF9y&B5x3{}8tZI{J7udV}C!9|Z*c^i{T z}eRP(x|_Xt_bj=GRW| zF?lzNsMD|%Bdu&@BWXTU^!nL%3w)rNRuG*$WMFZXMx&Z&l+ewPu>JcxKz0?<7_tEm zcj*QaOlqTwt(=agfF~(1n1khoks`aPbOWMJ`0~2>Ki`Mk`3P;K++#hZvDj$FDQTwW z!!q|&{uM@pYKp+v*S8XHpmC9Up09x8N`v_njp9zy50;-`ff9Dq6s}X21W7~1>n`0#__xQw9_|Q) z^~cy*FFYdI*={b|sH0+Dmm6CVk4%D3mP(>f2p0OUpT491h<7D>~30 zEs#5;b$Wj!o!yObuaB>pfzn1B9FQQp(W9^Z4HY^8%J?2rX6nCy@~7Z-w+;e6E#^i; zN&5R-mNns!TC8^>V|+AfQ^>o8Kb7;H0Z z^|W&YX-?XQw=hh1G25`kY`ZLGO&rPKpUzDsiZmO_64eK z(=aecQST!yrs^6xl0PNPpY^B6#}A8jbV&IwuA{>WSMw|*OkY^aGY3JV0P=YhjeFrw zi@Dx<7E-*%>+QS$CeEpGeL*FOt+QD~l}4d9;gDU)#Yg3FlbkZ3!?DN(n-DU(Z($J= zw9zJNu~gXJY1&%f*-5SiquvVI`i>7Za72&N@P_gs2yGk>p|)bKXog${V}_ zWe#K|<#gIDLVKS(GRbaRk`MI7-o%vZCe~goKRFfDipbH){UdDeq+2&Ew-iehzs_ zk5u8j2$^5Xv!QM$?zj09YpqKW!nywAMGG5P0{S>E$+&mUaoqU#S3 zzuk-3{D;Lng3ZJ^0@@MH$>&bo_C)}ig9TU?ec{apZCRpKf-{MonBG7 z9DSZADhzBzUenI%$gc;I;ownbwOtmoS9Jli@XLEBs(jvNXH7;Tru>{LgrvwUHbB9~WKh3PW@IDwl?7I)VtQ3hoK z7FH~!{TmRpb`&_1aFfP0sR5N`N0A_D#;p8!@c4mn#cjPTW{-w+CaW$mE%AIs9T1Ok zlZrb{r-`RE(d&g>W2P;@E3 zVD_;8Mguknw>NI<1;=3rkS%Po+l4J(09aDkxvorM#TM+ma8@5hE@-{o1VCMM5^JW- z(kxyy#fjrKn1p&9LnHCa2Om6>jjaNl{^D25!kx@2KSri{Lbgx@SZwIoE{oZxEk!i( zY0)cj#?*@l1m&^nCqeT9D4=4YD30lA#+I$6AWBxpG{$%*;JE6b!qC#hS2S-L4L z_zAHFH>vfUP9%tTku0qrp7+);6_jhZ$tgdnGlj&A2di6Now^@EvopGfJjr%h%-ylI z^kMf3r4rdU;(yI*QRIXV3??a(3%>{VgD>?3E)qAXCLQ22W+y$K_UCj^7ddt(d(u#a`+y=^f9=-AaX45P zz#>gEER~b}g(VC{{(QQ9v~|6Ce|dvx5W-oWVkR%@Rmi z!6l71f^gh!Jh}L?n5zz^!0Lw||qWLBbI&ge%>9Uox5EdDrGY%AIJ6n6LrGb239}Kb?-0 zID71z6YI4Y{f-8Z)MVytD-d~*Ug_oQ?eE>%n=gfW@n1RV^Lfa?;2yyPgN3=m*+0Gh z;`az}{7v8_Qs%RXQK2CreTBGlccXe6V1*@5WmU;w4P_ literal 0 HcmV?d00001 diff --git a/tests/repositories/fixtures/pypi.org/dists/pytest-3.5.0-py2.py3-none-any.whl b/tests/repositories/fixtures/pypi.org/dists/pytest-3.5.0-py2.py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..911f902125ddc9b704e50896c280db279b0948b8 GIT binary patch literal 3691 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 KcDjr|pZ){x-ev0m literal 0 HcmV?d00001 diff --git a/tests/repositories/fixtures/pypi.org/json/cleo/1.0.0a5.json b/tests/repositories/fixtures/pypi.org/json/cleo/1.0.0a5.json new file mode 100644 index 00000000000..570ed2f108b --- /dev/null +++ b/tests/repositories/fixtures/pypi.org/json/cleo/1.0.0a5.json @@ -0,0 +1,90 @@ +{ + "info": { + "author": "Sébastien Eustace", + "author_email": "sebastien@eustace.io", + "bugtrack_url": null, + "classifiers": [ + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9" + ], + "description": "", + "description_content_type": "text/markdown", + "docs_url": null, + "download_url": "", + "downloads": { + "last_day": -1, + "last_month": -1, + "last_week": -1 + }, + "home_page": "https://github.com/python-poetry/cleo", + "keywords": "cli,commands", + "license": "MIT", + "maintainer": "", + "maintainer_email": "", + "name": "cleo", + "package_url": "https://pypi.org/project/cleo/", + "platform": null, + "project_url": "https://pypi.org/project/cleo/", + "project_urls": { + "Homepage": "https://github.com/python-poetry/cleo" + }, + "release_url": "https://pypi.org/project/cleo/1.0.0a5/", + "requires_dist": [ + "crashtest (>=0.3.1,<0.4.0)", + "pylev (>=1.3.0,<2.0.0)" + ], + "requires_python": ">=3.7,<4.0", + "summary": "Cleo allows you to create beautiful and testable command-line interfaces.", + "version": "1.0.0a5", + "yanked": false, + "yanked_reason": null + }, + "last_serial": 14027784, + "urls": [ + { + "comment_text": "", + "digests": { + "md5": "4c78006d13e68c0c0796b954b1df0a3f", + "sha256": "ff53056589300976e960f75afb792dfbfc9c78dcbb5a448e207a17b643826360" + }, + "downloads": -1, + "filename": "cleo-1.0.0a5-py3-none-any.whl", + "has_sig": false, + "md5_digest": "4c78006d13e68c0c0796b954b1df0a3f", + "packagetype": "bdist_wheel", + "python_version": "py3", + "requires_python": ">=3.7,<4.0", + "size": 78701, + "upload_time": "2022-06-03T20:16:19", + "upload_time_iso_8601": "2022-06-03T20:16:19.386916Z", + "url": "https://files.pythonhosted.org/packages/45/0c/3825603bf62f360829b1eea29a43dadce30829067e288170b3bf738aafd0/cleo-1.0.0a5-py3-none-any.whl", + "yanked": false, + "yanked_reason": null + }, + { + "comment_text": "", + "digests": { + "md5": "90e60b2ad117d3534f92a4ce37f9f462", + "sha256": "097c9d0e0332fd53cc89fc11eb0a6ba0309e6a3933c08f7b38558555486925d3" + }, + "downloads": -1, + "filename": "cleo-1.0.0a5.tar.gz", + "has_sig": false, + "md5_digest": "90e60b2ad117d3534f92a4ce37f9f462", + "packagetype": "sdist", + "python_version": "source", + "requires_python": ">=3.7,<4.0", + "size": 61431, + "upload_time": "2022-06-03T20:16:21", + "upload_time_iso_8601": "2022-06-03T20:16:21.133890Z", + "url": "https://files.pythonhosted.org/packages/2f/16/1c1902b225756745f9860451a44a2e2a3c26ee91c72295e83c63df605ed1/cleo-1.0.0a5.tar.gz", + "yanked": false, + "yanked_reason": null + } + ], + "vulnerabilities": [] +} diff --git a/tests/repositories/fixtures/pypi.org/json/importlib-metadata.json b/tests/repositories/fixtures/pypi.org/json/importlib-metadata.json new file mode 100644 index 00000000000..2796a50aadb --- /dev/null +++ b/tests/repositories/fixtures/pypi.org/json/importlib-metadata.json @@ -0,0 +1,27 @@ +{ + "name": "importlib-metadata", + "files": [ + { + "hashes": { + "md5": "8ae1f31228e29443c08e07501a99d1b8", + "sha256": "dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070" + }, + "filename": "importlib_metadata-1.7.0-py2.py3-none-any.whl", + "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7", + "url": "https://files.pythonhosted.org/packages/8e/58/cdea07eb51fc2b906db0968a94700866fc46249bdc75cac23f9d13168929/importlib_metadata-1.7.0-py2.py3-none-any.whl" + }, + { + "hashes": { + "md5": "4505ea85600cca1e693a4f8f5dd27ba8", + "sha256": "90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83" + }, + "filename": "importlib_metadata-1.7.0.tar.gz", + "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7", + "url": "https://files.pythonhosted.org/packages/e2/ae/0b037584024c1557e537d25482c306cf6327b5a09b6c4b893579292c1c38/importlib_metadata-1.7.0.tar.gz" + } + ], + "meta": { + "api-version": "1.0", + "_last-serial": 3879671 + } +} diff --git a/tests/repositories/fixtures/pypi.org/json/importlib-metadata/1.7.0.json b/tests/repositories/fixtures/pypi.org/json/importlib-metadata/1.7.0.json new file mode 100644 index 00000000000..595674750f8 --- /dev/null +++ b/tests/repositories/fixtures/pypi.org/json/importlib-metadata/1.7.0.json @@ -0,0 +1,140 @@ +{ + "info": { + "author": "Barry Warsaw", + "author_email": "barry@python.org", + "bugtrack_url": null, + "classifiers": [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", + "Topic :: Software Development :: Libraries" + ], + "description": "", + "description_content_type": "", + "docs_url": null, + "download_url": "", + "downloads": { + "last_day": -1, + "last_month": -1, + "last_week": -1 + }, + "home_page": "http://importlib-metadata.readthedocs.io/", + "keywords": "", + "license": "Apache Software License", + "maintainer": "", + "maintainer_email": "", + "name": "importlib-metadata", + "package_url": "https://pypi.org/project/importlib-metadata/", + "platform": "", + "project_url": "https://pypi.org/project/importlib-metadata/", + "project_urls": { + "Homepage": "http://importlib-metadata.readthedocs.io/" + }, + "release_url": "https://pypi.org/project/importlib-metadata/1.7.0/", + "requires_dist": [ + "zipp (>=0.5)", + "pathlib2 ; python_version < \"3\"", + "contextlib2 ; python_version < \"3\"", + "configparser (>=3.5) ; python_version < \"3\"", + "sphinx ; extra == 'docs'", + "rst.linker ; extra == 'docs'", + "packaging ; extra == 'testing'", + "pep517 ; extra == 'testing'", + "importlib-resources (>=1.3) ; (python_version < \"3.9\") and extra == 'testing'" + ], + "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7", + "summary": "Read metadata from Python packages", + "version": "1.7.0", + "yanked": false, + "yanked_reason": null + }, + "last_serial": 10821863, + "releases": { + "1.7.0": [ + { + "comment_text": "", + "digests": { + "md5": "8ae1f31228e29443c08e07501a99d1b8", + "sha256": "dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070" + }, + "downloads": -1, + "filename": "importlib_metadata-1.7.0-py2.py3-none-any.whl", + "has_sig": false, + "md5_digest": "8ae1f31228e29443c08e07501a99d1b8", + "packagetype": "bdist_wheel", + "python_version": "py2.py3", + "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7", + "size": 31809, + "upload_time": "2020-06-26T21:38:16", + "upload_time_iso_8601": "2020-06-26T21:38:16.079439Z", + "url": "https://files.pythonhosted.org/packages/8e/58/cdea07eb51fc2b906db0968a94700866fc46249bdc75cac23f9d13168929/importlib_metadata-1.7.0-py2.py3-none-any.whl", + "yanked": false, + "yanked_reason": null + }, + { + "comment_text": "", + "digests": { + "md5": "4505ea85600cca1e693a4f8f5dd27ba8", + "sha256": "90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83" + }, + "downloads": -1, + "filename": "importlib_metadata-1.7.0.tar.gz", + "has_sig": false, + "md5_digest": "4505ea85600cca1e693a4f8f5dd27ba8", + "packagetype": "sdist", + "python_version": "source", + "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7", + "size": 29233, + "upload_time": "2020-06-26T21:38:17", + "upload_time_iso_8601": "2020-06-26T21:38:17.338581Z", + "url": "https://files.pythonhosted.org/packages/e2/ae/0b037584024c1557e537d25482c306cf6327b5a09b6c4b893579292c1c38/importlib_metadata-1.7.0.tar.gz", + "yanked": false, + "yanked_reason": null + } + ] + }, + "urls": [ + { + "comment_text": "", + "digests": { + "md5": "8ae1f31228e29443c08e07501a99d1b8", + "sha256": "dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070" + }, + "downloads": -1, + "filename": "importlib_metadata-1.7.0-py2.py3-none-any.whl", + "has_sig": false, + "md5_digest": "8ae1f31228e29443c08e07501a99d1b8", + "packagetype": "bdist_wheel", + "python_version": "py2.py3", + "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7", + "size": 31809, + "upload_time": "2020-06-26T21:38:16", + "upload_time_iso_8601": "2020-06-26T21:38:16.079439Z", + "url": "https://files.pythonhosted.org/packages/8e/58/cdea07eb51fc2b906db0968a94700866fc46249bdc75cac23f9d13168929/importlib_metadata-1.7.0-py2.py3-none-any.whl", + "yanked": false, + "yanked_reason": null + }, + { + "comment_text": "", + "digests": { + "md5": "4505ea85600cca1e693a4f8f5dd27ba8", + "sha256": "90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83" + }, + "downloads": -1, + "filename": "importlib_metadata-1.7.0.tar.gz", + "has_sig": false, + "md5_digest": "4505ea85600cca1e693a4f8f5dd27ba8", + "packagetype": "sdist", + "python_version": "source", + "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7", + "size": 29233, + "upload_time": "2020-06-26T21:38:17", + "upload_time_iso_8601": "2020-06-26T21:38:17.338581Z", + "url": "https://files.pythonhosted.org/packages/e2/ae/0b037584024c1557e537d25482c306cf6327b5a09b6c4b893579292c1c38/importlib_metadata-1.7.0.tar.gz", + "yanked": false, + "yanked_reason": null + } + ] +} diff --git a/tests/repositories/fixtures/pypi.org/json/poetry-core.json b/tests/repositories/fixtures/pypi.org/json/poetry-core.json new file mode 100644 index 00000000000..38b248a3c1d --- /dev/null +++ b/tests/repositories/fixtures/pypi.org/json/poetry-core.json @@ -0,0 +1,27 @@ +{ + "files": [ + { + "filename": "poetry_core-1.5.0-py3-none-any.whl", + "hashes": { + "sha256": "e216b70f013c47b82a72540d34347632c5bfe59fd54f5fe5d51f6a68b19aaf84" + }, + "requires-python": ">=3.7,<4.0", + "url": "https://files.pythonhosted.org/packages/2d/99/6b0c5fe90e247b2b7b96a27cdf39ee59a02aab3c01d7243fc0c63cd7fb73/poetry_core-1.5.0-py3-none-any.whl", + "yanked": false + }, + { + "filename": "poetry_core-1.5.0.tar.gz", + "hashes": { + "sha256": "253521bb7104e1df81f64d7b49ea1825057c91fa156d7d0bd752fefdad6f8c7a" + }, + "requires-python": ">=3.7,<4.0", + "url": "https://files.pythonhosted.org/packages/57/bb/2435fef60bb01f6c0891d9482c7053b50e90639f0f74d7658e99bdd4da69/poetry_core-1.5.0.tar.gz", + "yanked": false + } + ], + "meta": { + "_last-serial": 16600250, + "api-version": "1.0" + }, + "name": "poetry-core" +} diff --git a/tests/repositories/fixtures/pypi.org/json/poetry-core/1.5.0.json b/tests/repositories/fixtures/pypi.org/json/poetry-core/1.5.0.json new file mode 100644 index 00000000000..9cfc450c625 --- /dev/null +++ b/tests/repositories/fixtures/pypi.org/json/poetry-core/1.5.0.json @@ -0,0 +1,96 @@ +{ + "info": { + "author": "Sébastien Eustace", + "author_email": "sebastien@eustace.io", + "bugtrack_url": null, + "classifiers": [ + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Libraries :: Python Modules" + ], + "description": "", + "description_content_type": "text/markdown", + "docs_url": null, + "download_url": "", + "downloads": { + "last_day": -1, + "last_month": -1, + "last_week": -1 + }, + "home_page": "https://github.com/python-poetry/poetry-core", + "keywords": "packaging,dependency,poetry", + "license": "MIT", + "maintainer": "", + "maintainer_email": "", + "name": "poetry-core", + "package_url": "https://pypi.org/project/poetry-core/", + "platform": null, + "project_url": "https://pypi.org/project/poetry-core/", + "project_urls": { + "Bug Tracker": "https://github.com/python-poetry/poetry/issues", + "Homepage": "https://github.com/python-poetry/poetry-core", + "Repository": "https://github.com/python-poetry/poetry-core" + }, + "release_url": "https://pypi.org/project/poetry-core/1.5.0/", + "requires_dist": [ + "importlib-metadata (>=1.7.0) ; python_version < \"3.8\"" + ], + "requires_python": ">=3.7,<4.0", + "summary": "Poetry PEP 517 Build Backend", + "version": "1.5.0", + "yanked": false, + "yanked_reason": null + }, + "last_serial": 16600250, + "urls": [ + { + "comment_text": "", + "digests": { + "blake2b_256": "2d996b0c5fe90e247b2b7b96a27cdf39ee59a02aab3c01d7243fc0c63cd7fb73", + "md5": "be7589b4902793e66d7d979bd8581591", + "sha256": "e216b70f013c47b82a72540d34347632c5bfe59fd54f5fe5d51f6a68b19aaf84" + }, + "downloads": -1, + "filename": "poetry_core-1.5.0-py3-none-any.whl", + "has_sig": false, + "md5_digest": "be7589b4902793e66d7d979bd8581591", + "packagetype": "bdist_wheel", + "python_version": "py3", + "requires_python": ">=3.7,<4.0", + "size": 464992, + "upload_time": "2023-01-28T10:52:52", + "upload_time_iso_8601": "2023-01-28T10:52:52.445537Z", + "url": "https://files.pythonhosted.org/packages/2d/99/6b0c5fe90e247b2b7b96a27cdf39ee59a02aab3c01d7243fc0c63cd7fb73/poetry_core-1.5.0-py3-none-any.whl", + "yanked": false, + "yanked_reason": null + }, + { + "comment_text": "", + "digests": { + "blake2b_256": "57bb2435fef60bb01f6c0891d9482c7053b50e90639f0f74d7658e99bdd4da69", + "md5": "481671a4895af7cdda4944eab67f3843", + "sha256": "253521bb7104e1df81f64d7b49ea1825057c91fa156d7d0bd752fefdad6f8c7a" + }, + "downloads": -1, + "filename": "poetry_core-1.5.0.tar.gz", + "has_sig": false, + "md5_digest": "481671a4895af7cdda4944eab67f3843", + "packagetype": "sdist", + "python_version": "source", + "requires_python": ">=3.7,<4.0", + "size": 448812, + "upload_time": "2023-01-28T10:52:53", + "upload_time_iso_8601": "2023-01-28T10:52:53.916268Z", + "url": "https://files.pythonhosted.org/packages/57/bb/2435fef60bb01f6c0891d9482c7053b50e90639f0f74d7658e99bdd4da69/poetry_core-1.5.0.tar.gz", + "yanked": false, + "yanked_reason": null + } + ], + "vulnerabilities": [] +} diff --git a/tests/repositories/fixtures/pypi.org/json/zipp.json b/tests/repositories/fixtures/pypi.org/json/zipp.json new file mode 100644 index 00000000000..085679a0c6e --- /dev/null +++ b/tests/repositories/fixtures/pypi.org/json/zipp.json @@ -0,0 +1,27 @@ +{ + "name": "zipp", + "files": [ + { + "hashes": { + "md5": "0ec47fbf522751f6c5fa904cb33f1f59", + "sha256": "957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3" + }, + "filename": "zipp-3.5.0-py3-none-any.whl", + "requires_python": ">=3.6", + "url": "https://files.pythonhosted.org/packages/92/d9/89f433969fb8dc5b9cbdd4b4deb587720ec1aeb59a020cf15002b9593eef/zipp-3.5.0-py3-none-any.whl" + }, + { + "hashes": { + "md5": "617efbf3edb707c57008ec00f408972f", + "sha256": "f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4" + }, + "filename": "zipp-3.5.0.tar.gz", + "requires_python": ">=3.6", + "url": "https://files.pythonhosted.org/packages/3a/9f/1d4b62cbe8d222539a84089eeab603d8e45ee1f897803a0ae0860400d6e7/zipp-3.5.0.tar.gz" + } + ], + "meta": { + "api-version": "1.0", + "_last-serial": 3879671 + } +} diff --git a/tests/repositories/fixtures/pypi.org/json/zipp/3.5.0.json b/tests/repositories/fixtures/pypi.org/json/zipp/3.5.0.json new file mode 100644 index 00000000000..356bd2b3483 --- /dev/null +++ b/tests/repositories/fixtures/pypi.org/json/zipp/3.5.0.json @@ -0,0 +1,142 @@ +{ + "info": { + "author": "Jason R. Coombs", + "author_email": "jaraco@jaraco.com", + "bugtrack_url": null, + "classifiers": [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only" + ], + "description": "", + "description_content_type": "", + "docs_url": null, + "download_url": "", + "downloads": { + "last_day": -1, + "last_month": -1, + "last_week": -1 + }, + "home_page": "https://github.com/jaraco/zipp", + "keywords": "", + "license": "", + "maintainer": "", + "maintainer_email": "", + "name": "zipp", + "package_url": "https://pypi.org/project/zipp/", + "platform": "", + "project_url": "https://pypi.org/project/zipp/", + "project_urls": { + "Homepage": "https://github.com/jaraco/zipp" + }, + "release_url": "https://pypi.org/project/zipp/3.5.0/", + "requires_dist": [ + "sphinx ; extra == 'docs'", + "jaraco.packaging (>=8.2) ; extra == 'docs'", + "rst.linker (>=1.9) ; extra == 'docs'", + "pytest (>=4.6) ; extra == 'testing'", + "pytest-checkdocs (>=2.4) ; extra == 'testing'", + "pytest-flake8 ; extra == 'testing'", + "pytest-cov ; extra == 'testing'", + "pytest-enabler (>=1.0.1) ; extra == 'testing'", + "jaraco.itertools ; extra == 'testing'", + "func-timeout ; extra == 'testing'", + "pytest-black (>=0.3.7) ; (platform_python_implementation != \"PyPy\" and python_version < \"3.10\") and extra == 'testing'", + "pytest-mypy ; (platform_python_implementation != \"PyPy\" and python_version < \"3.10\") and extra == 'testing'" + ], + "requires_python": ">=3.6", + "summary": "Backport of pathlib-compatible object wrapper for zip files", + "version": "3.5.0", + "yanked": false, + "yanked_reason": null + }, + "last_serial": 10811847, + "releases": { + "3.5.0": [ + { + "comment_text": "", + "digests": { + "md5": "0ec47fbf522751f6c5fa904cb33f1f59", + "sha256": "957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3" + }, + "downloads": -1, + "filename": "zipp-3.5.0-py3-none-any.whl", + "has_sig": false, + "md5_digest": "0ec47fbf522751f6c5fa904cb33f1f59", + "packagetype": "bdist_wheel", + "python_version": "py3", + "requires_python": ">=3.6", + "size": 5700, + "upload_time": "2021-07-02T23:51:45", + "upload_time_iso_8601": "2021-07-02T23:51:45.759726Z", + "url": "https://files.pythonhosted.org/packages/92/d9/89f433969fb8dc5b9cbdd4b4deb587720ec1aeb59a020cf15002b9593eef/zipp-3.5.0-py3-none-any.whl", + "yanked": false, + "yanked_reason": null + }, + { + "comment_text": "", + "digests": { + "md5": "617efbf3edb707c57008ec00f408972f", + "sha256": "f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4" + }, + "downloads": -1, + "filename": "zipp-3.5.0.tar.gz", + "has_sig": false, + "md5_digest": "617efbf3edb707c57008ec00f408972f", + "packagetype": "sdist", + "python_version": "source", + "requires_python": ">=3.6", + "size": 13270, + "upload_time": "2021-07-02T23:51:47", + "upload_time_iso_8601": "2021-07-02T23:51:47.004396Z", + "url": "https://files.pythonhosted.org/packages/3a/9f/1d4b62cbe8d222539a84089eeab603d8e45ee1f897803a0ae0860400d6e7/zipp-3.5.0.tar.gz", + "yanked": false, + "yanked_reason": null + } + ] + }, + "urls": [ + { + "comment_text": "", + "digests": { + "md5": "0ec47fbf522751f6c5fa904cb33f1f59", + "sha256": "957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3" + }, + "downloads": -1, + "filename": "zipp-3.5.0-py3-none-any.whl", + "has_sig": false, + "md5_digest": "0ec47fbf522751f6c5fa904cb33f1f59", + "packagetype": "bdist_wheel", + "python_version": "py3", + "requires_python": ">=3.6", + "size": 5700, + "upload_time": "2021-07-02T23:51:45", + "upload_time_iso_8601": "2021-07-02T23:51:45.759726Z", + "url": "https://files.pythonhosted.org/packages/92/d9/89f433969fb8dc5b9cbdd4b4deb587720ec1aeb59a020cf15002b9593eef/zipp-3.5.0-py3-none-any.whl", + "yanked": false, + "yanked_reason": null + }, + { + "comment_text": "", + "digests": { + "md5": "617efbf3edb707c57008ec00f408972f", + "sha256": "f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4" + }, + "downloads": -1, + "filename": "zipp-3.5.0.tar.gz", + "has_sig": false, + "md5_digest": "617efbf3edb707c57008ec00f408972f", + "packagetype": "sdist", + "python_version": "source", + "requires_python": ">=3.6", + "size": 13270, + "upload_time": "2021-07-02T23:51:47", + "upload_time_iso_8601": "2021-07-02T23:51:47.004396Z", + "url": "https://files.pythonhosted.org/packages/3a/9f/1d4b62cbe8d222539a84089eeab603d8e45ee1f897803a0ae0860400d6e7/zipp-3.5.0.tar.gz", + "yanked": false, + "yanked_reason": null + } + ] +} diff --git a/tests/repositories/test_pypi_repository.py b/tests/repositories/test_pypi_repository.py index bff1a4fb5d5..35468e057b5 100644 --- a/tests/repositories/test_pypi_repository.py +++ b/tests/repositories/test_pypi_repository.py @@ -16,11 +16,14 @@ from requests.models import Response from poetry.factory import Factory +from poetry.repositories.exceptions import PackageNotFound +from poetry.repositories.link_sources.json import SimpleJsonPage from poetry.repositories.pypi_repository import PyPiRepository from poetry.utils._compat import encode if TYPE_CHECKING: + from packaging.utils import NormalizedName from pytest_mock import MockerFixture @@ -36,6 +39,14 @@ class MockRepository(PyPiRepository): def __init__(self, fallback: bool = False) -> None: super().__init__(url="http://foo.bar", disable_cache=True, fallback=fallback) + def get_json_page(self, name: NormalizedName) -> SimpleJsonPage: + fixture = self.JSON_FIXTURES / (name + ".json") + + if not fixture.exists(): + raise PackageNotFound(f"Package [{name}] not found.") + + return SimpleJsonPage("", json.loads(fixture.read_text())) + def _get( self, url: str, headers: dict[str, str] | None = None ) -> dict[str, Any] | None: diff --git a/tests/utils/test_helpers.py b/tests/utils/test_helpers.py index 835298fa405..dad06297609 100644 --- a/tests/utils/test_helpers.py +++ b/tests/utils/test_helpers.py @@ -73,7 +73,7 @@ def test_parse_requires(): def test_default_hash(fixture_dir: FixtureDirGetter) -> None: root_dir = Path(__file__).parent.parent.parent file_path = root_dir / fixture_dir("distributions/demo-0.1.0.tar.gz") - sha_256 = "72e8531e49038c5f9c4a837b088bfcb8011f4a9f76335c8f0654df6ac539b3d6" + sha_256 = "9fa123ad707a5c6c944743bf3e11a0e80d86cb518d3cf25320866ca3ef43e2ad" assert get_file_hash(file_path) == sha_256 @@ -88,40 +88,40 @@ def test_default_hash(fixture_dir: FixtureDirGetter) -> None: [ (hash_name, value) for hash_name, value in [ - ("sha224", "972d02f36539a98599aed0566bc8aaf3e6701f4e895dd797d8f5248e"), + ("sha224", "d26bd24163fe91c16b4b0162e773514beab77b76114d9faf6a31e350"), ( "sha3_512", - "c04ee109ae52d6440445e24dbd6d244a1d0f0289ef79cb7ba9bc3c139c0237169af9a8f61cd1cf4fc17f853ddf84f97c475ac5bb6c91a4aff0b825b884d4896c", # noqa: E501 + "196f4af9099185054ed72ca1d4c57707da5d724df0af7c3dfcc0fd018b0e0533908e790a291600c7d196fe4411b4f5f6db45213fe6e5cd5512bf18b2e9eff728", # noqa: E501 ), ( "blake2s", - "c336ecbc9d867c9d860accfba4c3723c51c4b5c47a1e0a955e1c8df499e36741", + "6dd9007d36c106defcf362cc637abeca41e8e93999928c8fcfaba515ed33bc93", ), ( "sha3_384", - "d4abb2459941369aabf8880c5287b7eeb80678e14f13c71b9ecf64c772029dc3f93939590bea9ecdb51a1d1a74fefc5a", # noqa: E501 + "787264d7885a0c305d2ee4daecfff435d11818399ef96cacef7e7c6bb638ce475f630d39fdd2800ca187dcd0071dc410", # noqa: E501 ), ( "blake2b", - "48e70abac547ab38e2330e6e6743a0c0f6274dcaa6df2c98135a78a9dd5b04a072d551fc3851b34da03eb0bf50dd71c7f32a8c36956e99fd6c66491bc7844800", # noqa: E501 + "077a34e8252c8f6776bddd0d34f321cc52762cb4c11a1c7aa9b6168023f1722caf53c9f029074a6eb990a8de341d415dd986293bc2a2fccddad428be5605696e", # noqa: E501 ), ( "sha256", - "72e8531e49038c5f9c4a837b088bfcb8011f4a9f76335c8f0654df6ac539b3d6", + "9fa123ad707a5c6c944743bf3e11a0e80d86cb518d3cf25320866ca3ef43e2ad", ), ( "sha512", - "e08a00a4b86358e49a318e7e3ba7a3d2fabdd17a2fef95559a0af681ea07ab1296b0b8e11e645297da296290661dc07ae3c8f74eab66bd18a80dce0c0ccb355b", # noqa: E501 + "766ecf369b6bdf801f6f7bbfe23923cc9793d633a55619472cd3d5763f9154711fbf57c8b6ca74e4a82fa9bd8380af831e7b8668e68e362669fc60b1d81d79ad", # noqa: E501 ), ( "sha384", - "aa3144e28c6700a83247e8ec8711af5d3f5f75997990d48ec41e66bd275b3d0e19ee6f2fe525a358f874aa717afd06a9", # noqa: E501 + "c638f32460f318035e4600284ba64fb531630740aebd33885946e527002d742787ff09eb65fd81bc34ce5ff5ef11cfe8", # noqa: E501 ), - ("sha3_224", "64bfc6e4125b4c6d67fd88ad1c7d1b5c4dc11a1970e433cd576c91d4"), - ("sha1", "4c057579005ac3e68e951a11ffdc4b27c6ae16af"), + ("sha3_224", "72980fc7bdf8c4d34268dc469442b09e1ccd2a8ff390954fc4d55a5a"), + ("sha1", "91b585bd38f72d7ceedb07d03f94911b772fdc4c"), ( "sha3_256", - "ba3d2a964b0680b6dc9565a03952e29c294c785d5a2307d3e2d785d73b75ed7e", + "7da5c08b416e6bcb339d6bedc0fe077c6e69af00607251ef4424c356ea061fcb", ), ] if hash_name in algorithms_guaranteed From 6eef349c4a9ed8826ff57d27a841bc3b8232b816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Tue, 23 Aug 2022 23:38:39 +0200 Subject: [PATCH 083/151] Use the wheel builder to build editable wheels --- src/poetry/installation/chef.py | 12 ++-- src/poetry/installation/executor.py | 104 +++++++++++++--------------- tests/installation/test_chef.py | 14 ++++ tests/installation/test_executor.py | 99 +++++++++++++++++++------- 4 files changed, 143 insertions(+), 86 deletions(-) diff --git a/src/poetry/installation/chef.py b/src/poetry/installation/chef.py index ec294b52d3a..866cc5344d4 100644 --- a/src/poetry/installation/chef.py +++ b/src/poetry/installation/chef.py @@ -94,18 +94,22 @@ def __init__(self, config: Config, env: Env) -> None: Path(config.get("cache-dir")).expanduser().joinpath("artifacts") ) - def prepare(self, archive: Path, output_dir: Path | None = None) -> Path: + def prepare( + self, archive: Path, output_dir: Path | None = None, *, editable: bool = False + ) -> Path: if not self._should_prepare(archive): return archive if archive.is_dir(): tmp_dir = tempfile.mkdtemp(prefix="poetry-chef-") - return self._prepare(archive, Path(tmp_dir)) + return self._prepare(archive, Path(tmp_dir), editable=editable) return self._prepare_sdist(archive, destination=output_dir) - def _prepare(self, directory: Path, destination: Path) -> Path: + def _prepare( + self, directory: Path, destination: Path, *, editable: bool = False + ) -> Path: with ephemeral_environment(self._env.python) as venv: env = IsolatedEnv(venv, self._config) builder = ProjectBuilder( @@ -124,7 +128,7 @@ def _prepare(self, directory: Path, destination: Path) -> Path: try: return Path( builder.build( - "wheel", + "wheel" if not editable else "editable", destination.as_posix(), ) ) diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index f84148a9e1a..22592770d1a 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -477,17 +477,18 @@ def _execute_uninstall(self, operation: Uninstall) -> int: def _install(self, operation: Install | Update) -> int: package = operation.package - if package.source_type == "directory": - if not self._use_wheel_installer: - return self._install_directory_without_wheel_installer(operation) - - return self._install_directory(operation) + if package.source_type == "directory" and not self._use_wheel_installer: + return self._install_directory_without_wheel_installer(operation) + cleanup_archive: bool = False if package.source_type == "git": - return self._install_git(operation) - - if package.source_type == "file": + archive = self._prepare_git_archive(operation) + cleanup_archive = True + elif package.source_type == "file": archive = self._prepare_archive(operation) + elif package.source_type == "directory": + archive = self._prepare_directory_archive(operation) + cleanup_archive = True elif package.source_type == "url": assert package.source_url is not None archive = self._download_link(operation, Link(package.source_url)) @@ -504,7 +505,18 @@ def _install(self, operation: Install | Update) -> int: if not self._use_wheel_installer: return self.pip_install(archive, upgrade=operation.job_type == "update") - self._wheel_installer.install(archive) + try: + if operation.job_type == "update": + # Uninstall first + # TODO: Make an uninstaller and find a way to rollback in case + # the new package can't be installed + assert isinstance(operation, Update) + self._remove(operation.initial_package) + + self._wheel_installer.install(archive) + finally: + if cleanup_archive: + archive.unlink() return 0 @@ -541,9 +553,9 @@ def _prepare_archive(self, operation: Install | Update) -> Path: if not Path(package.source_url).is_absolute() and package.root_dir: archive = package.root_dir / archive - return self._chef.prepare(archive) + return self._chef.prepare(archive, editable=package.develop) - def _install_directory(self, operation: Install | Update) -> int: + def _prepare_directory_archive(self, operation: Install | Update) -> Path: package = operation.package operation_message = self.get_operation_message(operation) @@ -562,27 +574,35 @@ def _install_directory(self, operation: Install | Update) -> int: if package.source_subdirectory: req /= package.source_subdirectory - if package.develop: - # Editable installations are currently not supported - # for PEP-517 build systems so we defer to pip. - # TODO: Remove this workaround once either PEP-660 or PEP-662 is accepted - return self.pip_install(req, editable=True) + return self._prepare_archive(operation) - archive = self._prepare_archive(operation) + def _prepare_git_archive(self, operation: Install | Update) -> Path: + from poetry.vcs.git import Git - try: - if operation.job_type == "update": - # Uninstall first - # TODO: Make an uninstaller and find a way to rollback in case - # the new package can't be installed - assert isinstance(operation, Update) - self._remove(operation.initial_package) + package = operation.package + operation_message = self.get_operation_message(operation) - self._wheel_installer.install(archive) - finally: - archive.unlink() + message = ( + f" • {operation_message}: Cloning..." + ) + self._write(operation, message) - return 0 + assert package.source_url is not None + source = Git.clone( + url=package.source_url, + source_root=self._env.path / "src", + revision=package.source_resolved_reference or package.source_reference, + ) + + # Now we just need to install from the source directory + original_url = package.source_url + package._source_url = str(source.path) + + archive = self._prepare_directory_archive(operation) + + package._source_url = original_url + + return archive def _install_directory_without_wheel_installer( self, operation: Install | Update @@ -650,34 +670,6 @@ def _install_directory_without_wheel_installer( return self.pip_install(req, upgrade=True, editable=package.develop) - def _install_git(self, operation: Install | Update) -> int: - from poetry.vcs.git import Git - - package = operation.package - operation_message = self.get_operation_message(operation) - - message = ( - f" • {operation_message}: Cloning..." - ) - self._write(operation, message) - - assert package.source_url is not None - source = Git.clone( - url=package.source_url, - source_root=self._env.path / "src", - revision=package.source_resolved_reference or package.source_reference, - ) - - # Now we just need to install from the source directory - original_url = package.source_url - package._source_url = str(source.path) - - status_code = self._install_directory(operation) - - package._source_url = original_url - - return status_code - def _download(self, operation: Install | Update) -> Path: link = self._chooser.choose_for(operation.package) diff --git a/tests/installation/test_chef.py b/tests/installation/test_chef.py index bd730880f6b..ac084455850 100644 --- a/tests/installation/test_chef.py +++ b/tests/installation/test_chef.py @@ -2,6 +2,7 @@ from pathlib import Path from typing import TYPE_CHECKING +from zipfile import ZipFile import pytest @@ -165,3 +166,16 @@ def test_prepare_directory_with_extensions( wheel = chef.prepare(archive) assert wheel.name == f"extended-0.1-{env.supported_tags[0]}.whl" + + +def test_prepare_directory_editable(config: Config, config_cache_dir: Path): + chef = Chef(config, EnvManager.get_system_env()) + + archive = Path(__file__).parent.parent.joinpath("fixtures/simple_project").resolve() + + wheel = chef.prepare(archive, editable=True) + + assert wheel.name == "simple_project-1.2.3-py2.py3-none-any.whl" + + with ZipFile(wheel) as z: + assert "simple_project.pth" in z.namelist() diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 0be2fb8ae78..19dc220d1c7 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -4,16 +4,19 @@ import json import re import shutil +import tempfile from pathlib import Path from typing import TYPE_CHECKING from typing import Any +from typing import Callable from urllib.parse import urlparse import pytest from cleo.formatters.style import Style from cleo.io.buffered_io import BufferedIO +from cleo.io.outputs.output import Verbosity from poetry.core.packages.package import Package from poetry.core.packages.utils.link import Link @@ -41,26 +44,40 @@ class Chef(BaseChef): - _directory_wheel = None - _sdist_wheel = None + _directory_wheels: list[Path] | None = None + _sdist_wheels: list[Path] | None = None - def set_directory_wheel(self, wheel: Path) -> None: - self._directory_wheel = wheel + def set_directory_wheel(self, wheels: Path | list[Path]) -> None: + if not isinstance(wheels, list): + wheels = [wheels] - def set_sdist_wheel(self, wheel: Path) -> None: - self._sdist_wheel = wheel + self._directory_wheels = wheels + + def set_sdist_wheel(self, wheels: Path | list[Path]) -> None: + if not isinstance(wheels, list): + wheels = [wheels] + + self._sdist_wheels = wheels def _prepare_sdist(self, archive: Path, destination: Path | None = None) -> Path: - if self._sdist_wheel is not None: - return self._sdist_wheel + if self._sdist_wheels is not None: + wheel = self._sdist_wheels.pop(0) + self._sdist_wheels.append(wheel) + + return wheel return super()._prepare_sdist(archive) - def _prepare(self, directory: Path, destination: Path) -> Path: - if self._directory_wheel is not None: - return self._directory_wheel + def _prepare( + self, directory: Path, destination: Path, *, editable: bool = False + ) -> Path: + if self._directory_wheels is not None: + wheel = self._directory_wheels.pop(0) + self._directory_wheels.append(wheel) - return super()._prepare(directory, destination) + return wheel + + return super()._prepare(directory, destination, editable=editable) @pytest.fixture @@ -132,17 +149,38 @@ def callback( @pytest.fixture() -def wheel(tmp_dir: Path) -> Path: - shutil.copyfile( - Path(__file__) - .parent.parent.joinpath( - "fixtures/distributions/demo-0.1.2-py2.py3-none-any.whl" +def copy_wheel(tmp_dir: Path) -> Callable[[], Path]: + def _copy_wheel() -> Path: + tmp_name = tempfile.mktemp() + Path(tmp_dir).joinpath(tmp_name).mkdir() + + shutil.copyfile( + Path(__file__) + .parent.parent.joinpath( + "fixtures/distributions/demo-0.1.2-py2.py3-none-any.whl" + ) + .as_posix(), + Path(tmp_dir) + .joinpath(tmp_name) + .joinpath("demo-0.1.2-py2.py3-none-any.whl") + .as_posix(), + ) + + return ( + Path(tmp_dir).joinpath(tmp_name).joinpath("demo-0.1.2-py2.py3-none-any.whl") ) - .as_posix(), - Path(tmp_dir).joinpath("demo-0.1.2-py2.py3-none-any.whl").as_posix(), - ) - return Path(tmp_dir).joinpath("demo-0.1.2-py2.py3-none-any.whl") + return _copy_wheel + + +@pytest.fixture() +def wheel(copy_wheel: Callable[[], Path]) -> Path: + archive = copy_wheel() + + yield archive + + if archive.exists(): + archive.unlink() def test_execute_executes_a_batch_of_operations( @@ -153,15 +191,18 @@ def test_execute_executes_a_batch_of_operations( tmp_dir: str, mock_file_downloads: None, env: MockEnv, - wheel: Path, + copy_wheel: Callable[[], Path], ): wheel_install = mocker.patch.object(WheelInstaller, "install") config.merge({"cache-dir": tmp_dir}) + prepare_spy = mocker.spy(Chef, "_prepare") chef = Chef(config, env) - chef.set_directory_wheel(wheel) - chef.set_sdist_wheel(wheel) + chef.set_directory_wheel([copy_wheel(), copy_wheel()]) + chef.set_sdist_wheel(copy_wheel()) + + io.set_verbosity(Verbosity.VERY_VERBOSE) executor = Executor(env, pool, config, io) executor._chef = chef @@ -223,11 +264,17 @@ def test_execute_executes_a_batch_of_operations( expected = set(expected.splitlines()) output = set(io.fetch_output().splitlines()) assert output == expected - assert wheel_install.call_count == 4 - # One pip uninstall and one pip editable install + assert wheel_install.call_count == 5 + # Two pip uninstalls: one for the remove operation one for the update operation assert len(env.executed) == 2 assert return_code == 0 + assert prepare_spy.call_count == 2 + assert prepare_spy.call_args_list == [ + mocker.call(chef, mocker.ANY, mocker.ANY, editable=False), + mocker.call(chef, mocker.ANY, mocker.ANY, editable=True), + ] + @pytest.mark.parametrize( "operations, has_warning", From a4838b7636ee7092bef4822d788abb3ed34024e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Sun, 18 Sep 2022 02:33:17 +0200 Subject: [PATCH 084/151] Change setting name for modern installation --- src/poetry/config/config.py | 10 +++++++--- src/poetry/console/commands/config.py | 1 + src/poetry/installation/executor.py | 8 +++++--- tests/console/commands/test_config.py | 6 +++--- tests/installation/test_executor.py | 7 ++++++- 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/poetry/config/config.py b/src/poetry/config/config.py index 98d37fcb117..6eda9699870 100644 --- a/src/poetry/config/config.py +++ b/src/poetry/config/config.py @@ -126,9 +126,13 @@ class Config: "experimental": { "new-installer": True, "system-git-client": False, - "wheel-installer": True, }, - "installer": {"parallel": True, "max-workers": None, "no-binary": None}, + "installer": { + "modern-installation": True, + "parallel": True, + "max-workers": None, + "no-binary": None, + }, } def __init__( @@ -270,7 +274,7 @@ def _get_normalizer(name: str) -> Callable[[str], Any]: "virtualenvs.options.prefer-active-python", "experimental.new-installer", "experimental.system-git-client", - "experimental.wheel-installer", + "installer.modern-installation", "installer.parallel", }: return boolean_normalizer diff --git a/src/poetry/console/commands/config.py b/src/poetry/console/commands/config.py index 898312adc77..998af125755 100644 --- a/src/poetry/console/commands/config.py +++ b/src/poetry/console/commands/config.py @@ -70,6 +70,7 @@ def unique_config_values(self) -> dict[str, tuple[Any, Any]]: "virtualenvs.prefer-active-python": (boolean_validator, boolean_normalizer), "experimental.new-installer": (boolean_validator, boolean_normalizer), "experimental.system-git-client": (boolean_validator, boolean_normalizer), + "installer.modern-installation": (boolean_validator, boolean_normalizer), "installer.parallel": (boolean_validator, boolean_normalizer), "installer.max-workers": (lambda val: int(val) > 0, int_normalizer), "virtualenvs.prompt": (str, lambda val: str(val)), diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index 22592770d1a..d3c2574f8aa 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -61,7 +61,9 @@ def __init__( self._enabled = True self._verbose = False self._wheel_installer = WheelInstaller(self._env) - self._use_wheel_installer = config.get("experimental.wheel-installer", True) + self._use_modern_installation = config.get( + "installer.modern-installation", True + ) if parallel is None: parallel = config.get("installer.parallel", True) @@ -477,7 +479,7 @@ def _execute_uninstall(self, operation: Uninstall) -> int: def _install(self, operation: Install | Update) -> int: package = operation.package - if package.source_type == "directory" and not self._use_wheel_installer: + if package.source_type == "directory" and not self._use_modern_installation: return self._install_directory_without_wheel_installer(operation) cleanup_archive: bool = False @@ -502,7 +504,7 @@ def _install(self, operation: Install | Update) -> int: ) self._write(operation, message) - if not self._use_wheel_installer: + if not self._use_modern_installation: return self.pip_install(archive, upgrade=operation.job_type == "update") try: diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index 547ae31fa6f..22fca7dcafe 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -53,8 +53,8 @@ def test_list_displays_default_value_if_not_set( expected = f"""cache-dir = {cache_dir} experimental.new-installer = true experimental.system-git-client = false -experimental.wheel-installer = true installer.max-workers = null +installer.modern-installation = true installer.no-binary = null installer.parallel = true virtualenvs.create = true @@ -83,8 +83,8 @@ def test_list_displays_set_get_setting( expected = f"""cache-dir = {cache_dir} experimental.new-installer = true experimental.system-git-client = false -experimental.wheel-installer = true installer.max-workers = null +installer.modern-installation = true installer.no-binary = null installer.parallel = true virtualenvs.create = false @@ -137,8 +137,8 @@ def test_list_displays_set_get_local_setting( expected = f"""cache-dir = {cache_dir} experimental.new-installer = true experimental.system-git-client = false -experimental.wheel-installer = true installer.max-workers = null +installer.modern-installation = true installer.no-binary = null installer.parallel = true virtualenvs.create = false diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 19dc220d1c7..51a987f662e 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -862,7 +862,12 @@ def test_executor_fallback_on_poetry_create_error_without_wheel_installer( "poetry.factory.Factory.create_poetry", side_effect=RuntimeError ) - config.merge({"cache-dir": tmp_dir, "experimental": {"wheel-installer": False}}) + config.merge( + { + "cache-dir": tmp_dir, + "installer": {"modern-installation": False}, + } + ) executor = Executor(env, pool, config, io) From 6c0ac3863f13ef9d884aabd7d7c368ddf8d14924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Sun, 18 Sep 2022 18:00:03 +0200 Subject: [PATCH 085/151] Add documentation for the new modern installation setting --- docs/configuration.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index af537772277..212341232bb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -190,6 +190,19 @@ the number of maximum workers is still limited at `number_of_cores + 4`. This configuration is ignored when `installer.parallel` is set to `false`. {{% /note %}} +### `installer.modern-installation` + +**Type**: `boolean` + +**Default**: `true` + +*Introduced in 1.4.0* + +Use a more modern and faster method for package installation. + +If this causes issues, you can disable it by setting it to `false` and report the problems +you encounter on the [issue tracker](https://github.com/python-poetry/poetry/issues). + ### `installer.no-binary` **Type**: `string | boolean` From f127bd27c35e3d191f2226875a25d097bdb17330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Sun, 18 Sep 2022 19:41:11 +0200 Subject: [PATCH 086/151] Improve error reporting for build backend errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Randy Döring <30527984+radoering@users.noreply.github.com> --- src/poetry/installation/chef.py | 35 ++++++++++---- src/poetry/installation/executor.py | 14 ++++++ tests/installation/test_executor.py | 72 +++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 8 deletions(-) diff --git a/src/poetry/installation/chef.py b/src/poetry/installation/chef.py index 866cc5344d4..2a4c02c7451 100644 --- a/src/poetry/installation/chef.py +++ b/src/poetry/installation/chef.py @@ -110,6 +110,8 @@ def prepare( def _prepare( self, directory: Path, destination: Path, *, editable: bool = False ) -> Path: + from subprocess import CalledProcessError + with ephemeral_environment(self._env.python) as venv: env = IsolatedEnv(venv, self._config) builder = ProjectBuilder( @@ -119,21 +121,38 @@ def _prepare( runner=quiet_subprocess_runner, ) env.install(builder.build_system_requires) - env.install( - builder.build_system_requires | builder.get_requires_for_build("wheel") - ) stdout = StringIO() - with redirect_stdout(stdout): - try: - return Path( + error: Exception | None = None + try: + with redirect_stdout(stdout): + env.install( + builder.build_system_requires + | builder.get_requires_for_build("wheel") + ) + path = Path( builder.build( "wheel" if not editable else "editable", destination.as_posix(), ) ) - except BuildBackendException as e: - raise ChefBuildError(str(e)) + except BuildBackendException as e: + message_parts = [str(e)] + if isinstance(e.exception, CalledProcessError) and ( + e.exception.stdout is not None or e.exception.stderr is not None + ): + message_parts.append( + e.exception.stderr.decode() + if e.exception.stderr is not None + else e.exception.stdout.decode() + ) + + error = ChefBuildError("\n\n".join(message_parts)) + + if error is not None: + raise error from None + + return path def _prepare_sdist(self, archive: Path, destination: Path | None = None) -> Path: from poetry.core.packages.utils.link import Link diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index d3c2574f8aa..764f3dd6aab 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -18,6 +18,7 @@ from poetry.core.packages.utils.link import Link from poetry.installation.chef import Chef +from poetry.installation.chef import ChefBuildError from poetry.installation.chooser import Chooser from poetry.installation.operations import Install from poetry.installation.operations import Uninstall @@ -295,6 +296,19 @@ def _execute_operation(self, operation: Operation) -> None: with self._lock: trace = ExceptionTrace(e) trace.render(io) + if isinstance(e, ChefBuildError): + pkg = operation.package + requirement = pkg.to_dependency().to_pep_508() + io.write_line("") + io.write_line( + "" + "Note: This error originates from the build backend," + " and is likely not a problem with poetry" + f" but with {pkg.pretty_name} ({pkg.full_pretty_version})" + " not supporting PEP 517 builds. You can verify this by" + f" running 'pip wheel --use-pep517 \"{requirement}\"'." + "" + ) io.write_line("") finally: with self._lock: diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 51a987f662e..c6277182f32 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -7,6 +7,7 @@ import tempfile from pathlib import Path +from subprocess import CalledProcessError from typing import TYPE_CHECKING from typing import Any from typing import Callable @@ -14,12 +15,15 @@ import pytest +from build import BuildBackendException +from build import ProjectBuilder from cleo.formatters.style import Style from cleo.io.buffered_io import BufferedIO from cleo.io.outputs.output import Verbosity from poetry.core.packages.package import Package from poetry.core.packages.utils.link import Link +from poetry.factory import Factory from poetry.installation.chef import Chef as BaseChef from poetry.installation.executor import Executor from poetry.installation.operations import Install @@ -903,3 +907,71 @@ def test_executor_fallback_on_poetry_create_error_without_wheel_installer( assert mock_pip_install.call_count == 1 assert mock_pip_install.call_args[1].get("upgrade") is True assert mock_pip_install.call_args[1].get("editable") is False + + +@pytest.mark.parametrize("failing_method", ["build", "get_requires_for_build"]) +def test_build_backend_errors_are_reported_correctly_if_caused_by_subprocess( + failing_method: str, + mocker: MockerFixture, + config: Config, + pool: RepositoryPool, + io: BufferedIO, + tmp_dir: str, + mock_file_downloads: None, + env: MockEnv, +): + mocker.patch.object(Factory, "create_pool", return_value=pool) + + error = BuildBackendException( + CalledProcessError(1, ["pip"], output=b"Error on stdout") + ) + mocker.patch.object(ProjectBuilder, failing_method, side_effect=error) + io.set_verbosity(Verbosity.NORMAL) + + executor = Executor(env, pool, config, io) + + package_name = "simple-project" + package_version = "1.2.3" + directory_package = Package( + package_name, + package_version, + source_type="directory", + source_url=Path(__file__) + .parent.parent.joinpath("fixtures/simple_project") + .resolve() + .as_posix(), + ) + + return_code = executor.execute( + [ + Install(directory_package), + ] + ) + + assert return_code == 1 + + package_url = directory_package.source_url + expected_start = f""" +Package operations: 1 install, 0 updates, 0 removals + + • Installing {package_name} ({package_version} {package_url}) + + ChefBuildError + + Backend operation failed: CalledProcessError(1, ['pip']) + \ + + Error on stdout +""" + + requirement = directory_package.to_dependency().to_pep_508() + expected_end = f""" +Note: This error originates from the build backend, and is likely not a problem with \ +poetry but with {package_name} ({package_version} {package_url}) not supporting \ +PEP 517 builds. You can verify this by running 'pip wheel --use-pep517 "{requirement}"'. + +""" + + output = io.fetch_output() + assert output.startswith(expected_start) + assert output.endswith(expected_end) From 53cdce0d33d100c3d893af0d80a196d1cfb587b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sat, 10 Dec 2022 18:33:12 +0100 Subject: [PATCH 087/151] Add option to compile bytecode during installation (similar to default behavior of old installer) --- docs/cli.md | 16 ++++++++++++++++ src/poetry/console/commands/install.py | 12 ++++++++---- src/poetry/installation/executor.py | 3 +++ src/poetry/installation/wheel_installer.py | 8 +++++++- tests/console/commands/test_install.py | 18 ++++++++++++++++++ tests/installation/test_wheel_installer.py | 20 ++++++++++++++++++++ 6 files changed, 72 insertions(+), 5 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index a4c682a757d..f09ffb97273 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -225,6 +225,21 @@ If you want to skip this installation, use the `--no-root` option. poetry install --no-root ``` +By default `poetry` does not compile Python source files to bytecode during installation. +This speeds up the installation process, but the first execution may take a little more +time because Python then compiles source files to bytecode automatically. +If you want to compile source files to bytecode during installation, +you can use the `--compile` option: + +```bash +poetry install --compile +``` + +{{% note %}} +The `--compile` option has no effect if `installer.modern-installation` +is set to `false` because the old installer always compiles source files to bytecode. +{{% /note %}} + ### Options * `--without`: The dependency groups to ignore. @@ -236,6 +251,7 @@ poetry install --no-root * `--dry-run`: Output the operations but do not execute anything (implicitly enables --verbose). * `--extras (-E)`: Features to install (multiple values allowed). * `--all-extras`: Install all extra features (conflicts with --extras). +* `--compile`: Compile Python source files to bytecode. * `--no-dev`: Do not install dev dependencies. (**Deprecated**, use `--without dev` or `--only main` instead) * `--remove-untracked`: Remove dependencies not presented in the lock file. (**Deprecated**, use `--sync` instead) diff --git a/src/poetry/console/commands/install.py b/src/poetry/console/commands/install.py index b499970aac9..453feaf55d0 100644 --- a/src/poetry/console/commands/install.py +++ b/src/poetry/console/commands/install.py @@ -54,12 +54,15 @@ class InstallCommand(InstallerCommand): multiple=True, ), option("all-extras", None, "Install all extra dependencies."), + option("only-root", None, "Exclude all dependencies."), option( - "only-root", + "compile", None, - "Exclude all dependencies.", - flag=True, - multiple=False, + ( + "Compile Python source files to bytecode." + " (This option has no effect if modern-installation is disabled" + " because the old installer always compiles.)" + ), ), ] @@ -146,6 +149,7 @@ def handle(self) -> int: self.installer.only_groups(self.activated_groups) self.installer.dry_run(self.option("dry-run")) self.installer.requires_synchronization(with_synchronization) + self.installer.executor.enable_bytecode_compilation(self.option("compile")) self.installer.verbose(self.io.is_verbose()) return_code = self.installer.run() diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index 764f3dd6aab..6ba544eb40d 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -123,6 +123,9 @@ def verbose(self, verbose: bool = True) -> Executor: return self + def enable_bytecode_compilation(self, enable: bool = True) -> None: + self._wheel_installer.enable_bytecode_compilation(enable) + def pip_install( self, req: Path, upgrade: bool = False, editable: bool = False ) -> int: diff --git a/src/poetry/installation/wheel_installer.py b/src/poetry/installation/wheel_installer.py index 78cecb39c98..c8df26960f8 100644 --- a/src/poetry/installation/wheel_installer.py +++ b/src/poetry/installation/wheel_installer.py @@ -66,7 +66,10 @@ def for_source(self, source: WheelFile) -> WheelDestination: scheme_dict["headers"] = str(Path(scheme_dict["headers"]) / source.distribution) return self.__class__( - scheme_dict, interpreter=self.interpreter, script_kind=self.script_kind + scheme_dict, + interpreter=self.interpreter, + script_kind=self.script_kind, + bytecode_optimization_levels=self.bytecode_optimization_levels, ) @@ -90,6 +93,9 @@ def __init__(self, env: Env) -> None: schemes, interpreter=self._env.python, script_kind=script_kind ) + def enable_bytecode_compilation(self, enable: bool = True) -> None: + self._destination.bytecode_optimization_levels = (1,) if enable else () + def install(self, wheel: Path) -> None: with WheelFile.open(Path(wheel.as_posix())) as source: install( diff --git a/tests/console/commands/test_install.py b/tests/console/commands/test_install.py index 9e4e59d1b5e..0200a452bfa 100644 --- a/tests/console/commands/test_install.py +++ b/tests/console/commands/test_install.py @@ -162,6 +162,24 @@ def test_sync_option_is_passed_to_the_installer( assert tester.command.installer._requires_synchronization +@pytest.mark.parametrize("compile", [False, True]) +def test_compile_option_is_passed_to_the_installer( + tester: CommandTester, mocker: MockerFixture, compile: bool +): + """ + The --compile option is passed properly to the installer. + """ + mocker.patch.object(tester.command.installer, "run", return_value=1) + enable_bytecode_compilation_mock = mocker.patch.object( + tester.command.installer.executor._wheel_installer, + "enable_bytecode_compilation", + ) + + tester.execute("--compile" if compile else "") + + enable_bytecode_compilation_mock.assert_called_once_with(compile) + + def test_no_all_extras_doesnt_populate_installer( tester: CommandTester, mocker: MockerFixture ): diff --git a/tests/installation/test_wheel_installer.py b/tests/installation/test_wheel_installer.py index 42dcec76292..7fc4826f940 100644 --- a/tests/installation/test_wheel_installer.py +++ b/tests/installation/test_wheel_installer.py @@ -59,3 +59,23 @@ def test_installer_file_contains_valid_version(default_installation: Path) -> No match = re.match(r"Poetry (?P.*)", installer_content) assert match parse_constraint(match.group("version")) # must not raise an error + + +def test_default_installation_no_bytecode(default_installation: Path) -> None: + cache_dir = default_installation / "demo" / "__pycache__" + assert not cache_dir.exists() + + +@pytest.mark.parametrize("compile", [True, False]) +def test_enable_bytecode_compilation( + env: MockEnv, demo_wheel: Path, compile: bool +) -> None: + installer = WheelInstaller(env) + installer.enable_bytecode_compilation(compile) + installer.install(demo_wheel) + cache_dir = Path(env.paths["purelib"]) / "demo" / "__pycache__" + if compile: + assert cache_dir.exists() + assert list(cache_dir.glob("*.pyc")) + else: + assert not cache_dir.exists() From 58fe8f22c8b4b46596463ca8eecc2a9463924a84 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Mon, 6 Feb 2023 21:28:02 +0000 Subject: [PATCH 088/151] mypy 1.0 --- poetry.lock | 60 ++++++++++++++--------------- pyproject.toml | 2 +- src/poetry/console/commands/init.py | 5 ++- src/poetry/layouts/layout.py | 4 +- 4 files changed, 34 insertions(+), 37 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2bae3a2b2a2..5485ef7fa4e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -911,42 +911,38 @@ files = [ [[package]] name = "mypy" -version = "0.991" +version = "1.0.0" description = "Optional static typing for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mypy-0.991-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab"}, - {file = "mypy-0.991-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d"}, - {file = "mypy-0.991-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6"}, - {file = "mypy-0.991-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb"}, - {file = "mypy-0.991-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305"}, - {file = "mypy-0.991-cp310-cp310-win_amd64.whl", hash = "sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c"}, - {file = "mypy-0.991-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372"}, - {file = "mypy-0.991-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f"}, - {file = "mypy-0.991-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33"}, - {file = "mypy-0.991-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05"}, - {file = "mypy-0.991-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad"}, - {file = "mypy-0.991-cp311-cp311-win_amd64.whl", hash = "sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297"}, - {file = "mypy-0.991-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813"}, - {file = "mypy-0.991-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711"}, - {file = "mypy-0.991-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd"}, - {file = "mypy-0.991-cp37-cp37m-win_amd64.whl", hash = "sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef"}, - {file = "mypy-0.991-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a"}, - {file = "mypy-0.991-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93"}, - {file = "mypy-0.991-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf"}, - {file = "mypy-0.991-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135"}, - {file = "mypy-0.991-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70"}, - {file = "mypy-0.991-cp38-cp38-win_amd64.whl", hash = "sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243"}, - {file = "mypy-0.991-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d"}, - {file = "mypy-0.991-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5"}, - {file = "mypy-0.991-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3"}, - {file = "mypy-0.991-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648"}, - {file = "mypy-0.991-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476"}, - {file = "mypy-0.991-cp39-cp39-win_amd64.whl", hash = "sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461"}, - {file = "mypy-0.991-py3-none-any.whl", hash = "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb"}, - {file = "mypy-0.991.tar.gz", hash = "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06"}, + {file = "mypy-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0626db16705ab9f7fa6c249c017c887baf20738ce7f9129da162bb3075fc1af"}, + {file = "mypy-1.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ace23f6bb4aec4604b86c4843276e8fa548d667dbbd0cb83a3ae14b18b2db6c"}, + {file = "mypy-1.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87edfaf344c9401942883fad030909116aa77b0fa7e6e8e1c5407e14549afe9a"}, + {file = "mypy-1.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0ab090d9240d6b4e99e1fa998c2d0aa5b29fc0fb06bd30e7ad6183c95fa07593"}, + {file = "mypy-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:7cc2c01dfc5a3cbddfa6c13f530ef3b95292f926329929001d45e124342cd6b7"}, + {file = "mypy-1.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14d776869a3e6c89c17eb943100f7868f677703c8a4e00b3803918f86aafbc52"}, + {file = "mypy-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bb2782a036d9eb6b5a6efcdda0986774bf798beef86a62da86cb73e2a10b423d"}, + {file = "mypy-1.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cfca124f0ac6707747544c127880893ad72a656e136adc935c8600740b21ff5"}, + {file = "mypy-1.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8845125d0b7c57838a10fd8925b0f5f709d0e08568ce587cc862aacce453e3dd"}, + {file = "mypy-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b1b9e1ed40544ef486fa8ac022232ccc57109f379611633ede8e71630d07d2"}, + {file = "mypy-1.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c7cf862aef988b5fbaa17764ad1d21b4831436701c7d2b653156a9497d92c83c"}, + {file = "mypy-1.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cd187d92b6939617f1168a4fe68f68add749902c010e66fe574c165c742ed88"}, + {file = "mypy-1.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4e5175026618c178dfba6188228b845b64131034ab3ba52acaffa8f6c361f805"}, + {file = "mypy-1.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2f6ac8c87e046dc18c7d1d7f6653a66787a4555085b056fe2d599f1f1a2a2d21"}, + {file = "mypy-1.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7306edca1c6f1b5fa0bc9aa645e6ac8393014fa82d0fa180d0ebc990ebe15964"}, + {file = "mypy-1.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3cfad08f16a9c6611e6143485a93de0e1e13f48cfb90bcad7d5fde1c0cec3d36"}, + {file = "mypy-1.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67cced7f15654710386e5c10b96608f1ee3d5c94ca1da5a2aad5889793a824c1"}, + {file = "mypy-1.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a86b794e8a56ada65c573183756eac8ac5b8d3d59daf9d5ebd72ecdbb7867a43"}, + {file = "mypy-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:50979d5efff8d4135d9db293c6cb2c42260e70fb010cbc697b1311a4d7a39ddb"}, + {file = "mypy-1.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ae4c7a99e5153496243146a3baf33b9beff714464ca386b5f62daad601d87af"}, + {file = "mypy-1.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e398652d005a198a7f3c132426b33c6b85d98aa7dc852137a2a3be8890c4072"}, + {file = "mypy-1.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be78077064d016bc1b639c2cbcc5be945b47b4261a4f4b7d8923f6c69c5c9457"}, + {file = "mypy-1.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92024447a339400ea00ac228369cd242e988dd775640755fa4ac0c126e49bb74"}, + {file = "mypy-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:fe523fcbd52c05040c7bee370d66fee8373c5972171e4fbc323153433198592d"}, + {file = "mypy-1.0.0-py3-none-any.whl", hash = "sha256:2efa963bdddb27cb4a0d42545cd137a8d2b883bd181bbc4525b568ef6eca258f"}, + {file = "mypy-1.0.0.tar.gz", hash = "sha256:f34495079c8d9da05b183f9f7daec2878280c2ad7cc81da686ef0b484cea2ecf"}, ] [package.dependencies] @@ -1985,4 +1981,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "077b0abda1e0e5ffc7286738a63098606e3bd4650f7bb2569efe16cc106e055e" +content-hash = "b200bd8e190a3e7410d91b29289e29149a8aa29fa633e344aed929ff16d7cd11" diff --git a/pyproject.toml b/pyproject.toml index c45c01a2e67..618693f4332 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,7 +106,7 @@ pytest-xdist = { version = "^3.1", extras = ["psutil"] } zipp = { version = "^3.4", python = "<3.8" } [tool.poetry.group.typing.dependencies] -mypy = ">=0.990" +mypy = ">=1.0" types-html5lib = ">=1.1.9" types-jsonschema = ">=4.9.0" types-requests = ">=2.28.8" diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index b1c400e1042..0393351c1d5 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -240,8 +240,9 @@ def handle(self) -> int: ) content = layout_.generate_poetry_content() - for section in content: - pyproject.data.append(section, content[section]) + for section, item in content.items(): + pyproject.data.append(section, item) + if self.io.is_interactive(): self.line("Generated file") self.line("") diff --git a/src/poetry/layouts/layout.py b/src/poetry/layouts/layout.py index 66b5ea946ca..74dcebfedaa 100644 --- a/src/poetry/layouts/layout.py +++ b/src/poetry/layouts/layout.py @@ -194,6 +194,6 @@ def _create_tests(path: Path) -> None: def _write_poetry(self, path: Path) -> None: pyproject = PyProjectTOML(path / "pyproject.toml") content = self.generate_poetry_content() - for section in content: - pyproject.data.append(section, content[section]) + for section, item in content.items(): + pyproject.data.append(section, item) pyproject.save() From 3ed013392cafeab1341c08599d1e22d5186bd4b9 Mon Sep 17 00:00:00 2001 From: Marek Grzenkowicz Date: Tue, 7 Feb 2023 06:32:55 +0100 Subject: [PATCH 089/151] installer: write spec-compliant direct_url.json for file and url dependencies (#7475) drop deprecated `hash` in favor of `hashes` * https://packaging.python.org/en/latest/specifications/direct-url/ supersedes PEP 610 * keeping `hash` for compatibility probably does not make sense since it was not spec-compliant (":" instead of "=" as separator between algorithm and value) --- src/poetry/installation/executor.py | 41 ++++++++++++++++++---------- tests/installation/test_executor.py | 42 ++++++++++++++++++++++++----- 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index 6ba544eb40d..a4ca1238cee 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -572,6 +572,8 @@ def _prepare_archive(self, operation: Install | Update) -> Path: if not Path(package.source_url).is_absolute() and package.root_dir: archive = package.root_dir / archive + self._populate_hashes_dict(archive, package) + return self._chef.prepare(archive, editable=package.develop) def _prepare_directory_archive(self, operation: Install | Update) -> Path: @@ -734,13 +736,15 @@ def _download_link(self, operation: Install | Update, link: Link) -> Path: archive = self._chef.prepare(archive, output_dir=output_dir) + self._populate_hashes_dict(archive, package) + + return archive + + def _populate_hashes_dict(self, archive: Path, package: Package) -> None: if package.files and archive.name in {f["file"] for f in package.files}: archive_hash = self._validate_archive_hash(archive, package) - self._hashes[package.name] = archive_hash - return archive - @staticmethod def _validate_archive_hash(archive: Path, package: Package) -> str: archive_hash: str = "sha256:" + get_file_hash(archive) @@ -874,20 +878,12 @@ def _create_git_url_reference(self, package: Package) -> dict[str, Any]: return reference def _create_url_url_reference(self, package: Package) -> dict[str, Any]: - archive_info = {} - - if package.name in self._hashes: - archive_info["hash"] = self._hashes[package.name] - - reference = {"url": package.source_url, "archive_info": archive_info} + archive_info = self._get_archive_info(package) - return reference + return {"url": package.source_url, "archive_info": archive_info} def _create_file_url_reference(self, package: Package) -> dict[str, Any]: - archive_info = {} - - if package.name in self._hashes: - archive_info["hash"] = self._hashes[package.name] + archive_info = self._get_archive_info(package) assert package.source_url is not None return { @@ -906,3 +902,20 @@ def _create_directory_url_reference(self, package: Package) -> dict[str, Any]: "url": Path(package.source_url).as_uri(), "dir_info": dir_info, } + + def _get_archive_info(self, package: Package) -> dict[str, Any]: + """ + Create dictionary `archive_info` for file `direct_url.json`. + + Specification: https://packaging.python.org/en/latest/specifications/direct-url + (it supersedes PEP 610) + + :param package: This must be a poetry package instance. + """ + archive_info = {} + + if package.name in self._hashes: + algorithm, value = self._hashes[package.name].split(":") + archive_info["hashes"] = {algorithm: value} + + return archive_info diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index c6277182f32..9ede4a63c5f 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -622,12 +622,27 @@ def test_executor_should_write_pep610_url_references_for_files( .resolve() ) package = Package("demo", "0.1.0", source_type="file", source_url=url.as_posix()) + # Set package.files so the executor will attempt to hash the package + package.files = [ + { + "file": "demo-0.1.0-py2.py3-none-any.whl", + "hash": "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a", # noqa: E501 + } + ] executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) - verify_installed_distribution( - tmp_venv, package, {"archive_info": {}, "url": url.as_uri()} - ) + expected_url_reference = { + "archive_info": { + "hashes": { + "sha256": ( + "70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a" + ) + }, + }, + "url": url.as_uri(), + } + verify_installed_distribution(tmp_venv, package, expected_url_reference) def test_executor_should_write_pep610_url_references_for_directories( @@ -701,12 +716,27 @@ def test_executor_should_write_pep610_url_references_for_urls( source_type="url", source_url="https://files.pythonhosted.org/demo-0.1.0-py2.py3-none-any.whl", ) + # Set package.files so the executor will attempt to hash the package + package.files = [ + { + "file": "demo-0.1.0-py2.py3-none-any.whl", + "hash": "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a", # noqa: E501 + } + ] executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) - verify_installed_distribution( - tmp_venv, package, {"archive_info": {}, "url": package.source_url} - ) + expected_url_reference = { + "archive_info": { + "hashes": { + "sha256": ( + "70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a" + ) + }, + }, + "url": package.source_url, + } + verify_installed_distribution(tmp_venv, package, expected_url_reference) def test_executor_should_write_pep610_url_references_for_git( From 8558f0cb94c11d5d1f901900d4a6ea8a8167bd35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sat, 4 Feb 2023 15:45:43 +0100 Subject: [PATCH 090/151] chore: remove deprecated repository modules --- src/poetry/repositories/__init__.py | 3 +-- src/poetry/repositories/cached.py | 18 ------------------ src/poetry/repositories/http.py | 17 ----------------- src/poetry/repositories/pool.py | 29 ----------------------------- tests/installation/test_executor.py | 2 +- 5 files changed, 2 insertions(+), 67 deletions(-) delete mode 100644 src/poetry/repositories/cached.py delete mode 100644 src/poetry/repositories/http.py delete mode 100644 src/poetry/repositories/pool.py diff --git a/src/poetry/repositories/__init__.py b/src/poetry/repositories/__init__.py index 78c59562ab8..d923a9691aa 100644 --- a/src/poetry/repositories/__init__.py +++ b/src/poetry/repositories/__init__.py @@ -1,8 +1,7 @@ from __future__ import annotations -from poetry.repositories.pool import Pool from poetry.repositories.repository import Repository from poetry.repositories.repository_pool import RepositoryPool -__all__ = ["Pool", "Repository", "RepositoryPool"] +__all__ = ["Repository", "RepositoryPool"] diff --git a/src/poetry/repositories/cached.py b/src/poetry/repositories/cached.py deleted file mode 100644 index 2686cf7db90..00000000000 --- a/src/poetry/repositories/cached.py +++ /dev/null @@ -1,18 +0,0 @@ -from __future__ import annotations - -import warnings - - -from poetry.repositories.cached_repository import ( # isort: skip # nopycln: import # noqa: E501, F401 - CachedRepository, -) - -warnings.warn( - ( - "Module poetry.repositories.cached is renamed and scheduled for removal in" - " poetry release 1.4.0. Please migrate to" - " poetry.repositories.cached_repository." - ), - DeprecationWarning, - stacklevel=2, -) diff --git a/src/poetry/repositories/http.py b/src/poetry/repositories/http.py deleted file mode 100644 index 94e89980b1f..00000000000 --- a/src/poetry/repositories/http.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import annotations - -import warnings - - -from poetry.repositories.http_repository import ( # isort: skip # nopycln: import # noqa: E501, F401 - HTTPRepository, -) - -warnings.warn( - ( - "Module poetry.repositories.http is renamed and scheduled for removal in poetry" - " release 1.4.0. Please migrate to poetry.repositories.http_repository." - ), - DeprecationWarning, - stacklevel=2, -) diff --git a/src/poetry/repositories/pool.py b/src/poetry/repositories/pool.py deleted file mode 100644 index 48ccac36e75..00000000000 --- a/src/poetry/repositories/pool.py +++ /dev/null @@ -1,29 +0,0 @@ -from __future__ import annotations - -import warnings - -from typing import TYPE_CHECKING - -from poetry.repositories.repository_pool import RepositoryPool - - -if TYPE_CHECKING: - from poetry.repositories.repository import Repository - - -class Pool(RepositoryPool): - def __init__( - self, - repositories: list[Repository] | None = None, - ignore_repository_names: bool = False, - ) -> None: - warnings.warn( - ( - "Object Pool from poetry.repositories.pool is renamed and scheduled for" - " removal in poetry release 1.4.0. Please migrate to RepositoryPool" - " from poetry.repositories.repository_pool." - ), - DeprecationWarning, - stacklevel=2, - ) - super().__init__(repositories, ignore_repository_names) diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 9ede4a63c5f..b0e02443042 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -30,7 +30,7 @@ from poetry.installation.operations import Uninstall from poetry.installation.operations import Update from poetry.installation.wheel_installer import WheelInstaller -from poetry.repositories.pool import RepositoryPool +from poetry.repositories.repository_pool import RepositoryPool from poetry.utils.env import MockEnv from tests.repositories.test_pypi_repository import MockRepository From e92de61ee01c8676a23b10189d94890931ad166a Mon Sep 17 00:00:00 2001 From: David Hotham Date: Mon, 13 Feb 2023 17:05:48 +0000 Subject: [PATCH 091/151] don't set shell=True on subprocess calls (#7471) --- src/poetry/utils/_compat.py | 8 ---- src/poetry/utils/env.py | 55 +++++++-------------------- tests/console/commands/env/helpers.py | 19 ++++----- tests/utils/test_env.py | 23 +++++------ 4 files changed, 36 insertions(+), 69 deletions(-) diff --git a/src/poetry/utils/_compat.py b/src/poetry/utils/_compat.py index 1a37ad13e8b..5887436e530 100644 --- a/src/poetry/utils/_compat.py +++ b/src/poetry/utils/_compat.py @@ -60,19 +60,11 @@ def to_str(string: str) -> str: return decode(string) -def list_to_shell_command(cmd: list[str]) -> str: - return " ".join( - f'"{token}"' if " " in token and token[0] not in {"'", '"'} else token - for token in cmd - ) - - __all__ = [ "WINDOWS", "cached_property", "decode", "encode", - "list_to_shell_command", "metadata", "to_str", "tomllib", diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 70f4b9b4772..ac4bc547c9e 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -40,7 +40,6 @@ from poetry.utils._compat import WINDOWS from poetry.utils._compat import decode from poetry.utils._compat import encode -from poetry.utils._compat import list_to_shell_command from poetry.utils._compat import metadata from poetry.utils.helpers import get_real_windows_path from poetry.utils.helpers import is_dir_writable @@ -171,9 +170,9 @@ def _version_nodot(version): """ GET_PYTHON_VERSION_ONELINER = ( - "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"" + "import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))" ) -GET_ENV_PATH_ONELINER = '"import sys; print(sys.prefix)"' +GET_ENV_PATH_ONELINER = "import sys; print(sys.prefix)" GET_SYS_PATH = """\ import json @@ -522,10 +521,7 @@ def _full_python_path(python: str) -> str: try: executable = decode( subprocess.check_output( - list_to_shell_command( - [python, "-c", '"import sys; print(sys.executable)"'] - ), - shell=True, + [python, "-c", "import sys; print(sys.executable)"], ).strip() ) except CalledProcessError as e: @@ -572,10 +568,7 @@ def get_python_version( if executable: python_patch = decode( subprocess.check_output( - list_to_shell_command( - [executable, "-c", GET_PYTHON_VERSION_ONELINER] - ), - shell=True, + [executable, "-c", GET_PYTHON_VERSION_ONELINER], ).strip() ) @@ -603,8 +596,7 @@ def activate(self, python: str) -> Env: try: python_version_string = decode( subprocess.check_output( - list_to_shell_command([python, "-c", GET_PYTHON_VERSION_ONELINER]), - shell=True, + [python, "-c", GET_PYTHON_VERSION_ONELINER], ) ) except CalledProcessError as e: @@ -799,8 +791,7 @@ def remove(self, python: str) -> Env: try: env_dir = decode( subprocess.check_output( - list_to_shell_command([python, "-c", GET_ENV_PATH_ONELINER]), - shell=True, + [python, "-c", GET_ENV_PATH_ONELINER], ) ).strip("\n") env_name = Path(env_dir).name @@ -859,8 +850,7 @@ def remove(self, python: str) -> Env: try: python_version_string = decode( subprocess.check_output( - list_to_shell_command([python, "-c", GET_PYTHON_VERSION_ONELINER]), - shell=True, + [python, "-c", GET_PYTHON_VERSION_ONELINER], ) ) except CalledProcessError as e: @@ -935,10 +925,7 @@ def create_venv( if executable: python_patch = decode( subprocess.check_output( - list_to_shell_command( - [executable, "-c", GET_PYTHON_VERSION_ONELINER] - ), - shell=True, + [executable, "-c", GET_PYTHON_VERSION_ONELINER], ).strip() ) python_minor = ".".join(python_patch.split(".")[:2]) @@ -985,11 +972,8 @@ def create_venv( try: python_patch = decode( subprocess.check_output( - list_to_shell_command( - [python, "-c", GET_PYTHON_VERSION_ONELINER] - ), + [python, "-c", GET_PYTHON_VERSION_ONELINER], stderr=subprocess.STDOUT, - shell=True, ).strip() ) except CalledProcessError: @@ -1531,18 +1515,9 @@ def _run(self, cmd: list[str], **kwargs: Any) -> int | str: env = kwargs.pop("env", dict(os.environ)) try: - if self._is_windows: - kwargs["shell"] = True - - command: str | list[str] - if kwargs.get("shell", False): - command = list_to_shell_command(cmd) - else: - command = cmd - if input_: output = subprocess.run( - command, + cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, input=encode(input_), @@ -1550,12 +1525,10 @@ def _run(self, cmd: list[str], **kwargs: Any) -> int | str: **kwargs, ).stdout elif call: - return subprocess.call( - command, stderr=subprocess.STDOUT, env=env, **kwargs - ) + return subprocess.call(cmd, stderr=subprocess.STDOUT, env=env, **kwargs) else: output = subprocess.check_output( - command, stderr=subprocess.STDOUT, env=env, **kwargs + cmd, stderr=subprocess.STDOUT, env=env, **kwargs ) except CalledProcessError as e: raise EnvCommandError(e, input=input_) @@ -1570,7 +1543,7 @@ def execute(self, bin: str, *args: str, **kwargs: Any) -> int: return os.execvpe(command[0], command, env=env) kwargs["shell"] = True - exe = subprocess.Popen([command[0]] + command[1:], env=env, **kwargs) + exe = subprocess.Popen(command, env=env, **kwargs) exe.communicate() return exe.returncode @@ -1909,7 +1882,7 @@ def execute(self, bin: str, *args: str, **kwargs: Any) -> int: if not self._is_windows: return os.execvpe(command[0], command, env=env) - exe = subprocess.Popen([command[0]] + command[1:], env=env, **kwargs) + exe = subprocess.Popen(command, env=env, **kwargs) exe.communicate() return exe.returncode diff --git a/tests/console/commands/env/helpers.py b/tests/console/commands/env/helpers.py index 0bf94128154..0a067b3c430 100644 --- a/tests/console/commands/env/helpers.py +++ b/tests/console/commands/env/helpers.py @@ -10,8 +10,6 @@ if TYPE_CHECKING: from collections.abc import Callable - from poetry.core.version.pep440.version import PEP440Version - VERSION_3_7_1 = Version.parse("3.7.1") @@ -20,16 +18,19 @@ def build_venv(path: Path | str, **_: Any) -> None: def check_output_wrapper( - version: PEP440Version = VERSION_3_7_1, -) -> Callable[[str, Any, Any], str]: - def check_output(cmd: str, *_: Any, **__: Any) -> str: - if "sys.version_info[:3]" in cmd: + version: Version = VERSION_3_7_1, +) -> Callable[[list[str], Any, Any], str]: + def check_output(cmd: list[str], *args: Any, **kwargs: Any) -> str: + # cmd is a list, like ["python", "-c", "do stuff"] + python_cmd = cmd[2] + if "sys.version_info[:3]" in python_cmd: return version.text - elif "sys.version_info[:2]" in cmd: + elif "sys.version_info[:2]" in python_cmd: return f"{version.major}.{version.minor}" - elif '-c "import sys; print(sys.executable)"' in cmd: - return f"/usr/bin/{cmd.split()[0]}" + elif "import sys; print(sys.executable)" in python_cmd: + return f"/usr/bin/{cmd[0]}" else: + assert "import sys; print(sys.prefix)" in python_cmd return str(Path("/prefix")) return check_output diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 1d9f6c3ba63..86bf4dfbc50 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -94,7 +94,7 @@ def test_virtualenvs_with_spaces_in_their_path_work_as_expected( venv = VirtualEnv(venv_path) - assert venv.run("python", "-V", shell=True).startswith("Python") + assert venv.run("python", "-V").startswith("Python") @pytest.mark.skipif(sys.platform != "darwin", reason="requires darwin") @@ -124,7 +124,7 @@ def test_env_commands_with_spaces_in_their_arg_work_as_expected( venv_path = Path(tmp_dir) / "Virtual Env" manager.build_venv(str(venv_path)) venv = VirtualEnv(venv_path) - assert venv.run("python", venv.pip, "--version", shell=True).startswith( + assert venv.run("python", venv.pip, "--version").startswith( f"pip {venv.pip_version} from " ) @@ -135,9 +135,7 @@ def test_env_shell_commands_with_stdinput_in_their_arg_work_as_expected( venv_path = Path(tmp_dir) / "Virtual Env" manager.build_venv(str(venv_path)) venv = VirtualEnv(venv_path) - run_output_path = Path( - venv.run("python", "-", input_=GET_BASE_PREFIX, shell=True).strip() - ) + run_output_path = Path(venv.run("python", "-", input_=GET_BASE_PREFIX).strip()) venv_base_prefix_path = Path(str(venv.get_base_prefix())) assert run_output_path.resolve() == venv_base_prefix_path.resolve() @@ -189,15 +187,18 @@ def build_venv(path: Path | str, **__: Any) -> None: def check_output_wrapper( version: Version = VERSION_3_7_1, -) -> Callable[[str, Any, Any], str]: - def check_output(cmd: str, *args: Any, **kwargs: Any) -> str: - if "sys.version_info[:3]" in cmd: +) -> Callable[[list[str], Any, Any], str]: + def check_output(cmd: list[str], *args: Any, **kwargs: Any) -> str: + # cmd is a list, like ["python", "-c", "do stuff"] + python_cmd = cmd[2] + if "sys.version_info[:3]" in python_cmd: return version.text - elif "sys.version_info[:2]" in cmd: + elif "sys.version_info[:2]" in python_cmd: return f"{version.major}.{version.minor}" - elif '-c "import sys; print(sys.executable)"' in cmd: - return f"/usr/bin/{cmd.split()[0]}" + elif "import sys; print(sys.executable)" in python_cmd: + return f"/usr/bin/{cmd[0]}" else: + assert "import sys; print(sys.prefix)" in python_cmd return str(Path("/prefix")) return check_output From 012fcb99b8b7561ccbe138f6097cfbd0d96bb39c Mon Sep 17 00:00:00 2001 From: Jerome Leclanche Date: Wed, 15 Feb 2023 17:22:21 +0100 Subject: [PATCH 092/151] env: do not mix stdout and stderr when running Python scripts and parsing the output (#6665) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Randy Döring <30527984+radoering@users.noreply.github.com> --- src/poetry/utils/env.py | 35 ++++++++++++++-------- tests/utils/test_env.py | 66 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 83 insertions(+), 18 deletions(-) diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index ac4bc547c9e..1c01a455c48 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -463,13 +463,16 @@ class EnvCommandError(EnvError): def __init__(self, e: CalledProcessError, input: str | None = None) -> None: self.e = e - message = ( - f"Command {e.cmd} errored with the following return code {e.returncode}," - f" and output: \n{decode(e.output)}" - ) + message_parts = [ + f"Command {e.cmd} errored with the following return code {e.returncode}" + ] + if e.output: + message_parts.append(f"Output:\n{decode(e.output)}") + if e.stderr: + message_parts.append(f"Error output:\n{decode(e.stderr)}") if input: - message += f"input was : {input}" - super().__init__(message) + message_parts.append(f"Input:\n{input}") + super().__init__("\n\n".join(message_parts)) class NoCompatiblePythonVersionFound(EnvError): @@ -1503,7 +1506,14 @@ def run_pip(self, *args: str, **kwargs: Any) -> int | str: def run_python_script(self, content: str, **kwargs: Any) -> int | str: return self.run( - self._executable, "-I", "-W", "ignore", "-", input_=content, **kwargs + self._executable, + "-I", + "-W", + "ignore", + "-", + input_=content, + stderr=subprocess.PIPE, + **kwargs, ) def _run(self, cmd: list[str], **kwargs: Any) -> int | str: @@ -1513,23 +1523,24 @@ def _run(self, cmd: list[str], **kwargs: Any) -> int | str: call = kwargs.pop("call", False) input_ = kwargs.pop("input_", None) env = kwargs.pop("env", dict(os.environ)) + stderr = kwargs.pop("stderr", subprocess.STDOUT) try: if input_: output = subprocess.run( cmd, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, + stderr=stderr, input=encode(input_), check=True, **kwargs, ).stdout elif call: - return subprocess.call(cmd, stderr=subprocess.STDOUT, env=env, **kwargs) - else: - output = subprocess.check_output( - cmd, stderr=subprocess.STDOUT, env=env, **kwargs + return subprocess.call( + cmd, stdout=subprocess.PIPE, stderr=stderr, env=env, **kwargs ) + else: + output = subprocess.check_output(cmd, stderr=stderr, env=env, **kwargs) except CalledProcessError as e: raise EnvCommandError(e, input=input_) diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 86bf4dfbc50..eb7b2ec59eb 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -947,35 +947,89 @@ def test_run_with_called_process_error( tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture ): mocker.patch( - "subprocess.run", side_effect=subprocess.CalledProcessError(42, "some_command") + "subprocess.run", + side_effect=subprocess.CalledProcessError( + 42, "some_command", "some output", "some error" + ), ) - with pytest.raises(EnvCommandError): + with pytest.raises(EnvCommandError) as error: tmp_venv.run("python", "-", input_=MINIMAL_SCRIPT) subprocess.run.assert_called_once() + assert "some output" in str(error.value) + assert "some error" in str(error.value) def test_call_with_input_and_called_process_error( tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture ): mocker.patch( - "subprocess.run", side_effect=subprocess.CalledProcessError(42, "some_command") + "subprocess.run", + side_effect=subprocess.CalledProcessError( + 42, "some_command", "some output", "some error" + ), ) kwargs = {"call": True} - with pytest.raises(EnvCommandError): + with pytest.raises(EnvCommandError) as error: tmp_venv.run("python", "-", input_=MINIMAL_SCRIPT, **kwargs) subprocess.run.assert_called_once() + assert "some output" in str(error.value) + assert "some error" in str(error.value) def test_call_no_input_with_called_process_error( tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture ): mocker.patch( - "subprocess.call", side_effect=subprocess.CalledProcessError(42, "some_command") + "subprocess.call", + side_effect=subprocess.CalledProcessError( + 42, "some_command", "some output", "some error" + ), ) kwargs = {"call": True} - with pytest.raises(EnvCommandError): + with pytest.raises(EnvCommandError) as error: tmp_venv.run("python", "-", **kwargs) subprocess.call.assert_called_once() + assert "some output" in str(error.value) + assert "some error" in str(error.value) + + +def test_check_output_with_called_process_error( + tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture +): + mocker.patch( + "subprocess.check_output", + side_effect=subprocess.CalledProcessError( + 42, "some_command", "some output", "some error" + ), + ) + with pytest.raises(EnvCommandError) as error: + tmp_venv.run("python", "-") + subprocess.check_output.assert_called_once() + assert "some output" in str(error.value) + assert "some error" in str(error.value) + + +def test_run_python_script_called_process_error( + tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture +): + mocker.patch( + "subprocess.run", + side_effect=subprocess.CalledProcessError( + 42, "some_command", "some output", "some error" + ), + ) + with pytest.raises(EnvCommandError) as error: + tmp_venv.run_python_script(MINIMAL_SCRIPT) + assert "some output" in str(error.value) + assert "some error" in str(error.value) + + +def test_run_python_script_only_stdout(tmp_dir: str, tmp_venv: VirtualEnv): + output = tmp_venv.run_python_script( + "import sys; print('some warning', file=sys.stderr); print('some output')" + ) + assert "some output" in output + assert "some warning" not in output def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ones_first( # noqa: E501 From b6d7723acf2af2e9c9e02a3a60aa7790fc7b0beb Mon Sep 17 00:00:00 2001 From: "Y.D.X" <73375426+YDX-2147483647@users.noreply.github.com> Date: Thu, 16 Feb 2023 00:36:18 +0800 Subject: [PATCH 093/151] docs: fix a typo (#7425) --- docs/pre-commit-hooks.md | 2 +- docs/pyproject.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pre-commit-hooks.md b/docs/pre-commit-hooks.md index 7fa16e8e896..5fcbd62ef27 100644 --- a/docs/pre-commit-hooks.md +++ b/docs/pre-commit-hooks.md @@ -106,7 +106,7 @@ repos: `pre-commit autoupdate` updates the `rev` for each repository defined in your `.pre-commit-config.yaml` to the latest available tag in the default branch. -Poetry follows a branching strategy, where the default branch is the active developement branch +Poetry follows a branching strategy, where the default branch is the active development branch and fixes gets back ported to stable branches. New tags are assigned in these stable branches. `pre-commit` does not support such a branching strategy and has decided to not implement diff --git a/docs/pyproject.md b/docs/pyproject.md index 955c64d4c6a..5149515df8c 100644 --- a/docs/pyproject.md +++ b/docs/pyproject.md @@ -208,7 +208,7 @@ packages = [ ] ``` -If you want to restrict a package to a specific [build](#build) format you can specify +If you want to restrict a package to a specific build format you can specify it by using `format`: ```toml From 852f7a871c88d8f93162714e16ed8c5d6f400222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juliano=20Lagan=C3=A1?= Date: Wed, 15 Feb 2023 17:38:50 +0100 Subject: [PATCH 094/151] docs: fix a typo (#7455) --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 212341232bb..cc477f22609 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -12,7 +12,7 @@ menu: # Configuration Poetry can be configured via the `config` command ([see more about its usage here]({{< relref "cli#config" >}} "config command documentation")) -or directly in the `config.toml` file that will be automatically be created when you first run that command. +or directly in the `config.toml` file that will be automatically created when you first run that command. This file can typically be found in one of the following directories: - macOS: `~/Library/Preferences/pypoetry` From b304b0d56ee20f3240fd7de36f130e84313a70d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20Marc=C3=A8=20i=20Igual?= Date: Thu, 16 Feb 2023 14:20:47 +0100 Subject: [PATCH 095/151] Fix year in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a97e31cbe48..c8575058841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log -## [1.3.2] - 2022-01-10 +## [1.3.2] - 2023-01-10 ### Fixed From c8945eb110aeda611cc6721565d7ad0c657d453a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sun, 15 Jan 2023 17:16:17 +0100 Subject: [PATCH 096/151] installer: deprecate old installer (setting `experimental.new-installer` to false) --- src/poetry/console/application.py | 5 +++- src/poetry/console/commands/install.py | 7 +++--- src/poetry/console/commands/lock.py | 7 +++--- src/poetry/console/commands/update.py | 7 +++--- src/poetry/installation/installer.py | 20 +++++++++++++++- src/poetry/utils/env.py | 22 +++++++++--------- tests/console/commands/test_add.py | 3 ++- tests/console/conftest.py | 1 - tests/installation/test_installer.py | 21 ----------------- tests/installation/test_installer_old.py | 5 ++++ ...ortlib_metadata-1.7.0-py2.py3-none-any.whl | Bin 0 -> 31809 bytes .../dists/zipp-3.5.0-py3-none-any.whl | Bin 0 -> 5700 bytes 12 files changed, 53 insertions(+), 45 deletions(-) create mode 100644 tests/repositories/fixtures/pypi.org/dists/importlib_metadata-1.7.0-py2.py3-none-any.whl create mode 100644 tests/repositories/fixtures/pypi.org/dists/zipp-3.5.0-py3-none-any.whl diff --git a/src/poetry/console/application.py b/src/poetry/console/application.py index 2dd422573fa..8c246b369bf 100644 --- a/src/poetry/console/application.py +++ b/src/poetry/console/application.py @@ -335,7 +335,10 @@ def configure_installer_for_command(command: InstallerCommand, io: IO) -> None: poetry.config, disable_cache=poetry.disable_cache, ) - installer.use_executor(poetry.config.get("experimental.new-installer", False)) + use_executor = poetry.config.get("experimental.new-installer", False) + if not use_executor: + # only set if false because the method is deprecated + installer.use_executor(False) command.set_installer(installer) def _load_plugins(self, io: IO | None = None) -> None: diff --git a/src/poetry/console/commands/install.py b/src/poetry/console/commands/install.py index 453feaf55d0..844ec37176f 100644 --- a/src/poetry/console/commands/install.py +++ b/src/poetry/console/commands/install.py @@ -95,9 +95,10 @@ def handle(self) -> int: from poetry.masonry.builders.editable import EditableBuilder - self.installer.use_executor( - self.poetry.config.get("experimental.new-installer", False) - ) + use_executor = self.poetry.config.get("experimental.new-installer", False) + if not use_executor: + # only set if false because the method is deprecated + self.installer.use_executor(False) if self.option("extras") and self.option("all-extras"): self.line_error( diff --git a/src/poetry/console/commands/lock.py b/src/poetry/console/commands/lock.py index d1009a265e8..2d74c109fba 100644 --- a/src/poetry/console/commands/lock.py +++ b/src/poetry/console/commands/lock.py @@ -35,9 +35,10 @@ class LockCommand(InstallerCommand): loggers = ["poetry.repositories.pypi_repository"] def handle(self) -> int: - self.installer.use_executor( - self.poetry.config.get("experimental.new-installer", False) - ) + use_executor = self.poetry.config.get("experimental.new-installer", False) + if not use_executor: + # only set if false because the method is deprecated + self.installer.use_executor(False) if self.option("check"): if self.poetry.locker.is_locked() and self.poetry.locker.is_fresh(): diff --git a/src/poetry/console/commands/update.py b/src/poetry/console/commands/update.py index 714a924e5bd..955438a5474 100644 --- a/src/poetry/console/commands/update.py +++ b/src/poetry/console/commands/update.py @@ -41,9 +41,10 @@ class UpdateCommand(InstallerCommand): def handle(self) -> int: packages = self.argument("packages") - self.installer.use_executor( - self.poetry.config.get("experimental.new-installer", False) - ) + use_executor = self.poetry.config.get("experimental.new-installer", False) + if not use_executor: + # only set if false because the method is deprecated + self.installer.use_executor(False) if packages: self.installer.whitelist({name: "*" for name in packages}) diff --git a/src/poetry/installation/installer.py b/src/poetry/installation/installer.py index 9b906f7678f..b5aa54f7072 100644 --- a/src/poetry/installation/installer.py +++ b/src/poetry/installation/installer.py @@ -1,5 +1,7 @@ from __future__ import annotations +import warnings + from typing import TYPE_CHECKING from cleo.io.null_io import NullIO @@ -71,7 +73,7 @@ def __init__( ) self._executor = executor - self._use_executor = False + self._use_executor = True self._installer = self._get_installer() if installed is None: @@ -180,6 +182,14 @@ def extras(self, extras: list[str]) -> Installer: return self def use_executor(self, use_executor: bool = True) -> Installer: + warnings.warn( + ( + "Calling use_executor() is deprecated since it's true by default now" + " and deactivating it will be removed in a future release." + ), + DeprecationWarning, + stacklevel=2, + ) self._use_executor = use_executor return self @@ -366,6 +376,14 @@ def _execute(self, operations: list[Operation]) -> int: if self._use_executor: return self._executor.execute(operations) + self._io.write_error( + "" + "Setting `experimental.new-installer` to false is deprecated and" + " slated for removal in an upcoming minor release.\n" + "(Despite of the setting's name the new installer is not experimental!)" + "" + ) + if not operations and (self._execute_operations or self._dry_run): self._io.write_line("No dependencies to install or update") diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 1c01a455c48..8d897fd2ac9 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -1917,6 +1917,17 @@ def __init__( self._execute = execute self.executed: list[list[str]] = [] + @property + def paths(self) -> dict[str, str]: + if self._paths is None: + self._paths = self.get_paths() + self._paths["platlib"] = str(self._path / "platlib") + self._paths["purelib"] = str(self._path / "purelib") + self._paths["scripts"] = str(self._path / "scripts") + self._paths["data"] = str(self._path / "data") + + return self._paths + def _run(self, cmd: list[str], **kwargs: Any) -> int | str: self.executed.append(cmd) @@ -2044,17 +2055,6 @@ def sys_path(self) -> list[str]: return self._sys_path - @property - def paths(self) -> dict[str, str]: - if self._paths is None: - self._paths = self.get_paths() - self._paths["platlib"] = str(self._path / "platlib") - self._paths["purelib"] = str(self._path / "purelib") - self._paths["scripts"] = str(self._path / "scripts") - self._paths["data"] = str(self._path / "data") - - return self._paths - def get_marker_env(self) -> dict[str, Any]: if self._mock_marker_env is not None: return self._mock_marker_env diff --git a/tests/console/commands/test_add.py b/tests/console/commands/test_add.py index a654e835579..b7ffde8ae1b 100644 --- a/tests/console/commands/test_add.py +++ b/tests/console/commands/test_add.py @@ -50,7 +50,8 @@ def tester(command_tester_factory: CommandTesterFactory) -> CommandTester: @pytest.fixture() def old_tester(tester: CommandTester) -> CommandTester: - tester.command.installer.use_executor(False) + with pytest.warns(DeprecationWarning): + tester.command.installer.use_executor(False) return tester diff --git a/tests/console/conftest.py b/tests/console/conftest.py index a78ceaa89f9..eed6665335e 100644 --- a/tests/console/conftest.py +++ b/tests/console/conftest.py @@ -166,7 +166,6 @@ def _tester( executor=executor or TestExecutor(env, poetry.pool, poetry.config, tester.io), ) - installer.use_executor(True) command.set_installer(installer) return tester diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 70761e48ebf..76e11ecd611 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -197,8 +197,6 @@ def installer( installed=installed, executor=Executor(env, pool, config, NullIO()), ) - installer.use_executor(True) - return installer @@ -1961,8 +1959,6 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de installed=installed, executor=Executor(env, pool, config, NullIO()), ) - installer.use_executor() - installer.update(True) installer.whitelist(["D"]) installer.run() @@ -1996,7 +1992,6 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de installed=installed, executor=Executor(env, pool, config, NullIO()), ) - installer.use_executor() package.add_dependency(Factory.create_dependency("poetry", {"version": "^0.12.0"})) @@ -2025,8 +2020,6 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de installed=installed, executor=Executor(env, pool, config, NullIO()), ) - installer.use_executor() - installer.update(True) installer.whitelist(["pytest"]) installer.run() @@ -2057,8 +2050,6 @@ def test_installer_required_extras_should_be_installed( installed=installed, executor=Executor(env, pool, config, NullIO()), ) - installer.use_executor() - package.add_dependency( Factory.create_dependency( "cachecontrol", {"version": "^0.12.5", "extras": ["filecache"]} @@ -2085,8 +2076,6 @@ def test_installer_required_extras_should_be_installed( installed=installed, executor=Executor(env, pool, config, NullIO()), ) - installer.use_executor() - installer.update(True) installer.run() @@ -2200,8 +2189,6 @@ def test_installer_can_install_dependencies_from_forced_source( installed=installed, executor=Executor(env, pool, config, NullIO()), ) - installer.use_executor() - installer.update(True) installer.run() @@ -2267,7 +2254,6 @@ def test_run_installs_with_same_version_url_files( NullIO(), ), ) - installer.use_executor(True) installer.run() expected = fixture("with-same-version-url-dependencies") @@ -2332,8 +2318,6 @@ def test_installer_can_handle_old_lock_files( installed=installed, executor=Executor(MockEnv(), pool, config, NullIO()), ) - installer.use_executor() - installer.run() assert installer.executor.installations_count == 6 @@ -2353,8 +2337,6 @@ def test_installer_can_handle_old_lock_files( NullIO(), ), ) - installer.use_executor() - installer.run() # funcsigs will be added @@ -2375,8 +2357,6 @@ def test_installer_can_handle_old_lock_files( NullIO(), ), ) - installer.use_executor() - installer.run() # colorama will be added @@ -2640,7 +2620,6 @@ def test_installer_distinguishes_locked_packages_by_source( NullIO(), ), ) - installer.use_executor(True) installer.run() # Results of installation are consistent with the platform requirements. diff --git a/tests/installation/test_installer_old.py b/tests/installation/test_installer_old.py index 2b7bbc22afa..e76fabb21f3 100644 --- a/tests/installation/test_installer_old.py +++ b/tests/installation/test_installer_old.py @@ -4,6 +4,7 @@ from pathlib import Path from typing import TYPE_CHECKING +from typing import Any import pytest @@ -40,6 +41,10 @@ class Installer(BaseInstaller): + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self._use_executor = False + def _get_installer(self) -> NoopInstaller: return NoopInstaller() diff --git a/tests/repositories/fixtures/pypi.org/dists/importlib_metadata-1.7.0-py2.py3-none-any.whl b/tests/repositories/fixtures/pypi.org/dists/importlib_metadata-1.7.0-py2.py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..cfcb2db4d4ae038282bfb271113cd7cf25a32d95 GIT binary patch literal 31809 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 literal 0 HcmV?d00001 From a3f6437bb1d5868f8ee8a00499a6fe1deab5a7ff Mon Sep 17 00:00:00 2001 From: KotlinIsland Date: Sun, 12 Feb 2023 19:32:34 +1000 Subject: [PATCH 097/151] docs: mention the different types of exact specifications and pep440 --- docs/dependency-specification.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/dependency-specification.md b/docs/dependency-specification.md index 3e7f55f755f..85521383b87 100644 --- a/docs/dependency-specification.md +++ b/docs/dependency-specification.md @@ -79,11 +79,15 @@ Multiple version requirements can also be separated with a comma, e.g. `>= 1.2, You can specify the exact version of a package. -`==1.2.3` is an example of an exact version specification. +`1.2.3` is an example of an exact version specification. This will tell Poetry to install this version and this version only. If other dependencies require a different version, the solver will ultimately fail and abort any install or update procedures. +Exact versions can also be specified with `==` according to [PEP 440](https://peps.python.org/pep-0440/). + +`==1.2.3` is an example of this. + ### Using the `@` operator When adding dependencies via `poetry add`, you can use the `@` operator. From b6fb3a3945302021073219c6826320b3f5ce639e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Mon, 27 Feb 2023 06:25:01 +0100 Subject: [PATCH 098/151] fix: don't merge sources into config (#7560) --- src/poetry/console/commands/source/add.py | 6 +--- src/poetry/factory.py | 24 +++++++------- src/poetry/installation/chef.py | 15 ++++----- src/poetry/installation/executor.py | 2 +- tests/console/commands/self/conftest.py | 10 ++++-- tests/console/commands/test_config.py | 39 +++++++++++++++++++++++ tests/installation/test_chef.py | 11 ++++--- tests/installation/test_executor.py | 12 +++---- 8 files changed, 80 insertions(+), 39 deletions(-) diff --git a/src/poetry/console/commands/source/add.py b/src/poetry/console/commands/source/add.py index f258e404af0..81a0ca66e91 100644 --- a/src/poetry/console/commands/source/add.py +++ b/src/poetry/console/commands/source/add.py @@ -83,13 +83,9 @@ def handle(self) -> int: self.line(f"Adding source with name {name}.") sources.append(source_to_table(new_source)) - self.poetry.config.merge( - {"sources": {source["name"]: source for source in sources}} - ) - # ensure new source is valid. eg: invalid name etc. try: - pool = Factory.create_pool(self.poetry.config, NullIO()) + pool = Factory.create_pool(self.poetry.config, sources, NullIO()) pool.repository(name) except ValueError as e: self.line_error( diff --git a/src/poetry/factory.py b/src/poetry/factory.py index ff03718b48d..43960b43285 100644 --- a/src/poetry/factory.py +++ b/src/poetry/factory.py @@ -23,6 +23,7 @@ if TYPE_CHECKING: + from collections.abc import Iterable from pathlib import Path from cleo.io.io import IO @@ -89,16 +90,14 @@ def create_poetry( disable_cache, ) - # Configuring sources - config.merge( - { - "sources": { - source["name"]: source - for source in poetry.local_config.get("source", []) - } - } + poetry.set_pool( + self.create_pool( + config, + poetry.local_config.get("source", []), + io, + disable_cache=disable_cache, + ) ) - poetry.set_pool(self.create_pool(config, io, disable_cache=disable_cache)) plugin_manager = PluginManager(Plugin.group, disable_plugins=disable_plugins) plugin_manager.load_plugins() @@ -114,7 +113,8 @@ def get_package(cls, name: str, version: str) -> ProjectPackage: @classmethod def create_pool( cls, - config: Config, + auth_config: Config, + sources: Iterable[dict[str, Any]] = (), io: IO | None = None, disable_cache: bool = False, ) -> RepositoryPool: @@ -128,9 +128,9 @@ def create_pool( pool = RepositoryPool() - for source in config.get("sources", {}).values(): + for source in sources: repository = cls.create_package_source( - source, config, disable_cache=disable_cache + source, auth_config, disable_cache=disable_cache ) is_default = source.get("default", False) is_secondary = source.get("secondary", False) diff --git a/src/poetry/installation/chef.py b/src/poetry/installation/chef.py index 2a4c02c7451..d3f7b09d38e 100644 --- a/src/poetry/installation/chef.py +++ b/src/poetry/installation/chef.py @@ -30,6 +30,7 @@ from poetry.core.packages.utils.link import Link from poetry.config.config import Config + from poetry.repositories import RepositoryPool from poetry.utils.env import Env @@ -42,9 +43,9 @@ class ChefBuildError(ChefError): class IsolatedEnv(BaseIsolatedEnv): - def __init__(self, env: Env, config: Config) -> None: + def __init__(self, env: Env, pool: RepositoryPool) -> None: self._env = env - self._config = config + self._pool = pool @property def executable(self) -> str: @@ -60,7 +61,6 @@ def install(self, requirements: Collection[str]) -> None: from poetry.core.packages.project_package import ProjectPackage from poetry.config.config import Config - from poetry.factory import Factory from poetry.installation.installer import Installer from poetry.packages.locker import Locker from poetry.repositories.installed_repository import InstalledRepository @@ -72,13 +72,12 @@ def install(self, requirements: Collection[str]) -> None: dependency = Dependency.create_from_pep_508(requirement) package.add_dependency(dependency) - pool = Factory.create_pool(self._config) installer = Installer( NullIO(), self._env, package, Locker(self._env.path.joinpath("poetry.lock"), {}), - pool, + self._pool, Config.create(), InstalledRepository.load(self._env), ) @@ -87,9 +86,9 @@ def install(self, requirements: Collection[str]) -> None: class Chef: - def __init__(self, config: Config, env: Env) -> None: - self._config = config + def __init__(self, config: Config, env: Env, pool: RepositoryPool) -> None: self._env = env + self._pool = pool self._cache_dir = ( Path(config.get("cache-dir")).expanduser().joinpath("artifacts") ) @@ -113,7 +112,7 @@ def _prepare( from subprocess import CalledProcessError with ephemeral_environment(self._env.python) as venv: - env = IsolatedEnv(venv, self._config) + env = IsolatedEnv(venv, self._pool) builder = ProjectBuilder( directory, python_executable=env.executable, diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index a4ca1238cee..eb7302aa63d 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -79,7 +79,7 @@ def __init__( self._authenticator = Authenticator( config, self._io, disable_cache=disable_cache, pool_size=self._max_workers ) - self._chef = Chef(config, self._env) + self._chef = Chef(config, self._env, pool) self._chooser = Chooser(pool, self._env, config) self._executor = ThreadPoolExecutor(max_workers=self._max_workers) diff --git a/tests/console/commands/self/conftest.py b/tests/console/commands/self/conftest.py index 68aedf1ac21..381e22e2f90 100644 --- a/tests/console/commands/self/conftest.py +++ b/tests/console/commands/self/conftest.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing import Any from typing import Callable import pytest @@ -14,6 +15,8 @@ if TYPE_CHECKING: + from collections.abc import Iterable + import httpretty from cleo.io.io import IO @@ -44,9 +47,12 @@ def pool(repo: TestRepository) -> RepositoryPool: def create_pool_factory( repo: Repository, -) -> Callable[[Config, IO, bool], RepositoryPool]: +) -> Callable[[Config, Iterable[dict[str, Any]], IO, bool], RepositoryPool]: def _create_pool( - config: Config, io: IO, disable_cache: bool = False + config: Config, + sources: Iterable[dict[str, Any]] = (), + io: IO | None = None, + disable_cache: bool = False, ) -> RepositoryPool: pool = RepositoryPool() pool.add_repository(repo) diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index 22fca7dcafe..283146d2c31 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -24,6 +24,7 @@ from poetry.config.dict_config_source import DictConfigSource from tests.types import CommandTesterFactory from tests.types import FixtureDirGetter + from tests.types import ProjectFactory @pytest.fixture() @@ -156,6 +157,44 @@ def test_list_displays_set_get_local_setting( assert tester.io.fetch_output() == expected +def test_list_must_not_display_sources_from_pyproject_toml( + project_factory: ProjectFactory, + fixture_dir: FixtureDirGetter, + command_tester_factory: CommandTesterFactory, + config: Config, + config_cache_dir: Path, +): + source = fixture_dir("with_non_default_source") + pyproject_content = (source / "pyproject.toml").read_text(encoding="utf-8") + poetry = project_factory("foo", pyproject_content=pyproject_content) + tester = command_tester_factory("config", poetry=poetry) + + tester.execute("--list") + + cache_dir = json.dumps(str(config_cache_dir)) + venv_path = json.dumps(os.path.join("{cache-dir}", "virtualenvs")) + expected = f"""cache-dir = {cache_dir} +experimental.new-installer = true +experimental.system-git-client = false +installer.max-workers = null +installer.modern-installation = true +installer.no-binary = null +installer.parallel = true +repositories.foo.url = "https://foo.bar/simple/" +virtualenvs.create = true +virtualenvs.in-project = null +virtualenvs.options.always-copy = false +virtualenvs.options.no-pip = false +virtualenvs.options.no-setuptools = false +virtualenvs.options.system-site-packages = false +virtualenvs.path = {venv_path} # {config_cache_dir / 'virtualenvs'} +virtualenvs.prefer-active-python = false +virtualenvs.prompt = "{{project_name}}-py{{python_version}}" +""" + + assert tester.io.fetch_output() == expected + + def test_set_pypi_token(tester: CommandTester, auth_config_source: DictConfigSource): tester.execute("pypi-token.pypi mytoken") tester.execute("--list") diff --git a/tests/installation/test_chef.py b/tests/installation/test_chef.py index ac084455850..e9046c5433f 100644 --- a/tests/installation/test_chef.py +++ b/tests/installation/test_chef.py @@ -63,6 +63,7 @@ def test_get_cached_archive_for_link( Tag("py3", "none", "any"), ], ), + Factory.create_pool(config), ) mocker.patch.object( @@ -87,6 +88,7 @@ def test_get_cached_archives_for_link(config: Config, mocker: MockerFixture): MockEnv( marker_env={"interpreter_name": "cpython", "interpreter_version": "3.8.3"} ), + Factory.create_pool(config), ) distributions = Path(__file__).parent.parent.joinpath("fixtures/distributions") @@ -110,6 +112,7 @@ def test_get_cache_directory_for_link(config: Config, config_cache_dir: Path): MockEnv( marker_env={"interpreter_name": "cpython", "interpreter_version": "3.8.3"} ), + Factory.create_pool(config), ) directory = chef.get_cache_directory_for_link( @@ -125,7 +128,7 @@ def test_get_cache_directory_for_link(config: Config, config_cache_dir: Path): def test_prepare_sdist(config: Config, config_cache_dir: Path) -> None: - chef = Chef(config, EnvManager.get_system_env()) + chef = Chef(config, EnvManager.get_system_env(), Factory.create_pool(config)) archive = ( Path(__file__) @@ -142,7 +145,7 @@ def test_prepare_sdist(config: Config, config_cache_dir: Path) -> None: def test_prepare_directory(config: Config, config_cache_dir: Path): - chef = Chef(config, EnvManager.get_system_env()) + chef = Chef(config, EnvManager.get_system_env(), Factory.create_pool(config)) archive = Path(__file__).parent.parent.joinpath("fixtures/simple_project").resolve() @@ -155,7 +158,7 @@ def test_prepare_directory_with_extensions( config: Config, config_cache_dir: Path ) -> None: env = EnvManager.get_system_env() - chef = Chef(config, env) + chef = Chef(config, env, Factory.create_pool(config)) archive = ( Path(__file__) @@ -169,7 +172,7 @@ def test_prepare_directory_with_extensions( def test_prepare_directory_editable(config: Config, config_cache_dir: Path): - chef = Chef(config, EnvManager.get_system_env()) + chef = Chef(config, EnvManager.get_system_env(), Factory.create_pool(config)) archive = Path(__file__).parent.parent.joinpath("fixtures/simple_project").resolve() diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index b0e02443042..1f2a6c37dd1 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -202,7 +202,7 @@ def test_execute_executes_a_batch_of_operations( config.merge({"cache-dir": tmp_dir}) prepare_spy = mocker.spy(Chef, "_prepare") - chef = Chef(config, env) + chef = Chef(config, env, Factory.create_pool(config)) chef.set_directory_wheel([copy_wheel(), copy_wheel()]) chef.set_sdist_wheel(copy_wheel()) @@ -661,7 +661,7 @@ def test_executor_should_write_pep610_url_references_for_directories( "demo", "0.1.2", source_type="directory", source_url=url.as_posix() ) - chef = Chef(config, tmp_venv) + chef = Chef(config, tmp_venv, Factory.create_pool(config)) chef.set_directory_wheel(wheel) executor = Executor(tmp_venv, pool, config, io) @@ -692,7 +692,7 @@ def test_executor_should_write_pep610_url_references_for_editable_directories( develop=True, ) - chef = Chef(config, tmp_venv) + chef = Chef(config, tmp_venv, Factory.create_pool(config)) chef.set_directory_wheel(wheel) executor = Executor(tmp_venv, pool, config, io) @@ -756,7 +756,7 @@ def test_executor_should_write_pep610_url_references_for_git( source_url="https://github.com/demo/demo.git", ) - chef = Chef(config, tmp_venv) + chef = Chef(config, tmp_venv, Factory.create_pool(config)) chef.set_directory_wheel(wheel) executor = Executor(tmp_venv, pool, config, io) @@ -794,7 +794,7 @@ def test_executor_should_write_pep610_url_references_for_git_with_subdirectories source_subdirectory="two", ) - chef = Chef(config, tmp_venv) + chef = Chef(config, tmp_venv, Factory.create_pool(config)) chef.set_directory_wheel(wheel) executor = Executor(tmp_venv, pool, config, io) @@ -950,8 +950,6 @@ def test_build_backend_errors_are_reported_correctly_if_caused_by_subprocess( mock_file_downloads: None, env: MockEnv, ): - mocker.patch.object(Factory, "create_pool", return_value=pool) - error = BuildBackendException( CalledProcessError(1, ["pip"], output=b"Error on stdout") ) From 0d25426dcd5e7464e7a42fbb00e5cac863d86683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sat, 18 Feb 2023 11:21:35 +0100 Subject: [PATCH 099/151] chore: bump required poetry-core version and relock dependencies --- poetry.lock | 406 +++++++++++++++++++++++++------------------------ pyproject.toml | 2 +- 2 files changed, 205 insertions(+), 203 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5485ef7fa4e..2588298f006 100644 --- a/poetry.lock +++ b/poetry.lock @@ -402,47 +402,49 @@ files = [ [[package]] name = "cryptography" -version = "39.0.0" +version = "39.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "cryptography-39.0.0-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52a1a6f81e738d07f43dab57831c29e57d21c81a942f4602fac7ee21b27f288"}, - {file = "cryptography-39.0.0-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:80ee674c08aaef194bc4627b7f2956e5ba7ef29c3cc3ca488cf15854838a8f72"}, - {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:887cbc1ea60786e534b00ba8b04d1095f4272d380ebd5f7a7eb4cc274710fad9"}, - {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f97109336df5c178ee7c9c711b264c502b905c2d2a29ace99ed761533a3460f"}, - {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a6915075c6d3a5e1215eab5d99bcec0da26036ff2102a1038401d6ef5bef25b"}, - {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:76c24dd4fd196a80f9f2f5405a778a8ca132f16b10af113474005635fe7e066c"}, - {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bae6c7f4a36a25291b619ad064a30a07110a805d08dc89984f4f441f6c1f3f96"}, - {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:875aea1039d78557c7c6b4db2fe0e9d2413439f4676310a5f269dd342ca7a717"}, - {file = "cryptography-39.0.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f6c0db08d81ead9576c4d94bbb27aed8d7a430fa27890f39084c2d0e2ec6b0df"}, - {file = "cryptography-39.0.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f3ed2d864a2fa1666e749fe52fb8e23d8e06b8012e8bd8147c73797c506e86f1"}, - {file = "cryptography-39.0.0-cp36-abi3-win32.whl", hash = "sha256:f671c1bb0d6088e94d61d80c606d65baacc0d374e67bf895148883461cd848de"}, - {file = "cryptography-39.0.0-cp36-abi3-win_amd64.whl", hash = "sha256:e324de6972b151f99dc078defe8fb1b0a82c6498e37bff335f5bc6b1e3ab5a1e"}, - {file = "cryptography-39.0.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:754978da4d0457e7ca176f58c57b1f9de6556591c19b25b8bcce3c77d314f5eb"}, - {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ee1fd0de9851ff32dbbb9362a4d833b579b4a6cc96883e8e6d2ff2a6bc7104f"}, - {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:fec8b932f51ae245121c4671b4bbc030880f363354b2f0e0bd1366017d891458"}, - {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:407cec680e811b4fc829de966f88a7c62a596faa250fc1a4b520a0355b9bc190"}, - {file = "cryptography-39.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7dacfdeee048814563eaaec7c4743c8aea529fe3dd53127313a792f0dadc1773"}, - {file = "cryptography-39.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ad04f413436b0781f20c52a661660f1e23bcd89a0e9bb1d6d20822d048cf2856"}, - {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50386acb40fbabbceeb2986332f0287f50f29ccf1497bae31cf5c3e7b4f4b34f"}, - {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:e5d71c5d5bd5b5c3eebcf7c5c2bb332d62ec68921a8c593bea8c394911a005ce"}, - {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:844ad4d7c3850081dffba91cdd91950038ee4ac525c575509a42d3fc806b83c8"}, - {file = "cryptography-39.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e0a05aee6a82d944f9b4edd6a001178787d1546ec7c6223ee9a848a7ade92e39"}, - {file = "cryptography-39.0.0.tar.gz", hash = "sha256:f964c7dcf7802d133e8dbd1565914fa0194f9d683d82411989889ecd701e8adf"}, + {file = "cryptography-39.0.1-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:6687ef6d0a6497e2b58e7c5b852b53f62142cfa7cd1555795758934da363a965"}, + {file = "cryptography-39.0.1-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:706843b48f9a3f9b9911979761c91541e3d90db1ca905fd63fee540a217698bc"}, + {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5d2d8b87a490bfcd407ed9d49093793d0f75198a35e6eb1a923ce1ee86c62b41"}, + {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e17b26de248c33f3acffb922748151d71827d6021d98c70e6c1a25ddd78505"}, + {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e124352fd3db36a9d4a21c1aa27fd5d051e621845cb87fb851c08f4f75ce8be6"}, + {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:5aa67414fcdfa22cf052e640cb5ddc461924a045cacf325cd164e65312d99502"}, + {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:35f7c7d015d474f4011e859e93e789c87d21f6f4880ebdc29896a60403328f1f"}, + {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f24077a3b5298a5a06a8e0536e3ea9ec60e4c7ac486755e5fb6e6ea9b3500106"}, + {file = "cryptography-39.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f0c64d1bd842ca2633e74a1a28033d139368ad959872533b1bab8c80e8240a0c"}, + {file = "cryptography-39.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4"}, + {file = "cryptography-39.0.1-cp36-abi3-win32.whl", hash = "sha256:fe913f20024eb2cb2f323e42a64bdf2911bb9738a15dba7d3cce48151034e3a8"}, + {file = "cryptography-39.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:ced4e447ae29ca194449a3f1ce132ded8fcab06971ef5f618605aacaa612beac"}, + {file = "cryptography-39.0.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:807ce09d4434881ca3a7594733669bd834f5b2c6d5c7e36f8c00f691887042ad"}, + {file = "cryptography-39.0.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c5caeb8188c24888c90b5108a441c106f7faa4c4c075a2bcae438c6e8ca73cef"}, + {file = "cryptography-39.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4789d1e3e257965e960232345002262ede4d094d1a19f4d3b52e48d4d8f3b885"}, + {file = "cryptography-39.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:96f1157a7c08b5b189b16b47bc9db2332269d6680a196341bf30046330d15388"}, + {file = "cryptography-39.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e422abdec8b5fa8462aa016786680720d78bdce7a30c652b7fadf83a4ba35336"}, + {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b0afd054cd42f3d213bf82c629efb1ee5f22eba35bf0eec88ea9ea7304f511a2"}, + {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:6f8ba7f0328b79f08bdacc3e4e66fb4d7aab0c3584e0bd41328dce5262e26b2e"}, + {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ef8b72fa70b348724ff1218267e7f7375b8de4e8194d1636ee60510aae104cd0"}, + {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:aec5a6c9864be7df2240c382740fcf3b96928c46604eaa7f3091f58b878c0bb6"}, + {file = "cryptography-39.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdd188c8a6ef8769f148f88f859884507b954cc64db6b52f66ef199bb9ad660a"}, + {file = "cryptography-39.0.1.tar.gz", hash = "sha256:d1f6198ee6d9148405e49887803907fe8962a23e6c6f83ea7d98f1c0de375695"}, ] [package.dependencies] cffi = ">=1.12" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1,!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] -pep8test = ["black", "ruff"] +pep8test = ["black", "check-manifest", "mypy", "ruff", "types-pytz", "types-requests"] sdist = ["setuptools-rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] +test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-shard (>=0.1.2)", "pytest-subtests", "pytest-xdist", "pytz"] +test-randomorder = ["pytest-randomly"] +tox = ["tox"] [[package]] name = "deepdiff" @@ -495,68 +497,68 @@ files = [ [[package]] name = "dulwich" -version = "0.21.2" +version = "0.21.3" description = "Python Git Library" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "dulwich-0.21.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:42c459742bb1802a7686b90f15b7e6f0475ab2a6a8b60b1b01fe8309d6182962"}, - {file = "dulwich-0.21.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:60db3151c7b21d4d30cb39f92a324f6b4296c4226077a9845a297b28062566ac"}, - {file = "dulwich-0.21.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7eacdee555ad5b24774e633b9976b0fd3721f87c4583c0d5644f66427a005276"}, - {file = "dulwich-0.21.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78bf7d82e0fb65a89b6f1cfe88bb46664056f852bed945d07b47b1d56fc76334"}, - {file = "dulwich-0.21.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578e1adec5105643a9c77875e6e6f02627da58a549fab4730584c7bacf0555f9"}, - {file = "dulwich-0.21.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2ea866332f6ed48738e62d2ba57fbad0f0c172191730e1ca61a460ae6603ff3d"}, - {file = "dulwich-0.21.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aaf539fb5d0dcdeab11b0f09f63e6e6851023b1fcdd92d66c51e3c1c45f60843"}, - {file = "dulwich-0.21.2-cp310-cp310-win32.whl", hash = "sha256:86203171f36d5524baa2d04c8b491fd83e2e840b1b3c587535604309b1022ffd"}, - {file = "dulwich-0.21.2-cp310-cp310-win_amd64.whl", hash = "sha256:d6dac3a7453a2de1c1f910794cf7147571771998d2325fea21abd3878a7e91ae"}, - {file = "dulwich-0.21.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09c90e5b4fe9adb0fc2989b67f7eee37a4ad205e2e32550cec7d0af388ffe7ab"}, - {file = "dulwich-0.21.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:37ea1dd93df7e8d8202be4a114633db832b2839793e3a43ab57adf5f5d9c1715"}, - {file = "dulwich-0.21.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b94140249d09976aa60dda88964695903b03956a1b3b37d0ced7f0ca27c249ce"}, - {file = "dulwich-0.21.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d4f7c53fe5910b68cc750b1ffff009fde808df5c3ae2906d2102d1d4290bbd6"}, - {file = "dulwich-0.21.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0ea27192f2c07a84765b06202dbb467ae0da6c93e7824e4e5022ca214e01f7c"}, - {file = "dulwich-0.21.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:acfc3650b8f67306afbc15dae22e9507fab8a0bdf5986c711c047a134a127321"}, - {file = "dulwich-0.21.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0448f4ae5753c08c4ab5e090a89f4d30974847bd41e352854b9a30c0dca00971"}, - {file = "dulwich-0.21.2-cp311-cp311-win32.whl", hash = "sha256:9eae7e44086b2e3bab3f74bfb28d69a6ae52d2fdc83e2d64191485063baf5102"}, - {file = "dulwich-0.21.2-cp311-cp311-win_amd64.whl", hash = "sha256:f3d364192572ade997e40dcc618fedb29176f89685ba07fae2a23d18811a848f"}, - {file = "dulwich-0.21.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2ae925e9501f26e7e925fd9141f606be1cdff164c2a6a0d93e2c68980ce00f9a"}, - {file = "dulwich-0.21.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9752c4eb4625209f61f8315b089709fb26dc4a593dade714e14d4c275d3658a5"}, - {file = "dulwich-0.21.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6380ffbb6048eb7bef3ed24ac684fb515061a3e881df12e8f8542854829215"}, - {file = "dulwich-0.21.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e3291ecffdeb4c1ae5453d698ee9d5ec9149e2c31c69e2056214e7a150422921"}, - {file = "dulwich-0.21.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1b26f54a735543468609d8120271cb0353d56bbbcb594daef38a880feb86a5d6"}, - {file = "dulwich-0.21.2-cp37-cp37m-win32.whl", hash = "sha256:bc8429e417ff5bce7f622beaa8dbdd660093b80a8d6ccb0040149cdf38c61c45"}, - {file = "dulwich-0.21.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7808c558589bea127e991a2a82a8ce3a317bed84d509ed54d46ee5d6324957c3"}, - {file = "dulwich-0.21.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:34e59d348461c81553ca520c3f95c85d62cf6baaa297abe5e80cea0d404bab82"}, - {file = "dulwich-0.21.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fddeed845ad99743cce8b43683b12fe2e164e920baa0c3baff6094b7d6a1831b"}, - {file = "dulwich-0.21.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4c0deea0a9447539db8e41ada9813353b1f572d2a56a7c2eb73178759eefc06"}, - {file = "dulwich-0.21.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e04411a8f2f6ad8dd3b18cc464e3cf996813e832b910d0b41c2e8f0aa4b2860"}, - {file = "dulwich-0.21.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8121fac495a79b946dfb2612ef48c644d5dd9c55278cc023f1337aa4c1dddbe9"}, - {file = "dulwich-0.21.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:06b5ea5a31372446375bb30304d8d3ce90795200f148df470faf41fa66061dc6"}, - {file = "dulwich-0.21.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a68393f843518818fd98e34191820ae92e5c09a0ae2c9cac6f474d69b3a243ab"}, - {file = "dulwich-0.21.2-cp38-cp38-win32.whl", hash = "sha256:48d047e8945348830e576170ef9e5f6f1dbf81567331db54caa3c4d67c93770b"}, - {file = "dulwich-0.21.2-cp38-cp38-win_amd64.whl", hash = "sha256:3023933c1bf35e149f74d94e42944fce9903c8daea6560cd0657da75f5653e80"}, - {file = "dulwich-0.21.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a911565cec4f38689d4888298d5cdd3c41151f15e8c2ca0238432a1194d55741"}, - {file = "dulwich-0.21.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c80b558cc5d9fbd0bc8609d5100b6b57d7a2f164ad1a2caf158179629fc0ef40"}, - {file = "dulwich-0.21.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:adcc225256f63e44dc8d81f66bd0bfdf1d1b161ed399a0923a434937adf64e54"}, - {file = "dulwich-0.21.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b96f96c02490181297fc5431d71679d448bf2b3c5aee579e546d1779a36567d"}, - {file = "dulwich-0.21.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb8a812d32400b12a0b70d603edd2b2fce4f5b0867062e2089f81db2e770c6e7"}, - {file = "dulwich-0.21.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:82873d5248dc9bfe2aae4a4bf8518633c4e12144cf6ecba56f2996abc8e4c53e"}, - {file = "dulwich-0.21.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e35981b025483f56a615a1c6440a235c675ec65cea8575b2260b636136197fa0"}, - {file = "dulwich-0.21.2-cp39-cp39-win32.whl", hash = "sha256:72ea83a8e6bb5e9f0f86bae262e2ff867d15073895030ac042a652cf64e52d44"}, - {file = "dulwich-0.21.2-cp39-cp39-win_amd64.whl", hash = "sha256:b13fe5509bc4a365788999ea1915ac2ab59617301f7c6bbb06b25cd5f0725f60"}, - {file = "dulwich-0.21.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2efa033a4477cc1f6f0056b39a970eb9ac19a10f1a51683a590a1068c5c3c084"}, - {file = "dulwich-0.21.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6fe1563d1efd3d6c4fcfda210d313d449e50ba5371500dc68a0fd3afb0a6ec3"}, - {file = "dulwich-0.21.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2a774c06d10700e66610bc021050bee6b81ebce141b2e695b5448e2e70466df"}, - {file = "dulwich-0.21.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ae82a1f0c9abe9a3719ebc611cbc2a60fcb9b02a567f750280ff5f783e21abfb"}, - {file = "dulwich-0.21.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:86e83c9b713323d47796b3b95ddf95f491ffaa07050ed6145ac2f3249b67bd06"}, - {file = "dulwich-0.21.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f24a7de7e5ec0889f674a4975b61d5e5a455313cb6d32bddb0981f42c0edd789"}, - {file = "dulwich-0.21.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc265def2b50a10bfdd9539a5d5d6a313e45493c1e20303f4c18ba045b1d555b"}, - {file = "dulwich-0.21.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7097a5a6e06d9339c52b636ea90f93b498441ab962075bc21279396c25e1ff7b"}, - {file = "dulwich-0.21.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c7b698ef8bf9ee7de13a282dc6f196ffcfd679c36434059f6c9e0be67b4c51a0"}, - {file = "dulwich-0.21.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:929b720a80c758f29b48ad9b25d3b16e2276767fadfbcdb1d910b9565f4dcc82"}, - {file = "dulwich-0.21.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7385eae5cc0b4777c5021d6b556eca9a0ebfc8e767329ebf5d55c45f931dbd3"}, - {file = "dulwich-0.21.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7efd37d32e924f97beb3ee4a8cf7fb00210db954f7c971d4c67970d8bbe9508b"}, - {file = "dulwich-0.21.2.tar.gz", hash = "sha256:d865ae7fd9497d64ce345a6784ff1775b01317fba9632ef9d2dfd7978f1b0d4f"}, + {file = "dulwich-0.21.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25376efc6ea2ee9daa868a120d4f9c905dcb7774f68931be921fba41a657f58a"}, + {file = "dulwich-0.21.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9213a114dd19cfca19715088f12f143e918c5e1b4e26f7acf1a823d7da9e1413"}, + {file = "dulwich-0.21.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:baf5b3b901272837bee2311ecbd28fdbe960d288a070dc72bdfdf48cfcbb8090"}, + {file = "dulwich-0.21.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7b8cb38a93de87b980f882f0dcd19f2e3ad43216f34e06916315cb3a03e6964"}, + {file = "dulwich-0.21.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4f8ff776ca38ce272d9c164a7f77db8a54a8cad6d9468124317adf8732be07d"}, + {file = "dulwich-0.21.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:03ed9448f2944166e28aa8d3f4c8feeceb5c6880e9ffe5ab274869d45abd9589"}, + {file = "dulwich-0.21.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6618e35268d116bffddd6dbec360a40c54b3164f8af0513d95d8698f36e2eacc"}, + {file = "dulwich-0.21.3-cp310-cp310-win32.whl", hash = "sha256:d7ad871d044a96f794170f2434e832c6b42804d0b53721377d03f865245cd273"}, + {file = "dulwich-0.21.3-cp310-cp310-win_amd64.whl", hash = "sha256:ba3d42cd83d7f89b9c1b2f76df971e8ab58815f8060da4dc67b9ae9dba1b34cc"}, + {file = "dulwich-0.21.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:092829f27a2c87cdf6b6523216822859ecf01d281ddfae0e58cad1f44adafff6"}, + {file = "dulwich-0.21.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bb54fe45deb55e4caae4ea2c1dba93ee79fb5c377287b14056d4c30fb156920e"}, + {file = "dulwich-0.21.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d0ac29adf468a838884e1507d81e872096238c76fe7da7f3325507e4390b6867"}, + {file = "dulwich-0.21.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8d1837c3d2d8e56aacc13a91ec7540b3baadc1b254fbdf225a2d15b72b654c3"}, + {file = "dulwich-0.21.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cf246530b8d574b33a9614da76881b96c190c0fe78f76ab016c88082c0da051"}, + {file = "dulwich-0.21.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:be0801ae3f9017c6437bcd23a4bf2b2aa88e465f7efeed4b079944d07e3df994"}, + {file = "dulwich-0.21.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5a1137177b62eec949c0f1564eef73920f842af5ebfc260c20d9cd47e8ecd519"}, + {file = "dulwich-0.21.3-cp311-cp311-win32.whl", hash = "sha256:b09b6166876d2cba8f331a548932b09e11c9386db0525c9ca15c399b666746fc"}, + {file = "dulwich-0.21.3-cp311-cp311-win_amd64.whl", hash = "sha256:250ec581682af846cb85844f8032b7642dd278006b1c3abd5e8e718eba0b1b00"}, + {file = "dulwich-0.21.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ad7de37c9ff817bc5d26f89100f87b7f1a5cc25e5eaaa54f11dc66cca9652e4"}, + {file = "dulwich-0.21.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b541bd58426a30753ab12cc024ba29b6699d197d9d0d9f130b9768ab20e0e6a"}, + {file = "dulwich-0.21.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f8d45f5fcdb52c60c902a951f549faad9979314e7e069f4fa3d14eb409b16a0"}, + {file = "dulwich-0.21.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a98989ff1ed20825728495ffb859cd700a120850074184d2e1ec08a0b1ab8ab3"}, + {file = "dulwich-0.21.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:33f73e8f902c6397cc73a727db1f6e75add8ce894bfbb1a15daa2f7a4138a744"}, + {file = "dulwich-0.21.3-cp37-cp37m-win32.whl", hash = "sha256:a2e6270923bf5ec0e9f720d689579a904f401c62193222d000d8cb8e880684e9"}, + {file = "dulwich-0.21.3-cp37-cp37m-win_amd64.whl", hash = "sha256:1799c04bd53ec404ebd2c82c1d66197a31e5f0549c95348bb7d3f57a28c94241"}, + {file = "dulwich-0.21.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c80ade5cdb0ea447e7f43b32abc2f4a628dcdfa64dc8ee5ab4262987e5e0814f"}, + {file = "dulwich-0.21.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:40f8f461eba87ef2e8ce0005ca2c12f1b4fdbbafd3a717b8570060d7cd35ee0c"}, + {file = "dulwich-0.21.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af7a417e19068b1abeb9addd3c045a2d6e40d15365af6aa3cbe2d47305b5bb11"}, + {file = "dulwich-0.21.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:026427b5ef0f1fe138ed22078e49b00175b58b11e5c18e2be00f06ee0782603b"}, + {file = "dulwich-0.21.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae38c6d24d7aff003a241c8f1dd268eb1c6f7625d91e3435836ff5a5eed05ce5"}, + {file = "dulwich-0.21.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:512bb4b04e403a38860f7eb22abeeaefba3c4a9c08bc7beec8885494c5828034"}, + {file = "dulwich-0.21.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3b048f84c94c3284f29bf228f1094ccc48763d76ede5c35632153bd7f697b846"}, + {file = "dulwich-0.21.3-cp38-cp38-win32.whl", hash = "sha256:a275b3a579dfd923d6330f6e5c2886dbdb5da4e004c5abecb107eb347d301412"}, + {file = "dulwich-0.21.3-cp38-cp38-win_amd64.whl", hash = "sha256:208d01a9cda1bae16c92e8c54e806701a16969346aba44b8d6921c6c227277a9"}, + {file = "dulwich-0.21.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:73f9feba3da1ae66f0b521d7c2727db7f5025a83facdc73f4f39abe2b6d4f00d"}, + {file = "dulwich-0.21.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cf7af6458cf6343a2a0632ae2fc5f04821b2ffefc7b8a27f4eacb726ef89c682"}, + {file = "dulwich-0.21.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aaf5c4528e83e3176e7dbb01dcec34fb41c93279a8f8527cf33e5df88bfb910"}, + {file = "dulwich-0.21.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:075c8e9d2694ff16fc6e8a5ec0c771b7c33be12e4ebecc346fd74315d3d84605"}, + {file = "dulwich-0.21.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3b686b49adeb7fc45791dfae96ffcffeba1038e8b7603f369d6661f59e479fc"}, + {file = "dulwich-0.21.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:21ee962211839bb6e52d41f363ce9dbb0638d341a1c02263e163d69012f58b25"}, + {file = "dulwich-0.21.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2bf2be68fddfc0adfe43be99ab31f6b0f16b9ef1e40464679ba831ff615ad4a3"}, + {file = "dulwich-0.21.3-cp39-cp39-win32.whl", hash = "sha256:ddb790f2fdc22984fba643866b21d04733c5cf7c3ace2a1e99e0c1c1d2336aab"}, + {file = "dulwich-0.21.3-cp39-cp39-win_amd64.whl", hash = "sha256:c97561c22fc05d0f6ba370d9bd67f86c313c38f31a1793e0ee9acb78ee28e4b8"}, + {file = "dulwich-0.21.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b9fc609a3d4009ee31212f435f5a75720ef24280f6d23edfd53f77b562a79c5b"}, + {file = "dulwich-0.21.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f2cb11fe789b72feeae7cdf6e27375c33ed6915f8ca5ea7ce81b5e234c75a9e"}, + {file = "dulwich-0.21.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c349431f5c8aa99b8744550d0bb4615f63e73450584202ac5db0e5d7da4d82ff"}, + {file = "dulwich-0.21.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:9f08e5cc10143d3da2a2cf735d8b932ef4e4e1d74b0c74ce66c52eab02068be8"}, + {file = "dulwich-0.21.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:67dbf4dd7586b2d437f539d5dc930ebceaf74a4150720644d6ea7e5ffc1cb2ff"}, + {file = "dulwich-0.21.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89af4ee347f361338bad5c27b023f9d19e7aed17aa75cb519f28e6cf1658a0ba"}, + {file = "dulwich-0.21.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cd83f84e58aa59fb9d85cf15e74be83a5be876ac5876d5030f60fcce7ab36f1"}, + {file = "dulwich-0.21.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8ba1fe3fb415fd34cae5ca090fb82030b6e8423d6eb2c4c9c4fbf50b15c7664c"}, + {file = "dulwich-0.21.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:058aaba18aefe18fcd84b216fd34d032ad453967dcf3dee263278951cd43e2d4"}, + {file = "dulwich-0.21.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08ee426b609dab552839b5c7394ae9af2112c164bb727b7f85a69980eced9251"}, + {file = "dulwich-0.21.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf1f6edc968619a4355481c29d5571726723bc12924e2b25bd3348919f9bc992"}, + {file = "dulwich-0.21.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c69c95d5242171d07396761f759a8a4d566e9a01bf99612f9b9e309e70a80fc"}, + {file = "dulwich-0.21.3.tar.gz", hash = "sha256:7ca3b453d767eb83b3ec58f0cfcdc934875a341cdfdb0dc55c1431c96608cf83"}, ] [package.dependencies] @@ -650,14 +652,14 @@ files = [ [[package]] name = "identify" -version = "2.5.17" +version = "2.5.18" description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "identify-2.5.17-py2.py3-none-any.whl", hash = "sha256:7d526dd1283555aafcc91539acc061d8f6f59adb0a7bba462735b0a318bff7ed"}, - {file = "identify-2.5.17.tar.gz", hash = "sha256:93cc61a861052de9d4c541a7acb7e3dcc9c11b398a2144f6e52ae5285f5f4f06"}, + {file = "identify-2.5.18-py2.py3-none-any.whl", hash = "sha256:93aac7ecf2f6abf879b8f29a8002d3c6de7086b8c28d88e1ad15045a15ab63f9"}, + {file = "identify-2.5.18.tar.gz", hash = "sha256:89e144fa560cc4cffb6ef2ab5e9fb18ed9f9b3cb054384bab4b95c12f6c309fe"}, ] [package.extras] @@ -698,14 +700,14 @@ testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packag [[package]] name = "importlib-resources" -version = "5.10.2" +version = "5.12.0" description = "Read resources from Python packages" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "importlib_resources-5.10.2-py3-none-any.whl", hash = "sha256:7d543798b0beca10b6a01ac7cafda9f822c54db9e8376a6bf57e0cbd74d486b6"}, - {file = "importlib_resources-5.10.2.tar.gz", hash = "sha256:e4a96c8cc0339647ff9a5e0550d9f276fc5a01ffa276012b58ec108cfd7b8484"}, + {file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"}, + {file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"}, ] [package.dependencies] @@ -911,38 +913,38 @@ files = [ [[package]] name = "mypy" -version = "1.0.0" +version = "1.0.1" description = "Optional static typing for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mypy-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0626db16705ab9f7fa6c249c017c887baf20738ce7f9129da162bb3075fc1af"}, - {file = "mypy-1.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ace23f6bb4aec4604b86c4843276e8fa548d667dbbd0cb83a3ae14b18b2db6c"}, - {file = "mypy-1.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87edfaf344c9401942883fad030909116aa77b0fa7e6e8e1c5407e14549afe9a"}, - {file = "mypy-1.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0ab090d9240d6b4e99e1fa998c2d0aa5b29fc0fb06bd30e7ad6183c95fa07593"}, - {file = "mypy-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:7cc2c01dfc5a3cbddfa6c13f530ef3b95292f926329929001d45e124342cd6b7"}, - {file = "mypy-1.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14d776869a3e6c89c17eb943100f7868f677703c8a4e00b3803918f86aafbc52"}, - {file = "mypy-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bb2782a036d9eb6b5a6efcdda0986774bf798beef86a62da86cb73e2a10b423d"}, - {file = "mypy-1.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cfca124f0ac6707747544c127880893ad72a656e136adc935c8600740b21ff5"}, - {file = "mypy-1.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8845125d0b7c57838a10fd8925b0f5f709d0e08568ce587cc862aacce453e3dd"}, - {file = "mypy-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b1b9e1ed40544ef486fa8ac022232ccc57109f379611633ede8e71630d07d2"}, - {file = "mypy-1.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c7cf862aef988b5fbaa17764ad1d21b4831436701c7d2b653156a9497d92c83c"}, - {file = "mypy-1.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cd187d92b6939617f1168a4fe68f68add749902c010e66fe574c165c742ed88"}, - {file = "mypy-1.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4e5175026618c178dfba6188228b845b64131034ab3ba52acaffa8f6c361f805"}, - {file = "mypy-1.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2f6ac8c87e046dc18c7d1d7f6653a66787a4555085b056fe2d599f1f1a2a2d21"}, - {file = "mypy-1.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7306edca1c6f1b5fa0bc9aa645e6ac8393014fa82d0fa180d0ebc990ebe15964"}, - {file = "mypy-1.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3cfad08f16a9c6611e6143485a93de0e1e13f48cfb90bcad7d5fde1c0cec3d36"}, - {file = "mypy-1.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67cced7f15654710386e5c10b96608f1ee3d5c94ca1da5a2aad5889793a824c1"}, - {file = "mypy-1.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a86b794e8a56ada65c573183756eac8ac5b8d3d59daf9d5ebd72ecdbb7867a43"}, - {file = "mypy-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:50979d5efff8d4135d9db293c6cb2c42260e70fb010cbc697b1311a4d7a39ddb"}, - {file = "mypy-1.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ae4c7a99e5153496243146a3baf33b9beff714464ca386b5f62daad601d87af"}, - {file = "mypy-1.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e398652d005a198a7f3c132426b33c6b85d98aa7dc852137a2a3be8890c4072"}, - {file = "mypy-1.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be78077064d016bc1b639c2cbcc5be945b47b4261a4f4b7d8923f6c69c5c9457"}, - {file = "mypy-1.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92024447a339400ea00ac228369cd242e988dd775640755fa4ac0c126e49bb74"}, - {file = "mypy-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:fe523fcbd52c05040c7bee370d66fee8373c5972171e4fbc323153433198592d"}, - {file = "mypy-1.0.0-py3-none-any.whl", hash = "sha256:2efa963bdddb27cb4a0d42545cd137a8d2b883bd181bbc4525b568ef6eca258f"}, - {file = "mypy-1.0.0.tar.gz", hash = "sha256:f34495079c8d9da05b183f9f7daec2878280c2ad7cc81da686ef0b484cea2ecf"}, + {file = "mypy-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:71a808334d3f41ef011faa5a5cd8153606df5fc0b56de5b2e89566c8093a0c9a"}, + {file = "mypy-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:920169f0184215eef19294fa86ea49ffd4635dedfdea2b57e45cb4ee85d5ccaf"}, + {file = "mypy-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27a0f74a298769d9fdc8498fcb4f2beb86f0564bcdb1a37b58cbbe78e55cf8c0"}, + {file = "mypy-1.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:65b122a993d9c81ea0bfde7689b3365318a88bde952e4dfa1b3a8b4ac05d168b"}, + {file = "mypy-1.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:5deb252fd42a77add936b463033a59b8e48eb2eaec2976d76b6878d031933fe4"}, + {file = "mypy-1.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2013226d17f20468f34feddd6aae4635a55f79626549099354ce641bc7d40262"}, + {file = "mypy-1.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:48525aec92b47baed9b3380371ab8ab6e63a5aab317347dfe9e55e02aaad22e8"}, + {file = "mypy-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c96b8a0c019fe29040d520d9257d8c8f122a7343a8307bf8d6d4a43f5c5bfcc8"}, + {file = "mypy-1.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:448de661536d270ce04f2d7dddaa49b2fdba6e3bd8a83212164d4174ff43aa65"}, + {file = "mypy-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:d42a98e76070a365a1d1c220fcac8aa4ada12ae0db679cb4d910fabefc88b994"}, + {file = "mypy-1.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e64f48c6176e243ad015e995de05af7f22bbe370dbb5b32bd6988438ec873919"}, + {file = "mypy-1.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fdd63e4f50e3538617887e9aee91855368d9fc1dea30da743837b0df7373bc4"}, + {file = "mypy-1.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dbeb24514c4acbc78d205f85dd0e800f34062efcc1f4a4857c57e4b4b8712bff"}, + {file = "mypy-1.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a2948c40a7dd46c1c33765718936669dc1f628f134013b02ff5ac6c7ef6942bf"}, + {file = "mypy-1.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5bc8d6bd3b274dd3846597855d96d38d947aedba18776aa998a8d46fabdaed76"}, + {file = "mypy-1.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:17455cda53eeee0a4adb6371a21dd3dbf465897de82843751cf822605d152c8c"}, + {file = "mypy-1.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e831662208055b006eef68392a768ff83596035ffd6d846786578ba1714ba8f6"}, + {file = "mypy-1.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e60d0b09f62ae97a94605c3f73fd952395286cf3e3b9e7b97f60b01ddfbbda88"}, + {file = "mypy-1.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:0af4f0e20706aadf4e6f8f8dc5ab739089146b83fd53cb4a7e0e850ef3de0bb6"}, + {file = "mypy-1.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:24189f23dc66f83b839bd1cce2dfc356020dfc9a8bae03978477b15be61b062e"}, + {file = "mypy-1.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93a85495fb13dc484251b4c1fd7a5ac370cd0d812bbfc3b39c1bafefe95275d5"}, + {file = "mypy-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f546ac34093c6ce33f6278f7c88f0f147a4849386d3bf3ae193702f4fe31407"}, + {file = "mypy-1.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c6c2ccb7af7154673c591189c3687b013122c5a891bb5651eca3db8e6c6c55bd"}, + {file = "mypy-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:15b5a824b58c7c822c51bc66308e759243c32631896743f030daf449fe3677f3"}, + {file = "mypy-1.0.1-py3-none-any.whl", hash = "sha256:eda5c8b9949ed411ff752b9a01adda31afe7eae1e53e946dbdf9db23865e66c4"}, + {file = "mypy-1.0.1.tar.gz", hash = "sha256:28cea5a6392bb43d266782983b5a4216c25544cd7d80be681a155ddcdafd152d"}, ] [package.dependencies] @@ -959,14 +961,14 @@ reports = ["lxml"] [[package]] name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.5" files = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] [[package]] @@ -1001,56 +1003,56 @@ dev = ["black", "mypy", "pytest"] [[package]] name = "orjson" -version = "3.8.5" +version = "3.8.6" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "orjson-3.8.5-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:143639b9898b094883481fac37733231da1c2ae3aec78a1dd8d3b58c9c9fceef"}, - {file = "orjson-3.8.5-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:31f43e63e0d94784c55e86bd376df3f80b574bea8c0bc5ecd8041009fa8ec78a"}, - {file = "orjson-3.8.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c802ea6d4a0d40f096aceb5e7ef0a26c23d276cb9334e1cadcf256bb090b6426"}, - {file = "orjson-3.8.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf298b55b371c2772420c5ace4d47b0a3ea1253667e20ded3c363160fd0575f6"}, - {file = "orjson-3.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68cb4a8501a463771d55bb22fc72795ec7e21d71ab083e000a2c3b651b6fb2af"}, - {file = "orjson-3.8.5-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:4f1427952b3bd92bfb63a61b7ffc33a9f54ec6de296fa8d924cbeba089866acb"}, - {file = "orjson-3.8.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c0a9f329468c8eb000742455b83546849bcd69495d6baa6e171c7ee8600a47bd"}, - {file = "orjson-3.8.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6535d527aa1e4a757a6ce9b61f3dd74edc762e7d2c6991643aae7c560c8440bd"}, - {file = "orjson-3.8.5-cp310-none-win_amd64.whl", hash = "sha256:2eee64c028adf6378dd714c8debc96d5b92b6bb4862debb65ca868e59bac6c63"}, - {file = "orjson-3.8.5-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:f5745ff473dd5c6718bf8c8d5bc183f638b4f3e03c7163ffcda4d4ef453f42ff"}, - {file = "orjson-3.8.5-cp311-cp311-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:544f1240b295083697027a5093ec66763218ff16f03521d5020e7a436d2e417b"}, - {file = "orjson-3.8.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c85c9c6bab97a831e7741089057347d99901b4db2451a076ca8adedc7d96297f"}, - {file = "orjson-3.8.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9bae7347764e7be6dada980fd071e865544c98317ab61af575c9cc5e1dc7e3fe"}, - {file = "orjson-3.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c67f6f6e9d26a06b63126112a7bc8d8529df048d31df2a257a8484b76adf3e5d"}, - {file = "orjson-3.8.5-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:758238364142fcbeca34c968beefc0875ffa10aa2f797c82f51cfb1d22d0934e"}, - {file = "orjson-3.8.5-cp311-none-win_amd64.whl", hash = "sha256:cc7579240fb88a626956a6cb4a181a11b62afbc409ce239a7b866568a2412fa2"}, - {file = "orjson-3.8.5-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:79aa3e47cbbd4eedbbde4f988f766d6cf38ccb51d52cfabfeb6b8d1b58654d25"}, - {file = "orjson-3.8.5-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:2544cd0d089faa862f5a39f508ee667419e3f9e11f119a6b1505cfce0eb26601"}, - {file = "orjson-3.8.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2be0025ca7e460bcacb250aba8ce0239be62957d58cf34045834cc9302611d3"}, - {file = "orjson-3.8.5-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b57bf72902d818506906e49c677a791f90dbd7f0997d60b14bc6c1ce4ce4cf9"}, - {file = "orjson-3.8.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ae9832a11c6a9efa8c14224e5caf6e35046efd781de14e59eb69ab4e561cf3"}, - {file = "orjson-3.8.5-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:0e28330cc6d51741cad0edd1b57caf6c5531aff30afe41402acde0a03246b8ed"}, - {file = "orjson-3.8.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:155954d725627b5480e6cc1ca488afb4fa685099a4ace5f5bf21a182fabf6706"}, - {file = "orjson-3.8.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ece1b6ef9312df5d5274ca6786e613b7da7de816356e36bcad9ea8a73d15ab71"}, - {file = "orjson-3.8.5-cp37-none-win_amd64.whl", hash = "sha256:6f58d1f0702332496bc1e2d267c7326c851991b62cf6395370d59c47f9890007"}, - {file = "orjson-3.8.5-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:933f4ab98362f46a59a6d0535986e1f0cae2f6b42435e24a55922b4bc872af0c"}, - {file = "orjson-3.8.5-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:47a7ca236b25a138a74b2cb5169adcdc5b2b8abdf661de438ba65967a2cde9dc"}, - {file = "orjson-3.8.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b573ca942c626fcf8a86be4f180b86b2498b18ae180f37b4180c2aced5808710"}, - {file = "orjson-3.8.5-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a9bab11611d5452efe4ae5315f5eb806f66104c08a089fb84c648d2e8e00f106"}, - {file = "orjson-3.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee2f5f6476617d01ca166266d70fd5605d3397a41f067022ce04a2e1ced4c8d"}, - {file = "orjson-3.8.5-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:ec0b0b6cd0b84f03537f22b719aca705b876c54ab5cf3471d551c9644127284f"}, - {file = "orjson-3.8.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:df3287dc304c8c4556dc85c4ab89eb333307759c1863f95e72e555c0cfce3e01"}, - {file = "orjson-3.8.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:09f40add3c2d208e20f8bf185df38f992bf5092202d2d30eced8f6959963f1d5"}, - {file = "orjson-3.8.5-cp38-none-win_amd64.whl", hash = "sha256:232ec1df0d708f74e0dd1fccac1e9a7008cd120d48fe695e8f0c9d80771da430"}, - {file = "orjson-3.8.5-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:8fba3e7aede3e88a01e94e6fe63d4580162b212e6da27ae85af50a1787e41416"}, - {file = "orjson-3.8.5-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:85e22c358cab170c8604e9edfffcc45dd7b0027ce57ed6bcacb556e8bfbbb704"}, - {file = "orjson-3.8.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeab1d8247507a75926adf3ca995c74e91f5db1f168815bf3e774f992ba52b50"}, - {file = "orjson-3.8.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:daaaef15a41e9e8cadc7677cefe00065ae10bce914eefe8da1cd26b3d063970b"}, - {file = "orjson-3.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ccc9f52cf46bd353c6ae1153eaf9d18257ddc110d135198b0cd8718474685ce"}, - {file = "orjson-3.8.5-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:d48c182c7ff4ea0787806de8a2f9298ca44fd0068ecd5f23a4b2d8e03c745cb6"}, - {file = "orjson-3.8.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1848e3b4cc09cc82a67262ae56e2a772b0548bb5a6f9dcaee10dcaaf0a5177b7"}, - {file = "orjson-3.8.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38480031bc8add58effe802291e4abf7042ef72ae1a4302efe9a36c8f8bfbfcc"}, - {file = "orjson-3.8.5-cp39-none-win_amd64.whl", hash = "sha256:0e9a1c2e649cbaed410c882cedc8f3b993d8f1426d9327f31762d3f46fe7cc88"}, - {file = "orjson-3.8.5.tar.gz", hash = "sha256:77a3b2bd0c4ef7723ea09081e3329dac568a62463aed127c1501441b07ffc64b"}, + {file = "orjson-3.8.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:062a9a74c10c439acc35cf67f31ac88d9464a11025700bab421e6cdf54a54a35"}, + {file = "orjson-3.8.6-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:692c255109867cc8211267f4416d2915845273bf4f403bbca5419f5b15ac9175"}, + {file = "orjson-3.8.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a20905c7a5ebc280343704c4dd19343ef966c9dea5a38ade6e0461a6deb8eda"}, + {file = "orjson-3.8.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34ce4a8b8f0fea483bce6985c015953f475540b7d756efd48a571b1803c318ee"}, + {file = "orjson-3.8.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57ecad7616ec842d8c382ed42a778cdcdadc67cfb46b804b43079f937b63b31"}, + {file = "orjson-3.8.6-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:323065cf14fdd4096dbf93ea1634e7e030044af8c1000803bcdc132fbfd395f5"}, + {file = "orjson-3.8.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4cb4f37fca8cf8309de421634447437f229bc03b240cec8ad4ac241fd4b1bcf4"}, + {file = "orjson-3.8.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:32353b14c5e0b55b6a8759e993482a2d8c44d492489840718b74658de67671e2"}, + {file = "orjson-3.8.6-cp310-none-win_amd64.whl", hash = "sha256:3e44f78db3a15902b5e8386119979691ed3dd61d1ded10bad2c7106fd50641ef"}, + {file = "orjson-3.8.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:c59ec129d523abd4f2d65c0733d0e57af7dd09c69142f1aa564b04358f04ace3"}, + {file = "orjson-3.8.6-cp311-cp311-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:d44d89314a66e98e690ce64c8771d963eb64ae6cea662d0a1d077ed024627228"}, + {file = "orjson-3.8.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:865ef341c4d310ac2689bf811dbc0930b2f13272f8eade1511dc40b186f6d562"}, + {file = "orjson-3.8.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:52809a37a0daa6992835ee0625aca22b4c0693dba3cb465948e6c9796de927b0"}, + {file = "orjson-3.8.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d7402121d06d11fafcaed7d06f9d68b11bbe39868e0e1bc19239ee5b6b98b2b"}, + {file = "orjson-3.8.6-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:583338b7dabb509ca4c3b4f160f58a5228bf6c6e0f8a2981663f683791f39d45"}, + {file = "orjson-3.8.6-cp311-none-win_amd64.whl", hash = "sha256:4a6c0a0ef2f535ba7a5d01f014b53d05eeb372d43556edb25c75a4d52690a123"}, + {file = "orjson-3.8.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9d35573e7f5817a26d8ce1134c3463d31bc3b39aad3ad7ae06bb67d6078fa9c0"}, + {file = "orjson-3.8.6-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:94d8fdc12adc0450994931d722cb38be5e4caa273219881abb96c15a9e9f151f"}, + {file = "orjson-3.8.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8fc43bfb73d394b9bf12062cd6dab72abf728ac7869f972e4bb7327fd3330b8"}, + {file = "orjson-3.8.6-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a38387387139695a7e52b9f568e39c1632b22eb34939afc5efed265fa8277b84"}, + {file = "orjson-3.8.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e048c6df7453c3da4de10fa5c44f6c655b157b712628888ce880cd5bbf30013"}, + {file = "orjson-3.8.6-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:d3b0950d792b25c0aa52505faf09237fd98136d09616a0837f7cdb0fde9e2730"}, + {file = "orjson-3.8.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:38bc8a388080d8fd297388bcff4939e350ffafe4a006567e0dd81cdb8c7b86fa"}, + {file = "orjson-3.8.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5b3251ab7113f2400d76f2b4a2d6592e7d5a5cf45fa948c894340553671ef8f1"}, + {file = "orjson-3.8.6-cp37-none-win_amd64.whl", hash = "sha256:2c83a33cf389fd286bd9ef0befc406307444b9553d2e9ba14b90b9332524cfa6"}, + {file = "orjson-3.8.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:53f51c23398cfe818d9bb09079d31a60c6cd77e7eee1d555cfcc735460db4190"}, + {file = "orjson-3.8.6-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:6190e23a2fb9fc78228b289b3ec295094671ca0299319c8c72727aa9e7dbe06f"}, + {file = "orjson-3.8.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61fff8a8b4cd4e489b291fe5105b6138b1831490f1a0dc726d5e17ebe811d595"}, + {file = "orjson-3.8.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c192813f527f886bd85abc5a9e8d9dde16ffa06d7305de526a7c4657730dbf4e"}, + {file = "orjson-3.8.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aae1487fba9d955b2679f0a697665ed8fc32563b3252acc240e097184c184e29"}, + {file = "orjson-3.8.6-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cd2bd48e9a14f2130790a3c2dcb897bd93c2e5c244919799430a6d9b8212cb50"}, + {file = "orjson-3.8.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:006178fd654a0a4f14f5912b8320ba9a26ab9c0ae7ce1c7eeb4b5249d6cada29"}, + {file = "orjson-3.8.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9d5ad2fddccc89ab64b6333823b250ce8430fc51f014954e5a2d4c933f5deb9f"}, + {file = "orjson-3.8.6-cp38-none-win_amd64.whl", hash = "sha256:aef3d558f5bd809733ebf2cbce7e1338ce62812db317478427236b97036aba0f"}, + {file = "orjson-3.8.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:7d216a5f3d23eac2c7c654e7bd30280c27cdf5edc32325e6ad8e880d36c265b7"}, + {file = "orjson-3.8.6-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:004122c95e08db7201b80224de3a8f2ad79b9717040e6884c6015f27b010127d"}, + {file = "orjson-3.8.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:006c492577ad046cb7e50237a8d8935131a35f7e7f8320fbc3514da6fbc0b436"}, + {file = "orjson-3.8.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:67554103b415349b6ee2db82d2422da1c8f4c2d280d20772217f6d1d227410b6"}, + {file = "orjson-3.8.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa5053f19584816f063c887d94385db481fc01d995d6a717ce4fbb929653ec2"}, + {file = "orjson-3.8.6-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:2bdd64566870a8a0bdcf8c7df2f4452391dd55070f5cd98cc581914e8c263d85"}, + {file = "orjson-3.8.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:550a4dec128d1adfd0262ef9ad7878d62d1cc0bddaaa05e41d8ca28414dc86bc"}, + {file = "orjson-3.8.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3f5ad9442e8a99fb436279a8614a00aca272ea8dabb692cadee70a4874d6e03"}, + {file = "orjson-3.8.6-cp39-none-win_amd64.whl", hash = "sha256:aa7b112e3273d1744f7bc983ffd3dd0d004062c69dfa68e119515a7e115c46c8"}, + {file = "orjson-3.8.6.tar.gz", hash = "sha256:91ef8a554d33fbc5bb61c3972f3e8baa994f72c4967671e64e7dac1cc06f50e1"}, ] [[package]] @@ -1147,14 +1149,14 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "poetry-core" -version = "1.5.0" +version = "1.5.1" description = "Poetry PEP 517 Build Backend" category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "poetry_core-1.5.0-py3-none-any.whl", hash = "sha256:e216b70f013c47b82a72540d34347632c5bfe59fd54f5fe5d51f6a68b19aaf84"}, - {file = "poetry_core-1.5.0.tar.gz", hash = "sha256:253521bb7104e1df81f64d7b49ea1825057c91fa156d7d0bd752fefdad6f8c7a"}, + {file = "poetry_core-1.5.1-py3-none-any.whl", hash = "sha256:b1900dea81eb18feb7323d404e5f10430205541a4a683a912893f9d2b5807797"}, + {file = "poetry_core-1.5.1.tar.gz", hash = "sha256:41887261358863f25831fa0ad1fe7e451fc32d1c81fcf7710ba5174cc0047c6d"}, ] [package.dependencies] @@ -1394,14 +1396,14 @@ pytest = "*" [[package]] name = "pytest-xdist" -version = "3.1.0" +version = "3.2.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-xdist-3.1.0.tar.gz", hash = "sha256:40fdb8f3544921c5dfcd486ac080ce22870e71d82ced6d2e78fa97c2addd480c"}, - {file = "pytest_xdist-3.1.0-py3-none-any.whl", hash = "sha256:70a76f191d8a1d2d6be69fc440cdf85f3e4c03c08b520fd5dc5d338d6cf07d89"}, + {file = "pytest-xdist-3.2.0.tar.gz", hash = "sha256:fa10f95a2564cd91652f2d132725183c3b590d9fdcdec09d3677386ecf4c1ce9"}, + {file = "pytest_xdist-3.2.0-py3-none-any.whl", hash = "sha256:336098e3bbd8193276867cc87db8b22903c3927665dff9d1ac8684c02f597b68"}, ] [package.dependencies] @@ -1633,14 +1635,14 @@ jeepney = ">=0.6" [[package]] name = "setuptools" -version = "67.0.0" +version = "67.3.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "setuptools-67.0.0-py3-none-any.whl", hash = "sha256:9d790961ba6219e9ff7d9557622d2fe136816a264dd01d5997cfc057d804853d"}, - {file = "setuptools-67.0.0.tar.gz", hash = "sha256:883131c5b6efa70b9101c7ef30b2b7b780a4283d5fc1616383cdf22c83cbefe6"}, + {file = "setuptools-67.3.2-py3-none-any.whl", hash = "sha256:bb6d8e508de562768f2027902929f8523932fcd1fb784e6d573d2cafac995a48"}, + {file = "setuptools-67.3.2.tar.gz", hash = "sha256:95f00380ef2ffa41d9bba85d95b27689d923c93dfbafed4aecd7cf988a25e012"}, ] [package.extras] @@ -1698,14 +1700,14 @@ files = [ [[package]] name = "trove-classifiers" -version = "2023.1.20" +version = "2023.2.8" description = "Canonical source for classifiers on PyPI (pypi.org)." category = "main" optional = false python-versions = "*" files = [ - {file = "trove-classifiers-2023.1.20.tar.gz", hash = "sha256:ed3fd4e1d2ea77ca9ed379380582566e6003a54d70a3e5521a9b2a7896609362"}, - {file = "trove_classifiers-2023.1.20-py3-none-any.whl", hash = "sha256:e4fb91e744a1b8a4e416226dbd4c3a58717295f54608e412cee526287d5d14d0"}, + {file = "trove-classifiers-2023.2.8.tar.gz", hash = "sha256:3b6960fb96c1d4cc9988bdc1b90bcd65fcf5d9843d884dfc86bd674ff81a4dea"}, + {file = "trove_classifiers-2023.2.8-py3-none-any.whl", hash = "sha256:3aff899dd9792c4d095740980a5967eb98094ff881faf0b29afc775b75aaaac6"}, ] [[package]] @@ -1756,26 +1758,26 @@ files = [ [[package]] name = "types-jsonschema" -version = "4.17.0.3" +version = "4.17.0.5" description = "Typing stubs for jsonschema" category = "dev" optional = false python-versions = "*" files = [ - {file = "types-jsonschema-4.17.0.3.tar.gz", hash = "sha256:746aa466ffed9a1acc7bdbd0ac0b5e068f00be2ee008c1d1e14b0944a8c8b24b"}, - {file = "types_jsonschema-4.17.0.3-py3-none-any.whl", hash = "sha256:c8d5b26b7c8da6a48d7fb1ce029b97e0ff6e74db3727efb968c69f39ad013685"}, + {file = "types-jsonschema-4.17.0.5.tar.gz", hash = "sha256:7adc7bfca4afe291de0c93eca9367aa72a4fbe8ce87fe15642c600ad97d45dd6"}, + {file = "types_jsonschema-4.17.0.5-py3-none-any.whl", hash = "sha256:79ac8a7763fe728947af90a24168b91621edf7e8425bf3670abd4ea0d4758fba"}, ] [[package]] name = "types-requests" -version = "2.28.11.8" +version = "2.28.11.13" description = "Typing stubs for requests" category = "dev" optional = false python-versions = "*" files = [ - {file = "types-requests-2.28.11.8.tar.gz", hash = "sha256:e67424525f84adfbeab7268a159d3c633862dafae15c5b19547ce1b55954f0a3"}, - {file = "types_requests-2.28.11.8-py3-none-any.whl", hash = "sha256:61960554baca0008ae7e2db2bd3b322ca9a144d3e80ce270f5fb640817e40994"}, + {file = "types-requests-2.28.11.13.tar.gz", hash = "sha256:3fd332842e8759ea5f7eb7789df8aa772ba155216ccf10ef4aa3b0e5b42e1b46"}, + {file = "types_requests-2.28.11.13-py3-none-any.whl", hash = "sha256:94896f6f8e9f3db11e422c6e3e4abbc5d7ccace853eac74b23bdd65eeee3cdee"}, ] [package.dependencies] @@ -1783,26 +1785,26 @@ types-urllib3 = "<1.27" [[package]] name = "types-urllib3" -version = "1.26.25.4" +version = "1.26.25.6" description = "Typing stubs for urllib3" category = "dev" optional = false python-versions = "*" files = [ - {file = "types-urllib3-1.26.25.4.tar.gz", hash = "sha256:eec5556428eec862b1ac578fb69aab3877995a99ffec9e5a12cf7fbd0cc9daee"}, - {file = "types_urllib3-1.26.25.4-py3-none-any.whl", hash = "sha256:ed6b9e8a8be488796f72306889a06a3fc3cb1aa99af02ab8afb50144d7317e49"}, + {file = "types-urllib3-1.26.25.6.tar.gz", hash = "sha256:35586727cbd7751acccf2c0f34a88baffc092f435ab62458f10776466590f2d5"}, + {file = "types_urllib3-1.26.25.6-py3-none-any.whl", hash = "sha256:a6c23c41bd03e542eaee5423a018f833077b51c4bf9ceb5aa544e12b812d5604"}, ] [[package]] name = "typing-extensions" -version = "4.4.0" +version = "4.5.0" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, + {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, + {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, ] [[package]] @@ -1845,25 +1847,25 @@ testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7 [[package]] name = "virtualenv" -version = "20.17.1" +version = "20.19.0" description = "Virtual Python Environment builder" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "virtualenv-20.17.1-py3-none-any.whl", hash = "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4"}, - {file = "virtualenv-20.17.1.tar.gz", hash = "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"}, + {file = "virtualenv-20.19.0-py3-none-any.whl", hash = "sha256:54eb59e7352b573aa04d53f80fc9736ed0ad5143af445a1e539aada6eb947dd1"}, + {file = "virtualenv-20.19.0.tar.gz", hash = "sha256:37a640ba82ed40b226599c522d411e4be5edb339a0c0de030c0dc7b646d61590"}, ] [package.dependencies] distlib = ">=0.3.6,<1" filelock = ">=3.4.1,<4" importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""} -platformdirs = ">=2.4,<3" +platformdirs = ">=2.4,<4" [package.extras] -docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] -testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] +test = ["covdefaults (>=2.2.2)", "coverage (>=7.1)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23)", "pytest (>=7.2.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)"] [[package]] name = "webencodings" @@ -1964,14 +1966,14 @@ cffi = ">=1.0" [[package]] name = "zipp" -version = "3.12.0" +version = "3.14.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "zipp-3.12.0-py3-none-any.whl", hash = "sha256:9eb0a4c5feab9b08871db0d672745b53450d7f26992fd1e4653aa43345e97b86"}, - {file = "zipp-3.12.0.tar.gz", hash = "sha256:73efd63936398aac78fd92b6f4865190119d6c91b531532e798977ea8dd402eb"}, + {file = "zipp-3.14.0-py3-none-any.whl", hash = "sha256:188834565033387710d046e3fe96acfc9b5e86cbca7f39ff69cf21a4128198b7"}, + {file = "zipp-3.14.0.tar.gz", hash = "sha256:9e5421e176ef5ab4c0ad896624e87a7b2f07aca746c9b2aa305952800cb8eecb"}, ] [package.extras] @@ -1981,4 +1983,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "b200bd8e190a3e7410d91b29289e29149a8aa29fa633e344aed929ff16d7cd11" +content-hash = "d00f73e992cf3ea9c61769fe7841ce475d119422476025bab8415cd4f278ad26" diff --git a/pyproject.toml b/pyproject.toml index 618693f4332..dd927bded52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ generate-setup-file = false [tool.poetry.dependencies] python = "^3.7" -poetry-core = "1.5.0" +poetry-core = "1.5.1" poetry-plugin-export = "^1.3.0" "backports.cached-property" = { version = "^1.0.2", python = "<3.8" } build = "^0.10.0" From 0e72a55c43a993ec0258facec23416c9212964ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sat, 18 Feb 2023 13:19:46 +0100 Subject: [PATCH 100/151] release: bump version to 1.4.0 --- CHANGELOG.md | 64 +++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 +- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8575058841..7541f024e01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,66 @@ # Change Log +## [1.4.0] - 2023-02-27 + +### Added + +- **Add a modern installer (`installer.modern-installation`) for faster installation of packages and independence from pip** ([#6205](https://github.com/python-poetry/poetry/pull/6205)). +- Add support for `Private ::` trove classifiers ([#7271](https://github.com/python-poetry/poetry/pull/7271)). +- Add the version of poetry in the `@generated` comment at the beginning of the lock file ([#7339](https://github.com/python-poetry/poetry/pull/7339)). +- Add support for `virtualenvs.prefer-active-python` when running `poetry new` and `poetry init` ([#7100](https://github.com/python-poetry/poetry/pull/7100)). + +### Changed + +- **Deprecate the old installer, i.e. setting `experimental.new-installer` to `false`** ([#7358](https://github.com/python-poetry/poetry/pull/7358)). +- Remove unused `platform` field from cached package info and bump the cache version ([#7304](https://github.com/python-poetry/poetry/pull/7304)). +- Extra dependencies of the root project are now sorted in the lock file ([#7375](https://github.com/python-poetry/poetry/pull/7375)). +- Remove upper boundary for `importlib-metadata` dependency ([#7434](https://github.com/python-poetry/poetry/pull/7434)). +- Validate path dependencies during use instead of during construction ([#6844](https://github.com/python-poetry/poetry/pull/6844)). +- Remove the deprecated `repository` modules ([#7468](https://github.com/python-poetry/poetry/pull/7468)). + +### Fixed + +- Fix an issue where an unconditional dependency of an extra was not installed in specific environments ([#7175](https://github.com/python-poetry/poetry/pull/7175)). +- Fix an issue where a pre-release of a dependency was chosen even if a stable release fulfilled the constraint ([#7225](https://github.com/python-poetry/poetry/pull/7225), [#7236](https://github.com/python-poetry/poetry/pull/7236)). +- Fix an issue where HTTP redirects were not handled correctly during publishing ([#7160](https://github.com/python-poetry/poetry/pull/7160)). +- Fix an issue where `poetry check` did not handle the `-C, --directory` option correctly ([#7241](https://github.com/python-poetry/poetry/pull/7241)). +- Fix an issue where the subdirectory information of a git dependency was not written to the lock file ([#7367](https://github.com/python-poetry/poetry/pull/7367)). +- Fix an issue where the wrong Python version was selected when creating an virtual environment ([#7221](https://github.com/python-poetry/poetry/pull/7221)). +- Fix an issue where packages that should be kept were uninstalled when calling `poetry install --sync` ([#7389](https://github.com/python-poetry/poetry/pull/7389)). +- Fix an issue where an incorrect value was set for `sys.argv[0]` when running installed scripts ([#6737](https://github.com/python-poetry/poetry/pull/6737)). +- Fix an issue where hashes in `direct_url.json` files were not written according to the specification ([#7475](https://github.com/python-poetry/poetry/pull/7475)). +- Fix an issue where poetry commands failed due to special characters in the path of the project or virtual environment ([#7471](https://github.com/python-poetry/poetry/pull/7471)). +- Fix an issue where poetry crashed with a `JSONDecodeError` when running a Python script that produced certain warnings ([#6665](https://github.com/python-poetry/poetry/pull/6665)). + +### Docs + +- Add advice on how to maintain a poetry plugin ([#6977](https://github.com/python-poetry/poetry/pull/6977)). +- Update tox examples to comply with the latest tox release ([#7341](https://github.com/python-poetry/poetry/pull/7341)). +- Mention that the `poetry export` can export `constraints.txt` files ([#7383](https://github.com/python-poetry/poetry/pull/7383)). +- Add clarifications for moving configuration files ([#6864](https://github.com/python-poetry/poetry/pull/6864)). +- Mention the different types of exact version specifications ([#7503](https://github.com/python-poetry/poetry/pull/7503)). + +### poetry-core ([`1.5.1`](https://github.com/python-poetry/poetry-core/releases/tag/1.5.1)) + +- Improve marker handling ([#528](https://github.com/python-poetry/poetry-core/pull/528), +[#534](https://github.com/python-poetry/poetry-core/pull/534), +[#530](https://github.com/python-poetry/poetry-core/pull/530), +[#546](https://github.com/python-poetry/poetry-core/pull/546), +[#547](https://github.com/python-poetry/poetry-core/pull/547)). +- Validate whether dependencies referenced in `extras` are defined in the main dependency group ([#542](https://github.com/python-poetry/poetry-core/pull/542)). +- Poetry no longer generates a `setup.py` file in sdists by default ([#318](https://github.com/python-poetry/poetry-core/pull/318)). +- Fix an issue where trailing newlines were allowed in `tool.poetry.description` ([#505](https://github.com/python-poetry/poetry-core/pull/505)). +- Fix an issue where the name of the data folder in wheels was not normalized ([#532](https://github.com/python-poetry/poetry-core/pull/532)). +- Fix an issue where the order of entries in the RECORD file was not deterministic ([#545](https://github.com/python-poetry/poetry-core/pull/545)). +- Fix an issue where zero padding was not correctly handled in version comparisons ([#540](https://github.com/python-poetry/poetry-core/pull/540)). +- Fix an issue where sdist builds did not support multiple READMEs ([#486](https://github.com/python-poetry/poetry-core/pull/486)). + +### poetry-plugin-export ([`^1.3.0`](https://github.com/python-poetry/poetry-plugin-export/releases/tag/1.3.0)) + +- Fix an issue where the export failed if there was a circular dependency on the root package ([#118](https://github.com/python-poetry/poetry-plugin-export/pull/118)). + + ## [1.3.2] - 2023-01-10 ### Fixed @@ -30,6 +90,7 @@ - Introduce a top level `-C, --directory` option to set the working path ([#6810](https://github.com/python-poetry/poetry/pull/6810)). ### Changed + - **New lock file format (version 2.0)** ([#6393](https://github.com/python-poetry/poetry/pull/6393)). - Path dependency metadata is unconditionally re-locked ([#6843](https://github.com/python-poetry/poetry/pull/6843)). - URL dependency hashes are locked ([#7121](https://github.com/python-poetry/poetry/pull/7121)). @@ -1701,7 +1762,8 @@ Initial release -[Unreleased]: https://github.com/python-poetry/poetry/compare/1.3.2...master +[Unreleased]: https://github.com/python-poetry/poetry/compare/1.4.0...master +[1.4.0]: https://github.com/python-poetry/poetry/releases/tag/1.4.0 [1.3.2]: https://github.com/python-poetry/poetry/releases/tag/1.3.2 [1.3.1]: https://github.com/python-poetry/poetry/releases/tag/1.3.1 [1.3.0]: https://github.com/python-poetry/poetry/releases/tag/1.3.0 diff --git a/pyproject.toml b/pyproject.toml index dd927bded52..3e3fdb7c3e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "poetry" -version = "1.4.0.dev0" +version = "1.4.0" description = "Python dependency management and packaging made easy." authors = [ "Sébastien Eustace ", From 3e32a4f83e89b2b2bede68e0d375a70931b04a8e Mon Sep 17 00:00:00 2001 From: arl Date: Wed, 1 Mar 2023 13:43:16 -0500 Subject: [PATCH 101/151] fix: properly get and install the dependencies for an editable build (#7579) --- src/poetry/installation/chef.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/poetry/installation/chef.py b/src/poetry/installation/chef.py index d3f7b09d38e..0115e439200 100644 --- a/src/poetry/installation/chef.py +++ b/src/poetry/installation/chef.py @@ -125,13 +125,14 @@ def _prepare( error: Exception | None = None try: with redirect_stdout(stdout): + dist_format = "wheel" if not editable else "editable" env.install( builder.build_system_requires - | builder.get_requires_for_build("wheel") + | builder.get_requires_for_build(dist_format) ) path = Path( builder.build( - "wheel" if not editable else "editable", + dist_format, destination.as_posix(), ) ) From a58a0e8fb68f50d0885df6da9349be15ed1b4f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Em=C3=ADdio=20S=2ES?= Date: Sat, 4 Mar 2023 02:56:54 -0300 Subject: [PATCH 102/151] Fix: Misconfiguration in ask dependency interactive (#7569) --- src/poetry/console/commands/init.py | 9 +++++++-- src/poetry/utils/dependency_specification.py | 2 +- tests/console/commands/test_init.py | 8 ++------ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index 0393351c1d5..fdf48832ce7 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -292,6 +292,11 @@ def _determine_requirements( ) question.set_validator(self._validate_package) + follow_up_question = self.create_question( + "\nAdd a package (leave blank to skip):" + ) + follow_up_question.set_validator(self._validate_package) + package = self.ask(question) while package: constraint = self._parse_requirements([package])[0] @@ -303,7 +308,7 @@ def _determine_requirements( ): self.line(f"Adding {package}") result.append(constraint) - package = self.ask("\nAdd a package (leave blank to skip):") + package = self.ask(follow_up_question) continue canonicalized_name = canonicalize_name(constraint["name"]) @@ -371,7 +376,7 @@ def _determine_requirements( result.append(constraint) if self.io.is_interactive(): - package = self.ask("\nAdd a package (leave blank to skip):") + package = self.ask(follow_up_question) return result diff --git a/src/poetry/utils/dependency_specification.py b/src/poetry/utils/dependency_specification.py index e80a2b97019..adcfd3d55ab 100644 --- a/src/poetry/utils/dependency_specification.py +++ b/src/poetry/utils/dependency_specification.py @@ -114,7 +114,7 @@ def _parse_dependency_specification_simple( require: DependencySpec = {} if " " in pair: - name, version = pair.split(" ", 2) + name, version = pair.split(" ", 1) extras_m = re.search(r"\[([\w\d,-_]+)\]$", name) if extras_m: extras = [e.strip() for e in extras_m.group(1).split(",")] diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py index c4d0171f43a..90b50b5f3cc 100644 --- a/tests/console/commands/test_init.py +++ b/tests/console/commands/test_init.py @@ -595,9 +595,6 @@ def test_interactive_with_file_dependency( def test_interactive_with_wrong_dependency_inputs( tester: CommandTester, repo: TestRepository ): - repo.add_package(get_package("pendulum", "2.0.0")) - repo.add_package(get_package("pytest", "3.6.0")) - inputs = [ "my-package", # Package name "1.2.3", # Version @@ -606,10 +603,8 @@ def test_interactive_with_wrong_dependency_inputs( "MIT", # License "^3.8", # Python "", # Interactive packages + "foo 1.19.2", "pendulum 2.0.0 foo", # Package name and constraint (invalid) - "pendulum 2.0.0", # Package name and constraint (invalid) - "pendulum 2.0.0", # Package name and constraint (invalid) - "pendulum 2.0.0", # Package name and constraint (invalid) "pendulum@^2.0.0", # Package name and constraint (valid) "", # End package selection "", # Interactive dev packages @@ -633,6 +628,7 @@ def test_interactive_with_wrong_dependency_inputs( [tool.poetry.dependencies] python = "^3.8" +foo = "1.19.2" pendulum = "^2.0.0" [tool.poetry.group.dev.dependencies] From 6a0087b4cab3097ac332cbd649faf8cc70e1db9c Mon Sep 17 00:00:00 2001 From: Evan Rittenhouse Date: Sat, 4 Mar 2023 07:18:18 -0600 Subject: [PATCH 103/151] docs: clarify behavior of --extras and subcommands (#7563) --- docs/pyproject.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/pyproject.md b/docs/pyproject.md index 5149515df8c..a344ffc0db5 100644 --- a/docs/pyproject.md +++ b/docs/pyproject.md @@ -395,6 +395,14 @@ You can install all extras with the `--all-extras` option: poetry install --all-extras ``` +{{% note %}} +Note that `install --extras` and the variations mentioned above (`--all-extras`, `--extras foo`, etc.) only work on dependencies defined in the current project. If you want to install extras defined by dependencies, you'll have to express that in the dependency itself: +```toml +[tool.poetry.group.dev.dependencies] +fastapi = {version="^0.92.0", extras=["all"]} +``` +{{% /note %}} + When installing or specifying Poetry-built packages, the extras defined in this section can be activated as described in [PEP 508](https://www.python.org/dev/peps/pep-0508/#extras). From f27308face9b09da8b5bbd59175b1b46392eb8d5 Mon Sep 17 00:00:00 2001 From: Harry Mander <41089556+harrymander@users.noreply.github.com> Date: Sun, 5 Mar 2023 02:53:26 +1300 Subject: [PATCH 104/151] fix: append subdirectory to archive path (#7580) --- src/poetry/installation/executor.py | 27 ++++--------------------- tests/installation/test_executor.py | 31 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index eb7302aa63d..22972882fcb 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -506,7 +506,7 @@ def _install(self, operation: Install | Update) -> int: elif package.source_type == "file": archive = self._prepare_archive(operation) elif package.source_type == "directory": - archive = self._prepare_directory_archive(operation) + archive = self._prepare_archive(operation) cleanup_archive = True elif package.source_type == "url": assert package.source_url is not None @@ -569,6 +569,8 @@ def _prepare_archive(self, operation: Install | Update) -> Path: assert package.source_url is not None archive = Path(package.source_url) + if package.source_subdirectory: + archive = archive / package.source_subdirectory if not Path(package.source_url).is_absolute() and package.root_dir: archive = package.root_dir / archive @@ -576,27 +578,6 @@ def _prepare_archive(self, operation: Install | Update) -> Path: return self._chef.prepare(archive, editable=package.develop) - def _prepare_directory_archive(self, operation: Install | Update) -> Path: - package = operation.package - operation_message = self.get_operation_message(operation) - - message = ( - f" • {operation_message}:" - " Building..." - ) - self._write(operation, message) - - assert package.source_url is not None - if package.root_dir: - req = package.root_dir / package.source_url - else: - req = Path(package.source_url).resolve(strict=False) - - if package.source_subdirectory: - req /= package.source_subdirectory - - return self._prepare_archive(operation) - def _prepare_git_archive(self, operation: Install | Update) -> Path: from poetry.vcs.git import Git @@ -619,7 +600,7 @@ def _prepare_git_archive(self, operation: Install | Update) -> Path: original_url = package.source_url package._source_url = str(source.path) - archive = self._prepare_directory_archive(operation) + archive = self._prepare_archive(operation) package._source_url = original_url diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 1f2a6c37dd1..34f397e6bd4 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -776,6 +776,37 @@ def test_executor_should_write_pep610_url_references_for_git( ) +def test_executor_should_append_subdirectory_for_git( + mocker: MockerFixture, + tmp_venv: VirtualEnv, + pool: RepositoryPool, + config: Config, + io: BufferedIO, + mock_file_downloads: None, + wheel: Path, +) -> None: + package = Package( + "demo", + "0.1.2", + source_type="git", + source_reference="master", + source_resolved_reference="123456", + source_url="https://github.com/demo/subdirectories.git", + source_subdirectory="two", + ) + + chef = Chef(config, tmp_venv, Factory.create_pool(config)) + chef.set_directory_wheel(wheel) + spy = mocker.spy(chef, "prepare") + + executor = Executor(tmp_venv, pool, config, io) + executor._chef = chef + executor.execute([Install(package)]) + + archive_arg = spy.call_args[0][0] + assert archive_arg == tmp_venv.path / "src/demo/subdirectories/two" + + def test_executor_should_write_pep610_url_references_for_git_with_subdirectories( tmp_venv: VirtualEnv, pool: RepositoryPool, From 26fbfd6725d36e94a4f85737be0ef65b945f21ce Mon Sep 17 00:00:00 2001 From: quantum-byte Date: Tue, 7 Mar 2023 17:14:45 +0100 Subject: [PATCH 105/151] installer: fix handling of sdist hashes (#7594) * fix hash mismatch during installation with `no-binary` option for packages that provide suitable wheels * do not skip hash check for sdists * write hash for sdists in direct_url.json --- src/poetry/installation/chef.py | 9 +- src/poetry/installation/executor.py | 21 ++- tests/installation/test_chef.py | 83 +++++++++++- tests/installation/test_executor.py | 200 ++++++++++++++++++++++------ 4 files changed, 262 insertions(+), 51 deletions(-) diff --git a/src/poetry/installation/chef.py b/src/poetry/installation/chef.py index 0115e439200..6eb1ae3f21b 100644 --- a/src/poetry/installation/chef.py +++ b/src/poetry/installation/chef.py @@ -101,7 +101,6 @@ def prepare( if archive.is_dir(): tmp_dir = tempfile.mkdtemp(prefix="poetry-chef-") - return self._prepare(archive, Path(tmp_dir), editable=editable) return self._prepare_sdist(archive, destination=output_dir) @@ -198,13 +197,19 @@ def _should_prepare(self, archive: Path) -> bool: def _is_wheel(cls, archive: Path) -> bool: return archive.suffix == ".whl" - def get_cached_archive_for_link(self, link: Link) -> Path | None: + def get_cached_archive_for_link(self, link: Link, *, strict: bool) -> Path | None: archives = self.get_cached_archives_for_link(link) if not archives: return None candidates: list[tuple[float | None, Path]] = [] for archive in archives: + if strict: + # in strict mode return the original cached archive instead of the + # prioritized archive type. + if link.filename == archive.name: + return archive + continue if archive.suffix != ".whl": candidates.append((float("inf"), archive)) continue diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index 22972882fcb..70e61a2cea6 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -693,11 +693,12 @@ def _download_link(self, operation: Install | Update, link: Link) -> Path: package = operation.package output_dir = self._chef.get_cache_directory_for_link(link) - archive = self._chef.get_cached_archive_for_link(link) - if archive is None: - # No cached distributions was found, so we download and prepare it + # Try to get cached original package for the link provided + original_archive = self._chef.get_cached_archive_for_link(link, strict=True) + if original_archive is None: + # No cached original distributions was found, so we download and prepare it try: - archive = self._download_archive(operation, link) + original_archive = self._download_archive(operation, link) except BaseException: cache_directory = self._chef.get_cache_directory_for_link(link) cached_file = cache_directory.joinpath(link.filename) @@ -708,6 +709,13 @@ def _download_link(self, operation: Install | Update, link: Link) -> Path: raise + # Get potential higher prioritized cached archive, otherwise it will fall back + # to the original archive. + archive = self._chef.get_cached_archive_for_link(link, strict=False) + # 'archive' can at this point never be None. Since we previously downloaded + # an archive, we now should have something cached that we can use here + assert archive is not None + if archive.suffix != ".whl": message = ( f" • {self.get_operation_message(operation)}:" @@ -717,7 +725,8 @@ def _download_link(self, operation: Install | Update, link: Link) -> Path: archive = self._chef.prepare(archive, output_dir=output_dir) - self._populate_hashes_dict(archive, package) + # Use the original archive to provide the correct hash. + self._populate_hashes_dict(original_archive, package) return archive @@ -729,7 +738,7 @@ def _populate_hashes_dict(self, archive: Path, package: Package) -> None: @staticmethod def _validate_archive_hash(archive: Path, package: Package) -> str: archive_hash: str = "sha256:" + get_file_hash(archive) - known_hashes = {f["hash"] for f in package.files} + known_hashes = {f["hash"] for f in package.files if f["file"] == archive.name} if archive_hash not in known_hashes: raise RuntimeError( diff --git a/tests/installation/test_chef.py b/tests/installation/test_chef.py index e9046c5433f..19283cdeaaa 100644 --- a/tests/installation/test_chef.py +++ b/tests/installation/test_chef.py @@ -1,5 +1,8 @@ from __future__ import annotations +import os +import tempfile + from pathlib import Path from typing import TYPE_CHECKING from zipfile import ZipFile @@ -38,20 +41,80 @@ def setup(mocker: MockerFixture, pool: RepositoryPool) -> None: @pytest.mark.parametrize( - ("link", "cached"), + ("link", "strict", "available_packages"), + [ + ( + "https://files.python-poetry.org/demo-0.1.0.tar.gz", + True, + [ + Path("/cache/demo-0.1.0-py2.py3-none-any"), + Path("/cache/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl"), + Path("/cache/demo-0.1.0-cp37-cp37-macosx_10_15_x86_64.whl"), + ], + ), + ( + "https://example.com/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl", + False, + [], + ), + ], +) +def test_get_not_found_cached_archive_for_link( + config: Config, + mocker: MockerFixture, + link: str, + strict: bool, + available_packages: list[Path], +): + chef = Chef( + config, + MockEnv( + version_info=(3, 8, 3), + marker_env={"interpreter_name": "cpython", "interpreter_version": "3.8.3"}, + supported_tags=[ + Tag("cp38", "cp38", "macosx_10_15_x86_64"), + Tag("py3", "none", "any"), + ], + ), + Factory.create_pool(config), + ) + + mocker.patch.object( + chef, "get_cached_archives_for_link", return_value=available_packages + ) + + archive = chef.get_cached_archive_for_link(Link(link), strict=strict) + + assert archive is None + + +@pytest.mark.parametrize( + ("link", "cached", "strict"), [ ( "https://files.python-poetry.org/demo-0.1.0.tar.gz", "/cache/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl", + False, ), ( "https://example.com/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl", "/cache/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl", + False, + ), + ( + "https://files.python-poetry.org/demo-0.1.0.tar.gz", + "/cache/demo-0.1.0.tar.gz", + True, + ), + ( + "https://example.com/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl", + "/cache/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl", + True, ), ], ) -def test_get_cached_archive_for_link( - config: Config, mocker: MockerFixture, link: str, cached: str +def test_get_found_cached_archive_for_link( + config: Config, mocker: MockerFixture, link: str, cached: str, strict: bool ): chef = Chef( config, @@ -77,7 +140,7 @@ def test_get_cached_archive_for_link( ], ) - archive = chef.get_cached_archive_for_link(Link(link)) + archive = chef.get_cached_archive_for_link(Link(link), strict=strict) assert Path(cached) == archive @@ -153,6 +216,10 @@ def test_prepare_directory(config: Config, config_cache_dir: Path): assert wheel.name == "simple_project-1.2.3-py2.py3-none-any.whl" + assert wheel.parent.parent == Path(tempfile.gettempdir()) + # cleanup generated tmp dir artifact + os.unlink(wheel) + def test_prepare_directory_with_extensions( config: Config, config_cache_dir: Path @@ -168,8 +235,12 @@ def test_prepare_directory_with_extensions( wheel = chef.prepare(archive) + assert wheel.parent.parent == Path(tempfile.gettempdir()) assert wheel.name == f"extended-0.1-{env.supported_tags[0]}.whl" + # cleanup generated tmp dir artifact + os.unlink(wheel) + def test_prepare_directory_editable(config: Config, config_cache_dir: Path): chef = Chef(config, EnvManager.get_system_env(), Factory.create_pool(config)) @@ -178,7 +249,11 @@ def test_prepare_directory_editable(config: Config, config_cache_dir: Path): wheel = chef.prepare(archive, editable=True) + assert wheel.parent.parent == Path(tempfile.gettempdir()) assert wheel.name == "simple_project-1.2.3-py2.py3-none-any.whl" with ZipFile(wheel) as z: assert "simple_project.pth" in z.namelist() + + # cleanup generated tmp dir artifact + os.unlink(wheel) diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 34f397e6bd4..578b270354b 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -139,9 +139,14 @@ def callback( ) if not fixture.exists(): - fixture = Path(__file__).parent.parent.joinpath( - "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl" - ) + if name == "demo-0.1.0.tar.gz": + fixture = Path(__file__).parent.parent.joinpath( + "fixtures/distributions/demo-0.1.0.tar.gz" + ) + else: + fixture = Path(__file__).parent.parent.joinpath( + "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl" + ) return [200, headers, fixture.read_bytes()] @@ -533,7 +538,7 @@ def test_executor_should_delete_incomplete_downloads( ) mocker.patch( "poetry.installation.chef.Chef.get_cached_archive_for_link", - side_effect=lambda link: None, + side_effect=lambda link, strict: None, ) mocker.patch( "poetry.installation.chef.Chef.get_cache_directory_for_link", @@ -601,6 +606,12 @@ def test_executor_should_not_write_pep610_url_references_for_cached_package( io: BufferedIO, ): link_cached = fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" + package.files = [ + { + "file": "demo-0.1.0-py2.py3-none-any.whl", + "hash": "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a", # noqa: E501 + } + ] mocker.patch( "poetry.installation.executor.Executor._download", return_value=link_cached @@ -611,7 +622,7 @@ def test_executor_should_not_write_pep610_url_references_for_cached_package( verify_installed_distribution(tmp_venv, package) -def test_executor_should_write_pep610_url_references_for_files( +def test_executor_should_write_pep610_url_references_for_wheel_files( tmp_venv: VirtualEnv, pool: RepositoryPool, config: Config, io: BufferedIO ): url = ( @@ -645,6 +656,38 @@ def test_executor_should_write_pep610_url_references_for_files( verify_installed_distribution(tmp_venv, package, expected_url_reference) +def test_executor_should_write_pep610_url_references_for_non_wheel_files( + tmp_venv: VirtualEnv, pool: RepositoryPool, config: Config, io: BufferedIO +): + url = ( + Path(__file__) + .parent.parent.joinpath("fixtures/distributions/demo-0.1.0.tar.gz") + .resolve() + ) + package = Package("demo", "0.1.0", source_type="file", source_url=url.as_posix()) + # Set package.files so the executor will attempt to hash the package + package.files = [ + { + "file": "demo-0.1.0.tar.gz", + "hash": "sha256:9fa123ad707a5c6c944743bf3e11a0e80d86cb518d3cf25320866ca3ef43e2ad", # noqa: E501 + } + ] + + executor = Executor(tmp_venv, pool, config, io) + executor.execute([Install(package)]) + expected_url_reference = { + "archive_info": { + "hashes": { + "sha256": ( + "9fa123ad707a5c6c944743bf3e11a0e80d86cb518d3cf25320866ca3ef43e2ad" + ) + }, + }, + "url": url.as_uri(), + } + verify_installed_distribution(tmp_venv, package, expected_url_reference) + + def test_executor_should_write_pep610_url_references_for_directories( tmp_venv: VirtualEnv, pool: RepositoryPool, @@ -703,13 +746,25 @@ def test_executor_should_write_pep610_url_references_for_editable_directories( ) -def test_executor_should_write_pep610_url_references_for_urls( +@pytest.mark.parametrize("is_artifact_cached", [False, True]) +def test_executor_should_write_pep610_url_references_for_wheel_urls( tmp_venv: VirtualEnv, pool: RepositoryPool, config: Config, io: BufferedIO, mock_file_downloads: None, + mocker: MockerFixture, + fixture_dir: FixtureDirGetter, + is_artifact_cached: bool, ): + if is_artifact_cached: + link_cached = fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" + mocker.patch( + "poetry.installation.chef.Chef.get_cached_archive_for_link", + return_value=link_cached, + ) + download_spy = mocker.spy(Executor, "_download_archive") + package = Package( "demo", "0.1.0", @@ -725,7 +780,8 @@ def test_executor_should_write_pep610_url_references_for_urls( ] executor = Executor(tmp_venv, pool, config, io) - executor.execute([Install(package)]) + operation = Install(package) + executor.execute([operation]) expected_url_reference = { "archive_info": { "hashes": { @@ -737,6 +793,104 @@ def test_executor_should_write_pep610_url_references_for_urls( "url": package.source_url, } verify_installed_distribution(tmp_venv, package, expected_url_reference) + if is_artifact_cached: + download_spy.assert_not_called() + else: + download_spy.assert_called_once_with( + mocker.ANY, operation, Link(package.source_url) + ) + + +@pytest.mark.parametrize( + ( + "is_sdist_cached", + "is_wheel_cached", + "expect_artifact_building", + "expect_artifact_download", + ), + [ + (True, False, True, False), + (True, True, False, False), + (False, False, True, True), + (False, True, False, True), + ], +) +def test_executor_should_write_pep610_url_references_for_non_wheel_urls( + tmp_venv: VirtualEnv, + pool: RepositoryPool, + config: Config, + io: BufferedIO, + mock_file_downloads: None, + mocker: MockerFixture, + fixture_dir: FixtureDirGetter, + is_sdist_cached: bool, + is_wheel_cached: bool, + expect_artifact_building: bool, + expect_artifact_download: bool, +): + built_wheel = fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" + mock_prepare = mocker.patch( + "poetry.installation.chef.Chef._prepare", + return_value=built_wheel, + ) + download_spy = mocker.spy(Executor, "_download_archive") + + if is_sdist_cached | is_wheel_cached: + cached_sdist = fixture_dir("distributions") / "demo-0.1.0.tar.gz" + cached_wheel = fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" + + def mock_get_cached_archive_for_link_func(_: Link, strict: bool): + if is_wheel_cached and not strict: + return cached_wheel + if is_sdist_cached: + return cached_sdist + return None + + mocker.patch( + "poetry.installation.chef.Chef.get_cached_archive_for_link", + side_effect=mock_get_cached_archive_for_link_func, + ) + + package = Package( + "demo", + "0.1.0", + source_type="url", + source_url="https://files.pythonhosted.org/demo-0.1.0.tar.gz", + ) + # Set package.files so the executor will attempt to hash the package + package.files = [ + { + "file": "demo-0.1.0.tar.gz", + "hash": "sha256:9fa123ad707a5c6c944743bf3e11a0e80d86cb518d3cf25320866ca3ef43e2ad", # noqa: E501 + } + ] + + executor = Executor(tmp_venv, pool, config, io) + operation = Install(package) + executor.execute([operation]) + expected_url_reference = { + "archive_info": { + "hashes": { + "sha256": ( + "9fa123ad707a5c6c944743bf3e11a0e80d86cb518d3cf25320866ca3ef43e2ad" + ) + }, + }, + "url": package.source_url, + } + verify_installed_distribution(tmp_venv, package, expected_url_reference) + + if expect_artifact_building: + mock_prepare.assert_called_once() + else: + mock_prepare.assert_not_called() + + if expect_artifact_download: + download_spy.assert_called_once_with( + mocker.ANY, operation, Link(package.source_url) + ) + else: + download_spy.assert_not_called() def test_executor_should_write_pep610_url_references_for_git( @@ -846,38 +1000,6 @@ def test_executor_should_write_pep610_url_references_for_git_with_subdirectories ) -def test_executor_should_use_cached_link_and_hash( - tmp_venv: VirtualEnv, - pool: RepositoryPool, - config: Config, - io: BufferedIO, - mocker: MockerFixture, - fixture_dir: FixtureDirGetter, -): - link_cached = fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" - - mocker.patch( - "poetry.installation.chef.Chef.get_cached_archive_for_link", - return_value=link_cached, - ) - - package = Package("demo", "0.1.0") - # Set package.files so the executor will attempt to hash the package - package.files = [ - { - "file": "demo-0.1.0-py2.py3-none-any.whl", - "hash": "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a", # noqa: E501 - } - ] - - executor = Executor(tmp_venv, pool, config, io) - archive = executor._download_link( - Install(package), - Link("https://example.com/demo-0.1.0-py2.py3-none-any.whl"), - ) - assert archive == link_cached - - @pytest.mark.parametrize( ("max_workers", "cpu_count", "side_effect", "expected_workers"), [ From 40061f9d1351a9f76d6bdc1db8b3d903864c1373 Mon Sep 17 00:00:00 2001 From: Jack Desert Date: Mon, 6 Mar 2023 11:45:40 -0500 Subject: [PATCH 106/151] Offer workaround for the cases where Poetry is downloading several versions of a package that does not have its metada correctly declared. --- docs/faq.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 5280072037b..2a2051d294a 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -22,10 +22,14 @@ and, as such, they are not available via the PyPI JSON API. At this point, Poetr but to download the packages and inspect them to get the necessary information. This is an expensive operation, both in bandwidth and time, which is why it seems this is a long process. -At the moment there is no way around it. +At the moment there is no way around it. However, if you notice that Poetry +is downloading many versions of a single package, you can lessen the workload +by constraining that one package in your pyproject.toml more narrowly. That way +Poetry does not have to sift through so many versions of it, which may speed up +the locking process considerably in some cases. {{% note %}} -Once Poetry has cached the releases' information, the dependency resolution process +Once Poetry has cached the releases' information on your machine, the dependency resolution process will be much faster. {{% /note %}} From 4a27e8fcad8ceaa88101ae7bfd718f4e1a4e9439 Mon Sep 17 00:00:00 2001 From: Mike Lundy Date: Fri, 17 Mar 2023 10:44:51 -0700 Subject: [PATCH 107/151] add missing env keyword in Env._run (#7626) The env keyword contains the modified path (that uses get_temp_environ) so without it, anything using the `input` path here would get the wrong env. fixes python-poetry#7623 --- src/poetry/utils/env.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 8d897fd2ac9..d5755d152c3 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -1533,6 +1533,7 @@ def _run(self, cmd: list[str], **kwargs: Any) -> int | str: stderr=stderr, input=encode(input_), check=True, + env=env, **kwargs, ).stdout elif call: From 8db1f00d3de3ff59ff3e254ede8b8ef3afb15ffe Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sat, 18 Mar 2023 13:49:50 +0000 Subject: [PATCH 108/151] update installer to 0.7.0 (#7671) --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- src/poetry/installation/wheel_installer.py | 3 ++- .../demo-0.1.2-py2.py3-none-any.whl | Bin 1537 -> 1552 bytes .../dists/pytest-3.5.1-py2.py3-none-any.whl | Bin 3693 -> 3708 bytes 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2588298f006..dff3d459c4c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. [[package]] name = "attrs" @@ -731,14 +731,14 @@ files = [ [[package]] name = "installer" -version = "0.6.0" +version = "0.7.0" description = "A library for installing Python wheels." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "installer-0.6.0-py3-none-any.whl", hash = "sha256:ae7c62d1d6158b5c096419102ad0d01fdccebf857e784cee57f94165635fe038"}, - {file = "installer-0.6.0.tar.gz", hash = "sha256:f3bd36cd261b440a88a1190b1becca0578fee90b4b62decc796932fdd5ae8839"}, + {file = "installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53"}, + {file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"}, ] [[package]] @@ -1983,4 +1983,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "d00f73e992cf3ea9c61769fe7841ce475d119422476025bab8415cd4f278ad26" +content-hash = "1b804b8be7ae9a7e94c4959d138908d8647410750fc9de0a5b524870a4238bdd" diff --git a/pyproject.toml b/pyproject.toml index 3e3fdb7c3e4..27a02f2a2a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,7 @@ dulwich = "^0.21.2" filelock = "^3.8.0" html5lib = "^1.0" importlib-metadata = { version = ">=4.4", python = "<3.10" } -installer = "^0.6.0" +installer = "^0.7.0" jsonschema = "^4.10.0" keyring = "^23.9.0" lockfile = "^0.12.2" diff --git a/src/poetry/installation/wheel_installer.py b/src/poetry/installation/wheel_installer.py index c8df26960f8..16de86e3022 100644 --- a/src/poetry/installation/wheel_installer.py +++ b/src/poetry/installation/wheel_installer.py @@ -97,7 +97,8 @@ def enable_bytecode_compilation(self, enable: bool = True) -> None: self._destination.bytecode_optimization_levels = (1,) if enable else () def install(self, wheel: Path) -> None: - with WheelFile.open(Path(wheel.as_posix())) as source: + with WheelFile.open(wheel) as source: + source.validate_record() install( source=source, destination=self._destination.for_source(source), diff --git a/tests/fixtures/distributions/demo-0.1.2-py2.py3-none-any.whl b/tests/fixtures/distributions/demo-0.1.2-py2.py3-none-any.whl index a01175c144ef549ae2f4e407eb436e3a1270106d..9f1ce9264e8701f49071acecb57f0cfe9dffea1b 100644 GIT binary patch delta 380 zcmZqVnZUDQ1ET~30|N&G`I2oAZ?uev- zaA^fM10%}|W(Ec@k$cWDyjg*P;lb}ZZmU)n%aRQVm(4r2r5E3B4WHn3YsH4I*VeE1 zp8Jh`-}jr(!|ku%lD)g6^iLVv!ul4;ee0f_RuG-F@7LGwKX$IItvet;=Y!rN{+6tb zF`mLw8+S>5V>mSPZ)*A?6~APOXz^tyWTWl#z0Mt#k62dv+~Z`~-HvM&xAkTvTzsc_ zTHIUs?q`+E=;ZfW3#Ir!{V~$0jNPzE@89HR$1d!)$Hik< zb56sq!r{^rqg@ZTXB}pk@t0xqdL~vT4LN2J9N_^9Ck6&!&@e1%T+KMqUK|uS5}_fi mkibC;n8~uNT1=;yK*|zWgBhbHZ(()jieX}4mgRcTtl81H*;A z>7D`}KX<%nnx(+EP^63HPKpSZ%gRjODEsB*F*`oXKG+(6zv$FX3y!lpKNd4)-MG>B zdzVkyZxz>HCMnlHuGd+zzpVd=vhAF;dnA{4TV#qX{&;&I+cw*s<`+zK4x2?SG!bd{ z^NV!hsBM`T`sGk}!PS0^;?sIQDXUzsUi*01mD#={vn=-VKK;y7Dj)tnPju>HV0Zms z@$lL7w%t1SPH7z1pK;c7{o{|DomSbj{ePaNk@W}K|ZA`bGtG#7&cSeTJPf`Nf`;qlFx*BKd%E}a0< lllxgECR?*gGp%O=E6-;QX0)GtnAMppgNcEm85pb#3;S8EEx{v>SAISb?oF)}d7GRRE6%PvYi~N5V|8ID1WxLqx-KpM-wvm8dDt*(~3=NK3mgcunZWG4nlO|IuaO~sm z&ihLYy4F74sX#g}g~^ZxvMo6&V}$6Jmz=XZ~v z20y;>-{+%Gd28RK`BzqDth)bMuS6}m%TDUh+2hOem) Date: Sat, 18 Mar 2023 18:22:13 +0100 Subject: [PATCH 109/151] chore: update poetry-core (#7676) --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index dff3d459c4c..36bd49a456a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1149,14 +1149,14 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "poetry-core" -version = "1.5.1" +version = "1.5.2" description = "Poetry PEP 517 Build Backend" category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "poetry_core-1.5.1-py3-none-any.whl", hash = "sha256:b1900dea81eb18feb7323d404e5f10430205541a4a683a912893f9d2b5807797"}, - {file = "poetry_core-1.5.1.tar.gz", hash = "sha256:41887261358863f25831fa0ad1fe7e451fc32d1c81fcf7710ba5174cc0047c6d"}, + {file = "poetry_core-1.5.2-py3-none-any.whl", hash = "sha256:832d40a1ea5fd10c0f648d0575cadddc8b79f06f91d83a1f1a73a7e1dfacfbd7"}, + {file = "poetry_core-1.5.2.tar.gz", hash = "sha256:c6556c3b1ec5b8668e6ef5a4494726bc41d31907339425e194e78a6178436c14"}, ] [package.dependencies] @@ -1983,4 +1983,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "1b804b8be7ae9a7e94c4959d138908d8647410750fc9de0a5b524870a4238bdd" +content-hash = "fb909b5c273da18b6715b134312d9a97edfa8dbfc2c7807fde3ace3d179c21ff" diff --git a/pyproject.toml b/pyproject.toml index 27a02f2a2a4..d01abd38610 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ generate-setup-file = false [tool.poetry.dependencies] python = "^3.7" -poetry-core = "1.5.1" +poetry-core = "1.5.2" poetry-plugin-export = "^1.3.0" "backports.cached-property" = { version = "^1.0.2", python = "<3.8" } build = "^0.10.0" From 4dfb894001360e0972fb9970ed5ad6bbbd17250a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sat, 18 Mar 2023 20:57:37 +0100 Subject: [PATCH 110/151] installer: fix optimization level (#7666) --- src/poetry/installation/wheel_installer.py | 2 +- tests/installation/test_wheel_installer.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/poetry/installation/wheel_installer.py b/src/poetry/installation/wheel_installer.py index 16de86e3022..ab2e0a82f3e 100644 --- a/src/poetry/installation/wheel_installer.py +++ b/src/poetry/installation/wheel_installer.py @@ -94,7 +94,7 @@ def __init__(self, env: Env) -> None: ) def enable_bytecode_compilation(self, enable: bool = True) -> None: - self._destination.bytecode_optimization_levels = (1,) if enable else () + self._destination.bytecode_optimization_levels = (-1,) if enable else () def install(self, wheel: Path) -> None: with WheelFile.open(wheel) as source: diff --git a/tests/installation/test_wheel_installer.py b/tests/installation/test_wheel_installer.py index 7fc4826f940..b7b3d7c7c93 100644 --- a/tests/installation/test_wheel_installer.py +++ b/tests/installation/test_wheel_installer.py @@ -77,5 +77,7 @@ def test_enable_bytecode_compilation( if compile: assert cache_dir.exists() assert list(cache_dir.glob("*.pyc")) + assert not list(cache_dir.glob("*.opt-1.pyc")) + assert not list(cache_dir.glob("*.opt-2.pyc")) else: assert not cache_dir.exists() From b8e912dc007c0c1138c300093d54884568007c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sat, 18 Mar 2023 21:14:19 +0100 Subject: [PATCH 111/151] installer: improve error messages for building dependencies (#7667) --- src/poetry/installation/executor.py | 21 +++++- .../README.rst | 2 + .../pyproject.toml | 29 ++++++++ .../simple_project/__init__.py | 0 tests/installation/test_executor.py | 68 ++++++++++++++++++- 5 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 tests/fixtures/build_system_requires_not_available/README.rst create mode 100644 tests/fixtures/build_system_requires_not_available/pyproject.toml create mode 100644 tests/fixtures/build_system_requires_not_available/simple_project/__init__.py diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index 70e61a2cea6..15ec0c0c47e 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -24,6 +24,7 @@ from poetry.installation.operations import Uninstall from poetry.installation.operations import Update from poetry.installation.wheel_installer import WheelInstaller +from poetry.puzzle.exceptions import SolverProblemError from poetry.utils._compat import decode from poetry.utils.authenticator import Authenticator from poetry.utils.env import EnvCommandError @@ -301,7 +302,14 @@ def _execute_operation(self, operation: Operation) -> None: trace.render(io) if isinstance(e, ChefBuildError): pkg = operation.package - requirement = pkg.to_dependency().to_pep_508() + pip_command = "pip wheel --use-pep517" + if pkg.develop: + requirement = pkg.source_url + pip_command += " --editable" + else: + requirement = ( + pkg.to_dependency().to_pep_508().split(";")[0].strip() + ) io.write_line("") io.write_line( "" @@ -309,9 +317,18 @@ def _execute_operation(self, operation: Operation) -> None: " and is likely not a problem with poetry" f" but with {pkg.pretty_name} ({pkg.full_pretty_version})" " not supporting PEP 517 builds. You can verify this by" - f" running 'pip wheel --use-pep517 \"{requirement}\"'." + f" running '{pip_command} \"{requirement}\"'." "" ) + elif isinstance(e, SolverProblemError): + pkg = operation.package + io.write_line("") + io.write_line( + "" + "Cannot resolve build-system.requires" + f" for {pkg.pretty_name}." + "" + ) io.write_line("") finally: with self._lock: diff --git a/tests/fixtures/build_system_requires_not_available/README.rst b/tests/fixtures/build_system_requires_not_available/README.rst new file mode 100644 index 00000000000..f7fe15470f9 --- /dev/null +++ b/tests/fixtures/build_system_requires_not_available/README.rst @@ -0,0 +1,2 @@ +My Package +========== diff --git a/tests/fixtures/build_system_requires_not_available/pyproject.toml b/tests/fixtures/build_system_requires_not_available/pyproject.toml new file mode 100644 index 00000000000..bfe752e82ba --- /dev/null +++ b/tests/fixtures/build_system_requires_not_available/pyproject.toml @@ -0,0 +1,29 @@ +[tool.poetry] +name = "simple-project" +version = "1.2.3" +description = "Some description." +authors = [ + "Sébastien Eustace " +] +license = "MIT" + +readme = ["README.rst"] + +homepage = "https://python-poetry.org" +repository = "https://github.com/python-poetry/poetry" +documentation = "https://python-poetry.org/docs" + +keywords = ["packaging", "dependency", "poetry"] + +classifiers = [ + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Libraries :: Python Modules" +] + +# Requirements +[tool.poetry.dependencies] +python = "^3.7" + +[build-system] +requires = ["poetry-core==0.999"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/fixtures/build_system_requires_not_available/simple_project/__init__.py b/tests/fixtures/build_system_requires_not_available/simple_project/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 578b270354b..c25c331d1ef 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -22,6 +22,7 @@ from cleo.io.outputs.output import Verbosity from poetry.core.packages.package import Package from poetry.core.packages.utils.link import Link +from poetry.core.packages.utils.utils import path_to_url from poetry.factory import Factory from poetry.installation.chef import Chef as BaseChef @@ -1093,8 +1094,10 @@ def test_executor_fallback_on_poetry_create_error_without_wheel_installer( @pytest.mark.parametrize("failing_method", ["build", "get_requires_for_build"]) +@pytest.mark.parametrize("editable", [False, True]) def test_build_backend_errors_are_reported_correctly_if_caused_by_subprocess( failing_method: str, + editable: bool, mocker: MockerFixture, config: Config, pool: RepositoryPool, @@ -1102,7 +1105,7 @@ def test_build_backend_errors_are_reported_correctly_if_caused_by_subprocess( tmp_dir: str, mock_file_downloads: None, env: MockEnv, -): +) -> None: error = BuildBackendException( CalledProcessError(1, ["pip"], output=b"Error on stdout") ) @@ -1121,7 +1124,10 @@ def test_build_backend_errors_are_reported_correctly_if_caused_by_subprocess( .parent.parent.joinpath("fixtures/simple_project") .resolve() .as_posix(), + develop=editable, ) + # must not be included in the error message + directory_package.python_versions = ">=3.7" return_code = executor.execute( [ @@ -1145,14 +1151,70 @@ def test_build_backend_errors_are_reported_correctly_if_caused_by_subprocess( Error on stdout """ - requirement = directory_package.to_dependency().to_pep_508() + if editable: + pip_command = "pip wheel --use-pep517 --editable" + requirement = directory_package.source_url + assert Path(requirement).exists() + else: + pip_command = "pip wheel --use-pep517" + requirement = f"{package_name} @ {path_to_url(directory_package.source_url)}" expected_end = f""" Note: This error originates from the build backend, and is likely not a problem with \ poetry but with {package_name} ({package_version} {package_url}) not supporting \ -PEP 517 builds. You can verify this by running 'pip wheel --use-pep517 "{requirement}"'. +PEP 517 builds. You can verify this by running '{pip_command} "{requirement}"'. """ output = io.fetch_output() assert output.startswith(expected_start) assert output.endswith(expected_end) + + +def test_build_system_requires_not_available( + config: Config, + pool: RepositoryPool, + io: BufferedIO, + tmp_dir: str, + mock_file_downloads: None, + env: MockEnv, + fixture_dir: FixtureDirGetter, +) -> None: + io.set_verbosity(Verbosity.NORMAL) + + executor = Executor(env, pool, config, io) + + package_name = "simple-project" + package_version = "1.2.3" + directory_package = Package( + package_name, + package_version, + source_type="directory", + source_url=fixture_dir("build_system_requires_not_available") + .resolve() + .as_posix(), + ) + + return_code = executor.execute( + [ + Install(directory_package), + ] + ) + + assert return_code == 1 + + package_url = directory_package.source_url + expected_start = f"""\ +Package operations: 1 install, 0 updates, 0 removals + + • Installing {package_name} ({package_version} {package_url}) + + SolveFailure + + Because -root- depends on poetry-core (0.999) which doesn't match any versions,\ + version solving failed. +""" + expected_end = "Cannot resolve build-system.requires for simple-project." + + output = io.fetch_output().strip() + assert output.startswith(expected_start) + assert output.endswith(expected_end) From 36ca3271c2ab1354b9328d831db8e3f901cfb5f4 Mon Sep 17 00:00:00 2001 From: Riccardo Albertazzi Date: Sun, 19 Mar 2023 14:41:30 +0100 Subject: [PATCH 112/151] refactor: extract cache utilities (#7621) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Randy Döring <30527984+radoering@users.noreply.github.com> --- src/poetry/config/config.py | 6 +- src/poetry/installation/chef.py | 89 ++---------- src/poetry/installation/chooser.py | 35 +---- src/poetry/installation/executor.py | 24 +++- src/poetry/utils/cache.py | 90 ++++++++++++ src/poetry/utils/wheel.py | 47 ++++++ tests/installation/test_chef.py | 214 ++++++---------------------- tests/installation/test_executor.py | 149 +++++++++---------- tests/utils/test_cache.py | 144 ++++++++++++++++++- 9 files changed, 419 insertions(+), 379 deletions(-) create mode 100644 src/poetry/utils/wheel.py diff --git a/src/poetry/config/config.py b/src/poetry/config/config.py index 6eda9699870..49b4631fbf4 100644 --- a/src/poetry/config/config.py +++ b/src/poetry/config/config.py @@ -210,7 +210,11 @@ def _get_environment_repositories() -> dict[str, dict[str, str]]: @property def repository_cache_directory(self) -> Path: - return Path(self.get("cache-dir")) / "cache" / "repositories" + return Path(self.get("cache-dir")).expanduser() / "cache" / "repositories" + + @property + def artifacts_cache_directory(self) -> Path: + return Path(self.get("cache-dir")).expanduser() / "artifacts" @property def virtualenvs_path(self) -> Path: diff --git a/src/poetry/installation/chef.py b/src/poetry/installation/chef.py index 6eb1ae3f21b..5f57ba3e27b 100644 --- a/src/poetry/installation/chef.py +++ b/src/poetry/installation/chef.py @@ -1,7 +1,5 @@ from __future__ import annotations -import hashlib -import json import tarfile import tempfile import zipfile @@ -19,18 +17,14 @@ from poetry.core.utils.helpers import temporary_directory from pyproject_hooks import quiet_subprocess_runner # type: ignore[import] -from poetry.installation.chooser import InvalidWheelName -from poetry.installation.chooser import Wheel from poetry.utils.env import ephemeral_environment if TYPE_CHECKING: from contextlib import AbstractContextManager - from poetry.core.packages.utils.link import Link - - from poetry.config.config import Config from poetry.repositories import RepositoryPool + from poetry.utils.cache import ArtifactCache from poetry.utils.env import Env @@ -86,12 +80,12 @@ def install(self, requirements: Collection[str]) -> None: class Chef: - def __init__(self, config: Config, env: Env, pool: RepositoryPool) -> None: + def __init__( + self, artifact_cache: ArtifactCache, env: Env, pool: RepositoryPool + ) -> None: self._env = env self._pool = pool - self._cache_dir = ( - Path(config.get("cache-dir")).expanduser().joinpath("artifacts") - ) + self._artifact_cache = artifact_cache def prepare( self, archive: Path, output_dir: Path | None = None, *, editable: bool = False @@ -181,7 +175,9 @@ def _prepare_sdist(self, archive: Path, destination: Path | None = None) -> Path sdist_dir = archive_dir if destination is None: - destination = self.get_cache_directory_for_link(Link(archive.as_uri())) + destination = self._artifact_cache.get_cache_directory_for_link( + Link(archive.as_uri()) + ) destination.mkdir(parents=True, exist_ok=True) @@ -196,72 +192,3 @@ def _should_prepare(self, archive: Path) -> bool: @classmethod def _is_wheel(cls, archive: Path) -> bool: return archive.suffix == ".whl" - - def get_cached_archive_for_link(self, link: Link, *, strict: bool) -> Path | None: - archives = self.get_cached_archives_for_link(link) - if not archives: - return None - - candidates: list[tuple[float | None, Path]] = [] - for archive in archives: - if strict: - # in strict mode return the original cached archive instead of the - # prioritized archive type. - if link.filename == archive.name: - return archive - continue - if archive.suffix != ".whl": - candidates.append((float("inf"), archive)) - continue - - try: - wheel = Wheel(archive.name) - except InvalidWheelName: - continue - - if not wheel.is_supported_by_environment(self._env): - continue - - candidates.append( - (wheel.get_minimum_supported_index(self._env.supported_tags), archive), - ) - - if not candidates: - return None - - return min(candidates)[1] - - def get_cached_archives_for_link(self, link: Link) -> list[Path]: - cache_dir = self.get_cache_directory_for_link(link) - - archive_types = ["whl", "tar.gz", "tar.bz2", "bz2", "zip"] - paths = [] - for archive_type in archive_types: - for archive in cache_dir.glob(f"*.{archive_type}"): - paths.append(Path(archive)) - - return paths - - def get_cache_directory_for_link(self, link: Link) -> Path: - key_parts = {"url": link.url_without_fragment} - - if link.hash_name is not None and link.hash is not None: - key_parts[link.hash_name] = link.hash - - if link.subdirectory_fragment: - key_parts["subdirectory"] = link.subdirectory_fragment - - key_parts["interpreter_name"] = self._env.marker_env["interpreter_name"] - key_parts["interpreter_version"] = "".join( - self._env.marker_env["interpreter_version"].split(".")[:2] - ) - - key = hashlib.sha256( - json.dumps( - key_parts, sort_keys=True, separators=(",", ":"), ensure_ascii=True - ).encode("ascii") - ).hexdigest() - - split_key = [key[:2], key[2:4], key[4:6], key[6:]] - - return self._cache_dir.joinpath(*split_key) diff --git a/src/poetry/installation/chooser.py b/src/poetry/installation/chooser.py index 821c09b38c8..b484504ed9a 100644 --- a/src/poetry/installation/chooser.py +++ b/src/poetry/installation/chooser.py @@ -6,11 +6,9 @@ from typing import TYPE_CHECKING from typing import Any -from packaging.tags import Tag - from poetry.config.config import Config from poetry.config.config import PackageFilterPolicy -from poetry.utils.patterns import wheel_file_re +from poetry.utils.wheel import Wheel if TYPE_CHECKING: @@ -25,37 +23,6 @@ logger = logging.getLogger(__name__) -class InvalidWheelName(Exception): - pass - - -class Wheel: - def __init__(self, filename: str) -> None: - wheel_info = wheel_file_re.match(filename) - if not wheel_info: - raise InvalidWheelName(f"{filename} is not a valid wheel filename.") - - self.filename = filename - self.name = wheel_info.group("name").replace("_", "-") - self.version = wheel_info.group("ver").replace("_", "-") - self.build_tag = wheel_info.group("build") - self.pyversions = wheel_info.group("pyver").split(".") - self.abis = wheel_info.group("abi").split(".") - self.plats = wheel_info.group("plat").split(".") - - self.tags = { - Tag(x, y, z) for x in self.pyversions for y in self.abis for z in self.plats - } - - def get_minimum_supported_index(self, tags: list[Tag]) -> int | None: - indexes = [tags.index(t) for t in self.tags if t in tags] - - return min(indexes) if indexes else None - - def is_supported_by_environment(self, env: Env) -> bool: - return bool(set(env.supported_tags).intersection(self.tags)) - - class Chooser: """ A Chooser chooses an appropriate release archive for packages. diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index 15ec0c0c47e..ebbba0a4d6c 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -27,6 +27,7 @@ from poetry.puzzle.exceptions import SolverProblemError from poetry.utils._compat import decode from poetry.utils.authenticator import Authenticator +from poetry.utils.cache import ArtifactCache from poetry.utils.env import EnvCommandError from poetry.utils.helpers import atomic_open from poetry.utils.helpers import get_file_hash @@ -77,10 +78,11 @@ def __init__( else: self._max_workers = 1 + self._artifact_cache = ArtifactCache(cache_dir=config.artifacts_cache_directory) self._authenticator = Authenticator( config, self._io, disable_cache=disable_cache, pool_size=self._max_workers ) - self._chef = Chef(config, self._env, pool) + self._chef = Chef(self._artifact_cache, self._env, pool) self._chooser = Chooser(pool, self._env, config) self._executor = ThreadPoolExecutor(max_workers=self._max_workers) @@ -709,15 +711,19 @@ def _download(self, operation: Install | Update) -> Path: def _download_link(self, operation: Install | Update, link: Link) -> Path: package = operation.package - output_dir = self._chef.get_cache_directory_for_link(link) + output_dir = self._artifact_cache.get_cache_directory_for_link(link) # Try to get cached original package for the link provided - original_archive = self._chef.get_cached_archive_for_link(link, strict=True) + original_archive = self._artifact_cache.get_cached_archive_for_link( + link, strict=True + ) if original_archive is None: # No cached original distributions was found, so we download and prepare it try: original_archive = self._download_archive(operation, link) except BaseException: - cache_directory = self._chef.get_cache_directory_for_link(link) + cache_directory = self._artifact_cache.get_cache_directory_for_link( + link + ) cached_file = cache_directory.joinpath(link.filename) # We can't use unlink(missing_ok=True) because it's not available # prior to Python 3.8 @@ -728,7 +734,11 @@ def _download_link(self, operation: Install | Update, link: Link) -> Path: # Get potential higher prioritized cached archive, otherwise it will fall back # to the original archive. - archive = self._chef.get_cached_archive_for_link(link, strict=False) + archive = self._artifact_cache.get_cached_archive_for_link( + link, + strict=False, + env=self._env, + ) # 'archive' can at this point never be None. Since we previously downloaded # an archive, we now should have something cached that we can use here assert archive is not None @@ -792,7 +802,9 @@ def _download_archive(self, operation: Install | Update, link: Link) -> Path: progress.start() done = 0 - archive = self._chef.get_cache_directory_for_link(link) / link.filename + archive = ( + self._artifact_cache.get_cache_directory_for_link(link) / link.filename + ) archive.parent.mkdir(parents=True, exist_ok=True) with atomic_open(archive) as f: for chunk in response.iter_content(chunk_size=4096): diff --git a/src/poetry/utils/cache.py b/src/poetry/utils/cache.py index ba88a077055..99bd5b40cee 100644 --- a/src/poetry/utils/cache.py +++ b/src/poetry/utils/cache.py @@ -8,11 +8,21 @@ import time from pathlib import Path +from typing import TYPE_CHECKING from typing import Any from typing import Callable from typing import Generic from typing import TypeVar +from poetry.utils.wheel import InvalidWheelName +from poetry.utils.wheel import Wheel + + +if TYPE_CHECKING: + from poetry.core.packages.utils.link import Link + + from poetry.utils.env import Env + # Used by Cachy for items that do not expire. MAX_DATE = 9999999999 @@ -196,3 +206,83 @@ def _deserialize(self, data_raw: bytes) -> CacheItem[T]: data = json.loads(data_str[10:]) expires = int(data_str[:10]) return CacheItem(data, expires) + + +class ArtifactCache: + def __init__(self, *, cache_dir: Path) -> None: + self._cache_dir = cache_dir + + def get_cache_directory_for_link(self, link: Link) -> Path: + key_parts = {"url": link.url_without_fragment} + + if link.hash_name is not None and link.hash is not None: + key_parts[link.hash_name] = link.hash + + if link.subdirectory_fragment: + key_parts["subdirectory"] = link.subdirectory_fragment + + key = hashlib.sha256( + json.dumps( + key_parts, sort_keys=True, separators=(",", ":"), ensure_ascii=True + ).encode("ascii") + ).hexdigest() + + split_key = [key[:2], key[2:4], key[4:6], key[6:]] + + return self._cache_dir.joinpath(*split_key) + + def get_cached_archive_for_link( + self, + link: Link, + *, + strict: bool, + env: Env | None = None, + ) -> Path | None: + assert strict or env is not None + + archives = self._get_cached_archives_for_link(link) + if not archives: + return None + + candidates: list[tuple[float | None, Path]] = [] + for archive in archives: + if strict: + # in strict mode return the original cached archive instead of the + # prioritized archive type. + if link.filename == archive.name: + return archive + continue + + assert env is not None + + if archive.suffix != ".whl": + candidates.append((float("inf"), archive)) + continue + + try: + wheel = Wheel(archive.name) + except InvalidWheelName: + continue + + if not wheel.is_supported_by_environment(env): + continue + + candidates.append( + (wheel.get_minimum_supported_index(env.supported_tags), archive), + ) + + if not candidates: + return None + + return min(candidates)[1] + + def _get_cached_archives_for_link(self, link: Link) -> list[Path]: + cache_dir = self.get_cache_directory_for_link(link) + + archive_types = ["whl", "tar.gz", "tar.bz2", "bz2", "zip"] + paths = [] + for archive_type in archive_types: + for archive in cache_dir.glob(f"*.{archive_type}"): + paths.append(Path(archive)) + + return paths diff --git a/src/poetry/utils/wheel.py b/src/poetry/utils/wheel.py new file mode 100644 index 00000000000..f45c50b3b35 --- /dev/null +++ b/src/poetry/utils/wheel.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +import logging + +from typing import TYPE_CHECKING + +from packaging.tags import Tag + +from poetry.utils.patterns import wheel_file_re + + +if TYPE_CHECKING: + from poetry.utils.env import Env + + +logger = logging.getLogger(__name__) + + +class InvalidWheelName(Exception): + pass + + +class Wheel: + def __init__(self, filename: str) -> None: + wheel_info = wheel_file_re.match(filename) + if not wheel_info: + raise InvalidWheelName(f"{filename} is not a valid wheel filename.") + + self.filename = filename + self.name = wheel_info.group("name").replace("_", "-") + self.version = wheel_info.group("ver").replace("_", "-") + self.build_tag = wheel_info.group("build") + self.pyversions = wheel_info.group("pyver").split(".") + self.abis = wheel_info.group("abi").split(".") + self.plats = wheel_info.group("plat").split(".") + + self.tags = { + Tag(x, y, z) for x in self.pyversions for y in self.abis for z in self.plats + } + + def get_minimum_supported_index(self, tags: list[Tag]) -> int | None: + indexes = [tags.index(t) for t in self.tags if t in tags] + + return min(indexes) if indexes else None + + def is_supported_by_environment(self, env: Env) -> bool: + return bool(set(env.supported_tags).intersection(self.tags)) diff --git a/tests/installation/test_chef.py b/tests/installation/test_chef.py index 19283cdeaaa..c03dfd07c4d 100644 --- a/tests/installation/test_chef.py +++ b/tests/installation/test_chef.py @@ -9,14 +9,13 @@ import pytest -from packaging.tags import Tag from poetry.core.packages.utils.link import Link from poetry.factory import Factory from poetry.installation.chef import Chef from poetry.repositories import RepositoryPool +from poetry.utils.cache import ArtifactCache from poetry.utils.env import EnvManager -from poetry.utils.env import MockEnv from tests.repositories.test_pypi_repository import MockRepository @@ -24,6 +23,7 @@ from pytest_mock import MockerFixture from tests.conftest import Config + from tests.types import FixtureDirGetter @pytest.fixture() @@ -40,166 +40,22 @@ def setup(mocker: MockerFixture, pool: RepositoryPool) -> None: mocker.patch.object(Factory, "create_pool", return_value=pool) -@pytest.mark.parametrize( - ("link", "strict", "available_packages"), - [ - ( - "https://files.python-poetry.org/demo-0.1.0.tar.gz", - True, - [ - Path("/cache/demo-0.1.0-py2.py3-none-any"), - Path("/cache/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl"), - Path("/cache/demo-0.1.0-cp37-cp37-macosx_10_15_x86_64.whl"), - ], - ), - ( - "https://example.com/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl", - False, - [], - ), - ], -) -def test_get_not_found_cached_archive_for_link( - config: Config, - mocker: MockerFixture, - link: str, - strict: bool, - available_packages: list[Path], -): - chef = Chef( - config, - MockEnv( - version_info=(3, 8, 3), - marker_env={"interpreter_name": "cpython", "interpreter_version": "3.8.3"}, - supported_tags=[ - Tag("cp38", "cp38", "macosx_10_15_x86_64"), - Tag("py3", "none", "any"), - ], - ), - Factory.create_pool(config), - ) - - mocker.patch.object( - chef, "get_cached_archives_for_link", return_value=available_packages - ) - - archive = chef.get_cached_archive_for_link(Link(link), strict=strict) - - assert archive is None - - -@pytest.mark.parametrize( - ("link", "cached", "strict"), - [ - ( - "https://files.python-poetry.org/demo-0.1.0.tar.gz", - "/cache/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl", - False, - ), - ( - "https://example.com/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl", - "/cache/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl", - False, - ), - ( - "https://files.python-poetry.org/demo-0.1.0.tar.gz", - "/cache/demo-0.1.0.tar.gz", - True, - ), - ( - "https://example.com/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl", - "/cache/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl", - True, - ), - ], -) -def test_get_found_cached_archive_for_link( - config: Config, mocker: MockerFixture, link: str, cached: str, strict: bool -): - chef = Chef( - config, - MockEnv( - version_info=(3, 8, 3), - marker_env={"interpreter_name": "cpython", "interpreter_version": "3.8.3"}, - supported_tags=[ - Tag("cp38", "cp38", "macosx_10_15_x86_64"), - Tag("py3", "none", "any"), - ], - ), - Factory.create_pool(config), - ) - - mocker.patch.object( - chef, - "get_cached_archives_for_link", - return_value=[ - Path("/cache/demo-0.1.0-py2.py3-none-any"), - Path("/cache/demo-0.1.0.tar.gz"), - Path("/cache/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl"), - Path("/cache/demo-0.1.0-cp37-cp37-macosx_10_15_x86_64.whl"), - ], - ) +@pytest.fixture +def artifact_cache(config: Config) -> ArtifactCache: + return ArtifactCache(cache_dir=config.artifacts_cache_directory) - archive = chef.get_cached_archive_for_link(Link(link), strict=strict) - assert Path(cached) == archive - - -def test_get_cached_archives_for_link(config: Config, mocker: MockerFixture): - chef = Chef( - config, - MockEnv( - marker_env={"interpreter_name": "cpython", "interpreter_version": "3.8.3"} - ), - Factory.create_pool(config), - ) - - distributions = Path(__file__).parent.parent.joinpath("fixtures/distributions") - mocker.patch.object( - chef, - "get_cache_directory_for_link", - return_value=distributions, - ) - - archives = chef.get_cached_archives_for_link( - Link("https://files.python-poetry.org/demo-0.1.0.tar.gz") - ) - - assert archives - assert set(archives) == set(distributions.glob("demo-0.1.*")) - - -def test_get_cache_directory_for_link(config: Config, config_cache_dir: Path): +def test_prepare_sdist( + config: Config, + config_cache_dir: Path, + artifact_cache: ArtifactCache, + fixture_dir: FixtureDirGetter, +) -> None: chef = Chef( - config, - MockEnv( - marker_env={"interpreter_name": "cpython", "interpreter_version": "3.8.3"} - ), - Factory.create_pool(config), - ) - - directory = chef.get_cache_directory_for_link( - Link("https://files.python-poetry.org/poetry-1.1.0.tar.gz") - ) - - expected = Path( - f"{config_cache_dir.as_posix()}/artifacts/ba/63/13/" - "283a3b3b7f95f05e9e6f84182d276f7bb0951d5b0cc24422b33f7a4648" - ) - - assert directory == expected - - -def test_prepare_sdist(config: Config, config_cache_dir: Path) -> None: - chef = Chef(config, EnvManager.get_system_env(), Factory.create_pool(config)) - - archive = ( - Path(__file__) - .parent.parent.joinpath("fixtures/distributions/demo-0.1.0.tar.gz") - .resolve() + artifact_cache, EnvManager.get_system_env(), Factory.create_pool(config) ) - - destination = chef.get_cache_directory_for_link(Link(archive.as_uri())) + archive = (fixture_dir("distributions") / "demo-0.1.0.tar.gz").resolve() + destination = artifact_cache.get_cache_directory_for_link(Link(archive.as_uri())) wheel = chef.prepare(archive) @@ -207,10 +63,16 @@ def test_prepare_sdist(config: Config, config_cache_dir: Path) -> None: assert wheel.name == "demo-0.1.0-py3-none-any.whl" -def test_prepare_directory(config: Config, config_cache_dir: Path): - chef = Chef(config, EnvManager.get_system_env(), Factory.create_pool(config)) - - archive = Path(__file__).parent.parent.joinpath("fixtures/simple_project").resolve() +def test_prepare_directory( + config: Config, + config_cache_dir: Path, + artifact_cache: ArtifactCache, + fixture_dir: FixtureDirGetter, +): + chef = Chef( + artifact_cache, EnvManager.get_system_env(), Factory.create_pool(config) + ) + archive = fixture_dir("simple_project").resolve() wheel = chef.prepare(archive) @@ -222,16 +84,14 @@ def test_prepare_directory(config: Config, config_cache_dir: Path): def test_prepare_directory_with_extensions( - config: Config, config_cache_dir: Path + config: Config, + config_cache_dir: Path, + artifact_cache: ArtifactCache, + fixture_dir: FixtureDirGetter, ) -> None: env = EnvManager.get_system_env() - chef = Chef(config, env, Factory.create_pool(config)) - - archive = ( - Path(__file__) - .parent.parent.joinpath("fixtures/extended_with_no_setup") - .resolve() - ) + chef = Chef(artifact_cache, env, Factory.create_pool(config)) + archive = fixture_dir("extended_with_no_setup").resolve() wheel = chef.prepare(archive) @@ -242,10 +102,16 @@ def test_prepare_directory_with_extensions( os.unlink(wheel) -def test_prepare_directory_editable(config: Config, config_cache_dir: Path): - chef = Chef(config, EnvManager.get_system_env(), Factory.create_pool(config)) - - archive = Path(__file__).parent.parent.joinpath("fixtures/simple_project").resolve() +def test_prepare_directory_editable( + config: Config, + config_cache_dir: Path, + artifact_cache: ArtifactCache, + fixture_dir: FixtureDirGetter, +): + chef = Chef( + artifact_cache, EnvManager.get_system_env(), Factory.create_pool(config) + ) + archive = fixture_dir("simple_project").resolve() wheel = chef.prepare(archive, editable=True) diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index c25c331d1ef..474aafab500 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -32,6 +32,7 @@ from poetry.installation.operations import Update from poetry.installation.wheel_installer import WheelInstaller from poetry.repositories.repository_pool import RepositoryPool +from poetry.utils.cache import ArtifactCache from poetry.utils.env import MockEnv from tests.repositories.test_pypi_repository import MockRepository @@ -93,7 +94,7 @@ def env(tmp_dir: str) -> MockEnv: return MockEnv(path=path, is_venv=True) -@pytest.fixture() +@pytest.fixture def io() -> BufferedIO: io = BufferedIO() io.output.formatter.set_style("c1_dark", Style("cyan", options=["dark"])) @@ -104,7 +105,7 @@ def io() -> BufferedIO: return io -@pytest.fixture() +@pytest.fixture def io_decorated() -> BufferedIO: io = BufferedIO(decorated=True) io.output.formatter.set_style("c1", Style("cyan")) @@ -113,14 +114,14 @@ def io_decorated() -> BufferedIO: return io -@pytest.fixture() +@pytest.fixture def io_not_decorated() -> BufferedIO: io = BufferedIO(decorated=False) return io -@pytest.fixture() +@pytest.fixture def pool() -> RepositoryPool: pool = RepositoryPool() pool.add_repository(MockRepository()) @@ -128,8 +129,15 @@ def pool() -> RepositoryPool: return pool -@pytest.fixture() -def mock_file_downloads(http: type[httpretty.httpretty]) -> None: +@pytest.fixture +def artifact_cache(config: Config) -> ArtifactCache: + return ArtifactCache(cache_dir=config.artifacts_cache_directory) + + +@pytest.fixture +def mock_file_downloads( + http: type[httpretty.httpretty], fixture_dir: FixtureDirGetter +) -> None: def callback( request: HTTPrettyRequest, uri: str, headers: dict[str, Any] ) -> list[int | dict[str, Any] | str]: @@ -141,12 +149,10 @@ def callback( if not fixture.exists(): if name == "demo-0.1.0.tar.gz": - fixture = Path(__file__).parent.parent.joinpath( - "fixtures/distributions/demo-0.1.0.tar.gz" - ) + fixture = fixture_dir("distributions") / "demo-0.1.0.tar.gz" else: - fixture = Path(__file__).parent.parent.joinpath( - "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl" + fixture = ( + fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" ) return [200, headers, fixture.read_bytes()] @@ -158,32 +164,25 @@ def callback( ) -@pytest.fixture() -def copy_wheel(tmp_dir: Path) -> Callable[[], Path]: +@pytest.fixture +def copy_wheel(tmp_dir: Path, fixture_dir: FixtureDirGetter) -> Callable[[], Path]: def _copy_wheel() -> Path: tmp_name = tempfile.mktemp() Path(tmp_dir).joinpath(tmp_name).mkdir() shutil.copyfile( - Path(__file__) - .parent.parent.joinpath( - "fixtures/distributions/demo-0.1.2-py2.py3-none-any.whl" - ) - .as_posix(), - Path(tmp_dir) - .joinpath(tmp_name) - .joinpath("demo-0.1.2-py2.py3-none-any.whl") - .as_posix(), + ( + fixture_dir("distributions") / "demo-0.1.2-py2.py3-none-any.whl" + ).as_posix(), + (Path(tmp_dir) / tmp_name / "demo-0.1.2-py2.py3-none-any.whl").as_posix(), ) - return ( - Path(tmp_dir).joinpath(tmp_name).joinpath("demo-0.1.2-py2.py3-none-any.whl") - ) + return Path(tmp_dir) / tmp_name / "demo-0.1.2-py2.py3-none-any.whl" return _copy_wheel -@pytest.fixture() +@pytest.fixture def wheel(copy_wheel: Callable[[], Path]) -> Path: archive = copy_wheel() @@ -202,13 +201,15 @@ def test_execute_executes_a_batch_of_operations( mock_file_downloads: None, env: MockEnv, copy_wheel: Callable[[], Path], + fixture_dir: FixtureDirGetter, ): wheel_install = mocker.patch.object(WheelInstaller, "install") config.merge({"cache-dir": tmp_dir}) + artifact_cache = ArtifactCache(cache_dir=config.artifacts_cache_directory) prepare_spy = mocker.spy(Chef, "_prepare") - chef = Chef(config, env, Factory.create_pool(config)) + chef = Chef(artifact_cache, env, Factory.create_pool(config)) chef.set_directory_wheel([copy_wheel(), copy_wheel()]) chef.set_sdist_wheel(copy_wheel()) @@ -221,10 +222,7 @@ def test_execute_executes_a_batch_of_operations( "demo", "0.1.0", source_type="file", - source_url=Path(__file__) - .parent.parent.joinpath( - "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl" - ) + source_url=(fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl") .resolve() .as_posix(), ) @@ -233,10 +231,7 @@ def test_execute_executes_a_batch_of_operations( "simple-project", "1.2.3", source_type="directory", - source_url=Path(__file__) - .parent.parent.joinpath("fixtures/simple_project") - .resolve() - .as_posix(), + source_url=fixture_dir("simple_project").resolve().as_posix(), ) git_package = Package( @@ -527,10 +522,9 @@ def test_executor_should_delete_incomplete_downloads( pool: RepositoryPool, mock_file_downloads: None, env: MockEnv, + fixture_dir: FixtureDirGetter, ): - fixture = Path(__file__).parent.parent.joinpath( - "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl" - ) + fixture = fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" destination_fixture = Path(tmp_dir) / "tomlkit-0.5.3-py2.py3-none-any.whl" shutil.copyfile(str(fixture), str(destination_fixture)) mocker.patch( @@ -538,11 +532,11 @@ def test_executor_should_delete_incomplete_downloads( side_effect=Exception("Download error"), ) mocker.patch( - "poetry.installation.chef.Chef.get_cached_archive_for_link", - side_effect=lambda link, strict: None, + "poetry.installation.executor.ArtifactCache.get_cached_archive_for_link", + return_value=None, ) mocker.patch( - "poetry.installation.chef.Chef.get_cache_directory_for_link", + "poetry.installation.executor.ArtifactCache.get_cache_directory_for_link", return_value=Path(tmp_dir), ) @@ -624,15 +618,13 @@ def test_executor_should_not_write_pep610_url_references_for_cached_package( def test_executor_should_write_pep610_url_references_for_wheel_files( - tmp_venv: VirtualEnv, pool: RepositoryPool, config: Config, io: BufferedIO + tmp_venv: VirtualEnv, + pool: RepositoryPool, + config: Config, + io: BufferedIO, + fixture_dir: FixtureDirGetter, ): - url = ( - Path(__file__) - .parent.parent.joinpath( - "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl" - ) - .resolve() - ) + url = (fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl").resolve() package = Package("demo", "0.1.0", source_type="file", source_url=url.as_posix()) # Set package.files so the executor will attempt to hash the package package.files = [ @@ -658,13 +650,13 @@ def test_executor_should_write_pep610_url_references_for_wheel_files( def test_executor_should_write_pep610_url_references_for_non_wheel_files( - tmp_venv: VirtualEnv, pool: RepositoryPool, config: Config, io: BufferedIO + tmp_venv: VirtualEnv, + pool: RepositoryPool, + config: Config, + io: BufferedIO, + fixture_dir: FixtureDirGetter, ): - url = ( - Path(__file__) - .parent.parent.joinpath("fixtures/distributions/demo-0.1.0.tar.gz") - .resolve() - ) + url = (fixture_dir("distributions") / "demo-0.1.0.tar.gz").resolve() package = Package("demo", "0.1.0", source_type="file", source_url=url.as_posix()) # Set package.files so the executor will attempt to hash the package package.files = [ @@ -693,19 +685,17 @@ def test_executor_should_write_pep610_url_references_for_directories( tmp_venv: VirtualEnv, pool: RepositoryPool, config: Config, + artifact_cache: ArtifactCache, io: BufferedIO, wheel: Path, + fixture_dir: FixtureDirGetter, ): - url = ( - Path(__file__) - .parent.parent.joinpath("fixtures/git/github.com/demo/demo") - .resolve() - ) + url = (fixture_dir("git") / "github.com" / "demo" / "demo").resolve() package = Package( "demo", "0.1.2", source_type="directory", source_url=url.as_posix() ) - chef = Chef(config, tmp_venv, Factory.create_pool(config)) + chef = Chef(artifact_cache, tmp_venv, Factory.create_pool(config)) chef.set_directory_wheel(wheel) executor = Executor(tmp_venv, pool, config, io) @@ -720,14 +710,12 @@ def test_executor_should_write_pep610_url_references_for_editable_directories( tmp_venv: VirtualEnv, pool: RepositoryPool, config: Config, + artifact_cache: ArtifactCache, io: BufferedIO, wheel: Path, + fixture_dir: FixtureDirGetter, ): - url = ( - Path(__file__) - .parent.parent.joinpath("fixtures/git/github.com/demo/demo") - .resolve() - ) + url = (fixture_dir("git") / "github.com" / "demo" / "demo").resolve() package = Package( "demo", "0.1.2", @@ -736,7 +724,7 @@ def test_executor_should_write_pep610_url_references_for_editable_directories( develop=True, ) - chef = Chef(config, tmp_venv, Factory.create_pool(config)) + chef = Chef(artifact_cache, tmp_venv, Factory.create_pool(config)) chef.set_directory_wheel(wheel) executor = Executor(tmp_venv, pool, config, io) @@ -761,7 +749,7 @@ def test_executor_should_write_pep610_url_references_for_wheel_urls( if is_artifact_cached: link_cached = fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" mocker.patch( - "poetry.installation.chef.Chef.get_cached_archive_for_link", + "poetry.installation.executor.ArtifactCache.get_cached_archive_for_link", return_value=link_cached, ) download_spy = mocker.spy(Executor, "_download_archive") @@ -840,7 +828,7 @@ def test_executor_should_write_pep610_url_references_for_non_wheel_urls( cached_sdist = fixture_dir("distributions") / "demo-0.1.0.tar.gz" cached_wheel = fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" - def mock_get_cached_archive_for_link_func(_: Link, strict: bool): + def mock_get_cached_archive_for_link_func(_: Link, *, strict: bool, **__: Any): if is_wheel_cached and not strict: return cached_wheel if is_sdist_cached: @@ -848,7 +836,7 @@ def mock_get_cached_archive_for_link_func(_: Link, strict: bool): return None mocker.patch( - "poetry.installation.chef.Chef.get_cached_archive_for_link", + "poetry.installation.executor.ArtifactCache.get_cached_archive_for_link", side_effect=mock_get_cached_archive_for_link_func, ) @@ -898,6 +886,7 @@ def test_executor_should_write_pep610_url_references_for_git( tmp_venv: VirtualEnv, pool: RepositoryPool, config: Config, + artifact_cache: ArtifactCache, io: BufferedIO, mock_file_downloads: None, wheel: Path, @@ -911,7 +900,7 @@ def test_executor_should_write_pep610_url_references_for_git( source_url="https://github.com/demo/demo.git", ) - chef = Chef(config, tmp_venv, Factory.create_pool(config)) + chef = Chef(artifact_cache, tmp_venv, Factory.create_pool(config)) chef.set_directory_wheel(wheel) executor = Executor(tmp_venv, pool, config, io) @@ -936,6 +925,7 @@ def test_executor_should_append_subdirectory_for_git( tmp_venv: VirtualEnv, pool: RepositoryPool, config: Config, + artifact_cache: ArtifactCache, io: BufferedIO, mock_file_downloads: None, wheel: Path, @@ -950,7 +940,7 @@ def test_executor_should_append_subdirectory_for_git( source_subdirectory="two", ) - chef = Chef(config, tmp_venv, Factory.create_pool(config)) + chef = Chef(artifact_cache, tmp_venv, Factory.create_pool(config)) chef.set_directory_wheel(wheel) spy = mocker.spy(chef, "prepare") @@ -966,6 +956,7 @@ def test_executor_should_write_pep610_url_references_for_git_with_subdirectories tmp_venv: VirtualEnv, pool: RepositoryPool, config: Config, + artifact_cache: ArtifactCache, io: BufferedIO, mock_file_downloads: None, wheel: Path, @@ -980,7 +971,7 @@ def test_executor_should_write_pep610_url_references_for_git_with_subdirectories source_subdirectory="two", ) - chef = Chef(config, tmp_venv, Factory.create_pool(config)) + chef = Chef(artifact_cache, tmp_venv, Factory.create_pool(config)) chef.set_directory_wheel(wheel) executor = Executor(tmp_venv, pool, config, io) @@ -1040,6 +1031,7 @@ def test_executor_fallback_on_poetry_create_error_without_wheel_installer( tmp_dir: str, mock_file_downloads: None, env: MockEnv, + fixture_dir: FixtureDirGetter, ): mock_pip_install = mocker.patch("poetry.installation.executor.pip_install") mock_sdist_builder = mocker.patch("poetry.core.masonry.builders.sdist.SdistBuilder") @@ -1063,10 +1055,7 @@ def test_executor_fallback_on_poetry_create_error_without_wheel_installer( "simple-project", "1.2.3", source_type="directory", - source_url=Path(__file__) - .parent.parent.joinpath("fixtures/simple_project") - .resolve() - .as_posix(), + source_url=fixture_dir("simple_project").resolve().as_posix(), ) return_code = executor.execute( @@ -1105,6 +1094,7 @@ def test_build_backend_errors_are_reported_correctly_if_caused_by_subprocess( tmp_dir: str, mock_file_downloads: None, env: MockEnv, + fixture_dir: FixtureDirGetter, ) -> None: error = BuildBackendException( CalledProcessError(1, ["pip"], output=b"Error on stdout") @@ -1120,10 +1110,7 @@ def test_build_backend_errors_are_reported_correctly_if_caused_by_subprocess( package_name, package_version, source_type="directory", - source_url=Path(__file__) - .parent.parent.joinpath("fixtures/simple_project") - .resolve() - .as_posix(), + source_url=fixture_dir("simple_project").resolve().as_posix(), develop=editable, ) # must not be included in the error message diff --git a/tests/utils/test_cache.py b/tests/utils/test_cache.py index c1bbae5071a..8cdbc93a284 100644 --- a/tests/utils/test_cache.py +++ b/tests/utils/test_cache.py @@ -1,5 +1,6 @@ from __future__ import annotations +from pathlib import Path from typing import TYPE_CHECKING from typing import Any from typing import TypeVar @@ -9,18 +10,21 @@ import pytest from cachy import CacheManager +from packaging.tags import Tag +from poetry.core.packages.utils.link import Link +from poetry.utils.cache import ArtifactCache from poetry.utils.cache import FileCache +from poetry.utils.env import MockEnv if TYPE_CHECKING: - from pathlib import Path - from _pytest.monkeypatch import MonkeyPatch from pytest import FixtureRequest from pytest_mock import MockerFixture from tests.conftest import Config + from tests.types import FixtureDirGetter FILE_CACHE = Union[FileCache, CacheManager] @@ -192,3 +196,139 @@ def test_cachy_compatibility( assert cachy_file_cache.get("key3") == test_str assert cachy_file_cache.get("key4") == test_obj + + +def test_get_cache_directory_for_link(tmp_path: Path) -> None: + cache = ArtifactCache(cache_dir=tmp_path) + directory = cache.get_cache_directory_for_link( + Link("https://files.python-poetry.org/poetry-1.1.0.tar.gz") + ) + + expected = Path( + f"{tmp_path.as_posix()}/11/4f/a8/" + "1c89d75547e4967082d30a28360401c82c83b964ddacee292201bf85f2" + ) + + assert directory == expected + + +def test_get_cached_archives_for_link( + fixture_dir: FixtureDirGetter, mocker: MockerFixture +) -> None: + distributions = fixture_dir("distributions") + cache = ArtifactCache(cache_dir=Path()) + + mocker.patch.object( + cache, + "get_cache_directory_for_link", + return_value=distributions, + ) + archives = cache._get_cached_archives_for_link( + Link("https://files.python-poetry.org/demo-0.1.0.tar.gz") + ) + + assert archives + assert set(archives) == set(distributions.glob("demo-0.1.*")) + + +@pytest.mark.parametrize( + ("link", "strict", "available_packages"), + [ + ( + "https://files.python-poetry.org/demo-0.1.0.tar.gz", + True, + [ + Path("/cache/demo-0.1.0-py2.py3-none-any"), + Path("/cache/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl"), + Path("/cache/demo-0.1.0-cp37-cp37-macosx_10_15_x86_64.whl"), + ], + ), + ( + "https://example.com/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl", + False, + [], + ), + ], +) +def test_get_not_found_cached_archive_for_link( + mocker: MockerFixture, + link: str, + strict: bool, + available_packages: list[Path], +) -> None: + env = MockEnv( + version_info=(3, 8, 3), + marker_env={"interpreter_name": "cpython", "interpreter_version": "3.8.3"}, + supported_tags=[ + Tag("cp38", "cp38", "macosx_10_15_x86_64"), + Tag("py3", "none", "any"), + ], + ) + cache = ArtifactCache(cache_dir=Path()) + + mocker.patch.object( + cache, + "_get_cached_archives_for_link", + return_value=available_packages, + ) + + archive = cache.get_cached_archive_for_link(Link(link), strict=strict, env=env) + + assert archive is None + + +@pytest.mark.parametrize( + ("link", "cached", "strict"), + [ + ( + "https://files.python-poetry.org/demo-0.1.0.tar.gz", + "/cache/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl", + False, + ), + ( + "https://example.com/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl", + "/cache/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl", + False, + ), + ( + "https://files.python-poetry.org/demo-0.1.0.tar.gz", + "/cache/demo-0.1.0.tar.gz", + True, + ), + ( + "https://example.com/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl", + "/cache/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl", + True, + ), + ], +) +def test_get_found_cached_archive_for_link( + mocker: MockerFixture, + link: str, + cached: str, + strict: bool, +) -> None: + env = MockEnv( + version_info=(3, 8, 3), + marker_env={"interpreter_name": "cpython", "interpreter_version": "3.8.3"}, + supported_tags=[ + Tag("cp38", "cp38", "macosx_10_15_x86_64"), + Tag("py3", "none", "any"), + ], + ) + cache = ArtifactCache(cache_dir=Path()) + + mocker.patch.object( + cache, + "_get_cached_archives_for_link", + return_value=[ + Path("/cache/demo-0.1.0-py2.py3-none-any"), + Path("/cache/demo-0.1.0.tar.gz"), + Path("/cache/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl"), + Path("/cache/demo-0.1.0-cp37-cp37-macosx_10_15_x86_64.whl"), + ], + ) + + archive = cache.get_cached_archive_for_link(Link(link), strict=strict, env=env) + + assert Path(cached) == archive From 472a17cd2f3f6c15d12834dfa00f5024c3ed3247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sun, 19 Mar 2023 06:31:45 +0100 Subject: [PATCH 113/151] chore: bump version and merge changelog from 1.4.1 --- CHANGELOG.md | 27 ++++++++++++++++++++++++++- pyproject.toml | 2 +- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7541f024e01..fc0f729831a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,30 @@ # Change Log +## [1.4.1] - 2023-03-19 + +### Fixed + +- Fix an issue where `poetry install` did not respect the requirements for building editable dependencies ([#7579](https://github.com/python-poetry/poetry/pull/7579)). +- Fix an issue where `poetry init` crashed due to bad input when adding packages interactively ([#7569](https://github.com/python-poetry/poetry/pull/7569)). +- Fix an issue where `poetry install` ignored the `subdirectory` argument of git dependencies ([#7580](https://github.com/python-poetry/poetry/pull/7580)). +- Fix an issue where installing packages with `no-binary` could result in a false hash mismatch ([#7594](https://github.com/python-poetry/poetry/pull/7594)). +- Fix an issue where the hash of sdists was neither validated nor written to the `direct_url.json` during installation ([#7594](https://github.com/python-poetry/poetry/pull/7594)). +- Fix an issue where `poetry install --sync` attempted to remove itself ([#7626](https://github.com/python-poetry/poetry/pull/7626)). +- Fix an issue where wheels with non-normalized `dist-info` directory names could not be installed ([#7671](https://github.com/python-poetry/poetry/pull/7671)). +- Fix an issue where `poetry install --compile` compiled with optimization level 1 ([#7666](https://github.com/python-poetry/poetry/pull/7666)). + +### Docs + +- Clarify the behavior of the `--extras` option ([#7563](https://github.com/python-poetry/poetry/pull/7563)). +- Expand the FAQ on reasons for slow dependency resolution ([#7620](https://github.com/python-poetry/poetry/pull/7620)). + + +### poetry-core ([`1.5.2`](https://github.com/python-poetry/poetry-core/releases/tag/1.5.2)) + +- Fix an issue where wheels built on Windows could contain duplicate entries in the RECORD file ([#555](https://github.com/python-poetry/poetry-core/pull/555)). + + ## [1.4.0] - 2023-02-27 ### Added @@ -1762,7 +1786,8 @@ Initial release -[Unreleased]: https://github.com/python-poetry/poetry/compare/1.4.0...master +[Unreleased]: https://github.com/python-poetry/poetry/compare/1.4.1...master +[1.4.1]: https://github.com/python-poetry/poetry/releases/tag/1.4.1 [1.4.0]: https://github.com/python-poetry/poetry/releases/tag/1.4.0 [1.3.2]: https://github.com/python-poetry/poetry/releases/tag/1.3.2 [1.3.1]: https://github.com/python-poetry/poetry/releases/tag/1.3.1 diff --git a/pyproject.toml b/pyproject.toml index d01abd38610..ef1980f5779 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "poetry" -version = "1.4.0" +version = "1.5.0.dev0" description = "Python dependency management and packaging made easy." authors = [ "Sébastien Eustace ", From 71b3d6e030c879f0b4b8e3a08eac2b38a402b6bd Mon Sep 17 00:00:00 2001 From: Evan Rittenhouse Date: Sun, 19 Mar 2023 16:20:34 -0500 Subject: [PATCH 114/151] Correctly parse Git submodule URLs (#7017) --- src/poetry/vcs/git/backend.py | 11 ++++++++++- tests/integration/test_utils_vcs_git.py | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/poetry/vcs/git/backend.py b/src/poetry/vcs/git/backend.py index 20d7a1223af..2e3032147f8 100644 --- a/src/poetry/vcs/git/backend.py +++ b/src/poetry/vcs/git/backend.py @@ -7,6 +7,7 @@ from pathlib import Path from subprocess import CalledProcessError from typing import TYPE_CHECKING +from urllib.parse import urljoin from dulwich import porcelain from dulwich.client import HTTPUnauthorized @@ -331,16 +332,24 @@ def _clone_submodules(cls, repo: Repo) -> None: repo_root = Path(repo.path) modules_config = repo_root.joinpath(".gitmodules") + # A relative URL by definition starts with ../ or ./ + relative_submodule_regex = re.compile(r"^\.{1,2}/") + if modules_config.exists(): config = ConfigFile.from_path(str(modules_config)) url: bytes path: bytes submodules = parse_submodules(config) + for path, url, name in submodules: path_relative = Path(path.decode("utf-8")) path_absolute = repo_root.joinpath(path_relative) + url_string = url.decode("utf-8") + if relative_submodule_regex.search(url_string): + url_string = urljoin(f"{Git.get_remote_url(repo)}/", url_string) + source_root = path_absolute.parent source_root.mkdir(parents=True, exist_ok=True) @@ -357,7 +366,7 @@ def _clone_submodules(cls, repo: Repo) -> None: continue cls.clone( - url=url.decode("utf-8"), + url=url_string, source_root=source_root, name=path_relative.name, revision=revision, diff --git a/tests/integration/test_utils_vcs_git.py b/tests/integration/test_utils_vcs_git.py index 5c399d5311b..ec0f558f3fe 100644 --- a/tests/integration/test_utils_vcs_git.py +++ b/tests/integration/test_utils_vcs_git.py @@ -240,6 +240,30 @@ def test_git_clone_clones_submodules(source_url: str) -> None: assert len(list(submodule_package_directory.glob("*"))) > 1 +def test_git_clone_clones_submodules_with_relative_urls(source_url: str) -> None: + with Git.clone(url=source_url, branch="relative_submodule") as repo: + submodule_package_directory = ( + Path(repo.path) / "submodules" / "relative-url-submodule" + ) + + assert submodule_package_directory.exists() + assert submodule_package_directory.joinpath("README.md").exists() + assert len(list(submodule_package_directory.glob("*"))) > 1 + + +def test_git_clone_clones_submodules_with_relative_urls_and_explicit_base( + source_url: str, +) -> None: + with Git.clone(url=source_url, branch="relative_submodule") as repo: + submodule_package_directory = ( + Path(repo.path) / "submodules" / "relative-url-submodule-with-base" + ) + + assert submodule_package_directory.exists() + assert submodule_package_directory.joinpath("README.md").exists() + assert len(list(submodule_package_directory.glob("*"))) > 1 + + def test_system_git_fallback_on_http_401( mocker: MockerFixture, source_url: str, From fa5543a639e76d14e7262572ea6b06fe099c0f46 Mon Sep 17 00:00:00 2001 From: Frederik Zahle Date: Thu, 23 Mar 2023 05:56:16 +0100 Subject: [PATCH 115/151] remove stdout=subprocess.PIPE from env._call, which can cause poetry install to hang (#7699) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Randy Döring <30527984+radoering@users.noreply.github.com> --- src/poetry/utils/env.py | 5 ++--- tests/utils/test_env.py | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index d5755d152c3..aa4d58bf223 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -1537,9 +1537,8 @@ def _run(self, cmd: list[str], **kwargs: Any) -> int | str: **kwargs, ).stdout elif call: - return subprocess.call( - cmd, stdout=subprocess.PIPE, stderr=stderr, env=env, **kwargs - ) + assert stderr != subprocess.PIPE + return subprocess.call(cmd, stderr=stderr, env=env, **kwargs) else: output = subprocess.check_output(cmd, stderr=stderr, env=env, **kwargs) except CalledProcessError as e: diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index eb7b2ec59eb..07823d4fb55 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -5,6 +5,7 @@ import sys from pathlib import Path +from threading import Thread from typing import TYPE_CHECKING from typing import Any @@ -1009,6 +1010,31 @@ def test_check_output_with_called_process_error( assert "some error" in str(error.value) +@pytest.mark.parametrize("out", ["sys.stdout", "sys.stderr"]) +def test_call_does_not_block_on_full_pipe( + tmp_path: Path, tmp_venv: VirtualEnv, out: str +): + """see https://github.com/python-poetry/poetry/issues/7698""" + script = tmp_path / "script.py" + script.write_text( + f"""\ +import sys +for i in range(10000): + print('just print a lot of text to fill the buffer', file={out}) +""" + ) + + def target(result: list[int]) -> None: + result.append(tmp_venv.run("python", str(script), call=True)) + + results = [] + # use a separate thread, so that the test does not block in case of error + thread = Thread(target=target, args=(results,)) + thread.start() + thread.join(1) # must not block + assert results and results[0] == 0 + + def test_run_python_script_called_process_error( tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture ): From 9f6f462cb8ffeef8d7cc5a7a4a42cffe4ca86ef7 Mon Sep 17 00:00:00 2001 From: NimajnebEC <46959407+NimajnebEC@users.noreply.github.com> Date: Mon, 27 Mar 2023 16:03:17 +0100 Subject: [PATCH 116/151] Fix generate_system_pyproject newline issues (#7705) --- .../console/commands/self/self_command.py | 3 +- src/poetry/factory.py | 10 +--- .../commands/self/test_self_command.py | 49 +++++++++++++++++++ 3 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 tests/console/commands/self/test_self_command.py diff --git a/src/poetry/console/commands/self/self_command.py b/src/poetry/console/commands/self/self_command.py index 0c1a4ec3f79..5be8f8b65e7 100644 --- a/src/poetry/console/commands/self/self_command.py +++ b/src/poetry/console/commands/self/self_command.py @@ -77,7 +77,8 @@ def generate_system_pyproject(self) -> None: for key in preserved: content["tool"]["poetry"][key] = preserved[key] # type: ignore[index] - self.system_pyproject.write_text(content.as_string(), encoding="utf-8") + pyproject = PyProjectTOML(self.system_pyproject) + pyproject.file.write(content) def reset_poetry(self) -> None: with directory(self.system_pyproject.parent): diff --git a/src/poetry/factory.py b/src/poetry/factory.py index 43960b43285..c8ec61d4cdd 100644 --- a/src/poetry/factory.py +++ b/src/poetry/factory.py @@ -190,9 +190,7 @@ def create_package_source( ) @classmethod - def create_pyproject_from_package( - cls, package: Package, path: Path | None = None - ) -> TOMLDocument: + def create_pyproject_from_package(cls, package: Package) -> TOMLDocument: import tomlkit from poetry.utils.dependency_specification import dependency_to_specification @@ -290,12 +288,6 @@ def create_pyproject_from_package( content["extras"] = extras_section pyproject = cast("TOMLDocument", pyproject) - pyproject.add(tomlkit.nl()) - - if path: - path.joinpath("pyproject.toml").write_text( - pyproject.as_string(), encoding="utf-8" - ) return pyproject diff --git a/tests/console/commands/self/test_self_command.py b/tests/console/commands/self/test_self_command.py new file mode 100644 index 00000000000..c07be3059d5 --- /dev/null +++ b/tests/console/commands/self/test_self_command.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +import pytest + +from poetry.core.packages.dependency import Dependency +from poetry.core.packages.package import Package +from poetry.core.packages.project_package import ProjectPackage + +from poetry.__version__ import __version__ +from poetry.console.commands.self.self_command import SelfCommand +from poetry.factory import Factory + + +@pytest.fixture +def example_system_pyproject(): + package = ProjectPackage("poetry-instance", __version__) + plugin = Package("poetry-plugin", "1.2.3") + + package.add_dependency( + Dependency(plugin.name, "^1.2.3", groups=[SelfCommand.ADDITIONAL_PACKAGE_GROUP]) + ) + content = Factory.create_pyproject_from_package(package) + return content.as_string().rstrip("\n") + + +@pytest.mark.parametrize("existing_newlines", [0, 2]) +def test_generate_system_pyproject_trailing_newline( + existing_newlines: int, + example_system_pyproject: str, +): + cmd = SelfCommand() + cmd.system_pyproject.write_text(example_system_pyproject + "\n" * existing_newlines) + cmd.generate_system_pyproject() + generated = cmd.system_pyproject.read_text() + + assert len(generated) - len(generated.rstrip("\n")) == existing_newlines + + +def test_generate_system_pyproject_carriage_returns( + example_system_pyproject: str, +): + cmd = SelfCommand() + cmd.system_pyproject.write_text(example_system_pyproject + "\n") + cmd.generate_system_pyproject() + + with open(cmd.system_pyproject, newline="") as f: # do not translate newlines + generated = f.read() + + assert "\r\r" not in generated From 2b15ce10f02b0c6347fe2f12ae902488edeaaf7c Mon Sep 17 00:00:00 2001 From: Nicco Kunzmann Date: Mon, 27 Mar 2023 16:20:47 +0100 Subject: [PATCH 117/151] Guide transition from --dev (#7647) --- src/poetry/console/commands/add.py | 5 ++++- src/poetry/console/commands/remove.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/poetry/console/commands/add.py b/src/poetry/console/commands/add.py index 05bdc21bf77..451965cd431 100644 --- a/src/poetry/console/commands/add.py +++ b/src/poetry/console/commands/add.py @@ -30,7 +30,10 @@ class AddCommand(InstallerCommand, InitCommand): option( "dev", "D", - "Add as a development dependency. (Deprecated)", + ( + "Add as a development dependency. (Deprecated) Use" + " --group=dev instead." + ), ), option("editable", "e", "Add vcs/path dependencies as editable."), option( diff --git a/src/poetry/console/commands/remove.py b/src/poetry/console/commands/remove.py index 88ea83880af..0eba3907b38 100644 --- a/src/poetry/console/commands/remove.py +++ b/src/poetry/console/commands/remove.py @@ -24,6 +24,7 @@ class RemoveCommand(InstallerCommand): ( "Remove a package from the development dependencies." " (Deprecated)" + " Use --group=dev instead." ), ), option( From 7687539deeb460ea321e3578a33c743dd2c8c2d3 Mon Sep 17 00:00:00 2001 From: martin-kokos <4807476+martin-kokos@users.noreply.github.com> Date: Fri, 31 Mar 2023 16:52:17 +0200 Subject: [PATCH 118/151] Cache: Add logic and tests for FileCache handling corrupt files (#7453) --- src/poetry/utils/cache.py | 12 +++++++- tests/utils/test_cache.py | 58 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/poetry/utils/cache.py b/src/poetry/utils/cache.py index 99bd5b40cee..facb380a0e5 100644 --- a/src/poetry/utils/cache.py +++ b/src/poetry/utils/cache.py @@ -4,6 +4,7 @@ import dataclasses import hashlib import json +import logging import shutil import time @@ -28,6 +29,8 @@ MAX_DATE = 9999999999 T = TypeVar("T") +logger = logging.getLogger(__name__) + def decode(string: bytes, encodings: list[str] | None = None) -> str: """ @@ -182,7 +185,14 @@ def _get_payload(self, key: str) -> T | None: return None with open(path, "rb") as f: - payload = self._deserialize(f.read()) + file_content = f.read() + + try: + payload = self._deserialize(file_content) + except (json.JSONDecodeError, ValueError): + self.forget(key) + logger.warning("Corrupt cache file was detected and cleaned up.") + return None if payload.expired: self.forget(key) diff --git a/tests/utils/test_cache.py b/tests/utils/test_cache.py index 8cdbc93a284..e9489688e94 100644 --- a/tests/utils/test_cache.py +++ b/tests/utils/test_cache.py @@ -1,5 +1,7 @@ from __future__ import annotations +import shutil + from pathlib import Path from typing import TYPE_CHECKING from typing import Any @@ -198,6 +200,62 @@ def test_cachy_compatibility( assert cachy_file_cache.get("key4") == test_obj +def test_missing_cache_file(poetry_file_cache: FileCache) -> None: + poetry_file_cache.put("key1", "value") + + key1_path = ( + poetry_file_cache.path + / "81/74/09/96/87/a2/66/21/8174099687a26621f4e2cdd7cc03b3dacedb3fb962255b1aafd033cabe831530" # noqa: E501 + ) + assert key1_path.exists() + key1_path.unlink() # corrupt cache by removing a key file + + assert poetry_file_cache.get("key1") is None + + +def test_missing_cache_path(poetry_file_cache: FileCache) -> None: + poetry_file_cache.put("key1", "value") + + key1_partial_path = poetry_file_cache.path / "81/74/09/96/87/a2/" + assert key1_partial_path.exists() + shutil.rmtree( + key1_partial_path + ) # corrupt cache by removing a subdirectory containing a key file + + assert poetry_file_cache.get("key1") is None + + +@pytest.mark.parametrize( + "corrupt_payload", + [ + "", # empty file + b"\x00", # null + "99999999", # truncated file + '999999a999"value"', # corrupt lifetime + b'9999999999"va\xd8\x00"', # invalid unicode + "fil3systemFa!led", # garbage file + ], +) +def test_detect_corrupted_cache_key_file( + corrupt_payload: str | bytes, poetry_file_cache: FileCache +) -> None: + poetry_file_cache.put("key1", "value") + + key1_path = ( + poetry_file_cache.path + / "81/74/09/96/87/a2/66/21/8174099687a26621f4e2cdd7cc03b3dacedb3fb962255b1aafd033cabe831530" # noqa: E501 + ) + assert key1_path.exists() + + # original content: 9999999999"value" + + write_modes = {str: "w", bytes: "wb"} + with open(key1_path, write_modes[type(corrupt_payload)]) as f: + f.write(corrupt_payload) # write corrupt data + + assert poetry_file_cache.get("key1") is None + + def test_get_cache_directory_for_link(tmp_path: Path) -> None: cache = ArtifactCache(cache_dir=tmp_path) directory = cache.get_cache_directory_for_link( From b933443901449ff52ab07aafad071cd8c31ca1c6 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Fri, 31 Mar 2023 16:26:53 +0100 Subject: [PATCH 119/151] Misc tidying (#7673) * correct logger for publisher * build 0.10.0 to get metadata in PackageInfo * prefer pathlib * passwords must be strings --- src/poetry/console/commands/config.py | 1 + src/poetry/console/commands/publish.py | 2 +- src/poetry/inspection/info.py | 8 +- src/poetry/installation/executor.py | 2 +- src/poetry/installation/pip_installer.py | 2 +- src/poetry/installation/wheel_installer.py | 6 +- src/poetry/packages/locker.py | 4 +- src/poetry/utils/cache.py | 5 +- src/poetry/utils/env.py | 96 +++++----- src/poetry/utils/helpers.py | 8 +- src/poetry/utils/setup_reader.py | 18 +- src/poetry/vcs/git/backend.py | 10 +- tests/conftest.py | 5 +- tests/console/commands/env/test_use.py | 8 +- tests/console/commands/test_init.py | 7 +- tests/installation/test_pip_installer.py | 26 ++- .../masonry/builders/test_editable_builder.py | 22 +-- tests/packages/test_locker.py | 46 +++-- tests/utils/test_env.py | 178 +++++++++--------- tests/utils/test_setup_reader.py | 35 ++-- 20 files changed, 246 insertions(+), 243 deletions(-) diff --git a/src/poetry/console/commands/config.py b/src/poetry/console/commands/config.py index 998af125755..281735a6f41 100644 --- a/src/poetry/console/commands/config.py +++ b/src/poetry/console/commands/config.py @@ -211,6 +211,7 @@ def handle(self) -> int: username = values[0] # Only username, so we prompt for password password = self.secret("Password:") + assert isinstance(password, str) elif len(values) != 2: raise ValueError( "Expected one or two arguments " diff --git a/src/poetry/console/commands/publish.py b/src/poetry/console/commands/publish.py index b98c58bfc73..a53df8208dd 100644 --- a/src/poetry/console/commands/publish.py +++ b/src/poetry/console/commands/publish.py @@ -44,7 +44,7 @@ class PublishCommand(Command): the config command. """ - loggers = ["poetry.masonry.publishing.publisher"] + loggers = ["poetry.publishing.publisher"] def handle(self) -> int: from poetry.publishing.publisher import Publisher diff --git a/src/poetry/inspection/info.py b/src/poetry/inspection/info.py index fe7ce6864e1..0e3fb4b4977 100644 --- a/src/poetry/inspection/info.py +++ b/src/poetry/inspection/info.py @@ -58,11 +58,11 @@ builder.metadata_path(dest) """ -PEP517_META_BUILD_DEPS = ["build==0.9.0", "pyproject_hooks==1.0.0"] +PEP517_META_BUILD_DEPS = ["build==0.10.0", "pyproject_hooks==1.0.0"] class PackageInfoError(ValueError): - def __init__(self, path: Path | str, *reasons: BaseException | str) -> None: + def __init__(self, path: Path, *reasons: BaseException | str) -> None: reasons = (f"Unable to determine package info for path: {path!s}",) + reasons super().__init__("\n\n".join(str(msg).strip() for msg in reasons if msg)) @@ -617,7 +617,7 @@ def get_pep517_metadata(path: Path) -> PackageInfo: ) cwd = Path.cwd() - os.chdir(path.as_posix()) + os.chdir(path) try: venv.run("python", "setup.py", "egg_info") info = PackageInfo.from_metadata(path) @@ -626,7 +626,7 @@ def get_pep517_metadata(path: Path) -> PackageInfo: path, "Fallback egg_info generation failed.", fbe ) finally: - os.chdir(cwd.as_posix()) + os.chdir(cwd) if info: logger.debug("Falling back to parsed setup.py file for %s", path) diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index ebbba0a4d6c..5d3f699d13c 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -650,7 +650,7 @@ def _install_directory_without_wheel_installer( if package.source_subdirectory: req /= package.source_subdirectory - pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml")) + pyproject = PyProjectTOML(req / "pyproject.toml") package_poetry = None if pyproject.is_poetry_project(): diff --git a/src/poetry/installation/pip_installer.py b/src/poetry/installation/pip_installer.py index 60d672abc54..a528c24f215 100644 --- a/src/poetry/installation/pip_installer.py +++ b/src/poetry/installation/pip_installer.py @@ -221,7 +221,7 @@ def install_directory(self, package: Package) -> str | int: if package.source_subdirectory: req /= package.source_subdirectory - pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml")) + pyproject = PyProjectTOML(req / "pyproject.toml") package_poetry = None if pyproject.is_poetry_project(): diff --git a/src/poetry/installation/wheel_installer.py b/src/poetry/installation/wheel_installer.py index ab2e0a82f3e..d81fe479cae 100644 --- a/src/poetry/installation/wheel_installer.py +++ b/src/poetry/installation/wheel_installer.py @@ -31,7 +31,7 @@ class WheelDestination(SchemeDictionaryDestination): def write_to_fs( self, scheme: Scheme, - path: Path | str, + path: str, stream: BinaryIO, is_executable: bool, ) -> RecordEntry: @@ -58,7 +58,7 @@ def write_to_fs( if is_executable: make_file_executable(target_path) - return RecordEntry(str(path), Hash(self.hash_algorithm, hash_), size) + return RecordEntry(path, Hash(self.hash_algorithm, hash_), size) def for_source(self, source: WheelFile) -> WheelDestination: scheme_dict = self.scheme_dict.copy() @@ -90,7 +90,7 @@ def __init__(self, env: Env) -> None: schemes["headers"] = schemes["include"] self._destination = WheelDestination( - schemes, interpreter=self._env.python, script_kind=script_kind + schemes, interpreter=str(self._env.python), script_kind=script_kind ) def enable_bytecode_compilation(self, enable: bool = True) -> None: diff --git a/src/poetry/packages/locker.py b/src/poetry/packages/locker.py index 2f4a2dcfda4..3d0206afb73 100644 --- a/src/poetry/packages/locker.py +++ b/src/poetry/packages/locker.py @@ -53,8 +53,8 @@ class Locker: _legacy_keys = ["dependencies", "source", "extras", "dev-dependencies"] _relevant_keys = [*_legacy_keys, "group"] - def __init__(self, lock: str | Path, local_config: dict[str, Any]) -> None: - self._lock = lock if isinstance(lock, Path) else Path(lock) + def __init__(self, lock: Path, local_config: dict[str, Any]) -> None: + self._lock = lock self._local_config = local_config self._lock_data: dict[str, Any] | None = None self._content_hash = self._get_content_hash() diff --git a/src/poetry/utils/cache.py b/src/poetry/utils/cache.py index facb380a0e5..d0d07fb113f 100644 --- a/src/poetry/utils/cache.py +++ b/src/poetry/utils/cache.py @@ -290,9 +290,8 @@ def _get_cached_archives_for_link(self, link: Link) -> list[Path]: cache_dir = self.get_cache_directory_for_link(link) archive_types = ["whl", "tar.gz", "tar.bz2", "bz2", "zip"] - paths = [] + paths: list[Path] = [] for archive_type in archive_types: - for archive in cache_dir.glob(f"*.{archive_type}"): - paths.append(Path(archive)) + paths += cache_dir.glob(f"*.{archive_type}") return paths diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index aa4d58bf223..734a7d64836 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -380,7 +380,7 @@ def remove_distribution_files(self, distribution_name: str) -> list[Path]: distribution_path: Path = distribution._path # type: ignore[attr-defined] if distribution_path.exists(): - remove_directory(str(distribution_path), force=True) + remove_directory(distribution_path, force=True) paths.append(distribution_path) @@ -388,16 +388,13 @@ def remove_distribution_files(self, distribution_name: str) -> list[Path]: def _path_method_wrapper( self, - path: str | Path, + path: Path, method: str, *args: Any, return_first: bool = True, writable_only: bool = False, **kwargs: Any, ) -> tuple[Path, Any] | list[tuple[Path, Any]]: - if isinstance(path, str): - path = Path(path) - candidates = self.make_candidates( path, writable_only=writable_only, strict=True ) @@ -419,17 +416,17 @@ def _path_method_wrapper( raise OSError(f"Unable to access any of {paths_csv(candidates)}") - def write_text(self, path: str | Path, *args: Any, **kwargs: Any) -> Path: + def write_text(self, path: Path, *args: Any, **kwargs: Any) -> Path: paths = self._path_method_wrapper(path, "write_text", *args, **kwargs) assert isinstance(paths, tuple) return paths[0] - def mkdir(self, path: str | Path, *args: Any, **kwargs: Any) -> Path: + def mkdir(self, path: Path, *args: Any, **kwargs: Any) -> Path: paths = self._path_method_wrapper(path, "mkdir", *args, **kwargs) assert isinstance(paths, tuple) return paths[0] - def exists(self, path: str | Path) -> bool: + def exists(self, path: Path) -> bool: return any( value[-1] for value in self._path_method_wrapper(path, "exists", return_first=False) @@ -437,7 +434,7 @@ def exists(self, path: str | Path) -> bool: def find( self, - path: str | Path, + path: Path, writable_only: bool = False, ) -> list[Path]: return [ @@ -520,7 +517,7 @@ def __init__(self, poetry: Poetry, io: None | IO = None) -> None: self._io = io or NullIO() @staticmethod - def _full_python_path(python: str) -> str: + def _full_python_path(python: str) -> Path: try: executable = decode( subprocess.check_output( @@ -530,10 +527,10 @@ def _full_python_path(python: str) -> str: except CalledProcessError as e: raise EnvCommandError(e) - return executable + return Path(executable) @staticmethod - def _detect_active_python(io: None | IO = None) -> str | None: + def _detect_active_python(io: None | IO = None) -> Path | None: io = io or NullIO() executable = None @@ -594,12 +591,12 @@ def activate(self, python: str) -> Env: # Executable in PATH or full executable path pass - python = self._full_python_path(python) + python_path = self._full_python_path(python) try: python_version_string = decode( subprocess.check_output( - [python, "-c", GET_PYTHON_VERSION_ONELINER], + [python_path, "-c", GET_PYTHON_VERSION_ONELINER], ) ) except CalledProcessError as e: @@ -624,7 +621,7 @@ def activate(self, python: str) -> Env: if patch != current_patch: create = True - self.create_venv(executable=python, force=create) + self.create_venv(executable=python_path, force=create) return self.get(reload=True) @@ -658,7 +655,7 @@ def activate(self, python: str) -> Env: if patch != current_patch: create = True - self.create_venv(executable=python, force=create) + self.create_venv(executable=python_path, force=create) # Activate envs[base_env_name] = {"minor": minor, "patch": patch} @@ -759,9 +756,7 @@ def list(self, name: str | None = None) -> list[VirtualEnv]: venv_name = self.generate_env_name(name, str(self._poetry.file.parent)) venv_path = self._poetry.config.virtualenvs_path - env_list = [ - VirtualEnv(Path(p)) for p in sorted(venv_path.glob(f"{venv_name}-py*")) - ] + env_list = [VirtualEnv(p) for p in sorted(venv_path.glob(f"{venv_name}-py*"))] venv = self._poetry.file.parent / ".venv" if ( @@ -837,7 +832,7 @@ def remove(self, python: str) -> Env: else: venv_path = self._poetry.config.virtualenvs_path # Get all the poetry envs, even for other projects - env_names = [Path(p).name for p in sorted(venv_path.glob("*-*-py*"))] + env_names = [p.name for p in sorted(venv_path.glob("*-*-py*"))] if python in env_names: raise IncorrectEnvError(python) @@ -885,7 +880,7 @@ def remove(self, python: str) -> Env: def create_venv( self, name: str | None = None, - executable: str | None = None, + executable: Path | None = None, force: bool = False, ) -> Env: if self._env is not None and not force: @@ -918,7 +913,9 @@ def create_venv( if not executable and prefer_active_python: executable = self._detect_active_python() - venv_path = cwd / ".venv" if root_venv else self._poetry.config.virtualenvs_path + venv_path: Path = ( + cwd / ".venv" if root_venv else self._poetry.config.virtualenvs_path + ) if not name: name = self._poetry.package.name assert name is not None @@ -1071,8 +1068,8 @@ def create_venv( @classmethod def build_venv( cls, - path: Path | str, - executable: str | Path | None = None, + path: Path, + executable: Path | None = None, flags: dict[str, bool] | None = None, with_pip: bool | None = None, with_wheel: bool | None = None, @@ -1103,14 +1100,13 @@ def build_venv( else flags.pop("no-wheel", flags["no-pip"]) ) - if isinstance(executable, Path): - executable = executable.resolve().as_posix() + executable_str = None if executable is None else executable.resolve().as_posix() args = [ "--no-download", "--no-periodic-update", "--python", - executable or sys.executable, + executable_str or sys.executable, ] if prompt is not None: @@ -1138,9 +1134,7 @@ def build_venv( return cli_result @classmethod - def remove_venv(cls, path: Path | str) -> None: - if isinstance(path, str): - path = Path(path) + def remove_venv(cls, path: Path) -> None: assert path.is_dir() try: remove_directory(path) @@ -1251,7 +1245,7 @@ def __init__(self, path: Path, base: Path | None = None) -> None: self._platlib: Path | None = None self._script_dirs: list[Path] | None = None - self._embedded_pip_path: str | None = None + self._embedded_pip_path: Path | None = None @property def path(self) -> Path: @@ -1271,7 +1265,7 @@ def python_implementation(self) -> str: return implementation @property - def python(self) -> str: + def python(self) -> Path: """ Path to current python executable """ @@ -1331,21 +1325,21 @@ def get_embedded_wheel(self, distribution: str) -> Path: return path @property - def pip_embedded(self) -> str: + def pip_embedded(self) -> Path: if self._embedded_pip_path is None: - self._embedded_pip_path = str(self.get_embedded_wheel("pip") / "pip") + self._embedded_pip_path = self.get_embedded_wheel("pip") / "pip" return self._embedded_pip_path @property - def pip(self) -> str: + def pip(self) -> Path: """ Path to current pip executable """ # we do not use as_posix() here due to issues with windows pathlib2 # implementation path = self._bin(self._pip_executable) - if not Path(path).exists(): - return str(self.pip_embedded) + if not path.exists(): + return self.pip_embedded return path @property @@ -1463,10 +1457,10 @@ def get_marker_env(self) -> dict[str, Any]: raise NotImplementedError() def get_pip_command(self, embedded: bool = False) -> list[str]: - if embedded or not Path(self._bin(self._pip_executable)).exists(): - return [self.python, self.pip_embedded] + if embedded or not self._bin(self._pip_executable).exists(): + return [str(self.python), str(self.pip_embedded)] # run as module so that pip can update itself on Windows - return [self.python, "-m", "pip"] + return [str(self.python), "-m", "pip"] def get_supported_tags(self) -> list[Tag]: raise NotImplementedError() @@ -1493,7 +1487,7 @@ def get_command_from_bin(self, bin: str) -> list[str]: # embedded pip when pip is not available in the environment return self.get_pip_command() - return [self._bin(bin)] + return [str(self._bin(bin))] def run(self, bin: str, *args: str, **kwargs: Any) -> str | int: cmd = self.get_command_from_bin(bin) + list(args) @@ -1572,7 +1566,7 @@ def script_dirs(self) -> list[Path]: self._script_dirs.append(self.userbase / self._script_dirs[0].name) return self._script_dirs - def _bin(self, bin: str) -> str: + def _bin(self, bin: str) -> Path: """ Return path to the given executable. """ @@ -1593,11 +1587,11 @@ def _bin(self, bin: str) -> str: bin_path = self._path / bin if bin_path.exists(): - return str(bin_path) + return bin_path - return bin + return Path(bin) - return str(bin_path) + return bin_path def __eq__(self, other: object) -> bool: if not isinstance(other, Env): @@ -1615,8 +1609,8 @@ class SystemEnv(Env): """ @property - def python(self) -> str: - return sys.executable + def python(self) -> Path: + return Path(sys.executable) @property def sys_path(self) -> list[str]: @@ -1942,20 +1936,20 @@ def execute(self, bin: str, *args: str, **kwargs: Any) -> int: return super().execute(bin, *args, **kwargs) return 0 - def _bin(self, bin: str) -> str: - return bin + def _bin(self, bin: str) -> Path: + return Path(bin) @contextmanager def ephemeral_environment( - executable: str | Path | None = None, + executable: Path | None = None, flags: dict[str, bool] | None = None, ) -> Iterator[VirtualEnv]: with temporary_directory() as tmp_dir: # TODO: cache PEP 517 build environment corresponding to each project venv venv_dir = Path(tmp_dir) / ".venv" EnvManager.build_venv( - path=venv_dir.as_posix(), + path=venv_dir, executable=executable, flags=flags, ) diff --git a/src/poetry/utils/helpers.py b/src/poetry/utils/helpers.py index 4d6ff50e28a..831203718fa 100644 --- a/src/poetry/utils/helpers.py +++ b/src/poetry/utils/helpers.py @@ -65,7 +65,7 @@ def _on_rm_error(func: Callable[[str], None], path: str, exc_info: Exception) -> def remove_directory( - path: Path | str, *args: Any, force: bool = False, **kwargs: Any + path: Path, *args: Any, force: bool = False, **kwargs: Any ) -> None: """ Helper function handle safe removal, and optionally forces stubborn file removal. @@ -74,8 +74,8 @@ def remove_directory( Internally, all arguments are passed to `shutil.rmtree`. """ - if Path(path).is_symlink(): - return os.unlink(str(path)) + if path.is_symlink(): + return os.unlink(path) kwargs["onerror"] = kwargs.pop("onerror", _on_rm_error if force else None) shutil.rmtree(path, *args, **kwargs) @@ -239,7 +239,7 @@ def get_win_folder(csidl_name: str) -> Path: raise RuntimeError("Method can only be called on Windows.") -def get_real_windows_path(path: str | Path) -> Path: +def get_real_windows_path(path: Path) -> Path: program_files = get_win_folder("CSIDL_PROGRAM_FILES") local_appdata = get_win_folder("CSIDL_LOCAL_APPDATA") diff --git a/src/poetry/utils/setup_reader.py b/src/poetry/utils/setup_reader.py index f0ecdde8220..ffb7d212509 100644 --- a/src/poetry/utils/setup_reader.py +++ b/src/poetry/utils/setup_reader.py @@ -3,12 +3,16 @@ import ast from configparser import ConfigParser -from pathlib import Path +from typing import TYPE_CHECKING from typing import Any from poetry.core.constraints.version import Version +if TYPE_CHECKING: + from pathlib import Path + + class SetupReader: """ Class that reads a setup.py file without executing it. @@ -25,10 +29,7 @@ class SetupReader: FILES = ["setup.py", "setup.cfg"] @classmethod - def read_from_directory(cls, directory: str | Path) -> dict[str, Any]: - if isinstance(directory, str): - directory = Path(directory) - + def read_from_directory(cls, directory: Path) -> dict[str, Any]: result = cls.DEFAULT.copy() for filename in cls.FILES: filepath = directory / filename @@ -44,10 +45,7 @@ def read_from_directory(cls, directory: str | Path) -> dict[str, Any]: return result - def read_setup_py(self, filepath: str | Path) -> dict[str, Any]: - if isinstance(filepath, str): - filepath = Path(filepath) - + def read_setup_py(self, filepath: Path) -> dict[str, Any]: with filepath.open(encoding="utf-8") as f: content = f.read() @@ -71,7 +69,7 @@ def read_setup_py(self, filepath: str | Path) -> dict[str, Any]: return result - def read_setup_cfg(self, filepath: str | Path) -> dict[str, Any]: + def read_setup_cfg(self, filepath: Path) -> dict[str, Any]: parser = ConfigParser() parser.read(str(filepath)) diff --git a/src/poetry/vcs/git/backend.py b/src/poetry/vcs/git/backend.py index 2e3032147f8..3d58850f468 100644 --- a/src/poetry/vcs/git/backend.py +++ b/src/poetry/vcs/git/backend.py @@ -137,11 +137,11 @@ def is_sha_short(self) -> bool: @dataclasses.dataclass class GitRepoLocalInfo: - repo: dataclasses.InitVar[Repo | Path | str] + repo: dataclasses.InitVar[Repo | Path] origin: str = dataclasses.field(init=False) revision: str = dataclasses.field(init=False) - def __post_init__(self, repo: Repo | Path | str) -> None: + def __post_init__(self, repo: Repo | Path) -> None: repo = Git.as_repo(repo=repo) if not isinstance(repo, Repo) else repo self.origin = Git.get_remote_url(repo=repo, remote="origin") self.revision = Git.get_revision(repo=repo) @@ -149,7 +149,7 @@ def __post_init__(self, repo: Repo | Path | str) -> None: class Git: @staticmethod - def as_repo(repo: Path | str) -> Repo: + def as_repo(repo: Path) -> Repo: return Repo(str(repo)) @staticmethod @@ -171,7 +171,7 @@ def get_revision(repo: Repo) -> str: return repo.head().decode("utf-8") @classmethod - def info(cls, repo: Repo | Path | str) -> GitRepoLocalInfo: + def info(cls, repo: Repo | Path) -> GitRepoLocalInfo: return GitRepoLocalInfo(repo=repo) @staticmethod @@ -302,7 +302,7 @@ def _clone(cls, url: str, refspec: GitRefSpec, target: Path) -> Repo: ), local.path, ) - remove_directory(local.path, force=True) + remove_directory(Path(local.path), force=True) if isinstance(e, AssertionError) and "Invalid object name" not in str(e): raise diff --git a/tests/conftest.py b/tests/conftest.py index 12f89a9f9b2..47c11049479 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -295,10 +295,11 @@ def _fixture_dir(name: str) -> Path: @pytest.fixture def tmp_dir() -> Iterator[str]: dir_ = tempfile.mkdtemp(prefix="poetry_") + path = Path(dir_) - yield Path(dir_).resolve().as_posix() + yield path.resolve().as_posix() - remove_directory(dir_, force=True) + remove_directory(path, force=True) @pytest.fixture diff --git a/tests/console/commands/env/test_use.py b/tests/console/commands/env/test_use.py index ad05e1e73b1..9cc5a9fc92a 100644 --- a/tests/console/commands/env/test_use.py +++ b/tests/console/commands/env/test_use.py @@ -55,7 +55,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( venv_cache: Path, venv_name: str, venvs_in_cache_config: None, -): +) -> None: mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(), @@ -70,7 +70,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( venv_py37 = venv_cache / f"{venv_name}-py3.7" mock_build_env.assert_called_with( venv_py37, - executable="/usr/bin/python3.7", + executable=Path("/usr/bin/python3.7"), flags={ "always-copy": False, "system-site-packages": False, @@ -99,7 +99,7 @@ def test_get_prefers_explicitly_activated_virtualenvs_over_env_var( venv_cache: Path, venv_name: str, venvs_in_cache_config: None, -): +) -> None: os.environ["VIRTUAL_ENV"] = "/environment/prefix" python_minor = ".".join(str(v) for v in current_python[:2]) @@ -128,7 +128,7 @@ def test_get_prefers_explicitly_activated_non_existing_virtualenvs_over_env_var( venv_cache: Path, venv_name: str, venvs_in_cache_config: None, -): +) -> None: os.environ["VIRTUAL_ENV"] = "/environment/prefix" python_minor = ".".join(str(v) for v in current_python[:2]) diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py index 90b50b5f3cc..1feda849149 100644 --- a/tests/console/commands/test_init.py +++ b/tests/console/commands/test_init.py @@ -36,11 +36,10 @@ @pytest.fixture def source_dir(tmp_path: Path) -> Iterator[Path]: - cwd = os.getcwd() - + cwd = Path.cwd() try: - os.chdir(str(tmp_path)) - yield Path(tmp_path.as_posix()) + os.chdir(tmp_path) + yield tmp_path finally: os.chdir(cwd) diff --git a/tests/installation/test_pip_installer.py b/tests/installation/test_pip_installer.py index d333a87b8ea..29713e1daf0 100644 --- a/tests/installation/test_pip_installer.py +++ b/tests/installation/test_pip_installer.py @@ -67,7 +67,7 @@ def installer(pool: RepositoryPool, env: NullEnv) -> PipInstaller: return PipInstaller(env, NullIO(), pool) -def test_requirement(installer: PipInstaller): +def test_requirement(installer: PipInstaller) -> None: package = Package("ipython", "7.5.0") package.files = [ {"file": "foo-0.1.0.tar.gz", "hash": "md5:dbdc53e3918f28fa335a173432402a00"}, @@ -88,7 +88,7 @@ def test_requirement(installer: PipInstaller): assert result == expected -def test_requirement_source_type_url(env: NullEnv): +def test_requirement_source_type_url(env: NullEnv) -> None: installer = PipInstaller(env, NullIO(), RepositoryPool()) foo = Package( @@ -121,7 +121,9 @@ def test_requirement_git_subdirectory( assert Path(cmd[-1]).parts[-3:] == ("demo", "subdirectories", "two") -def test_requirement_git_develop_false(installer: PipInstaller, package_git: Package): +def test_requirement_git_develop_false( + installer: PipInstaller, package_git: Package +) -> None: package_git.develop = False result = installer.requirement(package_git) expected = "git+git@github.com:demo/demo.git@master#egg=demo" @@ -131,7 +133,7 @@ def test_requirement_git_develop_false(installer: PipInstaller, package_git: Pac def test_install_with_non_pypi_default_repository( pool: RepositoryPool, installer: PipInstaller -): +) -> None: default = LegacyRepository("default", "https://default.com") another = LegacyRepository("another", "https://another.com") @@ -164,7 +166,9 @@ def test_install_with_non_pypi_default_repository( ("cert", "cert"), ], ) -def test_install_with_certs(mocker: MockerFixture, key: str, option: str, env: NullEnv): +def test_install_with_certs( + mocker: MockerFixture, key: str, option: str, env: NullEnv +) -> None: client_path = "path/to/client.pem" mocker.patch( "poetry.utils.authenticator.Authenticator.get_certs_for_url", @@ -195,7 +199,9 @@ def test_install_with_certs(mocker: MockerFixture, key: str, option: str, env: N assert cmd[cert_index + 1] == str(Path(client_path)) -def test_requirement_git_develop_true(installer: PipInstaller, package_git: Package): +def test_requirement_git_develop_true( + installer: PipInstaller, package_git: Package +) -> None: package_git.develop = True result = installer.requirement(package_git) expected = ["-e", "git+git@github.com:demo/demo.git@master#egg=demo"] @@ -205,7 +211,7 @@ def test_requirement_git_develop_true(installer: PipInstaller, package_git: Pack def test_uninstall_git_package_nspkg_pth_cleanup( mocker: MockerFixture, tmp_venv: VirtualEnv, pool: RepositoryPool -): +) -> None: # this test scenario requires a real installation using the pip installer installer = PipInstaller(tmp_venv, NullIO(), pool) @@ -236,7 +242,7 @@ def copy_only(source: Path, dest: Path) -> None: installer.install(package) installer.remove(package) - pth_file = f"{package.name}-nspkg.pth" + pth_file = Path(f"{package.name}-nspkg.pth") assert not tmp_venv.site_packages.exists(pth_file) # any command in the virtual environment should trigger the error message @@ -244,7 +250,7 @@ def copy_only(source: Path, dest: Path) -> None: assert not re.match(rf"Error processing line 1 of .*{pth_file}", output) -def test_install_with_trusted_host(config: Config, env: NullEnv): +def test_install_with_trusted_host(config: Config, env: NullEnv) -> None: config.merge({"certificates": {"default": {"cert": False}}}) default = LegacyRepository("default", "https://foo.bar") @@ -272,7 +278,7 @@ def test_install_with_trusted_host(config: Config, env: NullEnv): def test_install_directory_fallback_on_poetry_create_error( mocker: MockerFixture, tmp_venv: VirtualEnv, pool: RepositoryPool -): +) -> None: mock_create_poetry = mocker.patch( "poetry.factory.Factory.create_poetry", side_effect=RuntimeError ) diff --git a/tests/masonry/builders/test_editable_builder.py b/tests/masonry/builders/test_editable_builder.py index 529f2d03e22..3e90a0b3e24 100644 --- a/tests/masonry/builders/test_editable_builder.py +++ b/tests/masonry/builders/test_editable_builder.py @@ -86,7 +86,7 @@ def env_manager(simple_poetry: Poetry) -> EnvManager: def tmp_venv(tmp_dir: str, env_manager: EnvManager) -> VirtualEnv: venv_path = Path(tmp_dir) / "venv" - env_manager.build_venv(str(venv_path)) + env_manager.build_venv(venv_path) venv = VirtualEnv(venv_path) yield venv @@ -96,20 +96,20 @@ def tmp_venv(tmp_dir: str, env_manager: EnvManager) -> VirtualEnv: def test_builder_installs_proper_files_for_standard_packages( simple_poetry: Poetry, tmp_venv: VirtualEnv -): +) -> None: builder = EditableBuilder(simple_poetry, tmp_venv, NullIO()) builder.build() assert tmp_venv._bin_dir.joinpath("foo").exists() - pth_file = "simple_project.pth" + pth_file = Path("simple_project.pth") assert tmp_venv.site_packages.exists(pth_file) assert ( simple_poetry.file.parent.resolve().as_posix() == tmp_venv.site_packages.find(pth_file)[0].read_text().strip(os.linesep) ) - dist_info = "simple_project-1.2.3.dist-info" + dist_info = Path("simple_project-1.2.3.dist-info") assert tmp_venv.site_packages.exists(dist_info) dist_info = tmp_venv.site_packages.find(dist_info)[0] @@ -176,7 +176,7 @@ def test_builder_installs_proper_files_for_standard_packages( assert all(len(row) == 3 for row in records) record_entries = {row[0] for row in records} - pth_file = "simple_project.pth" + pth_file = Path("simple_project.pth") assert tmp_venv.site_packages.exists(pth_file) assert str(tmp_venv.site_packages.find(pth_file)[0]) in record_entries assert str(tmp_venv._bin_dir.joinpath("foo")) in record_entries @@ -223,7 +223,7 @@ def test_builder_installs_proper_files_for_standard_packages( def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts( mocker: MockerFixture, extended_poetry: Poetry, tmp_dir: str -): +) -> None: pip_install = mocker.patch("poetry.masonry.builders.editable.pip_install") env = MockEnv(path=Path(tmp_dir) / "foo") builder = EditableBuilder(extended_poetry, env, NullIO()) @@ -273,11 +273,11 @@ def test_builder_setup_generation_runs_with_pip_editable(tmp_dir: str) -> None: def test_builder_installs_proper_files_when_packages_configured( project_with_include: Poetry, tmp_venv: VirtualEnv -): +) -> None: builder = EditableBuilder(project_with_include, tmp_venv, NullIO()) builder.build() - pth_file = "with_include.pth" + pth_file = Path("with_include.pth") assert tmp_venv.site_packages.exists(pth_file) pth_file = tmp_venv.site_packages.find(pth_file)[0] @@ -298,12 +298,12 @@ def test_builder_installs_proper_files_when_packages_configured( def test_builder_generates_proper_metadata_when_multiple_readme_files( with_multiple_readme_files: Poetry, tmp_venv: VirtualEnv -): +) -> None: builder = EditableBuilder(with_multiple_readme_files, tmp_venv, NullIO()) builder.build() - dist_info = "my_package-0.1.dist-info" + dist_info = Path("my_package-0.1.dist-info") assert tmp_venv.site_packages.exists(dist_info) dist_info = tmp_venv.site_packages.find(dist_info)[0] @@ -336,7 +336,7 @@ def test_builder_generates_proper_metadata_when_multiple_readme_files( def test_builder_should_execute_build_scripts( mocker: MockerFixture, extended_without_setup_poetry: Poetry, tmp_path: Path -): +) -> None: env = MockEnv(path=tmp_path / "foo") mocker.patch( "poetry.masonry.builders.editable.build_environment" diff --git a/tests/packages/test_locker.py b/tests/packages/test_locker.py index a86b5d770ea..6f832dc8206 100644 --- a/tests/packages/test_locker.py +++ b/tests/packages/test_locker.py @@ -34,7 +34,7 @@ def locker() -> Locker: with tempfile.NamedTemporaryFile() as f: f.close() - locker = Locker(f.name, {}) + locker = Locker(Path(f.name), {}) return locker @@ -44,7 +44,7 @@ def root() -> ProjectPackage: return ProjectPackage("root", "1.2.3") -def test_lock_file_data_is_ordered(locker: Locker, root: ProjectPackage): +def test_lock_file_data_is_ordered(locker: Locker, root: ProjectPackage) -> None: package_a = get_package("A", "1.0.0") package_a.add_dependency(Factory.create_dependency("B", "^1.0")) package_a.files = [{"file": "foo", "hash": "456"}, {"file": "bar", "hash": "123"}] @@ -200,7 +200,7 @@ def test_lock_file_data_is_ordered(locker: Locker, root: ProjectPackage): assert content == expected -def test_locker_properly_loads_extras(locker: Locker): +def test_locker_properly_loads_extras(locker: Locker) -> None: content = f"""\ # {GENERATED_COMMENT} @@ -246,7 +246,7 @@ def test_locker_properly_loads_extras(locker: Locker): assert lockfile_dep.name == "lockfile" -def test_locker_properly_loads_nested_extras(locker: Locker): +def test_locker_properly_loads_nested_extras(locker: Locker) -> None: content = f"""\ # {GENERATED_COMMENT} @@ -327,7 +327,7 @@ def test_locker_properly_loads_nested_extras(locker: Locker): assert len(packages) == 1 -def test_locker_properly_loads_extras_legacy(locker: Locker): +def test_locker_properly_loads_extras_legacy(locker: Locker) -> None: content = f"""\ # {GENERATED_COMMENT} @@ -530,7 +530,9 @@ def test_locker_properly_assigns_metadata_files(locker: Locker) -> None: package.files = [] -def test_lock_packages_with_null_description(locker: Locker, root: ProjectPackage): +def test_lock_packages_with_null_description( + locker: Locker, root: ProjectPackage +) -> None: package_a = get_package("A", "1.0.0") package_a.description = None @@ -560,7 +562,9 @@ def test_lock_packages_with_null_description(locker: Locker, root: ProjectPackag assert content == expected -def test_lock_file_should_not_have_mixed_types(locker: Locker, root: ProjectPackage): +def test_lock_file_should_not_have_mixed_types( + locker: Locker, root: ProjectPackage +) -> None: package_a = get_package("A", "1.0.0") package_a.add_dependency(Factory.create_dependency("B", "^1.0.0")) package_a.add_dependency( @@ -604,7 +608,9 @@ def test_lock_file_should_not_have_mixed_types(locker: Locker, root: ProjectPack assert content == expected -def test_reading_lock_file_should_raise_an_error_on_invalid_data(locker: Locker): +def test_reading_lock_file_should_raise_an_error_on_invalid_data( + locker: Locker, +) -> None: content = f"""\ # {GENERATED_COMMENT} @@ -639,7 +645,7 @@ def test_reading_lock_file_should_raise_an_error_on_invalid_data(locker: Locker) def test_locking_legacy_repository_package_should_include_source_section( root: ProjectPackage, locker: Locker -): +) -> None: package_a = Package( "A", "1.0.0", @@ -682,7 +688,7 @@ def test_locking_legacy_repository_package_should_include_source_section( def test_locker_should_emit_warnings_if_lock_version_is_newer_but_allowed( locker: Locker, caplog: LogCaptureFixture -): +) -> None: version = ".".join(Version.parse(Locker._VERSION).next_minor().text.split(".")[:2]) content = f"""\ [metadata] @@ -712,7 +718,7 @@ def test_locker_should_emit_warnings_if_lock_version_is_newer_but_allowed( def test_locker_should_raise_an_error_if_lock_version_is_newer_and_not_allowed( locker: Locker, caplog: LogCaptureFixture -): +) -> None: content = f"""\ # {GENERATED_COMMENT} @@ -730,7 +736,9 @@ def test_locker_should_raise_an_error_if_lock_version_is_newer_and_not_allowed( _ = locker.lock_data -def test_root_extras_dependencies_are_ordered(locker: Locker, root: ProjectPackage): +def test_root_extras_dependencies_are_ordered( + locker: Locker, root: ProjectPackage +) -> None: root_dir = Path(__file__).parent.parent.joinpath("fixtures") Factory.create_dependency("B", "1.0.0", root_dir=root_dir) Factory.create_dependency("C", "1.0.0", root_dir=root_dir) @@ -765,7 +773,7 @@ def test_root_extras_dependencies_are_ordered(locker: Locker, root: ProjectPacka assert content == expected -def test_extras_dependencies_are_ordered(locker: Locker, root: ProjectPackage): +def test_extras_dependencies_are_ordered(locker: Locker, root: ProjectPackage) -> None: package_a = get_package("A", "1.0.0") package_a.add_dependency( Factory.create_dependency( @@ -805,7 +813,7 @@ def test_extras_dependencies_are_ordered(locker: Locker, root: ProjectPackage): def test_locker_should_neither_emit_warnings_nor_raise_error_for_lower_compatible_versions( # noqa: E501 locker: Locker, caplog: LogCaptureFixture -): +) -> None: older_version = "1.1" content = f"""\ [metadata] @@ -827,7 +835,7 @@ def test_locker_should_neither_emit_warnings_nor_raise_error_for_lower_compatibl def test_locker_dumps_dependency_information_correctly( locker: Locker, root: ProjectPackage -): +) -> None: root_dir = Path(__file__).parent.parent.joinpath("fixtures") package_a = get_package("A", "1.0.0") package_a.add_dependency( @@ -950,7 +958,7 @@ def test_locker_dumps_subdir(locker: Locker, root: ProjectPackage) -> None: def test_locker_dumps_dependency_extras_in_correct_order( locker: Locker, root: ProjectPackage -): +) -> None: root_dir = Path(__file__).parent.parent.joinpath("fixtures") package_a = get_package("A", "1.0.0") Factory.create_dependency("B", "1.0.0", root_dir=root_dir) @@ -996,7 +1004,7 @@ def test_locker_dumps_dependency_extras_in_correct_order( def test_locked_repository_uses_root_dir_of_package( locker: Locker, mocker: MockerFixture -): +) -> None: content = f"""\ # {GENERATED_COMMENT} @@ -1084,7 +1092,7 @@ def test_content_hash_with_legacy_is_compatible( assert (content_hash == old_content_hash) or fresh -def test_lock_file_resolves_file_url_symlinks(root: ProjectPackage): +def test_lock_file_resolves_file_url_symlinks(root: ProjectPackage) -> None: """ Create directories and file structure as follows: @@ -1119,7 +1127,7 @@ def test_lock_file_resolves_file_url_symlinks(root: ProjectPackage): # Test is not possible in that case. return raise - locker = Locker(str(symlink_path) + os.sep + Path(lock_file.name).name, {}) + locker = Locker(symlink_path / lock_file.name, {}) package_local = Package( "local-package", diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 07823d4fb55..b720974212a 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -68,7 +68,7 @@ def __init__( self._sys_path = sys_path @property - def sys_path(self) -> list[str] | None: + def sys_path(self) -> list[str]: if self._sys_path is not None: return self._sys_path @@ -88,10 +88,10 @@ def manager(poetry: Poetry) -> EnvManager: def test_virtualenvs_with_spaces_in_their_path_work_as_expected( tmp_dir: str, manager: EnvManager -): +) -> None: venv_path = Path(tmp_dir) / "Virtual Env" - manager.build_venv(str(venv_path)) + manager.build_venv(venv_path) venv = VirtualEnv(venv_path) @@ -99,12 +99,12 @@ def test_virtualenvs_with_spaces_in_their_path_work_as_expected( @pytest.mark.skipif(sys.platform != "darwin", reason="requires darwin") -def test_venv_backup_exclusion(tmp_dir: str, manager: EnvManager): +def test_venv_backup_exclusion(tmp_dir: str, manager: EnvManager) -> None: import xattr venv_path = Path(tmp_dir) / "Virtual Env" - manager.build_venv(str(venv_path)) + manager.build_venv(venv_path) value = ( b"bplist00_\x10\x11com.apple.backupd" @@ -121,20 +121,20 @@ def test_venv_backup_exclusion(tmp_dir: str, manager: EnvManager): def test_env_commands_with_spaces_in_their_arg_work_as_expected( tmp_dir: str, manager: EnvManager -): +) -> None: venv_path = Path(tmp_dir) / "Virtual Env" - manager.build_venv(str(venv_path)) + manager.build_venv(venv_path) venv = VirtualEnv(venv_path) - assert venv.run("python", venv.pip, "--version").startswith( + assert venv.run("python", str(venv.pip), "--version").startswith( f"pip {venv.pip_version} from " ) def test_env_shell_commands_with_stdinput_in_their_arg_work_as_expected( tmp_dir: str, manager: EnvManager -): +) -> None: venv_path = Path(tmp_dir) / "Virtual Env" - manager.build_venv(str(venv_path)) + manager.build_venv(venv_path) venv = VirtualEnv(venv_path) run_output_path = Path(venv.run("python", "-", input_=GET_BASE_PREFIX).strip()) venv_base_prefix_path = Path(str(venv.get_base_prefix())) @@ -143,9 +143,9 @@ def test_env_shell_commands_with_stdinput_in_their_arg_work_as_expected( def test_env_get_supported_tags_matches_inside_virtualenv( tmp_dir: str, manager: EnvManager -): +) -> None: venv_path = Path(tmp_dir) / "Virtual Env" - manager.build_venv(str(venv_path)) + manager.build_venv(venv_path) venv = VirtualEnv(venv_path) import packaging.tags @@ -170,7 +170,7 @@ def test_env_get_venv_with_venv_folder_present( poetry: Poetry, in_project_venv_dir: Path, in_project: bool | None, -): +) -> None: poetry.config.config["virtualenvs"]["in-project"] = in_project venv = manager.get() if in_project is False: @@ -212,7 +212,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( config: Config, mocker: MockerFixture, venv_name: str, -): +) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -232,7 +232,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( m.assert_called_with( Path(tmp_dir) / f"{venv_name}-py3.7", - executable="/usr/bin/python3.7", + executable=Path("/usr/bin/python3.7"), flags={ "always-copy": False, "system-site-packages": False, @@ -259,7 +259,7 @@ def test_activate_activates_existing_virtualenv_no_envs_file( config: Config, mocker: MockerFixture, venv_name: str, -): +) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -298,7 +298,7 @@ def test_activate_activates_same_virtualenv_with_envs_file( config: Config, mocker: MockerFixture, venv_name: str, -): +) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -341,7 +341,7 @@ def test_activate_activates_different_virtualenv_with_envs_file( config: Config, mocker: MockerFixture, venv_name: str, -): +) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -368,7 +368,7 @@ def test_activate_activates_different_virtualenv_with_envs_file( m.assert_called_with( Path(tmp_dir) / f"{venv_name}-py3.6", - executable="/usr/bin/python3.6", + executable=Path("/usr/bin/python3.6"), flags={ "always-copy": False, "system-site-packages": False, @@ -394,7 +394,7 @@ def test_activate_activates_recreates_for_different_patch( config: Config, mocker: MockerFixture, venv_name: str, -): +) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -432,7 +432,7 @@ def test_activate_activates_recreates_for_different_patch( build_venv_m.assert_called_with( Path(tmp_dir) / f"{venv_name}-py3.7", - executable="/usr/bin/python3.7", + executable=Path("/usr/bin/python3.7"), flags={ "always-copy": False, "system-site-packages": False, @@ -460,7 +460,7 @@ def test_activate_does_not_recreate_when_switching_minor( config: Config, mocker: MockerFixture, venv_name: str, -): +) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -511,7 +511,7 @@ def test_deactivate_non_activated_but_existing( config: Config, mocker: MockerFixture, venv_name: str, -): +) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -529,7 +529,6 @@ def test_deactivate_non_activated_but_existing( env = manager.get() assert env.path == Path(tmp_dir) / f"{venv_name}-py{python}" - assert Path("/prefix") def test_deactivate_activated( @@ -539,7 +538,7 @@ def test_deactivate_activated( config: Config, mocker: MockerFixture, venv_name: str, -): +) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -569,7 +568,6 @@ def test_deactivate_activated( env = manager.get() assert env.path == Path(tmp_dir) / f"{venv_name}-py{version.major}.{version.minor}" - assert Path("/prefix") envs = envs_file.read() assert len(envs) == 0 @@ -582,7 +580,7 @@ def test_get_prefers_explicitly_activated_virtualenvs_over_env_var( config: Config, mocker: MockerFixture, venv_name: str, -): +) -> None: os.environ["VIRTUAL_ENV"] = "/environment/prefix" config.merge({"virtualenvs": {"path": str(tmp_dir)}}) @@ -614,7 +612,7 @@ def test_list( poetry: Poetry, config: Config, venv_name: str, -): +) -> None: config.merge({"virtualenvs": {"path": str(tmp_dir)}}) (Path(tmp_dir) / f"{venv_name}-py3.7").mkdir() @@ -634,7 +632,7 @@ def test_remove_by_python_version( config: Config, mocker: MockerFixture, venv_name: str, -): +) -> None: config.merge({"virtualenvs": {"path": str(tmp_dir)}}) (Path(tmp_dir) / f"{venv_name}-py3.7").mkdir() @@ -659,7 +657,7 @@ def test_remove_by_name( config: Config, mocker: MockerFixture, venv_name: str, -): +) -> None: config.merge({"virtualenvs": {"path": str(tmp_dir)}}) (Path(tmp_dir) / f"{venv_name}-py3.7").mkdir() @@ -684,7 +682,7 @@ def test_remove_by_string_with_python_and_version( config: Config, mocker: MockerFixture, venv_name: str, -): +) -> None: config.merge({"virtualenvs": {"path": str(tmp_dir)}}) (Path(tmp_dir) / f"{venv_name}-py3.7").mkdir() @@ -709,7 +707,7 @@ def test_remove_by_full_path_to_python( config: Config, mocker: MockerFixture, venv_name: str, -): +) -> None: config.merge({"virtualenvs": {"path": str(tmp_dir)}}) (Path(tmp_dir) / f"{venv_name}-py3.7").mkdir() @@ -735,7 +733,7 @@ def test_raises_if_acting_on_different_project_by_full_path( poetry: Poetry, config: Config, mocker: MockerFixture, -): +) -> None: config.merge({"virtualenvs": {"path": str(tmp_dir)}}) different_venv_name = "different-project" @@ -761,7 +759,7 @@ def test_raises_if_acting_on_different_project_by_name( manager: EnvManager, poetry: Poetry, config: Config, -): +) -> None: config.merge({"virtualenvs": {"path": str(tmp_dir)}}) different_venv_name = ( @@ -788,7 +786,7 @@ def test_raises_when_passing_old_env_after_dir_rename( poetry: Poetry, config: Config, venv_name: str, -): +) -> None: # Make sure that poetry raises when trying to remove old venv after you've renamed # root directory of the project, which will create another venv with new name. # This is not ideal as you still "can't" remove it by name, but it at least doesn't @@ -817,7 +815,7 @@ def test_remove_also_deactivates( config: Config, mocker: MockerFixture, venv_name: str, -): +) -> None: config.merge({"virtualenvs": {"path": str(tmp_dir)}}) (Path(tmp_dir) / f"{venv_name}-py3.7").mkdir() @@ -850,7 +848,7 @@ def test_remove_keeps_dir_if_not_deleteable( config: Config, mocker: MockerFixture, venv_name: str, -): +) -> None: # Ensure we empty rather than delete folder if its is an active mount point. # See https://github.com/python-poetry/poetry/pull/2064 config.merge({"virtualenvs": {"path": str(tmp_dir)}}) @@ -897,17 +895,17 @@ def err_on_rm_venv_only(path: Path | str, *args: Any, **kwargs: Any) -> None: @pytest.mark.skipif(os.name == "nt", reason="Symlinks are not support for Windows") -def test_env_has_symlinks_on_nix(tmp_dir: str, tmp_venv: VirtualEnv): +def test_env_has_symlinks_on_nix(tmp_dir: str, tmp_venv: VirtualEnv) -> None: assert os.path.islink(tmp_venv.python) -def test_run_with_input(tmp_dir: str, tmp_venv: VirtualEnv): +def test_run_with_input(tmp_dir: str, tmp_venv: VirtualEnv) -> None: result = tmp_venv.run("python", "-", input_=MINIMAL_SCRIPT) assert result == "Minimal Output" + os.linesep -def test_run_with_input_non_zero_return(tmp_dir: str, tmp_venv: VirtualEnv): +def test_run_with_input_non_zero_return(tmp_dir: str, tmp_venv: VirtualEnv) -> None: with pytest.raises(EnvCommandError) as process_error: # Test command that will return non-zero returncode. tmp_venv.run("python", "-", input_=ERRORING_SCRIPT) @@ -917,7 +915,7 @@ def test_run_with_input_non_zero_return(tmp_dir: str, tmp_venv: VirtualEnv): def test_run_with_keyboard_interrupt( tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture -): +) -> None: mocker.patch("subprocess.run", side_effect=KeyboardInterrupt()) with pytest.raises(KeyboardInterrupt): tmp_venv.run("python", "-", input_=MINIMAL_SCRIPT) @@ -926,7 +924,7 @@ def test_run_with_keyboard_interrupt( def test_call_with_input_and_keyboard_interrupt( tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture -): +) -> None: mocker.patch("subprocess.run", side_effect=KeyboardInterrupt()) kwargs = {"call": True} with pytest.raises(KeyboardInterrupt): @@ -936,7 +934,7 @@ def test_call_with_input_and_keyboard_interrupt( def test_call_no_input_with_keyboard_interrupt( tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture -): +) -> None: mocker.patch("subprocess.call", side_effect=KeyboardInterrupt()) kwargs = {"call": True} with pytest.raises(KeyboardInterrupt): @@ -946,7 +944,7 @@ def test_call_no_input_with_keyboard_interrupt( def test_run_with_called_process_error( tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture -): +) -> None: mocker.patch( "subprocess.run", side_effect=subprocess.CalledProcessError( @@ -962,7 +960,7 @@ def test_run_with_called_process_error( def test_call_with_input_and_called_process_error( tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture -): +) -> None: mocker.patch( "subprocess.run", side_effect=subprocess.CalledProcessError( @@ -979,7 +977,7 @@ def test_call_with_input_and_called_process_error( def test_call_no_input_with_called_process_error( tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture -): +) -> None: mocker.patch( "subprocess.call", side_effect=subprocess.CalledProcessError( @@ -996,7 +994,7 @@ def test_call_no_input_with_called_process_error( def test_check_output_with_called_process_error( tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture -): +) -> None: mocker.patch( "subprocess.check_output", side_effect=subprocess.CalledProcessError( @@ -1037,7 +1035,7 @@ def target(result: list[int]) -> None: def test_run_python_script_called_process_error( tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture -): +) -> None: mocker.patch( "subprocess.run", side_effect=subprocess.CalledProcessError( @@ -1050,7 +1048,7 @@ def test_run_python_script_called_process_error( assert "some error" in str(error.value) -def test_run_python_script_only_stdout(tmp_dir: str, tmp_venv: VirtualEnv): +def test_run_python_script_only_stdout(tmp_dir: str, tmp_venv: VirtualEnv) -> None: output = tmp_venv.run_python_script( "import sys; print('some warning', file=sys.stderr); print('some output')" ) @@ -1065,7 +1063,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ mocker: MockerFixture, config_virtualenvs_path: Path, venv_name: str, -): +) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -1084,7 +1082,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py3.7", - executable="/usr/bin/python3", + executable=Path("/usr/bin/python3"), flags={ "always-copy": False, "system-site-packages": False, @@ -1102,7 +1100,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific mocker: MockerFixture, config_virtualenvs_path: Path, venv_name: str, -): +) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -1120,7 +1118,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py3.9", - executable="/usr/bin/python3.9", + executable=Path("/usr/bin/python3.9"), flags={ "always-copy": False, "system-site-packages": False, @@ -1133,7 +1131,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific def test_create_venv_fails_if_no_compatible_python_version_could_be_found( manager: EnvManager, poetry: Poetry, config: Config, mocker: MockerFixture -): +) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -1159,7 +1157,7 @@ def test_create_venv_fails_if_no_compatible_python_version_could_be_found( def test_create_venv_does_not_try_to_find_compatible_versions_with_executable( manager: EnvManager, poetry: Poetry, config: Config, mocker: MockerFixture -): +) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -1171,7 +1169,7 @@ def test_create_venv_does_not_try_to_find_compatible_versions_with_executable( ) with pytest.raises(NoCompatiblePythonVersionFound) as e: - manager.create_venv(executable="3.8") + manager.create_venv(executable=Path("python3.8")) expected_message = ( "The specified Python version (3.8.0) is not supported by the project (^4.8).\n" @@ -1190,7 +1188,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility( mocker: MockerFixture, config_virtualenvs_path: Path, venv_name: str, -): +) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -1231,7 +1229,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable( mocker: MockerFixture, config_virtualenvs_path: Path, venv_name: str, -): +) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -1249,12 +1247,12 @@ def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable( "poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: "" ) - manager.create_venv(executable=f"python{version.major}.{version.minor - 1}") + manager.create_venv(executable=Path(f"python{version.major}.{version.minor - 1}")) assert check_output.called m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py{version.major}.{version.minor - 1}", - executable=f"python{version.major}.{version.minor - 1}", + executable=Path(f"python{version.major}.{version.minor - 1}"), flags={ "always-copy": False, "system-site-packages": False, @@ -1267,7 +1265,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable( def test_create_venv_fails_if_current_python_version_is_not_supported( manager: EnvManager, poetry: Poetry -): +) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -1298,7 +1296,7 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( config: Config, tmp_dir: str, mocker: MockerFixture, -): +) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -1325,7 +1323,7 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( m.assert_called_with( poetry.file.parent / ".venv", - executable="/usr/bin/python3.7", + executable=Path("/usr/bin/python3.7"), flags={ "always-copy": False, "system-site-packages": False, @@ -1339,7 +1337,7 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( assert not envs_file.exists() -def test_system_env_has_correct_paths(): +def test_system_env_has_correct_paths() -> None: env = SystemEnv(Path(sys.prefix)) paths = env.paths @@ -1355,7 +1353,7 @@ def test_system_env_has_correct_paths(): "enabled", [True, False], ) -def test_system_env_usersite(mocker: MockerFixture, enabled: bool): +def test_system_env_usersite(mocker: MockerFixture, enabled: bool) -> None: mocker.patch("site.check_enableusersite", return_value=enabled) env = SystemEnv(Path(sys.prefix)) assert (enabled and env.usersite is not None) or ( @@ -1363,7 +1361,7 @@ def test_system_env_usersite(mocker: MockerFixture, enabled: bool): ) -def test_venv_has_correct_paths(tmp_venv: VirtualEnv): +def test_venv_has_correct_paths(tmp_venv: VirtualEnv) -> None: paths = tmp_venv.paths assert paths.get("purelib") is not None @@ -1377,7 +1375,7 @@ def test_venv_has_correct_paths(tmp_venv: VirtualEnv): ) -def test_env_system_packages(tmp_path: Path, poetry: Poetry): +def test_env_system_packages(tmp_path: Path, poetry: Poetry) -> None: venv_path = tmp_path / "venv" pyvenv_cfg = venv_path / "pyvenv.cfg" @@ -1401,7 +1399,7 @@ def test_env_system_packages(tmp_path: Path, poetry: Poetry): ) def test_env_no_pip( tmp_path: Path, poetry: Poetry, flags: dict[str, bool], packages: set[str] -): +) -> None: venv_path = tmp_path / "venv" EnvManager(poetry).build_venv(path=venv_path, flags=flags) env = VirtualEnv(venv_path) @@ -1416,9 +1414,9 @@ def test_env_no_pip( assert installed_packages == packages -def test_env_finds_the_correct_executables(tmp_dir: str, manager: EnvManager): +def test_env_finds_the_correct_executables(tmp_dir: str, manager: EnvManager) -> None: venv_path = Path(tmp_dir) / "Virtual Env" - manager.build_venv(str(venv_path), with_pip=True) + manager.build_venv(venv_path, with_pip=True) venv = VirtualEnv(venv_path) default_executable = expected_executable = f"python{'.exe' if WINDOWS else ''}" @@ -1448,14 +1446,12 @@ def test_env_finds_the_correct_executables(tmp_dir: str, manager: EnvManager): def test_env_finds_the_correct_executables_for_generic_env( tmp_dir: str, manager: EnvManager -): +) -> None: venv_path = Path(tmp_dir) / "Virtual Env" child_venv_path = Path(tmp_dir) / "Child Virtual Env" - manager.build_venv(str(venv_path), with_pip=True) + manager.build_venv(venv_path, with_pip=True) parent_venv = VirtualEnv(venv_path) - manager.build_venv( - str(child_venv_path), executable=parent_venv.python, with_pip=True - ) + manager.build_venv(child_venv_path, executable=parent_venv.python, with_pip=True) venv = GenericEnv(parent_venv.path, child_env=VirtualEnv(child_venv_path)) expected_executable = ( @@ -1475,14 +1471,12 @@ def test_env_finds_the_correct_executables_for_generic_env( def test_env_finds_fallback_executables_for_generic_env( tmp_dir: str, manager: EnvManager -): +) -> None: venv_path = Path(tmp_dir) / "Virtual Env" child_venv_path = Path(tmp_dir) / "Child Virtual Env" - manager.build_venv(str(venv_path), with_pip=True) + manager.build_venv(venv_path, with_pip=True) parent_venv = VirtualEnv(venv_path) - manager.build_venv( - str(child_venv_path), executable=parent_venv.python, with_pip=True - ) + manager.build_venv(child_venv_path, executable=parent_venv.python, with_pip=True) venv = GenericEnv(parent_venv.path, child_env=VirtualEnv(child_venv_path)) default_executable = f"python{'.exe' if WINDOWS else ''}" @@ -1544,7 +1538,7 @@ def test_create_venv_accepts_fallback_version_w_nonzero_patchlevel( mocker: MockerFixture, config_virtualenvs_path: Path, venv_name: str, -): +) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -1572,7 +1566,7 @@ def mock_check_output(cmd: str, *args: Any, **kwargs: Any) -> str: assert check_output.called m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py3.5", - executable="/usr/bin/python3.5", + executable=Path("/usr/bin/python3.5"), flags={ "always-copy": False, "system-site-packages": False, @@ -1586,7 +1580,7 @@ def mock_check_output(cmd: str, *args: Any, **kwargs: Any) -> str: def test_generate_env_name_ignores_case_for_case_insensitive_fs( poetry: Poetry, tmp_dir: str, -): +) -> None: venv_name1 = EnvManager.generate_env_name(poetry.package.name, "MyDiR") venv_name2 = EnvManager.generate_env_name(poetry.package.name, "mYdIr") if sys.platform == "win32": @@ -1595,7 +1589,7 @@ def test_generate_env_name_ignores_case_for_case_insensitive_fs( assert venv_name1 != venv_name2 -def test_generate_env_name_uses_real_path(tmp_dir: str, mocker: MockerFixture): +def test_generate_env_name_uses_real_path(tmp_dir: str, mocker: MockerFixture) -> None: mocker.patch("os.path.realpath", return_value="the_real_dir") venv_name1 = EnvManager.generate_env_name("simple-project", "the_real_dir") venv_name2 = EnvManager.generate_env_name("simple-project", "linked_dir") @@ -1613,7 +1607,7 @@ def extended_without_setup_poetry() -> Poetry: def test_build_environment_called_build_script_specified( mocker: MockerFixture, extended_without_setup_poetry: Poetry, tmp_dir: str -): +) -> None: project_env = MockEnv(path=Path(tmp_dir) / "project") ephemeral_env = MockEnv(path=Path(tmp_dir) / "ephemeral") @@ -1625,8 +1619,8 @@ def test_build_environment_called_build_script_specified( assert env == ephemeral_env assert env.executed == [ [ - sys.executable, - env.pip_embedded, + str(sys.executable), + str(env.pip_embedded), "install", "--disable-pip-version-check", "--ignore-installed", @@ -1638,7 +1632,7 @@ def test_build_environment_called_build_script_specified( def test_build_environment_not_called_without_build_script_specified( mocker: MockerFixture, poetry: Poetry, tmp_dir: str -): +) -> None: project_env = MockEnv(path=Path(tmp_dir) / "project") ephemeral_env = MockEnv(path=Path(tmp_dir) / "ephemeral") @@ -1656,7 +1650,7 @@ def test_create_venv_project_name_empty_sets_correct_prompt( config: Config, mocker: MockerFixture, config_virtualenvs_path: Path, -): +) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -1680,7 +1674,7 @@ def test_create_venv_project_name_empty_sets_correct_prompt( m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py3.7", - executable="/usr/bin/python3", + executable=Path("/usr/bin/python3"), flags={ "always-copy": False, "system-site-packages": False, @@ -1691,7 +1685,9 @@ def test_create_venv_project_name_empty_sets_correct_prompt( ) -def test_fallback_on_detect_active_python(poetry: Poetry, mocker: MockerFixture): +def test_fallback_on_detect_active_python( + poetry: Poetry, mocker: MockerFixture +) -> None: m = mocker.patch( "subprocess.check_output", side_effect=subprocess.CalledProcessError(1, "some command"), diff --git a/tests/utils/test_setup_reader.py b/tests/utils/test_setup_reader.py index d72e5386275..551ad707b34 100644 --- a/tests/utils/test_setup_reader.py +++ b/tests/utils/test_setup_reader.py @@ -1,7 +1,6 @@ from __future__ import annotations -import os - +from pathlib import Path from typing import TYPE_CHECKING import pytest @@ -16,16 +15,16 @@ @pytest.fixture() -def setup() -> Callable[[str], str]: - def _setup(name: str) -> str: - return os.path.join(os.path.dirname(__file__), "fixtures", "setups", name) +def setup() -> Callable[[str], Path]: + def _setup(name: str) -> Path: + return Path(__file__).parent / "fixtures" / "setups" / name return _setup def test_setup_reader_read_first_level_setup_call_with_direct_types( - setup: Callable[[str], str] -): + setup: Callable[[str], Path] +) -> None: result = SetupReader.read_from_directory(setup("flask")) expected_name = "Flask" @@ -58,8 +57,8 @@ def test_setup_reader_read_first_level_setup_call_with_direct_types( def test_setup_reader_read_first_level_setup_call_with_variables( - setup: Callable[[str], str] -): + setup: Callable[[str], Path] +) -> None: result = SetupReader.read_from_directory(setup("requests")) expected_name = None @@ -85,8 +84,8 @@ def test_setup_reader_read_first_level_setup_call_with_variables( def test_setup_reader_read_sub_level_setup_call_with_direct_types( - setup: Callable[[str], str] -): + setup: Callable[[str], Path] +) -> None: result = SetupReader.read_from_directory(setup("sqlalchemy")) expected_name = "SQLAlchemy" @@ -110,7 +109,7 @@ def test_setup_reader_read_sub_level_setup_call_with_direct_types( assert result["python_requires"] is None -def test_setup_reader_read_setup_cfg(setup: Callable[[str], str]): +def test_setup_reader_read_setup_cfg(setup: Callable[[str], Path]) -> None: result = SetupReader.read_from_directory(setup("with-setup-cfg")) expected_name = "with-setup-cfg" @@ -129,12 +128,12 @@ def test_setup_reader_read_setup_cfg(setup: Callable[[str], str]): assert result["python_requires"] == expected_python_requires -def test_setup_reader_read_setup_cfg_with_attr(setup: Callable[[str], str]): +def test_setup_reader_read_setup_cfg_with_attr(setup: Callable[[str], Path]) -> None: with pytest.raises(InvalidVersion): SetupReader.read_from_directory(setup("with-setup-cfg-attr")) -def test_setup_reader_read_setup_kwargs(setup: Callable[[str], str]): +def test_setup_reader_read_setup_kwargs(setup: Callable[[str], Path]) -> None: result = SetupReader.read_from_directory(setup("pendulum")) expected_name = "pendulum" @@ -150,7 +149,7 @@ def test_setup_reader_read_setup_kwargs(setup: Callable[[str], str]): assert result["python_requires"] == expected_python_requires -def test_setup_reader_read_setup_call_in_main(setup: Callable[[str], str]): +def test_setup_reader_read_setup_call_in_main(setup: Callable[[str], Path]) -> None: result = SetupReader.read_from_directory(setup("pyyaml")) expected_name = "PyYAML" @@ -166,7 +165,9 @@ def test_setup_reader_read_setup_call_in_main(setup: Callable[[str], str]): assert result["python_requires"] == expected_python_requires -def test_setup_reader_read_extras_require_with_variables(setup: Callable[[str], str]): +def test_setup_reader_read_extras_require_with_variables( + setup: Callable[[str], Path] +) -> None: result = SetupReader.read_from_directory(setup("extras_require_with_vars")) expected_name = "extras_require_with_vars" @@ -182,7 +183,7 @@ def test_setup_reader_read_extras_require_with_variables(setup: Callable[[str], assert result["python_requires"] == expected_python_requires -def test_setup_reader_setuptools(setup: Callable[[str], str]): +def test_setup_reader_setuptools(setup: Callable[[str], Path]) -> None: result = SetupReader.read_from_directory(setup("setuptools_setup")) expected_name = "my_package" From c2a7a8d6ea412d021a65068551fa814f0925dc40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Fri, 31 Mar 2023 21:39:33 +0200 Subject: [PATCH 120/151] installer: do not fail on invalid wheels, print only a warning (#7694) --- src/poetry/installation/executor.py | 8 +++ src/poetry/installation/wheel_installer.py | 8 ++- ..._invalid_record-0.1.0-py2.py3-none-any.whl | Bin 0 -> 1169 bytes ...invalid_record2-0.1.0-py2.py3-none-any.whl | Bin 0 -> 1307 bytes tests/installation/test_executor.py | 66 +++++++++++++++++- tests/utils/test_cache.py | 4 +- 6 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 tests/fixtures/distributions/demo_invalid_record-0.1.0-py2.py3-none-any.whl create mode 100644 tests/fixtures/distributions/demo_invalid_record2-0.1.0-py2.py3-none-any.whl diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index 5d3f699d13c..7f8b5701cd9 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -207,6 +207,14 @@ def execute(self, operations: list[Operation]) -> int: for warning in self._yanked_warnings: self._io.write_error_line(f"Warning: {warning}") + for path, issues in self._wheel_installer.invalid_wheels.items(): + formatted_issues = "\n".join(issues) + warning = ( + f"Validation of the RECORD file of {path.name} failed." + " Please report to the maintainers of that package so they can fix" + f" their build process. Details:\n{formatted_issues}\n" + ) + self._io.write_error_line(f"Warning: {warning}") return 1 if self._shutdown else 0 diff --git a/src/poetry/installation/wheel_installer.py b/src/poetry/installation/wheel_installer.py index d81fe479cae..eb324fd630c 100644 --- a/src/poetry/installation/wheel_installer.py +++ b/src/poetry/installation/wheel_installer.py @@ -10,6 +10,7 @@ from installer import install from installer.destinations import SchemeDictionaryDestination from installer.sources import WheelFile +from installer.sources import _WheelFileValidationError from poetry.__version__ import __version__ from poetry.utils._compat import WINDOWS @@ -93,12 +94,17 @@ def __init__(self, env: Env) -> None: schemes, interpreter=str(self._env.python), script_kind=script_kind ) + self.invalid_wheels: dict[Path, list[str]] = {} + def enable_bytecode_compilation(self, enable: bool = True) -> None: self._destination.bytecode_optimization_levels = (-1,) if enable else () def install(self, wheel: Path) -> None: with WheelFile.open(wheel) as source: - source.validate_record() + try: + source.validate_record() + except _WheelFileValidationError as e: + self.invalid_wheels[wheel] = e.issues install( source=source, destination=self._destination.for_source(source), diff --git a/tests/fixtures/distributions/demo_invalid_record-0.1.0-py2.py3-none-any.whl b/tests/fixtures/distributions/demo_invalid_record-0.1.0-py2.py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..184475aa2766a660ce6fac5b681ee128845088bc GIT binary patch literal 1169 zcmWIWW@Zs#U|`??Vnv1~uELiRK$aK~3jlFSYHq%Me0*kJW=VX!UO{DdzrMGw*10q1 zyZgg8ga(*jFur0O@SKSu0B(wFRY_Q8KW*zWEPj`X6B{k>-)NfIJ!87I8L2-*YA*nKCvZ^rdwT?E?Jk@w2SX!n!v^XGj|0>M$hAqHv5%ZX_s-= zBKxjI{#}Vp9&1xX=NtX|AjW#;;=j*gY40n|FI6(8IzMB|nCv{ySzT$abDHNaF5iFx zn^VDd51J=CACkVh$8*2E#=G|$@)4oUuw%Zv!&Dyja9}7$0I?>Kp&jnw>gvOL%8{!< zL4ftb-sK;dl+!L8;&$2Hv104N+V?o^~j+GN6ywDT*uN}61HICp+#jtS4{+B zEh1ePEV}{&CA4&&N=*g@8_H?22Ro0(0BCtqkR=Mx;G-GEx9|dQ1;L4 zjZDk##~YW#O==4)kn_If#;G}_m%;t^pDr`Y>20k!#M(owTKPxa1U2kAKp`r zTn!2WtQYn!|G=c2cHt1W%kGX9TMsU8nbGMfesABsUvI?=F0<8cF4!{na~g|tuwd^u zL6O{RyY+S))KO|(HvQSE9iIhrFLG^O_PJko$MUa74lOuxwg%z6FFhq;8y?JFbrt9= zYhaSlCf0dDuFn2JF1=^(mN^?RFg*Be=e?pxKz#QF&MuwDOP20F`BKOv`DEK)yT8}E z?|o<8=f7or)!7*#MTuu>elvdlnX>!dUB3N1Gb~PD-}y(@o-gFEdcKq0tKNk7>}x!V z&OG0BV*6pi8{b|X`lCF5c_5eB))@aoW;(^mQOhQLPV%_=($vkQ$4M%#|8daUiML+u zJDJ&CFm0!unn`Deie(Vje(ry=DZw#(0zbA@M+YtE5WTK=_d?vS%qw3O2UoT0txJDs za$c`TIL}7u^eZbpxiZ$3rVSsqEpYn2q>CZqFJpi=Ba;X-?o0;^A~5*h2%^xlAV{}8 z14K7wW>bM^Wl#XpZcyDsXFV>kHUWarning: Validation of the RECORD file of {wheel1} failed.\ + Please report to the maintainers of that package so they can fix their build process.\ + Details: +In .*?{wheel1}, demo/__init__.py is not mentioned in RECORD +In .*?{wheel1}, demo_invalid_record-0.1.0.dist-info/WHEEL is not mentioned in RECORD +""" + + warning2 = f"""\ +Warning: Validation of the RECORD file of {wheel2} failed.\ + Please report to the maintainers of that package so they can fix their build process.\ + Details: +In .*?{wheel2}, hash / size of demo_invalid_record2-0.1.0.dist-info/METADATA didn't\ + match RECORD +""" + + output = io.fetch_output() + error = io.fetch_error() + assert return_code == 0, f"\noutput: {output}\nerror: {error}\n" + assert re.match(f"{warning1}\n{warning2}", error) or re.match( + f"{warning2}\n{warning1}", error + ), error + + def test_execute_shows_skipped_operations_if_verbose( config: Config, pool: RepositoryPool, diff --git a/tests/utils/test_cache.py b/tests/utils/test_cache.py index e9489688e94..c2cd47f6d68 100644 --- a/tests/utils/test_cache.py +++ b/tests/utils/test_cache.py @@ -286,7 +286,9 @@ def test_get_cached_archives_for_link( ) assert archives - assert set(archives) == set(distributions.glob("demo-0.1.*")) + assert set(archives) == set(distributions.glob("*.whl")) | set( + distributions.glob("*.tar.gz") + ) @pytest.mark.parametrize( From 852000f8c1bab7917fb65912e5c914cf7d2d9930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Fri, 31 Mar 2023 21:44:13 +0200 Subject: [PATCH 121/151] docs: promote semantic versioning less aggressively --- docs/faq.md | 17 ++++++++++++++--- docs/libraries.md | 7 ++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 2a2051d294a..52e7a2be690 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -33,21 +33,32 @@ Once Poetry has cached the releases' information on your machine, the dependency will be much faster. {{% /note %}} -### Why are unbound version constraints a bad idea? +### Are unbound version constraints a bad idea? A version constraint without an upper bound such as `*` or `>=3.4` will allow updates to any future version of the dependency. This includes major versions breaking backward compatibility. Once a release of your package is published, you cannot tweak its dependencies anymore in case a dependency breaks BC – you have to do a new release but the previous one stays broken. +(Users can still work around the broken dependency by restricting it by themselves.) -The only good alternative is to define an upper bound on your constraints, +To avoid such issues you can define an upper bound on your constraints, which you can increase in a new release after testing that your package is compatible with the new major version of your dependency. -For example instead of using `>=3.4` you should use `^3.4` which allows all versions `<4.0`. +For example instead of using `>=3.4` you can use `^3.4` which allows all versions `<4.0`. The `^` operator works very well with libraries following [semantic versioning](https://semver.org). +However, when defining an upper bound, users of your package are not able to update +a dependency beyond the upper bound even if it does not break anything +and is fully compatible with your package. +You have to release a new version of your package with an increased upper bound first. + +If your package will be used as a library in other packages, it might be better to avoid +upper bounds and thus unnecessary dependency conflicts (unless you already know for sure +that the next release of the dependency will break your package). +If your package will be used as an application, it might be worth to define an upper bound. + ### Is tox supported? **Yes**. By using the [isolated builds](https://tox.readthedocs.io/en/latest/config.html#conf-isolated_build) `tox` provides, diff --git a/docs/libraries.md b/docs/libraries.md index ab56979540b..5ebe373cc94 100644 --- a/docs/libraries.md +++ b/docs/libraries.md @@ -19,10 +19,11 @@ This chapter will tell you how to make your library installable through Poetry. Poetry requires [PEP 440](https://peps.python.org/pep-0440)-compliant versions for all projects. -While Poetry does not enforce any release convention, it does encourage the use of +While Poetry does not enforce any release convention, it used to encourage the use of [semantic versioning](https://semver.org/) within the scope of -[PEP 440](https://peps.python.org/pep-0440/#semantic-versioning). This has many advantages for the end users -and allows them to set appropriate [version constraints]({{< relref "dependency-specification#version-constraints" >}}). +[PEP 440](https://peps.python.org/pep-0440/#semantic-versioning) and supports +[version constraints]({{< relref "dependency-specification/#caret-requirements" >}}) +that are especially suitable for semver. {{% note %}} From 28f708fbf3756d6596aa354dbe630ed5433fb351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Fri, 31 Mar 2023 21:44:50 +0200 Subject: [PATCH 122/151] docs: document Poetry's own versioning scheme --- docs/faq.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/faq.md b/docs/faq.md index 52e7a2be690..0a8b97b3075 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -33,6 +33,28 @@ Once Poetry has cached the releases' information on your machine, the dependency will be much faster. {{% /note %}} +### What kind of versioning scheme does Poetry use for itself? + +Poetry uses "major.minor.micro" version identifiers as mentioned in +[PEP 440](https://peps.python.org/pep-0440/#final-releases). + +Version bumps are done similar to Python's versioning: +* A major version bump (incrementing the first number) is only done for breaking changes + if a deprecation cycle is not possible and many users have to perform some manual steps + to migrate from one version to the next one. +* A minor version bump (incrementing the second number) may include new features as well + as new deprecations and drop features deprecated in an earlier minor release. +* A micro version bump (incrementing the third number) usually only includes bug fixes. + Deprecated features will not be dropped in a micro release. + +### Why does Poetry not adhere to semantic versioning? + +Because of its large user base, even small changes not considered relevant by most users +can turn out to be a breaking change for some users in hindsight. +Sticking to strict [semantic versioning](https://semver.org) and (almost) always bumping +the major version instead of the minor version does not seem desirable +since the minor version will not carry any meaning anymore. + ### Are unbound version constraints a bad idea? A version constraint without an upper bound such as `*` or `>=3.4` will allow updates to any future version of the dependency. From c8e963e668af2f71ddce2053e575ae0427b363e6 Mon Sep 17 00:00:00 2001 From: alm Date: Sat, 1 Apr 2023 16:24:39 +0300 Subject: [PATCH 123/151] Improve the locker test coverage by testing git rev and tag (#7749) --- tests/packages/test_locker.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/packages/test_locker.py b/tests/packages/test_locker.py index 6f832dc8206..80b689b1570 100644 --- a/tests/packages/test_locker.py +++ b/tests/packages/test_locker.py @@ -874,6 +874,16 @@ def test_locker_dumps_dependency_information_correctly( }, ) ) + package_a.add_dependency( + Factory.create_dependency( + "H", {"git": "https://github.com/python-poetry/poetry.git", "tag": "baz"} + ) + ) + package_a.add_dependency( + Factory.create_dependency( + "I", {"git": "https://github.com/python-poetry/poetry.git", "rev": "spam"} + ) + ) packages = [package_a] @@ -901,6 +911,8 @@ def test_locker_dumps_dependency_information_correctly( E = {{url = "https://python-poetry.org/poetry-1.2.0.tar.gz"}} F = {{git = "https://github.com/python-poetry/poetry.git", branch = "foo"}} G = {{git = "https://github.com/python-poetry/poetry.git", subdirectory = "bar"}} +H = {{git = "https://github.com/python-poetry/poetry.git", tag = "baz"}} +I = {{git = "https://github.com/python-poetry/poetry.git", rev = "spam"}} [metadata] lock-version = "2.0" From c89e994eead3fa56f573cc08276f3e8ad74f6e02 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Mon, 3 Apr 2023 12:05:42 +0100 Subject: [PATCH 124/151] update poetry to work with poetry-core after tomlkit is removed from there (#6616) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Randy Döring <30527984+radoering@users.noreply.github.com> --- src/poetry/config/config.py | 2 +- src/poetry/config/file_config_source.py | 3 +- src/poetry/console/commands/check.py | 3 +- src/poetry/console/commands/config.py | 2 +- src/poetry/console/commands/init.py | 2 +- .../console/commands/self/self_command.py | 2 +- src/poetry/factory.py | 14 +++-- src/poetry/inspection/info.py | 2 +- src/poetry/installation/executor.py | 3 +- src/poetry/installation/pip_installer.py | 2 +- src/poetry/layouts/layout.py | 3 +- src/poetry/masonry/builders/editable.py | 14 ++--- src/poetry/packages/locker.py | 2 +- src/poetry/poetry.py | 22 ++++++- src/poetry/pyproject/__init__.py | 0 src/poetry/pyproject/toml.py | 62 +++++++++++++++++++ src/poetry/toml/__init__.py | 7 +++ src/poetry/toml/exceptions.py | 8 +++ src/poetry/toml/file.py | 41 ++++++++++++ src/poetry/utils/env.py | 2 +- tests/console/commands/env/test_list.py | 2 +- tests/console/commands/env/test_use.py | 2 +- .../console/commands/self/test_add_plugins.py | 22 ++++--- .../commands/self/test_remove_plugins.py | 3 +- tests/console/commands/test_check.py | 2 +- tests/helpers.py | 5 +- tests/installation/test_installer.py | 10 +-- tests/installation/test_installer_old.py | 10 +-- tests/integration/test_utils_vcs_git.py | 2 +- tests/json/test_schema_sources.py | 3 +- tests/pyproject/__init__.py | 0 tests/pyproject/conftest.py | 43 +++++++++++++ tests/pyproject/test_pyproject_toml.py | 47 ++++++++++++++ tests/pyproject/test_pyproject_toml_file.py | 28 +++++++++ tests/test_factory.py | 2 +- tests/utils/test_env.py | 2 +- 36 files changed, 318 insertions(+), 61 deletions(-) create mode 100644 src/poetry/pyproject/__init__.py create mode 100644 src/poetry/pyproject/toml.py create mode 100644 src/poetry/toml/__init__.py create mode 100644 src/poetry/toml/exceptions.py create mode 100644 src/poetry/toml/file.py create mode 100644 tests/pyproject/__init__.py create mode 100644 tests/pyproject/conftest.py create mode 100644 tests/pyproject/test_pyproject_toml.py create mode 100644 tests/pyproject/test_pyproject_toml_file.py diff --git a/src/poetry/config/config.py b/src/poetry/config/config.py index 49b4631fbf4..9fb220be675 100644 --- a/src/poetry/config/config.py +++ b/src/poetry/config/config.py @@ -11,12 +11,12 @@ from typing import Any from packaging.utils import canonicalize_name -from poetry.core.toml import TOMLFile from poetry.config.dict_config_source import DictConfigSource from poetry.config.file_config_source import FileConfigSource from poetry.locations import CONFIG_DIR from poetry.locations import DEFAULT_CACHE_DIR +from poetry.toml import TOMLFile if TYPE_CHECKING: diff --git a/src/poetry/config/file_config_source.py b/src/poetry/config/file_config_source.py index 7119fa19911..b3ccbb2f925 100644 --- a/src/poetry/config/file_config_source.py +++ b/src/poetry/config/file_config_source.py @@ -13,9 +13,10 @@ if TYPE_CHECKING: from collections.abc import Iterator - from poetry.core.toml.file import TOMLFile from tomlkit.toml_document import TOMLDocument + from poetry.toml.file import TOMLFile + class FileConfigSource(ConfigSource): def __init__(self, file: TOMLFile, auth_config: bool = False) -> None: diff --git a/src/poetry/console/commands/check.py b/src/poetry/console/commands/check.py index 8a53cd5bc09..3ca831e7213 100644 --- a/src/poetry/console/commands/check.py +++ b/src/poetry/console/commands/check.py @@ -58,9 +58,8 @@ def validate_classifiers( return errors, warnings def handle(self) -> int: - from poetry.core.pyproject.toml import PyProjectTOML - from poetry.factory import Factory + from poetry.pyproject.toml import PyProjectTOML # Load poetry config and display errors, if any poetry_file = self.poetry.file.path diff --git a/src/poetry/console/commands/config.py b/src/poetry/console/commands/config.py index 281735a6f41..a35a8b5d263 100644 --- a/src/poetry/console/commands/config.py +++ b/src/poetry/console/commands/config.py @@ -86,11 +86,11 @@ def handle(self) -> int: from pathlib import Path from poetry.core.pyproject.exceptions import PyProjectException - from poetry.core.toml.file import TOMLFile from poetry.config.config import Config from poetry.config.file_config_source import FileConfigSource from poetry.locations import CONFIG_DIR + from poetry.toml.file import TOMLFile config = Config.create() config_file = TOMLFile(CONFIG_DIR / "config.toml") diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index fdf48832ce7..c77bff07468 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -73,11 +73,11 @@ def __init__(self) -> None: def handle(self) -> int: from pathlib import Path - from poetry.core.pyproject.toml import PyProjectTOML from poetry.core.vcs.git import GitConfig from poetry.config.config import Config from poetry.layouts import layout + from poetry.pyproject.toml import PyProjectTOML from poetry.utils.env import EnvManager project_path = Path.cwd() diff --git a/src/poetry/console/commands/self/self_command.py b/src/poetry/console/commands/self/self_command.py index 5be8f8b65e7..db626f1ed4e 100644 --- a/src/poetry/console/commands/self/self_command.py +++ b/src/poetry/console/commands/self/self_command.py @@ -5,11 +5,11 @@ from poetry.core.packages.dependency import Dependency from poetry.core.packages.project_package import ProjectPackage -from poetry.core.pyproject.toml import PyProjectTOML from poetry.__version__ import __version__ from poetry.console.commands.installer_command import InstallerCommand from poetry.factory import Factory +from poetry.pyproject.toml import PyProjectTOML from poetry.utils.env import EnvManager from poetry.utils.env import SystemEnv from poetry.utils.helpers import directory diff --git a/src/poetry/factory.py b/src/poetry/factory.py index c8ec61d4cdd..a2e7205f229 100644 --- a/src/poetry/factory.py +++ b/src/poetry/factory.py @@ -12,7 +12,6 @@ from poetry.core.factory import Factory as BaseFactory from poetry.core.packages.dependency_group import MAIN_GROUP from poetry.core.packages.project_package import ProjectPackage -from poetry.core.toml.file import TOMLFile from poetry.config.config import Config from poetry.json import validate_object @@ -20,6 +19,7 @@ from poetry.plugins.plugin import Plugin from poetry.plugins.plugin_manager import PluginManager from poetry.poetry import Poetry +from poetry.toml.file import TOMLFile if TYPE_CHECKING: @@ -55,15 +55,19 @@ def create_poetry( base_poetry = super().create_poetry(cwd=cwd, with_groups=with_groups) - locker = Locker( - base_poetry.file.parent / "poetry.lock", base_poetry.local_config + # TODO: backward compatibility, can be simplified if poetry-core with + # https://github.com/python-poetry/poetry-core/pull/483 is available + poetry_file: Path = ( + getattr(base_poetry, "pyproject_path", None) or base_poetry.file.path ) + locker = Locker(poetry_file.parent / "poetry.lock", base_poetry.local_config) + # Loading global configuration config = Config.create() # Loading local configuration - local_config_file = TOMLFile(base_poetry.file.parent / "poetry.toml") + local_config_file = TOMLFile(poetry_file.parent / "poetry.toml") if local_config_file.exists(): if io.is_debug(): io.write_line(f"Loading configuration file {local_config_file.path}") @@ -82,7 +86,7 @@ def create_poetry( config.merge({"repositories": repositories}) poetry = Poetry( - base_poetry.file.path, + poetry_file, base_poetry.local_config, base_poetry.package, locker, diff --git a/src/poetry/inspection/info.py b/src/poetry/inspection/info.py index 0e3fb4b4977..e538a9cc979 100644 --- a/src/poetry/inspection/info.py +++ b/src/poetry/inspection/info.py @@ -17,12 +17,12 @@ from poetry.core.factory import Factory from poetry.core.packages.dependency import Dependency from poetry.core.packages.package import Package -from poetry.core.pyproject.toml import PyProjectTOML from poetry.core.utils.helpers import parse_requires from poetry.core.utils.helpers import temporary_directory from poetry.core.version.markers import InvalidMarker from poetry.core.version.requirements import InvalidRequirement +from poetry.pyproject.toml import PyProjectTOML from poetry.utils.env import EnvCommandError from poetry.utils.env import ephemeral_environment from poetry.utils.setup_reader import SetupReader diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index 7f8b5701cd9..fc3efe37059 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -636,9 +636,8 @@ def _prepare_git_archive(self, operation: Install | Update) -> Path: def _install_directory_without_wheel_installer( self, operation: Install | Update ) -> int: - from poetry.core.pyproject.toml import PyProjectTOML - from poetry.factory import Factory + from poetry.pyproject.toml import PyProjectTOML package = operation.package operation_message = self.get_operation_message(operation) diff --git a/src/poetry/installation/pip_installer.py b/src/poetry/installation/pip_installer.py index a528c24f215..65c91bd15e3 100644 --- a/src/poetry/installation/pip_installer.py +++ b/src/poetry/installation/pip_installer.py @@ -11,9 +11,9 @@ from typing import Any from poetry.core.constraints.version import Version -from poetry.core.pyproject.toml import PyProjectTOML from poetry.installation.base_installer import BaseInstaller +from poetry.pyproject.toml import PyProjectTOML from poetry.repositories.http_repository import HTTPRepository from poetry.utils._compat import encode from poetry.utils.helpers import remove_directory diff --git a/src/poetry/layouts/layout.py b/src/poetry/layouts/layout.py index 74dcebfedaa..21d4c29234b 100644 --- a/src/poetry/layouts/layout.py +++ b/src/poetry/layouts/layout.py @@ -5,13 +5,14 @@ from typing import Any from packaging.utils import canonicalize_name -from poetry.core.pyproject.toml import PyProjectTOML from poetry.core.utils.helpers import module_name from tomlkit import inline_table from tomlkit import loads from tomlkit import table from tomlkit.toml_document import TOMLDocument +from poetry.pyproject.toml import PyProjectTOML + if TYPE_CHECKING: from typing import Mapping diff --git a/src/poetry/masonry/builders/editable.py b/src/poetry/masonry/builders/editable.py index d9725145814..81d577b1445 100644 --- a/src/poetry/masonry/builders/editable.py +++ b/src/poetry/masonry/builders/editable.py @@ -4,7 +4,6 @@ import hashlib import json import os -import shutil from base64 import urlsafe_b64encode from pathlib import Path @@ -44,6 +43,7 @@ class EditableBuilder(Builder): def __init__(self, poetry: Poetry, env: Env, io: IO) -> None: + self._poetry: Poetry super().__init__(poetry) self._env = env @@ -105,19 +105,15 @@ def _setup_build(self) -> None: pip_install(self._path, self._env, upgrade=True, editable=True) else: # Temporarily rename pyproject.toml - shutil.move( - str(self._poetry.file), str(self._poetry.file.with_suffix(".tmp")) - ) + renamed_pyproject = self._poetry.file.with_suffix(".tmp") + self._poetry.file.path.rename(renamed_pyproject) try: pip_install(self._path, self._env, upgrade=True, editable=True) finally: - shutil.move( - str(self._poetry.file.with_suffix(".tmp")), - str(self._poetry.file), - ) + renamed_pyproject.rename(self._poetry.file.path) finally: if not has_setup: - os.remove(str(setup)) + os.remove(setup) def _add_pth(self) -> list[Path]: paths = { diff --git a/src/poetry/packages/locker.py b/src/poetry/packages/locker.py index 3d0206afb73..8784a391ebd 100644 --- a/src/poetry/packages/locker.py +++ b/src/poetry/packages/locker.py @@ -16,7 +16,6 @@ from poetry.core.constraints.version import parse_constraint from poetry.core.packages.dependency import Dependency from poetry.core.packages.package import Package -from poetry.core.toml.file import TOMLFile from poetry.core.version.markers import parse_marker from poetry.core.version.requirements import InvalidRequirement from tomlkit import array @@ -26,6 +25,7 @@ from tomlkit import table from poetry.__version__ import __version__ +from poetry.toml.file import TOMLFile from poetry.utils._compat import tomllib diff --git a/src/poetry/poetry.py b/src/poetry/poetry.py index e9cb6e5db04..3cb227eaaaa 100644 --- a/src/poetry/poetry.py +++ b/src/poetry/poetry.py @@ -2,11 +2,13 @@ from typing import TYPE_CHECKING from typing import Any +from typing import cast from poetry.core.poetry import Poetry as BasePoetry from poetry.__version__ import __version__ from poetry.config.source import Source +from poetry.pyproject.toml import PyProjectTOML if TYPE_CHECKING: @@ -18,6 +20,7 @@ from poetry.packages.locker import Locker from poetry.plugins.plugin_manager import PluginManager from poetry.repositories.repository_pool import RepositoryPool + from poetry.toml import TOMLFile class Poetry(BasePoetry): @@ -34,7 +37,15 @@ def __init__( ) -> None: from poetry.repositories.repository_pool import RepositoryPool - super().__init__(file, local_config, package) + try: + super().__init__( # type: ignore[call-arg] + file, local_config, package, pyproject_type=PyProjectTOML + ) + except TypeError: + # TODO: backward compatibility, can be simplified if poetry-core with + # https://github.com/python-poetry/poetry-core/pull/483 is available + super().__init__(file, local_config, package) + self._pyproject = PyProjectTOML(file) self._locker = locker self._config = config @@ -42,6 +53,15 @@ def __init__( self._plugin_manager: PluginManager | None = None self._disable_cache = disable_cache + @property + def pyproject(self) -> PyProjectTOML: + pyproject = super().pyproject + return cast("PyProjectTOML", pyproject) + + @property + def file(self) -> TOMLFile: # type: ignore[override] + return self.pyproject.file + @property def locker(self) -> Locker: return self._locker diff --git a/src/poetry/pyproject/__init__.py b/src/poetry/pyproject/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/poetry/pyproject/toml.py b/src/poetry/pyproject/toml.py new file mode 100644 index 00000000000..3e9010b4c85 --- /dev/null +++ b/src/poetry/pyproject/toml.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from poetry.core.pyproject.toml import PyProjectTOML as BasePyProjectTOML +from tomlkit.api import table +from tomlkit.items import Table +from tomlkit.toml_document import TOMLDocument + +from poetry.toml import TOMLFile + + +if TYPE_CHECKING: + from pathlib import Path + + +class PyProjectTOML(BasePyProjectTOML): + """ + Enhanced version of poetry-core's PyProjectTOML + which is capable of writing pyproject.toml + + The poetry-core class uses tomli to read the file, + here we use tomlkit to preserve comments and formatting when writing. + """ + + def __init__(self, path: Path) -> None: + super().__init__(path) + self._toml_file = TOMLFile(path=path) + self._toml_document: TOMLDocument | None = None + + @property + def file(self) -> TOMLFile: # type: ignore[override] + return self._toml_file + + @property + def data(self) -> TOMLDocument: + if self._toml_document is None: + if not self.file.exists(): + self._toml_document = TOMLDocument() + else: + self._toml_document = self.file.read() + + return self._toml_document + + def save(self) -> None: + data = self.data + + if self._build_system is not None: + if "build-system" not in data: + data["build-system"] = table() + + build_system = data["build-system"] + assert isinstance(build_system, Table) + + build_system["requires"] = self._build_system.requires + build_system["build-backend"] = self._build_system.build_backend + + self.file.write(data=data) + + def reload(self) -> None: + self._toml_document = None + self._build_system = None diff --git a/src/poetry/toml/__init__.py b/src/poetry/toml/__init__.py new file mode 100644 index 00000000000..32aee9a2f04 --- /dev/null +++ b/src/poetry/toml/__init__.py @@ -0,0 +1,7 @@ +from __future__ import annotations + +from poetry.toml.exceptions import TOMLError +from poetry.toml.file import TOMLFile + + +__all__ = ["TOMLError", "TOMLFile"] diff --git a/src/poetry/toml/exceptions.py b/src/poetry/toml/exceptions.py new file mode 100644 index 00000000000..66fcec0063b --- /dev/null +++ b/src/poetry/toml/exceptions.py @@ -0,0 +1,8 @@ +from __future__ import annotations + +from poetry.core.exceptions import PoetryCoreException +from tomlkit.exceptions import TOMLKitError + + +class TOMLError(TOMLKitError, PoetryCoreException): + pass diff --git a/src/poetry/toml/file.py b/src/poetry/toml/file.py new file mode 100644 index 00000000000..79c2e018419 --- /dev/null +++ b/src/poetry/toml/file.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING +from typing import Any + +from tomlkit.toml_file import TOMLFile as BaseTOMLFile + + +if TYPE_CHECKING: + from pathlib import Path + + from tomlkit.toml_document import TOMLDocument + + +class TOMLFile(BaseTOMLFile): + def __init__(self, path: Path) -> None: + super().__init__(path) + self.__path = path + + @property + def path(self) -> Path: + return self.__path + + def exists(self) -> bool: + return self.__path.exists() + + def read(self) -> TOMLDocument: + from tomlkit.exceptions import TOMLKitError + + from poetry.toml import TOMLError + + try: + return super().read() + except (ValueError, TOMLKitError) as e: + raise TOMLError(f"Invalid TOML file {self.path.as_posix()}: {e}") + + def __getattr__(self, item: str) -> Any: + return getattr(self.__path, item) + + def __str__(self) -> str: + return self.__path.as_posix() diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 734a7d64836..d5a96fac4c2 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -33,10 +33,10 @@ from packaging.tags import sys_tags from poetry.core.constraints.version import Version from poetry.core.constraints.version import parse_constraint -from poetry.core.toml.file import TOMLFile from poetry.core.utils.helpers import temporary_directory from virtualenv.seed.wheels.embed import get_embed_wheel +from poetry.toml.file import TOMLFile from poetry.utils._compat import WINDOWS from poetry.utils._compat import decode from poetry.utils._compat import encode diff --git a/tests/console/commands/env/test_list.py b/tests/console/commands/env/test_list.py index 4a69dac0c3f..6c949b66d50 100644 --- a/tests/console/commands/env/test_list.py +++ b/tests/console/commands/env/test_list.py @@ -5,7 +5,7 @@ import pytest import tomlkit -from poetry.core.toml.file import TOMLFile +from poetry.toml.file import TOMLFile if TYPE_CHECKING: diff --git a/tests/console/commands/env/test_use.py b/tests/console/commands/env/test_use.py index 9cc5a9fc92a..72df646d97c 100644 --- a/tests/console/commands/env/test_use.py +++ b/tests/console/commands/env/test_use.py @@ -9,8 +9,8 @@ import tomlkit from poetry.core.constraints.version import Version -from poetry.core.toml.file import TOMLFile +from poetry.toml.file import TOMLFile from poetry.utils.env import MockEnv from tests.console.commands.env.helpers import build_venv from tests.console.commands.env.helpers import check_output_wrapper diff --git a/tests/console/commands/self/test_add_plugins.py b/tests/console/commands/self/test_add_plugins.py index e8447a32b13..37de59731f0 100644 --- a/tests/console/commands/self/test_add_plugins.py +++ b/tests/console/commands/self/test_add_plugins.py @@ -190,8 +190,10 @@ def test_add_existing_plugin_warns_about_no_operation( repo: TestRepository, installed: TestRepository, ): - SelfCommand.get_default_system_pyproject_file().write_text( - f"""\ + pyproject = SelfCommand.get_default_system_pyproject_file() + with open(pyproject, "w", encoding="utf-8", newline="") as f: + f.write( + f"""\ [tool.poetry] name = "poetry-instance" version = "1.2.0" @@ -203,9 +205,8 @@ def test_add_existing_plugin_warns_about_no_operation( [tool.poetry.group.{SelfCommand.ADDITIONAL_PACKAGE_GROUP}.dependencies] poetry-plugin = "^1.2.3" -""", - encoding="utf-8", - ) +""" + ) installed.add_package(Package("poetry-plugin", "1.2.3")) @@ -230,8 +231,10 @@ def test_add_existing_plugin_updates_if_requested( repo: TestRepository, installed: TestRepository, ): - SelfCommand.get_default_system_pyproject_file().write_text( - f"""\ + pyproject = SelfCommand.get_default_system_pyproject_file() + with open(pyproject, "w", encoding="utf-8", newline="") as f: + f.write( + f"""\ [tool.poetry] name = "poetry-instance" version = "1.2.0" @@ -243,9 +246,8 @@ def test_add_existing_plugin_updates_if_requested( [tool.poetry.group.{SelfCommand.ADDITIONAL_PACKAGE_GROUP}.dependencies] poetry-plugin = "^1.2.3" -""", - encoding="utf-8", - ) +""" + ) installed.add_package(Package("poetry-plugin", "1.2.3")) diff --git a/tests/console/commands/self/test_remove_plugins.py b/tests/console/commands/self/test_remove_plugins.py index 2b988443469..660b3584012 100644 --- a/tests/console/commands/self/test_remove_plugins.py +++ b/tests/console/commands/self/test_remove_plugins.py @@ -37,7 +37,8 @@ def install_plugin(installed: Repository) -> None: ) content = Factory.create_pyproject_from_package(package) system_pyproject_file = SelfCommand.get_default_system_pyproject_file() - system_pyproject_file.write_text(content.as_string(), encoding="utf-8") + with open(system_pyproject_file, "w", encoding="utf-8", newline="") as f: + f.write(content.as_string()) lock_content = { "package": [ diff --git a/tests/console/commands/test_check.py b/tests/console/commands/test_check.py index dbf665b6227..f23276c5c6d 100644 --- a/tests/console/commands/test_check.py +++ b/tests/console/commands/test_check.py @@ -29,7 +29,7 @@ def test_check_valid(tester: CommandTester): def test_check_invalid(mocker: MockerFixture, tester: CommandTester): - from poetry.core.toml import TOMLFile + from poetry.toml import TOMLFile mocker.patch( "poetry.poetry.Poetry.file", diff --git a/tests/helpers.py b/tests/helpers.py index feef37728c0..0bbaf414dde 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -12,7 +12,6 @@ from poetry.core.packages.package import Package from poetry.core.packages.utils.link import Link -from poetry.core.toml.file import TOMLFile from poetry.core.vcs.git import ParsedUrl from poetry.config.config import Config @@ -187,8 +186,8 @@ def reset_poetry(self) -> None: class TestLocker(Locker): - def __init__(self, lock: str | Path, local_config: dict) -> None: - self._lock = TOMLFile(lock) + def __init__(self, lock: Path, local_config: dict) -> None: + self._lock = lock self._local_config = local_config self._lock_data = None self._content_hash = self._get_content_hash() diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 76e11ecd611..98aac9c8430 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -18,7 +18,6 @@ from poetry.core.packages.dependency_group import DependencyGroup from poetry.core.packages.package import Package from poetry.core.packages.project_package import ProjectPackage -from poetry.core.toml.file import TOMLFile from poetry.factory import Factory from poetry.installation import Installer as BaseInstaller @@ -28,6 +27,7 @@ from poetry.repositories import Repository from poetry.repositories import RepositoryPool from poetry.repositories.installed_repository import InstalledRepository +from poetry.toml.file import TOMLFile from poetry.utils.env import MockEnv from poetry.utils.env import NullEnv from tests.helpers import MOCK_DEFAULT_GIT_REVISION @@ -101,8 +101,8 @@ def load( class Locker(BaseLocker): - def __init__(self, lock_path: str | Path) -> None: - self._lock = TOMLFile(Path(lock_path).joinpath("poetry.lock")) + def __init__(self, lock_path: Path) -> None: + self._lock = lock_path / "poetry.lock" self._written_data = None self._locked = False self._content_hash = self._get_content_hash() @@ -111,8 +111,8 @@ def __init__(self, lock_path: str | Path) -> None: def written_data(self) -> dict | None: return self._written_data - def set_lock_path(self, lock: str | Path) -> Locker: - self._lock = TOMLFile(Path(lock).joinpath("poetry.lock")) + def set_lock_path(self, lock: Path) -> Locker: + self._lock = lock / "poetry.lock" return self diff --git a/tests/installation/test_installer_old.py b/tests/installation/test_installer_old.py index e76fabb21f3..02ea33bc89b 100644 --- a/tests/installation/test_installer_old.py +++ b/tests/installation/test_installer_old.py @@ -10,7 +10,6 @@ from cleo.io.null_io import NullIO from poetry.core.packages.project_package import ProjectPackage -from poetry.core.toml.file import TOMLFile from poetry.factory import Factory from poetry.installation import Installer as BaseInstaller @@ -19,6 +18,7 @@ from poetry.repositories import Repository from poetry.repositories import RepositoryPool from poetry.repositories.installed_repository import InstalledRepository +from poetry.toml.file import TOMLFile from poetry.utils.env import MockEnv from poetry.utils.env import NullEnv from tests.helpers import get_dependency @@ -58,8 +58,8 @@ def load( class Locker(BaseLocker): - def __init__(self, lock_path: str | Path) -> None: - self._lock = TOMLFile(Path(lock_path).joinpath("poetry.lock")) + def __init__(self, lock_path: Path) -> None: + self._lock = lock_path / "poetry.lock" self._written_data = None self._locked = False self._content_hash = self._get_content_hash() @@ -68,8 +68,8 @@ def __init__(self, lock_path: str | Path) -> None: def written_data(self) -> dict | None: return self._written_data - def set_lock_path(self, lock: str | Path) -> Locker: - self._lock = TOMLFile(Path(lock).joinpath("poetry.lock")) + def set_lock_path(self, lock: Path) -> Locker: + self._lock = lock / "poetry.lock" return self diff --git a/tests/integration/test_utils_vcs_git.py b/tests/integration/test_utils_vcs_git.py index ec0f558f3fe..aaa864283ce 100644 --- a/tests/integration/test_utils_vcs_git.py +++ b/tests/integration/test_utils_vcs_git.py @@ -14,9 +14,9 @@ from dulwich.client import get_transport_and_path from dulwich.config import ConfigFile from dulwich.repo import Repo -from poetry.core.pyproject.toml import PyProjectTOML from poetry.console.exceptions import PoetryConsoleError +from poetry.pyproject.toml import PyProjectTOML from poetry.utils.authenticator import Authenticator from poetry.vcs.git import Git from poetry.vcs.git.backend import GitRefSpec diff --git a/tests/json/test_schema_sources.py b/tests/json/test_schema_sources.py index 4f20a0b3884..22769922e1c 100644 --- a/tests/json/test_schema_sources.py +++ b/tests/json/test_schema_sources.py @@ -2,9 +2,8 @@ from pathlib import Path -from poetry.core.toml import TOMLFile - from poetry.factory import Factory +from poetry.toml import TOMLFile FIXTURE_DIR = Path(__file__).parent / "fixtures" / "source" diff --git a/tests/pyproject/__init__.py b/tests/pyproject/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/pyproject/conftest.py b/tests/pyproject/conftest.py new file mode 100644 index 00000000000..82ff2198389 --- /dev/null +++ b/tests/pyproject/conftest.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pytest + + +if TYPE_CHECKING: + from pathlib import Path + + +@pytest.fixture +def pyproject_toml(tmp_path: Path) -> Path: + path = tmp_path / "pyproject.toml" + with path.open(mode="w"): + pass + return path + + +@pytest.fixture +def build_system_section(pyproject_toml: Path) -> str: + content = """ +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" +""" + with pyproject_toml.open(mode="a") as f: + f.write(content) + return content + + +@pytest.fixture +def poetry_section(pyproject_toml: Path) -> str: + content = """ +[tool.poetry] +name = "poetry" + +[tool.poetry.dependencies] +python = "^3.5" +""" + with pyproject_toml.open(mode="a") as f: + f.write(content) + return content diff --git a/tests/pyproject/test_pyproject_toml.py b/tests/pyproject/test_pyproject_toml.py new file mode 100644 index 00000000000..4f85d91c18f --- /dev/null +++ b/tests/pyproject/test_pyproject_toml.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +import uuid + +from typing import TYPE_CHECKING + +from poetry.pyproject.toml import PyProjectTOML + + +if TYPE_CHECKING: + from pathlib import Path + + +def test_pyproject_toml_reload(pyproject_toml: Path, poetry_section: str) -> None: + pyproject = PyProjectTOML(pyproject_toml) + name_original = pyproject.poetry_config["name"] + name_new = str(uuid.uuid4()) + + pyproject.poetry_config["name"] = name_new + assert isinstance(pyproject.poetry_config["name"], str) + assert pyproject.poetry_config["name"] == name_new + + pyproject.reload() + assert pyproject.poetry_config["name"] == name_original + + +def test_pyproject_toml_save( + pyproject_toml: Path, poetry_section: str, build_system_section: str +) -> None: + pyproject = PyProjectTOML(pyproject_toml) + + name = str(uuid.uuid4()) + build_backend = str(uuid.uuid4()) + build_requires = str(uuid.uuid4()) + + pyproject.poetry_config["name"] = name + pyproject.build_system.build_backend = build_backend + pyproject.build_system.requires.append(build_requires) + + pyproject.save() + + pyproject = PyProjectTOML(pyproject_toml) + + assert isinstance(pyproject.poetry_config["name"], str) + assert pyproject.poetry_config["name"] == name + assert pyproject.build_system.build_backend == build_backend + assert build_requires in pyproject.build_system.requires diff --git a/tests/pyproject/test_pyproject_toml_file.py b/tests/pyproject/test_pyproject_toml_file.py new file mode 100644 index 00000000000..1c7c02a1439 --- /dev/null +++ b/tests/pyproject/test_pyproject_toml_file.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pytest + +from poetry.core.exceptions import PoetryCoreException + +from poetry.toml import TOMLFile + + +if TYPE_CHECKING: + from pathlib import Path + + +def test_pyproject_toml_file_invalid(pyproject_toml: Path) -> None: + with pyproject_toml.open(mode="a") as f: + f.write("<<<<<<<<<<<") + + with pytest.raises(PoetryCoreException) as excval: + _ = TOMLFile(pyproject_toml).read() + + assert f"Invalid TOML file {pyproject_toml.as_posix()}" in str(excval.value) + + +def test_pyproject_toml_file_getattr(tmp_path: Path, pyproject_toml: Path) -> None: + file = TOMLFile(pyproject_toml) + assert file.parent == tmp_path diff --git a/tests/test_factory.py b/tests/test_factory.py index 7eafc85d210..5b56ddc6200 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -8,12 +8,12 @@ from deepdiff import DeepDiff from packaging.utils import canonicalize_name from poetry.core.constraints.version import parse_constraint -from poetry.core.toml.file import TOMLFile from poetry.factory import Factory from poetry.plugins.plugin import Plugin from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.pypi_repository import PyPiRepository +from poetry.toml.file import TOMLFile from tests.helpers import mock_metadata_entry_points diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index b720974212a..b41d22e6b53 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -13,10 +13,10 @@ import tomlkit from poetry.core.constraints.version import Version -from poetry.core.toml.file import TOMLFile from poetry.factory import Factory from poetry.repositories.installed_repository import InstalledRepository +from poetry.toml.file import TOMLFile from poetry.utils._compat import WINDOWS from poetry.utils.env import GET_BASE_PREFIX from poetry.utils.env import GET_PYTHON_VERSION_ONELINER From d07dddbc0e9c4bb4576023e1cfb71152c3672bbb Mon Sep 17 00:00:00 2001 From: David Hotham Date: Mon, 3 Apr 2023 13:12:31 +0100 Subject: [PATCH 125/151] installer tests should verify result of install (#7759) --- .../fixtures/with-pypi-repository.test | 134 ++++-------- tests/installation/test_installer.py | 205 +++++++++++------- tests/installation/test_installer_old.py | 175 +++++++++------ .../fixtures/pypi.org/json/setuptools.json | 28 ++- .../pypi.org/json/setuptools/67.6.1.json | 140 ++++++++++++ .../fixtures/pypi.org/json/wheel.json | 34 +++ .../fixtures/pypi.org/json/wheel/0.40.0.json | 98 +++++++++ 7 files changed, 585 insertions(+), 229 deletions(-) create mode 100644 tests/repositories/fixtures/pypi.org/json/setuptools/67.6.1.json create mode 100644 tests/repositories/fixtures/pypi.org/json/wheel.json create mode 100644 tests/repositories/fixtures/pypi.org/json/wheel/0.40.0.json diff --git a/tests/installation/fixtures/with-pypi-repository.test b/tests/installation/fixtures/with-pypi-repository.test index 03444764ee9..de60c4ffce2 100644 --- a/tests/installation/fixtures/with-pypi-repository.test +++ b/tests/installation/fixtures/with-pypi-repository.test @@ -1,3 +1,5 @@ +# This file is automatically @generated by Poetry 1.5.0.dev0 and should not be changed by hand. + [[package]] name = "attrs" version = "17.4.0" @@ -5,14 +7,10 @@ description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = "*" - -[[package.files]] -file = "attrs-17.4.0-py2.py3-none-any.whl" -hash = "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450" - -[[package.files]] -file = "attrs-17.4.0.tar.gz" -hash = "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9" +files = [ + {file = "attrs-17.4.0-py2.py3-none-any.whl", hash = "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450"}, + {file = "attrs-17.4.0.tar.gz", hash = "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9"}, +] [package.extras] dev = ["coverage", "hypothesis", "pympler", "pytest", "six", "sphinx", "zope.interface", "zope.interface"] @@ -26,30 +24,10 @@ description = "Cross-platform colored terminal text." category = "dev" optional = false python-versions = "*" - -[[package.files]] -file = "colorama-0.3.9-py2.py3-none-any.whl" -hash = "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda" - -[[package.files]] -file = "colorama-0.3.9.tar.gz" -hash = "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1" - -[[package]] -name = "funcsigs" -version = "1.0.2" -description = "Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+" -category = "dev" -optional = false -python-versions = "*" - -[[package.files]] -file = "funcsigs-1.0.2-py2.py3-none-any.whl" -hash = "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca" - -[[package.files]] -file = "funcsigs-1.0.2.tar.gz" -hash = "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" +files = [ + {file = "colorama-0.3.9-py2.py3-none-any.whl", hash = "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda"}, + {file = "colorama-0.3.9.tar.gz", hash = "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"}, +] [[package]] name = "more-itertools" @@ -58,18 +36,11 @@ description = "More routines for operating on iterables, beyond itertools" category = "dev" optional = false python-versions = "*" - -[[package.files]] -file = "more-itertools-4.1.0.tar.gz" -hash = "sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44" - -[[package.files]] -file = "more_itertools-4.1.0-py2-none-any.whl" -hash = "sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e" - -[[package.files]] -file = "more_itertools-4.1.0-py3-none-any.whl" -hash = "sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea" +files = [ + {file = "more-itertools-4.1.0.tar.gz", hash = "sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44"}, + {file = "more_itertools-4.1.0-py2-none-any.whl", hash = "sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e"}, + {file = "more_itertools-4.1.0-py3-none-any.whl", hash = "sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea"}, +] [package.dependencies] six = ">=1.0.0,<2.0.0" @@ -81,10 +52,9 @@ description = "plugin and hook calling mechanisms for python" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package.files]] -file = "pluggy-0.6.0.tar.gz" -hash = "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff" +files = [ + {file = "pluggy-0.6.0.tar.gz", hash = "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff"}, +] [[package]] name = "py" @@ -93,14 +63,10 @@ description = "library with cross-python path, ini-parsing, io, code, log facili category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package.files]] -file = "py-1.5.3-py2.py3-none-any.whl" -hash = "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a" - -[[package.files]] -file = "py-1.5.3.tar.gz" -hash = "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881" +files = [ + {file = "py-1.5.3-py2.py3-none-any.whl", hash = "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a"}, + {file = "py-1.5.3.tar.gz", hash = "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881"}, +] [[package]] name = "pytest" @@ -109,44 +75,36 @@ description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package.files]] -file = "pytest-3.5.0-py2.py3-none-any.whl" -hash = "sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c" - -[[package.files]] -file = "pytest-3.5.0.tar.gz" -hash = "sha256:fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1" +files = [ + {file = "pytest-3.5.0-py2.py3-none-any.whl", hash = "sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c"}, + {file = "pytest-3.5.0.tar.gz", hash = "sha256:fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1"}, +] [package.dependencies] -py = ">=1.5.0" -six = ">=1.10.0" attrs = ">=17.4.0" -setuptools = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} more-itertools = ">=4.0.0" pluggy = ">=0.5,<0.7" -funcsigs = {"version" = "*", "markers" = "python_version < \"3.0\""} -colorama = {"version" = "*", "markers" = "sys_platform == \"win32\""} +py = ">=1.5.0" +setuptools = "*" +six = ">=1.10.0" [[package]] name = "setuptools" -version = "39.2.0" +version = "67.6.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "dev" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" - -[[package.files]] -file = "setuptools-39.2.0-py2.py3-none-any.whl" -hash = "sha256:8fca9275c89964f13da985c3656cb00ba029d7f3916b37990927ffdf264e7926" - -[[package.files]] -file = "setuptools-39.2.0.zip" -hash = "sha256:f7cddbb5f5c640311eb00eab6e849f7701fa70bf6a183fc8a2c33dd1d1672fb2" +python-versions = ">=3.7" +files = [ + {file = "setuptools-67.6.1-py3-none-any.whl", hash = "sha256:e728ca814a823bf7bf60162daf9db95b93d532948c4c0bea762ce62f60189078"}, + {file = "setuptools-67.6.1.tar.gz", hash = "sha256:257de92a9d50a60b8e22abfcbb771571fde0dbf3ec234463212027a4eeecbe9a"}, +] [package.extras] -certs = ["certifi (==2016.9.26)"] -ssl = ["wincertstore (==0.2)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -155,16 +113,12 @@ description = "Python 2 and 3 compatibility utilities" category = "dev" optional = false python-versions = "*" - -[[package.files]] -file = "six-1.11.0-py2.py3-none-any.whl" -hash = "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" - -[[package.files]] -file = "six-1.11.0.tar.gz" -hash = "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9" +files = [ + {file = "six-1.11.0-py2.py3-none-any.whl", hash = "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"}, + {file = "six-1.11.0.tar.gz", hash = "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"}, +] [metadata] -python-versions = "*" lock-version = "2.0" +python-versions = ">=3.7" content-hash = "123456789" diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 98aac9c8430..841a2ccde64 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -207,9 +207,10 @@ def fixture(name: str) -> dict: def test_run_no_dependencies(installer: Installer, locker: Locker): - installer.run() - expected = fixture("no-dependencies") + result = installer.run() + assert result == 0 + expected = fixture("no-dependencies") assert locker.written_data == expected @@ -224,9 +225,10 @@ def test_run_with_dependencies( package.add_dependency(Factory.create_dependency("A", "~1.0")) package.add_dependency(Factory.create_dependency("B", "^1.0")) - installer.run() - expected = fixture("with-dependencies") + result = installer.run() + assert result == 0 + expected = fixture("with-dependencies") assert locker.written_data == expected @@ -292,9 +294,10 @@ def test_run_update_after_removing_dependencies( package.add_dependency(Factory.create_dependency("B", "~1.1")) installer.update(True) - installer.run() - expected = fixture("with-dependencies") + result = installer.run() + assert result == 0 + expected = fixture("with-dependencies") assert locker.written_data == expected assert installer.executor.installations_count == 0 @@ -413,7 +416,8 @@ def test_run_install_with_dependency_groups( installer.only_groups(groups) installer.requires_synchronization(True) - installer.run() + result = installer.run() + assert result == 0 assert installer.executor.installations_count == installs assert installer.executor.updates_count == updates @@ -483,7 +487,8 @@ def test_run_install_does_not_remove_locked_packages_if_installed_but_not_requir } ) - installer.run() + result = installer.run() + assert result == 0 assert installer.executor.installations_count == 0 assert installer.executor.updates_count == 0 @@ -625,7 +630,8 @@ def test_run_install_removes_no_longer_locked_packages_if_installed( ) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert installer.executor.installations_count == 0 assert installer.executor.updates_count == 0 @@ -703,7 +709,8 @@ def test_run_install_with_synchronization( ) installer.requires_synchronization(True) - installer.run() + result = installer.run() + assert result == 0 assert installer.executor.installations_count == 0 assert installer.executor.updates_count == 0 @@ -756,9 +763,10 @@ def test_run_whitelist_add( installer.update(True) installer.whitelist(["B"]) - installer.run() - expected = fixture("with-dependencies") + result = installer.run() + assert result == 0 + expected = fixture("with-dependencies") assert locker.written_data == expected @@ -811,9 +819,10 @@ def test_run_whitelist_remove( installer.update(True) installer.whitelist(["B"]) - installer.run() - expected = fixture("remove") + result = installer.run() + assert result == 0 + expected = fixture("remove") assert locker.written_data == expected assert installer.executor.installations_count == 1 assert installer.executor.updates_count == 0 @@ -838,9 +847,10 @@ def test_add_with_sub_dependencies( package_a.add_dependency(Factory.create_dependency("D", "^1.0")) package_b.add_dependency(Factory.create_dependency("C", "~1.2")) - installer.run() - expected = fixture("with-sub-dependencies") + result = installer.run() + assert result == 0 + expected = fixture("with-sub-dependencies") assert locker.written_data == expected @@ -865,9 +875,10 @@ def test_run_with_python_versions( package.add_dependency(Factory.create_dependency("B", "^1.0")) package.add_dependency(Factory.create_dependency("C", "^1.0")) - installer.run() - expected = fixture("with-python-versions") + result = installer.run() + assert result == 0 + expected = fixture("with-python-versions") assert locker.written_data == expected @@ -900,9 +911,10 @@ def test_run_with_optional_and_python_restricted_dependencies( Factory.create_dependency("C", {"version": "^1.0", "python": "~2.7 || ^3.4"}) ) - installer.run() - expected = fixture("with-optional-dependencies") + result = installer.run() + assert result == 0 + expected = fixture("with-optional-dependencies") assert locker.written_data == expected # We should only have 2 installs: @@ -946,9 +958,10 @@ def test_run_with_optional_and_platform_restricted_dependencies( Factory.create_dependency("C", {"version": "^1.0", "platform": "darwin"}) ) - installer.run() - expected = fixture("with-platform-dependencies") + result = installer.run() + assert result == 0 + expected = fixture("with-platform-dependencies") assert locker.written_data == expected # We should only have 2 installs: @@ -980,9 +993,10 @@ def test_run_with_dependencies_extras( Factory.create_dependency("B", {"version": "^1.0", "extras": ["foo"]}) ) - installer.run() - expected = fixture("with-dependencies-extras") + result = installer.run() + assert result == 0 + expected = fixture("with-dependencies-extras") assert locker.written_data == expected @@ -1011,9 +1025,10 @@ def test_run_with_dependencies_nested_extras( package.add_dependency(dependency_a) - installer.run() - expected = fixture("with-dependencies-nested-extras") + result = installer.run() + assert result == 0 + expected = fixture("with-dependencies-nested-extras") assert locker.written_data == expected @@ -1038,9 +1053,10 @@ def test_run_does_not_install_extras_if_not_requested( Factory.create_dependency("D", {"version": "^1.0", "optional": True}) ) - installer.run() - expected = fixture("extras") + result = installer.run() + assert result == 0 + expected = fixture("extras") # Extras are pinned in lock assert locker.written_data == expected @@ -1070,10 +1086,11 @@ def test_run_installs_extras_if_requested( ) installer.extras(["foo"]) - installer.run() - expected = fixture("extras") + result = installer.run() + assert result == 0 # Extras are pinned in lock + expected = fixture("extras") assert locker.written_data == expected # But should not be installed @@ -1103,7 +1120,9 @@ def test_run_installs_extras_with_deps_if_requested( package_c.add_dependency(Factory.create_dependency("D", "^1.0")) installer.extras(["foo"]) - installer.run() + result = installer.run() + assert result == 0 + expected = fixture("extras-with-dependencies") # Extras are pinned in lock @@ -1138,9 +1157,9 @@ def test_run_installs_extras_with_deps_if_requested_locked( package_c.add_dependency(Factory.create_dependency("D", "^1.0")) installer.extras(["foo"]) - installer.run() + result = installer.run() + assert result == 0 - # But should not be installed assert installer.executor.installations_count == 4 # A, B, C, D @@ -1158,8 +1177,10 @@ def test_installer_with_pypi_repository( NullIO(), env, package, locker, pool, config, installed=installed ) + package.python_versions = ">=3.7" package.add_dependency(Factory.create_dependency("pytest", "^3.5", groups=["dev"])) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-pypi-repository") @@ -1185,7 +1206,8 @@ def test_run_installs_with_local_file( repo.add_package(get_package("pendulum", "1.4.4")) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-file-dependency") @@ -1212,7 +1234,8 @@ def test_run_installs_wheel_with_no_requires_dist( ) ) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-wheel-dependency-no-requires-dist") @@ -1243,7 +1266,8 @@ def test_run_installs_with_local_poetry_directory_and_extras( repo.add_package(get_package("pendulum", "1.4.4")) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-directory-dependency-poetry") assert locker.written_data == expected @@ -1274,7 +1298,8 @@ def test_run_installs_with_local_poetry_directory_transitive( repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cachy", "0.2.0")) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-directory-dependency-poetry-transitive") @@ -1308,7 +1333,8 @@ def test_run_installs_with_local_poetry_file_transitive( repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cachy", "0.2.0")) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-file-dependency-transitive") @@ -1340,7 +1366,8 @@ def test_run_installs_with_local_setuptools_directory( repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cachy", "0.2.0")) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-directory-dependency-setuptools") @@ -1386,9 +1413,10 @@ def test_run_with_prereleases( installer.update(True) installer.whitelist({"B": "^1.1"}) - installer.run() - expected = fixture("with-prereleases") + result = installer.run() + assert result == 0 + expected = fixture("with-prereleases") assert locker.written_data == expected @@ -1472,9 +1500,10 @@ def test_run_update_all_with_lock( installer.update(True) - installer.run() - expected = fixture("update-with-lock") + result = installer.run() + assert result == 0 + expected = fixture("update-with-lock") assert locker.written_data == expected @@ -1545,9 +1574,10 @@ def test_run_update_with_locked_extras( installer.update(True) installer.whitelist("D") - installer.run() - expected = fixture("update-with-locked-extras") + result = installer.run() + assert result == 0 + expected = fixture("update-with-locked-extras") assert locker.written_data == expected @@ -1578,7 +1608,8 @@ def test_run_install_duplicate_dependencies_different_constraints( repo.add_package(package_c12) repo.add_package(package_c15) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-duplicate-dependencies") @@ -1690,7 +1721,8 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock( repo.add_package(package_c15) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-duplicate-dependencies") @@ -1756,7 +1788,8 @@ def test_run_update_uninstalls_after_removal_transient_dependency( installed.add_package(get_package("B", "1.0")) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert installer.executor.installations_count == 0 assert installer.executor.updates_count == 0 @@ -1861,7 +1894,8 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda installer.update(True) installer.whitelist(["A"]) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-duplicate-dependencies-update") @@ -1896,7 +1930,8 @@ def test_installer_test_solver_finds_compatible_package_for_dependency_python_no repo.add_package(package_a100) repo.add_package(package_a101) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-conditional-dependency") assert locker.written_data == expected @@ -1935,7 +1970,8 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de repo.add_package(package_d) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert installer.executor.installations_count == 3 assert installer.executor.updates_count == 0 @@ -1961,7 +1997,8 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de ) installer.update(True) installer.whitelist(["D"]) - installer.run() + result = installer.run() + assert result == 0 assert installer.executor.installations_count == 1 assert installer.executor.updates_count == 0 @@ -1996,7 +2033,8 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de package.add_dependency(Factory.create_dependency("poetry", {"version": "^0.12.0"})) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert installer.executor.installations_count == 3 assert installer.executor.updates_count == 0 @@ -2022,7 +2060,8 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de ) installer.update(True) installer.whitelist(["pytest"]) - installer.run() + result = installer.run() + assert result == 0 assert installer.executor.installations_count == 7 assert installer.executor.updates_count == 0 @@ -2057,7 +2096,8 @@ def test_installer_required_extras_should_be_installed( ) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert installer.executor.installations_count == 2 assert installer.executor.updates_count == 0 @@ -2077,7 +2117,8 @@ def test_installer_required_extras_should_be_installed( executor=Executor(env, pool, config, NullIO()), ) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert installer.executor.installations_count == 2 assert installer.executor.updates_count == 0 @@ -2144,21 +2185,24 @@ def test_update_multiple_times_with_split_dependencies_is_idempotent( expected = fixture("with-multiple-updates") installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert locker.written_data == expected locker.mock_lock_data(locker.written_data) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert locker.written_data == expected locker.mock_lock_data(locker.written_data) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert locker.written_data == expected @@ -2190,7 +2234,8 @@ def test_installer_can_install_dependencies_from_forced_source( executor=Executor(env, pool, config, NullIO()), ) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert installer.executor.installations_count == 1 assert installer.executor.updates_count == 0 @@ -2205,7 +2250,8 @@ def test_run_installs_with_url_file( repo.add_package(get_package("pendulum", "1.4.4")) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-url-dependency") @@ -2254,7 +2300,8 @@ def test_run_installs_with_same_version_url_files( NullIO(), ), ) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-same-version-url-dependencies") assert locker.written_data == expected @@ -2278,7 +2325,8 @@ def test_installer_uses_prereleases_if_they_are_compatible( repo.add_package(package_b) - installer.run() + result = installer.run() + assert result == 0 del installer.installer.installs[:] locker.locked(True) @@ -2288,7 +2336,8 @@ def test_installer_uses_prereleases_if_they_are_compatible( installer.whitelist(["b"]) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert installer.executor.installations_count == 2 @@ -2318,7 +2367,8 @@ def test_installer_can_handle_old_lock_files( installed=installed, executor=Executor(MockEnv(), pool, config, NullIO()), ) - installer.run() + result = installer.run() + assert result == 0 assert installer.executor.installations_count == 6 @@ -2337,7 +2387,8 @@ def test_installer_can_handle_old_lock_files( NullIO(), ), ) - installer.run() + result = installer.run() + assert result == 0 # funcsigs will be added assert installer.executor.installations_count == 7 @@ -2357,7 +2408,8 @@ def test_installer_can_handle_old_lock_files( NullIO(), ), ) - installer.run() + result = installer.run() + assert result == 0 # colorama will be added assert installer.executor.installations_count == 8 @@ -2382,9 +2434,10 @@ def test_run_with_dependencies_quiet( package.add_dependency(Factory.create_dependency("A", "~1.0")) package.add_dependency(Factory.create_dependency("B", "^1.0")) - installer.run() - expected = fixture("with-dependencies") + result = installer.run() + assert result == 0 + expected = fixture("with-dependencies") assert locker.written_data == expected installer._io.output._buffer.seek(0) @@ -2445,7 +2498,8 @@ def test_installer_should_use_the_locked_version_of_git_dependencies( repo.add_package(get_package("pendulum", "1.4.4")) - installer.run() + result = installer.run() + assert result == 0 assert installer.executor.installations[-1] == Package( "demo", @@ -2486,7 +2540,8 @@ def test_installer_should_use_the_locked_version_of_git_dependencies_with_extras repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cleo", "1.0.0")) - installer.run() + result = installer.run() + assert result == 0 assert len(installer.executor.installations) == 3 assert installer.executor.installations[-1] == Package( @@ -2524,7 +2579,8 @@ def test_installer_should_use_the_locked_version_of_git_dependencies_without_ref repo.add_package(get_package("pendulum", "1.4.4")) - installer.run() + result = installer.run() + assert result == 0 assert len(installer.executor.installations) == 2 assert installer.executor.installations[-1] == Package( @@ -2620,7 +2676,8 @@ def test_installer_distinguishes_locked_packages_by_source( NullIO(), ), ) - installer.run() + result = installer.run() + assert result == 0 # Results of installation are consistent with the platform requirements. version = "1.11.0" if env_platform == "darwin" else "1.11.0+cpu" diff --git a/tests/installation/test_installer_old.py b/tests/installation/test_installer_old.py index 02ea33bc89b..45d3a5c767d 100644 --- a/tests/installation/test_installer_old.py +++ b/tests/installation/test_installer_old.py @@ -154,9 +154,10 @@ def fixture(name: str) -> str: def test_run_no_dependencies(installer: Installer, locker: Locker): - installer.run() - expected = fixture("no-dependencies") + result = installer.run() + assert result == 0 + expected = fixture("no-dependencies") assert locker.written_data == expected @@ -171,9 +172,10 @@ def test_run_with_dependencies( package.add_dependency(Factory.create_dependency("A", "~1.0")) package.add_dependency(Factory.create_dependency("B", "^1.0")) - installer.run() - expected = fixture("with-dependencies") + result = installer.run() + assert result == 0 + expected = fixture("with-dependencies") assert locker.written_data == expected @@ -239,9 +241,10 @@ def test_run_update_after_removing_dependencies( package.add_dependency(Factory.create_dependency("B", "~1.1")) installer.update(True) - installer.run() - expected = fixture("with-dependencies") + result = installer.run() + assert result == 0 + expected = fixture("with-dependencies") assert locker.written_data == expected installs = installer.installer.installs @@ -317,7 +320,8 @@ def test_run_install_no_group( package.add_dependency(Factory.create_dependency("C", "~1.2", groups=["dev"])) installer.only_groups([]) - installer.run() + result = installer.run() + assert result == 0 installs = installer.installer.installs assert len(installs) == 0 @@ -400,7 +404,8 @@ def test_run_install_with_synchronization( ) installer.requires_synchronization(True) - installer.run() + result = installer.run() + assert result == 0 installs = installer.installer.installs assert len(installs) == 0 @@ -456,9 +461,10 @@ def test_run_whitelist_add( installer.update(True) installer.whitelist(["B"]) - installer.run() - expected = fixture("with-dependencies") + result = installer.run() + assert result == 0 + expected = fixture("with-dependencies") assert locker.written_data == expected @@ -511,9 +517,10 @@ def test_run_whitelist_remove( installer.update(True) installer.whitelist(["B"]) - installer.run() - expected = fixture("remove") + result = installer.run() + assert result == 0 + expected = fixture("remove") assert locker.written_data == expected assert len(installer.installer.installs) == 1 assert len(installer.installer.updates) == 0 @@ -538,9 +545,10 @@ def test_add_with_sub_dependencies( package_a.add_dependency(Factory.create_dependency("D", "^1.0")) package_b.add_dependency(Factory.create_dependency("C", "~1.2")) - installer.run() - expected = fixture("with-sub-dependencies") + result = installer.run() + assert result == 0 + expected = fixture("with-sub-dependencies") assert locker.written_data == expected @@ -565,9 +573,10 @@ def test_run_with_python_versions( package.add_dependency(Factory.create_dependency("B", "^1.0")) package.add_dependency(Factory.create_dependency("C", "^1.0")) - installer.run() - expected = fixture("with-python-versions") + result = installer.run() + assert result == 0 + expected = fixture("with-python-versions") assert locker.written_data == expected @@ -600,9 +609,10 @@ def test_run_with_optional_and_python_restricted_dependencies( Factory.create_dependency("C", {"version": "^1.0", "python": "~2.7 || ^3.4"}) ) - installer.run() - expected = fixture("with-optional-dependencies") + result = installer.run() + assert result == 0 + expected = fixture("with-optional-dependencies") assert locker.written_data == expected installer = installer.installer @@ -647,9 +657,10 @@ def test_run_with_optional_and_platform_restricted_dependencies( Factory.create_dependency("C", {"version": "^1.0", "platform": "darwin"}) ) - installer.run() - expected = fixture("with-platform-dependencies") + result = installer.run() + assert result == 0 + expected = fixture("with-platform-dependencies") assert locker.written_data == expected installer = installer.installer @@ -682,9 +693,10 @@ def test_run_with_dependencies_extras( Factory.create_dependency("B", {"version": "^1.0", "extras": ["foo"]}) ) - installer.run() - expected = fixture("with-dependencies-extras") + result = installer.run() + assert result == 0 + expected = fixture("with-dependencies-extras") assert locker.written_data == expected @@ -709,10 +721,11 @@ def test_run_does_not_install_extras_if_not_requested( Factory.create_dependency("D", {"version": "^1.0", "optional": True}) ) - installer.run() - expected = fixture("extras") + result = installer.run() + assert result == 0 # Extras are pinned in lock + expected = fixture("extras") assert locker.written_data == expected # But should not be installed @@ -742,10 +755,11 @@ def test_run_installs_extras_if_requested( ) installer.extras(["foo"]) - installer.run() - expected = fixture("extras") + result = installer.run() + assert result == 0 # Extras are pinned in lock + expected = fixture("extras") assert locker.written_data == expected # But should not be installed @@ -776,10 +790,11 @@ def test_run_installs_extras_with_deps_if_requested( package_c.add_dependency(Factory.create_dependency("D", "^1.0")) installer.extras(["foo"]) - installer.run() - expected = fixture("extras-with-dependencies") + result = installer.run() + assert result == 0 # Extras are pinned in lock + expected = fixture("extras-with-dependencies") assert locker.written_data == expected # But should not be installed @@ -812,7 +827,8 @@ def test_run_installs_extras_with_deps_if_requested_locked( package_c.add_dependency(Factory.create_dependency("D", "^1.0")) installer.extras(["foo"]) - installer.run() + result = installer.run() + assert result == 0 # But should not be installed installer = installer.installer @@ -833,11 +849,12 @@ def test_installer_with_pypi_repository( NullIO(), env, package, locker, pool, config, installed=installed ) + package.python_versions = ">=3.7" package.add_dependency(Factory.create_dependency("pytest", "^3.5", groups=["dev"])) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-pypi-repository") - assert expected == locker.written_data @@ -853,7 +870,8 @@ def test_run_installs_with_local_file( repo.add_package(get_package("pendulum", "1.4.4")) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-file-dependency") @@ -874,7 +892,8 @@ def test_run_installs_wheel_with_no_requires_dist( ) package.add_dependency(Factory.create_dependency("demo", {"file": str(file_path)})) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-wheel-dependency-no-requires-dist") @@ -900,7 +919,8 @@ def test_run_installs_with_local_poetry_directory_and_extras( repo.add_package(get_package("pendulum", "1.4.4")) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-directory-dependency-poetry") @@ -932,7 +952,8 @@ def test_run_installs_with_local_poetry_directory_transitive( repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cachy", "0.2.0")) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-directory-dependency-poetry-transitive") @@ -964,7 +985,8 @@ def test_run_installs_with_local_poetry_file_transitive( repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cachy", "0.2.0")) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-file-dependency-transitive") @@ -989,7 +1011,8 @@ def test_run_installs_with_local_setuptools_directory( repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cachy", "0.2.0")) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-directory-dependency-setuptools") @@ -1036,9 +1059,10 @@ def test_run_with_prereleases( installer.update(True) installer.whitelist({"B": "^1.1"}) - installer.run() - expected = fixture("with-prereleases") + result = installer.run() + assert result == 0 + expected = fixture("with-prereleases") assert locker.written_data == expected @@ -1122,9 +1146,10 @@ def test_run_update_all_with_lock( installer.update(True) - installer.run() - expected = fixture("update-with-lock") + result = installer.run() + assert result == 0 + expected = fixture("update-with-lock") assert locker.written_data == expected @@ -1195,9 +1220,10 @@ def test_run_update_with_locked_extras( installer.update(True) installer.whitelist("D") - installer.run() - expected = fixture("update-with-locked-extras") + result = installer.run() + assert result == 0 + expected = fixture("update-with-locked-extras") assert locker.written_data == expected @@ -1228,7 +1254,8 @@ def test_run_install_duplicate_dependencies_different_constraints( repo.add_package(package_c12) repo.add_package(package_c15) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-duplicate-dependencies") @@ -1342,7 +1369,8 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock( repo.add_package(package_c15) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-duplicate-dependencies") @@ -1411,7 +1439,8 @@ def test_run_update_uninstalls_after_removal_transient_dependency( installed.add_package(get_package("B", "1.0")) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 installs = installer.installer.installs assert len(installs) == 0 @@ -1519,7 +1548,8 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda installer.update(True) installer.whitelist(["A"]) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-duplicate-dependencies-update") @@ -1557,7 +1587,8 @@ def test_installer_test_solver_finds_compatible_package_for_dependency_python_no repo.add_package(package_a100) repo.add_package(package_a101) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-conditional-dependency") assert locker.written_data == expected @@ -1598,7 +1629,8 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de repo.add_package(package_d) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert len(installer.installer.installs) == 3 assert len(installer.installer.updates) == 0 @@ -1618,7 +1650,8 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de installer.update(True) installer.whitelist(["D"]) - installer.run() + result = installer.run() + assert result == 0 assert len(installer.installer.installs) == 1 assert len(installer.installer.updates) == 0 @@ -1646,7 +1679,8 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de package.add_dependency(Factory.create_dependency("poetry", {"version": "^0.12.0"})) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert len(installer.installer.installs) == 3 assert len(installer.installer.updates) == 0 @@ -1666,7 +1700,8 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de installer.update(True) installer.whitelist(["pytest"]) - installer.run() + result = installer.run() + assert result == 0 assert len(installer.installer.installs) == 7 assert len(installer.installer.updates) == 0 @@ -1695,7 +1730,8 @@ def test_installer_required_extras_should_be_installed( ) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert len(installer.installer.installs) == 2 assert len(installer.installer.updates) == 0 @@ -1709,7 +1745,8 @@ def test_installer_required_extras_should_be_installed( ) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert len(installer.installer.installs) == 2 assert len(installer.installer.updates) == 0 @@ -1776,21 +1813,24 @@ def test_update_multiple_times_with_split_dependencies_is_idempotent( expected = fixture("with-multiple-updates") installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert locker.written_data == expected locker.mock_lock_data(locker.written_data) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert locker.written_data == expected locker.mock_lock_data(locker.written_data) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert locker.written_data == expected @@ -1816,7 +1856,8 @@ def test_installer_can_install_dependencies_from_forced_source( ) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert len(installer.installer.installs) == 1 assert len(installer.installer.updates) == 0 @@ -1831,7 +1872,8 @@ def test_run_installs_with_url_file( repo.add_package(get_package("pendulum", "1.4.4")) - installer.run() + result = installer.run() + assert result == 0 expected = fixture("with-url-dependency") @@ -1855,7 +1897,8 @@ def test_installer_uses_prereleases_if_they_are_compatible( repo.add_package(package_b) - installer.run() + result = installer.run() + assert result == 0 del installer.installer.installs[:] locker.locked(True) @@ -1865,7 +1908,8 @@ def test_installer_uses_prereleases_if_they_are_compatible( installer.whitelist(["b"]) installer.update(True) - installer.run() + result = installer.run() + assert result == 0 assert len(installer.installer.installs) == 2 @@ -1889,7 +1933,8 @@ def test_installer_can_handle_old_lock_files( NullIO(), MockEnv(), package, locker, pool, config, installed=installed ) - installer.run() + result = installer.run() + assert result == 0 assert len(installer.installer.installs) == 6 @@ -1903,7 +1948,8 @@ def test_installer_can_handle_old_lock_files( installed=installed, ) - installer.run() + result = installer.run() + assert result == 0 # funcsigs will be added assert len(installer.installer.installs) == 7 @@ -1918,7 +1964,8 @@ def test_installer_can_handle_old_lock_files( installed=installed, ) - installer.run() + result = installer.run() + assert result == 0 # colorama will be added assert len(installer.installer.installs) == 8 diff --git a/tests/repositories/fixtures/pypi.org/json/setuptools.json b/tests/repositories/fixtures/pypi.org/json/setuptools.json index 6703a5efc9b..24858bd11b8 100644 --- a/tests/repositories/fixtures/pypi.org/json/setuptools.json +++ b/tests/repositories/fixtures/pypi.org/json/setuptools.json @@ -16,10 +16,36 @@ "md5": "dd4e3fa83a21bf7bf9c51026dc8a4e59", "sha256": "f7cddbb5f5c640311eb00eab6e849f7701fa70bf6a183fc8a2c33dd1d1672fb2" } + }, + { + "filename": "setuptools-67.6.1-py3-none-any.whl", + "hashes": { + "sha256": "e728ca814a823bf7bf60162daf9db95b93d532948c4c0bea762ce62f60189078" + }, + "requires-python": ">=3.7", + "size": 1089263, + "upload-time": "2023-03-28T13:45:43.525946Z", + "url": "https://files.pythonhosted.org/packages/0b/fc/8781442def77b0aa22f63f266d4dadd486ebc0c5371d6290caf4320da4b7/setuptools-67.6.1-py3-none-any.whl", + "yanked": false + }, + { + "filename": "setuptools-67.6.1.tar.gz", + "hashes": { + "sha256": "257de92a9d50a60b8e22abfcbb771571fde0dbf3ec234463212027a4eeecbe9a" + }, + "requires-python": ">=3.7", + "size": 2486256, + "upload-time": "2023-03-28T13:45:45.967259Z", + "url": "https://files.pythonhosted.org/packages/cb/46/22ec35f286a77e6b94adf81b4f0d59f402ed981d4251df0ba7b992299146/setuptools-67.6.1.tar.gz", + "yanked": false } ], "meta": { "api-version": "1.0", "_last-serial": 3879671 - } + }, + "versions": [ + "39.2.0", + "67.6.1" + ] } diff --git a/tests/repositories/fixtures/pypi.org/json/setuptools/67.6.1.json b/tests/repositories/fixtures/pypi.org/json/setuptools/67.6.1.json new file mode 100644 index 00000000000..7a2c4192756 --- /dev/null +++ b/tests/repositories/fixtures/pypi.org/json/setuptools/67.6.1.json @@ -0,0 +1,140 @@ +{ + "info": { + "author": "Python Packaging Authority", + "author_email": "distutils-sig@python.org", + "bugtrack_url": null, + "classifiers": [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: System :: Archiving :: Packaging", + "Topic :: System :: Systems Administration", + "Topic :: Utilities" + ], + "description": ".. image:: https://img.shields.io/pypi/v/setuptools.svg\n :target: https://pypi.org/project/setuptools\n\n.. image:: https://img.shields.io/pypi/pyversions/setuptools.svg\n\n.. image:: https://github.com/pypa/setuptools/workflows/tests/badge.svg\n :target: https://github.com/pypa/setuptools/actions?query=workflow%3A%22tests%22\n :alt: tests\n\n.. image:: https://img.shields.io/badge/code%20style-black-000000.svg\n :target: https://github.com/psf/black\n :alt: Code style: Black\n\n.. image:: https://img.shields.io/readthedocs/setuptools/latest.svg\n :target: https://setuptools.pypa.io\n\n.. image:: https://img.shields.io/badge/skeleton-2023-informational\n :target: https://blog.jaraco.com/skeleton\n\n.. image:: https://img.shields.io/codecov/c/github/pypa/setuptools/master.svg?logo=codecov&logoColor=white\n :target: https://codecov.io/gh/pypa/setuptools\n\n.. image:: https://tidelift.com/badges/github/pypa/setuptools?style=flat\n :target: https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=readme\n\n.. image:: https://img.shields.io/discord/803025117553754132\n :target: https://discord.com/channels/803025117553754132/815945031150993468\n :alt: Discord\n\nSee the `Installation Instructions\n`_ in the Python Packaging\nUser's Guide for instructions on installing, upgrading, and uninstalling\nSetuptools.\n\nQuestions and comments should be directed to `GitHub Discussions\n`_.\nBug reports and especially tested patches may be\nsubmitted directly to the `bug tracker\n`_.\n\n\nCode of Conduct\n===============\n\nEveryone interacting in the setuptools project's codebases, issue trackers,\nchat rooms, and fora is expected to follow the\n`PSF Code of Conduct `_.\n\n\nFor Enterprise\n==============\n\nAvailable as part of the Tidelift Subscription.\n\nSetuptools and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use.\n\n`Learn more `_.\n\n\nSecurity Contact\n================\n\nTo report a security vulnerability, please use the\n`Tidelift security contact `_.\nTidelift will coordinate the fix and disclosure.\n", + "description_content_type": "", + "docs_url": null, + "download_url": "", + "downloads": { + "last_day": -1, + "last_month": -1, + "last_week": -1 + }, + "home_page": "https://github.com/pypa/setuptools", + "keywords": "CPAN PyPI distutils eggs package management", + "license": "", + "maintainer": "", + "maintainer_email": "", + "name": "setuptools", + "package_url": "https://pypi.org/project/setuptools/", + "platform": null, + "project_url": "https://pypi.org/project/setuptools/", + "project_urls": { + "Changelog": "https://setuptools.pypa.io/en/stable/history.html", + "Documentation": "https://setuptools.pypa.io/", + "Homepage": "https://github.com/pypa/setuptools" + }, + "release_url": "https://pypi.org/project/setuptools/67.6.1/", + "requires_dist": [ + "sphinx (>=3.5) ; extra == 'docs'", + "jaraco.packaging (>=9) ; extra == 'docs'", + "rst.linker (>=1.9) ; extra == 'docs'", + "furo ; extra == 'docs'", + "sphinx-lint ; extra == 'docs'", + "jaraco.tidelift (>=1.4) ; extra == 'docs'", + "pygments-github-lexers (==0.0.5) ; extra == 'docs'", + "sphinx-favicon ; extra == 'docs'", + "sphinx-inline-tabs ; extra == 'docs'", + "sphinx-reredirects ; extra == 'docs'", + "sphinxcontrib-towncrier ; extra == 'docs'", + "sphinx-notfound-page (==0.8.3) ; extra == 'docs'", + "sphinx-hoverxref (<2) ; extra == 'docs'", + "pytest (>=6) ; extra == 'testing'", + "pytest-checkdocs (>=2.4) ; extra == 'testing'", + "flake8 (<5) ; extra == 'testing'", + "pytest-enabler (>=1.3) ; extra == 'testing'", + "pytest-perf ; extra == 'testing'", + "flake8-2020 ; extra == 'testing'", + "virtualenv (>=13.0.0) ; extra == 'testing'", + "wheel ; extra == 'testing'", + "pip (>=19.1) ; extra == 'testing'", + "jaraco.envs (>=2.2) ; extra == 'testing'", + "pytest-xdist ; extra == 'testing'", + "jaraco.path (>=3.2.0) ; extra == 'testing'", + "build[virtualenv] ; extra == 'testing'", + "filelock (>=3.4.0) ; extra == 'testing'", + "pip-run (>=8.8) ; extra == 'testing'", + "ini2toml[lite] (>=0.9) ; extra == 'testing'", + "tomli-w (>=1.0.0) ; extra == 'testing'", + "pytest-timeout ; extra == 'testing'", + "pytest ; extra == 'testing-integration'", + "pytest-xdist ; extra == 'testing-integration'", + "pytest-enabler ; extra == 'testing-integration'", + "virtualenv (>=13.0.0) ; extra == 'testing-integration'", + "tomli ; extra == 'testing-integration'", + "wheel ; extra == 'testing-integration'", + "jaraco.path (>=3.2.0) ; extra == 'testing-integration'", + "jaraco.envs (>=2.2) ; extra == 'testing-integration'", + "build[virtualenv] ; extra == 'testing-integration'", + "filelock (>=3.4.0) ; extra == 'testing-integration'", + "pytest-black (>=0.3.7) ; (platform_python_implementation != \"PyPy\") and extra == 'testing'", + "pytest-cov ; (platform_python_implementation != \"PyPy\") and extra == 'testing'", + "pytest-mypy (>=0.9.1) ; (platform_python_implementation != \"PyPy\") and extra == 'testing'", + "pytest-flake8 ; (python_version < \"3.12\") and extra == 'testing'" + ], + "requires_python": ">=3.7", + "summary": "Easily download, build, install, upgrade, and uninstall Python packages", + "version": "67.6.1", + "yanked": false, + "yanked_reason": null + }, + "last_serial": 17478645, + "urls": [ + { + "comment_text": "", + "digests": { + "blake2b_256": "0bfc8781442def77b0aa22f63f266d4dadd486ebc0c5371d6290caf4320da4b7", + "md5": "3b5b846e000da033d54eeaaf7915126e", + "sha256": "e728ca814a823bf7bf60162daf9db95b93d532948c4c0bea762ce62f60189078" + }, + "downloads": -1, + "filename": "setuptools-67.6.1-py3-none-any.whl", + "has_sig": false, + "md5_digest": "3b5b846e000da033d54eeaaf7915126e", + "packagetype": "bdist_wheel", + "python_version": "py3", + "requires_python": ">=3.7", + "size": 1089263, + "upload_time": "2023-03-28T13:45:43", + "upload_time_iso_8601": "2023-03-28T13:45:43.525946Z", + "url": "https://files.pythonhosted.org/packages/0b/fc/8781442def77b0aa22f63f266d4dadd486ebc0c5371d6290caf4320da4b7/setuptools-67.6.1-py3-none-any.whl", + "yanked": false, + "yanked_reason": null + }, + { + "comment_text": "", + "digests": { + "blake2b_256": "cb4622ec35f286a77e6b94adf81b4f0d59f402ed981d4251df0ba7b992299146", + "md5": "a661b7cdf4cf1e914f866506c1022dee", + "sha256": "257de92a9d50a60b8e22abfcbb771571fde0dbf3ec234463212027a4eeecbe9a" + }, + "downloads": -1, + "filename": "setuptools-67.6.1.tar.gz", + "has_sig": false, + "md5_digest": "a661b7cdf4cf1e914f866506c1022dee", + "packagetype": "sdist", + "python_version": "source", + "requires_python": ">=3.7", + "size": 2486256, + "upload_time": "2023-03-28T13:45:45", + "upload_time_iso_8601": "2023-03-28T13:45:45.967259Z", + "url": "https://files.pythonhosted.org/packages/cb/46/22ec35f286a77e6b94adf81b4f0d59f402ed981d4251df0ba7b992299146/setuptools-67.6.1.tar.gz", + "yanked": false, + "yanked_reason": null + } + ], + "vulnerabilities": [] +} diff --git a/tests/repositories/fixtures/pypi.org/json/wheel.json b/tests/repositories/fixtures/pypi.org/json/wheel.json new file mode 100644 index 00000000000..6a6d6b8df7b --- /dev/null +++ b/tests/repositories/fixtures/pypi.org/json/wheel.json @@ -0,0 +1,34 @@ +{ + "files": [ + { + "filename": "wheel-0.40.0-py3-none-any.whl", + "hashes": { + "sha256": "d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247" + }, + "requires-python": ">=3.7", + "size": 64545, + "upload-time": "2023-03-14T15:10:00.828550Z", + "url": "https://files.pythonhosted.org/packages/61/86/cc8d1ff2ca31a312a25a708c891cf9facbad4eae493b3872638db6785eb5/wheel-0.40.0-py3-none-any.whl", + "yanked": false + }, + { + "filename": "wheel-0.40.0.tar.gz", + "hashes": { + "sha256": "cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873" + }, + "requires-python": ">=3.7", + "size": 96226, + "upload-time": "2023-03-14T15:10:02.873691Z", + "url": "https://files.pythonhosted.org/packages/fc/ef/0335f7217dd1e8096a9e8383e1d472aa14717878ffe07c4772e68b6e8735/wheel-0.40.0.tar.gz", + "yanked": false + } + ], + "meta": { + "_last-serial": 17289142, + "api-version": "1.1" + }, + "name": "wheel", + "versions": [ + "0.40.0" + ] +} diff --git a/tests/repositories/fixtures/pypi.org/json/wheel/0.40.0.json b/tests/repositories/fixtures/pypi.org/json/wheel/0.40.0.json new file mode 100644 index 00000000000..6043ad6cd68 --- /dev/null +++ b/tests/repositories/fixtures/pypi.org/json/wheel/0.40.0.json @@ -0,0 +1,98 @@ +{ + "info": { + "author": "", + "author_email": "Daniel Holth ", + "bugtrack_url": null, + "classifiers": [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Topic :: System :: Archiving :: Packaging" + ], + "description": "wheel\n=====\n\nThis library is the reference implementation of the Python wheel packaging\nstandard, as defined in `PEP 427`_.\n\nIt has two different roles:\n\n#. A setuptools_ extension for building wheels that provides the\n ``bdist_wheel`` setuptools command\n#. A command line tool for working with wheel files\n\nIt should be noted that wheel is **not** intended to be used as a library, and\nas such there is no stable, public API.\n\n.. _PEP 427: https://www.python.org/dev/peps/pep-0427/\n.. _setuptools: https://pypi.org/project/setuptools/\n\nDocumentation\n-------------\n\nThe documentation_ can be found on Read The Docs.\n\n.. _documentation: https://wheel.readthedocs.io/\n\nCode of Conduct\n---------------\n\nEveryone interacting in the wheel project's codebases, issue trackers, chat\nrooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.\n\n.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md\n\n", + "description_content_type": "text/x-rst", + "docs_url": null, + "download_url": "", + "downloads": { + "last_day": -1, + "last_month": -1, + "last_week": -1 + }, + "home_page": "", + "keywords": "wheel,packaging", + "license": "", + "maintainer": "", + "maintainer_email": "Alex Grönholm ", + "name": "wheel", + "package_url": "https://pypi.org/project/wheel/", + "platform": null, + "project_url": "https://pypi.org/project/wheel/", + "project_urls": { + "Changelog": "https://wheel.readthedocs.io/en/stable/news.html", + "Documentation": "https://wheel.readthedocs.io/", + "Issue Tracker": "https://github.com/pypa/wheel/issues" + }, + "release_url": "https://pypi.org/project/wheel/0.40.0/", + "requires_dist": [ + "pytest >= 6.0.0 ; extra == \"test\"" + ], + "requires_python": ">=3.7", + "summary": "A built-package format for Python", + "version": "0.40.0", + "yanked": false, + "yanked_reason": null + }, + "last_serial": 17289142, + "urls": [ + { + "comment_text": "", + "digests": { + "blake2b_256": "6186cc8d1ff2ca31a312a25a708c891cf9facbad4eae493b3872638db6785eb5", + "md5": "517d39f133bd7b1ff17caf09784b7543", + "sha256": "d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247" + }, + "downloads": -1, + "filename": "wheel-0.40.0-py3-none-any.whl", + "has_sig": false, + "md5_digest": "517d39f133bd7b1ff17caf09784b7543", + "packagetype": "bdist_wheel", + "python_version": "py3", + "requires_python": ">=3.7", + "size": 64545, + "upload_time": "2023-03-14T15:10:00", + "upload_time_iso_8601": "2023-03-14T15:10:00.828550Z", + "url": "https://files.pythonhosted.org/packages/61/86/cc8d1ff2ca31a312a25a708c891cf9facbad4eae493b3872638db6785eb5/wheel-0.40.0-py3-none-any.whl", + "yanked": false, + "yanked_reason": null + }, + { + "comment_text": "", + "digests": { + "blake2b_256": "fcef0335f7217dd1e8096a9e8383e1d472aa14717878ffe07c4772e68b6e8735", + "md5": "ec5004c46d1905da98bb5bc1a10ddd21", + "sha256": "cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873" + }, + "downloads": -1, + "filename": "wheel-0.40.0.tar.gz", + "has_sig": false, + "md5_digest": "ec5004c46d1905da98bb5bc1a10ddd21", + "packagetype": "sdist", + "python_version": "source", + "requires_python": ">=3.7", + "size": 96226, + "upload_time": "2023-03-14T15:10:02", + "upload_time_iso_8601": "2023-03-14T15:10:02.873691Z", + "url": "https://files.pythonhosted.org/packages/fc/ef/0335f7217dd1e8096a9e8383e1d472aa14717878ffe07c4772e68b6e8735/wheel-0.40.0.tar.gz", + "yanked": false, + "yanked_reason": null + } + ], + "vulnerabilities": [] +} From fba14ba5edabd98fd57737e2d583896db54c3fee Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Tue, 22 Nov 2022 13:49:44 +0100 Subject: [PATCH 126/151] fix: consistently retry on error codes in publish and install --- src/poetry/publishing/uploader.py | 3 ++- src/poetry/utils/authenticator.py | 3 ++- src/poetry/utils/constants.py | 3 +++ tests/utils/test_authenticator.py | 3 ++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/poetry/publishing/uploader.py b/src/poetry/publishing/uploader.py index 1f3a6bd81b9..7e6cd6d9b6a 100644 --- a/src/poetry/publishing/uploader.py +++ b/src/poetry/publishing/uploader.py @@ -21,6 +21,7 @@ from poetry.__version__ import __version__ from poetry.utils.constants import REQUESTS_TIMEOUT +from poetry.utils.constants import STATUS_FORCELIST from poetry.utils.patterns import wheel_file_re @@ -68,7 +69,7 @@ def adapter(self) -> adapters.HTTPAdapter: connect=5, total=10, allowed_methods=["GET"], - status_forcelist=[500, 501, 502, 503], + status_forcelist=STATUS_FORCELIST, ) return adapters.HTTPAdapter(max_retries=retry) diff --git a/src/poetry/utils/authenticator.py b/src/poetry/utils/authenticator.py index 8db92913043..2b1011e4d56 100644 --- a/src/poetry/utils/authenticator.py +++ b/src/poetry/utils/authenticator.py @@ -24,6 +24,7 @@ from poetry.config.config import Config from poetry.exceptions import PoetryException from poetry.utils.constants import REQUESTS_TIMEOUT +from poetry.utils.constants import STATUS_FORCELIST from poetry.utils.password_manager import HTTPAuthCredential from poetry.utils.password_manager import PasswordManager @@ -259,7 +260,7 @@ def request( if is_last_attempt: raise e else: - if resp.status_code not in [502, 503, 504] or is_last_attempt: + if resp.status_code not in STATUS_FORCELIST or is_last_attempt: if raise_for_status: resp.raise_for_status() return resp diff --git a/src/poetry/utils/constants.py b/src/poetry/utils/constants.py index 0f799b16d7d..b755fb68e5e 100644 --- a/src/poetry/utils/constants.py +++ b/src/poetry/utils/constants.py @@ -3,3 +3,6 @@ # Timeout for HTTP requests using the requests library. REQUESTS_TIMEOUT = 15 + +# Server response codes to retry requests on. +STATUS_FORCELIST = [500, 501, 502, 503, 504] diff --git a/tests/utils/test_authenticator.py b/tests/utils/test_authenticator.py index 91e6a574bc8..545b73f53bf 100644 --- a/tests/utils/test_authenticator.py +++ b/tests/utils/test_authenticator.py @@ -249,7 +249,8 @@ def callback(*_: Any, **___: Any) -> None: (401, 0), (403, 0), (404, 0), - (500, 0), + (500, 5), + (501, 5), (502, 5), (503, 5), (504, 5), From 6b3a6161103ff35908608eb324cfa015bb4a785a Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Tue, 22 Nov 2022 16:32:25 +0100 Subject: [PATCH 127/151] fix: respect retry-after header with 429 responses --- src/poetry/publishing/uploader.py | 1 + src/poetry/utils/authenticator.py | 12 +++++++++++- src/poetry/utils/constants.py | 4 +++- tests/utils/test_authenticator.py | 28 ++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/poetry/publishing/uploader.py b/src/poetry/publishing/uploader.py index 7e6cd6d9b6a..256fd235259 100644 --- a/src/poetry/publishing/uploader.py +++ b/src/poetry/publishing/uploader.py @@ -69,6 +69,7 @@ def adapter(self) -> adapters.HTTPAdapter: connect=5, total=10, allowed_methods=["GET"], + respect_retry_after_header=True, status_forcelist=STATUS_FORCELIST, ) diff --git a/src/poetry/utils/authenticator.py b/src/poetry/utils/authenticator.py index 2b1011e4d56..0fb238fb56c 100644 --- a/src/poetry/utils/authenticator.py +++ b/src/poetry/utils/authenticator.py @@ -24,6 +24,7 @@ from poetry.config.config import Config from poetry.exceptions import PoetryException from poetry.utils.constants import REQUESTS_TIMEOUT +from poetry.utils.constants import RETRY_AFTER_HEADER from poetry.utils.constants import STATUS_FORCELIST from poetry.utils.password_manager import HTTPAuthCredential from poetry.utils.password_manager import PasswordManager @@ -251,6 +252,7 @@ def request( send_kwargs.update(settings) attempt = 0 + resp = None while True: is_last_attempt = attempt >= 5 @@ -267,7 +269,7 @@ def request( if not is_last_attempt: attempt += 1 - delay = 0.5 * attempt + delay = self._get_backoff(resp, attempt) logger.debug("Retrying HTTP request in %s seconds.", delay) time.sleep(delay) continue @@ -275,6 +277,14 @@ def request( # this should never really be hit under any sane circumstance raise PoetryException("Failed HTTP {} request", method.upper()) + def _get_backoff(self, response: requests.Response | None, attempt: int) -> float: + if response is not None: + retry_after = response.headers.get(RETRY_AFTER_HEADER, "") + if retry_after: + return float(retry_after) + + return 0.5 * attempt + def get(self, url: str, **kwargs: Any) -> requests.Response: return self.request("get", url, **kwargs) diff --git a/src/poetry/utils/constants.py b/src/poetry/utils/constants.py index b755fb68e5e..56bec540ae2 100644 --- a/src/poetry/utils/constants.py +++ b/src/poetry/utils/constants.py @@ -4,5 +4,7 @@ # Timeout for HTTP requests using the requests library. REQUESTS_TIMEOUT = 15 +RETRY_AFTER_HEADER = "retry-after" + # Server response codes to retry requests on. -STATUS_FORCELIST = [500, 501, 502, 503, 504] +STATUS_FORCELIST = [429, 500, 501, 502, 503, 504] diff --git a/tests/utils/test_authenticator.py b/tests/utils/test_authenticator.py index 545b73f53bf..05c5490ac11 100644 --- a/tests/utils/test_authenticator.py +++ b/tests/utils/test_authenticator.py @@ -242,6 +242,33 @@ def callback(*_: Any, **___: Any) -> None: assert sleep.call_count == 5 +def test_authenticator_request_respects_retry_header( + mocker: MockerFixture, + config: Config, + http: type[httpretty.httpretty], +): + sleep = mocker.patch("time.sleep") + sdist_uri = f"https://foo.bar/files/{uuid.uuid4()!s}/foo-0.1.0.tar.gz" + content = str(uuid.uuid4()) + seen = [] + + def callback( + request: requests.Request, uri: str, response_headers: dict + ) -> list[int | dict | str]: + if not seen.count(uri): + seen.append(uri) + return [429, {"Retry-After": "42"}, "Retry later"] + + return [200, response_headers, content] + + http.register_uri(httpretty.GET, sdist_uri, body=callback) + authenticator = Authenticator(config, NullIO()) + + response = authenticator.request("get", sdist_uri) + assert sleep.call_args[0] == (42.0,) + assert response.text == content + + @pytest.mark.parametrize( ["status", "attempts"], [ @@ -249,6 +276,7 @@ def callback(*_: Any, **___: Any) -> None: (401, 0), (403, 0), (404, 0), + (429, 5), (500, 5), (501, 5), (502, 5), From 7585c375204880091ec01a9cf375f2a5cea70f9e Mon Sep 17 00:00:00 2001 From: samypr100 <3933065+samypr100@users.noreply.github.com> Date: Wed, 5 Apr 2023 13:48:11 +0000 Subject: [PATCH 128/151] Error when invalid groups are referenced (#7529) --- docs/managing-dependencies.md | 2 +- src/poetry/console/commands/group_command.py | 22 ++++++++++ src/poetry/console/exceptions.py | 4 ++ tests/console/commands/test_install.py | 46 ++++++++++++++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) diff --git a/docs/managing-dependencies.md b/docs/managing-dependencies.md index 16622624857..14932d43df9 100644 --- a/docs/managing-dependencies.md +++ b/docs/managing-dependencies.md @@ -163,7 +163,7 @@ poetry install --only docs ``` {{% note %}} -If you only want to install the project's runtime dependencies, you can do so with the +If you only want to install the project's runtime dependencies, you can do so with the `--only main` notation: ```bash diff --git a/src/poetry/console/commands/group_command.py b/src/poetry/console/commands/group_command.py index 27438bf0c07..88b9a1ce39a 100644 --- a/src/poetry/console/commands/group_command.py +++ b/src/poetry/console/commands/group_command.py @@ -1,11 +1,13 @@ from __future__ import annotations +from collections import defaultdict from typing import TYPE_CHECKING from cleo.helpers import option from poetry.core.packages.dependency_group import MAIN_GROUP from poetry.console.commands.command import Command +from poetry.console.exceptions import GroupNotFound if TYPE_CHECKING: @@ -78,6 +80,7 @@ def activated_groups(self) -> set[str]: for groups in self.option(key, "") for group in groups.split(",") } + self._validate_group_options(groups) for opt, new, group in [ ("no-dev", "only", MAIN_GROUP), @@ -107,3 +110,22 @@ def project_with_activated_groups_only(self) -> ProjectPackage: return self.poetry.package.with_dependency_groups( list(self.activated_groups), only=True ) + + def _validate_group_options(self, group_options: dict[str, set[str]]) -> None: + """ + Raises en error if it detects that a group is not part of pyproject.toml + """ + invalid_options = defaultdict(set) + for opt, groups in group_options.items(): + for group in groups: + if not self.poetry.package.has_dependency_group(group): + invalid_options[group].add(opt) + if invalid_options: + message_parts = [] + for group in sorted(invalid_options): + opts = ", ".join( + f"--{opt}" + for opt in sorted(invalid_options[group]) + ) + message_parts.append(f"{group} (via {opts})") + raise GroupNotFound(f"Group(s) not found: {', '.join(message_parts)}") diff --git a/src/poetry/console/exceptions.py b/src/poetry/console/exceptions.py index aadc8c17e7f..2cc359ddb75 100644 --- a/src/poetry/console/exceptions.py +++ b/src/poetry/console/exceptions.py @@ -5,3 +5,7 @@ class PoetryConsoleError(CleoError): pass + + +class GroupNotFound(PoetryConsoleError): + pass diff --git a/tests/console/commands/test_install.py b/tests/console/commands/test_install.py index 0200a452bfa..f39081872d0 100644 --- a/tests/console/commands/test_install.py +++ b/tests/console/commands/test_install.py @@ -1,5 +1,7 @@ from __future__ import annotations +import re + from typing import TYPE_CHECKING import pytest @@ -7,6 +9,8 @@ from poetry.core.masonry.utils.module import ModuleOrPackageNotFound from poetry.core.packages.dependency_group import MAIN_GROUP +from poetry.console.exceptions import GroupNotFound + if TYPE_CHECKING: from cleo.testers.command_tester import CommandTester @@ -257,6 +261,48 @@ def test_only_root_conflicts_with_without_only( ) +@pytest.mark.parametrize( + ("options", "valid_groups", "should_raise"), + [ + ({"--with": MAIN_GROUP}, {MAIN_GROUP}, False), + ({"--with": "spam"}, set(), True), + ({"--with": "spam,foo"}, {"foo"}, True), + ({"--without": "spam"}, set(), True), + ({"--without": "spam,bar"}, {"bar"}, True), + ({"--with": "eggs,ham", "--without": "spam"}, set(), True), + ({"--with": "eggs,ham", "--without": "spam,baz"}, {"baz"}, True), + ({"--only": "spam"}, set(), True), + ({"--only": "bim"}, {"bim"}, False), + ({"--only": MAIN_GROUP}, {MAIN_GROUP}, False), + ], +) +def test_invalid_groups_with_without_only( + tester: CommandTester, + mocker: MockerFixture, + options: dict[str, str], + valid_groups: set[str], + should_raise: bool, +): + mocker.patch.object(tester.command.installer, "run", return_value=0) + + cmd_args = " ".join(f"{flag} {groups}" for (flag, groups) in options.items()) + + if not should_raise: + tester.execute(cmd_args) + assert tester.status_code == 0 + else: + with pytest.raises(GroupNotFound, match=r"^Group\(s\) not found:") as e: + tester.execute(cmd_args) + assert tester.status_code is None + for opt, groups in options.items(): + group_list = groups.split(",") + invalid_groups = sorted(set(group_list) - valid_groups) + for group in invalid_groups: + assert ( + re.search(rf"{group} \(via .*{opt}.*\)", str(e.value)) is not None + ) + + def test_remove_untracked_outputs_deprecation_warning( tester: CommandTester, mocker: MockerFixture, From 161b19cb4e4686fc5a0a7925001534a87f6c4052 Mon Sep 17 00:00:00 2001 From: Wagner Macedo Date: Wed, 5 Apr 2023 16:07:06 +0200 Subject: [PATCH 129/151] poetry run: deprecate uninstalled entry points (#7606) --- src/poetry/console/commands/run.py | 14 ++++++++++++++ tests/console/commands/test_run.py | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/poetry/console/commands/run.py b/src/poetry/console/commands/run.py index 63af286d3b1..5b008a2fc53 100644 --- a/src/poetry/console/commands/run.py +++ b/src/poetry/console/commands/run.py @@ -63,6 +63,9 @@ def run_script(self, script: str | dict[str, str], args: list[str]) -> int: if script_path.exists(): args = [str(script_path), *args[1:]] break + else: + # If we reach this point, the script is not installed + self._warning_not_installed_script(args[0]) if isinstance(script, dict): script = script["callable"] @@ -81,3 +84,14 @@ def run_script(self, script: str | dict[str, str], args: list[str]) -> int: ] return self.env.execute(*cmd) + + def _warning_not_installed_script(self, script: str) -> None: + message = f"""\ +Warning: '{script}' is an entry point defined in pyproject.toml, but it's not \ +installed as a script. You may get improper `sys.argv[0]`. + +The support to run uninstalled scripts will be removed in a future release. + +Run `poetry install` to resolve and get rid of this message. +""" + self.line_error(message, style="warning") diff --git a/tests/console/commands/test_run.py b/tests/console/commands/test_run.py index 6b5bd95a728..7442ac10f37 100644 --- a/tests/console/commands/test_run.py +++ b/tests/console/commands/test_run.py @@ -180,3 +180,17 @@ def test_run_script_sys_argv0( ) argv1 = "absolute" if installed_script else "relative" assert tester.execute(f"check-argv0 {argv1}") == 0 + + if installed_script: + expected_message = "" + else: + expected_message = """\ +Warning: 'check-argv0' is an entry point defined in pyproject.toml, but it's not \ +installed as a script. You may get improper `sys.argv[0]`. + +The support to run uninstalled scripts will be removed in a future release. + +Run `poetry install` to resolve and get rid of this message. + +""" + assert tester.io.fetch_error() == expected_message From 623bfffbe79961b7fb999906ff60b5df578f9825 Mon Sep 17 00:00:00 2001 From: Wagner Macedo Date: Thu, 6 Apr 2023 16:57:10 +0200 Subject: [PATCH 130/151] Only write lock file when installation is success (#7498) (affects `poetry add` and `poetry update`) --- src/poetry/installation/installer.py | 16 +- src/poetry/packages/locker.py | 18 +- .../console/commands/self/test_add_plugins.py | 28 +- .../commands/self/test_remove_plugins.py | 4 +- tests/console/commands/self/test_update.py | 4 +- tests/console/commands/test_add.py | 249 ++++++++++-------- tests/installation/test_installer.py | 24 ++ tests/installation/test_installer_old.py | 1 + 8 files changed, 213 insertions(+), 131 deletions(-) diff --git a/src/poetry/installation/installer.py b/src/poetry/installation/installer.py index b5aa54f7072..ec5911ce8f7 100644 --- a/src/poetry/installation/installer.py +++ b/src/poetry/installation/installer.py @@ -291,12 +291,10 @@ def _do_install(self) -> int: lockfile_repo = LockfileRepository() self._populate_lockfile_repo(lockfile_repo, ops) - if self._update: + if self._lock and self._update: + # If we are only in lock mode, no need to go any further self._write_lock_file(lockfile_repo) - - if self._lock: - # If we are only in lock mode, no need to go any further - return 0 + return 0 if self._groups is not None: root = self._package.with_dependency_groups(list(self._groups), only=True) @@ -362,7 +360,13 @@ def _do_install(self) -> int: self._filter_operations(ops, lockfile_repo) # Execute operations - return self._execute(ops) + status = self._execute(ops) + + if status == 0 and self._update: + # Only write lock file when installation is success + self._write_lock_file(lockfile_repo) + + return status def _write_lock_file(self, repo: LockfileRepository, force: bool = False) -> None: if self._write_lock and (force or self._update): diff --git a/src/poetry/packages/locker.py b/src/poetry/packages/locker.py index 8784a391ebd..987a1acd609 100644 --- a/src/poetry/packages/locker.py +++ b/src/poetry/packages/locker.py @@ -224,6 +224,18 @@ def locked_repository(self) -> LockfileRepository: return repository def set_lock_data(self, root: Package, packages: list[Package]) -> bool: + """Store lock data and eventually persist to the lock file""" + lock = self._compute_lock_data(root, packages) + + if self._should_write(lock): + self._write_lock_data(lock) + return True + + return False + + def _compute_lock_data( + self, root: Package, packages: list[Package] + ) -> TOMLDocument: package_specs = self._lock_packages(packages) # Retrieving hashes for package in package_specs: @@ -254,6 +266,10 @@ def set_lock_data(self, root: Package, packages: list[Package]) -> bool: "content-hash": self._content_hash, } + return lock + + def _should_write(self, lock: TOMLDocument) -> bool: + # if lock file exists: compare with existing lock data do_write = True if self.is_locked(): try: @@ -263,8 +279,6 @@ def set_lock_data(self, root: Package, packages: list[Package]) -> bool: pass else: do_write = lock != lock_data - if do_write: - self._write_lock_data(lock) return do_write def _write_lock_data(self, data: TOMLDocument) -> None: diff --git a/tests/console/commands/self/test_add_plugins.py b/tests/console/commands/self/test_add_plugins.py index 37de59731f0..e7bd3c1707f 100644 --- a/tests/console/commands/self/test_add_plugins.py +++ b/tests/console/commands/self/test_add_plugins.py @@ -49,11 +49,11 @@ def test_add_no_constraint( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals • Installing poetry-plugin (0.1.0) + +Writing lock file """ assert_plugin_add_result(tester, expected, "^0.1.0") @@ -71,11 +71,11 @@ def test_add_with_constraint( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals • Installing poetry-plugin (0.2.0) + +Writing lock file """ assert_plugin_add_result(tester, expected, "^0.2.0") @@ -93,12 +93,12 @@ def test_add_with_git_constraint( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals • Installing pendulum (2.0.5) • Installing poetry-plugin (0.1.2 9cf87a2) + +Writing lock file """ assert_plugin_add_result( @@ -119,13 +119,13 @@ def test_add_with_git_constraint_with_extras( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 3 installs, 0 updates, 0 removals • Installing pendulum (2.0.5) • Installing tomlkit (0.7.0) • Installing poetry-plugin (0.1.2 9cf87a2) + +Writing lock file """ assert_plugin_add_result( @@ -162,12 +162,12 @@ def test_add_with_git_constraint_with_subdirectory( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals • Installing pendulum (2.0.5) • Installing poetry-plugin (0.1.2 9cf87a2) + +Writing lock file """ constraint = { @@ -262,11 +262,11 @@ def test_add_existing_plugin_updates_if_requested( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 0 installs, 1 update, 0 removals • Updating poetry-plugin (1.2.3 -> 2.3.4) + +Writing lock file """ assert_plugin_add_result(tester, expected, "^2.3.4") @@ -298,12 +298,12 @@ def test_adding_a_plugin_can_update_poetry_dependencies_if_needed( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 1 update, 0 removals • Updating tomlkit (0.7.1 -> 0.7.2) • Installing poetry-plugin (1.2.3) + +Writing lock file """ assert_plugin_add_result(tester, expected, "^1.2.3") diff --git a/tests/console/commands/self/test_remove_plugins.py b/tests/console/commands/self/test_remove_plugins.py index 660b3584012..17f24df5708 100644 --- a/tests/console/commands/self/test_remove_plugins.py +++ b/tests/console/commands/self/test_remove_plugins.py @@ -73,11 +73,11 @@ def test_remove_installed_package(tester: CommandTester): Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 0 installs, 0 updates, 1 removal • Removing poetry-plugin (1.2.3) + +Writing lock file """ assert tester.io.fetch_output() == expected diff --git a/tests/console/commands/self/test_update.py b/tests/console/commands/self/test_update.py index 09c3a21b501..6720406de8b 100644 --- a/tests/console/commands/self/test_update.py +++ b/tests/console/commands/self/test_update.py @@ -71,12 +71,12 @@ def test_self_update_can_update_from_recommended_installation( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 0 installs, 2 updates, 0 removals • Updating cleo (0.8.2 -> 1.0.0) • Updating poetry ({__version__} -> {new_version}) + +Writing lock file """ assert tester.io.fetch_output() == expected_output diff --git a/tests/console/commands/test_add.py b/tests/console/commands/test_add.py index b7ffde8ae1b..5c2a1bafe1f 100644 --- a/tests/console/commands/test_add.py +++ b/tests/console/commands/test_add.py @@ -16,6 +16,8 @@ if TYPE_CHECKING: + from typing import Any + from cleo.testers.command_tester import CommandTester from pytest_mock import MockerFixture @@ -70,11 +72,11 @@ def test_add_no_constraint( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals • Installing cachy (0.2.0) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -100,11 +102,11 @@ def test_add_replace_by_constraint( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals • Installing cachy (0.2.0) + +Writing lock file """ assert tester.io.fetch_output() == expected assert tester.command.installer.executor.installations_count == 1 @@ -119,11 +121,11 @@ def test_add_replace_by_constraint( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals • Installing cachy (0.1.0) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -167,11 +169,11 @@ def test_add_equal_constraint( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals • Installing cachy (0.1.0) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -191,11 +193,11 @@ def test_add_greater_constraint( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals • Installing cachy (0.2.0) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -225,12 +227,12 @@ def test_add_constraint_with_extras( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals • Installing msgpack-python (0.5.3) • Installing cachy (0.1.0) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -255,12 +257,12 @@ def test_add_constraint_dependencies( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals • Installing msgpack-python (0.5.3) • Installing cachy (0.2.0) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -285,12 +287,12 @@ def test_add_git_constraint( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals • Installing pendulum (1.4.4) • Installing demo (0.1.2 9cf87a2) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -321,12 +323,12 @@ def test_add_git_constraint_with_poetry( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals • Installing pendulum (1.4.4) • Installing demo (0.1.2 9cf87a2) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -354,14 +356,14 @@ def test_add_git_constraint_with_extras( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 4 installs, 0 updates, 0 removals • Installing cleo (0.6.5) • Installing pendulum (1.4.4) • Installing tomlkit (0.5.5) • Installing demo (0.1.2 9cf87a2) + +Writing lock file """ assert tester.io.fetch_output().strip() == expected.strip() @@ -400,11 +402,11 @@ def test_add_git_constraint_with_subdirectory( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals • Installing two (2.0.0 9cf87a2) + +Writing lock file """ assert tester.io.fetch_output().strip() == expected.strip() assert tester.command.installer.executor.installations_count == 1 @@ -444,12 +446,12 @@ def test_add_git_ssh_constraint( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals • Installing pendulum (1.4.4) • Installing demo (0.1.2 9cf87a2) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -491,12 +493,12 @@ def test_add_directory_constraint( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals • Installing pendulum (1.4.4) • Installing demo (0.1.2 {app.poetry.file.parent.joinpath(path).resolve().as_posix()}) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -532,12 +534,12 @@ def test_add_directory_with_poetry( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals • Installing pendulum (1.4.4) • Installing demo (0.1.2 {app.poetry.file.parent.joinpath(path).resolve().as_posix()}) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -564,12 +566,12 @@ def test_add_file_constraint_wheel( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals • Installing pendulum (1.4.4) • Installing demo (0.1.0 {app.poetry.file.parent.joinpath(path).resolve().as_posix()}) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -600,12 +602,12 @@ def test_add_file_constraint_sdist( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals • Installing pendulum (1.4.4) • Installing demo (0.1.0 {app.poetry.file.parent.joinpath(path).resolve().as_posix()}) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -640,12 +642,12 @@ def test_add_constraint_with_extras_option( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals • Installing msgpack-python (0.5.3) • Installing cachy (0.2.0) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -680,13 +682,13 @@ def test_add_url_constraint_wheel( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals • Installing pendulum (1.4.4) • Installing demo\ (0.1.0 https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -722,8 +724,6 @@ def test_add_url_constraint_wheel_with_extras( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 4 installs, 0 updates, 0 removals • Installing cleo (0.6.5) @@ -731,6 +731,8 @@ def test_add_url_constraint_wheel_with_extras( • Installing tomlkit (0.5.5) • Installing demo\ (0.1.0 https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl) + +Writing lock file """ # Order might be different, split into lines and compare the overall output. expected = set(expected.splitlines()) @@ -764,11 +766,11 @@ def test_add_constraint_with_python( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals • Installing cachy (0.2.0) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -802,11 +804,11 @@ def test_add_constraint_with_platform( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals • Installing cachy (0.2.0) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -853,11 +855,11 @@ def test_add_constraint_with_source( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals • Installing cachy (0.2.0) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -915,11 +917,11 @@ def test_add_to_section_that_does_not_exist_yet( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals • Installing cachy (0.2.0) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -962,11 +964,11 @@ def test_add_to_dev_section_deprecated( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals • Installing cachy (0.2.0) + +Writing lock file """ assert tester.io.fetch_error() == warning @@ -993,11 +995,11 @@ def test_add_should_not_select_prereleases( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals • Installing pyyaml (3.13) + +Writing lock file """ assert tester.io.fetch_output() == expected @@ -1112,11 +1114,11 @@ def test_add_should_work_when_adding_existing_package_with_latest_constraint( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals • Installing foo (1.1.2) + +Writing lock file """ assert expected in tester.io.fetch_output() @@ -1141,11 +1143,11 @@ def test_add_chooses_prerelease_if_only_prereleases_are_available( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals • Installing foo (1.2.3b1) + +Writing lock file """ assert expected in tester.io.fetch_output() @@ -1164,11 +1166,11 @@ def test_add_prefers_stable_releases( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals • Installing foo (1.2.3) + +Writing lock file """ assert expected in tester.io.fetch_output() @@ -1212,11 +1214,11 @@ def test_add_no_constraint_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals - Installing cachy (0.2.0) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1245,11 +1247,11 @@ def test_add_equal_constraint_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals - Installing cachy (0.1.0) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1273,11 +1275,11 @@ def test_add_greater_constraint_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals - Installing cachy (0.2.0) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1309,12 +1311,12 @@ def test_add_constraint_with_extras_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals - Installing msgpack-python (0.5.3) - Installing cachy (0.1.0) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1343,12 +1345,12 @@ def test_add_constraint_dependencies_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals - Installing msgpack-python (0.5.3) - Installing cachy (0.2.0) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1372,12 +1374,12 @@ def test_add_git_constraint_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals - Installing pendulum (1.4.4) - Installing demo (0.1.2 9cf87a2) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1407,12 +1409,12 @@ def test_add_git_constraint_with_poetry_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals - Installing pendulum (1.4.4) - Installing demo (0.1.2 9cf87a2) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1439,14 +1441,14 @@ def test_add_git_constraint_with_extras_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 4 installs, 0 updates, 0 removals - Installing cleo (0.6.5) - Installing pendulum (1.4.4) - Installing tomlkit (0.5.5) - Installing demo (0.1.2 9cf87a2) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1478,12 +1480,12 @@ def test_add_git_ssh_constraint_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals - Installing pendulum (1.4.4) - Installing demo (0.1.2 9cf87a2) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1520,12 +1522,12 @@ def test_add_directory_constraint_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals - Installing pendulum (1.4.4) - Installing demo (0.1.2 {app.poetry.file.parent.joinpath(path).resolve().as_posix()}) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1558,12 +1560,12 @@ def test_add_directory_with_poetry_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals - Installing pendulum (1.4.4) - Installing demo (0.1.2 {app.poetry.file.parent.joinpath(path).resolve().as_posix()}) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1591,12 +1593,12 @@ def test_add_file_constraint_wheel_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals - Installing pendulum (1.4.4) - Installing demo (0.1.0 {app.poetry.file.parent.joinpath(path).resolve().as_posix()}) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1629,12 +1631,12 @@ def test_add_file_constraint_sdist_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals - Installing pendulum (1.4.4) - Installing demo (0.1.0 {app.poetry.file.parent.joinpath(path).resolve().as_posix()}) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1671,12 +1673,12 @@ def test_add_constraint_with_extras_option_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals - Installing msgpack-python (0.5.3) - Installing cachy (0.2.0) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1713,13 +1715,13 @@ def test_add_url_constraint_wheel_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 2 installs, 0 updates, 0 removals - Installing pendulum (1.4.4) - Installing demo\ (0.1.0 https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1756,8 +1758,6 @@ def test_add_url_constraint_wheel_with_extras_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 4 installs, 0 updates, 0 removals - Installing cleo (0.6.5) @@ -1765,6 +1765,8 @@ def test_add_url_constraint_wheel_with_extras_old_installer( - Installing tomlkit (0.5.5) - Installing demo\ (0.1.0 https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1800,11 +1802,11 @@ def test_add_constraint_with_python_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals - Installing cachy (0.2.0) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1840,11 +1842,11 @@ def test_add_constraint_with_platform_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals - Installing cachy (0.2.0) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1893,11 +1895,11 @@ def test_add_constraint_with_source_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals - Installing cachy (0.2.0) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1959,11 +1961,11 @@ def test_add_to_section_that_does_no_exist_yet_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals - Installing cachy (0.2.0) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -1993,11 +1995,11 @@ def test_add_should_not_select_prereleases_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals - Installing pyyaml (3.13) + +Writing lock file """ assert old_tester.io.fetch_output() == expected @@ -2058,11 +2060,11 @@ def test_add_should_work_when_adding_existing_package_with_latest_constraint_old Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals - Installing foo (1.1.2) + +Writing lock file """ assert expected in old_tester.io.fetch_output() @@ -2090,11 +2092,11 @@ def test_add_chooses_prerelease_if_only_prereleases_are_available_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals - Installing foo (1.2.3b1) + +Writing lock file """ assert expected in old_tester.io.fetch_output() @@ -2116,11 +2118,11 @@ def test_add_preferes_stable_releases_old_installer( Updating dependencies Resolving dependencies... -Writing lock file - Package operations: 1 install, 0 updates, 0 removals - Installing foo (1.2.3) + +Writing lock file """ assert expected in old_tester.io.fetch_output() @@ -2157,7 +2159,8 @@ def test_add_keyboard_interrupt_restore_content( tester = command_tester_factory("add", poetry=poetry_with_up_to_date_lockfile) mocker.patch( - "poetry.installation.installer.Installer.run", side_effect=KeyboardInterrupt() + "poetry.installation.installer.Installer._execute", + side_effect=KeyboardInterrupt(), ) original_pyproject_content = poetry_with_up_to_date_lockfile.file.read() original_lockfile_content = poetry_with_up_to_date_lockfile._locker.lock_data @@ -2200,3 +2203,39 @@ def test_add_with_dry_run_keep_files_intact( assert ( poetry_with_up_to_date_lockfile._locker.lock_data == original_lockfile_content ) + + +def test_add_should_not_change_lock_file_when_dependency_installation_fail( + poetry_with_up_to_date_lockfile: Poetry, + repo: TestRepository, + command_tester_factory: CommandTesterFactory, + mocker: MockerFixture, +): + tester = command_tester_factory("add", poetry=poetry_with_up_to_date_lockfile) + + repo.add_package(get_package("docker", "4.3.1")) + repo.add_package(get_package("cachy", "0.2.0")) + + original_pyproject_content = poetry_with_up_to_date_lockfile.file.read() + original_lockfile_content = poetry_with_up_to_date_lockfile.locker.lock_data + + def error(_: Any) -> int: + tester.io.write("\n BuildError\n\n") + return 1 + + mocker.patch("poetry.installation.installer.Installer._execute", side_effect=error) + tester.execute("cachy") + + expected = """\ +Using version ^0.2.0 for cachy + +Updating dependencies +Resolving dependencies... + + BuildError + +""" + + assert poetry_with_up_to_date_lockfile.file.read() == original_pyproject_content + assert poetry_with_up_to_date_lockfile.locker.lock_data == original_lockfile_content + assert tester.io.fetch_output() == expected diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 841a2ccde64..400278e7798 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -105,6 +105,7 @@ def __init__(self, lock_path: Path) -> None: self._lock = lock_path / "poetry.lock" self._written_data = None self._locked = False + self._lock_data = None self._content_hash = self._get_content_hash() @property @@ -2415,6 +2416,29 @@ def test_installer_can_handle_old_lock_files( assert installer.executor.installations_count == 8 +def test_installer_does_not_write_lock_file_when_installation_fails( + installer: Installer, + locker: Locker, + repo: Repository, + package: ProjectPackage, + mocker: MockerFixture, +): + repo.add_package(get_package("A", "1.0")) + package.add_dependency(Factory.create_dependency("A", "~1.0")) + + locker.locked(False) + + mocker.patch("poetry.installation.installer.Installer._execute", return_value=1) + result = installer.run() + assert result == 1 # error + + assert locker._lock_data is None + + assert installer.executor.installations_count == 0 + assert installer.executor.updates_count == 0 + assert installer.executor.removals_count == 0 + + @pytest.mark.parametrize("quiet", [True, False]) def test_run_with_dependencies_quiet( installer: Installer, diff --git a/tests/installation/test_installer_old.py b/tests/installation/test_installer_old.py index 45d3a5c767d..ced7a6fc934 100644 --- a/tests/installation/test_installer_old.py +++ b/tests/installation/test_installer_old.py @@ -62,6 +62,7 @@ def __init__(self, lock_path: Path) -> None: self._lock = lock_path / "poetry.lock" self._written_data = None self._locked = False + self._lock_data = None self._content_hash = self._get_content_hash() @property From bd4c6a6973ab2bccae49514f131b26d4237a311e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sun, 2 Apr 2023 10:20:48 +0200 Subject: [PATCH 131/151] chore: merge changelog from 1.4.2 --- CHANGELOG.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc0f729831a..8cc748df8fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Change Log +## [1.4.2] - 2023-04-02 + +### Changed + +- When trying to install wheels with invalid `RECORD` files, Poetry does not fail anymore but only prints a warning. + This mitigates an unintended change introduced in Poetry 1.4.1 ([#7694](https://github.com/python-poetry/poetry/pull/7694)). + +### Fixed + +- Fix an issue where relative git submodule urls were not parsed correctly ([#7017](https://github.com/python-poetry/poetry/pull/7017)). +- Fix an issue where Poetry could freeze when building a project with a build script if it generated enough output to fill the OS pipe buffer ([#7699](https://github.com/python-poetry/poetry/pull/7699)). + + ## [1.4.1] - 2023-03-19 ### Fixed @@ -1786,7 +1799,8 @@ Initial release -[Unreleased]: https://github.com/python-poetry/poetry/compare/1.4.1...master +[Unreleased]: https://github.com/python-poetry/poetry/compare/1.4.2...master +[1.4.2]: https://github.com/python-poetry/poetry/releases/tag/1.4.2 [1.4.1]: https://github.com/python-poetry/poetry/releases/tag/1.4.1 [1.4.0]: https://github.com/python-poetry/poetry/releases/tag/1.4.0 [1.3.2]: https://github.com/python-poetry/poetry/releases/tag/1.3.2 From 860c83872b7d93a25acfb331ccf1d00ebf2e302a Mon Sep 17 00:00:00 2001 From: David Hotham Date: Thu, 6 Apr 2023 16:23:57 +0100 Subject: [PATCH 132/151] Avoid resource warning false positive in unit test (#7769) --- tests/console/commands/test_add.py | 3 +-- tests/console/commands/test_build.py | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/console/commands/test_add.py b/tests/console/commands/test_add.py index 5c2a1bafe1f..516934c4b32 100644 --- a/tests/console/commands/test_add.py +++ b/tests/console/commands/test_add.py @@ -52,8 +52,7 @@ def tester(command_tester_factory: CommandTesterFactory) -> CommandTester: @pytest.fixture() def old_tester(tester: CommandTester) -> CommandTester: - with pytest.warns(DeprecationWarning): - tester.command.installer.use_executor(False) + tester.command.installer._use_executor = False return tester diff --git a/tests/console/commands/test_build.py b/tests/console/commands/test_build.py index 6bb71aace71..2d7ed87007a 100644 --- a/tests/console/commands/test_build.py +++ b/tests/console/commands/test_build.py @@ -39,6 +39,8 @@ def test_build_with_multiple_readme_files( assert wheel_file.exists() assert wheel_file.stat().st_size > 0 - sdist_content = tarfile.open(sdist_file).getnames() + with tarfile.open(sdist_file) as tf: + sdist_content = tf.getnames() + assert "my_package-0.1/README-1.rst" in sdist_content assert "my_package-0.1/README-2.rst" in sdist_content From f6e1f93691eb4b0292ac0a54b659b5516084bd11 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sat, 8 Apr 2023 13:04:38 +0100 Subject: [PATCH 133/151] handle importlib-metadata deprecation (#7774) --- src/poetry/repositories/installed_repository.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/poetry/repositories/installed_repository.py b/src/poetry/repositories/installed_repository.py index 2a3496ac5ba..d8ff78c8455 100644 --- a/src/poetry/repositories/installed_repository.py +++ b/src/poetry/repositories/installed_repository.py @@ -257,9 +257,8 @@ def load(cls, env: Env, with_dependencies: bool = False) -> InstalledRepository: if path in skipped: continue - try: - name = canonicalize_name(distribution.metadata["name"]) - except TypeError: + name = distribution.metadata.get("name") # type: ignore[attr-defined] + if name is None: logger.warning( ( "Project environment contains an invalid distribution" @@ -271,6 +270,8 @@ def load(cls, env: Env, with_dependencies: bool = False) -> InstalledRepository: skipped.add(path) continue + name = canonicalize_name(name) + if name in seen: continue From dfb4904813220566de31fd061e117bf916044841 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sat, 8 Apr 2023 14:07:43 +0100 Subject: [PATCH 134/151] use shutil.which() to detect the active python (#7771) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Randy Döring <30527984+radoering@users.noreply.github.com> --- src/poetry/utils/env.py | 72 ++++++++++++--------- tests/console/commands/env/helpers.py | 8 ++- tests/console/commands/env/test_use.py | 5 ++ tests/utils/test_env.py | 90 ++++++++++++++++++++++++-- 4 files changed, 139 insertions(+), 36 deletions(-) diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index d5a96fac4c2..91a35f212e7 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -9,6 +9,7 @@ import platform import plistlib import re +import shutil import subprocess import sys import sysconfig @@ -472,6 +473,11 @@ def __init__(self, e: CalledProcessError, input: str | None = None) -> None: super().__init__("\n\n".join(message_parts)) +class PythonVersionNotFound(EnvError): + def __init__(self, expected: str) -> None: + super().__init__(f"Could not find the python executable {expected}") + + class NoCompatiblePythonVersionFound(EnvError): def __init__(self, expected: str, given: str | None = None) -> None: if given: @@ -517,34 +523,39 @@ def __init__(self, poetry: Poetry, io: None | IO = None) -> None: self._io = io or NullIO() @staticmethod - def _full_python_path(python: str) -> Path: + def _full_python_path(python: str) -> Path | None: + # eg first find pythonXY.bat on windows. + path_python = shutil.which(python) + if path_python is None: + return None + try: executable = decode( subprocess.check_output( - [python, "-c", "import sys; print(sys.executable)"], + [path_python, "-c", "import sys; print(sys.executable)"], ).strip() ) - except CalledProcessError as e: - raise EnvCommandError(e) + return Path(executable) - return Path(executable) + except CalledProcessError: + return None @staticmethod def _detect_active_python(io: None | IO = None) -> Path | None: io = io or NullIO() - executable = None + io.write_error_line( + ( + "Trying to detect current active python executable as specified in" + " the config." + ), + verbosity=Verbosity.VERBOSE, + ) - try: - io.write_error_line( - ( - "Trying to detect current active python executable as specified in" - " the config." - ), - verbosity=Verbosity.VERBOSE, - ) - executable = EnvManager._full_python_path("python") + executable = EnvManager._full_python_path("python") + + if executable is not None: io.write_error_line(f"Found: {executable}", verbosity=Verbosity.VERBOSE) - except EnvCommandError: + else: io.write_error_line( ( "Unable to detect the current active python executable. Falling" @@ -552,6 +563,7 @@ def _detect_active_python(io: None | IO = None) -> Path | None: ), verbosity=Verbosity.VERBOSE, ) + return executable @staticmethod @@ -592,6 +604,8 @@ def activate(self, python: str) -> Env: pass python_path = self._full_python_path(python) + if python_path is None: + raise PythonVersionNotFound(python) try: python_version_string = decode( @@ -949,25 +963,26 @@ def create_venv( "Trying to find and use a compatible version. " ) - for python_to_try in sorted( + for suffix in sorted( self._poetry.package.AVAILABLE_PYTHONS, key=lambda v: (v.startswith("3"), -len(v), v), reverse=True, ): - if len(python_to_try) == 1: - if not parse_constraint(f"^{python_to_try}.0").allows_any( + if len(suffix) == 1: + if not parse_constraint(f"^{suffix}.0").allows_any( supported_python ): continue - elif not supported_python.allows_any( - parse_constraint(python_to_try + ".*") - ): + elif not supported_python.allows_any(parse_constraint(suffix + ".*")): continue - python = "python" + python_to_try - + python_name = f"python{suffix}" if self._io.is_debug(): - self._io.write_error_line(f"Trying {python}") + self._io.write_error_line(f"Trying {python_name}") + + python = self._full_python_path(python_name) + if python is None: + continue try: python_patch = decode( @@ -979,14 +994,11 @@ def create_venv( except CalledProcessError: continue - if not python_patch: - continue - if supported_python.allows(Version.parse(python_patch)): self._io.write_error_line( - f"Using {python} ({python_patch})" + f"Using {python_name} ({python_patch})" ) - executable = self._full_python_path(python) + executable = python python_minor = ".".join(python_patch.split(".")[:2]) break diff --git a/tests/console/commands/env/helpers.py b/tests/console/commands/env/helpers.py index 0a067b3c430..942c27243d4 100644 --- a/tests/console/commands/env/helpers.py +++ b/tests/console/commands/env/helpers.py @@ -1,5 +1,7 @@ from __future__ import annotations +import os + from pathlib import Path from typing import TYPE_CHECKING from typing import Any @@ -28,9 +30,11 @@ def check_output(cmd: list[str], *args: Any, **kwargs: Any) -> str: elif "sys.version_info[:2]" in python_cmd: return f"{version.major}.{version.minor}" elif "import sys; print(sys.executable)" in python_cmd: - return f"/usr/bin/{cmd[0]}" + executable = cmd[0] + basename = os.path.basename(executable) + return f"/usr/bin/{basename}" else: assert "import sys; print(sys.prefix)" in python_cmd - return str(Path("/prefix")) + return "/prefix" return check_output diff --git a/tests/console/commands/env/test_use.py b/tests/console/commands/env/test_use.py index 72df646d97c..d0abd38f8c4 100644 --- a/tests/console/commands/env/test_use.py +++ b/tests/console/commands/env/test_use.py @@ -56,6 +56,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( venv_name: str, venvs_in_cache_config: None, ) -> None: + mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(), @@ -94,6 +95,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( def test_get_prefers_explicitly_activated_virtualenvs_over_env_var( + mocker: MockerFixture, tester: CommandTester, current_python: tuple[int, int, int], venv_cache: Path, @@ -112,6 +114,8 @@ def test_get_prefers_explicitly_activated_virtualenvs_over_env_var( doc[venv_name] = {"minor": python_minor, "patch": python_patch} envs_file.write(doc) + mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") + tester.execute(python_minor) expected = f"""\ @@ -134,6 +138,7 @@ def test_get_prefers_explicitly_activated_non_existing_virtualenvs_over_env_var( python_minor = ".".join(str(v) for v in current_python[:2]) venv_dir = venv_cache / f"{venv_name}-py{python_minor}" + mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( "poetry.utils.env.EnvManager._env", new_callable=mocker.PropertyMock, diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index b41d22e6b53..5e1fb3cdbb9 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -27,6 +27,7 @@ from poetry.utils.env import InvalidCurrentPythonVersionError from poetry.utils.env import MockEnv from poetry.utils.env import NoCompatiblePythonVersionFound +from poetry.utils.env import PythonVersionNotFound from poetry.utils.env import SystemEnv from poetry.utils.env import VirtualEnv from poetry.utils.env import build_environment @@ -197,10 +198,12 @@ def check_output(cmd: list[str], *args: Any, **kwargs: Any) -> str: elif "sys.version_info[:2]" in python_cmd: return f"{version.major}.{version.minor}" elif "import sys; print(sys.executable)" in python_cmd: - return f"/usr/bin/{cmd[0]}" + executable = cmd[0] + basename = os.path.basename(executable) + return f"/usr/bin/{basename}" else: assert "import sys; print(sys.prefix)" in python_cmd - return str(Path("/prefix")) + return "/prefix" return check_output @@ -218,6 +221,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(), @@ -252,6 +256,30 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( assert env.base == Path("/prefix") +def test_activate_fails_when_python_cannot_be_found( + tmp_dir: str, + manager: EnvManager, + poetry: Poetry, + config: Config, + mocker: MockerFixture, + venv_name: str, +) -> None: + if "VIRTUAL_ENV" in os.environ: + del os.environ["VIRTUAL_ENV"] + + os.mkdir(os.path.join(tmp_dir, f"{venv_name}-py3.7")) + + config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + + mocker.patch("shutil.which", return_value=None) + + with pytest.raises(PythonVersionNotFound) as e: + manager.activate("python3.7") + + expected_message = "Could not find the python executable python3.7" + assert str(e.value) == expected_message + + def test_activate_activates_existing_virtualenv_no_envs_file( tmp_dir: str, manager: EnvManager, @@ -267,6 +295,7 @@ def test_activate_activates_existing_virtualenv_no_envs_file( config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(), @@ -311,6 +340,7 @@ def test_activate_activates_same_virtualenv_with_envs_file( config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(), @@ -354,6 +384,7 @@ def test_activate_activates_different_virtualenv_with_envs_file( config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), @@ -407,6 +438,7 @@ def test_activate_activates_recreates_for_different_patch( config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(), @@ -474,6 +506,7 @@ def test_activate_does_not_recreate_when_switching_minor( config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), @@ -1070,6 +1103,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ poetry.package.python_versions = "^3.6" mocker.patch("sys.version_info", (2, 7, 16)) + mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.7.5")), @@ -1093,6 +1127,34 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ ) +def test_create_venv_finds_no_python_executable( + manager: EnvManager, + poetry: Poetry, + config: Config, + mocker: MockerFixture, + config_virtualenvs_path: Path, + venv_name: str, +) -> None: + if "VIRTUAL_ENV" in os.environ: + del os.environ["VIRTUAL_ENV"] + + poetry.package.python_versions = "^3.6" + + mocker.patch("sys.version_info", (2, 7, 16)) + mocker.patch("shutil.which", return_value=None) + + with pytest.raises(NoCompatiblePythonVersionFound) as e: + manager.create_venv() + + expected_message = ( + "Poetry was unable to find a compatible version. " + "If you have one, you can explicitly use it " + 'via the "env use" command.' + ) + + assert str(e.value) == expected_message + + def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific_ones( manager: EnvManager, poetry: Poetry, @@ -1107,8 +1169,10 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific poetry.package.python_versions = "^3.6" mocker.patch("sys.version_info", (2, 7, 16)) + mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( - "subprocess.check_output", side_effect=["3.5.3", "3.9.0", "/usr/bin/python3.9"] + "subprocess.check_output", + side_effect=["/usr/bin/python3", "3.5.3", "/usr/bin/python3.9", "3.9.0"], ) m = mocker.patch( "poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: "" @@ -1309,6 +1373,7 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( } ) + mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(), @@ -1546,13 +1611,15 @@ def test_create_venv_accepts_fallback_version_w_nonzero_patchlevel( def mock_check_output(cmd: str, *args: Any, **kwargs: Any) -> str: if GET_PYTHON_VERSION_ONELINER in cmd: - if "python3.5" in cmd: + executable = cmd[0] + if "python3.5" in str(executable): return "3.5.12" else: return "3.7.1" else: return "/usr/bin/python3.5" + mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") check_output = mocker.patch( "subprocess.check_output", side_effect=mock_check_output, @@ -1662,6 +1729,7 @@ def test_create_venv_project_name_empty_sets_correct_prompt( venv_name = manager.generate_env_name("", str(poetry.file.parent)) mocker.patch("sys.version_info", (2, 7, 16)) + mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.7.5")), @@ -1697,3 +1765,17 @@ def test_fallback_on_detect_active_python( assert active_python is None assert m.call_count == 1 + + +@pytest.mark.skipif(sys.platform != "win32", reason="Windows only") +def test_detect_active_python_with_bat(poetry: Poetry, tmp_path: Path) -> None: + """On Windows pyenv uses batch files for python management.""" + python_wrapper = tmp_path / "python.bat" + wrapped_python = Path(r"C:\SpecialPython\python.exe") + with python_wrapper.open("w") as f: + f.write(f"@echo {wrapped_python}") + os.environ["PATH"] = str(python_wrapper.parent) + os.pathsep + os.environ["PATH"] + + active_python = EnvManager(poetry)._detect_active_python() + + assert active_python == wrapped_python From fd706c8c12a7e7392a92dce0b6767a1d5ba13cb1 Mon Sep 17 00:00:00 2001 From: Maxim Koltsov Date: Fri, 16 Dec 2022 15:00:24 +0300 Subject: [PATCH 135/151] Cache git dependencies as wheels (#7473) Currently, poetry install will clone, build and install every git dependency when it's not present in the environment. This is OK for developer's machines, but not OK for CI - there environment is always fresh, and installing git dependencies takes significant time on each CI run, especially if the dependency has C extensions that need to be built. This commit builds a wheel for every git dependency that has precise reference hash in lock file and is not required to be in editable mode, stores that wheel in a cache dir and will install from it instead of cloning the repository again. --- src/poetry/installation/chef.py | 4 +- src/poetry/installation/executor.py | 39 +++++++++-- src/poetry/utils/cache.py | 45 ++++++++++-- tests/installation/test_executor.py | 103 ++++++++++++++++++++++++++-- tests/utils/test_cache.py | 45 ++++++++---- 5 files changed, 205 insertions(+), 31 deletions(-) diff --git a/src/poetry/installation/chef.py b/src/poetry/installation/chef.py index 5f57ba3e27b..e62b0b6f422 100644 --- a/src/poetry/installation/chef.py +++ b/src/poetry/installation/chef.py @@ -94,8 +94,8 @@ def prepare( return archive if archive.is_dir(): - tmp_dir = tempfile.mkdtemp(prefix="poetry-chef-") - return self._prepare(archive, Path(tmp_dir), editable=editable) + destination = output_dir or Path(tempfile.mkdtemp(prefix="poetry-chef-")) + return self._prepare(archive, destination=destination, editable=editable) return self._prepare_sdist(archive, destination=output_dir) diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index fc3efe37059..a22d21e16b2 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -529,7 +529,7 @@ def _install(self, operation: Install | Update) -> int: cleanup_archive: bool = False if package.source_type == "git": archive = self._prepare_git_archive(operation) - cleanup_archive = True + cleanup_archive = operation.package.develop elif package.source_type == "file": archive = self._prepare_archive(operation) elif package.source_type == "directory": @@ -584,7 +584,9 @@ def _remove(self, package: Package) -> int: raise - def _prepare_archive(self, operation: Install | Update) -> Path: + def _prepare_archive( + self, operation: Install | Update, *, output_dir: Path | None = None + ) -> Path: package = operation.package operation_message = self.get_operation_message(operation) @@ -603,12 +605,28 @@ def _prepare_archive(self, operation: Install | Update) -> Path: self._populate_hashes_dict(archive, package) - return self._chef.prepare(archive, editable=package.develop) + return self._chef.prepare( + archive, editable=package.develop, output_dir=output_dir + ) def _prepare_git_archive(self, operation: Install | Update) -> Path: from poetry.vcs.git import Git package = operation.package + assert package.source_url is not None + + if package.source_resolved_reference and not package.develop: + # Only cache git archives when we know precise reference hash, + # otherwise we might get stale archives + cached_archive = self._artifact_cache.get_cached_archive_for_git( + package.source_url, + package.source_resolved_reference, + package.source_subdirectory, + env=self._env, + ) + if cached_archive is not None: + return cached_archive + operation_message = self.get_operation_message(operation) message = ( @@ -616,7 +634,6 @@ def _prepare_git_archive(self, operation: Install | Update) -> Path: ) self._write(operation, message) - assert package.source_url is not None source = Git.clone( url=package.source_url, source_root=self._env.path / "src", @@ -627,10 +644,22 @@ def _prepare_git_archive(self, operation: Install | Update) -> Path: original_url = package.source_url package._source_url = str(source.path) - archive = self._prepare_archive(operation) + output_dir = None + if package.source_resolved_reference and not package.develop: + output_dir = self._artifact_cache.get_cache_directory_for_git( + original_url, + package.source_resolved_reference, + package.source_subdirectory, + ) + archive = self._prepare_archive(operation, output_dir=output_dir) package._source_url = original_url + if output_dir is not None and output_dir.is_dir(): + # Mark directories with cached git packages, to distinguish from + # "normal" cache + (output_dir / ".created_from_git_dependency").touch() + return archive def _install_directory_without_wheel_installer( diff --git a/src/poetry/utils/cache.py b/src/poetry/utils/cache.py index d0d07fb113f..6f2220556ea 100644 --- a/src/poetry/utils/cache.py +++ b/src/poetry/utils/cache.py @@ -231,6 +231,9 @@ def get_cache_directory_for_link(self, link: Link) -> Path: if link.subdirectory_fragment: key_parts["subdirectory"] = link.subdirectory_fragment + return self._get_directory_from_hash(key_parts) + + def _get_directory_from_hash(self, key_parts: object) -> Path: key = hashlib.sha256( json.dumps( key_parts, sort_keys=True, separators=(",", ":"), ensure_ascii=True @@ -238,28 +241,60 @@ def get_cache_directory_for_link(self, link: Link) -> Path: ).hexdigest() split_key = [key[:2], key[2:4], key[4:6], key[6:]] - return self._cache_dir.joinpath(*split_key) + def get_cache_directory_for_git( + self, url: str, ref: str, subdirectory: str | None + ) -> Path: + key_parts = {"url": url, "ref": ref} + if subdirectory: + key_parts["subdirectory"] = subdirectory + + return self._get_directory_from_hash(key_parts) + def get_cached_archive_for_link( self, link: Link, *, strict: bool, env: Env | None = None, + ) -> Path | None: + cache_dir = self.get_cache_directory_for_link(link) + + return self._get_cached_archive( + cache_dir, strict=strict, filename=link.filename, env=env + ) + + def get_cached_archive_for_git( + self, url: str, reference: str, subdirectory: str | None, env: Env + ) -> Path | None: + cache_dir = self.get_cache_directory_for_git(url, reference, subdirectory) + + return self._get_cached_archive(cache_dir, strict=False, env=env) + + def _get_cached_archive( + self, + cache_dir: Path, + *, + strict: bool, + filename: str | None = None, + env: Env | None = None, ) -> Path | None: assert strict or env is not None + # implication "strict -> filename should not be None" + assert not strict or filename is not None - archives = self._get_cached_archives_for_link(link) + archives = self._get_cached_archives(cache_dir) if not archives: return None candidates: list[tuple[float | None, Path]] = [] + for archive in archives: if strict: # in strict mode return the original cached archive instead of the # prioritized archive type. - if link.filename == archive.name: + if filename == archive.name: return archive continue @@ -286,9 +321,7 @@ def get_cached_archive_for_link( return min(candidates)[1] - def _get_cached_archives_for_link(self, link: Link) -> list[Path]: - cache_dir = self.get_cache_directory_for_link(link) - + def _get_cached_archives(self, cache_dir: Path) -> list[Path]: archive_types = ["whl", "tar.gz", "tar.bz2", "bz2", "zip"] paths: list[Path] = [] for archive_type in archive_types: diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 98412529977..1401ffec981 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -34,6 +34,7 @@ from poetry.repositories.repository_pool import RepositoryPool from poetry.utils.cache import ArtifactCache from poetry.utils.env import MockEnv +from poetry.vcs.git.backend import Git from tests.repositories.test_pypi_repository import MockRepository @@ -81,7 +82,10 @@ def _prepare( wheel = self._directory_wheels.pop(0) self._directory_wheels.append(wheel) - return wheel + destination.mkdir(parents=True, exist_ok=True) + dst_wheel = destination / wheel.name + shutil.copyfile(wheel, dst_wheel) + return dst_wheel return super()._prepare(directory, destination, editable=editable) @@ -276,8 +280,8 @@ def test_execute_executes_a_batch_of_operations( assert prepare_spy.call_count == 2 assert prepare_spy.call_args_list == [ - mocker.call(chef, mocker.ANY, mocker.ANY, editable=False), - mocker.call(chef, mocker.ANY, mocker.ANY, editable=True), + mocker.call(chef, mocker.ANY, destination=mocker.ANY, editable=False), + mocker.call(chef, mocker.ANY, destination=mocker.ANY, editable=True), ] @@ -675,6 +679,7 @@ def test_executor_should_not_write_pep610_url_references_for_cached_package( executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) verify_installed_distribution(tmp_venv, package) + assert link_cached.exists(), "cached file should not be deleted" def test_executor_should_write_pep610_url_references_for_wheel_files( @@ -707,6 +712,7 @@ def test_executor_should_write_pep610_url_references_for_wheel_files( "url": url.as_uri(), } verify_installed_distribution(tmp_venv, package, expected_url_reference) + assert url.exists(), "source file should not be deleted" def test_executor_should_write_pep610_url_references_for_non_wheel_files( @@ -739,6 +745,7 @@ def test_executor_should_write_pep610_url_references_for_non_wheel_files( "url": url.as_uri(), } verify_installed_distribution(tmp_venv, package, expected_url_reference) + assert url.exists(), "source file should not be deleted" def test_executor_should_write_pep610_url_references_for_directories( @@ -749,6 +756,7 @@ def test_executor_should_write_pep610_url_references_for_directories( io: BufferedIO, wheel: Path, fixture_dir: FixtureDirGetter, + mocker: MockerFixture, ): url = (fixture_dir("git") / "github.com" / "demo" / "demo").resolve() package = Package( @@ -757,6 +765,7 @@ def test_executor_should_write_pep610_url_references_for_directories( chef = Chef(artifact_cache, tmp_venv, Factory.create_pool(config)) chef.set_directory_wheel(wheel) + prepare_spy = mocker.spy(chef, "prepare") executor = Executor(tmp_venv, pool, config, io) executor._chef = chef @@ -764,6 +773,7 @@ def test_executor_should_write_pep610_url_references_for_directories( verify_installed_distribution( tmp_venv, package, {"dir_info": {}, "url": url.as_uri()} ) + assert not prepare_spy.spy_return.exists(), "archive not cleaned up" def test_executor_should_write_pep610_url_references_for_editable_directories( @@ -774,6 +784,7 @@ def test_executor_should_write_pep610_url_references_for_editable_directories( io: BufferedIO, wheel: Path, fixture_dir: FixtureDirGetter, + mocker: MockerFixture, ): url = (fixture_dir("git") / "github.com" / "demo" / "demo").resolve() package = Package( @@ -786,6 +797,7 @@ def test_executor_should_write_pep610_url_references_for_editable_directories( chef = Chef(artifact_cache, tmp_venv, Factory.create_pool(config)) chef.set_directory_wheel(wheel) + prepare_spy = mocker.spy(chef, "prepare") executor = Executor(tmp_venv, pool, config, io) executor._chef = chef @@ -793,6 +805,7 @@ def test_executor_should_write_pep610_url_references_for_editable_directories( verify_installed_distribution( tmp_venv, package, {"dir_info": {"editable": True}, "url": url.as_uri()} ) + assert not prepare_spy.spy_return.exists(), "archive not cleaned up" @pytest.mark.parametrize("is_artifact_cached", [False, True]) @@ -848,6 +861,7 @@ def test_executor_should_write_pep610_url_references_for_wheel_urls( download_spy.assert_called_once_with( mocker.ANY, operation, Link(package.source_url) ) + assert download_spy.spy_return.exists(), "cached file should not be deleted" @pytest.mark.parametrize( @@ -938,10 +952,12 @@ def mock_get_cached_archive_for_link_func(_: Link, *, strict: bool, **__: Any): download_spy.assert_called_once_with( mocker.ANY, operation, Link(package.source_url) ) + assert download_spy.spy_return.exists(), "cached file should not be deleted" else: download_spy.assert_not_called() +@pytest.mark.parametrize("is_artifact_cached", [False, True]) def test_executor_should_write_pep610_url_references_for_git( tmp_venv: VirtualEnv, pool: RepositoryPool, @@ -950,18 +966,33 @@ def test_executor_should_write_pep610_url_references_for_git( io: BufferedIO, mock_file_downloads: None, wheel: Path, + mocker: MockerFixture, + fixture_dir: FixtureDirGetter, + is_artifact_cached: bool, ): + if is_artifact_cached: + link_cached = fixture_dir("distributions") / "demo-0.1.2-py2.py3-none-any.whl" + mocker.patch( + "poetry.installation.executor.ArtifactCache.get_cached_archive_for_git", + return_value=link_cached, + ) + clone_spy = mocker.spy(Git, "clone") + + source_resolved_reference = "123456" + source_url = "https://github.com/demo/demo.git" + package = Package( "demo", "0.1.2", source_type="git", source_reference="master", - source_resolved_reference="123456", - source_url="https://github.com/demo/demo.git", + source_resolved_reference=source_resolved_reference, + source_url=source_url, ) chef = Chef(artifact_cache, tmp_venv, Factory.create_pool(config)) chef.set_directory_wheel(wheel) + prepare_spy = mocker.spy(chef, "prepare") executor = Executor(tmp_venv, pool, config, io) executor._chef = chef @@ -979,6 +1010,68 @@ def test_executor_should_write_pep610_url_references_for_git( }, ) + if is_artifact_cached: + clone_spy.assert_not_called() + prepare_spy.assert_not_called() + else: + clone_spy.assert_called_once_with( + url=source_url, source_root=mocker.ANY, revision=source_resolved_reference + ) + prepare_spy.assert_called_once() + assert prepare_spy.spy_return.exists(), "cached file should not be deleted" + assert (prepare_spy.spy_return.parent / ".created_from_git_dependency").exists() + + +def test_executor_should_write_pep610_url_references_for_editable_git( + tmp_venv: VirtualEnv, + pool: RepositoryPool, + config: Config, + artifact_cache: ArtifactCache, + io: BufferedIO, + mock_file_downloads: None, + wheel: Path, + mocker: MockerFixture, + fixture_dir: FixtureDirGetter, +): + source_resolved_reference = "123456" + source_url = "https://github.com/demo/demo.git" + + package = Package( + "demo", + "0.1.2", + source_type="git", + source_reference="master", + source_resolved_reference=source_resolved_reference, + source_url=source_url, + develop=True, + ) + + chef = Chef(artifact_cache, tmp_venv, Factory.create_pool(config)) + chef.set_directory_wheel(wheel) + prepare_spy = mocker.spy(chef, "prepare") + cache_spy = mocker.spy(artifact_cache, "get_cached_archive_for_git") + + executor = Executor(tmp_venv, pool, config, io) + executor._chef = chef + executor.execute([Install(package)]) + verify_installed_distribution( + tmp_venv, + package, + { + "vcs_info": { + "vcs": "git", + "requested_revision": "master", + "commit_id": "123456", + }, + "url": package.source_url, + }, + ) + + cache_spy.assert_not_called() + prepare_spy.assert_called_once() + assert not prepare_spy.spy_return.exists(), "editable git should not be cached" + assert not (prepare_spy.spy_return.parent / ".created_from_git_dependency").exists() + def test_executor_should_append_subdirectory_for_git( mocker: MockerFixture, diff --git a/tests/utils/test_cache.py b/tests/utils/test_cache.py index c2cd47f6d68..3475ab25cb2 100644 --- a/tests/utils/test_cache.py +++ b/tests/utils/test_cache.py @@ -270,20 +270,32 @@ def test_get_cache_directory_for_link(tmp_path: Path) -> None: assert directory == expected -def test_get_cached_archives_for_link( - fixture_dir: FixtureDirGetter, mocker: MockerFixture -) -> None: +@pytest.mark.parametrize("subdirectory", [None, "subdir"]) +def test_get_cache_directory_for_git(tmp_path: Path, subdirectory: str | None) -> None: + cache = ArtifactCache(cache_dir=tmp_path) + directory = cache.get_cache_directory_for_git( + url="https://github.com/demo/demo.git", ref="123456", subdirectory=subdirectory + ) + + if subdirectory: + expected = Path( + f"{tmp_path.as_posix()}/53/08/33/" + "7851e5806669aa15ab0c555b13bd5523978057323c6a23a9cee18ec51c" + ) + else: + expected = Path( + f"{tmp_path.as_posix()}/61/14/30/" + "7c57f8fd71e4eee40b18893b9b586cba45177f15e300f4fb8b14ccc933" + ) + + assert directory == expected + + +def test_get_cached_archives(fixture_dir: FixtureDirGetter) -> None: distributions = fixture_dir("distributions") cache = ArtifactCache(cache_dir=Path()) - mocker.patch.object( - cache, - "get_cache_directory_for_link", - return_value=distributions, - ) - archives = cache._get_cached_archives_for_link( - Link("https://files.python-poetry.org/demo-0.1.0.tar.gz") - ) + archives = cache._get_cached_archives(distributions) assert archives assert set(archives) == set(distributions.glob("*.whl")) | set( @@ -328,7 +340,7 @@ def test_get_not_found_cached_archive_for_link( mocker.patch.object( cache, - "_get_cached_archives_for_link", + "_get_cached_archives", return_value=available_packages, ) @@ -380,7 +392,7 @@ def test_get_found_cached_archive_for_link( mocker.patch.object( cache, - "_get_cached_archives_for_link", + "_get_cached_archives", return_value=[ Path("/cache/demo-0.1.0-py2.py3-none-any"), Path("/cache/demo-0.1.0.tar.gz"), @@ -392,3 +404,10 @@ def test_get_found_cached_archive_for_link( archive = cache.get_cached_archive_for_link(Link(link), strict=strict, env=env) assert Path(cached) == archive + + +def test_get_cached_archive_for_git() -> None: + """Smoke test that checks that no assertion is raised.""" + cache = ArtifactCache(cache_dir=Path()) + archive = cache.get_cached_archive_for_git("url", "ref", "subdirectory", MockEnv()) + assert archive is None From d5f83fffc9c5813a589f7ef928fe31549171954e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sun, 9 Apr 2023 18:17:00 +0200 Subject: [PATCH 136/151] installer: fix content of `direct_url.json` for editable installs from git (#7473) --- src/poetry/installation/executor.py | 9 +++++---- tests/installation/test_executor.py | 8 ++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index a22d21e16b2..d8b658461f0 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -653,7 +653,8 @@ def _prepare_git_archive(self, operation: Install | Update) -> Path: ) archive = self._prepare_archive(operation, output_dir=output_dir) - package._source_url = original_url + if not package.develop: + package._source_url = original_url if output_dir is not None and output_dir.is_dir(): # Mark directories with cached git packages, to distinguish from @@ -893,12 +894,12 @@ def _save_url_reference(self, operation: Operation) -> None: url_reference: dict[str, Any] | None = None - if package.source_type == "git": + if package.source_type == "git" and not package.develop: url_reference = self._create_git_url_reference(package) + elif package.source_type in ("directory", "git"): + url_reference = self._create_directory_url_reference(package) elif package.source_type == "url": url_reference = self._create_url_url_reference(package) - elif package.source_type == "directory": - url_reference = self._create_directory_url_reference(package) elif package.source_type == "file": url_reference = self._create_file_url_reference(package) diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 1401ffec981..0ac7f78e2cd 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -1058,12 +1058,8 @@ def test_executor_should_write_pep610_url_references_for_editable_git( tmp_venv, package, { - "vcs_info": { - "vcs": "git", - "requested_revision": "master", - "commit_id": "123456", - }, - "url": package.source_url, + "dir_info": {"editable": True}, + "url": Path(package.source_url).as_uri(), }, ) From c5a711159fc00d3408884877d61a72ff0bd0a53e Mon Sep 17 00:00:00 2001 From: David Hotham Date: Mon, 10 Apr 2023 12:49:00 +0100 Subject: [PATCH 137/151] safe decoding of build backend errors (#7781) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Randy Döring <30527984+radoering@users.noreply.github.com> --- src/poetry/installation/chef.py | 5 +-- src/poetry/utils/cache.py | 39 ++--------------------- tests/installation/test_executor.py | 48 +++++++++++++++++++++++++---- 3 files changed, 47 insertions(+), 45 deletions(-) diff --git a/src/poetry/installation/chef.py b/src/poetry/installation/chef.py index e62b0b6f422..5ea53476c25 100644 --- a/src/poetry/installation/chef.py +++ b/src/poetry/installation/chef.py @@ -17,6 +17,7 @@ from poetry.core.utils.helpers import temporary_directory from pyproject_hooks import quiet_subprocess_runner # type: ignore[import] +from poetry.utils._compat import decode from poetry.utils.env import ephemeral_environment @@ -135,9 +136,9 @@ def _prepare( e.exception.stdout is not None or e.exception.stderr is not None ): message_parts.append( - e.exception.stderr.decode() + decode(e.exception.stderr) if e.exception.stderr is not None - else e.exception.stdout.decode() + else decode(e.exception.stdout) ) error = ChefBuildError("\n\n".join(message_parts)) diff --git a/src/poetry/utils/cache.py b/src/poetry/utils/cache.py index 6f2220556ea..0b8c9e4d611 100644 --- a/src/poetry/utils/cache.py +++ b/src/poetry/utils/cache.py @@ -1,6 +1,5 @@ from __future__ import annotations -import contextlib import dataclasses import hashlib import json @@ -15,6 +14,8 @@ from typing import Generic from typing import TypeVar +from poetry.utils._compat import decode +from poetry.utils._compat import encode from poetry.utils.wheel import InvalidWheelName from poetry.utils.wheel import Wheel @@ -32,42 +33,6 @@ logger = logging.getLogger(__name__) -def decode(string: bytes, encodings: list[str] | None = None) -> str: - """ - Compatiblity decode function pulled from cachy. - - :param string: The byte string to decode. - :param encodings: List of encodings to apply - :return: Decoded string - """ - if encodings is None: - encodings = ["utf-8", "latin1", "ascii"] - - for encoding in encodings: - with contextlib.suppress(UnicodeDecodeError): - return string.decode(encoding) - - return string.decode(encodings[0], errors="ignore") - - -def encode(string: str, encodings: list[str] | None = None) -> bytes: - """ - Compatibility encode function from cachy. - - :param string: The string to encode. - :param encodings: List of encodings to apply - :return: Encoded byte string - """ - if encodings is None: - encodings = ["utf-8", "latin1", "ascii"] - - for encoding in encodings: - with contextlib.suppress(UnicodeDecodeError): - return string.encode(encoding) - - return string.encode(encodings[0], errors="ignore") - - def _expiration(minutes: int) -> int: """ Calculates the time in seconds since epoch that occurs 'minutes' from now. diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 0ac7f78e2cd..05e2cdc648f 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -1240,7 +1240,6 @@ def test_build_backend_errors_are_reported_correctly_if_caused_by_subprocess( config: Config, pool: RepositoryPool, io: BufferedIO, - tmp_dir: str, mock_file_downloads: None, env: MockEnv, fixture_dir: FixtureDirGetter, @@ -1265,11 +1264,7 @@ def test_build_backend_errors_are_reported_correctly_if_caused_by_subprocess( # must not be included in the error message directory_package.python_versions = ">=3.7" - return_code = executor.execute( - [ - Install(directory_package), - ] - ) + return_code = executor.execute([Install(directory_package)]) assert return_code == 1 @@ -1306,6 +1301,47 @@ def test_build_backend_errors_are_reported_correctly_if_caused_by_subprocess( assert output.endswith(expected_end) +@pytest.mark.parametrize("encoding", ["utf-8", "latin-1"]) +@pytest.mark.parametrize("stderr", [None, "Errör on stderr"]) +def test_build_backend_errors_are_reported_correctly_if_caused_by_subprocess_encoding( + encoding: str, + stderr: str | None, + mocker: MockerFixture, + config: Config, + pool: RepositoryPool, + io: BufferedIO, + mock_file_downloads: None, + env: MockEnv, + fixture_dir: FixtureDirGetter, +) -> None: + """Test that the output of the subprocess is decoded correctly.""" + stdout = "Errör on stdout" + error = BuildBackendException( + CalledProcessError( + 1, + ["pip"], + output=stdout.encode(encoding), + stderr=stderr.encode(encoding) if stderr else None, + ) + ) + mocker.patch.object(ProjectBuilder, "get_requires_for_build", side_effect=error) + io.set_verbosity(Verbosity.NORMAL) + + executor = Executor(env, pool, config, io) + + directory_package = Package( + "simple-project", + "1.2.3", + source_type="directory", + source_url=fixture_dir("simple_project").resolve().as_posix(), + ) + + return_code = executor.execute([Install(directory_package)]) + + assert return_code == 1 + assert (stderr or stdout) in io.fetch_output() + + def test_build_system_requires_not_available( config: Config, pool: RepositoryPool, From d2e6ad6e456c6688c1c4be691aaa5e230644ccd6 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Mon, 10 Apr 2023 13:06:25 +0100 Subject: [PATCH 138/151] avoid infinite loop when adding a dependency (#7405) --- src/poetry/mixology/version_solver.py | 4 +- tests/conftest.py | 13 ++- tests/console/commands/test_add.py | 33 +++++++ .../with_path_dependency/bazz/setup.py | 12 +++ .../fixtures/with_path_dependency/poetry.lock | 98 +++++++++++++++++++ .../with_path_dependency/pyproject.toml | 15 +++ tests/types.py | 1 + 7 files changed, 170 insertions(+), 6 deletions(-) create mode 100644 tests/fixtures/with_path_dependency/bazz/setup.py create mode 100644 tests/fixtures/with_path_dependency/poetry.lock create mode 100644 tests/fixtures/with_path_dependency/pyproject.toml diff --git a/src/poetry/mixology/version_solver.py b/src/poetry/mixology/version_solver.py index 6c66dc4c5e9..04ba762d963 100644 --- a/src/poetry/mixology/version_solver.py +++ b/src/poetry/mixology/version_solver.py @@ -331,7 +331,9 @@ def _resolve_conflict(self, incompatibility: Incompatibility) -> Incompatibility # .. _algorithm documentation: # https://github.com/dart-lang/pub/tree/master/doc/solver.md#conflict-resolution # noqa: E501 if difference is not None: - new_terms.append(difference.inverse) + inverse = difference.inverse + if inverse.dependency != most_recent_satisfier.dependency: + new_terms.append(inverse) incompatibility = Incompatibility( new_terms, ConflictCause(incompatibility, most_recent_satisfier.cause) diff --git a/tests/conftest.py b/tests/conftest.py index 47c11049479..5094c97b667 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -378,6 +378,7 @@ def _factory( install_deps: bool = True, source: Path | None = None, locker_config: dict[str, Any] | None = None, + use_test_locker: bool = True, ) -> Poetry: project_dir = workspace / f"poetry-fixture-{name}" dependencies = dependencies or {} @@ -412,12 +413,14 @@ def _factory( poetry = Factory().create_poetry(project_dir) - locker = TestLocker( - poetry.locker.lock, locker_config or poetry.locker._local_config - ) - locker.write() + if use_test_locker: + locker = TestLocker( + poetry.locker.lock, locker_config or poetry.locker._local_config + ) + locker.write() + + poetry.set_locker(locker) - poetry.set_locker(locker) poetry.set_config(config) pool = RepositoryPool() diff --git a/tests/console/commands/test_add.py b/tests/console/commands/test_add.py index 516934c4b32..706aa637edc 100644 --- a/tests/console/commands/test_add.py +++ b/tests/console/commands/test_add.py @@ -10,6 +10,7 @@ from poetry.core.constraints.version import Version from poetry.core.packages.package import Package +from poetry.puzzle.exceptions import SolverProblemError from poetry.repositories.legacy_repository import LegacyRepository from tests.helpers import get_dependency from tests.helpers import get_package @@ -45,6 +46,20 @@ def poetry_with_up_to_date_lockfile( ) +@pytest.fixture +def poetry_with_path_dependency( + project_factory: ProjectFactory, fixture_dir: FixtureDirGetter +) -> Poetry: + source = fixture_dir("with_path_dependency") + + poetry = project_factory( + name="foobar", + source=source, + use_test_locker=False, + ) + return poetry + + @pytest.fixture() def tester(command_tester_factory: CommandTesterFactory) -> CommandTester: return command_tester_factory("add") @@ -2238,3 +2253,21 @@ def error(_: Any) -> int: assert poetry_with_up_to_date_lockfile.file.read() == original_pyproject_content assert poetry_with_up_to_date_lockfile.locker.lock_data == original_lockfile_content assert tester.io.fetch_output() == expected + + +def test_add_with_path_dependency_no_loopiness( + poetry_with_path_dependency: Poetry, + repo: TestRepository, + command_tester_factory: CommandTesterFactory, +) -> None: + """https://github.com/python-poetry/poetry/issues/7398""" + tester = command_tester_factory("add", poetry=poetry_with_path_dependency) + + requests_old = get_package("requests", "2.25.1") + requests_new = get_package("requests", "2.28.2") + + repo.add_package(requests_old) + repo.add_package(requests_new) + + with pytest.raises(SolverProblemError): + tester.execute("requests") diff --git a/tests/fixtures/with_path_dependency/bazz/setup.py b/tests/fixtures/with_path_dependency/bazz/setup.py new file mode 100644 index 00000000000..97223196678 --- /dev/null +++ b/tests/fixtures/with_path_dependency/bazz/setup.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +from distutils.core import setup + + +setup( + name="bazz", + version="1", + py_modules=["demo"], + package_dir={"src": "src"}, + install_requires=["requests~=2.25.1"], +) diff --git a/tests/fixtures/with_path_dependency/poetry.lock b/tests/fixtures/with_path_dependency/poetry.lock new file mode 100644 index 00000000000..3c2e1b9b7c6 --- /dev/null +++ b/tests/fixtures/with_path_dependency/poetry.lock @@ -0,0 +1,98 @@ +# This file is automatically @generated by Poetry 1.4.0.dev0 and should not be changed by hand. + +[[package]] +name = "bazz" +version = "1" +description = "" +category = "main" +optional = false +python-versions = "*" +files = [] +develop = true + +[package.dependencies] +requests = ">=2.25.1,<2.26.0" + +[package.source] +type = "directory" +url = "bazz" + +[[package]] +name = "certifi" +version = "2022.12.7" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] + +[[package]] +name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] + +[[package]] +name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] + +[[package]] +name = "requests" +version = "2.25.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +security = ["cryptography (>=1.3.4)", "pyOpenSSL (>=0.14)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] + +[[package]] +name = "urllib3" +version = "1.26.14" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, + {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "81853eb3be9c7faf1950f23273d0fb3dae75833f6e4aa21b6c3038da25ba26f6" diff --git a/tests/fixtures/with_path_dependency/pyproject.toml b/tests/fixtures/with_path_dependency/pyproject.toml new file mode 100644 index 00000000000..705ba4fe229 --- /dev/null +++ b/tests/fixtures/with_path_dependency/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "foobar" +version = "0.1.0" +description = "" +authors = [] +readme = "README.md" +packages = [{include = "foobar"}] + +[tool.poetry.dependencies] +python = "^3.9" +bazz = { path = "./bazz", develop = true } + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/types.py b/tests/types.py index 95ce4cbc940..c990414f52e 100644 --- a/tests/types.py +++ b/tests/types.py @@ -46,6 +46,7 @@ def __call__( poetry_lock_content: str | None = None, install_deps: bool = True, source: Path | None = None, + use_test_locker: bool = True, ) -> Poetry: ... From 7c9a565992bc442bccb81e473b37bc6264f8dc9b Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 10 Apr 2023 07:21:08 -0500 Subject: [PATCH 139/151] Add option to skip installing directory dependencies (#6845) --- docs/cli.md | 10 ++++++++ docs/faq.md | 34 ++++++++++++++++++++++++++ src/poetry/console/commands/install.py | 11 +++++++++ src/poetry/installation/installer.py | 7 ++++++ src/poetry/puzzle/transaction.py | 11 +++++++-- tests/console/commands/test_install.py | 18 ++++++++++++++ tests/installation/test_installer.py | 27 ++++++++++++++++---- 7 files changed, 111 insertions(+), 7 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index f09ffb97273..9bef8f7a699 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -225,6 +225,14 @@ If you want to skip this installation, use the `--no-root` option. poetry install --no-root ``` +Similar to `--no-root` you can use `--no-directory` to skip directory path dependencies: + +```bash +poetry install --no-directory +``` + +This is mainly useful for caching in CI or when building Docker images. See the [FAQ entry]({{< relref "faq#poetry-busts-my-docker-cache-because-it-requires-me-to-copy-my-source-files-in-before-installing-3rd-party-dependencies" >}}) for more information on this option. + By default `poetry` does not compile Python source files to bytecode during installation. This speeds up the installation process, but the first execution may take a little more time because Python then compiles source files to bytecode automatically. @@ -240,6 +248,7 @@ The `--compile` option has no effect if `installer.modern-installation` is set to `false` because the old installer always compiles source files to bytecode. {{% /note %}} + ### Options * `--without`: The dependency groups to ignore. @@ -248,6 +257,7 @@ is set to `false` because the old installer always compiles source files to byte * `--only-root`: Install only the root project, exclude all dependencies. * `--sync`: Synchronize the environment with the locked packages and the specified groups. * `--no-root`: Do not install the root package (your project). +* `--no-directory`: Skip all directory path dependencies (including transitive ones). * `--dry-run`: Output the operations but do not execute anything (implicitly enables --verbose). * `--extras (-E)`: Features to install (multiple values allowed). * `--all-extras`: Install all extra features (conflicts with --extras). diff --git a/docs/faq.md b/docs/faq.md index 0a8b97b3075..71ac22c1c2d 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -189,3 +189,37 @@ This is done so to be compliant with the broader Python ecosystem. For example, if Poetry builds a distribution for a project that uses a version that is not valid according to [PEP 440](https://peps.python.org/pep-0440), third party tools will be unable to parse the version correctly. + + +### Poetry busts my Docker cache because it requires me to COPY my source files in before installing 3rd party dependencies + +By default running `poetry install ...` requires you to have your source files present (both the "root" package and any directory path dependencies you might have). +This interacts poorly with Docker's caching mechanisms because any change to a source file will make any layers (subsequent commands in your Dockerfile) re-run. +For example, you might have a Dockerfile that looks something like this: + +```text +FROM python +COPY pyproject.toml poetry.lock . +COPY src/ ./src +RUN pip install poetry && poetry install --no-dev +``` + +As soon as *any* source file changes, the cache for the `RUN` layer will be invalidated, which forces all 3rd party dependencies (likely the slowest step out of these) to be installed again if you changed any files in `src/`. + +To avoid this cache busting you can split this into two steps: + +1. Install 3rd party dependencies. +2. Copy over your source code and install just the source code. + +This might look something like this: + +```text +FROM python +COPY pyproject.toml poetry.lock . +RUN pip install poetry && poetry install --no-root --no-directory +COPY src/ ./src +RUN poetry install --no-dev +``` + +The two key options we are using here are `--no-root` (skips installing the project source) and `--no-directory` (skips installing any local directory path dependencies, you can omit this if you don't have any). +[More information on the options available for `poetry install`]({{< relref "cli#install" >}}). diff --git a/src/poetry/console/commands/install.py b/src/poetry/console/commands/install.py index 844ec37176f..90003d5793e 100644 --- a/src/poetry/console/commands/install.py +++ b/src/poetry/console/commands/install.py @@ -30,6 +30,16 @@ class InstallCommand(InstallerCommand): option( "no-root", None, "Do not install the root package (the current project)." ), + option( + "no-directory", + None, + ( + "Do not install any directory path dependencies; useful to install" + " dependencies without source code, e.g. for caching of Docker layers)" + ), + flag=True, + multiple=False, + ), option( "dry-run", None, @@ -148,6 +158,7 @@ def handle(self) -> int: with_synchronization = True self.installer.only_groups(self.activated_groups) + self.installer.skip_directory(self.option("no-directory")) self.installer.dry_run(self.option("dry-run")) self.installer.requires_synchronization(with_synchronization) self.installer.executor.enable_bytecode_compilation(self.option("compile")) diff --git a/src/poetry/installation/installer.py b/src/poetry/installation/installer.py index ec5911ce8f7..1df9a887853 100644 --- a/src/poetry/installation/installer.py +++ b/src/poetry/installation/installer.py @@ -59,6 +59,7 @@ def __init__( self._verbose = False self._write_lock = True self._groups: Iterable[str] | None = None + self._skip_directory = False self._execute_operations = True self._lock = False @@ -150,6 +151,11 @@ def update(self, update: bool = True) -> Installer: return self + def skip_directory(self, skip_directory: bool = False) -> Installer: + self._skip_directory = skip_directory + + return self + def lock(self, update: bool = True) -> Installer: """ Prepare the installer for locking only. @@ -334,6 +340,7 @@ def _do_install(self) -> int: ops = solver.solve(use_latest=self._whitelist).calculate_operations( with_uninstalls=self._requires_synchronization, synchronize=self._requires_synchronization, + skip_directory=self._skip_directory, ) if not self._requires_synchronization: diff --git a/src/poetry/puzzle/transaction.py b/src/poetry/puzzle/transaction.py index 74d0d6c5e61..665093416cb 100644 --- a/src/poetry/puzzle/transaction.py +++ b/src/poetry/puzzle/transaction.py @@ -27,7 +27,11 @@ def __init__( self._root_package = root_package def calculate_operations( - self, with_uninstalls: bool = True, synchronize: bool = False + self, + with_uninstalls: bool = True, + synchronize: bool = False, + *, + skip_directory: bool = False, ) -> list[Operation]: from poetry.installation.operations import Install from poetry.installation.operations import Uninstall @@ -70,7 +74,10 @@ def calculate_operations( break - if not installed: + if not ( + installed + or (skip_directory and result_package.source_type == "directory") + ): operations.append(Install(result_package, priority=priority)) if with_uninstalls: diff --git a/tests/console/commands/test_install.py b/tests/console/commands/test_install.py index f39081872d0..a1e9379de49 100644 --- a/tests/console/commands/test_install.py +++ b/tests/console/commands/test_install.py @@ -184,6 +184,24 @@ def test_compile_option_is_passed_to_the_installer( enable_bytecode_compilation_mock.assert_called_once_with(compile) +@pytest.mark.parametrize("skip_directory_cli_value", [True, False]) +def test_no_directory_is_passed_to_installer( + tester: CommandTester, mocker: MockerFixture, skip_directory_cli_value: bool +): + """ + The --no-directory option is passed to the installer. + """ + + mocker.patch.object(tester.command.installer, "run", return_value=1) + + if skip_directory_cli_value is True: + tester.execute("--no-directory") + else: + tester.execute() + + assert tester.command.installer._skip_directory is skip_directory_cli_value + + def test_no_all_extras_doesnt_populate_installer( tester: CommandTester, mocker: MockerFixture ): diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 400278e7798..4d6e975b45a 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -60,12 +60,12 @@ class Executor(BaseExecutor): def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) - self._installs: list[DependencyPackage] = [] + self._installs: list[Package] = [] self._updates: list[DependencyPackage] = [] self._uninstalls: list[DependencyPackage] = [] @property - def installations(self) -> list[DependencyPackage]: + def installations(self) -> list[Package]: return self._installs @property @@ -1276,14 +1276,18 @@ def test_run_installs_with_local_poetry_directory_and_extras( assert installer.executor.installations_count == 2 -def test_run_installs_with_local_poetry_directory_transitive( +@pytest.mark.parametrize("skip_directory", [True, False]) +def test_run_installs_with_local_poetry_directory_and_skip_directory_flag( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage, - tmpdir: Path, fixture_dir: FixtureDirGetter, + skip_directory: bool, ): + """When we set Installer.skip_directory(True) no path dependencies should + be installed (including transitive dependencies). + """ root_dir = fixture_dir("directory") package.root_dir = root_dir locker.set_lock_path(root_dir) @@ -1299,14 +1303,27 @@ def test_run_installs_with_local_poetry_directory_transitive( repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cachy", "0.2.0")) + installer.skip_directory(skip_directory) + result = installer.run() assert result == 0 + executor: Executor = installer.executor # type: ignore + expected = fixture("with-directory-dependency-poetry-transitive") assert locker.written_data == expected - assert installer.executor.installations_count == 6 + directory_installs = [ + p.name for p in executor.installations if p.source_type == "directory" + ] + + if skip_directory: + assert not directory_installs, directory_installs + assert installer.executor.installations_count == 2 + else: + assert directory_installs, directory_installs + assert installer.executor.installations_count == 6 def test_run_installs_with_local_poetry_file_transitive( From 71754bcd239f3665a2940fbb5ca523982d54960a Mon Sep 17 00:00:00 2001 From: David Hotham Date: Tue, 11 Apr 2023 16:05:33 +0100 Subject: [PATCH 140/151] more information in case of error (#7784) --- src/poetry/inspection/info.py | 2 +- tests/inspection/test_info.py | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/poetry/inspection/info.py b/src/poetry/inspection/info.py index e538a9cc979..10bd7d04ac9 100644 --- a/src/poetry/inspection/info.py +++ b/src/poetry/inspection/info.py @@ -623,7 +623,7 @@ def get_pep517_metadata(path: Path) -> PackageInfo: info = PackageInfo.from_metadata(path) except EnvCommandError as fbe: raise PackageInfoError( - path, "Fallback egg_info generation failed.", fbe + path, e, "Fallback egg_info generation failed.", fbe ) finally: os.chdir(cwd) diff --git a/tests/inspection/test_info.py b/tests/inspection/test_info.py index c789fae8185..cf222059286 100644 --- a/tests/inspection/test_info.py +++ b/tests/inspection/test_info.py @@ -250,12 +250,8 @@ def test_info_setup_missing_mandatory_should_trigger_pep517( setup_py.write_text(decode(setup)) spy = mocker.spy(VirtualEnv, "run") - try: - PackageInfo.from_directory(source_dir) - except PackageInfoError: - assert spy.call_count == 3 - else: - assert spy.call_count == 1 + _ = PackageInfo.from_directory(source_dir) + assert spy.call_count == 1 def test_info_prefer_poetry_config_over_egg_info(): From 5a9da19fa85631f46a5f1edc9e61c2b65255dab2 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Wed, 12 Apr 2023 13:49:11 +0100 Subject: [PATCH 141/151] Tidying (#7779) --- pyproject.toml | 1 - src/poetry/config/config.py | 2 +- src/poetry/console/commands/init.py | 5 +- src/poetry/console/commands/new.py | 5 +- .../console/commands/self/show/plugins.py | 4 +- src/poetry/console/commands/show.py | 5 +- .../console/io/inputs/run_argv_input.py | 7 +- src/poetry/installation/chef.py | 4 +- src/poetry/installation/installer.py | 5 +- src/poetry/installation/wheel_installer.py | 5 +- src/poetry/json/__init__.py | 5 +- src/poetry/layouts/layout.py | 6 +- src/poetry/mixology/version_solver.py | 4 +- src/poetry/plugins/plugin_manager.py | 2 +- src/poetry/puzzle/provider.py | 2 +- src/poetry/puzzle/solver.py | 2 +- src/poetry/repositories/pypi_repository.py | 4 +- src/poetry/utils/_compat.py | 5 -- src/poetry/utils/cache.py | 7 +- src/poetry/utils/env.py | 10 +-- src/poetry/utils/extras.py | 2 +- src/poetry/utils/helpers.py | 8 +- src/poetry/utils/setup_reader.py | 2 +- tests/compat.py | 6 +- tests/conftest.py | 31 +++---- tests/console/commands/source/test_add.py | 12 +-- tests/console/commands/source/test_remove.py | 4 +- tests/console/commands/source/test_show.py | 10 ++- tests/console/commands/test_init.py | 7 +- tests/console/commands/test_show.py | 70 ++++++++------- tests/helpers.py | 2 +- tests/inspection/test_info.py | 86 +++++++++---------- tests/installation/test_chooser.py | 16 ++-- tests/mixology/helpers.py | 8 +- .../version_solver/test_backtracking.py | 18 ++-- .../version_solver/test_basic_graph.py | 8 +- .../version_solver/test_dependency_cache.py | 4 +- .../version_solver/test_python_constraint.py | 2 +- .../version_solver/test_unsolvable.py | 16 ++-- .../mixology/version_solver/test_with_lock.py | 17 ++-- tests/publishing/test_publisher.py | 14 +-- tests/publishing/test_uploader.py | 20 ++--- tests/puzzle/test_provider.py | 12 +-- tests/repositories/test_legacy_repository.py | 6 +- tests/repositories/test_pypi_repository.py | 9 +- tests/types.py | 2 + tests/utils/test_env_site.py | 5 +- 47 files changed, 223 insertions(+), 264 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ef1980f5779..7f87ec3c2ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -169,7 +169,6 @@ enable_error_code = [ # warning. [[tool.mypy.overrides]] module = [ - 'poetry.console.commands.self.show.plugins', 'poetry.plugins.plugin_manager', 'poetry.repositories.installed_repository', 'poetry.utils.env', diff --git a/src/poetry/config/config.py b/src/poetry/config/config.py index 9fb220be675..65aa3100cf3 100644 --- a/src/poetry/config/config.py +++ b/src/poetry/config/config.py @@ -199,7 +199,7 @@ def _get_environment_repositories() -> dict[str, dict[str, str]]: repositories = {} pattern = re.compile(r"POETRY_REPOSITORIES_(?P[A-Z_]+)_URL") - for env_key in os.environ.keys(): + for env_key in os.environ: match = pattern.match(env_key) if match: repositories[match.group("name").lower().replace("_", "-")] = { diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index c77bff07468..9204c487f38 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -149,10 +149,7 @@ def handle(self) -> int: question.set_validator(lambda v: self._validate_author(v, author)) author = self.ask(question) - if not author: - authors = [] - else: - authors = [author] + authors = [author] if author else [] license = self.option("license") if not license: diff --git a/src/poetry/console/commands/new.py b/src/poetry/console/commands/new.py index 492a7258a94..5f896cf80e8 100644 --- a/src/poetry/console/commands/new.py +++ b/src/poetry/console/commands/new.py @@ -39,10 +39,7 @@ def handle(self) -> int: " be ignored. You should consider the option --path instead." ) - if self.option("src"): - layout_cls = layout("src") - else: - layout_cls = layout("standard") + layout_cls = layout("src") if self.option("src") else layout("standard") path = Path(self.argument("path")) if not path.is_absolute(): diff --git a/src/poetry/console/commands/self/show/plugins.py b/src/poetry/console/commands/self/show/plugins.py index fa2653433ed..15c98548a9e 100644 --- a/src/poetry/console/commands/self/show/plugins.py +++ b/src/poetry/console/commands/self/show/plugins.py @@ -25,14 +25,14 @@ def append(self, entry_point: metadata.EntryPoint) -> None: from poetry.plugins.application_plugin import ApplicationPlugin from poetry.plugins.plugin import Plugin - group = entry_point.group # type: ignore[attr-defined] + group = entry_point.group if group == ApplicationPlugin.group: self.application_plugins.append(entry_point) elif group == Plugin.group: self.plugins.append(entry_point) else: - name = entry_point.name # type: ignore[attr-defined] + name = entry_point.name raise ValueError(f"Unknown plugin group ({group}) for {name}") diff --git a/src/poetry/console/commands/show.py b/src/poetry/console/commands/show.py index b0248f7df77..a97c0dc34bc 100644 --- a/src/poetry/console/commands/show.py +++ b/src/poetry/console/commands/show.py @@ -142,10 +142,7 @@ def _display_single_package_information( packages = [pkg] if required_by: packages = [ - p - for p in locked_packages - for r in required_by.keys() - if p.name == r + p for p in locked_packages for r in required_by if p.name == r ] else: # if no rev-deps exist we'll make this clear as it can otherwise diff --git a/src/poetry/console/io/inputs/run_argv_input.py b/src/poetry/console/io/inputs/run_argv_input.py index 964d88c2c17..36735202118 100644 --- a/src/poetry/console/io/inputs/run_argv_input.py +++ b/src/poetry/console/io/inputs/run_argv_input.py @@ -43,12 +43,9 @@ def has_parameter_option( # Options with values: # For long options, test for '--option=' at beginning # For short options, test for '-o' at beginning - if value.find("--") == 0: - leading = value + "=" - else: - leading = value + leading = value + "=" if value.startswith("--") else value - if token == value or leading != "" and token.find(leading) == 0: + if token == value or leading != "" and token.startswith(leading): return True return False diff --git a/src/poetry/installation/chef.py b/src/poetry/installation/chef.py index 5ea53476c25..d9114441191 100644 --- a/src/poetry/installation/chef.py +++ b/src/poetry/installation/chef.py @@ -8,8 +8,6 @@ from io import StringIO from pathlib import Path from typing import TYPE_CHECKING -from typing import Callable -from typing import Collection from build import BuildBackendException from build import ProjectBuilder @@ -22,6 +20,8 @@ if TYPE_CHECKING: + from collections.abc import Callable + from collections.abc import Collection from contextlib import AbstractContextManager from poetry.repositories import RepositoryPool diff --git a/src/poetry/installation/installer.py b/src/poetry/installation/installer.py index 1df9a887853..c049f0da500 100644 --- a/src/poetry/installation/installer.py +++ b/src/poetry/installation/installer.py @@ -554,10 +554,7 @@ def _get_operations_from_lock( def _filter_operations(self, ops: Iterable[Operation], repo: Repository) -> None: extra_packages = self._get_extra_packages(repo) for op in ops: - if isinstance(op, Update): - package = op.target_package - else: - package = op.package + package = op.target_package if isinstance(op, Update) else op.package if op.job_type == "uninstall": continue diff --git a/src/poetry/installation/wheel_installer.py b/src/poetry/installation/wheel_installer.py index eb324fd630c..18e42e9cbd9 100644 --- a/src/poetry/installation/wheel_installer.py +++ b/src/poetry/installation/wheel_installer.py @@ -1,6 +1,5 @@ from __future__ import annotations -import os import platform import sys @@ -51,9 +50,9 @@ def write_to_fs( if not parent_folder.exists(): # Due to the parallel installation it can happen # that two threads try to create the directory. - os.makedirs(parent_folder, exist_ok=True) + parent_folder.mkdir(parents=True, exist_ok=True) - with open(target_path, "wb") as f: + with target_path.open("wb") as f: hash_, size = copyfileobj_with_hashing(stream, f, self.hash_algorithm) if is_executable: diff --git a/src/poetry/json/__init__.py b/src/poetry/json/__init__.py index 1a47e0fab7b..3cabdcf69e4 100644 --- a/src/poetry/json/__init__.py +++ b/src/poetry/json/__init__.py @@ -1,7 +1,6 @@ from __future__ import annotations import json -import os from pathlib import Path from typing import Any @@ -11,7 +10,7 @@ from poetry.core.json import SCHEMA_DIR as CORE_SCHEMA_DIR -SCHEMA_DIR = os.path.join(os.path.dirname(__file__), "schemas") +SCHEMA_DIR = Path(__file__).parent / "schemas" class ValidationError(ValueError): @@ -39,7 +38,7 @@ def validate_object(obj: dict[str, Any]) -> list[str]: errors.append(message) core_schema = json.loads( - Path(CORE_SCHEMA_DIR, "poetry-schema.json").read_text(encoding="utf-8") + (CORE_SCHEMA_DIR / "poetry-schema.json").read_text(encoding="utf-8") ) properties = {*schema["properties"].keys(), *core_schema["properties"].keys()} diff --git a/src/poetry/layouts/layout.py b/src/poetry/layouts/layout.py index 21d4c29234b..45fcb28b6a5 100644 --- a/src/poetry/layouts/layout.py +++ b/src/poetry/layouts/layout.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: - from typing import Mapping + from collections.abc import Mapping from tomlkit.items import InlineTable @@ -49,8 +49,8 @@ def __init__( author: str | None = None, license: str | None = None, python: str = "*", - dependencies: dict[str, str | Mapping[str, Any]] | None = None, - dev_dependencies: dict[str, str | Mapping[str, Any]] | None = None, + dependencies: Mapping[str, str | Mapping[str, Any]] | None = None, + dev_dependencies: Mapping[str, str | Mapping[str, Any]] | None = None, ) -> None: self._project = canonicalize_name(project) self._package_path_relative = Path( diff --git a/src/poetry/mixology/version_solver.py b/src/poetry/mixology/version_solver.py index 04ba762d963..6671c684606 100644 --- a/src/poetry/mixology/version_solver.py +++ b/src/poetry/mixology/version_solver.py @@ -320,8 +320,8 @@ def _resolve_conflict(self, incompatibility: Incompatibility) -> Incompatibility # The most_recent_satisfier may not satisfy most_recent_term on its own # if there are a collection of constraints on most_recent_term that # only satisfy it together. For example, if most_recent_term is - # `foo ^1.0.0` and _solution contains `[foo >=1.0.0, - # foo <2.0.0]`, then most_recent_satisfier will be `foo <2.0.0` even + # `foo ^1.0.0` and _solution contains `[foo >=1.0.0, + # foo <2.0.0]`, then most_recent_satisfier will be `foo <2.0.0` even # though it doesn't totally satisfy `foo ^1.0.0`. # # In this case, we add `not (most_recent_satisfier \ most_recent_term)` to diff --git a/src/poetry/plugins/plugin_manager.py b/src/poetry/plugins/plugin_manager.py index 1a01dc36335..309facb1c39 100644 --- a/src/poetry/plugins/plugin_manager.py +++ b/src/poetry/plugins/plugin_manager.py @@ -71,7 +71,7 @@ def activate(self, *args: Any, **kwargs: Any) -> None: plugin.activate(*args, **kwargs) def _load_plugin_entry_point(self, ep: metadata.EntryPoint) -> None: - logger.debug("Loading the %s plugin", ep.name) # type: ignore[attr-defined] + logger.debug("Loading the %s plugin", ep.name) plugin = ep.load() # type: ignore[no-untyped-call] diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index ed88b57d977..28558ab4607 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -12,7 +12,6 @@ from contextlib import contextmanager from pathlib import Path from typing import TYPE_CHECKING -from typing import Collection from typing import cast from cleo.ui.progress_indicator import ProgressIndicator @@ -40,6 +39,7 @@ if TYPE_CHECKING: from collections.abc import Callable + from collections.abc import Collection from collections.abc import Iterable from collections.abc import Iterator diff --git a/src/poetry/puzzle/solver.py b/src/poetry/puzzle/solver.py index 66e1aca6a5b..682033a04ae 100644 --- a/src/poetry/puzzle/solver.py +++ b/src/poetry/puzzle/solver.py @@ -5,7 +5,6 @@ from collections import defaultdict from contextlib import contextmanager from typing import TYPE_CHECKING -from typing import Collection from typing import FrozenSet from typing import Tuple from typing import TypeVar @@ -21,6 +20,7 @@ if TYPE_CHECKING: + from collections.abc import Collection from collections.abc import Iterator from cleo.io.io import IO diff --git a/src/poetry/repositories/pypi_repository.py b/src/poetry/repositories/pypi_repository.py index 763e25e94bb..2863382383c 100644 --- a/src/poetry/repositories/pypi_repository.py +++ b/src/poetry/repositories/pypi_repository.py @@ -17,7 +17,7 @@ from poetry.repositories.exceptions import PackageNotFound from poetry.repositories.http_repository import HTTPRepository from poetry.repositories.link_sources.json import SimpleJsonPage -from poetry.utils._compat import to_str +from poetry.utils._compat import decode from poetry.utils.constants import REQUESTS_TIMEOUT @@ -82,7 +82,7 @@ def search(self, query: str) -> list[Package]: try: package = Package(name, version) - package.description = to_str(description.strip()) + package.description = decode(description.strip()) results.append(package) except InvalidVersion: self._log( diff --git a/src/poetry/utils/_compat.py b/src/poetry/utils/_compat.py index 5887436e530..cf316b58c26 100644 --- a/src/poetry/utils/_compat.py +++ b/src/poetry/utils/_compat.py @@ -56,16 +56,11 @@ def encode(string: str, encodings: list[str] | None = None) -> bytes: return string.encode(encodings[0], errors="ignore") -def to_str(string: str) -> str: - return decode(string) - - __all__ = [ "WINDOWS", "cached_property", "decode", "encode", "metadata", - "to_str", "tomllib", ] diff --git a/src/poetry/utils/cache.py b/src/poetry/utils/cache.py index 0b8c9e4d611..3d449c356f6 100644 --- a/src/poetry/utils/cache.py +++ b/src/poetry/utils/cache.py @@ -10,7 +10,6 @@ from pathlib import Path from typing import TYPE_CHECKING from typing import Any -from typing import Callable from typing import Generic from typing import TypeVar @@ -21,6 +20,8 @@ if TYPE_CHECKING: + from collections.abc import Callable + from poetry.core.packages.utils.link import Link from poetry.utils.env import Env @@ -108,7 +109,7 @@ def put(self, key: str, value: Any, minutes: int | None = None) -> None: ) path = self._path(key) path.parent.mkdir(parents=True, exist_ok=True) - with open(path, "wb") as f: + with path.open("wb") as f: f.write(self._serialize(payload)) def forget(self, key: str) -> None: @@ -149,7 +150,7 @@ def _get_payload(self, key: str) -> T | None: if not path.exists(): return None - with open(path, "rb") as f: + with path.open("rb") as f: file_content = f.read() try: diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 91a35f212e7..47d5ac62a3f 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -1234,10 +1234,7 @@ def __init__(self, path: Path, base: Path | None = None) -> None: path = get_real_windows_path(path) base = get_real_windows_path(base) if base else None - if not self._is_windows or self._is_mingw: - bin_dir = "bin" - else: - bin_dir = "Scripts" + bin_dir = "bin" if not self._is_windows or self._is_mingw else "Scripts" self._path = path self._bin_dir = self._path / bin_dir @@ -1268,8 +1265,9 @@ def base(self) -> Path: return self._base @property - def version_info(self) -> tuple[Any, ...]: - return tuple(self.marker_env["version_info"]) + def version_info(self) -> tuple[int, int, int, str, int]: + version_info: tuple[int, int, int, str, int] = self.marker_env["version_info"] + return version_info @property def python_implementation(self) -> str: diff --git a/src/poetry/utils/extras.py b/src/poetry/utils/extras.py index f6b93df82cc..5c2d593196d 100644 --- a/src/poetry/utils/extras.py +++ b/src/poetry/utils/extras.py @@ -6,7 +6,7 @@ if TYPE_CHECKING: from collections.abc import Collection from collections.abc import Iterable - from typing import Mapping + from collections.abc import Mapping from packaging.utils import NormalizedName from poetry.core.packages.package import Package diff --git a/src/poetry/utils/helpers.py b/src/poetry/utils/helpers.py index 831203718fa..b39e8230e10 100644 --- a/src/poetry/utils/helpers.py +++ b/src/poetry/utils/helpers.py @@ -8,18 +8,18 @@ import sys import tempfile +from collections.abc import Mapping from contextlib import contextmanager from pathlib import Path from typing import TYPE_CHECKING from typing import Any -from typing import Iterator -from typing import Mapping from poetry.utils.constants import REQUESTS_TIMEOUT if TYPE_CHECKING: from collections.abc import Callable + from collections.abc import Iterator from io import BufferedWriter from poetry.core.packages.package import Package @@ -82,7 +82,7 @@ def remove_directory( def merge_dicts(d1: dict[str, Any], d2: dict[str, Any]) -> None: - for k in d2.keys(): + for k in d2: if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], Mapping): merge_dicts(d1[k], d2[k]) else: @@ -121,7 +121,7 @@ def download_file( # but skip the updating set_indicator = total_size > 1024 * 1024 - with open(dest, "wb") as f: + with dest.open("wb") as f: for chunk in response.iter_content(chunk_size=chunk_size): if chunk: f.write(chunk) diff --git a/src/poetry/utils/setup_reader.py b/src/poetry/utils/setup_reader.py index ffb7d212509..f9cecc4335a 100644 --- a/src/poetry/utils/setup_reader.py +++ b/src/poetry/utils/setup_reader.py @@ -39,7 +39,7 @@ def read_from_directory(cls, directory: Path) -> dict[str, Any]: read_file_func = getattr(cls(), "read_" + filename.replace(".", "_")) new_result = read_file_func(filepath) - for key in result.keys(): + for key in result: if new_result[key]: result[key] = new_result[key] diff --git a/tests/compat.py b/tests/compat.py index bacac6652c3..a5808d9288b 100644 --- a/tests/compat.py +++ b/tests/compat.py @@ -8,6 +8,8 @@ from typing_extensions import Protocol # nopycln: import else: - import zipfile # noqa: F401 + import zipfile - from typing import Protocol # noqa: F401 + from typing import Protocol + +__all__ = ["zipfile", "Protocol"] diff --git a/tests/conftest.py b/tests/conftest.py index 5094c97b667..11e424894f6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,7 +11,6 @@ from pathlib import Path from typing import TYPE_CHECKING from typing import Any -from typing import TextIO import httpretty import pytest @@ -41,6 +40,7 @@ if TYPE_CHECKING: from collections.abc import Iterator + from collections.abc import Mapping from _pytest.config import Config as PyTestConfig from _pytest.config.argparsing import Parser @@ -70,6 +70,9 @@ def pytest_configure(config: PyTestConfig) -> None: class Config(BaseConfig): + _config_source: DictConfigSource + _auth_config_source: DictConfigSource + def get(self, setting_name: str, default: Any = None) -> Any: self.merge(self._config_source.config) self.merge(self._auth_config_source.config) @@ -91,7 +94,7 @@ def all(self) -> dict[str, Any]: class DummyBackend(KeyringBackend): def __init__(self) -> None: - self._passwords = {} + self._passwords: dict[str, dict[str | None, str | None]] = {} @classmethod def priority(cls) -> int: @@ -302,31 +305,16 @@ def tmp_dir() -> Iterator[str]: remove_directory(path, force=True) -@pytest.fixture -def mocked_open_files(mocker: MockerFixture) -> list: - files = [] - original = Path.open - - def mocked_open(self: Path, *args: Any, **kwargs: Any) -> TextIO: - if self.name in {"pyproject.toml"}: - return mocker.MagicMock() - return original(self, *args, **kwargs) - - mocker.patch("pathlib.Path.open", mocked_open) - - return files - - @pytest.fixture def tmp_venv(tmp_dir: str) -> Iterator[VirtualEnv]: venv_path = Path(tmp_dir) / "venv" - EnvManager.build_venv(str(venv_path)) + EnvManager.build_venv(venv_path) venv = VirtualEnv(venv_path) yield venv - shutil.rmtree(str(venv.path)) + shutil.rmtree(venv.path) @pytest.fixture @@ -371,8 +359,8 @@ def project_factory( def _factory( name: str | None = None, - dependencies: dict[str, str] | None = None, - dev_dependencies: dict[str, str] | None = None, + dependencies: Mapping[str, str] | None = None, + dev_dependencies: Mapping[str, str] | None = None, pyproject_content: str | None = None, poetry_lock_content: str | None = None, install_deps: bool = True, @@ -397,6 +385,7 @@ def _factory( ) as f: f.write(pyproject_content) else: + assert name is not None layout("src")( name, "0.1.0", diff --git a/tests/console/commands/source/test_add.py b/tests/console/commands/source/test_add.py index 7e43b9e2014..40a53192a4c 100644 --- a/tests/console/commands/source/test_add.py +++ b/tests/console/commands/source/test_add.py @@ -43,7 +43,7 @@ def test_source_add_simple( source_existing: Source, source_one: Source, poetry_with_source: Poetry, -): +) -> None: tester.execute(f"{source_one.name} {source_one.url}") assert_source_added(tester, poetry_with_source, source_existing, source_one) @@ -53,7 +53,7 @@ def test_source_add_default( source_existing: Source, source_default: Source, poetry_with_source: Poetry, -): +) -> None: tester.execute(f"--default {source_default.name} {source_default.url}") assert_source_added(tester, poetry_with_source, source_existing, source_default) @@ -63,12 +63,12 @@ def test_source_add_secondary( source_existing: Source, source_secondary: Source, poetry_with_source: Poetry, -): +) -> None: tester.execute(f"--secondary {source_secondary.name} {source_secondary.url}") assert_source_added(tester, poetry_with_source, source_existing, source_secondary) -def test_source_add_error_default_and_secondary(tester: CommandTester): +def test_source_add_error_default_and_secondary(tester: CommandTester) -> None: tester.execute("--default --secondary error https://error.com") assert ( tester.io.fetch_error().strip() @@ -77,7 +77,7 @@ def test_source_add_error_default_and_secondary(tester: CommandTester): assert tester.status_code == 1 -def test_source_add_error_pypi(tester: CommandTester): +def test_source_add_error_pypi(tester: CommandTester) -> None: tester.execute("pypi https://test.pypi.org/simple/") assert ( tester.io.fetch_error().strip() @@ -89,7 +89,7 @@ def test_source_add_error_pypi(tester: CommandTester): def test_source_add_existing( tester: CommandTester, source_existing: Source, poetry_with_source: Poetry -): +) -> None: tester.execute(f"--default {source_existing.name} {source_existing.url}") assert ( tester.io.fetch_output().strip() diff --git a/tests/console/commands/source/test_remove.py b/tests/console/commands/source/test_remove.py index 914eb3e03b3..49d881328a8 100644 --- a/tests/console/commands/source/test_remove.py +++ b/tests/console/commands/source/test_remove.py @@ -28,7 +28,7 @@ def test_source_remove_simple( source_existing: Source, source_one: Source, source_two: Source, -): +) -> None: tester.execute(f"{source_existing.name}") assert ( tester.io.fetch_output().strip() @@ -42,7 +42,7 @@ def test_source_remove_simple( assert tester.status_code == 0 -def test_source_remove_error(tester: CommandTester): +def test_source_remove_error(tester: CommandTester) -> None: tester.execute("error") assert tester.io.fetch_error().strip() == "Source with name error was not found." assert tester.status_code == 1 diff --git a/tests/console/commands/source/test_show.py b/tests/console/commands/source/test_show.py index 8b975cf4b92..d25b9788365 100644 --- a/tests/console/commands/source/test_show.py +++ b/tests/console/commands/source/test_show.py @@ -22,7 +22,7 @@ def tester( return command_tester_factory("source show", poetry=poetry_with_source) -def test_source_show_simple(tester: CommandTester): +def test_source_show_simple(tester: CommandTester) -> None: tester.execute("") expected = """\ @@ -47,7 +47,7 @@ def test_source_show_simple(tester: CommandTester): assert tester.status_code == 0 -def test_source_show_one(tester: CommandTester, source_one: Source): +def test_source_show_one(tester: CommandTester, source_one: Source) -> None: tester.execute(f"{source_one.name}") expected = """\ @@ -62,7 +62,9 @@ def test_source_show_one(tester: CommandTester, source_one: Source): assert tester.status_code == 0 -def test_source_show_two(tester: CommandTester, source_one: Source, source_two: Source): +def test_source_show_two( + tester: CommandTester, source_one: Source, source_two: Source +) -> None: tester.execute(f"{source_one.name} {source_two.name}") expected = """\ @@ -82,7 +84,7 @@ def test_source_show_two(tester: CommandTester, source_one: Source, source_two: assert tester.status_code == 0 -def test_source_show_error(tester: CommandTester): +def test_source_show_error(tester: CommandTester) -> None: tester.execute("error") assert tester.io.fetch_error().strip() == "No source found with name(s): error" assert tester.status_code == 1 diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py index 1feda849149..e3da0a989a6 100644 --- a/tests/console/commands/test_init.py +++ b/tests/console/commands/test_init.py @@ -16,7 +16,6 @@ from poetry.console.commands.init import InitCommand from poetry.repositories import RepositoryPool -from poetry.utils._compat import decode from tests.helpers import PoetryTestApplication from tests.helpers import get_package @@ -896,7 +895,7 @@ def test_init_existing_pyproject_simple( [tool.black] line-length = 88 """ - pyproject_file.write_text(decode(existing_section)) + pyproject_file.write_text(existing_section) tester.execute(inputs=init_basic_inputs) assert f"{existing_section}\n{init_basic_toml}" in pyproject_file.read_text() @@ -936,7 +935,7 @@ def test_init_non_interactive_existing_pyproject_add_dependency( [tool.black] line-length = 88 """ - pyproject_file.write_text(decode(existing_section)) + pyproject_file.write_text(existing_section) repo.add_package(get_package("foo", "1.19.2")) @@ -975,7 +974,7 @@ def test_init_existing_pyproject_with_build_system_fails( requires = ["setuptools >= 40.6.0", "wheel"] build-backend = "setuptools.build_meta" """ - pyproject_file.write_text(decode(existing_section)) + pyproject_file.write_text(existing_section) tester.execute(inputs=init_basic_inputs) assert ( tester.io.fetch_error().strip() diff --git a/tests/console/commands/test_show.py b/tests/console/commands/test_show.py index 8f7eadb2d3f..7fb6c60c7eb 100644 --- a/tests/console/commands/test_show.py +++ b/tests/console/commands/test_show.py @@ -29,7 +29,7 @@ def tester(command_tester_factory: CommandTesterFactory) -> CommandTester: def test_show_basic_with_installed_packages( tester: CommandTester, poetry: Poetry, installed: Repository -): +) -> None: poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0")) poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0")) poetry.package.add_dependency( @@ -258,7 +258,7 @@ def test_show_basic_with_group_options( tester: CommandTester, poetry: Poetry, installed: Repository, -): +) -> None: _configure_project_with_groups(poetry, installed) tester.execute(options) @@ -268,7 +268,7 @@ def test_show_basic_with_group_options( def test_show_basic_with_installed_packages_single( tester: CommandTester, poetry: Poetry, installed: Repository -): +) -> None: poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0")) cachy_010 = get_package("cachy", "0.1.0") @@ -310,7 +310,7 @@ def test_show_basic_with_installed_packages_single( def test_show_basic_with_installed_packages_single_canonicalized( tester: CommandTester, poetry: Poetry, installed: Repository -): +) -> None: poetry.package.add_dependency(Factory.create_dependency("foo-bar", "^0.1.0")) foo_bar = get_package("foo-bar", "0.1.0") @@ -352,7 +352,7 @@ def test_show_basic_with_installed_packages_single_canonicalized( def test_show_basic_with_not_installed_packages_non_decorated( tester: CommandTester, poetry: Poetry, installed: Repository -): +) -> None: poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0")) poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0")) @@ -408,7 +408,7 @@ def test_show_basic_with_not_installed_packages_non_decorated( def test_show_basic_with_not_installed_packages_decorated( tester: CommandTester, poetry: Poetry, installed: Repository -): +) -> None: poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0")) poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0")) @@ -467,7 +467,7 @@ def test_show_latest_non_decorated( poetry: Poetry, installed: Repository, repo: TestRepository, -): +) -> None: poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0")) poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0")) @@ -537,7 +537,7 @@ def test_show_latest_decorated( poetry: Poetry, installed: Repository, repo: TestRepository, -): +) -> None: poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0")) poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0")) @@ -609,7 +609,7 @@ def test_show_outdated( poetry: Poetry, installed: Repository, repo: TestRepository, -): +) -> None: poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0")) poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0")) @@ -675,7 +675,7 @@ def test_show_outdated_with_only_up_to_date_packages( poetry: Poetry, installed: Repository, repo: TestRepository, -): +) -> None: cachy_020 = get_package("cachy", "0.2.0") cachy_020.description = "Cachy package" @@ -717,7 +717,7 @@ def test_show_outdated_has_prerelease_but_not_allowed( poetry: Poetry, installed: Repository, repo: TestRepository, -): +) -> None: poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0")) poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0")) @@ -788,7 +788,7 @@ def test_show_outdated_has_prerelease_and_allowed( poetry: Poetry, installed: Repository, repo: TestRepository, -): +) -> None: poetry.package.add_dependency( Factory.create_dependency( "cachy", {"version": ">=0.0.1", "allow-prereleases": True} @@ -863,7 +863,7 @@ def test_show_outdated_formatting( poetry: Poetry, installed: Repository, repo: TestRepository, -): +) -> None: poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0")) poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0")) @@ -942,7 +942,7 @@ def test_show_outdated_local_dependencies( poetry: Poetry, installed: Repository, repo: TestRepository, -): +) -> None: cachy_010 = get_package("cachy", "0.1.0") cachy_010.description = "Cachy package" cachy_020 = get_package("cachy", "0.2.0") @@ -1058,7 +1058,7 @@ def test_show_outdated_git_dev_dependency( poetry: Poetry, installed: Repository, repo: TestRepository, -): +) -> None: cachy_010 = get_package("cachy", "0.1.0") cachy_010.description = "Cachy package" cachy_020 = get_package("cachy", "0.2.0") @@ -1158,7 +1158,7 @@ def test_show_outdated_no_dev_git_dev_dependency( poetry: Poetry, installed: Repository, repo: TestRepository, -): +) -> None: cachy_010 = get_package("cachy", "0.1.0") cachy_010.description = "Cachy package" cachy_020 = get_package("cachy", "0.2.0") @@ -1255,7 +1255,7 @@ def test_show_hides_incompatible_package( poetry: Poetry, installed: Repository, repo: TestRepository, -): +) -> None: poetry.package.add_dependency( Factory.create_dependency("cachy", {"version": "^0.1.0", "python": "< 2.0"}) ) @@ -1316,7 +1316,7 @@ def test_show_all_shows_incompatible_package( poetry: Poetry, installed: Repository, repo: TestRepository, -): +) -> None: cachy_010 = get_package("cachy", "0.1.0") cachy_010.description = "Cachy package" @@ -1374,7 +1374,7 @@ def test_show_hides_incompatible_package_with_duplicate( poetry: Poetry, installed: Repository, repo: TestRepository, -): +) -> None: poetry.package.add_dependency( Factory.create_dependency("cachy", {"version": "0.1.0", "platform": "linux"}) ) @@ -1422,7 +1422,7 @@ def test_show_all_shows_all_duplicates( poetry: Poetry, installed: Repository, repo: TestRepository, -): +) -> None: poetry.package.add_dependency( Factory.create_dependency("cachy", {"version": "0.1.0", "platform": "linux"}) ) @@ -1468,7 +1468,7 @@ def test_show_all_shows_all_duplicates( def test_show_non_dev_with_basic_installed_packages( tester: CommandTester, poetry: Poetry, installed: Repository -): +) -> None: poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0")) poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0")) poetry.package.add_dependency( @@ -1544,7 +1544,7 @@ def test_show_non_dev_with_basic_installed_packages( def test_show_with_group_only( tester: CommandTester, poetry: Poetry, installed: Repository -): +) -> None: poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0")) poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0")) poetry.package.add_dependency( @@ -1619,7 +1619,7 @@ def test_show_with_group_only( def test_show_with_optional_group( tester: CommandTester, poetry: Poetry, installed: Repository -): +) -> None: poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0")) poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0")) group = DependencyGroup("dev", optional=True) @@ -1703,7 +1703,9 @@ def test_show_with_optional_group( assert tester.io.fetch_output() == expected -def test_show_tree(tester: CommandTester, poetry: Poetry, installed: Repository): +def test_show_tree( + tester: CommandTester, poetry: Poetry, installed: Repository +) -> None: poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.2.0")) cachy2 = get_package("cachy", "0.2.0") @@ -1755,7 +1757,9 @@ def test_show_tree(tester: CommandTester, poetry: Poetry, installed: Repository) assert tester.io.fetch_output() == expected -def test_show_tree_no_dev(tester: CommandTester, poetry: Poetry, installed: Repository): +def test_show_tree_no_dev( + tester: CommandTester, poetry: Poetry, installed: Repository +) -> None: poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.2.0")) poetry.package.add_dependency( Factory.create_dependency("pytest", "^6.1.0", groups=["dev"]) @@ -1824,7 +1828,7 @@ def test_show_tree_no_dev(tester: CommandTester, poetry: Poetry, installed: Repo def test_show_tree_why_package( tester: CommandTester, poetry: Poetry, installed: Repository -): +) -> None: poetry.package.add_dependency(Factory.create_dependency("a", "=0.0.1")) a = get_package("a", "0.0.1") @@ -1881,7 +1885,9 @@ def test_show_tree_why_package( assert tester.io.fetch_output() == expected -def test_show_tree_why(tester: CommandTester, poetry: Poetry, installed: Repository): +def test_show_tree_why( + tester: CommandTester, poetry: Poetry, installed: Repository +) -> None: poetry.package.add_dependency(Factory.create_dependency("a", "=0.0.1")) a = get_package("a", "0.0.1") @@ -1939,7 +1945,7 @@ def test_show_tree_why(tester: CommandTester, poetry: Poetry, installed: Reposit def test_show_required_by_deps( tester: CommandTester, poetry: Poetry, installed: Repository -): +) -> None: poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.2.0")) poetry.package.add_dependency(Factory.create_dependency("pendulum", "2.0.0")) @@ -2014,7 +2020,7 @@ def test_show_required_by_deps( assert actual == expected -def test_show_errors_without_lock_file(tester: CommandTester, poetry: Poetry): +def test_show_errors_without_lock_file(tester: CommandTester, poetry: Poetry) -> None: assert not poetry.locker.lock.exists() tester.execute() @@ -2029,7 +2035,7 @@ def test_show_dependency_installed_from_git_in_dev( poetry: Poetry, installed: Repository, repo: TestRepository, -): +) -> None: # Add a regular dependency for a package in main, and a git dependency for the same # package in dev. poetry.package.add_dependency(Factory.create_dependency("demo", "^0.1.1")) @@ -2097,7 +2103,7 @@ def test_url_dependency_is_not_outdated_by_repository_package( poetry: Poetry, installed: Repository, repo: TestRepository, -): +) -> None: demo_url = "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl" poetry.package.add_dependency( Factory.create_dependency( @@ -2157,7 +2163,7 @@ def test_show_outdated_missing_directory_dependency( poetry: Poetry, installed: Repository, repo: TestRepository, -): +) -> None: with (poetry.pyproject.file.path.parent / "poetry.lock").open(mode="rb") as f: data = tomllib.load(f) poetry.locker.mock_lock_data(data) diff --git a/tests/helpers.py b/tests/helpers.py index 0bbaf414dde..3ab77da917b 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -305,7 +305,7 @@ def recurse_keys(obj: Mapping[str, Any]) -> Iterator[tuple[list[str], Any]]: :return: dict """ if isinstance(obj, dict): - for key in obj.keys(): + for key in obj: for leaf in recurse_keys(obj[key]): leaf_path, leaf_value = leaf leaf_path.insert(0, key) diff --git a/tests/inspection/test_info.py b/tests/inspection/test_info.py index cf222059286..65628f2ffb4 100644 --- a/tests/inspection/test_info.py +++ b/tests/inspection/test_info.py @@ -8,7 +8,6 @@ from poetry.inspection.info import PackageInfo from poetry.inspection.info import PackageInfoError -from poetry.utils._compat import decode from poetry.utils.env import EnvCommandError from poetry.utils.env import VirtualEnv @@ -21,7 +20,7 @@ @pytest.fixture(autouse=True) -def pep517_metadata_mock(): +def pep517_metadata_mock() -> None: pass @@ -44,12 +43,10 @@ def source_dir(tmp_path: Path) -> Path: def demo_setup(source_dir: Path) -> Path: setup_py = source_dir / "setup.py" setup_py.write_text( - decode( - "from setuptools import setup; " - 'setup(name="demo", ' - 'version="0.1.0", ' - 'install_requires=["package"])' - ) + "from setuptools import setup; " + 'setup(name="demo", ' + 'version="0.1.0", ' + 'install_requires=["package"])' ) return source_dir @@ -58,16 +55,14 @@ def demo_setup(source_dir: Path) -> Path: def demo_setup_cfg(source_dir: Path) -> Path: setup_cfg = source_dir / "setup.cfg" setup_cfg.write_text( - decode( - "\n".join( - [ - "[metadata]", - "name = demo", - "version = 0.1.0", - "[options]", - "install_requires = package", - ] - ) + "\n".join( + [ + "[metadata]", + "name = demo", + "version = 0.1.0", + "[options]", + "install_requires = package", + ] ) ) return source_dir @@ -77,12 +72,10 @@ def demo_setup_cfg(source_dir: Path) -> Path: def demo_setup_complex(source_dir: Path) -> Path: setup_py = source_dir / "setup.py" setup_py.write_text( - decode( - "from setuptools import setup; " - 'setup(name="demo", ' - 'version="0.1.0", ' - 'install_requires=[i for i in ["package"]])' - ) + "from setuptools import setup; " + 'setup(name="demo", ' + 'version="0.1.0", ' + 'install_requires=[i for i in ["package"]])' ) return source_dir @@ -90,13 +83,11 @@ def demo_setup_complex(source_dir: Path) -> Path: @pytest.fixture def demo_setup_complex_pep517_legacy(demo_setup_complex: Path) -> Path: pyproject_toml = demo_setup_complex / "pyproject.toml" - pyproject_toml.write_text( - decode('[build-system]\nrequires = ["setuptools", "wheel"]') - ) + pyproject_toml.write_text('[build-system]\nrequires = ["setuptools", "wheel"]') return demo_setup_complex -def demo_check_info(info: PackageInfo, requires_dist: set[str] = None) -> None: +def demo_check_info(info: PackageInfo, requires_dist: set[str] | None = None) -> None: assert info.name == "demo" assert info.version == "0.1.0" assert info.requires_dist @@ -120,22 +111,22 @@ def demo_check_info(info: PackageInfo, requires_dist: set[str] = None) -> None: ) -def test_info_from_sdist(demo_sdist: Path): +def test_info_from_sdist(demo_sdist: Path) -> None: info = PackageInfo.from_sdist(demo_sdist) demo_check_info(info) -def test_info_from_wheel(demo_wheel: Path): +def test_info_from_wheel(demo_wheel: Path) -> None: info = PackageInfo.from_wheel(demo_wheel) demo_check_info(info) -def test_info_from_bdist(demo_wheel: Path): +def test_info_from_bdist(demo_wheel: Path) -> None: info = PackageInfo.from_bdist(demo_wheel) demo_check_info(info) -def test_info_from_poetry_directory(): +def test_info_from_poetry_directory() -> None: info = PackageInfo.from_directory( FIXTURE_DIR_INSPECTIONS / "demo", disable_build=True ) @@ -144,7 +135,7 @@ def test_info_from_poetry_directory(): def test_info_from_poetry_directory_fallback_on_poetry_create_error( mocker: MockerFixture, -): +) -> None: mock_create_poetry = mocker.patch( "poetry.inspection.info.Factory.create_poetry", side_effect=RuntimeError ) @@ -160,24 +151,25 @@ def test_info_from_poetry_directory_fallback_on_poetry_create_error( assert mock_get_pep517_metadata.call_count == 1 -def test_info_from_requires_txt(): +def test_info_from_requires_txt() -> None: info = PackageInfo.from_metadata( FIXTURE_DIR_INSPECTIONS / "demo_only_requires_txt.egg-info" ) + assert info is not None demo_check_info(info) -def test_info_from_setup_py(demo_setup: Path): +def test_info_from_setup_py(demo_setup: Path) -> None: info = PackageInfo.from_setup_files(demo_setup) demo_check_info(info, requires_dist={"package"}) -def test_info_from_setup_cfg(demo_setup_cfg: Path): +def test_info_from_setup_cfg(demo_setup_cfg: Path) -> None: info = PackageInfo.from_setup_files(demo_setup_cfg) demo_check_info(info, requires_dist={"package"}) -def test_info_no_setup_pkg_info_no_deps(): +def test_info_no_setup_pkg_info_no_deps() -> None: info = PackageInfo.from_directory( FIXTURE_DIR_INSPECTIONS / "demo_no_setup_pkg_info_no_deps", disable_build=True, @@ -187,28 +179,28 @@ def test_info_no_setup_pkg_info_no_deps(): assert info.requires_dist is None -def test_info_setup_simple(mocker: MockerFixture, demo_setup: Path): +def test_info_setup_simple(mocker: MockerFixture, demo_setup: Path) -> None: spy = mocker.spy(VirtualEnv, "run") info = PackageInfo.from_directory(demo_setup) assert spy.call_count == 0 demo_check_info(info, requires_dist={"package"}) -def test_info_setup_cfg(mocker: MockerFixture, demo_setup_cfg: Path): +def test_info_setup_cfg(mocker: MockerFixture, demo_setup_cfg: Path) -> None: spy = mocker.spy(VirtualEnv, "run") info = PackageInfo.from_directory(demo_setup_cfg) assert spy.call_count == 0 demo_check_info(info, requires_dist={"package"}) -def test_info_setup_complex(demo_setup_complex: Path): +def test_info_setup_complex(demo_setup_complex: Path) -> None: info = PackageInfo.from_directory(demo_setup_complex) demo_check_info(info, requires_dist={"package"}) def test_info_setup_complex_pep517_error( mocker: MockerFixture, demo_setup_complex: Path -): +) -> None: mocker.patch( "poetry.utils.env.VirtualEnv.run", autospec=True, @@ -219,14 +211,16 @@ def test_info_setup_complex_pep517_error( PackageInfo.from_directory(demo_setup_complex) -def test_info_setup_complex_pep517_legacy(demo_setup_complex_pep517_legacy: Path): +def test_info_setup_complex_pep517_legacy( + demo_setup_complex_pep517_legacy: Path, +) -> None: info = PackageInfo.from_directory(demo_setup_complex_pep517_legacy) demo_check_info(info, requires_dist={"package"}) def test_info_setup_complex_disable_build( mocker: MockerFixture, demo_setup_complex: Path -): +) -> None: spy = mocker.spy(VirtualEnv, "run") info = PackageInfo.from_directory(demo_setup_complex, disable_build=True) assert spy.call_count == 0 @@ -238,7 +232,7 @@ def test_info_setup_complex_disable_build( @pytest.mark.parametrize("missing", ["version", "name", "install_requires"]) def test_info_setup_missing_mandatory_should_trigger_pep517( mocker: MockerFixture, source_dir: Path, missing: str -): +) -> None: setup = "from setuptools import setup; " setup += "setup(" setup += 'name="demo", ' if missing != "name" else "" @@ -247,14 +241,14 @@ def test_info_setup_missing_mandatory_should_trigger_pep517( setup += ")" setup_py = source_dir / "setup.py" - setup_py.write_text(decode(setup)) + setup_py.write_text(setup) spy = mocker.spy(VirtualEnv, "run") _ = PackageInfo.from_directory(source_dir) assert spy.call_count == 1 -def test_info_prefer_poetry_config_over_egg_info(): +def test_info_prefer_poetry_config_over_egg_info() -> None: info = PackageInfo.from_directory( FIXTURE_DIR_INSPECTIONS / "demo_with_obsolete_egg_info" ) diff --git a/tests/installation/test_chooser.py b/tests/installation/test_chooser.py index a50338489a4..fbd3861ad05 100644 --- a/tests/installation/test_chooser.py +++ b/tests/installation/test_chooser.py @@ -58,7 +58,7 @@ def callback( fixture = JSON_FIXTURES / (name + ".json") if not fixture.exists(): - return + return None with fixture.open(encoding="utf-8") as f: return [200, headers, f.read()] @@ -132,7 +132,7 @@ def test_chooser_chooses_universal_wheel_link_if_available( mock_legacy: None, source_type: str, pool: RepositoryPool, -): +) -> None: chooser = Chooser(pool, env) package = Package("pytest", "3.5.0") @@ -170,7 +170,7 @@ def test_chooser_no_binary_policy( policy: str, filename: str, config: Config, -): +) -> None: config.merge({"installer": {"no-binary": policy.split(",")}}) chooser = Chooser(pool, env, config) @@ -197,7 +197,7 @@ def test_chooser_chooses_specific_python_universal_wheel_link_if_available( mock_legacy: None, source_type: str, pool: RepositoryPool, -): +) -> None: chooser = Chooser(pool, env) package = Package("isort", "4.3.4") @@ -218,7 +218,7 @@ def test_chooser_chooses_specific_python_universal_wheel_link_if_available( @pytest.mark.parametrize("source_type", ["", "legacy"]) def test_chooser_chooses_system_specific_wheel_link_if_available( mock_pypi: None, mock_legacy: None, source_type: str, pool: RepositoryPool -): +) -> None: env = MockEnv( supported_tags=[Tag("cp37", "cp37m", "win32"), Tag("py3", "none", "any")] ) @@ -246,7 +246,7 @@ def test_chooser_chooses_sdist_if_no_compatible_wheel_link_is_available( mock_legacy: None, source_type: str, pool: RepositoryPool, -): +) -> None: chooser = Chooser(pool, env) package = Package("pyyaml", "3.13.0") @@ -271,7 +271,7 @@ def test_chooser_chooses_distributions_that_match_the_package_hashes( mock_legacy: None, source_type: str, pool: RepositoryPool, -): +) -> None: chooser = Chooser(pool, env) package = Package("isort", "4.3.4") @@ -381,7 +381,7 @@ def test_chooser_throws_an_error_if_package_hashes_do_not_match( mock_legacy: None, source_type: None, pool: RepositoryPool, -): +) -> None: chooser = Chooser(pool, env) package = Package("isort", "4.3.4") diff --git a/tests/mixology/helpers.py b/tests/mixology/helpers.py index 500d2d95c07..c2c28e19853 100644 --- a/tests/mixology/helpers.py +++ b/tests/mixology/helpers.py @@ -10,11 +10,13 @@ if TYPE_CHECKING: + from collections.abc import Mapping + from packaging.utils import NormalizedName from poetry.core.factory import DependencyConstraint from poetry.core.packages.project_package import ProjectPackage - from poetry.mixology import SolverResult + from poetry.mixology.result import SolverResult from poetry.repositories import Repository from tests.mixology.version_solver.conftest import Provider @@ -23,7 +25,7 @@ def add_to_repo( repository: Repository, name: str, version: str, - deps: dict[str, DependencyConstraint] | None = None, + deps: Mapping[str, DependencyConstraint] | None = None, python: str | None = None, yanked: bool = False, ) -> None: @@ -62,7 +64,7 @@ def check_solver_result( except AssertionError as e: if error: assert str(e) == error - return + return None raise packages = {} diff --git a/tests/mixology/version_solver/test_backtracking.py b/tests/mixology/version_solver/test_backtracking.py index 6354d4c6a48..68e8f638746 100644 --- a/tests/mixology/version_solver/test_backtracking.py +++ b/tests/mixology/version_solver/test_backtracking.py @@ -16,7 +16,7 @@ def test_circular_dependency_on_older_version( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: root.add_dependency(Factory.create_dependency("a", ">=1.0.0")) add_to_repo(repo, "a", "1.0.0") @@ -28,7 +28,7 @@ def test_circular_dependency_on_older_version( def test_diamond_dependency_graph( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: root.add_dependency(Factory.create_dependency("a", "*")) root.add_dependency(Factory.create_dependency("b", "*")) @@ -47,7 +47,7 @@ def test_diamond_dependency_graph( def test_backjumps_after_partial_satisfier( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: # c 2.0.0 is incompatible with y 2.0.0 because it requires x 1.0.0, but that # requirement only exists because of both a and b. The solver should be able # to deduce c 2.0.0's incompatibility and select c 1.0.0 instead. @@ -72,7 +72,7 @@ def test_backjumps_after_partial_satisfier( def test_rolls_back_leaf_versions_first( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: # The latest versions of a and b disagree on c. An older version of either # will resolve the problem. This test validates that b, which is farther # in the dependency graph from myapp is downgraded first. @@ -88,7 +88,9 @@ def test_rolls_back_leaf_versions_first( check_solver_result(root, provider, {"a": "2.0.0", "b": "1.0.0", "c": "2.0.0"}) -def test_simple_transitive(root: ProjectPackage, provider: Provider, repo: Repository): +def test_simple_transitive( + root: ProjectPackage, provider: Provider, repo: Repository +) -> None: # Only one version of baz, so foo and bar will have to downgrade # until they reach it root.add_dependency(Factory.create_dependency("foo", "*")) @@ -110,7 +112,7 @@ def test_simple_transitive(root: ProjectPackage, provider: Provider, repo: Repos def test_backjump_to_nearer_unsatisfied_package( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: # This ensures it doesn't exhaustively search all versions of b when it's # a-2.0.0 whose dependency on c-2.0.0-nonexistent led to the problem. We # make sure b has more versions than a so that the solver tries a first @@ -132,7 +134,7 @@ def test_backjump_to_nearer_unsatisfied_package( def test_traverse_into_package_with_fewer_versions_first( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: # Dependencies are ordered so that packages with fewer versions are tried # first. Here, there are two valid solutions (either a or b must be # downgraded once). The chosen one depends on which dep is traversed first. @@ -158,7 +160,7 @@ def test_traverse_into_package_with_fewer_versions_first( def test_backjump_past_failed_package_on_disjoint_constraint( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: root.add_dependency(Factory.create_dependency("a", "*")) root.add_dependency(Factory.create_dependency("foo", ">2.0.0")) diff --git a/tests/mixology/version_solver/test_basic_graph.py b/tests/mixology/version_solver/test_basic_graph.py index 210abc371e4..8146f5ad1be 100644 --- a/tests/mixology/version_solver/test_basic_graph.py +++ b/tests/mixology/version_solver/test_basic_graph.py @@ -18,7 +18,7 @@ def test_simple_dependencies( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: root.add_dependency(Factory.create_dependency("a", "1.0.0")) root.add_dependency(Factory.create_dependency("b", "1.0.0")) @@ -45,7 +45,7 @@ def test_simple_dependencies( def test_shared_dependencies_with_overlapping_constraints( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: root.add_dependency(Factory.create_dependency("a", "1.0.0")) root.add_dependency(Factory.create_dependency("b", "1.0.0")) @@ -62,7 +62,7 @@ def test_shared_dependencies_with_overlapping_constraints( def test_shared_dependency_where_dependent_version_affects_other_dependencies( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: root.add_dependency(Factory.create_dependency("foo", "<=1.0.2")) root.add_dependency(Factory.create_dependency("bar", "1.0.0")) @@ -82,7 +82,7 @@ def test_shared_dependency_where_dependent_version_affects_other_dependencies( def test_circular_dependency( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: root.add_dependency(Factory.create_dependency("foo", "1.0.0")) add_to_repo(repo, "foo", "1.0.0", deps={"bar": "1.0.0"}) diff --git a/tests/mixology/version_solver/test_dependency_cache.py b/tests/mixology/version_solver/test_dependency_cache.py index 4606911e7e1..dffa7e535be 100644 --- a/tests/mixology/version_solver/test_dependency_cache.py +++ b/tests/mixology/version_solver/test_dependency_cache.py @@ -18,7 +18,7 @@ def test_solver_dependency_cache_respects_source_type( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: dependency_pypi = Factory.create_dependency("demo", ">=0.1.0") dependency_git = Factory.create_dependency( "demo", {"git": "https://github.com/demo/demo.git"}, groups=["dev"] @@ -62,7 +62,7 @@ def test_solver_dependency_cache_respects_source_type( def test_solver_dependency_cache_respects_subdirectories( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: dependency_one = Factory.create_dependency( "one", { diff --git a/tests/mixology/version_solver/test_python_constraint.py b/tests/mixology/version_solver/test_python_constraint.py index 52bbdd7bdab..d54a918bbd6 100644 --- a/tests/mixology/version_solver/test_python_constraint.py +++ b/tests/mixology/version_solver/test_python_constraint.py @@ -16,7 +16,7 @@ def test_dependency_does_not_match_root_python_constraint( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: provider.set_package_python_versions("^3.6") root.add_dependency(Factory.create_dependency("foo", "*")) diff --git a/tests/mixology/version_solver/test_unsolvable.py b/tests/mixology/version_solver/test_unsolvable.py index 75ff37da9cb..3886949a672 100644 --- a/tests/mixology/version_solver/test_unsolvable.py +++ b/tests/mixology/version_solver/test_unsolvable.py @@ -17,7 +17,7 @@ def test_no_version_matching_constraint( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: root.add_dependency(Factory.create_dependency("foo", "^1.0")) add_to_repo(repo, "foo", "2.0.0") @@ -35,7 +35,7 @@ def test_no_version_matching_constraint( def test_no_version_that_matches_combined_constraints( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: root.add_dependency(Factory.create_dependency("foo", "1.0.0")) root.add_dependency(Factory.create_dependency("bar", "1.0.0")) @@ -58,7 +58,7 @@ def test_no_version_that_matches_combined_constraints( def test_disjoint_constraints( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: root.add_dependency(Factory.create_dependency("foo", "1.0.0")) root.add_dependency(Factory.create_dependency("bar", "1.0.0")) @@ -80,7 +80,7 @@ def test_disjoint_constraints( def test_disjoint_root_constraints( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: root.add_dependency(Factory.create_dependency("foo", "1.0.0")) root.add_dependency(Factory.create_dependency("foo", "2.0.0")) @@ -95,7 +95,7 @@ def test_disjoint_root_constraints( def test_disjoint_root_constraints_path_dependencies( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: provider.set_package_python_versions("^3.7") fixtures = Path(__file__).parent.parent.parent / "fixtures" project_dir = fixtures.joinpath("with_conditional_path_deps") @@ -112,7 +112,9 @@ def test_disjoint_root_constraints_path_dependencies( check_solver_result(root, provider, error=error) -def test_no_valid_solution(root: ProjectPackage, provider: Provider, repo: Repository): +def test_no_valid_solution( + root: ProjectPackage, provider: Provider, repo: Repository +) -> None: root.add_dependency(Factory.create_dependency("a", "*")) root.add_dependency(Factory.create_dependency("b", "*")) @@ -135,7 +137,7 @@ def test_no_valid_solution(root: ProjectPackage, provider: Provider, repo: Repos def test_package_with_the_same_name_gives_clear_error_message( root: ProjectPackage, provider: Provider, repo: Repository -): +) -> None: pkg_name = "a" root.add_dependency(Factory.create_dependency(pkg_name, "*")) add_to_repo(repo, pkg_name, "1.0.0", deps={pkg_name: "1.0.0"}) diff --git a/tests/mixology/version_solver/test_with_lock.py b/tests/mixology/version_solver/test_with_lock.py index 79f428491ae..75351c842f8 100644 --- a/tests/mixology/version_solver/test_with_lock.py +++ b/tests/mixology/version_solver/test_with_lock.py @@ -22,7 +22,7 @@ def test_with_compatible_locked_dependencies( root: ProjectPackage, repo: Repository, pool: RepositoryPool -): +) -> None: root.add_dependency(Factory.create_dependency("foo", "*")) add_to_repo(repo, "foo", "1.0.0", deps={"bar": "1.0.0"}) @@ -44,7 +44,7 @@ def test_with_compatible_locked_dependencies( def test_with_incompatible_locked_dependencies( root: ProjectPackage, repo: Repository, pool: RepositoryPool -): +) -> None: root.add_dependency(Factory.create_dependency("foo", ">1.0.1")) add_to_repo(repo, "foo", "1.0.0", deps={"bar": "1.0.0"}) @@ -66,7 +66,7 @@ def test_with_incompatible_locked_dependencies( def test_with_unrelated_locked_dependencies( root: ProjectPackage, repo: Repository, pool: RepositoryPool -): +) -> None: root.add_dependency(Factory.create_dependency("foo", "*")) add_to_repo(repo, "foo", "1.0.0", deps={"bar": "1.0.0"}) @@ -89,7 +89,7 @@ def test_with_unrelated_locked_dependencies( def test_unlocks_dependencies_if_necessary_to_ensure_that_a_new_dependency_is_satisfied( root: ProjectPackage, repo: Repository, pool: RepositoryPool -): +) -> None: root.add_dependency(Factory.create_dependency("foo", "*")) root.add_dependency(Factory.create_dependency("newdep", "2.0.0")) @@ -126,7 +126,7 @@ def test_unlocks_dependencies_if_necessary_to_ensure_that_a_new_dependency_is_sa def test_with_compatible_locked_dependencies_use_latest( root: ProjectPackage, repo: Repository, pool: RepositoryPool -): +) -> None: root.add_dependency(Factory.create_dependency("foo", "*")) root.add_dependency(Factory.create_dependency("baz", "*")) @@ -156,7 +156,7 @@ def test_with_compatible_locked_dependencies_use_latest( def test_with_compatible_locked_dependencies_with_extras( root: ProjectPackage, repo: Repository, pool: RepositoryPool -): +) -> None: root.add_dependency(Factory.create_dependency("foo", "^1.0")) package_foo_0 = get_package("foo", "1.0.0") @@ -190,7 +190,7 @@ def test_with_compatible_locked_dependencies_with_extras( def test_with_yanked_package_in_lock( root: ProjectPackage, repo: Repository, pool: RepositoryPool -): +) -> None: root.add_dependency(Factory.create_dependency("foo", "*")) add_to_repo(repo, "foo", "1") @@ -205,6 +205,7 @@ def test_with_yanked_package_in_lock( provider, result={"foo": "2"}, ) + assert result is not None foo = result.packages[0] assert foo.yanked @@ -219,7 +220,7 @@ def test_with_yanked_package_in_lock( def test_no_update_is_respected_for_legacy_repository( root: ProjectPackage, repo: Repository, pool: RepositoryPool -): +) -> None: root.add_dependency(Factory.create_dependency("foo", "^1.0")) foo_100 = Package( diff --git a/tests/publishing/test_publisher.py b/tests/publishing/test_publisher.py index afc992f828a..5233785a854 100644 --- a/tests/publishing/test_publisher.py +++ b/tests/publishing/test_publisher.py @@ -23,7 +23,7 @@ def test_publish_publishes_to_pypi_by_default( fixture_dir: FixtureDirGetter, mocker: MockerFixture, config: Config -): +) -> None: uploader_auth = mocker.patch("poetry.publishing.uploader.Uploader.auth") uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload") poetry = Factory().create_poetry(fixture_dir("sample_project")) @@ -48,7 +48,7 @@ def test_publish_can_publish_to_given_repository( mocker: MockerFixture, config: Config, fixture_name: str, -): +) -> None: uploader_auth = mocker.patch("poetry.publishing.uploader.Uploader.auth") uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload") @@ -77,7 +77,7 @@ def test_publish_can_publish_to_given_repository( def test_publish_raises_error_for_undefined_repository( fixture_dir: FixtureDirGetter, config: Config -): +) -> None: poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry._config = config poetry.config.merge( @@ -91,7 +91,7 @@ def test_publish_raises_error_for_undefined_repository( def test_publish_uses_token_if_it_exists( fixture_dir: FixtureDirGetter, mocker: MockerFixture, config: Config -): +) -> None: uploader_auth = mocker.patch("poetry.publishing.uploader.Uploader.auth") uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload") poetry = Factory().create_poetry(fixture_dir("sample_project")) @@ -110,7 +110,7 @@ def test_publish_uses_token_if_it_exists( def test_publish_uses_cert( fixture_dir: FixtureDirGetter, mocker: MockerFixture, config: Config -): +) -> None: cert = "path/to/ca.pem" uploader_auth = mocker.patch("poetry.publishing.uploader.Uploader.auth") uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload") @@ -141,7 +141,7 @@ def test_publish_uses_cert( def test_publish_uses_client_cert( fixture_dir: FixtureDirGetter, mocker: MockerFixture, config: Config -): +) -> None: client_cert = "path/to/client.pem" uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload") poetry = Factory().create_poetry(fixture_dir("sample_project")) @@ -172,7 +172,7 @@ def test_publish_read_from_environment_variable( environ: None, mocker: MockerFixture, config: Config, -): +) -> None: os.environ["POETRY_REPOSITORIES_FOO_URL"] = "https://foo.bar" os.environ["POETRY_HTTP_BASIC_FOO_USERNAME"] = "bar" os.environ["POETRY_HTTP_BASIC_FOO_PASSWORD"] = "baz" diff --git a/tests/publishing/test_uploader.py b/tests/publishing/test_uploader.py index f0721d8f12d..9443d09fabb 100644 --- a/tests/publishing/test_uploader.py +++ b/tests/publishing/test_uploader.py @@ -26,7 +26,7 @@ def uploader(fixture_dir: FixtureDirGetter) -> Uploader: def test_uploader_properly_handles_400_errors( http: type[httpretty.httpretty], uploader: Uploader -): +) -> None: http.register_uri(http.POST, "https://foo.com", status=400, body="Bad request") with pytest.raises(UploadError) as e: @@ -37,7 +37,7 @@ def test_uploader_properly_handles_400_errors( def test_uploader_properly_handles_403_errors( http: type[httpretty.httpretty], uploader: Uploader -): +) -> None: http.register_uri(http.POST, "https://foo.com", status=403, body="Unauthorized") with pytest.raises(UploadError) as e: @@ -48,7 +48,7 @@ def test_uploader_properly_handles_403_errors( def test_uploader_properly_handles_nonstandard_errors( http: type[httpretty.httpretty], uploader: Uploader -): +) -> None: # content based off a true story. # Message changed to protect the ~~innocent~~ guilty. content = ( @@ -62,7 +62,7 @@ def test_uploader_properly_handles_nonstandard_errors( with pytest.raises(UploadError) as e: uploader.upload("https://foo.com") - assert str(e.value) == f"HTTP Error 400: Bad Request | {content}" + assert str(e.value) == f"HTTP Error 400: Bad Request | {content!r}" @pytest.mark.parametrize( @@ -79,7 +79,7 @@ def test_uploader_properly_handles_nonstandard_errors( ) def test_uploader_properly_handles_redirects( http: type[httpretty.httpretty], uploader: Uploader, status: int, body: str -): +) -> None: http.register_uri(http.POST, "https://foo.com", status=status, body=body) with pytest.raises(UploadError) as e: @@ -93,7 +93,7 @@ def test_uploader_properly_handles_redirects( def test_uploader_properly_handles_301_redirects( http: type[httpretty.httpretty], uploader: Uploader -): +) -> None: http.register_uri(http.POST, "https://foo.com", status=301, body="Redirect") with pytest.raises(UploadError) as e: @@ -107,7 +107,7 @@ def test_uploader_properly_handles_301_redirects( def test_uploader_registers_for_appropriate_400_errors( mocker: MockerFixture, http: type[httpretty.httpretty], uploader: Uploader -): +) -> None: register = mocker.patch("poetry.publishing.uploader.Uploader._register") http.register_uri( http.POST, "https://foo.com", status=400, body="No package was ever registered" @@ -131,7 +131,7 @@ def test_uploader_registers_for_appropriate_400_errors( ) def test_uploader_skips_existing( http: type[httpretty.httpretty], uploader: Uploader, status: int, body: str -): +) -> None: http.register_uri(http.POST, "https://foo.com", status=status, body=body) # should not raise @@ -140,7 +140,7 @@ def test_uploader_skips_existing( def test_uploader_skip_existing_bubbles_unskippable_errors( http: type[httpretty.httpretty], uploader: Uploader -): +) -> None: http.register_uri(http.POST, "https://foo.com", status=403, body="Unauthorized") with pytest.raises(UploadError): @@ -149,7 +149,7 @@ def test_uploader_skip_existing_bubbles_unskippable_errors( def test_uploader_properly_handles_file_not_existing( mocker: MockerFixture, http: type[httpretty.httpretty], uploader: Uploader -): +) -> None: mocker.patch("pathlib.Path.is_file", return_value=False) with pytest.raises(UploadError) as e: diff --git a/tests/puzzle/test_provider.py b/tests/puzzle/test_provider.py index 5e96d952832..9554a1fdae9 100644 --- a/tests/puzzle/test_provider.py +++ b/tests/puzzle/test_provider.py @@ -93,17 +93,7 @@ def test_search_for( repository.add_package(foo2a) repository.add_package(foo2) repository.add_package(foo3a) - # TODO: remove workaround when poetry-core with - # https://github.com/python-poetry/poetry-core/pull/543 is available - if str(dependency.constraint) == ">=1a": - result = provider.search_for(dependency) - assert result == expected or result == [ - Package("foo", "3a"), - Package("foo", "2"), - Package("foo", "2a"), - Package("foo", "1"), - ] - return + assert provider.search_for(dependency) == expected diff --git a/tests/repositories/test_legacy_repository.py b/tests/repositories/test_legacy_repository.py index bdb68935894..298d3f860f4 100644 --- a/tests/repositories/test_legacy_repository.py +++ b/tests/repositories/test_legacy_repository.py @@ -3,6 +3,7 @@ import base64 import re import shutil +import urllib.parse as urlparse from pathlib import Path from typing import TYPE_CHECKING @@ -21,11 +22,6 @@ from poetry.repositories.link_sources.html import SimpleRepositoryPage -try: - import urllib.parse as urlparse -except ImportError: - import urlparse - if TYPE_CHECKING: import httpretty diff --git a/tests/repositories/test_pypi_repository.py b/tests/repositories/test_pypi_repository.py index 35468e057b5..3c2c1a6b532 100644 --- a/tests/repositories/test_pypi_repository.py +++ b/tests/repositories/test_pypi_repository.py @@ -19,7 +19,6 @@ from poetry.repositories.exceptions import PackageNotFound from poetry.repositories.link_sources.json import SimpleJsonPage from poetry.repositories.pypi_repository import PyPiRepository -from poetry.utils._compat import encode if TYPE_CHECKING: @@ -302,10 +301,8 @@ def test_pypi_repository_supports_reading_bz2_files() -> None: ] } - for name in expected_extras.keys(): - assert ( - sorted(package.extras[name], key=lambda r: r.name) == expected_extras[name] - ) + for name, expected_extra in expected_extras.items(): + assert sorted(package.extras[name], key=lambda r: r.name) == expected_extra def test_invalid_versions_ignored() -> None: @@ -325,7 +322,7 @@ def test_get_should_invalid_cache_on_too_many_redirects_error( response = Response() response.status_code = 200 response.encoding = "utf-8" - response.raw = BytesIO(encode('{"foo": "bar"}')) + response.raw = BytesIO(b'{"foo": "bar"}') mocker.patch( "poetry.utils.authenticator.Authenticator.get", side_effect=[TooManyRedirects(), response], diff --git a/tests/types.py b/tests/types.py index c990414f52e..5cc9ea5bf24 100644 --- a/tests/types.py +++ b/tests/types.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing import Any from tests.compat import Protocol @@ -46,6 +47,7 @@ def __call__( poetry_lock_content: str | None = None, install_deps: bool = True, source: Path | None = None, + locker_config: dict[str, Any] | None = None, use_test_locker: bool = True, ) -> Poetry: ... diff --git a/tests/utils/test_env_site.py b/tests/utils/test_env_site.py index f3fdc9bb6d3..b1c493761e8 100644 --- a/tests/utils/test_env_site.py +++ b/tests/utils/test_env_site.py @@ -5,7 +5,6 @@ from pathlib import Path from typing import TYPE_CHECKING -from poetry.utils._compat import decode from poetry.utils.env import SitePackages @@ -23,7 +22,7 @@ def test_env_site_simple(tmp_dir: str, mocker: MockerFixture): assert len(candidates) == 1 assert candidates[0].as_posix() == hello.as_posix() - content = decode(str(uuid.uuid4())) + content = str(uuid.uuid4()) site_packages.write_text(Path("hello.txt"), content, encoding="utf-8") assert hello.read_text(encoding="utf-8") == content @@ -42,7 +41,7 @@ def test_env_site_select_first(tmp_dir: str): assert len(candidates) == 2 assert len(site_packages.find(Path("hello.txt"))) == 0 - content = decode(str(uuid.uuid4())) + content = str(uuid.uuid4()) site_packages.write_text(Path("hello.txt"), content, encoding="utf-8") assert (site_packages.path / "hello.txt").exists() From 3a31f2de4d3379cdddb84cd6601d215dd292ebe1 Mon Sep 17 00:00:00 2001 From: martin-kokos <4807476+martin-kokos@users.noreply.github.com> Date: Sat, 15 Apr 2023 18:22:53 +0200 Subject: [PATCH 142/151] tests: stop using tmp_dir fixture in favor of pytest.tmp_path fixture (#7412) --- .github/workflows/main.yml | 6 + src/poetry/utils/pip.py | 2 +- tests/conftest.py | 40 +-- tests/console/commands/env/conftest.py | 8 +- tests/console/commands/env/test_list.py | 6 +- .../commands/self/test_show_plugins.py | 6 +- tests/console/commands/test_new.py | 18 +- tests/console/conftest.py | 4 +- tests/inspection/test_info.py | 4 +- tests/installation/test_executor.py | 50 ++- tests/integration/test_utils_vcs_git.py | 6 +- .../masonry/builders/test_editable_builder.py | 12 +- tests/plugins/test_plugin_manager.py | 2 +- .../repositories/test_installed_repository.py | 4 +- tests/utils/test_env.py | 284 +++++++++--------- tests/utils/test_env_site.py | 13 +- tests/utils/test_pip.py | 6 +- 17 files changed, 232 insertions(+), 239 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a7d2ee447ba..df018e67b32 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,6 +64,12 @@ jobs: if: ${{ matrix.os == 'Windows' }} run: echo "$APPDATA\Python\Scripts" >> $GITHUB_PATH + - name: Enable long paths for git on Windows + if: ${{ matrix.os == 'Windows' }} + # Enable handling long path names (+260 char) on the Windows platform + # https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation + run: git config --system core.longpaths true + - name: Configure poetry run: poetry config virtualenvs.in-project true diff --git a/src/poetry/utils/pip.py b/src/poetry/utils/pip.py index 58b3504fba7..eb38bbf0b3b 100644 --- a/src/poetry/utils/pip.py +++ b/src/poetry/utils/pip.py @@ -55,4 +55,4 @@ def pip_install( try: return environment.run_pip(*args) except EnvCommandError as e: - raise PoetryException(f"Failed to install {path.as_posix()}") from e + raise PoetryException(f"Failed to install {path}") from e diff --git a/tests/conftest.py b/tests/conftest.py index 11e424894f6..98e6030dd18 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,7 +5,6 @@ import re import shutil import sys -import tempfile from contextlib import suppress from pathlib import Path @@ -28,7 +27,6 @@ from poetry.utils.env import EnvManager from poetry.utils.env import SystemEnv from poetry.utils.env import VirtualEnv -from poetry.utils.helpers import remove_directory from tests.helpers import MOCK_DEFAULT_GIT_REVISION from tests.helpers import TestLocker from tests.helpers import TestRepository @@ -169,8 +167,8 @@ def with_chained_null_keyring(mocker: MockerFixture) -> None: @pytest.fixture -def config_cache_dir(tmp_dir: str) -> Path: - path = Path(tmp_dir) / ".cache" / "pypoetry" +def config_cache_dir(tmp_path: Path) -> Path: + path = tmp_path / ".cache" / "pypoetry" path.mkdir(parents=True) return path @@ -219,8 +217,10 @@ def config( @pytest.fixture() -def config_dir(tmp_dir: str) -> Path: - return Path(tempfile.mkdtemp(prefix="poetry_config_", dir=tmp_dir)) +def config_dir(tmp_path: Path) -> Path: + path = tmp_path / "config" + path.mkdir() + return path @pytest.fixture(autouse=True) @@ -296,18 +296,8 @@ def _fixture_dir(name: str) -> Path: @pytest.fixture -def tmp_dir() -> Iterator[str]: - dir_ = tempfile.mkdtemp(prefix="poetry_") - path = Path(dir_) - - yield path.resolve().as_posix() - - remove_directory(path, force=True) - - -@pytest.fixture -def tmp_venv(tmp_dir: str) -> Iterator[VirtualEnv]: - venv_path = Path(tmp_dir) / "venv" +def tmp_venv(tmp_path: Path) -> Iterator[VirtualEnv]: + venv_path = tmp_path / "venv" EnvManager.build_venv(venv_path) @@ -348,14 +338,14 @@ def repo(http: type[httpretty.httpretty]) -> TestRepository: @pytest.fixture def project_factory( - tmp_dir: str, + tmp_path: Path, config: Config, repo: TestRepository, installed: Repository, default_python: str, load_required_fixtures: None, ) -> ProjectFactory: - workspace = Path(tmp_dir) + workspace = tmp_path def _factory( name: str | None = None, @@ -380,9 +370,7 @@ def _factory( project_dir.mkdir(parents=True, exist_ok=True) if pyproject_content: - with project_dir.joinpath("pyproject.toml").open( - "w", encoding="utf-8" - ) as f: + with (project_dir / "pyproject.toml").open("w", encoding="utf-8") as f: f.write(pyproject_content) else: assert name is not None @@ -446,10 +434,10 @@ def set_simple_log_formatter() -> None: @pytest.fixture -def fixture_copier(fixture_base: Path, tmp_dir: str) -> FixtureCopier: +def fixture_copier(fixture_base: Path, tmp_path: Path) -> FixtureCopier: def _copy(relative_path: str, target: Path | None = None) -> Path: - path = fixture_base.joinpath(relative_path) - target = target or Path(tmp_dir, relative_path) + path = fixture_base / relative_path + target = target or (tmp_path / relative_path) target.parent.mkdir(parents=True, exist_ok=True) if target.exists(): diff --git a/tests/console/commands/env/conftest.py b/tests/console/commands/env/conftest.py index 832efe5e5cc..4f613248cc0 100644 --- a/tests/console/commands/env/conftest.py +++ b/tests/console/commands/env/conftest.py @@ -2,7 +2,6 @@ import os -from pathlib import Path from typing import TYPE_CHECKING import pytest @@ -12,6 +11,7 @@ if TYPE_CHECKING: from collections.abc import Iterator + from pathlib import Path from tests.helpers import PoetryTestApplication @@ -25,8 +25,8 @@ def venv_name(app: PoetryTestApplication) -> str: @pytest.fixture -def venv_cache(tmp_dir: str) -> Path: - return Path(tmp_dir) +def venv_cache(tmp_path: Path) -> Path: + return tmp_path @pytest.fixture(scope="module") @@ -49,7 +49,7 @@ def venvs_in_cache_dirs( ) -> list[str]: directories = [] for version in python_versions: - directory = venv_cache.joinpath(f"{venv_name}-py{version}") + directory = venv_cache / f"{venv_name}-py{version}" directory.mkdir(parents=True, exist_ok=True) directories.append(directory.name) return directories diff --git a/tests/console/commands/env/test_list.py b/tests/console/commands/env/test_list.py index 6c949b66d50..687493faabb 100644 --- a/tests/console/commands/env/test_list.py +++ b/tests/console/commands/env/test_list.py @@ -39,7 +39,7 @@ def test_none_activated( ): mocker.patch("poetry.utils.env.EnvManager.get", return_value=env) tester.execute() - expected = "\n".join(venvs_in_cache_dirs).strip() + expected = "\n".join(venvs_in_cache_dirs) assert tester.io.fetch_output().strip() == expected @@ -50,9 +50,7 @@ def test_activated( venv_activate_37: None, ): tester.execute() - expected = ( - "\n".join(venvs_in_cache_dirs).strip().replace("py3.7", "py3.7 (Activated)") - ) + expected = "\n".join(venvs_in_cache_dirs).replace("py3.7", "py3.7 (Activated)") assert tester.io.fetch_output().strip() == expected diff --git a/tests/console/commands/self/test_show_plugins.py b/tests/console/commands/self/test_show_plugins.py index 3ce27786760..c2a93bc632a 100644 --- a/tests/console/commands/self/test_show_plugins.py +++ b/tests/console/commands/self/test_show_plugins.py @@ -1,6 +1,5 @@ from __future__ import annotations -from pathlib import Path from typing import TYPE_CHECKING from typing import Any from typing import Callable @@ -17,6 +16,7 @@ if TYPE_CHECKING: from os import PathLike + from pathlib import Path from cleo.io.io import IO from cleo.testers.command_tester import CommandTester @@ -64,7 +64,7 @@ def plugin_package(plugin_package_requires_dist: list[str]) -> Package: @pytest.fixture() -def plugin_distro(plugin_package: Package, tmp_dir: str) -> metadata.Distribution: +def plugin_distro(plugin_package: Package, tmp_path: Path) -> metadata.Distribution: class MockDistribution(metadata.Distribution): def read_text(self, filename: str) -> str | None: if filename == "METADATA": @@ -81,7 +81,7 @@ def read_text(self, filename: str) -> str | None: return None def locate_file(self, path: PathLike[str]) -> PathLike[str]: - return Path(tmp_dir, path) + return tmp_path / path return MockDistribution() diff --git a/tests/console/commands/test_new.py b/tests/console/commands/test_new.py index 58560b01e89..2b2cc9d83f2 100644 --- a/tests/console/commands/test_new.py +++ b/tests/console/commands/test_new.py @@ -155,18 +155,20 @@ def test_command_new( package_path: str, include_from: str | None, tester: CommandTester, - tmp_dir: str, + tmp_path: Path, ): - path = Path(tmp_dir) / directory - options.append(path.as_posix()) + path = tmp_path / directory + options.append(str(path)) tester.execute(" ".join(options)) verify_project_directory(path, package_name, package_path, include_from) @pytest.mark.parametrize(("fmt",), [(None,), ("md",), ("rst",), ("adoc",), ("creole",)]) -def test_command_new_with_readme(fmt: str | None, tester: CommandTester, tmp_dir: str): +def test_command_new_with_readme( + fmt: str | None, tester: CommandTester, tmp_path: Path +): package = "package" - path = Path(tmp_dir) / package + path = tmp_path / package options = [path.as_posix()] if fmt: @@ -191,7 +193,7 @@ def test_respect_prefer_active_on_new( config: Config, mocker: MockerFixture, tester: CommandTester, - tmp_dir: str, + tmp_path: Path, ): from poetry.utils.env import GET_PYTHON_VERSION_ONELINER @@ -208,8 +210,8 @@ def mock_check_output(cmd: str, *_: Any, **__: Any) -> str: config.config["virtualenvs"]["prefer-active-python"] = prefer_active package = "package" - path = Path(tmp_dir) / package - options = [path.as_posix()] + path = tmp_path / package + options = [str(path)] tester.execute(" ".join(options)) pyproject_file = path / "pyproject.toml" diff --git a/tests/console/conftest.py b/tests/console/conftest.py index eed6665335e..7813273e1a8 100644 --- a/tests/console/conftest.py +++ b/tests/console/conftest.py @@ -40,8 +40,8 @@ def installer() -> NoopInstaller: @pytest.fixture -def env(tmp_dir: str) -> MockEnv: - path = Path(tmp_dir) / ".venv" +def env(tmp_path: Path) -> MockEnv: + path = tmp_path / ".venv" path.mkdir(parents=True) return MockEnv(path=path, is_venv=True) diff --git a/tests/inspection/test_info.py b/tests/inspection/test_info.py index 65628f2ffb4..4b529e3bedc 100644 --- a/tests/inspection/test_info.py +++ b/tests/inspection/test_info.py @@ -36,7 +36,9 @@ def demo_wheel() -> Path: @pytest.fixture def source_dir(tmp_path: Path) -> Path: - return Path(tmp_path.as_posix()) + path = tmp_path / "source" + path.mkdir() + return path @pytest.fixture diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 05e2cdc648f..c8a529b40a7 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -91,8 +91,8 @@ def _prepare( @pytest.fixture -def env(tmp_dir: str) -> MockEnv: - path = Path(tmp_dir) / ".venv" +def env(tmp_path: Path) -> MockEnv: + path = tmp_path / ".venv" path.mkdir(parents=True) return MockEnv(path=path, is_venv=True) @@ -169,19 +169,16 @@ def callback( @pytest.fixture -def copy_wheel(tmp_dir: Path, fixture_dir: FixtureDirGetter) -> Callable[[], Path]: +def copy_wheel(tmp_path: Path, fixture_dir: FixtureDirGetter) -> Callable[[], Path]: def _copy_wheel() -> Path: tmp_name = tempfile.mktemp() - Path(tmp_dir).joinpath(tmp_name).mkdir() + (tmp_path / tmp_name).mkdir() shutil.copyfile( - ( - fixture_dir("distributions") / "demo-0.1.2-py2.py3-none-any.whl" - ).as_posix(), - (Path(tmp_dir) / tmp_name / "demo-0.1.2-py2.py3-none-any.whl").as_posix(), + fixture_dir("distributions") / "demo-0.1.2-py2.py3-none-any.whl", + tmp_path / tmp_name / "demo-0.1.2-py2.py3-none-any.whl", ) - - return Path(tmp_dir) / tmp_name / "demo-0.1.2-py2.py3-none-any.whl" + return tmp_path / tmp_name / "demo-0.1.2-py2.py3-none-any.whl" return _copy_wheel @@ -201,7 +198,7 @@ def test_execute_executes_a_batch_of_operations( config: Config, pool: RepositoryPool, io: BufferedIO, - tmp_dir: str, + tmp_path: Path, mock_file_downloads: None, env: MockEnv, copy_wheel: Callable[[], Path], @@ -209,7 +206,7 @@ def test_execute_executes_a_batch_of_operations( ): wheel_install = mocker.patch.object(WheelInstaller, "install") - config.merge({"cache-dir": tmp_dir}) + config.merge({"cache-dir": str(tmp_path)}) artifact_cache = ArtifactCache(cache_dir=config.artifacts_cache_directory) prepare_spy = mocker.spy(Chef, "_prepare") @@ -312,13 +309,13 @@ def test_execute_prints_warning_for_yanked_package( config: Config, pool: RepositoryPool, io: BufferedIO, - tmp_dir: str, + tmp_path: Path, mock_file_downloads: None, env: MockEnv, operations: list[Operation], has_warning: bool, ): - config.merge({"cache-dir": tmp_dir}) + config.merge({"cache-dir": str(tmp_path)}) executor = Executor(env, pool, config, io) @@ -345,11 +342,11 @@ def test_execute_prints_warning_for_invalid_wheels( config: Config, pool: RepositoryPool, io: BufferedIO, - tmp_dir: str, + tmp_path: Path, mock_file_downloads: None, env: MockEnv, ): - config.merge({"cache-dir": tmp_dir}) + config.merge({"cache-dir": str(tmp_path)}) executor = Executor(env, pool, config, io) @@ -460,11 +457,11 @@ def test_execute_works_with_ansi_output( config: Config, pool: RepositoryPool, io_decorated: BufferedIO, - tmp_dir: str, + tmp_path: Path, mock_file_downloads: None, env: MockEnv, ): - config.merge({"cache-dir": tmp_dir}) + config.merge({"cache-dir": str(tmp_path)}) executor = Executor(env, pool, config, io_decorated) @@ -497,11 +494,11 @@ def test_execute_works_with_no_ansi_output( config: Config, pool: RepositoryPool, io_not_decorated: BufferedIO, - tmp_dir: str, + tmp_path: Path, mock_file_downloads: None, env: MockEnv, ): - config.merge({"cache-dir": tmp_dir}) + config.merge({"cache-dir": str(tmp_path)}) executor = Executor(env, pool, config, io_not_decorated) @@ -581,7 +578,7 @@ def write_line(string: str, **kwargs: Any) -> None: def test_executor_should_delete_incomplete_downloads( config: Config, io: BufferedIO, - tmp_dir: str, + tmp_path: Path, mocker: MockerFixture, pool: RepositoryPool, mock_file_downloads: None, @@ -589,7 +586,7 @@ def test_executor_should_delete_incomplete_downloads( fixture_dir: FixtureDirGetter, ): fixture = fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" - destination_fixture = Path(tmp_dir) / "tomlkit-0.5.3-py2.py3-none-any.whl" + destination_fixture = tmp_path / "tomlkit-0.5.3-py2.py3-none-any.whl" shutil.copyfile(str(fixture), str(destination_fixture)) mocker.patch( "poetry.installation.executor.Executor._download_archive", @@ -601,10 +598,10 @@ def test_executor_should_delete_incomplete_downloads( ) mocker.patch( "poetry.installation.executor.ArtifactCache.get_cache_directory_for_link", - return_value=Path(tmp_dir), + return_value=tmp_path, ) - config.merge({"cache-dir": tmp_dir}) + config.merge({"cache-dir": str(tmp_path)}) executor = Executor(env, pool, config, io) @@ -1177,7 +1174,7 @@ def test_executor_fallback_on_poetry_create_error_without_wheel_installer( config: Config, pool: RepositoryPool, io: BufferedIO, - tmp_dir: str, + tmp_path: Path, mock_file_downloads: None, env: MockEnv, fixture_dir: FixtureDirGetter, @@ -1193,7 +1190,7 @@ def test_executor_fallback_on_poetry_create_error_without_wheel_installer( config.merge( { - "cache-dir": tmp_dir, + "cache-dir": str(tmp_path), "installer": {"modern-installation": False}, } ) @@ -1346,7 +1343,6 @@ def test_build_system_requires_not_available( config: Config, pool: RepositoryPool, io: BufferedIO, - tmp_dir: str, mock_file_downloads: None, env: MockEnv, fixture_dir: FixtureDirGetter, diff --git a/tests/integration/test_utils_vcs_git.py b/tests/integration/test_utils_vcs_git.py index aaa864283ce..ae979edf759 100644 --- a/tests/integration/test_utils_vcs_git.py +++ b/tests/integration/test_utils_vcs_git.py @@ -23,7 +23,7 @@ if TYPE_CHECKING: - from _pytest.tmpdir import TempdirFactory + from _pytest.tmpdir import TempPathFactory from dulwich.client import FetchPackResult from dulwich.client import GitClient from pytest_mock import MockerFixture @@ -79,9 +79,9 @@ def source_directory_name(source_url: str) -> str: @pytest.fixture(scope="module") -def local_repo(tmpdir_factory: TempdirFactory, source_directory_name: str) -> Repo: +def local_repo(tmp_path_factory: TempPathFactory, source_directory_name: str) -> Repo: with Repo.init( - tmpdir_factory.mktemp("src") / source_directory_name, mkdir=True + tmp_path_factory.mktemp("src") / source_directory_name, mkdir=True ) as repo: yield repo diff --git a/tests/masonry/builders/test_editable_builder.py b/tests/masonry/builders/test_editable_builder.py index 3e90a0b3e24..eab115b3181 100644 --- a/tests/masonry/builders/test_editable_builder.py +++ b/tests/masonry/builders/test_editable_builder.py @@ -83,8 +83,8 @@ def env_manager(simple_poetry: Poetry) -> EnvManager: @pytest.fixture -def tmp_venv(tmp_dir: str, env_manager: EnvManager) -> VirtualEnv: - venv_path = Path(tmp_dir) / "venv" +def tmp_venv(tmp_path: Path, env_manager: EnvManager) -> VirtualEnv: + venv_path = tmp_path / "venv" env_manager.build_venv(venv_path) @@ -222,10 +222,10 @@ def test_builder_installs_proper_files_for_standard_packages( def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts( - mocker: MockerFixture, extended_poetry: Poetry, tmp_dir: str + mocker: MockerFixture, extended_poetry: Poetry, tmp_path: Path ) -> None: pip_install = mocker.patch("poetry.masonry.builders.editable.pip_install") - env = MockEnv(path=Path(tmp_dir) / "foo") + env = MockEnv(path=tmp_path / "foo") builder = EditableBuilder(extended_poetry, env, NullIO()) builder.build() @@ -235,10 +235,10 @@ def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts( assert [] == env.executed -def test_builder_setup_generation_runs_with_pip_editable(tmp_dir: str) -> None: +def test_builder_setup_generation_runs_with_pip_editable(tmp_path: Path) -> None: # create an isolated copy of the project fixture = Path(__file__).parent.parent.parent / "fixtures" / "extended_project" - extended_project = Path(tmp_dir) / "extended_project" + extended_project = tmp_path / "extended_project" shutil.copytree(fixture, extended_project) assert extended_project.exists() diff --git a/tests/plugins/test_plugin_manager.py b/tests/plugins/test_plugin_manager.py index 868d61f26f0..a3fcc4249d1 100644 --- a/tests/plugins/test_plugin_manager.py +++ b/tests/plugins/test_plugin_manager.py @@ -47,7 +47,7 @@ def activate(self, poetry: Poetry, io: BufferedIO) -> None: @pytest.fixture() -def poetry(tmp_dir: str, config: Config) -> Poetry: +def poetry(tmp_path: Path, config: Config) -> Poetry: poetry = Poetry( CWD / "pyproject.toml", {}, diff --git a/tests/repositories/test_installed_repository.py b/tests/repositories/test_installed_repository.py index 5d71bcd7497..592d99a999a 100644 --- a/tests/repositories/test_installed_repository.py +++ b/tests/repositories/test_installed_repository.py @@ -101,9 +101,9 @@ def test_load_successful(repository: InstalledRepository): def test_load_successful_with_invalid_distribution( - caplog: LogCaptureFixture, mocker: MockerFixture, env: MockEnv, tmp_dir: str + caplog: LogCaptureFixture, mocker: MockerFixture, env: MockEnv, tmp_path: Path ) -> None: - invalid_dist_info = Path(tmp_dir) / "site-packages" / "invalid-0.1.0.dist-info" + invalid_dist_info = tmp_path / "site-packages" / "invalid-0.1.0.dist-info" invalid_dist_info.mkdir(parents=True) mocker.patch( "poetry.utils._compat.metadata.Distribution.discover", diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 5e1fb3cdbb9..c8589f48594 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -88,9 +88,9 @@ def manager(poetry: Poetry) -> EnvManager: def test_virtualenvs_with_spaces_in_their_path_work_as_expected( - tmp_dir: str, manager: EnvManager + tmp_path: Path, manager: EnvManager ) -> None: - venv_path = Path(tmp_dir) / "Virtual Env" + venv_path = tmp_path / "Virtual Env" manager.build_venv(venv_path) @@ -100,10 +100,10 @@ def test_virtualenvs_with_spaces_in_their_path_work_as_expected( @pytest.mark.skipif(sys.platform != "darwin", reason="requires darwin") -def test_venv_backup_exclusion(tmp_dir: str, manager: EnvManager) -> None: +def test_venv_backup_exclusion(tmp_path: Path, manager: EnvManager): import xattr - venv_path = Path(tmp_dir) / "Virtual Env" + venv_path = tmp_path / "Virtual Env" manager.build_venv(venv_path) @@ -121,9 +121,9 @@ def test_venv_backup_exclusion(tmp_dir: str, manager: EnvManager) -> None: def test_env_commands_with_spaces_in_their_arg_work_as_expected( - tmp_dir: str, manager: EnvManager + tmp_path: Path, manager: EnvManager ) -> None: - venv_path = Path(tmp_dir) / "Virtual Env" + venv_path = tmp_path / "Virtual Env" manager.build_venv(venv_path) venv = VirtualEnv(venv_path) assert venv.run("python", str(venv.pip), "--version").startswith( @@ -132,9 +132,9 @@ def test_env_commands_with_spaces_in_their_arg_work_as_expected( def test_env_shell_commands_with_stdinput_in_their_arg_work_as_expected( - tmp_dir: str, manager: EnvManager + tmp_path: Path, manager: EnvManager ) -> None: - venv_path = Path(tmp_dir) / "Virtual Env" + venv_path = tmp_path / "Virtual Env" manager.build_venv(venv_path) venv = VirtualEnv(venv_path) run_output_path = Path(venv.run("python", "-", input_=GET_BASE_PREFIX).strip()) @@ -143,9 +143,9 @@ def test_env_shell_commands_with_stdinput_in_their_arg_work_as_expected( def test_env_get_supported_tags_matches_inside_virtualenv( - tmp_dir: str, manager: EnvManager + tmp_path: Path, manager: EnvManager ) -> None: - venv_path = Path(tmp_dir) / "Virtual Env" + venv_path = tmp_path / "Virtual Env" manager.build_venv(venv_path) venv = VirtualEnv(venv_path) @@ -209,7 +209,7 @@ def check_output(cmd: list[str], *args: Any, **kwargs: Any) -> str: def test_activate_activates_non_existing_virtualenv_no_envs_file( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, @@ -219,7 +219,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( @@ -235,7 +235,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( env = manager.activate("python3.7") m.assert_called_with( - Path(tmp_dir) / f"{venv_name}-py3.7", + tmp_path / f"{venv_name}-py3.7", executable=Path("/usr/bin/python3.7"), flags={ "always-copy": False, @@ -246,18 +246,18 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( prompt="simple-project-py3.7", ) - envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") + envs_file = TOMLFile(tmp_path / "envs.toml") assert envs_file.exists() envs = envs_file.read() assert envs[venv_name]["minor"] == "3.7" assert envs[venv_name]["patch"] == "3.7.1" - assert env.path == Path(tmp_dir) / f"{venv_name}-py3.7" + assert env.path == tmp_path / f"{venv_name}-py3.7" assert env.base == Path("/prefix") def test_activate_fails_when_python_cannot_be_found( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, @@ -267,9 +267,9 @@ def test_activate_fails_when_python_cannot_be_found( if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] - os.mkdir(os.path.join(tmp_dir, f"{venv_name}-py3.7")) + os.mkdir(tmp_path / f"{venv_name}-py3.7") - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) mocker.patch("shutil.which", return_value=None) @@ -281,7 +281,7 @@ def test_activate_fails_when_python_cannot_be_found( def test_activate_activates_existing_virtualenv_no_envs_file( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, @@ -291,9 +291,9 @@ def test_activate_activates_existing_virtualenv_no_envs_file( if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] - os.mkdir(os.path.join(tmp_dir, f"{venv_name}-py3.7")) + os.mkdir(tmp_path / f"{venv_name}-py3.7") - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( @@ -310,18 +310,18 @@ def test_activate_activates_existing_virtualenv_no_envs_file( m.assert_not_called() - envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") + envs_file = TOMLFile(tmp_path / "envs.toml") assert envs_file.exists() envs = envs_file.read() assert envs[venv_name]["minor"] == "3.7" assert envs[venv_name]["patch"] == "3.7.1" - assert env.path == Path(tmp_dir) / f"{venv_name}-py3.7" + assert env.path == tmp_path / f"{venv_name}-py3.7" assert env.base == Path("/prefix") def test_activate_activates_same_virtualenv_with_envs_file( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, @@ -331,14 +331,14 @@ def test_activate_activates_same_virtualenv_with_envs_file( if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] - envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") + envs_file = TOMLFile(tmp_path / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": "3.7", "patch": "3.7.1"} envs_file.write(doc) - os.mkdir(os.path.join(tmp_dir, f"{venv_name}-py3.7")) + os.mkdir(tmp_path / f"{venv_name}-py3.7") - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( @@ -360,12 +360,12 @@ def test_activate_activates_same_virtualenv_with_envs_file( assert envs[venv_name]["minor"] == "3.7" assert envs[venv_name]["patch"] == "3.7.1" - assert env.path == Path(tmp_dir) / f"{venv_name}-py3.7" + assert env.path == tmp_path / f"{venv_name}-py3.7" assert env.base == Path("/prefix") def test_activate_activates_different_virtualenv_with_envs_file( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, @@ -375,14 +375,14 @@ def test_activate_activates_different_virtualenv_with_envs_file( if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] - envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") + envs_file = TOMLFile(tmp_path / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": "3.7", "patch": "3.7.1"} envs_file.write(doc) - os.mkdir(os.path.join(tmp_dir, f"{venv_name}-py3.7")) + os.mkdir(tmp_path / f"{venv_name}-py3.7") - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( @@ -398,7 +398,7 @@ def test_activate_activates_different_virtualenv_with_envs_file( env = manager.activate("python3.6") m.assert_called_with( - Path(tmp_dir) / f"{venv_name}-py3.6", + tmp_path / f"{venv_name}-py3.6", executable=Path("/usr/bin/python3.6"), flags={ "always-copy": False, @@ -414,12 +414,12 @@ def test_activate_activates_different_virtualenv_with_envs_file( assert envs[venv_name]["minor"] == "3.6" assert envs[venv_name]["patch"] == "3.6.6" - assert env.path == Path(tmp_dir) / f"{venv_name}-py3.6" + assert env.path == tmp_path / f"{venv_name}-py3.6" assert env.base == Path("/prefix") def test_activate_activates_recreates_for_different_patch( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, @@ -429,14 +429,14 @@ def test_activate_activates_recreates_for_different_patch( if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] - envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") + envs_file = TOMLFile(tmp_path / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": "3.7", "patch": "3.7.0"} envs_file.write(doc) - os.mkdir(os.path.join(tmp_dir, f"{venv_name}-py3.7")) + os.mkdir(tmp_path / f"{venv_name}-py3.7") - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( @@ -463,7 +463,7 @@ def test_activate_activates_recreates_for_different_patch( env = manager.activate("python3.7") build_venv_m.assert_called_with( - Path(tmp_dir) / f"{venv_name}-py3.7", + tmp_path / f"{venv_name}-py3.7", executable=Path("/usr/bin/python3.7"), flags={ "always-copy": False, @@ -473,20 +473,20 @@ def test_activate_activates_recreates_for_different_patch( }, prompt="simple-project-py3.7", ) - remove_venv_m.assert_called_with(Path(tmp_dir) / f"{venv_name}-py3.7") + remove_venv_m.assert_called_with(tmp_path / f"{venv_name}-py3.7") assert envs_file.exists() envs = envs_file.read() assert envs[venv_name]["minor"] == "3.7" assert envs[venv_name]["patch"] == "3.7.1" - assert env.path == Path(tmp_dir) / f"{venv_name}-py3.7" + assert env.path == tmp_path / f"{venv_name}-py3.7" assert env.base == Path("/prefix") - assert (Path(tmp_dir) / f"{venv_name}-py3.7").exists() + assert (tmp_path / f"{venv_name}-py3.7").exists() def test_activate_does_not_recreate_when_switching_minor( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, @@ -496,15 +496,15 @@ def test_activate_does_not_recreate_when_switching_minor( if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] - envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") + envs_file = TOMLFile(tmp_path / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": "3.7", "patch": "3.7.0"} envs_file.write(doc) - os.mkdir(os.path.join(tmp_dir, f"{venv_name}-py3.7")) - os.mkdir(os.path.join(tmp_dir, f"{venv_name}-py3.6")) + os.mkdir(tmp_path / f"{venv_name}-py3.7") + os.mkdir(tmp_path / f"{venv_name}-py3.6") - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( @@ -532,13 +532,13 @@ def test_activate_does_not_recreate_when_switching_minor( assert envs[venv_name]["minor"] == "3.6" assert envs[venv_name]["patch"] == "3.6.6" - assert env.path == Path(tmp_dir) / f"{venv_name}-py3.6" + assert env.path == tmp_path / f"{venv_name}-py3.6" assert env.base == Path("/prefix") - assert (Path(tmp_dir) / f"{venv_name}-py3.6").exists() + assert (tmp_path / f"{venv_name}-py3.6").exists() def test_deactivate_non_activated_but_existing( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, @@ -549,9 +549,9 @@ def test_deactivate_non_activated_but_existing( del os.environ["VIRTUAL_ENV"] python = ".".join(str(c) for c in sys.version_info[:2]) - (Path(tmp_dir) / f"{venv_name}-py{python}").mkdir() + (tmp_path / f"{venv_name}-py{python}").mkdir() - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) mocker.patch( "subprocess.check_output", @@ -561,11 +561,11 @@ def test_deactivate_non_activated_but_existing( manager.deactivate() env = manager.get() - assert env.path == Path(tmp_dir) / f"{venv_name}-py{python}" + assert env.path == tmp_path / f"{venv_name}-py{python}" def test_deactivate_activated( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, @@ -577,12 +577,10 @@ def test_deactivate_activated( version = Version.from_parts(*sys.version_info[:3]) other_version = Version.parse("3.4") if version.major == 2 else version.next_minor() - (Path(tmp_dir) / f"{venv_name}-py{version.major}.{version.minor}").mkdir() - ( - Path(tmp_dir) / f"{venv_name}-py{other_version.major}.{other_version.minor}" - ).mkdir() + (tmp_path / f"{venv_name}-py{version.major}.{version.minor}").mkdir() + (tmp_path / f"{venv_name}-py{other_version.major}.{other_version.minor}").mkdir() - envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") + envs_file = TOMLFile(tmp_path / "envs.toml") doc = tomlkit.document() doc[venv_name] = { "minor": f"{other_version.major}.{other_version.minor}", @@ -590,7 +588,7 @@ def test_deactivate_activated( } envs_file.write(doc) - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) mocker.patch( "subprocess.check_output", @@ -600,14 +598,14 @@ def test_deactivate_activated( manager.deactivate() env = manager.get() - assert env.path == Path(tmp_dir) / f"{venv_name}-py{version.major}.{version.minor}" + assert env.path == tmp_path / f"{venv_name}-py{version.major}.{version.minor}" envs = envs_file.read() assert len(envs) == 0 def test_get_prefers_explicitly_activated_virtualenvs_over_env_var( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, @@ -616,10 +614,10 @@ def test_get_prefers_explicitly_activated_virtualenvs_over_env_var( ) -> None: os.environ["VIRTUAL_ENV"] = "/environment/prefix" - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) - (Path(tmp_dir) / f"{venv_name}-py3.7").mkdir() + config.merge({"virtualenvs": {"path": str(tmp_path)}}) + (tmp_path / f"{venv_name}-py3.7").mkdir() - envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") + envs_file = TOMLFile(tmp_path / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": "3.7", "patch": "3.7.0"} envs_file.write(doc) @@ -635,41 +633,41 @@ def test_get_prefers_explicitly_activated_virtualenvs_over_env_var( env = manager.get() - assert env.path == Path(tmp_dir) / f"{venv_name}-py3.7" + assert env.path == tmp_path / f"{venv_name}-py3.7" assert env.base == Path("/prefix") def test_list( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, venv_name: str, ) -> None: - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) - (Path(tmp_dir) / f"{venv_name}-py3.7").mkdir() - (Path(tmp_dir) / f"{venv_name}-py3.6").mkdir() + (tmp_path / f"{venv_name}-py3.7").mkdir() + (tmp_path / f"{venv_name}-py3.6").mkdir() venvs = manager.list() assert len(venvs) == 2 - assert venvs[0].path == (Path(tmp_dir) / f"{venv_name}-py3.6") - assert venvs[1].path == (Path(tmp_dir) / f"{venv_name}-py3.7") + assert venvs[0].path == tmp_path / f"{venv_name}-py3.6" + assert venvs[1].path == tmp_path / f"{venv_name}-py3.7" def test_remove_by_python_version( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, mocker: MockerFixture, venv_name: str, ) -> None: - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) - (Path(tmp_dir) / f"{venv_name}-py3.7").mkdir() - (Path(tmp_dir) / f"{venv_name}-py3.6").mkdir() + (tmp_path / f"{venv_name}-py3.7").mkdir() + (tmp_path / f"{venv_name}-py3.6").mkdir() mocker.patch( "subprocess.check_output", @@ -678,23 +676,23 @@ def test_remove_by_python_version( venv = manager.remove("3.6") - expected_venv_path = Path(tmp_dir) / f"{venv_name}-py3.6" + expected_venv_path = tmp_path / f"{venv_name}-py3.6" assert venv.path == expected_venv_path assert not expected_venv_path.exists() def test_remove_by_name( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, mocker: MockerFixture, venv_name: str, ) -> None: - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) - (Path(tmp_dir) / f"{venv_name}-py3.7").mkdir() - (Path(tmp_dir) / f"{venv_name}-py3.6").mkdir() + (tmp_path / f"{venv_name}-py3.7").mkdir() + (tmp_path / f"{venv_name}-py3.6").mkdir() mocker.patch( "subprocess.check_output", @@ -703,23 +701,23 @@ def test_remove_by_name( venv = manager.remove(f"{venv_name}-py3.6") - expected_venv_path = Path(tmp_dir) / f"{venv_name}-py3.6" + expected_venv_path = tmp_path / f"{venv_name}-py3.6" assert venv.path == expected_venv_path assert not expected_venv_path.exists() def test_remove_by_string_with_python_and_version( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, mocker: MockerFixture, venv_name: str, ) -> None: - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) - (Path(tmp_dir) / f"{venv_name}-py3.7").mkdir() - (Path(tmp_dir) / f"{venv_name}-py3.6").mkdir() + (tmp_path / f"{venv_name}-py3.7").mkdir() + (tmp_path / f"{venv_name}-py3.6").mkdir() mocker.patch( "subprocess.check_output", @@ -728,30 +726,30 @@ def test_remove_by_string_with_python_and_version( venv = manager.remove("python3.6") - expected_venv_path = Path(tmp_dir) / f"{venv_name}-py3.6" + expected_venv_path = tmp_path / f"{venv_name}-py3.6" assert venv.path == expected_venv_path assert not expected_venv_path.exists() def test_remove_by_full_path_to_python( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, mocker: MockerFixture, venv_name: str, ) -> None: - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) - (Path(tmp_dir) / f"{venv_name}-py3.7").mkdir() - (Path(tmp_dir) / f"{venv_name}-py3.6").mkdir() + (tmp_path / f"{venv_name}-py3.7").mkdir() + (tmp_path / f"{venv_name}-py3.6").mkdir() mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) - expected_venv_path = Path(tmp_dir) / f"{venv_name}-py3.6" + expected_venv_path = tmp_path / f"{venv_name}-py3.6" python_path = expected_venv_path / "bin" / "python" venv = manager.remove(str(python_path)) @@ -761,16 +759,16 @@ def test_remove_by_full_path_to_python( def test_raises_if_acting_on_different_project_by_full_path( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, mocker: MockerFixture, ) -> None: - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) different_venv_name = "different-project" - different_venv_path = Path(tmp_dir) / f"{different_venv_name}-py3.6" + different_venv_path = tmp_path / f"{different_venv_name}-py3.6" different_venv_bin_path = different_venv_path / "bin" different_venv_bin_path.mkdir(parents=True) @@ -788,12 +786,12 @@ def test_raises_if_acting_on_different_project_by_full_path( def test_raises_if_acting_on_different_project_by_name( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, ) -> None: - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) different_venv_name = ( EnvManager.generate_env_name( @@ -802,7 +800,7 @@ def test_raises_if_acting_on_different_project_by_name( ) + "-py3.6" ) - different_venv_path = Path(tmp_dir) / different_venv_name + different_venv_path = tmp_path / different_venv_name different_venv_bin_path = different_venv_path / "bin" different_venv_bin_path.mkdir(parents=True) @@ -814,7 +812,7 @@ def test_raises_if_acting_on_different_project_by_name( def test_raises_when_passing_old_env_after_dir_rename( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, @@ -824,17 +822,17 @@ def test_raises_when_passing_old_env_after_dir_rename( # root directory of the project, which will create another venv with new name. # This is not ideal as you still "can't" remove it by name, but it at least doesn't # cause any unwanted side effects - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) previous_venv_name = EnvManager.generate_env_name( poetry.package.name, "previous_dir_name", ) - venv_path = Path(tmp_dir) / f"{venv_name}-py3.6" + venv_path = tmp_path / f"{venv_name}-py3.6" venv_path.mkdir() previous_venv_name = f"{previous_venv_name}-py3.6" - previous_venv_path = Path(tmp_dir) / previous_venv_name + previous_venv_path = tmp_path / previous_venv_name previous_venv_path.mkdir() with pytest.raises(IncorrectEnvError): @@ -842,31 +840,31 @@ def test_raises_when_passing_old_env_after_dir_rename( def test_remove_also_deactivates( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, mocker: MockerFixture, venv_name: str, ) -> None: - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) - (Path(tmp_dir) / f"{venv_name}-py3.7").mkdir() - (Path(tmp_dir) / f"{venv_name}-py3.6").mkdir() + (tmp_path / f"{venv_name}-py3.7").mkdir() + (tmp_path / f"{venv_name}-py3.6").mkdir() mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) - envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") + envs_file = TOMLFile(tmp_path / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": "3.6", "patch": "3.6.6"} envs_file.write(doc) venv = manager.remove("python3.6") - expected_venv_path = Path(tmp_dir) / f"{venv_name}-py3.6" + expected_venv_path = tmp_path / f"{venv_name}-py3.6" assert venv.path == expected_venv_path assert not expected_venv_path.exists() @@ -875,7 +873,7 @@ def test_remove_also_deactivates( def test_remove_keeps_dir_if_not_deleteable( - tmp_dir: str, + tmp_path: Path, manager: EnvManager, poetry: Poetry, config: Config, @@ -884,9 +882,9 @@ def test_remove_keeps_dir_if_not_deleteable( ) -> None: # Ensure we empty rather than delete folder if its is an active mount point. # See https://github.com/python-poetry/poetry/pull/2064 - config.merge({"virtualenvs": {"path": str(tmp_dir)}}) + config.merge({"virtualenvs": {"path": str(tmp_path)}}) - venv_path = Path(tmp_dir) / f"{venv_name}-py3.6" + venv_path = tmp_path / f"{venv_name}-py3.6" venv_path.mkdir() folder1_path = venv_path / "folder1" @@ -928,17 +926,17 @@ def err_on_rm_venv_only(path: Path | str, *args: Any, **kwargs: Any) -> None: @pytest.mark.skipif(os.name == "nt", reason="Symlinks are not support for Windows") -def test_env_has_symlinks_on_nix(tmp_dir: str, tmp_venv: VirtualEnv) -> None: +def test_env_has_symlinks_on_nix(tmp_path: Path, tmp_venv: VirtualEnv) -> None: assert os.path.islink(tmp_venv.python) -def test_run_with_input(tmp_dir: str, tmp_venv: VirtualEnv) -> None: +def test_run_with_input(tmp_path: Path, tmp_venv: VirtualEnv) -> None: result = tmp_venv.run("python", "-", input_=MINIMAL_SCRIPT) assert result == "Minimal Output" + os.linesep -def test_run_with_input_non_zero_return(tmp_dir: str, tmp_venv: VirtualEnv) -> None: +def test_run_with_input_non_zero_return(tmp_path: Path, tmp_venv: VirtualEnv) -> None: with pytest.raises(EnvCommandError) as process_error: # Test command that will return non-zero returncode. tmp_venv.run("python", "-", input_=ERRORING_SCRIPT) @@ -947,7 +945,7 @@ def test_run_with_input_non_zero_return(tmp_dir: str, tmp_venv: VirtualEnv) -> N def test_run_with_keyboard_interrupt( - tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture + tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture ) -> None: mocker.patch("subprocess.run", side_effect=KeyboardInterrupt()) with pytest.raises(KeyboardInterrupt): @@ -956,7 +954,7 @@ def test_run_with_keyboard_interrupt( def test_call_with_input_and_keyboard_interrupt( - tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture + tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture ) -> None: mocker.patch("subprocess.run", side_effect=KeyboardInterrupt()) kwargs = {"call": True} @@ -966,7 +964,7 @@ def test_call_with_input_and_keyboard_interrupt( def test_call_no_input_with_keyboard_interrupt( - tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture + tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture ) -> None: mocker.patch("subprocess.call", side_effect=KeyboardInterrupt()) kwargs = {"call": True} @@ -976,7 +974,7 @@ def test_call_no_input_with_keyboard_interrupt( def test_run_with_called_process_error( - tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture + tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture ) -> None: mocker.patch( "subprocess.run", @@ -992,7 +990,7 @@ def test_run_with_called_process_error( def test_call_with_input_and_called_process_error( - tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture + tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture ) -> None: mocker.patch( "subprocess.run", @@ -1009,7 +1007,7 @@ def test_call_with_input_and_called_process_error( def test_call_no_input_with_called_process_error( - tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture + tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture ) -> None: mocker.patch( "subprocess.call", @@ -1026,7 +1024,7 @@ def test_call_no_input_with_called_process_error( def test_check_output_with_called_process_error( - tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture + tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture ) -> None: mocker.patch( "subprocess.check_output", @@ -1067,7 +1065,7 @@ def target(result: list[int]) -> None: def test_run_python_script_called_process_error( - tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture + tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture ) -> None: mocker.patch( "subprocess.run", @@ -1081,7 +1079,7 @@ def test_run_python_script_called_process_error( assert "some error" in str(error.value) -def test_run_python_script_only_stdout(tmp_dir: str, tmp_venv: VirtualEnv) -> None: +def test_run_python_script_only_stdout(tmp_path: Path, tmp_venv: VirtualEnv) -> None: output = tmp_venv.run_python_script( "import sys; print('some warning', file=sys.stderr); print('some output')" ) @@ -1358,7 +1356,7 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( manager: EnvManager, poetry: Poetry, config: Config, - tmp_dir: str, + tmp_path: Path, mocker: MockerFixture, ) -> None: if "VIRTUAL_ENV" in os.environ: @@ -1367,7 +1365,7 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( config.merge( { "virtualenvs": { - "path": str(Path(tmp_dir) / "virtualenvs"), + "path": str(tmp_path / "virtualenvs"), "in-project": True, } } @@ -1398,7 +1396,7 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( prompt="simple-project-py3.7", ) - envs_file = TOMLFile(Path(tmp_dir) / "virtualenvs" / "envs.toml") + envs_file = TOMLFile(tmp_path / "virtualenvs" / "envs.toml") assert not envs_file.exists() @@ -1479,8 +1477,8 @@ def test_env_no_pip( assert installed_packages == packages -def test_env_finds_the_correct_executables(tmp_dir: str, manager: EnvManager) -> None: - venv_path = Path(tmp_dir) / "Virtual Env" +def test_env_finds_the_correct_executables(tmp_path: Path, manager: EnvManager) -> None: + venv_path = tmp_path / "Virtual Env" manager.build_venv(venv_path, with_pip=True) venv = VirtualEnv(venv_path) @@ -1510,10 +1508,10 @@ def test_env_finds_the_correct_executables(tmp_dir: str, manager: EnvManager) -> def test_env_finds_the_correct_executables_for_generic_env( - tmp_dir: str, manager: EnvManager + tmp_path: Path, manager: EnvManager ) -> None: - venv_path = Path(tmp_dir) / "Virtual Env" - child_venv_path = Path(tmp_dir) / "Child Virtual Env" + venv_path = tmp_path / "Virtual Env" + child_venv_path = tmp_path / "Child Virtual Env" manager.build_venv(venv_path, with_pip=True) parent_venv = VirtualEnv(venv_path) manager.build_venv(child_venv_path, executable=parent_venv.python, with_pip=True) @@ -1535,10 +1533,10 @@ def test_env_finds_the_correct_executables_for_generic_env( def test_env_finds_fallback_executables_for_generic_env( - tmp_dir: str, manager: EnvManager + tmp_path: Path, manager: EnvManager ) -> None: - venv_path = Path(tmp_dir) / "Virtual Env" - child_venv_path = Path(tmp_dir) / "Child Virtual Env" + venv_path = tmp_path / "Virtual Env" + child_venv_path = tmp_path / "Child Virtual Env" manager.build_venv(venv_path, with_pip=True) parent_venv = VirtualEnv(venv_path) manager.build_venv(child_venv_path, executable=parent_venv.python, with_pip=True) @@ -1646,7 +1644,7 @@ def mock_check_output(cmd: str, *args: Any, **kwargs: Any) -> str: def test_generate_env_name_ignores_case_for_case_insensitive_fs( poetry: Poetry, - tmp_dir: str, + tmp_path: Path, ) -> None: venv_name1 = EnvManager.generate_env_name(poetry.package.name, "MyDiR") venv_name2 = EnvManager.generate_env_name(poetry.package.name, "mYdIr") @@ -1656,7 +1654,9 @@ def test_generate_env_name_ignores_case_for_case_insensitive_fs( assert venv_name1 != venv_name2 -def test_generate_env_name_uses_real_path(tmp_dir: str, mocker: MockerFixture) -> None: +def test_generate_env_name_uses_real_path( + tmp_path: Path, mocker: MockerFixture +) -> None: mocker.patch("os.path.realpath", return_value="the_real_dir") venv_name1 = EnvManager.generate_env_name("simple-project", "the_real_dir") venv_name2 = EnvManager.generate_env_name("simple-project", "linked_dir") @@ -1673,10 +1673,10 @@ def extended_without_setup_poetry() -> Poetry: def test_build_environment_called_build_script_specified( - mocker: MockerFixture, extended_without_setup_poetry: Poetry, tmp_dir: str + mocker: MockerFixture, extended_without_setup_poetry: Poetry, tmp_path: Path ) -> None: - project_env = MockEnv(path=Path(tmp_dir) / "project") - ephemeral_env = MockEnv(path=Path(tmp_dir) / "ephemeral") + project_env = MockEnv(path=tmp_path / "project") + ephemeral_env = MockEnv(path=tmp_path / "ephemeral") mocker.patch( "poetry.utils.env.ephemeral_environment" @@ -1698,10 +1698,10 @@ def test_build_environment_called_build_script_specified( def test_build_environment_not_called_without_build_script_specified( - mocker: MockerFixture, poetry: Poetry, tmp_dir: str + mocker: MockerFixture, poetry: Poetry, tmp_path: Path ) -> None: - project_env = MockEnv(path=Path(tmp_dir) / "project") - ephemeral_env = MockEnv(path=Path(tmp_dir) / "ephemeral") + project_env = MockEnv(path=tmp_path / "project") + ephemeral_env = MockEnv(path=tmp_path / "ephemeral") mocker.patch( "poetry.utils.env.ephemeral_environment" diff --git a/tests/utils/test_env_site.py b/tests/utils/test_env_site.py index b1c493761e8..d4882b46530 100644 --- a/tests/utils/test_env_site.py +++ b/tests/utils/test_env_site.py @@ -12,12 +12,12 @@ from pytest_mock import MockerFixture -def test_env_site_simple(tmp_dir: str, mocker: MockerFixture): +def test_env_site_simple(tmp_path: Path, mocker: MockerFixture): # emulate permission error when creating directory mocker.patch("pathlib.Path.mkdir", side_effect=OSError()) - site_packages = SitePackages(Path("/non-existent"), fallbacks=[Path(tmp_dir)]) + site_packages = SitePackages(Path("/non-existent"), fallbacks=[tmp_path]) candidates = site_packages.make_candidates(Path("hello.txt"), writable_only=True) - hello = Path(tmp_dir) / "hello.txt" + hello = tmp_path / "hello.txt" assert len(candidates) == 1 assert candidates[0].as_posix() == hello.as_posix() @@ -30,12 +30,11 @@ def test_env_site_simple(tmp_dir: str, mocker: MockerFixture): assert not (site_packages.path / "hello.txt").exists() -def test_env_site_select_first(tmp_dir: str): - path = Path(tmp_dir) - fallback = path / "fallback" +def test_env_site_select_first(tmp_path: Path): + fallback = tmp_path / "fallback" fallback.mkdir(parents=True) - site_packages = SitePackages(path, fallbacks=[fallback]) + site_packages = SitePackages(tmp_path, fallbacks=[fallback]) candidates = site_packages.make_candidates(Path("hello.txt"), writable_only=True) assert len(candidates) == 2 diff --git a/tests/utils/test_pip.py b/tests/utils/test_pip.py index c6bf8422538..6f5c4851dab 100644 --- a/tests/utils/test_pip.py +++ b/tests/utils/test_pip.py @@ -10,6 +10,8 @@ if TYPE_CHECKING: + from pathlib import Path + from pytest_mock import MockerFixture from poetry.utils.env import VirtualEnv @@ -17,7 +19,7 @@ def test_pip_install_successful( - tmp_dir: str, tmp_venv: VirtualEnv, fixture_dir: FixtureDirGetter + tmp_path: Path, tmp_venv: VirtualEnv, fixture_dir: FixtureDirGetter ): file_path = fixture_dir("distributions/demo-0.1.0-py2.py3-none-any.whl") result = pip_install(file_path, tmp_venv) @@ -26,7 +28,7 @@ def test_pip_install_successful( def test_pip_install_with_keyboard_interrupt( - tmp_dir: str, + tmp_path: Path, tmp_venv: VirtualEnv, fixture_dir: FixtureDirGetter, mocker: MockerFixture, From 5806b42e4e75a903ad64f3045afa068ffe9ddfce Mon Sep 17 00:00:00 2001 From: Bart Kamphorst Date: Mon, 24 Oct 2022 22:54:59 +0200 Subject: [PATCH 143/151] sources: introduce "priority" key for sources and deprecate flags "default" and "secondary", adjust cli accordingly (#7658) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Randy Döring <30527984+radoering@users.noreply.github.com> --- docs/cli.md | 7 +- docs/dependency-specification.md | 2 +- docs/repositories.md | 74 ++++++-- src/poetry/config/source.py | 35 +++- src/poetry/console/commands/source/add.py | 76 +++++++-- src/poetry/console/commands/source/show.py | 18 +- src/poetry/factory.py | 38 +++-- src/poetry/json/schemas/poetry.json | 32 +++- src/poetry/repositories/repository_pool.py | 42 ++++- tests/console/commands/source/conftest.py | 50 +++++- tests/console/commands/source/test_add.py | 161 +++++++++++++++++- tests/console/commands/source/test_show.py | 99 ++++++++--- tests/console/commands/test_config.py | 2 +- .../with_default_source/pyproject.toml | 2 +- .../with_default_source_legacy/README.rst | 2 + .../with_default_source_legacy/pyproject.toml | 61 +++++++ .../pyproject.toml | 4 +- .../pyproject.toml | 24 +++ .../pyproject.toml | 2 +- .../pyproject.toml | 23 +++ .../pyproject.toml | 2 +- .../pyproject.toml | 19 +++ .../pyproject.toml | 19 +++ .../pyproject.toml | 0 .../with_two_default_sources/pyproject.toml | 4 +- .../README.rst | 2 + .../pyproject.toml | 66 +++++++ tests/installation/test_pip_installer.py | 7 +- .../source/complete_invalid_priority.toml | 17 ++ ...plete_invalid_priority_legacy_and_new.toml | 18 ++ .../fixtures/source/complete_invalid_url.toml | 15 ++ .../json/fixtures/source/complete_valid.toml | 3 +- ...nvalid.toml => complete_valid_legacy.toml} | 1 + tests/json/test_schema_sources.py | 39 ++++- tests/puzzle/test_solver.py | 5 +- tests/repositories/test_repository_pool.py | 52 ++++-- tests/test_factory.py | 144 +++++++++++----- tests/utils/test_source.py | 46 ++++- 38 files changed, 1014 insertions(+), 199 deletions(-) create mode 100644 tests/fixtures/with_default_source_legacy/README.rst create mode 100644 tests/fixtures/with_default_source_legacy/pyproject.toml create mode 100644 tests/fixtures/with_non_default_multiple_secondary_sources_legacy/pyproject.toml create mode 100644 tests/fixtures/with_non_default_multiple_sources_legacy/pyproject.toml create mode 100644 tests/fixtures/with_non_default_secondary_source_legacy/pyproject.toml create mode 100644 tests/fixtures/with_non_default_source_explicit/pyproject.toml rename tests/fixtures/{with_non_default_source => with_non_default_source_implicit}/pyproject.toml (100%) create mode 100644 tests/fixtures/with_two_default_sources_legacy/README.rst create mode 100644 tests/fixtures/with_two_default_sources_legacy/pyproject.toml create mode 100644 tests/json/fixtures/source/complete_invalid_priority.toml create mode 100644 tests/json/fixtures/source/complete_invalid_priority_legacy_and_new.toml create mode 100644 tests/json/fixtures/source/complete_invalid_url.toml rename tests/json/fixtures/source/{complete_invalid.toml => complete_valid_legacy.toml} (90%) diff --git a/docs/cli.md b/docs/cli.md index 9bef8f7a699..ae161d1d7ac 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -784,11 +784,12 @@ You cannot use the name `pypi` as it is reserved for use by the default PyPI sou #### Options -* `--default`: Set this source as the [default]({{< relref "repositories#disabling-the-pypi-repository" >}}) (disable PyPI). -* `--secondary`: Set this source as a [secondary]({{< relref "repositories#install-dependencies-from-a-private-repository" >}}) source. +* `--default`: Set this source as the [default]({{< relref "repositories#default-package-source" >}}) (disable PyPI). Deprecated in favor of `--priority`. +* `--secondary`: Set this source as a [secondary]({{< relref "repositories#secondary-package-sources" >}}) source. Deprecated in favor of `--priority`. +* `--priority`: Set the priority of this source. Accepted values are: [`default`]({{< relref "repositories#default-package-source" >}}), and [`secondary`]({{< relref "repositories#secondary-package-sources" >}}). Refer to the dedicated sections in [Repositories]({{< relref "repositories" >}}) for more information. {{% note %}} -You cannot set a source as both `default` and `secondary`. +At most one of the options above can be provided. See [package sources]({{< relref "repositories#package-sources" >}}) for more information. {{% /note %}} ### source show diff --git a/docs/dependency-specification.md b/docs/dependency-specification.md index 85521383b87..1d0d0b51a53 100644 --- a/docs/dependency-specification.md +++ b/docs/dependency-specification.md @@ -258,7 +258,7 @@ you can use the `source` property: [[tool.poetry.source]] name = "foo" url = "https://foo.bar/simple/" -secondary = true +priority = "secondary" [tool.poetry.dependencies] my-cool-package = { version = "*", source = "foo" } diff --git a/docs/repositories.md b/docs/repositories.md index 8d52a10994f..c8d92301532 100644 --- a/docs/repositories.md +++ b/docs/repositories.md @@ -33,7 +33,7 @@ First, [configure](#project-configuration) the [package source](#package-source) project. ```bash -poetry source add --secondary foo https://pypi.example.org/simple/ +poetry source add --priority=secondary foo https://pypi.example.org/simple/ ``` Then, assuming the repository requires authentication, configure credentials for it. @@ -120,12 +120,18 @@ This will generate the following configuration snippet in your [[tool.poetry.source]] name = "foo" url = "https://foo.bar/simple/" -default = false -secondary = false +priority = "primary" ``` -Any package source not marked as `secondary` will take precedence over [PyPI](https://pypi.org). +If `priority` is undefined, the source is considered a primary source that takes precedence over PyPI and secondary sources. +Package sources are considered in the following order: +1. [default source](#default-package-source), +2. primary sources, +3. PyPI (unless disabled by another default source), +4. [secondary sources](#secondary-package-sources), + +Within each priority class, package sources are considered in order of appearance in `pyproject.toml`. {{% note %}} @@ -148,10 +154,10 @@ you must declare **all** package sources to be [secondary](#secondary-package-so By default, Poetry configures [PyPI](https://pypi.org) as the default package source for your project. You can alter this behaviour and exclusively look up packages only from the configured -package sources by adding a **single** source with `default = true`. +package sources by adding a **single** source with `priority = "default"`. ```bash -poetry source add --default foo https://foo.bar/simple/ +poetry source add --priority=default foo https://foo.bar/simple/ ``` {{% warning %}} @@ -164,30 +170,31 @@ as a package source for your project. #### Secondary Package Sources If package sources are configured as secondary, all it means is that these will be given a lower -priority when selecting compatible package distribution that also exists in your default package -source. +priority when selecting compatible package distribution that also exists in your default and primary package sources. -You can configure a package source as a secondary source with `secondary = true` in your package +You can configure a package source as a secondary source with `priority = "secondary"` in your package source configuration. ```bash -poetry source add --secondary foo https://foo.bar/simple/ +poetry source add --priority=secondary https://foo.bar/simple/ ``` There can be more than one secondary package source. -{{% note %}} +#### Package Source Constraint All package sources (including secondary sources) will be searched during the package lookup process. These network requests will occur for all sources, regardless of if the package is found at one or more sources. -In order to limit the search for a specific package to a particular package repository, you can specify the source explicitly. This is strongly suggested for all private packages to avoid dependency confusion attacks. +In order to limit the search for a specific package to a particular package repository, you can specify the source explicitly. ```bash poetry add --source internal-pypi httpx ``` +This results in the following configuration in `pyproject.toml`: + ```toml [tool.poetry.dependencies] ... @@ -195,10 +202,47 @@ httpx = { version = "^0.22", source = "internal-pypi" } [[tool.poetry.source]] name = "internal-pypi" -url = "https://foo.bar/simple/" -secondary = true +url = ... +priority = ... ``` +{{% note %}} + +A repository that is configured to be the only source for retrieving a certain package can itself have any priority. +If a repository is configured to be the source of a package, it will be the only source that is considered for that package +and the repository priority will have no effect on the resolution. + +{{% /note %}} + +{{% note %}} + +Package `source` keys are not inherited by their dependencies. +In particular, if `package-A` is configured to be found in `source = internal-pypi`, +and `package-A` depends on `package-B` that is also to be found on `internal-pypi`, +then `package-B` needs to be configured as such in `pyproject.toml`. +The easiest way to achieve this is to add `package-B` with a wildcard constraint: + +```bash +poetry add --source internal-pypi package-B@* +``` + +This will ensure that `package-B` is searched only in the `internal-pypi` package source. +The version constraints on `package-B` are derived from `package-A` (and other client packages), as usual. + +If you want to avoid additional main dependencies, +you can add `package-B` to a dedicated [dependency group]({{< relref "managing-dependencies#dependency-groups" >}}): + +```bash +poetry add --group explicit --source internal-pypi package-B@* +``` + +{{% /note %}} + +{{% note %}} + +Package source constraints are strongly suggested for all packages that are expected +to be provided only by one specific source to avoid dependency confusion attacks. + {{% /note %}} ### Supported Package Sources @@ -231,7 +275,7 @@ httpx = {version = "^0.22.0", source = "pypi"} {{% warning %}} -If any source within a project is configured with `default = true`, The implicit `pypi` source will +If any source within a project is configured with `priority = "default"`, The implicit `pypi` source will be disabled and not used for any packages. {{% /warning %}} diff --git a/src/poetry/config/source.py b/src/poetry/config/source.py index f3af0c589e2..aa0f9499b08 100644 --- a/src/poetry/config/source.py +++ b/src/poetry/config/source.py @@ -1,14 +1,43 @@ from __future__ import annotations import dataclasses +import warnings + +from poetry.repositories.repository_pool import Priority @dataclasses.dataclass(order=True, eq=True) class Source: name: str url: str - default: bool = dataclasses.field(default=False) - secondary: bool = dataclasses.field(default=False) + default: dataclasses.InitVar[bool] = False + secondary: dataclasses.InitVar[bool] = False + priority: Priority = ( + Priority.PRIMARY + ) # cheating in annotation: str will be converted to Priority in __post_init__ + + def __post_init__(self, default: bool, secondary: bool) -> None: + if isinstance(self.priority, str): + self.priority = Priority[self.priority.upper()] + if default or secondary: + warnings.warn( + ( + "Parameters 'default' and 'secondary' to" + " 'Source' are deprecated. Please provide" + " 'priority' instead." + ), + DeprecationWarning, + stacklevel=2, + ) + if default: + self.priority = Priority.DEFAULT + elif secondary: + self.priority = Priority.SECONDARY def to_dict(self) -> dict[str, str | bool]: - return dataclasses.asdict(self) + return dataclasses.asdict( + self, + dict_factory=lambda x: { + k: v if not isinstance(v, Priority) else v.name.lower() for (k, v) in x + }, + ) diff --git a/src/poetry/console/commands/source/add.py b/src/poetry/console/commands/source/add.py index 81a0ca66e91..6875d444be8 100644 --- a/src/poetry/console/commands/source/add.py +++ b/src/poetry/console/commands/source/add.py @@ -7,6 +7,7 @@ from poetry.config.source import Source from poetry.console.commands.command import Command +from poetry.repositories.repository_pool import Priority class SourceAddCommand(Command): @@ -28,35 +29,74 @@ class SourceAddCommand(Command): ( "Set this source as the default (disable PyPI). A " "default source will also be the fallback source if " - "you add other sources." + "you add other sources. (Deprecated, use --priority)" ), ), - option("secondary", "s", "Set this source as secondary."), + option( + "secondary", + "s", + ( + "Set this source as secondary. (Deprecated, use" + " --priority)" + ), + ), + option( + "priority", + "p", + ( + "Set the priority of this source. One of:" + f" {', '.join(p.name.lower() for p in Priority)}. Defaults to" + f" {Priority.PRIMARY.name.lower()}." + ), + flag=False, + ), ] def handle(self) -> int: from poetry.factory import Factory from poetry.utils.source import source_to_table - name = self.argument("name") - url = self.argument("url") - is_default = self.option("default") - is_secondary = self.option("secondary") + name: str = self.argument("name") + url: str = self.argument("url") + is_default: bool = self.option("default", False) + is_secondary: bool = self.option("secondary", False) + priority: Priority | None = self.option("priority", None) if is_default and is_secondary: self.line_error( - "Cannot configure a source as both default and" - " secondary." + "Cannot configure a source as both default and" + " secondary." ) return 1 - new_source: Source | None = Source( - name=name, url=url, default=is_default, secondary=is_secondary - ) + if is_default or is_secondary: + if priority is not None: + self.line_error( + "Priority was passed through both --priority and a" + " deprecated flag (--default or --secondary). Please only provide" + " one of these." + ) + return 1 + else: + self.line_error( + "Warning: Priority was set through a deprecated flag" + " (--default or --secondary). Consider using --priority next" + " time." + ) + + if is_default: + priority = Priority.DEFAULT + elif is_secondary: + priority = Priority.SECONDARY + elif priority is None: + priority = Priority.PRIMARY + + new_source = Source(name=name, url=url, priority=priority) existing_sources = self.poetry.get_sources() sources = AoT([]) + is_new_source = True for source in existing_sources: if source == new_source: self.line( @@ -64,7 +104,10 @@ def handle(self) -> int: " addition." ) return 0 - elif source.default and is_default: + elif ( + source.priority is Priority.DEFAULT + and new_source.priority is Priority.DEFAULT + ): self.line_error( f"Source with name {source.name} is already set to" " default. Only one default source can be configured at a" @@ -72,16 +115,17 @@ def handle(self) -> int: ) return 1 - if new_source and source.name == name: - self.line(f"Source with name {name} already exists. Updating.") + if source.name == name: source = new_source - new_source = None + is_new_source = False sources.append(source_to_table(source)) - if new_source is not None: + if is_new_source: self.line(f"Adding source with name {name}.") sources.append(source_to_table(new_source)) + else: + self.line(f"Source with name {name} already exists. Updating.") # ensure new source is valid. eg: invalid name etc. try: diff --git a/src/poetry/console/commands/source/show.py b/src/poetry/console/commands/source/show.py index 8a89a39a55c..5014708d391 100644 --- a/src/poetry/console/commands/source/show.py +++ b/src/poetry/console/commands/source/show.py @@ -33,14 +33,12 @@ def handle(self) -> int: return 0 if names and not any(s.name in names for s in sources): - self.line_error(f"No source found with name(s): {', '.join(names)}") + self.line_error( + f"No source found with name(s): {', '.join(names)}", + style="error", + ) return 1 - bool_string = { - True: "yes", - False: "no", - } - for source in sources: if names and source.name not in names: continue @@ -50,12 +48,8 @@ def handle(self) -> int: ["name", f" : {source.name}"], ["url", f" : {source.url}"], [ - "default", - f" : {bool_string.get(source.default, False)}", - ], - [ - "secondary", - f" : {bool_string.get(source.secondary, False)}", + "priority", + f" : {source.priority.name.lower()}", ], ] table.add_rows(rows) diff --git a/src/poetry/factory.py b/src/poetry/factory.py index a2e7205f229..d1a46a654aa 100644 --- a/src/poetry/factory.py +++ b/src/poetry/factory.py @@ -123,6 +123,7 @@ def create_pool( disable_cache: bool = False, ) -> RepositoryPool: from poetry.repositories import RepositoryPool + from poetry.repositories.repository_pool import Priority if io is None: io = NullIO() @@ -136,31 +137,46 @@ def create_pool( repository = cls.create_package_source( source, auth_config, disable_cache=disable_cache ) - is_default = source.get("default", False) - is_secondary = source.get("secondary", False) + priority = Priority[source.get("priority", Priority.PRIMARY.name).upper()] + if "default" in source or "secondary" in source: + warning = ( + "Found deprecated key 'default' or 'secondary' in" + " pyproject.toml configuration for source" + f" {source.get('name')}. Please provide the key 'priority'" + " instead. Accepted values are:" + f" {', '.join(repr(p.name.lower()) for p in Priority)}." + ) + io.write_error_line(f"Warning: {warning}") + if source.get("default"): + priority = Priority.DEFAULT + elif source.get("secondary"): + priority = Priority.SECONDARY + if io.is_debug(): message = f"Adding repository {repository.name} ({repository.url})" - if is_default: + if priority is Priority.DEFAULT: message += " and setting it as the default one" - elif is_secondary: - message += " and setting it as secondary" + else: + message += f" and setting it as {priority.name.lower()}" io.write_line(message) - pool.add_repository(repository, is_default, secondary=is_secondary) + pool.add_repository(repository, priority=priority) - # Put PyPI last to prefer private repositories - # unless we have no default source AND no primary sources - # (default = false, secondary = false) + # Only add PyPI if no default repository is configured if pool.has_default(): if io.is_debug(): io.write_line("Deactivating the PyPI repository") else: from poetry.repositories.pypi_repository import PyPiRepository - default = not pool.has_primary_repositories() + if pool.has_primary_repositories(): + pypi_priority = Priority.SECONDARY + else: + pypi_priority = Priority.DEFAULT + pool.add_repository( - PyPiRepository(disable_cache=disable_cache), default, not default + PyPiRepository(disable_cache=disable_cache), priority=pypi_priority ) return pool diff --git a/src/poetry/json/schemas/poetry.json b/src/poetry/json/schemas/poetry.json index 7532fd836b4..04d0ff340d7 100644 --- a/src/poetry/json/schemas/poetry.json +++ b/src/poetry/json/schemas/poetry.json @@ -26,20 +26,28 @@ "properties": { "name": { "type": "string", - "description": "The name of the repository" + "description": "The name of the repository." }, "url": { "type": "string", - "description": "The url of the repository", + "description": "The url of the repository.", "format": "uri" }, "default": { "type": "boolean", - "description": "Make this repository the default (disable PyPI)" + "description": "Make this repository the default (disable PyPI). (deprecated, see priority)" }, "secondary": { "type": "boolean", - "description": "Declare this repository as secondary, i.e. it will only be looked up last for packages." + "description": "Declare this repository as secondary, i.e. default repositories take precedence. (deprecated, see priority)" + }, + "priority": { + "enum": [ + "primary", + "default", + "secondary" + ], + "description": "Declare the priority of this repository." }, "links": { "type": "boolean", @@ -49,6 +57,22 @@ "type": "boolean", "description": "For PEP 503 simple API repositories, pre-fetch and index the available packages. (experimental)" } + }, + "not": { + "anyOf": [ + { + "required": [ + "priority", + "default" + ] + }, + { + "required": [ + "priority", + "secondary" + ] + } + ] } } } diff --git a/src/poetry/repositories/repository_pool.py b/src/poetry/repositories/repository_pool.py index 79d70479c82..a4dffbbff37 100644 --- a/src/poetry/repositories/repository_pool.py +++ b/src/poetry/repositories/repository_pool.py @@ -1,6 +1,7 @@ from __future__ import annotations import enum +import warnings from collections import OrderedDict from dataclasses import dataclass @@ -71,13 +72,24 @@ def has_repository(self, name: str) -> bool: return name.lower() in self._repositories def repository(self, name: str) -> Repository: + return self._get_prioritized_repository(name).repository + + def get_priority(self, name: str) -> Priority: + return self._get_prioritized_repository(name).priority + + def _get_prioritized_repository(self, name: str) -> PrioritizedRepository: name = name.lower() if self.has_repository(name): - return self._repositories[name].repository + return self._repositories[name] raise IndexError(f'Repository "{name}" does not exist.') def add_repository( - self, repository: Repository, default: bool = False, secondary: bool = False + self, + repository: Repository, + default: bool = False, + secondary: bool = False, + *, + priority: Priority = Priority.PRIMARY, ) -> RepositoryPool: """ Adds a repository to the pool. @@ -88,14 +100,24 @@ def add_repository( f"A repository with name {repository_name} was already added." ) - if default and self.has_default(): + if default or secondary: + warnings.warn( + ( + "Parameters 'default' and 'secondary' to" + " 'RepositoryPool.add_repository' are deprecated. Please provide" + " the keyword-argument 'priority' instead." + ), + DeprecationWarning, + stacklevel=2, + ) + if default: + priority = Priority.DEFAULT + else: + priority = Priority.SECONDARY + + if priority is Priority.DEFAULT and self.has_default(): raise ValueError("Only one repository can be the default.") - priority = Priority.PRIMARY - if default: - priority = Priority.DEFAULT - elif secondary: - priority = Priority.SECONDARY self._repositories[repository_name] = PrioritizedRepository( repository, priority ) @@ -103,7 +125,9 @@ def add_repository( def remove_repository(self, name: str) -> RepositoryPool: if not self.has_repository(name): - raise IndexError(f"Pool can not remove unknown repository '{name}'.") + raise IndexError( + f"RepositoryPool can not remove unknown repository '{name}'." + ) del self._repositories[name.lower()] return self diff --git a/tests/console/commands/source/conftest.py b/tests/console/commands/source/conftest.py index 254b3454297..f9db68a058a 100644 --- a/tests/console/commands/source/conftest.py +++ b/tests/console/commands/source/conftest.py @@ -5,6 +5,7 @@ import pytest from poetry.config.source import Source +from poetry.repositories.repository_pool import Priority if TYPE_CHECKING: @@ -24,15 +25,32 @@ def source_two() -> Source: @pytest.fixture -def source_default() -> Source: +def source_default_deprecated() -> Source: return Source(name="default", url="https://default.com", default=True) @pytest.fixture -def source_secondary() -> Source: +def source_secondary_deprecated() -> Source: return Source(name="secondary", url="https://secondary.com", secondary=True) +@pytest.fixture +def source_primary() -> Source: + return Source(name="primary", url="https://primary.com", priority=Priority.PRIMARY) + + +@pytest.fixture +def source_default() -> Source: + return Source(name="default", url="https://default.com", priority=Priority.DEFAULT) + + +@pytest.fixture +def source_secondary() -> Source: + return Source( + name="secondary", url="https://secondary.com", priority=Priority.SECONDARY + ) + + _existing_source = Source(name="existing", url="https://existing.com") @@ -41,7 +59,7 @@ def source_existing() -> Source: return _existing_source -PYPROJECT_WITH_SOURCES = f""" +PYPROJECT_WITHOUT_SOURCES = """ [tool.poetry] name = "source-command-test" version = "0.1.0" @@ -52,6 +70,10 @@ def source_existing() -> Source: python = "^3.9" [tool.poetry.dev-dependencies] +""" + + +PYPROJECT_WITH_SOURCES = f"""{PYPROJECT_WITHOUT_SOURCES} [[tool.poetry.source]] name = "{_existing_source.name}" @@ -59,6 +81,11 @@ def source_existing() -> Source: """ +@pytest.fixture +def poetry_without_source(project_factory: ProjectFactory) -> Poetry: + return project_factory(pyproject_content=PYPROJECT_WITHOUT_SOURCES) + + @pytest.fixture def poetry_with_source(project_factory: ProjectFactory) -> Poetry: return project_factory(pyproject_content=PYPROJECT_WITH_SOURCES) @@ -74,3 +101,20 @@ def add_multiple_sources( add = command_tester_factory("source add", poetry=poetry_with_source) for source in [source_one, source_two]: add.execute(f"{source.name} {source.url}") + + +@pytest.fixture +def add_all_source_types( + command_tester_factory: CommandTesterFactory, + poetry_with_source: Poetry, + source_primary: Source, + source_default: Source, + source_secondary: Source, +) -> None: + add = command_tester_factory("source add", poetry=poetry_with_source) + for source in [ + source_primary, + source_default, + source_secondary, + ]: + add.execute(f"{source.name} {source.url} --priority={source.name}") diff --git a/tests/console/commands/source/test_add.py b/tests/console/commands/source/test_add.py index 40a53192a4c..d25239c052c 100644 --- a/tests/console/commands/source/test_add.py +++ b/tests/console/commands/source/test_add.py @@ -1,16 +1,16 @@ from __future__ import annotations -import dataclasses - from typing import TYPE_CHECKING import pytest +from poetry.config.source import Source +from poetry.repositories.repository_pool import Priority + if TYPE_CHECKING: from cleo.testers.command_tester import CommandTester - from poetry.config.source import Source from poetry.poetry import Poetry from tests.types import CommandTesterFactory @@ -22,6 +22,28 @@ def tester( return command_tester_factory("source add", poetry=poetry_with_source) +def assert_source_added_legacy( + tester: CommandTester, + poetry: Poetry, + source_existing: Source, + source_added: Source, +) -> None: + assert ( + tester.io.fetch_error().strip() + == "Warning: Priority was set through a deprecated flag" + " (--default or --secondary). Consider using --priority next" + " time." + ) + assert ( + tester.io.fetch_output().strip() + == f"Adding source with name {source_added.name}." + ) + poetry.pyproject.reload() + sources = poetry.get_sources() + assert sources == [source_existing, source_added] + assert tester.status_code == 0 + + def assert_source_added( tester: CommandTester, poetry: Poetry, @@ -48,27 +70,73 @@ def test_source_add_simple( assert_source_added(tester, poetry_with_source, source_existing, source_one) -def test_source_add_default( +def test_source_add_default_legacy( tester: CommandTester, source_existing: Source, source_default: Source, poetry_with_source: Poetry, ) -> None: tester.execute(f"--default {source_default.name} {source_default.url}") + assert_source_added_legacy( + tester, poetry_with_source, source_existing, source_default + ) + + +def test_source_add_secondary_legacy( + tester: CommandTester, + source_existing: Source, + source_secondary: Source, + poetry_with_source: Poetry, +): + tester.execute(f"--secondary {source_secondary.name} {source_secondary.url}") + assert_source_added_legacy( + tester, poetry_with_source, source_existing, source_secondary + ) + + +def test_source_add_default( + tester: CommandTester, + source_existing: Source, + source_default: Source, + poetry_with_source: Poetry, +): + tester.execute(f"--priority=default {source_default.name} {source_default.url}") assert_source_added(tester, poetry_with_source, source_existing, source_default) +def test_source_add_second_default_fails( + tester: CommandTester, + source_existing: Source, + source_default: Source, + poetry_with_source: Poetry, +): + tester.execute(f"--priority=default {source_default.name} {source_default.url}") + assert_source_added(tester, poetry_with_source, source_existing, source_default) + poetry_with_source.pyproject.reload() + + tester.execute(f"--priority=default {source_default.name}1 {source_default.url}") + assert ( + tester.io.fetch_error().strip() + == f"Source with name {source_default.name} is already set to" + " default. Only one default source can be configured at a" + " time." + ) + assert tester.status_code == 1 + + def test_source_add_secondary( tester: CommandTester, source_existing: Source, source_secondary: Source, poetry_with_source: Poetry, ) -> None: - tester.execute(f"--secondary {source_secondary.name} {source_secondary.url}") + tester.execute( + f"--priority=secondary {source_secondary.name} {source_secondary.url}" + ) assert_source_added(tester, poetry_with_source, source_existing, source_secondary) -def test_source_add_error_default_and_secondary(tester: CommandTester) -> None: +def test_source_add_error_default_and_secondary_legacy(tester: CommandTester) -> None: tester.execute("--default --secondary error https://error.com") assert ( tester.io.fetch_error().strip() @@ -77,6 +145,17 @@ def test_source_add_error_default_and_secondary(tester: CommandTester) -> None: assert tester.status_code == 1 +def test_source_add_error_priority_and_deprecated_legacy(tester: CommandTester): + tester.execute("--priority secondary --secondary error https://error.com") + assert ( + tester.io.fetch_error().strip() + == "Priority was passed through both --priority and a" + " deprecated flag (--default or --secondary). Please only provide" + " one of these." + ) + assert tester.status_code == 1 + + def test_source_add_error_pypi(tester: CommandTester) -> None: tester.execute("pypi https://test.pypi.org/simple/") assert ( @@ -87,10 +166,16 @@ def test_source_add_error_pypi(tester: CommandTester) -> None: assert tester.status_code == 1 -def test_source_add_existing( +def test_source_add_existing_legacy( tester: CommandTester, source_existing: Source, poetry_with_source: Poetry ) -> None: tester.execute(f"--default {source_existing.name} {source_existing.url}") + assert ( + tester.io.fetch_error().strip() + == "Warning: Priority was set through a deprecated flag" + " (--default or --secondary). Consider using --priority next" + " time." + ) assert ( tester.io.fetch_output().strip() == f"Source with name {source_existing.name} already exists. Updating." @@ -101,4 +186,64 @@ def test_source_add_existing( assert len(sources) == 1 assert sources[0] != source_existing - assert sources[0] == dataclasses.replace(source_existing, default=True) + expected_source = Source( + name=source_existing.name, url=source_existing.url, priority=Priority.DEFAULT + ) + assert sources[0] == expected_source + + +def test_source_add_existing_no_change( + tester: CommandTester, source_existing: Source, poetry_with_source: Poetry +): + tester.execute(f"--priority=primary {source_existing.name} {source_existing.url}") + assert ( + tester.io.fetch_output().strip() + == f"Source with name {source_existing.name} already exists. Skipping addition." + ) + + poetry_with_source.pyproject.reload() + sources = poetry_with_source.get_sources() + + assert len(sources) == 1 + assert sources[0] == source_existing + + +def test_source_add_existing_updating( + tester: CommandTester, source_existing: Source, poetry_with_source: Poetry +): + tester.execute(f"--priority=default {source_existing.name} {source_existing.url}") + assert ( + tester.io.fetch_output().strip() + == f"Source with name {source_existing.name} already exists. Updating." + ) + + poetry_with_source.pyproject.reload() + sources = poetry_with_source.get_sources() + + assert len(sources) == 1 + assert sources[0] != source_existing + expected_source = Source( + name=source_existing.name, url=source_existing.url, priority=Priority.DEFAULT + ) + assert sources[0] == expected_source + + +def test_source_add_existing_fails_due_to_other_default( + tester: CommandTester, + source_existing: Source, + source_default: Source, + poetry_with_source: Poetry, +): + tester.execute(f"--priority=default {source_default.name} {source_default.url}") + tester.io.fetch_output() + + tester.execute(f"--priority=default {source_existing.name} {source_existing.url}") + + assert ( + tester.io.fetch_error().strip() + == f"Source with name {source_default.name} is already set to" + " default. Only one default source can be configured at a" + " time." + ) + assert tester.io.fetch_output().strip() == "" + assert tester.status_code == 1 diff --git a/tests/console/commands/source/test_show.py b/tests/console/commands/source/test_show.py index d25b9788365..b636d1bd30a 100644 --- a/tests/console/commands/source/test_show.py +++ b/tests/console/commands/source/test_show.py @@ -22,24 +22,38 @@ def tester( return command_tester_factory("source show", poetry=poetry_with_source) +@pytest.fixture +def tester_no_sources( + command_tester_factory: CommandTesterFactory, + poetry_without_source: Poetry, +) -> CommandTester: + return command_tester_factory("source show", poetry=poetry_without_source) + + +@pytest.fixture +def tester_all_types( + command_tester_factory: CommandTesterFactory, + poetry_with_source: Poetry, + add_all_source_types: None, +) -> CommandTester: + return command_tester_factory("source show", poetry=poetry_with_source) + + def test_source_show_simple(tester: CommandTester) -> None: tester.execute("") expected = """\ -name : existing -url : https://existing.com -default : no -secondary : no - -name : one -url : https://one.com -default : no -secondary : no - -name : two -url : https://two.com -default : no -secondary : no +name : existing +url : https://existing.com +priority : primary + +name : one +url : https://one.com +priority : primary + +name : two +url : https://two.com +priority : primary """.splitlines() assert [ line.strip() for line in tester.io.fetch_output().strip().splitlines() @@ -51,10 +65,9 @@ def test_source_show_one(tester: CommandTester, source_one: Source) -> None: tester.execute(f"{source_one.name}") expected = """\ -name : one -url : https://one.com -default : no -secondary : no +name : one +url : https://one.com +priority : primary """.splitlines() assert [ line.strip() for line in tester.io.fetch_output().strip().splitlines() @@ -68,15 +81,13 @@ def test_source_show_two( tester.execute(f"{source_one.name} {source_two.name}") expected = """\ -name : one -url : https://one.com -default : no -secondary : no - -name : two -url : https://two.com -default : no -secondary : no +name : one +url : https://one.com +priority : primary + +name : two +url : https://two.com +priority : primary """.splitlines() assert [ line.strip() for line in tester.io.fetch_output().strip().splitlines() @@ -84,6 +95,40 @@ def test_source_show_two( assert tester.status_code == 0 +@pytest.mark.parametrize( + "source_str", + ( + "source_primary", + "source_default", + "source_secondary", + ), +) +def test_source_show_given_priority( + tester_all_types: CommandTester, source_str: Source, request: pytest.FixtureRequest +) -> None: + source = request.getfixturevalue(source_str) + tester_all_types.execute(f"{source.name}") + + expected = f"""\ +name : {source.name} +url : {source.url} +priority : {source.name} +""".splitlines() + assert [ + line.strip() for line in tester_all_types.io.fetch_output().strip().splitlines() + ] == expected + assert tester_all_types.status_code == 0 + + +def test_source_show_no_sources(tester_no_sources: CommandTester) -> None: + tester_no_sources.execute("error") + assert ( + tester_no_sources.io.fetch_output().strip() + == "No sources configured for this project." + ) + assert tester_no_sources.status_code == 0 + + def test_source_show_error(tester: CommandTester) -> None: tester.execute("error") assert tester.io.fetch_error().strip() == "No source found with name(s): error" diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index 283146d2c31..975132f4bc0 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -164,7 +164,7 @@ def test_list_must_not_display_sources_from_pyproject_toml( config: Config, config_cache_dir: Path, ): - source = fixture_dir("with_non_default_source") + source = fixture_dir("with_non_default_source_implicit") pyproject_content = (source / "pyproject.toml").read_text(encoding="utf-8") poetry = project_factory("foo", pyproject_content=pyproject_content) tester = command_tester_factory("config", poetry=poetry) diff --git a/tests/fixtures/with_default_source/pyproject.toml b/tests/fixtures/with_default_source/pyproject.toml index 0d639ec25d7..7a274d6201d 100644 --- a/tests/fixtures/with_default_source/pyproject.toml +++ b/tests/fixtures/with_default_source/pyproject.toml @@ -58,4 +58,4 @@ my-script = "my_package:main" [[tool.poetry.source]] name = "foo" url = "https://foo.bar/simple/" -default = true +priority = "default" diff --git a/tests/fixtures/with_default_source_legacy/README.rst b/tests/fixtures/with_default_source_legacy/README.rst new file mode 100644 index 00000000000..f7fe15470f9 --- /dev/null +++ b/tests/fixtures/with_default_source_legacy/README.rst @@ -0,0 +1,2 @@ +My Package +========== diff --git a/tests/fixtures/with_default_source_legacy/pyproject.toml b/tests/fixtures/with_default_source_legacy/pyproject.toml new file mode 100644 index 00000000000..0d639ec25d7 --- /dev/null +++ b/tests/fixtures/with_default_source_legacy/pyproject.toml @@ -0,0 +1,61 @@ +[tool.poetry] +name = "my-package" +version = "1.2.3" +description = "Some description." +authors = [ + "Sébastien Eustace " +] +license = "MIT" + +readme = "README.rst" + +homepage = "https://python-poetry.org" +repository = "https://github.com/python-poetry/poetry" +documentation = "https://python-poetry.org/docs" + +keywords = ["packaging", "dependency", "poetry"] + +classifiers = [ + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Libraries :: Python Modules" +] + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.6" +cleo = "^0.6" +pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" } +requests = { version = "^2.18", optional = true, extras=[ "security" ] } +pathlib2 = { version = "^2.2", python = "~2.7" } + +orator = { version = "^0.9", optional = true } + +# File dependency +demo = { path = "../distributions/demo-0.1.0-py2.py3-none-any.whl" } + +# Dir dependency with setup.py +my-package = { path = "../project_with_setup/" } + +# Dir dependency with pyproject.toml +simple-project = { path = "../simple_project/" } + + +[tool.poetry.extras] +db = [ "orator" ] + +[tool.poetry.dev-dependencies] +pytest = "~3.4" + + +[tool.poetry.scripts] +my-script = "my_package:main" + + +[tool.poetry.plugins."blogtool.parsers"] +".rst" = "some_module::SomeClass" + + +[[tool.poetry.source]] +name = "foo" +url = "https://foo.bar/simple/" +default = true diff --git a/tests/fixtures/with_non_default_multiple_secondary_sources/pyproject.toml b/tests/fixtures/with_non_default_multiple_secondary_sources/pyproject.toml index 933bee96912..d5db76c6094 100644 --- a/tests/fixtures/with_non_default_multiple_secondary_sources/pyproject.toml +++ b/tests/fixtures/with_non_default_multiple_secondary_sources/pyproject.toml @@ -16,9 +16,9 @@ python = "~2.7 || ^3.6" [[tool.poetry.source]] name = "foo" url = "https://foo.bar/simple/" -secondary = true +priority = "secondary" [[tool.poetry.source]] name = "bar" url = "https://bar.baz/simple/" -secondary = true +priority = "secondary" diff --git a/tests/fixtures/with_non_default_multiple_secondary_sources_legacy/pyproject.toml b/tests/fixtures/with_non_default_multiple_secondary_sources_legacy/pyproject.toml new file mode 100644 index 00000000000..933bee96912 --- /dev/null +++ b/tests/fixtures/with_non_default_multiple_secondary_sources_legacy/pyproject.toml @@ -0,0 +1,24 @@ +[tool.poetry] +name = "my-package" +version = "1.2.3" +description = "Some description." +authors = [ + "Your Name " +] +license = "MIT" + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.6" + +[tool.poetry.dev-dependencies] + +[[tool.poetry.source]] +name = "foo" +url = "https://foo.bar/simple/" +secondary = true + +[[tool.poetry.source]] +name = "bar" +url = "https://bar.baz/simple/" +secondary = true diff --git a/tests/fixtures/with_non_default_multiple_sources/pyproject.toml b/tests/fixtures/with_non_default_multiple_sources/pyproject.toml index 6cacb602e8b..e40dd03e66e 100644 --- a/tests/fixtures/with_non_default_multiple_sources/pyproject.toml +++ b/tests/fixtures/with_non_default_multiple_sources/pyproject.toml @@ -16,7 +16,7 @@ python = "~2.7 || ^3.6" [[tool.poetry.source]] name = "foo" url = "https://foo.bar/simple/" -secondary = true +priority = "secondary" [[tool.poetry.source]] name = "bar" diff --git a/tests/fixtures/with_non_default_multiple_sources_legacy/pyproject.toml b/tests/fixtures/with_non_default_multiple_sources_legacy/pyproject.toml new file mode 100644 index 00000000000..6cacb602e8b --- /dev/null +++ b/tests/fixtures/with_non_default_multiple_sources_legacy/pyproject.toml @@ -0,0 +1,23 @@ +[tool.poetry] +name = "my-package" +version = "1.2.3" +description = "Some description." +authors = [ + "Your Name " +] +license = "MIT" + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.6" + +[tool.poetry.dev-dependencies] + +[[tool.poetry.source]] +name = "foo" +url = "https://foo.bar/simple/" +secondary = true + +[[tool.poetry.source]] +name = "bar" +url = "https://bar.baz/simple/" diff --git a/tests/fixtures/with_non_default_secondary_source/pyproject.toml b/tests/fixtures/with_non_default_secondary_source/pyproject.toml index 453e3f9747f..5cce04b0591 100644 --- a/tests/fixtures/with_non_default_secondary_source/pyproject.toml +++ b/tests/fixtures/with_non_default_secondary_source/pyproject.toml @@ -16,4 +16,4 @@ python = "~2.7 || ^3.6" [[tool.poetry.source]] name = "foo" url = "https://foo.bar/simple/" -secondary = true +priority = "secondary" diff --git a/tests/fixtures/with_non_default_secondary_source_legacy/pyproject.toml b/tests/fixtures/with_non_default_secondary_source_legacy/pyproject.toml new file mode 100644 index 00000000000..453e3f9747f --- /dev/null +++ b/tests/fixtures/with_non_default_secondary_source_legacy/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "my-package" +version = "1.2.3" +description = "Some description." +authors = [ + "Your Name " +] +license = "MIT" + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.6" + +[tool.poetry.dev-dependencies] + +[[tool.poetry.source]] +name = "foo" +url = "https://foo.bar/simple/" +secondary = true diff --git a/tests/fixtures/with_non_default_source_explicit/pyproject.toml b/tests/fixtures/with_non_default_source_explicit/pyproject.toml new file mode 100644 index 00000000000..23e7733cf34 --- /dev/null +++ b/tests/fixtures/with_non_default_source_explicit/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "my-package" +version = "1.2.3" +description = "Some description." +authors = [ + "Your Name " +] +license = "MIT" + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.6" + +[tool.poetry.dev-dependencies] + +[[tool.poetry.source]] +name = "foo" +url = "https://foo.bar/simple/" +priority = "primary" diff --git a/tests/fixtures/with_non_default_source/pyproject.toml b/tests/fixtures/with_non_default_source_implicit/pyproject.toml similarity index 100% rename from tests/fixtures/with_non_default_source/pyproject.toml rename to tests/fixtures/with_non_default_source_implicit/pyproject.toml diff --git a/tests/fixtures/with_two_default_sources/pyproject.toml b/tests/fixtures/with_two_default_sources/pyproject.toml index 0d0a8a5446b..fa84b0a40a7 100644 --- a/tests/fixtures/with_two_default_sources/pyproject.toml +++ b/tests/fixtures/with_two_default_sources/pyproject.toml @@ -58,9 +58,9 @@ my-script = "my_package:main" [[tool.poetry.source]] name = "foo" url = "https://foo.bar/simple/" -default = true +priority = "default" [[tool.poetry.source]] name = "bar" url = "https://bar.foo/simple/" -default = true +priority = "default" diff --git a/tests/fixtures/with_two_default_sources_legacy/README.rst b/tests/fixtures/with_two_default_sources_legacy/README.rst new file mode 100644 index 00000000000..f7fe15470f9 --- /dev/null +++ b/tests/fixtures/with_two_default_sources_legacy/README.rst @@ -0,0 +1,2 @@ +My Package +========== diff --git a/tests/fixtures/with_two_default_sources_legacy/pyproject.toml b/tests/fixtures/with_two_default_sources_legacy/pyproject.toml new file mode 100644 index 00000000000..0d0a8a5446b --- /dev/null +++ b/tests/fixtures/with_two_default_sources_legacy/pyproject.toml @@ -0,0 +1,66 @@ +[tool.poetry] +name = "my-package" +version = "1.2.3" +description = "Some description." +authors = [ + "Sébastien Eustace " +] +license = "MIT" + +readme = "README.rst" + +homepage = "https://python-poetry.org" +repository = "https://github.com/python-poetry/poetry" +documentation = "https://python-poetry.org/docs" + +keywords = ["packaging", "dependency", "poetry"] + +classifiers = [ + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Libraries :: Python Modules" +] + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.6" +cleo = "^0.6" +pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" } +requests = { version = "^2.18", optional = true, extras=[ "security" ] } +pathlib2 = { version = "^2.2", python = "~2.7" } + +orator = { version = "^0.9", optional = true } + +# File dependency +demo = { path = "../distributions/demo-0.1.0-py2.py3-none-any.whl" } + +# Dir dependency with setup.py +my-package = { path = "../project_with_setup/" } + +# Dir dependency with pyproject.toml +simple-project = { path = "../simple_project/" } + + +[tool.poetry.extras] +db = [ "orator" ] + +[tool.poetry.dev-dependencies] +pytest = "~3.4" + + +[tool.poetry.scripts] +my-script = "my_package:main" + + +[tool.poetry.plugins."blogtool.parsers"] +".rst" = "some_module::SomeClass" + + +[[tool.poetry.source]] +name = "foo" +url = "https://foo.bar/simple/" +default = true + +[[tool.poetry.source]] +name = "bar" +url = "https://bar.foo/simple/" +default = true diff --git a/tests/installation/test_pip_installer.py b/tests/installation/test_pip_installer.py index 29713e1daf0..fa16adc4aa7 100644 --- a/tests/installation/test_pip_installer.py +++ b/tests/installation/test_pip_installer.py @@ -13,6 +13,7 @@ from poetry.installation.pip_installer import PipInstaller from poetry.repositories.legacy_repository import LegacyRepository +from poetry.repositories.repository_pool import Priority from poetry.repositories.repository_pool import RepositoryPool from poetry.utils.authenticator import RepositoryCertificateConfig from poetry.utils.env import NullEnv @@ -137,7 +138,7 @@ def test_install_with_non_pypi_default_repository( default = LegacyRepository("default", "https://default.com") another = LegacyRepository("another", "https://another.com") - pool.add_repository(default, default=True) + pool.add_repository(default, priority=Priority.DEFAULT) pool.add_repository(another) foo = Package( @@ -177,7 +178,7 @@ def test_install_with_certs( default = LegacyRepository("default", "https://foo.bar") pool = RepositoryPool() - pool.add_repository(default, default=True) + pool.add_repository(default, priority=Priority.DEFAULT) installer = PipInstaller(env, NullIO(), pool) @@ -255,7 +256,7 @@ def test_install_with_trusted_host(config: Config, env: NullEnv) -> None: default = LegacyRepository("default", "https://foo.bar") pool = RepositoryPool() - pool.add_repository(default, default=True) + pool.add_repository(default, priority=Priority.DEFAULT) installer = PipInstaller(env, NullIO(), pool) diff --git a/tests/json/fixtures/source/complete_invalid_priority.toml b/tests/json/fixtures/source/complete_invalid_priority.toml new file mode 100644 index 00000000000..ec27c209a2a --- /dev/null +++ b/tests/json/fixtures/source/complete_invalid_priority.toml @@ -0,0 +1,17 @@ +[tool.poetry] +name = "foobar" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.10" + +[[tool.poetry.source]] +name = "pypi-simple" +url = "https://pypi.org/simple/" +priority = "arbitrary" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/json/fixtures/source/complete_invalid_priority_legacy_and_new.toml b/tests/json/fixtures/source/complete_invalid_priority_legacy_and_new.toml new file mode 100644 index 00000000000..4e2789b49d8 --- /dev/null +++ b/tests/json/fixtures/source/complete_invalid_priority_legacy_and_new.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "foobar" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.10" + +[[tool.poetry.source]] +name = "pypi-simple" +url = "https://pypi.org/simple/" +default = false +priority = "primary" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/json/fixtures/source/complete_invalid_url.toml b/tests/json/fixtures/source/complete_invalid_url.toml new file mode 100644 index 00000000000..6c61ba5df9b --- /dev/null +++ b/tests/json/fixtures/source/complete_invalid_url.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "foobar" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.10" + +[[tool.poetry.source]] +name = "pypi-simple" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/json/fixtures/source/complete_valid.toml b/tests/json/fixtures/source/complete_valid.toml index d0b4565ffa4..46186e1a7a3 100644 --- a/tests/json/fixtures/source/complete_valid.toml +++ b/tests/json/fixtures/source/complete_valid.toml @@ -10,8 +10,7 @@ python = "^3.10" [[tool.poetry.source]] name = "pypi-simple" url = "https://pypi.org/simple/" -default = false -secondary = false +priority = "primary" [build-system] requires = ["poetry-core"] diff --git a/tests/json/fixtures/source/complete_invalid.toml b/tests/json/fixtures/source/complete_valid_legacy.toml similarity index 90% rename from tests/json/fixtures/source/complete_invalid.toml rename to tests/json/fixtures/source/complete_valid_legacy.toml index bd70bc6a833..d0b4565ffa4 100644 --- a/tests/json/fixtures/source/complete_invalid.toml +++ b/tests/json/fixtures/source/complete_valid_legacy.toml @@ -9,6 +9,7 @@ python = "^3.10" [[tool.poetry.source]] name = "pypi-simple" +url = "https://pypi.org/simple/" default = false secondary = false diff --git a/tests/json/test_schema_sources.py b/tests/json/test_schema_sources.py index 22769922e1c..410b85a1183 100644 --- a/tests/json/test_schema_sources.py +++ b/tests/json/test_schema_sources.py @@ -9,16 +9,51 @@ FIXTURE_DIR = Path(__file__).parent / "fixtures" / "source" +def test_pyproject_toml_valid_legacy() -> None: + toml = TOMLFile(FIXTURE_DIR / "complete_valid_legacy.toml").read() + content = toml["tool"]["poetry"] + assert Factory.validate(content) == {"errors": [], "warnings": []} + + def test_pyproject_toml_valid() -> None: toml = TOMLFile(FIXTURE_DIR / "complete_valid.toml").read() content = toml["tool"]["poetry"] assert Factory.validate(content) == {"errors": [], "warnings": []} -def test_pyproject_toml_invalid() -> None: - toml = TOMLFile(FIXTURE_DIR / "complete_invalid.toml").read() +def test_pyproject_toml_invalid_url() -> None: + toml = TOMLFile(FIXTURE_DIR / "complete_invalid_url.toml").read() content = toml["tool"]["poetry"] assert Factory.validate(content) == { "errors": ["[source.0] 'url' is a required property"], "warnings": [], } + + +def test_pyproject_toml_invalid_priority() -> None: + toml = TOMLFile(FIXTURE_DIR / "complete_invalid_priority.toml").read() + content = toml["tool"]["poetry"] + assert Factory.validate(content) == { + "errors": [ + "[source.0.priority] 'arbitrary' is not one of ['primary', 'default'," + " 'secondary']" + ], + "warnings": [], + } + + +def test_pyproject_toml_invalid_priority_legacy_and_new() -> None: + toml = TOMLFile( + FIXTURE_DIR / "complete_invalid_priority_legacy_and_new.toml" + ).read() + content = toml["tool"]["poetry"] + assert Factory.validate(content) == { + "errors": [ + "[source.0] {'name': 'pypi-simple', 'url': " + "'https://pypi.org/simple/', 'default': False, 'priority': " + "'primary'} should not be valid under {'anyOf': [{'required': " + "['priority', 'default']}, {'required': ['priority', " + "'secondary']}]}" + ], + "warnings": [], + } diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 0018da79da5..3eaed889351 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -23,6 +23,7 @@ from poetry.puzzle.exceptions import SolverProblemError from poetry.puzzle.provider import IncompatibleConstraintsError from poetry.repositories.repository import Repository +from poetry.repositories.repository_pool import Priority from poetry.repositories.repository_pool import RepositoryPool from poetry.utils.env import MockEnv from tests.helpers import MOCK_DEFAULT_GIT_REVISION @@ -2915,7 +2916,7 @@ def test_solver_does_not_choose_from_secondary_repository_by_default( package.add_dependency(Factory.create_dependency("clikit", {"version": "^0.2.0"})) pool = RepositoryPool() - pool.add_repository(MockPyPIRepository(), secondary=True) + pool.add_repository(MockPyPIRepository(), priority=Priority.SECONDARY) pool.add_repository(MockLegacyRepository()) solver = Solver(package, pool, [], [], io) @@ -2965,7 +2966,7 @@ def test_solver_chooses_from_secondary_if_explicit( ) pool = RepositoryPool() - pool.add_repository(MockPyPIRepository(), secondary=True) + pool.add_repository(MockPyPIRepository(), priority=Priority.SECONDARY) pool.add_repository(MockLegacyRepository()) solver = Solver(package, pool, [], [], io) diff --git a/tests/repositories/test_repository_pool.py b/tests/repositories/test_repository_pool.py index ccc0e269224..7bc77331eec 100644 --- a/tests/repositories/test_repository_pool.py +++ b/tests/repositories/test_repository_pool.py @@ -8,6 +8,7 @@ from poetry.repositories import RepositoryPool from poetry.repositories.exceptions import PackageNotFound from poetry.repositories.legacy_repository import LegacyRepository +from poetry.repositories.repository_pool import Priority from tests.helpers import get_dependency from tests.helpers import get_package @@ -27,6 +28,7 @@ def test_pool_with_initial_repositories() -> None: assert len(pool.repositories) == 1 assert not pool.has_default() assert pool.has_primary_repositories() + assert pool.get_priority("repo") == Priority.PRIMARY def test_repository_no_repository() -> None: @@ -47,20 +49,36 @@ def test_adding_repositories_with_same_name_twice_raises_value_error() -> None: RepositoryPool([repo1]).add_repository(repo2) -def test_repository_from_normal_pool() -> None: +@pytest.mark.parametrize("priority", (p for p in Priority)) +def test_repository_from_single_repo_pool(priority: Priority) -> None: repo = LegacyRepository("foo", "https://foo.bar") pool = RepositoryPool() - pool.add_repository(repo) - - assert pool.repository("foo") is repo + pool.add_repository(repo, priority=priority) -def test_repository_from_secondary_pool() -> None: + assert pool.repository("foo") is repo + assert pool.get_priority("foo") == priority + + +@pytest.mark.parametrize( + ("default", "secondary", "expected_priority"), + [ + (False, True, Priority.SECONDARY), + (True, False, Priority.DEFAULT), + (True, True, Priority.DEFAULT), + ], +) +def test_repository_from_single_repo_pool_legacy( + default: bool, secondary: bool, expected_priority: Priority +) -> None: repo = LegacyRepository("foo", "https://foo.bar") pool = RepositoryPool() - pool.add_repository(repo, secondary=True) + + with pytest.warns(DeprecationWarning): + pool.add_repository(repo, default=default, secondary=secondary) assert pool.repository("foo") is repo + assert pool.get_priority("foo") == expected_priority def test_repository_with_normal_default_and_secondary_repositories() -> None: @@ -71,9 +89,9 @@ def test_repository_with_normal_default_and_secondary_repositories() -> None: pool = RepositoryPool() pool.add_repository(repo1) - pool.add_repository(secondary, secondary=True) + pool.add_repository(secondary, priority=Priority.SECONDARY) pool.add_repository(repo2) - pool.add_repository(default, default=True) + pool.add_repository(default, priority=Priority.DEFAULT) assert pool.repository("secondary") is secondary assert pool.repository("default") is default @@ -115,19 +133,19 @@ def test_remove_default_repository() -> None: pool = RepositoryPool() pool.add_repository(repo1) pool.add_repository(repo2) - pool.add_repository(default, default=True) + pool.add_repository(default, priority=Priority.DEFAULT) assert pool.has_default() pool.remove_repository("default") + assert not pool.has_repository("default") assert not pool.has_default() - pool.add_repository(new_default, default=True) + pool.add_repository(new_default, priority=Priority.DEFAULT) + assert pool.get_priority("new_default") is Priority.DEFAULT assert pool.has_default() - assert pool.repositories[0] is new_default - assert not pool.has_repository("default") def test_repository_ordering() -> None: @@ -141,21 +159,21 @@ def test_repository_ordering() -> None: secondary3 = LegacyRepository("secondary3", "https://secondary3.com") pool = RepositoryPool() - pool.add_repository(secondary1, secondary=True) + pool.add_repository(secondary1, priority=Priority.SECONDARY) pool.add_repository(primary1) - pool.add_repository(default1, default=True) + pool.add_repository(default1, priority=Priority.DEFAULT) pool.add_repository(primary2) - pool.add_repository(secondary2, secondary=True) + pool.add_repository(secondary2, priority=Priority.SECONDARY) pool.remove_repository("primary2") pool.remove_repository("secondary2") pool.add_repository(primary3) - pool.add_repository(secondary3, secondary=True) + pool.add_repository(secondary3, priority=Priority.SECONDARY) assert pool.repositories == [default1, primary1, primary3, secondary1, secondary3] with pytest.raises(ValueError): - pool.add_repository(default2, default=True) + pool.add_repository(default2, priority=Priority.DEFAULT) def test_pool_get_package_in_any_repository() -> None: diff --git a/tests/test_factory.py b/tests/test_factory.py index 5b56ddc6200..356d0b24142 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -5,6 +5,7 @@ import pytest +from cleo.io.buffered_io import BufferedIO from deepdiff import DeepDiff from packaging.utils import canonicalize_name from poetry.core.constraints.version import parse_constraint @@ -13,6 +14,7 @@ from poetry.plugins.plugin import Plugin from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.pypi_repository import PyPiRepository +from poetry.repositories.repository_pool import Priority from poetry.toml.file import TOMLFile from tests.helpers import mock_metadata_entry_points @@ -206,40 +208,80 @@ def test_create_poetry_with_multi_constraints_dependency(): assert len(package.requires) == 2 -def test_poetry_with_default_source(with_simple_keyring: None): - poetry = Factory().create_poetry(fixtures_dir / "with_default_source") +def test_poetry_with_default_source_legacy(with_simple_keyring: None): + io = BufferedIO() + poetry = Factory().create_poetry(fixtures_dir / "with_default_source_legacy", io=io) assert len(poetry.pool.repositories) == 1 + assert "Found deprecated key" in io.fetch_error() + + +def test_poetry_with_default_source(with_simple_keyring: None): + io = BufferedIO() + poetry = Factory().create_poetry(fixtures_dir / "with_default_source", io=io) + assert len(poetry.pool.repositories) == 1 + assert io.fetch_error() == "" -def test_poetry_with_non_default_source(with_simple_keyring: None): - poetry = Factory().create_poetry(fixtures_dir / "with_non_default_source") - assert len(poetry.pool.repositories) == 2 +@pytest.mark.parametrize( + "fixture_filename", + ("with_non_default_source_implicit", "with_non_default_source_explicit"), +) +def test_poetry_with_non_default_source( + fixture_filename: str, with_simple_keyring: None +): + poetry = Factory().create_poetry(fixtures_dir / fixture_filename) assert not poetry.pool.has_default() + assert poetry.pool.has_repository("PyPI") + assert poetry.pool.get_priority("PyPI") is Priority.SECONDARY + assert isinstance(poetry.pool.repository("PyPI"), PyPiRepository) + assert poetry.pool.has_repository("foo") + assert poetry.pool.get_priority("foo") is Priority.PRIMARY + assert isinstance(poetry.pool.repository("foo"), LegacyRepository) + assert {repo.name for repo in poetry.pool.repositories} == {"PyPI", "foo"} + - assert poetry.pool.repositories[0].name == "foo" - assert isinstance(poetry.pool.repositories[0], LegacyRepository) +def test_poetry_with_non_default_secondary_source_legacy(with_simple_keyring: None): + poetry = Factory().create_poetry( + fixtures_dir / "with_non_default_secondary_source_legacy" + ) - assert poetry.pool.repositories[1].name == "PyPI" - assert isinstance(poetry.pool.repositories[1], PyPiRepository) + assert poetry.pool.has_repository("PyPI") + assert isinstance(poetry.pool.repository("PyPI"), PyPiRepository) + assert poetry.pool.get_priority("PyPI") is Priority.DEFAULT + assert poetry.pool.has_repository("foo") + assert isinstance(poetry.pool.repository("foo"), LegacyRepository) + assert [repo.name for repo in poetry.pool.repositories] == ["PyPI", "foo"] def test_poetry_with_non_default_secondary_source(with_simple_keyring: None): poetry = Factory().create_poetry(fixtures_dir / "with_non_default_secondary_source") - assert len(poetry.pool.repositories) == 2 + assert poetry.pool.has_repository("PyPI") + assert isinstance(poetry.pool.repository("PyPI"), PyPiRepository) + assert poetry.pool.get_priority("PyPI") is Priority.DEFAULT + assert poetry.pool.has_repository("foo") + assert isinstance(poetry.pool.repository("foo"), LegacyRepository) + assert [repo.name for repo in poetry.pool.repositories] == ["PyPI", "foo"] - assert poetry.pool.has_default() - repository = poetry.pool.repositories[0] - assert repository.name == "PyPI" - assert isinstance(repository, PyPiRepository) +def test_poetry_with_non_default_multiple_secondary_sources_legacy( + with_simple_keyring: None, +): + poetry = Factory().create_poetry( + fixtures_dir / "with_non_default_multiple_secondary_sources_legacy" + ) - repository = poetry.pool.repositories[1] - assert repository.name == "foo" - assert isinstance(repository, LegacyRepository) + assert poetry.pool.has_repository("PyPI") + assert isinstance(poetry.pool.repository("PyPI"), PyPiRepository) + assert poetry.pool.get_priority("PyPI") is Priority.DEFAULT + assert poetry.pool.has_repository("foo") + assert isinstance(poetry.pool.repository("foo"), LegacyRepository) + assert poetry.pool.has_repository("bar") + assert isinstance(poetry.pool.repository("bar"), LegacyRepository) + assert {repo.name for repo in poetry.pool.repositories} == {"PyPI", "foo", "bar"} def test_poetry_with_non_default_multiple_secondary_sources(with_simple_keyring: None): @@ -247,52 +289,60 @@ def test_poetry_with_non_default_multiple_secondary_sources(with_simple_keyring: fixtures_dir / "with_non_default_multiple_secondary_sources" ) - assert len(poetry.pool.repositories) == 3 + assert poetry.pool.has_repository("PyPI") + assert isinstance(poetry.pool.repository("PyPI"), PyPiRepository) + assert poetry.pool.get_priority("PyPI") is Priority.DEFAULT + assert poetry.pool.has_repository("foo") + assert isinstance(poetry.pool.repository("foo"), LegacyRepository) + assert poetry.pool.has_repository("bar") + assert isinstance(poetry.pool.repository("bar"), LegacyRepository) + assert {repo.name for repo in poetry.pool.repositories} == {"PyPI", "foo", "bar"} - assert poetry.pool.has_default() - repository = poetry.pool.repositories[0] - assert repository.name == "PyPI" - assert isinstance(repository, PyPiRepository) - - repository = poetry.pool.repositories[1] - assert repository.name == "foo" - assert isinstance(repository, LegacyRepository) +def test_poetry_with_non_default_multiple_sources_legacy(with_simple_keyring: None): + poetry = Factory().create_poetry( + fixtures_dir / "with_non_default_multiple_sources_legacy" + ) - repository = poetry.pool.repositories[2] - assert repository.name == "bar" - assert isinstance(repository, LegacyRepository) + assert not poetry.pool.has_default() + assert poetry.pool.has_repository("bar") + assert isinstance(poetry.pool.repository("bar"), LegacyRepository) + assert poetry.pool.has_repository("PyPI") + assert poetry.pool.get_priority("PyPI") is Priority.SECONDARY + assert isinstance(poetry.pool.repository("PyPI"), PyPiRepository) + assert poetry.pool.has_repository("foo") + assert isinstance(poetry.pool.repository("foo"), LegacyRepository) + assert {repo.name for repo in poetry.pool.repositories} == {"bar", "PyPI", "foo"} def test_poetry_with_non_default_multiple_sources(with_simple_keyring: None): poetry = Factory().create_poetry(fixtures_dir / "with_non_default_multiple_sources") - assert len(poetry.pool.repositories) == 3 - assert not poetry.pool.has_default() - - repository = poetry.pool.repositories[0] - assert repository.name == "bar" - assert isinstance(repository, LegacyRepository) - - repository = poetry.pool.repositories[1] - assert repository.name == "foo" - assert isinstance(repository, LegacyRepository) - - repository = poetry.pool.repositories[2] - assert repository.name == "PyPI" - assert isinstance(repository, PyPiRepository) + assert poetry.pool.has_repository("PyPI") + assert isinstance(poetry.pool.repository("PyPI"), PyPiRepository) + assert poetry.pool.get_priority("PyPI") is Priority.SECONDARY + assert poetry.pool.has_repository("bar") + assert isinstance(poetry.pool.repository("bar"), LegacyRepository) + assert poetry.pool.has_repository("foo") + assert isinstance(poetry.pool.repository("foo"), LegacyRepository) + assert {repo.name for repo in poetry.pool.repositories} == {"PyPI", "bar", "foo"} def test_poetry_with_no_default_source(): poetry = Factory().create_poetry(fixtures_dir / "sample_project") - assert len(poetry.pool.repositories) == 1 + assert poetry.pool.has_repository("PyPI") + assert poetry.pool.get_priority("PyPI") is Priority.DEFAULT + assert isinstance(poetry.pool.repository("PyPI"), PyPiRepository) + assert {repo.name for repo in poetry.pool.repositories} == {"PyPI"} - assert poetry.pool.has_default() - assert poetry.pool.repositories[0].name == "PyPI" - assert isinstance(poetry.pool.repositories[0], PyPiRepository) +def test_poetry_with_two_default_sources_legacy(with_simple_keyring: None): + with pytest.raises(ValueError) as e: + Factory().create_poetry(fixtures_dir / "with_two_default_sources_legacy") + + assert str(e.value) == "Only one repository can be the default." def test_poetry_with_two_default_sources(with_simple_keyring: None): diff --git a/tests/utils/test_source.py b/tests/utils/test_source.py index a970b7262ca..a5093324d04 100644 --- a/tests/utils/test_source.py +++ b/tests/utils/test_source.py @@ -7,6 +7,7 @@ from tomlkit.items import Trivia from poetry.config.source import Source +from poetry.repositories.repository_pool import Priority from poetry.utils.source import source_to_table @@ -16,25 +17,58 @@ ( Source("foo", "https://example.com"), { - "default": False, "name": "foo", - "secondary": False, + "priority": "primary", "url": "https://example.com", }, ), ( - Source("bar", "https://example.com/bar", True, True), + Source("bar", "https://example.com/bar", priority=Priority.SECONDARY), { - "default": True, "name": "bar", - "secondary": True, + "priority": "secondary", "url": "https://example.com/bar", }, ), ], ) -def test_source_to_table(source: Source, table_body: dict[str, str | bool]): +def test_source_to_table(source: Source, table_body: dict[str, str | bool]) -> None: table = Table(Container(), Trivia(), False) table._value = table_body assert source_to_table(source) == table + + +def test_source_default_is_primary() -> None: + source = Source("foo", "https://example.com") + assert source.priority == Priority.PRIMARY + + +@pytest.mark.parametrize( + ("default", "secondary", "expected_priority"), + [ + (False, True, Priority.SECONDARY), + (True, False, Priority.DEFAULT), + (True, True, Priority.DEFAULT), + ], +) +def test_source_legacy_handling( + default: bool, secondary: bool, expected_priority: Priority +) -> None: + with pytest.warns(DeprecationWarning): + source = Source( + "foo", "https://example.com", default=default, secondary=secondary + ) + assert source.priority == expected_priority + + +@pytest.mark.parametrize( + ("priority", "expected_priority"), + [ + ("secondary", Priority.SECONDARY), + ("SECONDARY", Priority.SECONDARY), + ], +) +def test_source_priority_as_string(priority: str, expected_priority: Priority) -> None: + source = Source("foo", "https://example.com", priority=priority) + assert source.priority == Priority.SECONDARY From 4c37735165b6ddfd1d10fea508c9f083ad7addb4 Mon Sep 17 00:00:00 2001 From: Bart Kamphorst Date: Mon, 24 Oct 2022 22:13:49 +0200 Subject: [PATCH 144/151] sources: introduce priority "explicit" (#7658) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Explicit sources are considered only for packages that explicitly indicate their source. Co-authored-by: Randy Döring <30527984+radoering@users.noreply.github.com> --- docs/cli.md | 2 +- docs/repositories.md | 19 +++++- src/poetry/json/schemas/poetry.json | 3 +- src/poetry/repositories/repository_pool.py | 28 ++++++-- tests/console/commands/source/conftest.py | 9 +++ tests/console/commands/source/test_add.py | 10 +++ tests/console/commands/source/test_show.py | 1 + .../with_explicit_source/pyproject.toml | 19 ++++++ .../json/fixtures/source/complete_valid.toml | 2 +- tests/json/test_schema_sources.py | 2 +- tests/puzzle/test_solver.py | 65 +++++++++++++++++++ tests/repositories/test_repository_pool.py | 19 +++++- tests/test_factory.py | 13 ++++ tests/utils/test_source.py | 4 +- 14 files changed, 184 insertions(+), 12 deletions(-) create mode 100644 tests/fixtures/with_explicit_source/pyproject.toml diff --git a/docs/cli.md b/docs/cli.md index ae161d1d7ac..e57b1548e2e 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -786,7 +786,7 @@ You cannot use the name `pypi` as it is reserved for use by the default PyPI sou * `--default`: Set this source as the [default]({{< relref "repositories#default-package-source" >}}) (disable PyPI). Deprecated in favor of `--priority`. * `--secondary`: Set this source as a [secondary]({{< relref "repositories#secondary-package-sources" >}}) source. Deprecated in favor of `--priority`. -* `--priority`: Set the priority of this source. Accepted values are: [`default`]({{< relref "repositories#default-package-source" >}}), and [`secondary`]({{< relref "repositories#secondary-package-sources" >}}). Refer to the dedicated sections in [Repositories]({{< relref "repositories" >}}) for more information. +* `--priority`: Set the priority of this source. Accepted values are: [`default`]({{< relref "repositories#default-package-source" >}}), [`secondary`]({{< relref "repositories#secondary-package-sources" >}}), and [`explicit`]({{< relref "repositories#explicit-package-sources" >}}). Refer to the dedicated sections in [Repositories]({{< relref "repositories" >}}) for more information. {{% note %}} At most one of the options above can be provided. See [package sources]({{< relref "repositories#package-sources" >}}) for more information. diff --git a/docs/repositories.md b/docs/repositories.md index c8d92301532..38f993892f4 100644 --- a/docs/repositories.md +++ b/docs/repositories.md @@ -123,7 +123,7 @@ url = "https://foo.bar/simple/" priority = "primary" ``` -If `priority` is undefined, the source is considered a primary source that takes precedence over PyPI and secondary sources. +If `priority` is undefined, the source is considered a primary source that takes precedence over PyPI, secondary and explicit sources. Package sources are considered in the following order: 1. [default source](#default-package-source), @@ -131,6 +131,8 @@ Package sources are considered in the following order: 3. PyPI (unless disabled by another default source), 4. [secondary sources](#secondary-package-sources), +[Explicit sources](#explicit-package-sources) are considered only for packages that explicitly [indicate their source](#package-source-constraint). + Within each priority class, package sources are considered in order of appearance in `pyproject.toml`. {{% note %}} @@ -181,6 +183,20 @@ poetry source add --priority=secondary https://foo.bar/simple/ There can be more than one secondary package source. +#### Explicit Package Sources + +*Introduced in 1.5.0* + +If package sources are configured as explicit, these sources are only searched when a package configuration [explicitly indicates](#package-source-constraint) that it should be found on this package source. + +You can configure a package source as an explicit source with `priority = "explicit` in your package source configuration. + +```bash +poetry source add --priority=explicit foo https://foo.bar/simple/ +``` + +There can be more than one explicit package source. + #### Package Source Constraint All package sources (including secondary sources) will be searched during the package lookup @@ -209,6 +225,7 @@ priority = ... {{% note %}} A repository that is configured to be the only source for retrieving a certain package can itself have any priority. +In particular, it does not need to have priority `"explicit"`. If a repository is configured to be the source of a package, it will be the only source that is considered for that package and the repository priority will have no effect on the resolution. diff --git a/src/poetry/json/schemas/poetry.json b/src/poetry/json/schemas/poetry.json index 04d0ff340d7..c9191b03d23 100644 --- a/src/poetry/json/schemas/poetry.json +++ b/src/poetry/json/schemas/poetry.json @@ -45,7 +45,8 @@ "enum": [ "primary", "default", - "secondary" + "secondary", + "explicit" ], "description": "Declare the priority of this repository." }, diff --git a/src/poetry/repositories/repository_pool.py b/src/poetry/repositories/repository_pool.py index a4dffbbff37..304f7e9ed33 100644 --- a/src/poetry/repositories/repository_pool.py +++ b/src/poetry/repositories/repository_pool.py @@ -26,6 +26,7 @@ class Priority(IntEnum): DEFAULT = enum.auto() PRIMARY = enum.auto() SECONDARY = enum.auto() + EXPLICIT = enum.auto() @dataclass(frozen=True) @@ -51,11 +52,30 @@ def __init__( @property def repositories(self) -> list[Repository]: - unsorted_repositories = self._repositories.values() - sorted_repositories = sorted( - unsorted_repositories, key=lambda prio_repo: prio_repo.priority + """ + Returns the repositories in the pool, + in the order they will be searched for packages. + + ATTENTION: For backwards compatibility and practical reasons, + repositories with priority EXPLICIT are NOT included, + because they will not be searched. + """ + sorted_repositories = self._sorted_repositories + return [ + prio_repo.repository + for prio_repo in sorted_repositories + if prio_repo.priority is not Priority.EXPLICIT + ] + + @property + def all_repositories(self) -> list[Repository]: + return [prio_repo.repository for prio_repo in self._sorted_repositories] + + @property + def _sorted_repositories(self) -> list[PrioritizedRepository]: + return sorted( + self._repositories.values(), key=lambda prio_repo: prio_repo.priority ) - return [prio_repo.repository for prio_repo in sorted_repositories] def has_default(self) -> bool: return self._contains_priority(Priority.DEFAULT) diff --git a/tests/console/commands/source/conftest.py b/tests/console/commands/source/conftest.py index f9db68a058a..5ec79df2c9e 100644 --- a/tests/console/commands/source/conftest.py +++ b/tests/console/commands/source/conftest.py @@ -51,6 +51,13 @@ def source_secondary() -> Source: ) +@pytest.fixture +def source_explicit() -> Source: + return Source( + name="explicit", url="https://explicit.com", priority=Priority.EXPLICIT + ) + + _existing_source = Source(name="existing", url="https://existing.com") @@ -110,11 +117,13 @@ def add_all_source_types( source_primary: Source, source_default: Source, source_secondary: Source, + source_explicit: Source, ) -> None: add = command_tester_factory("source add", poetry=poetry_with_source) for source in [ source_primary, source_default, source_secondary, + source_explicit, ]: add.execute(f"{source.name} {source.url} --priority={source.name}") diff --git a/tests/console/commands/source/test_add.py b/tests/console/commands/source/test_add.py index d25239c052c..468e9dec271 100644 --- a/tests/console/commands/source/test_add.py +++ b/tests/console/commands/source/test_add.py @@ -136,6 +136,16 @@ def test_source_add_secondary( assert_source_added(tester, poetry_with_source, source_existing, source_secondary) +def test_source_add_explicit( + tester: CommandTester, + source_existing: Source, + source_explicit: Source, + poetry_with_source: Poetry, +) -> None: + tester.execute(f"--priority=explicit {source_explicit.name} {source_explicit.url}") + assert_source_added(tester, poetry_with_source, source_existing, source_explicit) + + def test_source_add_error_default_and_secondary_legacy(tester: CommandTester) -> None: tester.execute("--default --secondary error https://error.com") assert ( diff --git a/tests/console/commands/source/test_show.py b/tests/console/commands/source/test_show.py index b636d1bd30a..d3c94682650 100644 --- a/tests/console/commands/source/test_show.py +++ b/tests/console/commands/source/test_show.py @@ -101,6 +101,7 @@ def test_source_show_two( "source_primary", "source_default", "source_secondary", + "source_explicit", ), ) def test_source_show_given_priority( diff --git a/tests/fixtures/with_explicit_source/pyproject.toml b/tests/fixtures/with_explicit_source/pyproject.toml new file mode 100644 index 00000000000..e19ad99bad4 --- /dev/null +++ b/tests/fixtures/with_explicit_source/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "my-package" +version = "1.2.3" +description = "Some description." +authors = [ + "Your Name " +] +license = "MIT" + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.6" + +[tool.poetry.dev-dependencies] + +[[tool.poetry.source]] +name = "explicit" +url = "https://explicit.com/simple/" +priority = "explicit" diff --git a/tests/json/fixtures/source/complete_valid.toml b/tests/json/fixtures/source/complete_valid.toml index 46186e1a7a3..fedab7bed39 100644 --- a/tests/json/fixtures/source/complete_valid.toml +++ b/tests/json/fixtures/source/complete_valid.toml @@ -10,7 +10,7 @@ python = "^3.10" [[tool.poetry.source]] name = "pypi-simple" url = "https://pypi.org/simple/" -priority = "primary" +priority = "explicit" [build-system] requires = ["poetry-core"] diff --git a/tests/json/test_schema_sources.py b/tests/json/test_schema_sources.py index 410b85a1183..78e446bc6b3 100644 --- a/tests/json/test_schema_sources.py +++ b/tests/json/test_schema_sources.py @@ -36,7 +36,7 @@ def test_pyproject_toml_invalid_priority() -> None: assert Factory.validate(content) == { "errors": [ "[source.0.priority] 'arbitrary' is not one of ['primary', 'default'," - " 'secondary']" + " 'secondary', 'explicit']" ], "warnings": [], } diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 3eaed889351..080fa975c63 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -2998,6 +2998,71 @@ def test_solver_chooses_from_secondary_if_explicit( assert ops[2].package.source_url is None +def test_solver_does_not_choose_from_explicit_repository( + package: ProjectPackage, io: NullIO +) -> None: + package.python_versions = "^3.7" + package.add_dependency(Factory.create_dependency("attrs", {"version": "^17.4.0"})) + + pool = RepositoryPool() + pool.add_repository(MockPyPIRepository(), priority=Priority.EXPLICIT) + pool.add_repository(MockLegacyRepository()) + + solver = Solver(package, pool, [], [], io) + + with pytest.raises(SolverProblemError): + solver.solve() + + +def test_solver_chooses_direct_dependency_from_explicit_if_explicit( + package: ProjectPackage, + io: NullIO, +) -> None: + package.python_versions = "^3.7" + package.add_dependency( + Factory.create_dependency("pylev", {"version": "^1.2.0", "source": "PyPI"}) + ) + + pool = RepositoryPool() + pool.add_repository(MockPyPIRepository(), priority=Priority.EXPLICIT) + pool.add_repository(MockLegacyRepository()) + + solver = Solver(package, pool, [], [], io) + + transaction = solver.solve() + + ops = check_solver_result( + transaction, + [ + {"job": "install", "package": get_package("pylev", "1.3.0")}, + ], + ) + + assert ops[0].package.source_type is None + assert ops[0].package.source_url is None + + +def test_solver_ignores_explicit_repo_for_transient_dependencies( + package: ProjectPackage, + io: NullIO, +) -> None: + # clikit depends on pylev, which is in MockPyPIRepository (explicit) but not in + # MockLegacyRepository + package.python_versions = "^3.7" + package.add_dependency( + Factory.create_dependency("clikit", {"version": "^0.2.0", "source": "PyPI"}) + ) + + pool = RepositoryPool() + pool.add_repository(MockPyPIRepository(), priority=Priority.EXPLICIT) + pool.add_repository(MockLegacyRepository()) + + solver = Solver(package, pool, [], [], io) + + with pytest.raises(SolverProblemError): + solver.solve() + + def test_solver_discards_packages_with_empty_markers( package: ProjectPackage, repo: Repository, diff --git a/tests/repositories/test_repository_pool.py b/tests/repositories/test_repository_pool.py index 7bc77331eec..21c5941d73d 100644 --- a/tests/repositories/test_repository_pool.py +++ b/tests/repositories/test_repository_pool.py @@ -81,26 +81,43 @@ def test_repository_from_single_repo_pool_legacy( assert pool.get_priority("foo") == expected_priority -def test_repository_with_normal_default_and_secondary_repositories() -> None: +def test_repository_with_normal_default_secondary_and_explicit_repositories(): secondary = LegacyRepository("secondary", "https://secondary.com") default = LegacyRepository("default", "https://default.com") repo1 = LegacyRepository("foo", "https://foo.bar") repo2 = LegacyRepository("bar", "https://bar.baz") + explicit = LegacyRepository("explicit", "https://bar.baz") pool = RepositoryPool() pool.add_repository(repo1) pool.add_repository(secondary, priority=Priority.SECONDARY) pool.add_repository(repo2) + pool.add_repository(explicit, priority=Priority.EXPLICIT) pool.add_repository(default, priority=Priority.DEFAULT) assert pool.repository("secondary") is secondary assert pool.repository("default") is default assert pool.repository("foo") is repo1 assert pool.repository("bar") is repo2 + assert pool.repository("explicit") is explicit assert pool.has_default() assert pool.has_primary_repositories() +def test_repository_explicit_repositories_do_not_show() -> None: + explicit = LegacyRepository("explicit", "https://explicit.com") + default = LegacyRepository("default", "https://default.com") + + pool = RepositoryPool() + pool.add_repository(explicit, priority=Priority.EXPLICIT) + pool.add_repository(default, priority=Priority.DEFAULT) + + assert pool.repository("explicit") is explicit + assert pool.repository("default") is default + assert pool.repositories == [default] + assert pool.all_repositories == [default, explicit] + + def test_remove_non_existing_repository_raises_indexerror() -> None: pool = RepositoryPool() diff --git a/tests/test_factory.py b/tests/test_factory.py index 356d0b24142..2ba1104e768 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -338,6 +338,19 @@ def test_poetry_with_no_default_source(): assert {repo.name for repo in poetry.pool.repositories} == {"PyPI"} +def test_poetry_with_explicit_source(with_simple_keyring: None) -> None: + poetry = Factory().create_poetry(fixtures_dir / "with_explicit_source") + + assert len(poetry.pool.repositories) == 1 + assert len(poetry.pool.all_repositories) == 2 + assert poetry.pool.has_repository("PyPI") + assert poetry.pool.get_priority("PyPI") is Priority.DEFAULT + assert isinstance(poetry.pool.repository("PyPI"), PyPiRepository) + assert poetry.pool.has_repository("explicit") + assert isinstance(poetry.pool.repository("explicit"), LegacyRepository) + assert [repo.name for repo in poetry.pool.repositories] == ["PyPI"] + + def test_poetry_with_two_default_sources_legacy(with_simple_keyring: None): with pytest.raises(ValueError) as e: Factory().create_poetry(fixtures_dir / "with_two_default_sources_legacy") diff --git a/tests/utils/test_source.py b/tests/utils/test_source.py index a5093324d04..4908a6e0978 100644 --- a/tests/utils/test_source.py +++ b/tests/utils/test_source.py @@ -23,10 +23,10 @@ }, ), ( - Source("bar", "https://example.com/bar", priority=Priority.SECONDARY), + Source("bar", "https://example.com/bar", priority=Priority.EXPLICIT), { "name": "bar", - "priority": "secondary", + "priority": "explicit", "url": "https://example.com/bar", }, ), From 3855bc589427bdbe945f6f59226e2b54c76cbb0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sun, 16 Apr 2023 07:30:43 +0200 Subject: [PATCH 145/151] tests: consistently use fixture_dir --- tests/conftest.py | 10 +- tests/console/commands/self/test_update.py | 10 +- tests/console/commands/test_build.py | 15 +- tests/console/commands/test_check.py | 24 +- tests/console/conftest.py | 14 +- tests/helpers.py | 12 +- tests/inspection/test_info.py | 34 +- tests/installation/test_pip_installer.py | 10 +- .../masonry/builders/test_editable_builder.py | 39 +- .../version_solver/test_unsolvable.py | 10 +- tests/packages/test_locker.py | 35 +- tests/plugins/test_plugin_manager.py | 19 +- tests/puzzle/test_provider.py | 152 +++----- tests/puzzle/test_solver.py | 353 ++++++++---------- tests/test_factory.py | 128 ++++--- tests/utils/test_env.py | 20 +- tests/utils/test_helpers.py | 10 +- 17 files changed, 419 insertions(+), 476 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 98e6030dd18..65abc45743a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -282,6 +282,11 @@ def http() -> Iterator[type[httpretty.httpretty]]: yield httpretty +@pytest.fixture +def project_root() -> Path: + return Path(__file__).parent.parent + + @pytest.fixture(scope="session") def fixture_base() -> Path: return Path(__file__).parent / "fixtures" @@ -417,11 +422,6 @@ def _factory( return _factory -@pytest.fixture -def project_root() -> Path: - return Path(__file__).parent.parent - - @pytest.fixture(autouse=True) def set_simple_log_formatter() -> None: """ diff --git a/tests/console/commands/self/test_update.py b/tests/console/commands/self/test_update.py index 6720406de8b..d2c6ef0c933 100644 --- a/tests/console/commands/self/test_update.py +++ b/tests/console/commands/self/test_update.py @@ -1,6 +1,5 @@ from __future__ import annotations -from pathlib import Path from typing import TYPE_CHECKING import pytest @@ -20,12 +19,11 @@ from tests.helpers import TestRepository from tests.types import CommandTesterFactory + from tests.types import FixtureDirGetter -FIXTURES = Path(__file__).parent.joinpath("fixtures") - -@pytest.fixture() -def setup(mocker: MockerFixture, fixture_dir: Path): +@pytest.fixture +def setup(mocker: MockerFixture, fixture_dir: FixtureDirGetter) -> None: mocker.patch.object( Executor, "_download", @@ -46,7 +44,7 @@ def test_self_update_can_update_from_recommended_installation( tester: CommandTester, repo: TestRepository, installed: TestRepository, -): +) -> None: new_version = Version.parse(__version__).next_minor().text old_poetry = Package("poetry", __version__) diff --git a/tests/console/commands/test_build.py b/tests/console/commands/test_build.py index 2d7ed87007a..ed9f3a3c5fa 100644 --- a/tests/console/commands/test_build.py +++ b/tests/console/commands/test_build.py @@ -3,23 +3,26 @@ import shutil import tarfile -from pathlib import Path from typing import TYPE_CHECKING from poetry.factory import Factory if TYPE_CHECKING: + from pathlib import Path + from poetry.utils.env import VirtualEnv from tests.types import CommandTesterFactory + from tests.types import FixtureDirGetter def test_build_with_multiple_readme_files( - tmp_path: Path, tmp_venv: VirtualEnv, command_tester_factory: CommandTesterFactory -): - source_dir = ( - Path(__file__).parent.parent.parent / "fixtures" / "with_multiple_readme_files" - ) + fixture_dir: FixtureDirGetter, + tmp_path: Path, + tmp_venv: VirtualEnv, + command_tester_factory: CommandTesterFactory, +) -> None: + source_dir = fixture_dir("with_multiple_readme_files") target_dir = tmp_path / "project" shutil.copytree(str(source_dir), str(target_dir)) diff --git a/tests/console/commands/test_check.py b/tests/console/commands/test_check.py index f23276c5c6d..771f95813dc 100644 --- a/tests/console/commands/test_check.py +++ b/tests/console/commands/test_check.py @@ -1,6 +1,5 @@ from __future__ import annotations -from pathlib import Path from typing import TYPE_CHECKING import pytest @@ -11,6 +10,7 @@ from pytest_mock import MockerFixture from tests.types import CommandTesterFactory + from tests.types import FixtureDirGetter @pytest.fixture() @@ -18,7 +18,7 @@ def tester(command_tester_factory: CommandTesterFactory) -> CommandTester: return command_tester_factory("check") -def test_check_valid(tester: CommandTester): +def test_check_valid(tester: CommandTester) -> None: tester.execute() expected = """\ @@ -28,17 +28,14 @@ def test_check_valid(tester: CommandTester): assert tester.io.fetch_output() == expected -def test_check_invalid(mocker: MockerFixture, tester: CommandTester): +def test_check_invalid( + mocker: MockerFixture, tester: CommandTester, fixture_dir: FixtureDirGetter +) -> None: from poetry.toml import TOMLFile mocker.patch( "poetry.poetry.Poetry.file", - return_value=TOMLFile( - Path(__file__).parent.parent.parent - / "fixtures" - / "invalid_pyproject" - / "pyproject.toml" - ), + return_value=TOMLFile(fixture_dir("invalid_pyproject") / "pyproject.toml"), new_callable=mocker.PropertyMock, ) @@ -61,13 +58,12 @@ def test_check_invalid(mocker: MockerFixture, tester: CommandTester): assert tester.io.fetch_error() == expected -def test_check_private(mocker: MockerFixture, tester: CommandTester): +def test_check_private( + mocker: MockerFixture, tester: CommandTester, fixture_dir: FixtureDirGetter +) -> None: mocker.patch( "poetry.factory.Factory.locate", - return_value=Path(__file__).parent.parent.parent - / "fixtures" - / "private_pyproject" - / "pyproject.toml", + return_value=fixture_dir("private_pyproject") / "pyproject.toml", ) tester.execute() diff --git a/tests/console/conftest.py b/tests/console/conftest.py index 7813273e1a8..8d04adf2c42 100644 --- a/tests/console/conftest.py +++ b/tests/console/conftest.py @@ -2,7 +2,6 @@ import os -from pathlib import Path from typing import TYPE_CHECKING import pytest @@ -22,6 +21,7 @@ if TYPE_CHECKING: from collections.abc import Iterator + from pathlib import Path from pytest_mock import MockerFixture @@ -31,6 +31,7 @@ from poetry.utils.env import Env from tests.conftest import Config from tests.types import CommandTesterFactory + from tests.types import FixtureDirGetter from tests.types import ProjectFactory @@ -96,11 +97,12 @@ def project_directory() -> str: @pytest.fixture -def poetry(project_directory: str, project_factory: ProjectFactory) -> Poetry: - return project_factory( - name="simple", - source=Path(__file__).parent.parent / "fixtures" / project_directory, - ) +def poetry( + project_directory: str, + project_factory: ProjectFactory, + fixture_dir: FixtureDirGetter, +) -> Poetry: + return project_factory(name="simple", source=fixture_dir(project_directory)) @pytest.fixture diff --git a/tests/helpers.py b/tests/helpers.py index 3ab77da917b..b21de6a551d 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -68,13 +68,6 @@ def get_dependency( return Factory.create_dependency(name, constraint or "*", groups=groups) -def fixture(path: str | None = None) -> Path: - if path: - return FIXTURE_PATH / path - else: - return FIXTURE_PATH - - def copy_or_symlink(source: Path, dest: Path) -> None: if dest.is_symlink() or dest.is_file(): dest.unlink() # missing_ok is only available in Python >= 3.8 @@ -113,7 +106,7 @@ def mock_clone( parsed = ParsedUrl.parse(url) path = re.sub(r"(.git)?$", "", parsed.pathname.lstrip("/")) - folder = Path(__file__).parent / "fixtures" / "git" / parsed.resource / path + folder = FIXTURE_PATH / "git" / parsed.resource / path if not source_root: source_root = Path(Config.create().get("cache-dir")) / "src" @@ -128,8 +121,7 @@ def mock_clone( def mock_download(url: str, dest: Path) -> None: parts = urllib.parse.urlparse(url) - fixtures = Path(__file__).parent / "fixtures" - fixture = fixtures / parts.path.lstrip("/") + fixture = FIXTURE_PATH / parts.path.lstrip("/") copy_or_symlink(fixture, dest) diff --git a/tests/inspection/test_info.py b/tests/inspection/test_info.py index 4b529e3bedc..4b1e0335944 100644 --- a/tests/inspection/test_info.py +++ b/tests/inspection/test_info.py @@ -1,6 +1,5 @@ from __future__ import annotations -from pathlib import Path from subprocess import CalledProcessError from typing import TYPE_CHECKING @@ -13,10 +12,11 @@ if TYPE_CHECKING: + from pathlib import Path + from pytest_mock import MockerFixture -FIXTURE_DIR_BASE = Path(__file__).parent.parent / "fixtures" -FIXTURE_DIR_INSPECTIONS = FIXTURE_DIR_BASE / "inspection" + from tests.types import FixtureDirGetter @pytest.fixture(autouse=True) @@ -25,13 +25,13 @@ def pep517_metadata_mock() -> None: @pytest.fixture -def demo_sdist() -> Path: - return FIXTURE_DIR_BASE / "distributions" / "demo-0.1.0.tar.gz" +def demo_sdist(fixture_dir: FixtureDirGetter) -> Path: + return fixture_dir("distributions") / "demo-0.1.0.tar.gz" @pytest.fixture -def demo_wheel() -> Path: - return FIXTURE_DIR_BASE / "distributions" / "demo-0.1.0-py2.py3-none-any.whl" +def demo_wheel(fixture_dir: FixtureDirGetter) -> Path: + return fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" @pytest.fixture @@ -128,15 +128,15 @@ def test_info_from_bdist(demo_wheel: Path) -> None: demo_check_info(info) -def test_info_from_poetry_directory() -> None: +def test_info_from_poetry_directory(fixture_dir: FixtureDirGetter) -> None: info = PackageInfo.from_directory( - FIXTURE_DIR_INSPECTIONS / "demo", disable_build=True + fixture_dir("inspection") / "demo", disable_build=True ) demo_check_info(info) def test_info_from_poetry_directory_fallback_on_poetry_create_error( - mocker: MockerFixture, + mocker: MockerFixture, fixture_dir: FixtureDirGetter ) -> None: mock_create_poetry = mocker.patch( "poetry.inspection.info.Factory.create_poetry", side_effect=RuntimeError @@ -146,16 +146,16 @@ def test_info_from_poetry_directory_fallback_on_poetry_create_error( "poetry.inspection.info.get_pep517_metadata" ) - PackageInfo.from_directory(FIXTURE_DIR_INSPECTIONS / "demo_poetry_package") + PackageInfo.from_directory(fixture_dir("inspection") / "demo_poetry_package") assert mock_create_poetry.call_count == 1 assert mock_get_poetry_package.call_count == 1 assert mock_get_pep517_metadata.call_count == 1 -def test_info_from_requires_txt() -> None: +def test_info_from_requires_txt(fixture_dir: FixtureDirGetter) -> None: info = PackageInfo.from_metadata( - FIXTURE_DIR_INSPECTIONS / "demo_only_requires_txt.egg-info" + fixture_dir("inspection") / "demo_only_requires_txt.egg-info" ) assert info is not None demo_check_info(info) @@ -171,9 +171,9 @@ def test_info_from_setup_cfg(demo_setup_cfg: Path) -> None: demo_check_info(info, requires_dist={"package"}) -def test_info_no_setup_pkg_info_no_deps() -> None: +def test_info_no_setup_pkg_info_no_deps(fixture_dir: FixtureDirGetter) -> None: info = PackageInfo.from_directory( - FIXTURE_DIR_INSPECTIONS / "demo_no_setup_pkg_info_no_deps", + fixture_dir("inspection") / "demo_no_setup_pkg_info_no_deps", disable_build=True, ) assert info.name == "demo" @@ -250,8 +250,8 @@ def test_info_setup_missing_mandatory_should_trigger_pep517( assert spy.call_count == 1 -def test_info_prefer_poetry_config_over_egg_info() -> None: +def test_info_prefer_poetry_config_over_egg_info(fixture_dir: FixtureDirGetter) -> None: info = PackageInfo.from_directory( - FIXTURE_DIR_INSPECTIONS / "demo_with_obsolete_egg_info" + fixture_dir("inspection") / "demo_with_obsolete_egg_info" ) demo_check_info(info) diff --git a/tests/installation/test_pip_installer.py b/tests/installation/test_pip_installer.py index fa16adc4aa7..2a07da92f73 100644 --- a/tests/installation/test_pip_installer.py +++ b/tests/installation/test_pip_installer.py @@ -24,6 +24,7 @@ from poetry.utils.env import VirtualEnv from tests.conftest import Config + from tests.types import FixtureDirGetter @pytest.fixture @@ -278,7 +279,10 @@ def test_install_with_trusted_host(config: Config, env: NullEnv) -> None: def test_install_directory_fallback_on_poetry_create_error( - mocker: MockerFixture, tmp_venv: VirtualEnv, pool: RepositoryPool + mocker: MockerFixture, + tmp_venv: VirtualEnv, + pool: RepositoryPool, + fixture_dir: FixtureDirGetter, ) -> None: mock_create_poetry = mocker.patch( "poetry.factory.Factory.create_poetry", side_effect=RuntimeError @@ -293,9 +297,7 @@ def test_install_directory_fallback_on_poetry_create_error( "demo", "1.0.0", source_type="directory", - source_url=str( - Path(__file__).parent.parent / "fixtures/inspection/demo_poetry_package" - ), + source_url=str(fixture_dir("inspection") / "demo_poetry_package"), ) installer = PipInstaller(tmp_venv, NullIO(), pool) diff --git a/tests/masonry/builders/test_editable_builder.py b/tests/masonry/builders/test_editable_builder.py index eab115b3181..28b6628a972 100644 --- a/tests/masonry/builders/test_editable_builder.py +++ b/tests/masonry/builders/test_editable_builder.py @@ -28,51 +28,40 @@ from pytest_mock import MockerFixture from poetry.poetry import Poetry + from tests.types import FixtureDirGetter @pytest.fixture() -def simple_poetry() -> Poetry: - poetry = Factory().create_poetry( - Path(__file__).parent.parent.parent / "fixtures" / "simple_project" - ) +def simple_poetry(fixture_dir: FixtureDirGetter) -> Poetry: + poetry = Factory().create_poetry(fixture_dir("simple_project")) return poetry @pytest.fixture() -def project_with_include() -> Poetry: - poetry = Factory().create_poetry( - Path(__file__).parent.parent.parent / "fixtures" / "with-include" - ) +def project_with_include(fixture_dir: FixtureDirGetter) -> Poetry: + poetry = Factory().create_poetry(fixture_dir("with-include")) return poetry @pytest.fixture() -def extended_poetry() -> Poetry: - poetry = Factory().create_poetry( - Path(__file__).parent.parent.parent / "fixtures" / "extended_project" - ) +def extended_poetry(fixture_dir: FixtureDirGetter) -> Poetry: + poetry = Factory().create_poetry(fixture_dir("extended_project")) return poetry @pytest.fixture() -def extended_without_setup_poetry() -> Poetry: - poetry = Factory().create_poetry( - Path(__file__).parent.parent.parent - / "fixtures" - / "extended_project_without_setup" - ) +def extended_without_setup_poetry(fixture_dir: FixtureDirGetter) -> Poetry: + poetry = Factory().create_poetry(fixture_dir("extended_project_without_setup")) return poetry @pytest.fixture -def with_multiple_readme_files() -> Poetry: - poetry = Factory().create_poetry( - Path(__file__).parent.parent.parent / "fixtures" / "with_multiple_readme_files" - ) +def with_multiple_readme_files(fixture_dir: FixtureDirGetter) -> Poetry: + poetry = Factory().create_poetry(fixture_dir("with_multiple_readme_files")) return poetry @@ -235,9 +224,11 @@ def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts( assert [] == env.executed -def test_builder_setup_generation_runs_with_pip_editable(tmp_path: Path) -> None: +def test_builder_setup_generation_runs_with_pip_editable( + fixture_dir: FixtureDirGetter, tmp_path: Path +) -> None: # create an isolated copy of the project - fixture = Path(__file__).parent.parent.parent / "fixtures" / "extended_project" + fixture = fixture_dir("extended_project") extended_project = tmp_path / "extended_project" shutil.copytree(fixture, extended_project) diff --git a/tests/mixology/version_solver/test_unsolvable.py b/tests/mixology/version_solver/test_unsolvable.py index 3886949a672..92cc5a98515 100644 --- a/tests/mixology/version_solver/test_unsolvable.py +++ b/tests/mixology/version_solver/test_unsolvable.py @@ -1,6 +1,5 @@ from __future__ import annotations -from pathlib import Path from typing import TYPE_CHECKING from poetry.factory import Factory @@ -13,6 +12,7 @@ from poetry.repositories import Repository from tests.mixology.version_solver.conftest import Provider + from tests.types import FixtureDirGetter def test_no_version_matching_constraint( @@ -94,11 +94,13 @@ def test_disjoint_root_constraints( def test_disjoint_root_constraints_path_dependencies( - root: ProjectPackage, provider: Provider, repo: Repository + root: ProjectPackage, + provider: Provider, + repo: Repository, + fixture_dir: FixtureDirGetter, ) -> None: provider.set_package_python_versions("^3.7") - fixtures = Path(__file__).parent.parent.parent / "fixtures" - project_dir = fixtures.joinpath("with_conditional_path_deps") + project_dir = fixture_dir("with_conditional_path_deps") dependency1 = Factory.create_dependency("demo", {"path": project_dir / "demo_one"}) root.add_dependency(dependency1) dependency2 = Factory.create_dependency("demo", {"path": project_dir / "demo_two"}) diff --git a/tests/packages/test_locker.py b/tests/packages/test_locker.py index 80b689b1570..bdd80e4ec86 100644 --- a/tests/packages/test_locker.py +++ b/tests/packages/test_locker.py @@ -737,14 +737,13 @@ def test_locker_should_raise_an_error_if_lock_version_is_newer_and_not_allowed( def test_root_extras_dependencies_are_ordered( - locker: Locker, root: ProjectPackage + locker: Locker, root: ProjectPackage, fixture_base: Path ) -> None: - root_dir = Path(__file__).parent.parent.joinpath("fixtures") - Factory.create_dependency("B", "1.0.0", root_dir=root_dir) - Factory.create_dependency("C", "1.0.0", root_dir=root_dir) - package_first = Factory.create_dependency("first", "1.0.0", root_dir=root_dir) - package_second = Factory.create_dependency("second", "1.0.0", root_dir=root_dir) - package_third = Factory.create_dependency("third", "1.0.0", root_dir=root_dir) + Factory.create_dependency("B", "1.0.0", root_dir=fixture_base) + Factory.create_dependency("C", "1.0.0", root_dir=fixture_base) + package_first = Factory.create_dependency("first", "1.0.0", root_dir=fixture_base) + package_second = Factory.create_dependency("second", "1.0.0", root_dir=fixture_base) + package_third = Factory.create_dependency("third", "1.0.0", root_dir=fixture_base) root.extras = { "C": [package_third, package_second, package_first], @@ -834,25 +833,24 @@ def test_locker_should_neither_emit_warnings_nor_raise_error_for_lower_compatibl def test_locker_dumps_dependency_information_correctly( - locker: Locker, root: ProjectPackage + locker: Locker, root: ProjectPackage, fixture_base: Path ) -> None: - root_dir = Path(__file__).parent.parent.joinpath("fixtures") package_a = get_package("A", "1.0.0") package_a.add_dependency( Factory.create_dependency( - "B", {"path": "project_with_extras", "develop": True}, root_dir=root_dir + "B", {"path": "project_with_extras", "develop": True}, root_dir=fixture_base ) ) package_a.add_dependency( Factory.create_dependency( "C", {"path": "directory/project_with_transitive_directory_dependencies"}, - root_dir=root_dir, + root_dir=fixture_base, ) ) package_a.add_dependency( Factory.create_dependency( - "D", {"path": "distributions/demo-0.1.0.tar.gz"}, root_dir=root_dir + "D", {"path": "distributions/demo-0.1.0.tar.gz"}, root_dir=fixture_base ) ) package_a.add_dependency( @@ -969,15 +967,14 @@ def test_locker_dumps_subdir(locker: Locker, root: ProjectPackage) -> None: def test_locker_dumps_dependency_extras_in_correct_order( - locker: Locker, root: ProjectPackage + locker: Locker, root: ProjectPackage, fixture_base: Path ) -> None: - root_dir = Path(__file__).parent.parent.joinpath("fixtures") package_a = get_package("A", "1.0.0") - Factory.create_dependency("B", "1.0.0", root_dir=root_dir) - Factory.create_dependency("C", "1.0.0", root_dir=root_dir) - package_first = Factory.create_dependency("first", "1.0.0", root_dir=root_dir) - package_second = Factory.create_dependency("second", "1.0.0", root_dir=root_dir) - package_third = Factory.create_dependency("third", "1.0.0", root_dir=root_dir) + Factory.create_dependency("B", "1.0.0", root_dir=fixture_base) + Factory.create_dependency("C", "1.0.0", root_dir=fixture_base) + package_first = Factory.create_dependency("first", "1.0.0", root_dir=fixture_base) + package_second = Factory.create_dependency("second", "1.0.0", root_dir=fixture_base) + package_third = Factory.create_dependency("third", "1.0.0", root_dir=fixture_base) package_a.extras = { "C": [package_third, package_second, package_first], diff --git a/tests/plugins/test_plugin_manager.py b/tests/plugins/test_plugin_manager.py index a3fcc4249d1..fb2ce6c94f7 100644 --- a/tests/plugins/test_plugin_manager.py +++ b/tests/plugins/test_plugin_manager.py @@ -1,6 +1,5 @@ from __future__ import annotations -from pathlib import Path from typing import TYPE_CHECKING import pytest @@ -21,8 +20,7 @@ from pytest_mock import MockerFixture from tests.conftest import Config - -CWD = Path(__file__).parent.parent / "fixtures" / "simple_project" + from tests.types import FixtureDirGetter class ManagerFactory(Protocol): @@ -46,13 +44,14 @@ def activate(self, poetry: Poetry, io: BufferedIO) -> None: poetry.package.version = "9.9.9" -@pytest.fixture() -def poetry(tmp_path: Path, config: Config) -> Poetry: +@pytest.fixture +def poetry(fixture_dir: FixtureDirGetter, config: Config) -> Poetry: + project_path = fixture_dir("simple_project") poetry = Poetry( - CWD / "pyproject.toml", + project_path / "pyproject.toml", {}, ProjectPackage("simple-project", "1.2.3"), - Locker(CWD / "poetry.lock", {}), + Locker(project_path / "poetry.lock", {}), config, ) @@ -82,7 +81,7 @@ def test_load_plugins_and_activate( poetry: Poetry, io: BufferedIO, with_my_plugin: None, -): +) -> None: manager = manager_factory() manager.load_plugins() manager.activate(poetry, io) @@ -106,7 +105,7 @@ def test_load_plugins_with_invalid_plugin( poetry: Poetry, io: BufferedIO, with_invalid_plugin: None, -): +) -> None: manager = manager_factory() with pytest.raises(ValueError): @@ -118,7 +117,7 @@ def test_load_plugins_with_plugins_disabled( poetry: Poetry, io: BufferedIO, with_my_plugin: None, -): +) -> None: no_plugin_manager.load_plugins() assert poetry.package.version.text == "1.2.3" diff --git a/tests/puzzle/test_provider.py b/tests/puzzle/test_provider.py index 9554a1fdae9..560ae872f05 100644 --- a/tests/puzzle/test_provider.py +++ b/tests/puzzle/test_provider.py @@ -1,6 +1,5 @@ from __future__ import annotations -from pathlib import Path from subprocess import CalledProcessError from typing import TYPE_CHECKING @@ -29,6 +28,8 @@ if TYPE_CHECKING: from pytest_mock import MockerFixture + from tests.types import FixtureDirGetter + SOME_URL = "https://example.com/path.tar.gz" @@ -157,7 +158,7 @@ def test_search_for_direct_origin_and_extras( @pytest.mark.parametrize("value", [True, False]) -def test_search_for_vcs_retains_develop_flag(provider: Provider, value: bool): +def test_search_for_vcs_retains_develop_flag(provider: Provider, value: bool) -> None: dependency = VCSDependency( "demo", "git", "https://github.com/demo/demo.git", develop=value ) @@ -165,7 +166,7 @@ def test_search_for_vcs_retains_develop_flag(provider: Provider, value: bool): assert package.develop == value -def test_search_for_vcs_setup_egg_info(provider: Provider): +def test_search_for_vcs_setup_egg_info(provider: Provider) -> None: dependency = VCSDependency("demo", "git", "https://github.com/demo/demo.git") package = provider.search_for_direct_origin_dependency(dependency) @@ -183,7 +184,7 @@ def test_search_for_vcs_setup_egg_info(provider: Provider): } -def test_search_for_vcs_setup_egg_info_with_extras(provider: Provider): +def test_search_for_vcs_setup_egg_info_with_extras(provider: Provider) -> None: dependency = VCSDependency( "demo", "git", "https://github.com/demo/demo.git", extras=["foo"] ) @@ -203,7 +204,7 @@ def test_search_for_vcs_setup_egg_info_with_extras(provider: Provider): } -def test_search_for_vcs_read_setup(provider: Provider, mocker: MockerFixture): +def test_search_for_vcs_read_setup(provider: Provider, mocker: MockerFixture) -> None: mocker.patch("poetry.utils.env.EnvManager.get", return_value=MockEnv()) dependency = VCSDependency("demo", "git", "https://github.com/demo/demo.git") @@ -225,7 +226,7 @@ def test_search_for_vcs_read_setup(provider: Provider, mocker: MockerFixture): def test_search_for_vcs_read_setup_with_extras( provider: Provider, mocker: MockerFixture -): +) -> None: mocker.patch("poetry.utils.env.EnvManager.get", return_value=MockEnv()) dependency = VCSDependency( @@ -245,7 +246,7 @@ def test_search_for_vcs_read_setup_with_extras( def test_search_for_vcs_read_setup_raises_error_if_no_version( provider: Provider, mocker: MockerFixture -): +) -> None: mocker.patch( "poetry.inspection.info.get_pep517_metadata", return_value=PackageInfo(name="demo", version=None), @@ -258,15 +259,12 @@ def test_search_for_vcs_read_setup_raises_error_if_no_version( @pytest.mark.parametrize("directory", ["demo", "non-canonical-name"]) -def test_search_for_directory_setup_egg_info(provider: Provider, directory: str): +def test_search_for_directory_setup_egg_info( + provider: Provider, directory: str, fixture_dir: FixtureDirGetter +) -> None: dependency = DirectoryDependency( "demo", - Path(__file__).parent.parent - / "fixtures" - / "git" - / "github.com" - / "demo" - / directory, + fixture_dir("git") / "github.com" / "demo" / directory, ) package = provider.search_for_direct_origin_dependency(dependency) @@ -284,15 +282,12 @@ def test_search_for_directory_setup_egg_info(provider: Provider, directory: str) } -def test_search_for_directory_setup_egg_info_with_extras(provider: Provider): +def test_search_for_directory_setup_egg_info_with_extras( + provider: Provider, fixture_dir: FixtureDirGetter +) -> None: dependency = DirectoryDependency( "demo", - Path(__file__).parent.parent - / "fixtures" - / "git" - / "github.com" - / "demo" - / "demo", + fixture_dir("git") / "github.com" / "demo" / "demo", extras=["foo"], ) @@ -312,21 +307,13 @@ def test_search_for_directory_setup_egg_info_with_extras(provider: Provider): @pytest.mark.parametrize("directory", ["demo", "non-canonical-name"]) -def test_search_for_directory_setup_with_base(provider: Provider, directory: str): +def test_search_for_directory_setup_with_base( + provider: Provider, directory: str, fixture_dir: FixtureDirGetter +) -> None: dependency = DirectoryDependency( "demo", - Path(__file__).parent.parent - / "fixtures" - / "git" - / "github.com" - / "demo" - / directory, - base=Path(__file__).parent.parent - / "fixtures" - / "git" - / "github.com" - / "demo" - / directory, + fixture_dir("git") / "github.com" / "demo" / directory, + base=fixture_dir("git") / "github.com" / "demo" / directory, ) package = provider.search_for_direct_origin_dependency(dependency) @@ -342,29 +329,17 @@ def test_search_for_directory_setup_with_base(provider: Provider, directory: str "foo": [get_dependency("cleo")], "bar": [get_dependency("tomlkit")], } - assert package.root_dir == ( - Path(__file__).parent.parent - / "fixtures" - / "git" - / "github.com" - / "demo" - / directory - ) + assert package.root_dir == (fixture_dir("git") / "github.com" / "demo" / directory) def test_search_for_directory_setup_read_setup( - provider: Provider, mocker: MockerFixture -): + provider: Provider, mocker: MockerFixture, fixture_dir: FixtureDirGetter +) -> None: mocker.patch("poetry.utils.env.EnvManager.get", return_value=MockEnv()) dependency = DirectoryDependency( "demo", - Path(__file__).parent.parent - / "fixtures" - / "git" - / "github.com" - / "demo" - / "demo", + fixture_dir("git") / "github.com" / "demo" / "demo", ) package = provider.search_for_direct_origin_dependency(dependency) @@ -383,18 +358,13 @@ def test_search_for_directory_setup_read_setup( def test_search_for_directory_setup_read_setup_with_extras( - provider: Provider, mocker: MockerFixture -): + provider: Provider, mocker: MockerFixture, fixture_dir: FixtureDirGetter +) -> None: mocker.patch("poetry.utils.env.EnvManager.get", return_value=MockEnv()) dependency = DirectoryDependency( "demo", - Path(__file__).parent.parent - / "fixtures" - / "git" - / "github.com" - / "demo" - / "demo", + fixture_dir("git") / "github.com" / "demo" / "demo", extras=["foo"], ) @@ -413,15 +383,12 @@ def test_search_for_directory_setup_read_setup_with_extras( } -def test_search_for_directory_setup_read_setup_with_no_dependencies(provider: Provider): +def test_search_for_directory_setup_read_setup_with_no_dependencies( + provider: Provider, fixture_dir: FixtureDirGetter +) -> None: dependency = DirectoryDependency( "demo", - Path(__file__).parent.parent - / "fixtures" - / "git" - / "github.com" - / "demo" - / "no-dependencies", + fixture_dir("git") / "github.com" / "demo" / "no-dependencies", ) package = provider.search_for_direct_origin_dependency(dependency) @@ -432,10 +399,12 @@ def test_search_for_directory_setup_read_setup_with_no_dependencies(provider: Pr assert package.extras == {} -def test_search_for_directory_poetry(provider: Provider): +def test_search_for_directory_poetry( + provider: Provider, fixture_dir: FixtureDirGetter +) -> None: dependency = DirectoryDependency( "project-with-extras", - Path(__file__).parent.parent / "fixtures" / "project_with_extras", + fixture_dir("project_with_extras"), ) package = provider.search_for_direct_origin_dependency(dependency) @@ -460,10 +429,12 @@ def test_search_for_directory_poetry(provider: Provider): } -def test_search_for_directory_poetry_with_extras(provider: Provider): +def test_search_for_directory_poetry_with_extras( + provider: Provider, fixture_dir: FixtureDirGetter +) -> None: dependency = DirectoryDependency( "project-with-extras", - Path(__file__).parent.parent / "fixtures" / "project_with_extras", + fixture_dir("project_with_extras"), extras=["extras_a"], ) @@ -489,13 +460,12 @@ def test_search_for_directory_poetry_with_extras(provider: Provider): } -def test_search_for_file_sdist(provider: Provider): +def test_search_for_file_sdist( + provider: Provider, fixture_dir: FixtureDirGetter +) -> None: dependency = FileDependency( "demo", - Path(__file__).parent.parent - / "fixtures" - / "distributions" - / "demo-0.1.0.tar.gz", + fixture_dir("distributions") / "demo-0.1.0.tar.gz", ) package = provider.search_for_direct_origin_dependency(dependency) @@ -520,13 +490,12 @@ def test_search_for_file_sdist(provider: Provider): } -def test_search_for_file_sdist_with_extras(provider: Provider): +def test_search_for_file_sdist_with_extras( + provider: Provider, fixture_dir: FixtureDirGetter +) -> None: dependency = FileDependency( "demo", - Path(__file__).parent.parent - / "fixtures" - / "distributions" - / "demo-0.1.0.tar.gz", + fixture_dir("distributions") / "demo-0.1.0.tar.gz", extras=["foo"], ) @@ -552,13 +521,12 @@ def test_search_for_file_sdist_with_extras(provider: Provider): } -def test_search_for_file_wheel(provider: Provider): +def test_search_for_file_wheel( + provider: Provider, fixture_dir: FixtureDirGetter +) -> None: dependency = FileDependency( "demo", - Path(__file__).parent.parent - / "fixtures" - / "distributions" - / "demo-0.1.0-py2.py3-none-any.whl", + fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl", ) package = provider.search_for_direct_origin_dependency(dependency) @@ -583,13 +551,12 @@ def test_search_for_file_wheel(provider: Provider): } -def test_search_for_file_wheel_with_extras(provider: Provider): +def test_search_for_file_wheel_with_extras( + provider: Provider, fixture_dir: FixtureDirGetter +) -> None: dependency = FileDependency( "demo", - Path(__file__).parent.parent - / "fixtures" - / "distributions" - / "demo-0.1.0-py2.py3-none-any.whl", + fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl", extras=["foo"], ) @@ -639,10 +606,9 @@ def test_complete_package_does_not_merge_different_source_names( def test_complete_package_preserves_source_type( - provider: Provider, root: ProjectPackage + provider: Provider, root: ProjectPackage, fixture_dir: FixtureDirGetter ) -> None: - fixtures = Path(__file__).parent.parent / "fixtures" - project_dir = fixtures.joinpath("with_conditional_path_deps") + project_dir = fixture_dir("with_conditional_path_deps") for folder in ["demo_one", "demo_two"]: path = (project_dir / folder).as_posix() root.add_dependency(Factory.create_dependency("demo", {"path": path})) @@ -737,7 +703,7 @@ def test_complete_package_with_extras_preserves_source_name( @pytest.mark.parametrize("with_extra", [False, True]) def test_complete_package_fetches_optional_vcs_dependency_only_if_requested( provider: Provider, repository: Repository, mocker: MockerFixture, with_extra: bool -): +) -> None: optional_vcs_dependency = Factory.create_dependency( "demo", {"git": "https://github.com/demo/demo.git", "optional": True} ) diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 080fa975c63..10e67c9ab6e 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -2,7 +2,6 @@ import re -from pathlib import Path from typing import TYPE_CHECKING from typing import Any @@ -43,6 +42,7 @@ from poetry.installation.operations.operation import Operation from poetry.puzzle.provider import Provider from poetry.puzzle.transaction import Transaction + from tests.types import FixtureDirGetter DEFAULT_SOURCE_REF = ( VCSDependency("poetry", "git", "git@github.com:python-poetry/poetry.git").branch @@ -115,7 +115,7 @@ def check_solver_result( def test_solver_install_single( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.0") @@ -128,7 +128,7 @@ def test_solver_install_single( def test_solver_remove_if_no_longer_locked( package: ProjectPackage, pool: RepositoryPool, io: NullIO -): +) -> None: package_a = get_package("A", "1.0") solver = Solver(package, pool, [package_a], [package_a], io) @@ -139,7 +139,7 @@ def test_solver_remove_if_no_longer_locked( def test_remove_non_installed( package: ProjectPackage, repo: Repository, pool: RepositoryPool, io: NullIO -): +) -> None: package_a = get_package("A", "1.0") repo.add_package(package_a) @@ -151,7 +151,7 @@ def test_remove_non_installed( def test_install_non_existing_package_fail( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("B", "1")) package_a = get_package("A", "1.0") @@ -163,7 +163,7 @@ def test_install_non_existing_package_fail( def test_install_unpublished_package_does_not_fail( package: ProjectPackage, repo: Repository, pool: RepositoryPool, io: NullIO -): +) -> None: package.add_dependency(Factory.create_dependency("B", "1")) package_a = get_package("A", "1.0") @@ -188,7 +188,9 @@ def test_install_unpublished_package_does_not_fail( ) -def test_solver_with_deps(solver: Solver, repo: Repository, package: ProjectPackage): +def test_solver_with_deps( + solver: Solver, repo: Repository, package: ProjectPackage +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.0") @@ -214,7 +216,7 @@ def test_solver_with_deps(solver: Solver, repo: Repository, package: ProjectPack def test_install_honours_not_equal( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.0") @@ -244,7 +246,7 @@ def test_install_honours_not_equal( def test_install_with_deps_in_order( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package.add_dependency(Factory.create_dependency("B", "*")) package.add_dependency(Factory.create_dependency("C", "*")) @@ -275,7 +277,7 @@ def test_install_with_deps_in_order( def test_install_installed( package: ProjectPackage, repo: Repository, pool: RepositoryPool, io: NullIO -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.0") @@ -291,7 +293,7 @@ def test_install_installed( def test_update_installed( package: ProjectPackage, repo: Repository, pool: RepositoryPool, io: NullIO -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.0") @@ -309,7 +311,7 @@ def test_update_installed( def test_update_with_use_latest( package: ProjectPackage, repo: Repository, pool: RepositoryPool, io: NullIO -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package.add_dependency(Factory.create_dependency("B", "*")) @@ -337,7 +339,9 @@ def test_update_with_use_latest( ) -def test_solver_sets_groups(solver: Solver, repo: Repository, package: ProjectPackage): +def test_solver_sets_groups( + solver: Solver, repo: Repository, package: ProjectPackage +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package.add_dependency(Factory.create_dependency("B", "*", groups=["dev"])) @@ -368,7 +372,7 @@ def test_solver_sets_groups(solver: Solver, repo: Repository, package: ProjectPa def test_solver_respects_root_package_python_versions( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: set_package_python_versions(solver.provider, "~3.4") package.add_dependency(Factory.create_dependency("A", "*")) package.add_dependency(Factory.create_dependency("B", "*")) @@ -401,7 +405,7 @@ def test_solver_respects_root_package_python_versions( def test_solver_fails_if_mismatch_root_python_versions( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: set_package_python_versions(solver.provider, "^3.4") package.add_dependency(Factory.create_dependency("A", "*")) package.add_dependency(Factory.create_dependency("B", "*")) @@ -423,7 +427,7 @@ def test_solver_fails_if_mismatch_root_python_versions( def test_solver_ignores_python_restricted_if_mismatch_root_package_python_versions( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: set_package_python_versions(solver.provider, "~3.8") package.add_dependency( Factory.create_dependency("A", {"version": "1.0", "python": "<3.8"}) @@ -447,7 +451,7 @@ def test_solver_ignores_python_restricted_if_mismatch_root_package_python_versio def test_solver_solves_optional_and_compatible_packages( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: set_package_python_versions(solver.provider, "~3.4") package.extras["foo"] = [get_dependency("B")] package.add_dependency( @@ -482,7 +486,7 @@ def test_solver_solves_optional_and_compatible_packages( def test_solver_does_not_return_extras_if_not_requested( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package.add_dependency(Factory.create_dependency("B", "*")) @@ -509,7 +513,7 @@ def test_solver_does_not_return_extras_if_not_requested( def test_solver_returns_extras_if_requested( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package.add_dependency( Factory.create_dependency("B", {"version": "*", "extras": ["foo"]}) @@ -549,7 +553,7 @@ def test_solver_returns_extras_only_requested( repo: Repository, package: ProjectPackage, enabled_extra: bool | None, -): +) -> None: extras = [enabled_extra] if enabled_extra is not None else [] package.add_dependency(Factory.create_dependency("A", "*")) @@ -611,7 +615,7 @@ def test_solver_returns_extras_when_multiple_extras_use_same_dependency( repo: Repository, package: ProjectPackage, enabled_extra: bool | None, -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.0") @@ -660,7 +664,7 @@ def test_solver_returns_extras_only_requested_nested( repo: Repository, package: ProjectPackage, enabled_extra: bool | None, -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.0") @@ -715,7 +719,7 @@ def test_solver_returns_extras_only_requested_nested( def test_solver_finds_extras_next_to_non_extras( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: # Root depends on A[foo] package.add_dependency( Factory.create_dependency("A", {"version": "*", "extras": ["foo"]}) @@ -762,7 +766,7 @@ def test_solver_finds_extras_next_to_non_extras( def test_solver_merge_extras_into_base_package_multiple_repos_fixes_5727( solver: Solver, repo: Repository, pool: RepositoryPool, package: ProjectPackage -): +) -> None: package.add_dependency( Factory.create_dependency("A", {"version": "*", "source": "legacy"}) ) @@ -794,7 +798,7 @@ def test_solver_merge_extras_into_base_package_multiple_repos_fixes_5727( def test_solver_returns_extras_if_excluded_by_markers_without_extras( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency( Factory.create_dependency("A", {"version": "*", "extras": ["foo"]}) ) @@ -833,7 +837,7 @@ def test_solver_returns_extras_if_excluded_by_markers_without_extras( def test_solver_returns_prereleases_if_requested( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package.add_dependency(Factory.create_dependency("B", "*")) package.add_dependency( @@ -864,7 +868,7 @@ def test_solver_returns_prereleases_if_requested( def test_solver_does_not_return_prereleases_if_not_requested( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package.add_dependency(Factory.create_dependency("B", "*")) package.add_dependency(Factory.create_dependency("C", "*")) @@ -893,7 +897,7 @@ def test_solver_does_not_return_prereleases_if_not_requested( def test_solver_sub_dependencies_with_requirements( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package.add_dependency(Factory.create_dependency("B", "*")) @@ -931,7 +935,7 @@ def test_solver_sub_dependencies_with_requirements( def test_solver_sub_dependencies_with_requirements_complex( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency( Factory.create_dependency("A", {"version": "^1.0", "python": "<5.0"}) ) @@ -990,7 +994,7 @@ def test_solver_sub_dependencies_with_requirements_complex( def test_solver_sub_dependencies_with_not_supported_python_version( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: set_package_python_versions(solver.provider, "^3.5") package.add_dependency(Factory.create_dependency("A", "*")) @@ -1012,7 +1016,7 @@ def test_solver_sub_dependencies_with_not_supported_python_version( def test_solver_sub_dependencies_with_not_supported_python_version_transitive( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: set_package_python_versions(solver.provider, "^3.4") package.add_dependency( @@ -1056,7 +1060,7 @@ def test_solver_sub_dependencies_with_not_supported_python_version_transitive( def test_solver_with_dependency_in_both_main_and_dev_dependencies( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: set_package_python_versions(solver.provider, "^3.5") package.add_dependency(Factory.create_dependency("A", "*")) package.add_dependency( @@ -1109,7 +1113,7 @@ def test_solver_with_dependency_in_both_main_and_dev_dependencies( def test_solver_with_dependency_in_both_main_and_dev_dependencies_with_one_more_dependent( # noqa: E501 solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package.add_dependency(Factory.create_dependency("E", "*")) package.add_dependency( @@ -1169,7 +1173,7 @@ def test_solver_with_dependency_in_both_main_and_dev_dependencies_with_one_more_ def test_solver_with_dependency_and_prerelease_sub_dependencies( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.0") @@ -1196,7 +1200,7 @@ def test_solver_with_dependency_and_prerelease_sub_dependencies( def test_solver_circular_dependency( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.0") @@ -1228,7 +1232,7 @@ def test_solver_circular_dependency( def test_solver_circular_dependency_chain( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.0") @@ -1265,7 +1269,7 @@ def test_solver_circular_dependency_chain( def test_solver_dense_dependencies( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: # The root package depends on packages A0...An-1, # And package Ai depends on packages A0...Ai-1 # This graph is a transitive tournament @@ -1288,7 +1292,7 @@ def test_solver_dense_dependencies( def test_solver_duplicate_dependencies_same_constraint( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.0") @@ -1317,7 +1321,7 @@ def test_solver_duplicate_dependencies_same_constraint( def test_solver_duplicate_dependencies_different_constraints( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.0") @@ -1349,7 +1353,7 @@ def test_solver_duplicate_dependencies_different_constraints( def test_solver_duplicate_dependencies_different_constraints_same_requirements( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.0") @@ -1376,7 +1380,7 @@ def test_solver_duplicate_dependencies_different_constraints_same_requirements( def test_solver_duplicate_dependencies_different_constraints_merge_by_marker( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.0") @@ -1414,7 +1418,7 @@ def test_solver_duplicate_dependencies_different_constraints_merge_by_marker( @pytest.mark.parametrize("git_first", [False, True]) def test_solver_duplicate_dependencies_different_sources_types_are_preserved( solver: Solver, repo: Repository, package: ProjectPackage, git_first: bool -): +) -> None: pendulum = get_package("pendulum", "2.0.3") repo.add_package(pendulum) repo.add_package(get_package("cleo", "1.0.0")) @@ -1482,7 +1486,7 @@ def test_solver_duplicate_dependencies_different_sources_types_are_preserved( def test_solver_duplicate_dependencies_different_constraints_merge_no_markers( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package.add_dependency(Factory.create_dependency("B", "1.0")) @@ -1683,7 +1687,7 @@ def test_solver_duplicate_dependencies_different_constraints_discard_no_markers3 def test_solver_duplicate_dependencies_ignore_overrides_with_empty_marker_intersection( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: """ Distinct requirements per marker: * Python 2.7: A (which requires B) and B @@ -1754,7 +1758,7 @@ def test_solver_duplicate_dependencies_ignore_overrides_with_empty_marker_inters def test_solver_duplicate_dependencies_ignore_overrides_with_empty_marker_intersection2( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: """ Empty intersection between top level dependency and transient dependency. """ @@ -1797,7 +1801,7 @@ def test_solver_duplicate_dependencies_ignore_overrides_with_empty_marker_inters def test_solver_duplicate_dependencies_sub_dependencies( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.0") @@ -1836,10 +1840,11 @@ def test_solver_duplicate_dependencies_sub_dependencies( ) -def test_duplicate_path_dependencies(solver: Solver, package: ProjectPackage) -> None: +def test_duplicate_path_dependencies( + solver: Solver, package: ProjectPackage, fixture_dir: FixtureDirGetter +) -> None: set_package_python_versions(solver.provider, "^3.7") - fixtures = Path(__file__).parent.parent / "fixtures" - project_dir = fixtures / "with_conditional_path_deps" + project_dir = fixture_dir("with_conditional_path_deps") path1 = (project_dir / "demo_one").as_posix() demo1 = Package("demo", "1.2.3", source_type="directory", source_url=path1) @@ -1869,11 +1874,10 @@ def test_duplicate_path_dependencies(solver: Solver, package: ProjectPackage) -> def test_duplicate_path_dependencies_same_path( - solver: Solver, package: ProjectPackage + solver: Solver, package: ProjectPackage, fixture_dir: FixtureDirGetter ) -> None: set_package_python_versions(solver.provider, "^3.7") - fixtures = Path(__file__).parent.parent / "fixtures" - project_dir = fixtures / "with_conditional_path_deps" + project_dir = fixture_dir("with_conditional_path_deps") path1 = (project_dir / "demo_one").as_posix() demo1 = Package("demo", "1.2.3", source_type="directory", source_url=path1) @@ -1895,7 +1899,7 @@ def test_duplicate_path_dependencies_same_path( def test_solver_fails_if_dependency_name_does_not_match_package( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency( Factory.create_dependency( "my-demo", {"git": "https://github.com/demo/demo.git"} @@ -1908,7 +1912,7 @@ def test_solver_fails_if_dependency_name_does_not_match_package( def test_solver_does_not_get_stuck_in_recursion_on_circular_dependency( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package_a = get_package("A", "1.0") package_a.add_dependency(Factory.create_dependency("B", "^1.0")) package_b = get_package("B", "1.0") @@ -1936,7 +1940,7 @@ def test_solver_does_not_get_stuck_in_recursion_on_circular_dependency( def test_solver_can_resolve_git_dependencies( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: pendulum = get_package("pendulum", "2.0.3") cleo = get_package("cleo", "1.0.0") repo.add_package(pendulum) @@ -1971,7 +1975,7 @@ def test_solver_can_resolve_git_dependencies( def test_solver_can_resolve_git_dependencies_with_extras( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: pendulum = get_package("pendulum", "2.0.3") cleo = get_package("cleo", "1.0.0") repo.add_package(pendulum) @@ -2011,7 +2015,7 @@ def test_solver_can_resolve_git_dependencies_with_extras( ) def test_solver_can_resolve_git_dependencies_with_ref( solver: Solver, repo: Repository, package: ProjectPackage, ref: dict[str, str] -): +) -> None: pendulum = get_package("pendulum", "2.0.3") cleo = get_package("cleo", "1.0.0") repo.add_package(pendulum) @@ -2046,7 +2050,7 @@ def test_solver_can_resolve_git_dependencies_with_ref( def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requirement_is_compatible( # noqa: E501 solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: set_package_python_versions(solver.provider, "~2.7 || ^3.4") package.add_dependency( Factory.create_dependency("A", {"version": "^1.0", "python": "^3.6"}) @@ -2064,7 +2068,7 @@ def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requir def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requirement_is_compatible_multiple( # noqa: E501 solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: set_package_python_versions(solver.provider, "~2.7 || ^3.4") package.add_dependency( Factory.create_dependency("A", {"version": "^1.0", "python": "^3.6"}) @@ -2096,7 +2100,7 @@ def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requir def test_solver_triggers_conflict_for_dependency_python_not_fully_compatible_with_package_python( # noqa: E501 solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: set_package_python_versions(solver.provider, "~2.7 || ^3.4") package.add_dependency( Factory.create_dependency("A", {"version": "^1.0", "python": "^3.5"}) @@ -2113,7 +2117,7 @@ def test_solver_triggers_conflict_for_dependency_python_not_fully_compatible_wit def test_solver_finds_compatible_package_for_dependency_python_not_fully_compatible_with_package_python( # noqa: E501 solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: set_package_python_versions(solver.provider, "~2.7 || ^3.4") package.add_dependency( Factory.create_dependency("A", {"version": "^1.0", "python": "^3.5"}) @@ -2135,7 +2139,7 @@ def test_solver_finds_compatible_package_for_dependency_python_not_fully_compati def test_solver_does_not_trigger_new_resolution_on_duplicate_dependencies_if_only_extras( # noqa: E501 solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: dep1 = Dependency.create_from_pep_508('B (>=1.0); extra == "foo"') dep1.activate() dep2 = Dependency.create_from_pep_508('B (>=2.0); extra == "bar"') @@ -2173,7 +2177,7 @@ def test_solver_does_not_trigger_new_resolution_on_duplicate_dependencies_if_onl def test_solver_does_not_raise_conflict_for_locked_conditional_dependencies( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: set_package_python_versions(solver.provider, "~2.7 || ^3.4") package.add_dependency( Factory.create_dependency("A", {"version": "^1.0", "python": "^3.6"}) @@ -2205,7 +2209,7 @@ def test_solver_does_not_raise_conflict_for_locked_conditional_dependencies( def test_solver_returns_extras_if_requested_in_dependencies_and_not_in_root_package( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package.add_dependency(Factory.create_dependency("B", "*")) package.add_dependency(Factory.create_dependency("C", "*")) @@ -2244,7 +2248,7 @@ def test_solver_returns_extras_if_requested_in_dependencies_and_not_in_root_pack def test_solver_should_not_resolve_prerelease_version_if_not_requested( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "~1.8.0")) package.add_dependency(Factory.create_dependency("B", "^0.5.0")) @@ -2263,7 +2267,7 @@ def test_solver_should_not_resolve_prerelease_version_if_not_requested( def test_solver_ignores_dependencies_with_incompatible_python_full_version_marker( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: set_package_python_versions(solver.provider, "^3.6") package.add_dependency(Factory.create_dependency("A", "^1.0")) package.add_dependency(Factory.create_dependency("B", "^2.0")) @@ -2296,7 +2300,7 @@ def test_solver_ignores_dependencies_with_incompatible_python_full_version_marke def test_solver_git_dependencies_update( package: ProjectPackage, repo: Repository, pool: RepositoryPool, io: NullIO -): +) -> None: pendulum = get_package("pendulum", "2.0.3") cleo = get_package("cleo", "1.0.0") repo.add_package(pendulum) @@ -2345,7 +2349,7 @@ def test_solver_git_dependencies_update( def test_solver_git_dependencies_update_skipped( package: ProjectPackage, repo: Repository, pool: RepositoryPool, io: NullIO -): +) -> None: pendulum = get_package("pendulum", "2.0.3") cleo = get_package("cleo", "1.0.0") repo.add_package(pendulum) @@ -2378,7 +2382,7 @@ def test_solver_git_dependencies_update_skipped( def test_solver_git_dependencies_short_hash_update_skipped( package: ProjectPackage, repo: Repository, pool: RepositoryPool, io: NullIO -): +) -> None: pendulum = get_package("pendulum", "2.0.3") cleo = get_package("cleo", "1.0.0") repo.add_package(pendulum) @@ -2423,19 +2427,15 @@ def test_solver_git_dependencies_short_hash_update_skipped( def test_solver_can_resolve_directory_dependencies( - solver: Solver, repo: Repository, package: ProjectPackage -): + solver: Solver, + repo: Repository, + package: ProjectPackage, + fixture_dir: FixtureDirGetter, +) -> None: pendulum = get_package("pendulum", "2.0.3") repo.add_package(pendulum) - path = ( - Path(__file__).parent.parent - / "fixtures" - / "git" - / "github.com" - / "demo" - / "demo" - ).as_posix() + path = (fixture_dir("git") / "github.com" / "demo" / "demo").as_posix() package.add_dependency(Factory.create_dependency("demo", {"path": path})) @@ -2460,8 +2460,9 @@ def test_solver_can_resolve_directory_dependencies_nested_editable( repo: Repository, pool: RepositoryPool, io: NullIO, -): - base = Path(__file__).parent.parent / "fixtures" / "project_with_nested_local" + fixture_dir: FixtureDirGetter, +) -> None: + base = fixture_dir("project_with_nested_local") poetry = Factory().create_poetry(cwd=base) package = poetry.package @@ -2511,21 +2512,17 @@ def test_solver_can_resolve_directory_dependencies_nested_editable( def test_solver_can_resolve_directory_dependencies_with_extras( - solver: Solver, repo: Repository, package: ProjectPackage -): + solver: Solver, + repo: Repository, + package: ProjectPackage, + fixture_dir: FixtureDirGetter, +) -> None: pendulum = get_package("pendulum", "2.0.3") cleo = get_package("cleo", "1.0.0") repo.add_package(pendulum) repo.add_package(cleo) - path = ( - Path(__file__).parent.parent - / "fixtures" - / "git" - / "github.com" - / "demo" - / "demo" - ).as_posix() + path = (fixture_dir("git") / "github.com" / "demo" / "demo").as_posix() package.add_dependency( Factory.create_dependency("demo", {"path": path, "extras": ["foo"]}) @@ -2553,17 +2550,15 @@ def test_solver_can_resolve_directory_dependencies_with_extras( def test_solver_can_resolve_sdist_dependencies( - solver: Solver, repo: Repository, package: ProjectPackage -): + solver: Solver, + repo: Repository, + package: ProjectPackage, + fixture_dir: FixtureDirGetter, +) -> None: pendulum = get_package("pendulum", "2.0.3") repo.add_package(pendulum) - path = ( - Path(__file__).parent.parent - / "fixtures" - / "distributions" - / "demo-0.1.0.tar.gz" - ).as_posix() + path = (fixture_dir("distributions") / "demo-0.1.0.tar.gz").as_posix() package.add_dependency(Factory.create_dependency("demo", {"path": path})) @@ -2585,19 +2580,17 @@ def test_solver_can_resolve_sdist_dependencies( def test_solver_can_resolve_sdist_dependencies_with_extras( - solver: Solver, repo: Repository, package: ProjectPackage -): + solver: Solver, + repo: Repository, + package: ProjectPackage, + fixture_dir: FixtureDirGetter, +) -> None: pendulum = get_package("pendulum", "2.0.3") cleo = get_package("cleo", "1.0.0") repo.add_package(pendulum) repo.add_package(cleo) - path = ( - Path(__file__).parent.parent - / "fixtures" - / "distributions" - / "demo-0.1.0.tar.gz" - ).as_posix() + path = (fixture_dir("distributions") / "demo-0.1.0.tar.gz").as_posix() package.add_dependency( Factory.create_dependency("demo", {"path": path, "extras": ["foo"]}) @@ -2625,17 +2618,15 @@ def test_solver_can_resolve_sdist_dependencies_with_extras( def test_solver_can_resolve_wheel_dependencies( - solver: Solver, repo: Repository, package: ProjectPackage -): + solver: Solver, + repo: Repository, + package: ProjectPackage, + fixture_dir: FixtureDirGetter, +) -> None: pendulum = get_package("pendulum", "2.0.3") repo.add_package(pendulum) - path = ( - Path(__file__).parent.parent - / "fixtures" - / "distributions" - / "demo-0.1.0-py2.py3-none-any.whl" - ).as_posix() + path = (fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl").as_posix() package.add_dependency(Factory.create_dependency("demo", {"path": path})) @@ -2657,19 +2648,17 @@ def test_solver_can_resolve_wheel_dependencies( def test_solver_can_resolve_wheel_dependencies_with_extras( - solver: Solver, repo: Repository, package: ProjectPackage -): + solver: Solver, + repo: Repository, + package: ProjectPackage, + fixture_dir: FixtureDirGetter, +) -> None: pendulum = get_package("pendulum", "2.0.3") cleo = get_package("cleo", "1.0.0") repo.add_package(pendulum) repo.add_package(cleo) - path = ( - Path(__file__).parent.parent - / "fixtures" - / "distributions" - / "demo-0.1.0-py2.py3-none-any.whl" - ).as_posix() + path = (fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl").as_posix() package.add_dependency( Factory.create_dependency("demo", {"path": path, "extras": ["foo"]}) @@ -2698,7 +2687,7 @@ def test_solver_can_resolve_wheel_dependencies_with_extras( def test_solver_can_solve_with_legacy_repository_using_proper_dists( package: ProjectPackage, io: NullIO -): +) -> None: repo = MockLegacyRepository() pool = RepositoryPool([repo]) @@ -2741,7 +2730,7 @@ def test_solver_can_solve_with_legacy_repository_using_proper_dists( def test_solver_can_solve_with_legacy_repository_using_proper_python_compatible_dists( package: ProjectPackage, io: NullIO, -): +) -> None: package.python_versions = "^3.7" repo = MockLegacyRepository() @@ -2770,7 +2759,7 @@ def test_solver_can_solve_with_legacy_repository_using_proper_python_compatible_ ) -def test_solver_skips_invalid_versions(package: ProjectPackage, io: NullIO): +def test_solver_skips_invalid_versions(package: ProjectPackage, io: NullIO) -> None: package.python_versions = "^3.7" repo = MockPyPIRepository() @@ -2789,7 +2778,7 @@ def test_solver_skips_invalid_versions(package: ProjectPackage, io: NullIO): def test_multiple_constraints_on_root( package: ProjectPackage, solver: Solver, repo: Repository -): +) -> None: package.add_dependency( Factory.create_dependency("foo", {"version": "^1.0", "python": "^2.7"}) ) @@ -2813,7 +2802,7 @@ def test_multiple_constraints_on_root( def test_solver_chooses_most_recent_version_amongst_repositories( package: ProjectPackage, io: NullIO -): +) -> None: package.python_versions = "^3.7" package.add_dependency(Factory.create_dependency("tomlkit", {"version": "^0.5"})) @@ -2834,7 +2823,7 @@ def test_solver_chooses_most_recent_version_amongst_repositories( def test_solver_chooses_from_correct_repository_if_forced( package: ProjectPackage, io: NullIO -): +) -> None: package.python_versions = "^3.7" package.add_dependency( Factory.create_dependency("tomlkit", {"version": "^0.5", "source": "legacy"}) @@ -2869,7 +2858,7 @@ def test_solver_chooses_from_correct_repository_if_forced( def test_solver_chooses_from_correct_repository_if_forced_and_transitive_dependency( package: ProjectPackage, io: NullIO, -): +) -> None: package.python_versions = "^3.7" package.add_dependency(Factory.create_dependency("foo", "^1.0")) package.add_dependency( @@ -2911,7 +2900,7 @@ def test_solver_chooses_from_correct_repository_if_forced_and_transitive_depende def test_solver_does_not_choose_from_secondary_repository_by_default( package: ProjectPackage, io: NullIO -): +) -> None: package.python_versions = "^3.7" package.add_dependency(Factory.create_dependency("clikit", {"version": "^0.2.0"})) @@ -2959,7 +2948,7 @@ def test_solver_does_not_choose_from_secondary_repository_by_default( def test_solver_chooses_from_secondary_if_explicit( package: ProjectPackage, io: NullIO, -): +) -> None: package.python_versions = "^3.7" package.add_dependency( Factory.create_dependency("clikit", {"version": "^0.2.0", "source": "PyPI"}) @@ -3068,7 +3057,7 @@ def test_solver_discards_packages_with_empty_markers( repo: Repository, pool: RepositoryPool, io: NullIO, -): +) -> None: package.python_versions = "~2.7 || ^3.4" package.add_dependency( Factory.create_dependency( @@ -3104,7 +3093,7 @@ def test_solver_discards_packages_with_empty_markers( def test_solver_does_not_raise_conflict_for_conditional_dev_dependencies( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: set_package_python_versions(solver.provider, "~2.7 || ^3.5") package.add_dependency( Factory.create_dependency( @@ -3136,7 +3125,7 @@ def test_solver_does_not_raise_conflict_for_conditional_dev_dependencies( def test_solver_does_not_loop_indefinitely_on_duplicate_constraints_with_extras( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: set_package_python_versions(solver.provider, "~2.7 || ^3.5") package.add_dependency( Factory.create_dependency( @@ -3170,7 +3159,7 @@ def test_solver_does_not_fail_with_locked_git_and_non_git_dependencies( repo: Repository, pool: RepositoryPool, io: NullIO, -): +) -> None: package.add_dependency( Factory.create_dependency("demo", {"git": "https://github.com/demo/demo.git"}) ) @@ -3205,7 +3194,7 @@ def test_solver_does_not_fail_with_locked_git_and_non_git_dependencies( def test_ignore_python_constraint_no_overlap_dependencies( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: pytest = get_package("demo", "1.0.0") pytest.add_dependency( Factory.create_dependency( @@ -3230,7 +3219,7 @@ def test_ignore_python_constraint_no_overlap_dependencies( def test_solver_should_not_go_into_an_infinite_loop_on_duplicate_dependencies( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: set_package_python_versions(solver.provider, "~2.7 || ^3.5") package.add_dependency(Factory.create_dependency("A", "^1.0")) @@ -3263,7 +3252,7 @@ def test_solver_should_not_go_into_an_infinite_loop_on_duplicate_dependencies( def test_solver_synchronize_single( package: ProjectPackage, pool: RepositoryPool, io: NullIO -): +) -> None: package_a = get_package("a", "1.0") solver = Solver(package, pool, [package_a], [], io) @@ -3279,7 +3268,7 @@ def test_solver_with_synchronization_keeps_critical_package( package: ProjectPackage, pool: RepositoryPool, io: NullIO, -): +) -> None: package_pip = get_package("setuptools", "1.0") solver = Solver(package, pool, [package_pip], [], io) @@ -3289,8 +3278,11 @@ def test_solver_with_synchronization_keeps_critical_package( def test_solver_cannot_choose_another_version_for_directory_dependencies( - solver: Solver, repo: Repository, package: ProjectPackage -): + solver: Solver, + repo: Repository, + package: ProjectPackage, + fixture_dir: FixtureDirGetter, +) -> None: pendulum = get_package("pendulum", "2.0.3") demo = get_package("demo", "0.1.0") foo = get_package("foo", "1.2.3") @@ -3299,14 +3291,7 @@ def test_solver_cannot_choose_another_version_for_directory_dependencies( repo.add_package(demo) repo.add_package(pendulum) - path = ( - Path(__file__).parent.parent - / "fixtures" - / "git" - / "github.com" - / "demo" - / "demo" - ).as_posix() + path = (fixture_dir("git") / "github.com" / "demo" / "demo").as_posix() package.add_dependency(Factory.create_dependency("demo", {"path": path})) package.add_dependency(Factory.create_dependency("foo", "^1.2.3")) @@ -3318,8 +3303,11 @@ def test_solver_cannot_choose_another_version_for_directory_dependencies( def test_solver_cannot_choose_another_version_for_file_dependencies( - solver: Solver, repo: Repository, package: ProjectPackage -): + solver: Solver, + repo: Repository, + package: ProjectPackage, + fixture_dir: FixtureDirGetter, +) -> None: pendulum = get_package("pendulum", "2.0.3") demo = get_package("demo", "0.0.8") foo = get_package("foo", "1.2.3") @@ -3328,12 +3316,7 @@ def test_solver_cannot_choose_another_version_for_file_dependencies( repo.add_package(demo) repo.add_package(pendulum) - path = ( - Path(__file__).parent.parent - / "fixtures" - / "distributions" - / "demo-0.1.0-py2.py3-none-any.whl" - ).as_posix() + path = (fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl").as_posix() package.add_dependency(Factory.create_dependency("demo", {"path": path})) package.add_dependency(Factory.create_dependency("foo", "^1.2.3")) @@ -3346,7 +3329,7 @@ def test_solver_cannot_choose_another_version_for_file_dependencies( def test_solver_cannot_choose_another_version_for_git_dependencies( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: pendulum = get_package("pendulum", "2.0.3") demo = get_package("demo", "0.0.8") foo = get_package("foo", "1.2.3") @@ -3371,13 +3354,9 @@ def test_solver_cannot_choose_another_version_for_url_dependencies( repo: Repository, package: ProjectPackage, http: type[httpretty.httpretty], -): - path = ( - Path(__file__).parent.parent - / "fixtures" - / "distributions" - / "demo-0.1.0-py2.py3-none-any.whl" - ) + fixture_dir: FixtureDirGetter, +) -> None: + path = fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" http.register_uri( "GET", @@ -3409,7 +3388,7 @@ def test_solver_cannot_choose_another_version_for_url_dependencies( def test_solver_should_not_update_same_version_packages_if_installed_has_no_source_type( package: ProjectPackage, repo: Repository, pool: RepositoryPool, io: NullIO -): +) -> None: package.add_dependency(Factory.create_dependency("foo", "1.0.0")) foo = Package( @@ -3458,7 +3437,7 @@ def test_solver_should_use_the_python_constraint_from_the_environment_if_availab def test_solver_should_resolve_all_versions_for_multiple_duplicate_dependencies( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.python_versions = "~2.7 || ^3.5" package.add_dependency( Factory.create_dependency( @@ -3506,7 +3485,7 @@ def test_solver_should_resolve_all_versions_for_multiple_duplicate_dependencies( def test_solver_should_not_raise_errors_for_irrelevant_python_constraints( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.python_versions = "^3.6" set_package_python_versions(solver.provider, "^3.6") package.add_dependency( @@ -3524,7 +3503,7 @@ def test_solver_should_not_raise_errors_for_irrelevant_python_constraints( def test_solver_can_resolve_transitive_extras( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("requests", "^2.24.0")) package.add_dependency(Factory.create_dependency("PyOTA", "^2.1.0")) @@ -3562,7 +3541,7 @@ def test_solver_can_resolve_transitive_extras( def test_solver_can_resolve_for_packages_with_missing_extras( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency( Factory.create_dependency( "django-anymail", {"version": "^6.0", "extras": ["postmark"]} @@ -3599,7 +3578,7 @@ def test_solver_can_resolve_for_packages_with_missing_extras( def test_solver_can_resolve_python_restricted_package_dependencies( package: ProjectPackage, repo: Repository, pool: RepositoryPool, io: NullIO -): +) -> None: package.add_dependency( Factory.create_dependency("futures", {"version": "^3.3.0", "python": "~2.7"}) ) @@ -3630,7 +3609,7 @@ def test_solver_can_resolve_python_restricted_package_dependencies( def test_solver_should_not_raise_errors_for_irrelevant_transitive_python_constraints( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: package.python_versions = "~2.7 || ^3.5" set_package_python_versions(solver.provider, "~2.7 || ^3.5") package.add_dependency(Factory.create_dependency("virtualenv", "^20.4.3")) @@ -3685,7 +3664,7 @@ def test_solver_keeps_multiple_locked_dependencies_for_same_package( pool: RepositoryPool, io: NullIO, is_locked: bool, -): +) -> None: package.add_dependency( Factory.create_dependency("A", {"version": "~1.1", "python": "<3.7"}) ) @@ -3739,7 +3718,7 @@ def test_solver_does_not_update_ref_of_locked_vcs_package( pool: RepositoryPool, io: NullIO, is_locked: bool, -): +) -> None: locked_ref = "123456" latest_ref = "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24" demo_locked = Package( @@ -3795,7 +3774,7 @@ def test_solver_does_not_fetch_locked_vcs_package_with_ref( pool: RepositoryPool, io: NullIO, mocker: MockerFixture, -): +) -> None: locked_ref = "123456" demo_locked = Package( "demo", @@ -3824,8 +3803,11 @@ def test_solver_does_not_fetch_locked_vcs_package_with_ref( def test_solver_direct_origin_dependency_with_extras_requested_by_other_package( - solver: Solver, repo: Repository, package: ProjectPackage -): + solver: Solver, + repo: Repository, + package: ProjectPackage, + fixture_dir: FixtureDirGetter, +) -> None: """ Another package requires the same dependency with extras that is required by the project as direct origin dependency without any extras. @@ -3840,14 +3822,7 @@ def test_solver_direct_origin_dependency_with_extras_requested_by_other_package( repo.add_package(pendulum) repo.add_package(cleo) - path = ( - Path(__file__).parent.parent - / "fixtures" - / "git" - / "github.com" - / "demo" - / "demo" - ).as_posix() + path = (fixture_dir("git") / "github.com" / "demo" / "demo").as_posix() # project requires path dependency of demo while demo-foo requires demo[foo] package.add_dependency(Factory.create_dependency("demo", {"path": path})) @@ -3877,7 +3852,7 @@ def test_solver_direct_origin_dependency_with_extras_requested_by_other_package( def test_solver_incompatible_dependency_with_and_without_extras( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: """ The solver first encounters a requirement for google-auth and then later an incompatible requirement for google-auth[aiohttp]. @@ -3929,7 +3904,7 @@ def test_solver_incompatible_dependency_with_and_without_extras( def test_update_with_prerelease_and_no_solution( package: ProjectPackage, repo: Repository, pool: RepositoryPool, io: NullIO -): +) -> None: # Locked and installed: cleo which depends on an old version of crashtest. cleo = get_package("cleo", "1.0.0a5") crashtest = get_package("crashtest", "0.3.0") @@ -4001,7 +3976,7 @@ def test_update_with_use_latest_vs_lock( pool: RepositoryPool, io: NullIO, is_locked: bool, -): +) -> None: """ A1 depends on B2, A2 and A3 depend on B1. Same for C. B1 depends on A2/C2, B2 depends on A1/C1. diff --git a/tests/test_factory.py b/tests/test_factory.py index 2ba1104e768..d3a39620e86 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -1,6 +1,5 @@ from __future__ import annotations -from pathlib import Path from typing import TYPE_CHECKING import pytest @@ -26,8 +25,6 @@ from poetry.poetry import Poetry from tests.types import FixtureDirGetter -fixtures_dir = Path(__file__).parent / "fixtures" - class MyPlugin(Plugin): def activate(self, poetry: Poetry, io: IO) -> None: @@ -35,8 +32,8 @@ def activate(self, poetry: Poetry, io: IO) -> None: poetry.package.readmes = ("README.md",) -def test_create_poetry(): - poetry = Factory().create_poetry(fixtures_dir / "sample_project") +def test_create_poetry(fixture_dir: FixtureDirGetter) -> None: + poetry = Factory().create_poetry(fixture_dir("sample_project")) package = poetry.package @@ -48,7 +45,7 @@ def test_create_poetry(): for readme in package.readmes: assert ( - readme.relative_to(fixtures_dir).as_posix() == "sample_project/README.rst" + readme.relative_to(fixture_dir("sample_project")).as_posix() == "README.rst" ) assert package.homepage == "https://python-poetry.org" @@ -147,8 +144,10 @@ def test_create_poetry(): ("project_with_extras",), ], ) -def test_create_pyproject_from_package(project: str): - poetry = Factory().create_poetry(fixtures_dir / project) +def test_create_pyproject_from_package( + project: str, fixture_dir: FixtureDirGetter +) -> None: + poetry = Factory().create_poetry(fixture_dir(project)) package = poetry.package pyproject = Factory.create_pyproject_from_package(package) @@ -177,8 +176,10 @@ def test_create_pyproject_from_package(project: str): assert not DeepDiff(expected, result) -def test_create_poetry_with_packages_and_includes(): - poetry = Factory().create_poetry(fixtures_dir / "with-include") +def test_create_poetry_with_packages_and_includes( + fixture_dir: FixtureDirGetter, +) -> None: + poetry = Factory().create_poetry(fixture_dir("with-include")) package = poetry.package @@ -198,9 +199,11 @@ def test_create_poetry_with_packages_and_includes(): ] -def test_create_poetry_with_multi_constraints_dependency(): +def test_create_poetry_with_multi_constraints_dependency( + fixture_dir: FixtureDirGetter, +) -> None: poetry = Factory().create_poetry( - fixtures_dir / "project_with_multi_constraints_dependency" + fixture_dir("project_with_multi_constraints_dependency") ) package = poetry.package @@ -208,30 +211,34 @@ def test_create_poetry_with_multi_constraints_dependency(): assert len(package.requires) == 2 -def test_poetry_with_default_source_legacy(with_simple_keyring: None): +def test_poetry_with_default_source_legacy( + fixture_dir: FixtureDirGetter, with_simple_keyring: None +) -> None: io = BufferedIO() - poetry = Factory().create_poetry(fixtures_dir / "with_default_source_legacy", io=io) + poetry = Factory().create_poetry(fixture_dir("with_default_source_legacy"), io=io) assert len(poetry.pool.repositories) == 1 assert "Found deprecated key" in io.fetch_error() -def test_poetry_with_default_source(with_simple_keyring: None): +def test_poetry_with_default_source( + fixture_dir: FixtureDirGetter, with_simple_keyring: None +) -> None: io = BufferedIO() - poetry = Factory().create_poetry(fixtures_dir / "with_default_source", io=io) + poetry = Factory().create_poetry(fixture_dir("with_default_source"), io=io) assert len(poetry.pool.repositories) == 1 assert io.fetch_error() == "" @pytest.mark.parametrize( - "fixture_filename", + "project", ("with_non_default_source_implicit", "with_non_default_source_explicit"), ) def test_poetry_with_non_default_source( - fixture_filename: str, with_simple_keyring: None -): - poetry = Factory().create_poetry(fixtures_dir / fixture_filename) + project: str, fixture_dir: FixtureDirGetter, with_simple_keyring: None +) -> None: + poetry = Factory().create_poetry(fixture_dir(project)) assert not poetry.pool.has_default() assert poetry.pool.has_repository("PyPI") @@ -243,9 +250,11 @@ def test_poetry_with_non_default_source( assert {repo.name for repo in poetry.pool.repositories} == {"PyPI", "foo"} -def test_poetry_with_non_default_secondary_source_legacy(with_simple_keyring: None): +def test_poetry_with_non_default_secondary_source_legacy( + fixture_dir: FixtureDirGetter, with_simple_keyring: None +) -> None: poetry = Factory().create_poetry( - fixtures_dir / "with_non_default_secondary_source_legacy" + fixture_dir("with_non_default_secondary_source_legacy") ) assert poetry.pool.has_repository("PyPI") @@ -256,8 +265,10 @@ def test_poetry_with_non_default_secondary_source_legacy(with_simple_keyring: No assert [repo.name for repo in poetry.pool.repositories] == ["PyPI", "foo"] -def test_poetry_with_non_default_secondary_source(with_simple_keyring: None): - poetry = Factory().create_poetry(fixtures_dir / "with_non_default_secondary_source") +def test_poetry_with_non_default_secondary_source( + fixture_dir: FixtureDirGetter, with_simple_keyring: None +) -> None: + poetry = Factory().create_poetry(fixture_dir("with_non_default_secondary_source")) assert poetry.pool.has_repository("PyPI") assert isinstance(poetry.pool.repository("PyPI"), PyPiRepository) @@ -268,10 +279,11 @@ def test_poetry_with_non_default_secondary_source(with_simple_keyring: None): def test_poetry_with_non_default_multiple_secondary_sources_legacy( + fixture_dir: FixtureDirGetter, with_simple_keyring: None, -): +) -> None: poetry = Factory().create_poetry( - fixtures_dir / "with_non_default_multiple_secondary_sources_legacy" + fixture_dir("with_non_default_multiple_secondary_sources_legacy") ) assert poetry.pool.has_repository("PyPI") @@ -284,9 +296,11 @@ def test_poetry_with_non_default_multiple_secondary_sources_legacy( assert {repo.name for repo in poetry.pool.repositories} == {"PyPI", "foo", "bar"} -def test_poetry_with_non_default_multiple_secondary_sources(with_simple_keyring: None): +def test_poetry_with_non_default_multiple_secondary_sources( + fixture_dir: FixtureDirGetter, with_simple_keyring: None +) -> None: poetry = Factory().create_poetry( - fixtures_dir / "with_non_default_multiple_secondary_sources" + fixture_dir("with_non_default_multiple_secondary_sources") ) assert poetry.pool.has_repository("PyPI") @@ -299,9 +313,11 @@ def test_poetry_with_non_default_multiple_secondary_sources(with_simple_keyring: assert {repo.name for repo in poetry.pool.repositories} == {"PyPI", "foo", "bar"} -def test_poetry_with_non_default_multiple_sources_legacy(with_simple_keyring: None): +def test_poetry_with_non_default_multiple_sources_legacy( + fixture_dir: FixtureDirGetter, with_simple_keyring: None +) -> None: poetry = Factory().create_poetry( - fixtures_dir / "with_non_default_multiple_sources_legacy" + fixture_dir("with_non_default_multiple_sources_legacy") ) assert not poetry.pool.has_default() @@ -315,8 +331,10 @@ def test_poetry_with_non_default_multiple_sources_legacy(with_simple_keyring: No assert {repo.name for repo in poetry.pool.repositories} == {"bar", "PyPI", "foo"} -def test_poetry_with_non_default_multiple_sources(with_simple_keyring: None): - poetry = Factory().create_poetry(fixtures_dir / "with_non_default_multiple_sources") +def test_poetry_with_non_default_multiple_sources( + fixture_dir: FixtureDirGetter, with_simple_keyring: None +) -> None: + poetry = Factory().create_poetry(fixture_dir("with_non_default_multiple_sources")) assert not poetry.pool.has_default() assert poetry.pool.has_repository("PyPI") @@ -329,8 +347,8 @@ def test_poetry_with_non_default_multiple_sources(with_simple_keyring: None): assert {repo.name for repo in poetry.pool.repositories} == {"PyPI", "bar", "foo"} -def test_poetry_with_no_default_source(): - poetry = Factory().create_poetry(fixtures_dir / "sample_project") +def test_poetry_with_no_default_source(fixture_dir: FixtureDirGetter) -> None: + poetry = Factory().create_poetry(fixture_dir("sample_project")) assert poetry.pool.has_repository("PyPI") assert poetry.pool.get_priority("PyPI") is Priority.DEFAULT @@ -338,8 +356,10 @@ def test_poetry_with_no_default_source(): assert {repo.name for repo in poetry.pool.repositories} == {"PyPI"} -def test_poetry_with_explicit_source(with_simple_keyring: None) -> None: - poetry = Factory().create_poetry(fixtures_dir / "with_explicit_source") +def test_poetry_with_explicit_source( + fixture_dir: FixtureDirGetter, with_simple_keyring: None +) -> None: + poetry = Factory().create_poetry(fixture_dir("with_explicit_source")) assert len(poetry.pool.repositories) == 1 assert len(poetry.pool.all_repositories) == 2 @@ -351,29 +371,33 @@ def test_poetry_with_explicit_source(with_simple_keyring: None) -> None: assert [repo.name for repo in poetry.pool.repositories] == ["PyPI"] -def test_poetry_with_two_default_sources_legacy(with_simple_keyring: None): +def test_poetry_with_two_default_sources_legacy( + fixture_dir: FixtureDirGetter, with_simple_keyring: None +) -> None: with pytest.raises(ValueError) as e: - Factory().create_poetry(fixtures_dir / "with_two_default_sources_legacy") + Factory().create_poetry(fixture_dir("with_two_default_sources_legacy")) assert str(e.value) == "Only one repository can be the default." -def test_poetry_with_two_default_sources(with_simple_keyring: None): +def test_poetry_with_two_default_sources( + fixture_dir: FixtureDirGetter, with_simple_keyring: None +) -> None: with pytest.raises(ValueError) as e: - Factory().create_poetry(fixtures_dir / "with_two_default_sources") + Factory().create_poetry(fixture_dir("with_two_default_sources")) assert str(e.value) == "Only one repository can be the default." -def test_validate(): - complete = TOMLFile(fixtures_dir / "complete.toml") +def test_validate(fixture_dir: FixtureDirGetter) -> None: + complete = TOMLFile(fixture_dir("complete.toml")) content = complete.read()["tool"]["poetry"] assert Factory.validate(content) == {"errors": [], "warnings": []} -def test_validate_fails(): - complete = TOMLFile(fixtures_dir / "complete.toml") +def test_validate_fails(fixture_dir: FixtureDirGetter) -> None: + complete = TOMLFile(fixture_dir("complete.toml")) content = complete.read()["tool"]["poetry"] content["this key is not in the schema"] = "" @@ -385,11 +409,11 @@ def test_validate_fails(): assert Factory.validate(content) == {"errors": [expected], "warnings": []} -def test_create_poetry_fails_on_invalid_configuration(): +def test_create_poetry_fails_on_invalid_configuration( + fixture_dir: FixtureDirGetter, +) -> None: with pytest.raises(RuntimeError) as e: - Factory().create_poetry( - Path(__file__).parent / "fixtures" / "invalid_pyproject" / "pyproject.toml" - ) + Factory().create_poetry(fixture_dir("invalid_pyproject") / "pyproject.toml") expected = """\ The Poetry configuration is invalid: @@ -398,7 +422,7 @@ def test_create_poetry_fails_on_invalid_configuration(): assert str(e.value) == expected -def test_create_poetry_with_local_config(fixture_dir: FixtureDirGetter): +def test_create_poetry_with_local_config(fixture_dir: FixtureDirGetter) -> None: poetry = Factory().create_poetry(fixture_dir("with_local_config")) assert not poetry.config.get("virtualenvs.in-project") @@ -409,9 +433,11 @@ def test_create_poetry_with_local_config(fixture_dir: FixtureDirGetter): assert not poetry.config.get("virtualenvs.options.system-site-packages") -def test_create_poetry_with_plugins(mocker: MockerFixture): +def test_create_poetry_with_plugins( + mocker: MockerFixture, fixture_dir: FixtureDirGetter +) -> None: mock_metadata_entry_points(mocker, MyPlugin) - poetry = Factory().create_poetry(fixtures_dir / "sample_project") + poetry = Factory().create_poetry(fixture_dir("sample_project")) assert poetry.package.readmes == ("README.md",) diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index c8589f48594..a5f8957424f 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -42,6 +42,7 @@ from poetry.poetry import Poetry from tests.conftest import Config + from tests.types import FixtureDirGetter from tests.types import ProjectFactory MINIMAL_SCRIPT = """\ @@ -77,9 +78,8 @@ def sys_path(self) -> list[str]: @pytest.fixture() -def poetry(project_factory: ProjectFactory) -> Poetry: - fixture = Path(__file__).parent.parent / "fixtures" / "simple_project" - return project_factory("simple", source=fixture) +def poetry(project_factory: ProjectFactory, fixture_dir: FixtureDirGetter) -> Poetry: + return project_factory("simple", source=fixture_dir("simple_project")) @pytest.fixture() @@ -100,7 +100,7 @@ def test_virtualenvs_with_spaces_in_their_path_work_as_expected( @pytest.mark.skipif(sys.platform != "darwin", reason="requires darwin") -def test_venv_backup_exclusion(tmp_path: Path, manager: EnvManager): +def test_venv_backup_exclusion(tmp_path: Path, manager: EnvManager) -> None: import xattr venv_path = tmp_path / "Virtual Env" @@ -1042,7 +1042,7 @@ def test_check_output_with_called_process_error( @pytest.mark.parametrize("out", ["sys.stdout", "sys.stderr"]) def test_call_does_not_block_on_full_pipe( tmp_path: Path, tmp_venv: VirtualEnv, out: str -): +) -> None: """see https://github.com/python-poetry/poetry/issues/7698""" script = tmp_path / "script.py" script.write_text( @@ -1664,10 +1664,8 @@ def test_generate_env_name_uses_real_path( @pytest.fixture() -def extended_without_setup_poetry() -> Poetry: - poetry = Factory().create_poetry( - Path(__file__).parent.parent / "fixtures" / "extended_project_without_setup" - ) +def extended_without_setup_poetry(fixture_dir: FixtureDirGetter) -> Poetry: + poetry = Factory().create_poetry(fixture_dir("extended_project_without_setup")) return poetry @@ -1713,6 +1711,7 @@ def test_build_environment_not_called_without_build_script_specified( def test_create_venv_project_name_empty_sets_correct_prompt( + fixture_dir: FixtureDirGetter, project_factory: ProjectFactory, config: Config, mocker: MockerFixture, @@ -1721,8 +1720,7 @@ def test_create_venv_project_name_empty_sets_correct_prompt( if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] - fixture = Path(__file__).parent.parent / "fixtures" / "no_name_project" - poetry = project_factory("no", source=fixture) + poetry = project_factory("no", source=fixture_dir("no_name_project")) manager = EnvManager(poetry) poetry.package.python_versions = "^3.7" diff --git a/tests/utils/test_helpers.py b/tests/utils/test_helpers.py index dad06297609..55314a7d743 100644 --- a/tests/utils/test_helpers.py +++ b/tests/utils/test_helpers.py @@ -1,6 +1,5 @@ from __future__ import annotations -from pathlib import Path from typing import TYPE_CHECKING import pytest @@ -14,7 +13,7 @@ from tests.types import FixtureDirGetter -def test_parse_requires(): +def test_parse_requires() -> None: requires = """\ jsonschema>=2.6.0.0,<3.0.0.0 lockfile>=0.12.0.0,<0.13.0.0 @@ -71,10 +70,8 @@ def test_parse_requires(): def test_default_hash(fixture_dir: FixtureDirGetter) -> None: - root_dir = Path(__file__).parent.parent.parent - file_path = root_dir / fixture_dir("distributions/demo-0.1.0.tar.gz") sha_256 = "9fa123ad707a5c6c944743bf3e11a0e80d86cb518d3cf25320866ca3ef43e2ad" - assert get_file_hash(file_path) == sha_256 + assert get_file_hash(fixture_dir("distributions") / "demo-0.1.0.tar.gz") == sha_256 try: @@ -130,6 +127,5 @@ def test_default_hash(fixture_dir: FixtureDirGetter) -> None: def test_guaranteed_hash( hash_name: str, expected: str, fixture_dir: FixtureDirGetter ) -> None: - root_dir = Path(__file__).parent.parent.parent - file_path = root_dir / fixture_dir("distributions/demo-0.1.0.tar.gz") + file_path = fixture_dir("distributions") / "demo-0.1.0.tar.gz" assert get_file_hash(file_path, hash_name) == expected From 99f106c55bf17ddb8ff8db133cdc9bb36e0f05f5 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 16 Apr 2023 15:29:00 +0100 Subject: [PATCH 146/151] some unit test typechecking (#7802) --- pyproject.toml | 9 ++ tests/config/test_config.py | 11 +- tests/console/commands/cache/test_list.py | 4 +- tests/console/commands/debug/test_resolve.py | 8 +- tests/console/commands/env/test_list.py | 12 +- tests/console/commands/env/test_remove.py | 10 +- .../console/commands/self/test_add_plugins.py | 16 +-- .../commands/self/test_remove_plugins.py | 4 +- .../commands/self/test_show_plugins.py | 6 +- tests/console/commands/test_about.py | 2 +- tests/console/commands/test_add.py | 130 +++++++++--------- tests/console/commands/test_config.py | 24 ++-- tests/console/commands/test_init.py | 60 ++++---- tests/console/commands/test_install.py | 40 +++--- tests/console/commands/test_lock.py | 12 +- tests/console/commands/test_new.py | 6 +- tests/console/commands/test_publish.py | 16 ++- tests/console/commands/test_remove.py | 12 +- tests/console/commands/test_run.py | 8 +- tests/console/commands/test_shell.py | 6 +- tests/console/commands/test_update.py | 2 +- tests/console/commands/test_version.py | 12 +- tests/console/test_application.py | 10 +- tests/installation/test_chef.py | 4 +- tests/installation/test_executor.py | 50 +++---- tests/installation/test_installer.py | 115 ++++++++-------- tests/integration/test_utils_vcs_git.py | 8 +- tests/plugins/test_plugin_manager.py | 12 +- tests/puzzle/test_provider.py | 3 +- tests/puzzle/test_solver.py | 2 +- .../repositories/test_installed_repository.py | 40 +++--- tests/utils/test_authenticator.py | 78 +++++------ tests/utils/test_password_manager.py | 43 +++--- 33 files changed, 410 insertions(+), 365 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7f87ec3c2ff..63e65101815 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -160,6 +160,11 @@ enable_error_code = [ "redundant-expr", "truthy-bool", ] +exclude = [ + "tests/fixtures", + "tests/masonry/builders/fixtures", + "tests/utils/fixtures" +] # use of importlib-metadata backport at python3.7 makes it impossible to # satisfy mypy without some ignores: but we get a different set of ignores at @@ -178,6 +183,10 @@ warn_unused_ignores = false [[tool.mypy.overrides]] module = [ 'cachecontrol.*', + 'cachy.*', + 'deepdiff.*', + 'httpretty.*', + 'keyring.*', 'lockfile.*', 'pexpect.*', 'requests_toolbelt.*', diff --git a/tests/config/test_config.py b/tests/config/test_config.py index 0d4f3ba7a2b..0194e7799d5 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -5,6 +5,7 @@ from pathlib import Path from typing import TYPE_CHECKING +from typing import Any import pytest @@ -19,7 +20,7 @@ from collections.abc import Iterator -def get_options_based_on_normalizer(normalizer: Callable) -> str: +def get_options_based_on_normalizer(normalizer: Callable[[str], Any]) -> str: flattened_config = flatten_dict(obj=Config.default_config, delimiter=".") for k in flattened_config: @@ -30,13 +31,13 @@ def get_options_based_on_normalizer(normalizer: Callable) -> str: @pytest.mark.parametrize( ("name", "value"), [("installer.parallel", True), ("virtualenvs.create", True)] ) -def test_config_get_default_value(config: Config, name: str, value: bool): +def test_config_get_default_value(config: Config, name: str, value: bool) -> None: assert config.get(name) is value def test_config_get_processes_depended_on_values( config: Config, config_cache_dir: Path -): +) -> None: assert str(config_cache_dir / "virtualenvs") == config.get("virtualenvs.path") @@ -62,7 +63,7 @@ def test_config_get_from_environment_variable( env_var: str, env_value: str, value: bool, -): +) -> None: os.environ[env_var] = env_value assert config.get(name) is value @@ -73,6 +74,6 @@ def test_config_get_from_environment_variable( ) def test_config_expands_tilde_for_virtualenvs_path( config: Config, path_config: str, expected: Path -): +) -> None: config.merge({"virtualenvs": {"path": path_config}}) assert config.virtualenvs_path == expected diff --git a/tests/console/commands/cache/test_list.py b/tests/console/commands/cache/test_list.py index 22db34cb0de..2def8564831 100644 --- a/tests/console/commands/cache/test_list.py +++ b/tests/console/commands/cache/test_list.py @@ -20,7 +20,7 @@ def tester(command_tester_factory: CommandTesterFactory) -> CommandTester: def test_cache_list( tester: CommandTester, mock_caches: None, repository_one: str, repository_two: str -): +) -> None: tester.execute() expected = f"""\ @@ -31,7 +31,7 @@ def test_cache_list( assert tester.io.fetch_output() == expected -def test_cache_list_empty(tester: CommandTester, repository_cache_dir: Path): +def test_cache_list_empty(tester: CommandTester, repository_cache_dir: Path) -> None: tester.execute() expected = """\ diff --git a/tests/console/commands/debug/test_resolve.py b/tests/console/commands/debug/test_resolve.py index ddb45d4d1c8..1d733e3612f 100644 --- a/tests/console/commands/debug/test_resolve.py +++ b/tests/console/commands/debug/test_resolve.py @@ -33,7 +33,7 @@ def __add_packages(repo: TestRepository) -> None: repo.add_package(get_package("cleo", "0.6.5")) -def test_debug_resolve_gives_resolution_results(tester: CommandTester): +def test_debug_resolve_gives_resolution_results(tester: CommandTester) -> None: tester.execute("cachy") expected = """\ @@ -48,7 +48,9 @@ def test_debug_resolve_gives_resolution_results(tester: CommandTester): assert tester.io.fetch_output() == expected -def test_debug_resolve_tree_option_gives_the_dependency_tree(tester: CommandTester): +def test_debug_resolve_tree_option_gives_the_dependency_tree( + tester: CommandTester, +) -> None: tester.execute("cachy --tree") expected = """\ @@ -63,7 +65,7 @@ def test_debug_resolve_tree_option_gives_the_dependency_tree(tester: CommandTest assert tester.io.fetch_output() == expected -def test_debug_resolve_git_dependency(tester: CommandTester): +def test_debug_resolve_git_dependency(tester: CommandTester) -> None: tester.execute("git+https://github.com/demo/demo.git") expected = """\ diff --git a/tests/console/commands/env/test_list.py b/tests/console/commands/env/test_list.py index 687493faabb..b617d3e39c2 100644 --- a/tests/console/commands/env/test_list.py +++ b/tests/console/commands/env/test_list.py @@ -36,7 +36,7 @@ def test_none_activated( venvs_in_cache_dirs: list[str], mocker: MockerFixture, env: MockEnv, -): +) -> None: mocker.patch("poetry.utils.env.EnvManager.get", return_value=env) tester.execute() expected = "\n".join(venvs_in_cache_dirs) @@ -48,13 +48,15 @@ def test_activated( venvs_in_cache_dirs: list[str], venv_cache: Path, venv_activate_37: None, -): +) -> None: tester.execute() expected = "\n".join(venvs_in_cache_dirs).replace("py3.7", "py3.7 (Activated)") assert tester.io.fetch_output().strip() == expected -def test_in_project_venv(tester: CommandTester, venvs_in_project_dir: list[str]): +def test_in_project_venv( + tester: CommandTester, venvs_in_project_dir: list[str] +) -> None: tester.execute() expected = ".venv (Activated)\n" assert tester.io.fetch_output() == expected @@ -62,7 +64,7 @@ def test_in_project_venv(tester: CommandTester, venvs_in_project_dir: list[str]) def test_in_project_venv_no_explicit_config( tester: CommandTester, venvs_in_project_dir_none: list[str] -): +) -> None: tester.execute() expected = ".venv (Activated)\n" assert tester.io.fetch_output() == expected @@ -70,7 +72,7 @@ def test_in_project_venv_no_explicit_config( def test_in_project_venv_is_false( tester: CommandTester, venvs_in_project_dir_false: list[str] -): +) -> None: tester.execute() expected = "" assert tester.io.fetch_output() == expected diff --git a/tests/console/commands/env/test_remove.py b/tests/console/commands/env/test_remove.py index 428d4f13dfd..9586be90ea1 100644 --- a/tests/console/commands/env/test_remove.py +++ b/tests/console/commands/env/test_remove.py @@ -29,7 +29,7 @@ def test_remove_by_python_version( venvs_in_cache_dirs: list[str], venv_name: str, venv_cache: Path, -): +) -> None: check_output = mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), @@ -49,7 +49,7 @@ def test_remove_by_name( venvs_in_cache_dirs: list[str], venv_name: str, venv_cache: Path, -): +) -> None: expected = "" for name in venvs_in_cache_dirs: @@ -67,7 +67,7 @@ def test_remove_all( venvs_in_cache_dirs: list[str], venv_name: str, venv_cache: Path, -): +) -> None: expected = {""} tester.execute("--all") for name in venvs_in_cache_dirs: @@ -81,7 +81,7 @@ def test_remove_all_and_version( venvs_in_cache_dirs: list[str], venv_name: str, venv_cache: Path, -): +) -> None: expected = {""} tester.execute(f"--all {venvs_in_cache_dirs[0]}") for name in venvs_in_cache_dirs: @@ -95,7 +95,7 @@ def test_remove_multiple( venvs_in_cache_dirs: list[str], venv_name: str, venv_cache: Path, -): +) -> None: expected = {""} removed_envs = venvs_in_cache_dirs[0:2] remaining_envs = venvs_in_cache_dirs[2:] diff --git a/tests/console/commands/self/test_add_plugins.py b/tests/console/commands/self/test_add_plugins.py index e7bd3c1707f..981a3fe8db8 100644 --- a/tests/console/commands/self/test_add_plugins.py +++ b/tests/console/commands/self/test_add_plugins.py @@ -38,7 +38,7 @@ def assert_plugin_add_result( def test_add_no_constraint( tester: CommandTester, repo: TestRepository, -): +) -> None: repo.add_package(Package("poetry-plugin", "0.1.0")) tester.execute("poetry-plugin") @@ -61,7 +61,7 @@ def test_add_no_constraint( def test_add_with_constraint( tester: CommandTester, repo: TestRepository, -): +) -> None: repo.add_package(Package("poetry-plugin", "0.1.0")) repo.add_package(Package("poetry-plugin", "0.2.0")) @@ -84,7 +84,7 @@ def test_add_with_constraint( def test_add_with_git_constraint( tester: CommandTester, repo: TestRepository, -): +) -> None: repo.add_package(Package("pendulum", "2.0.5")) tester.execute("git+https://github.com/demo/poetry-plugin.git") @@ -109,7 +109,7 @@ def test_add_with_git_constraint( def test_add_with_git_constraint_with_extras( tester: CommandTester, repo: TestRepository, -): +) -> None: repo.add_package(Package("pendulum", "2.0.5")) repo.add_package(Package("tomlkit", "0.7.0")) @@ -153,7 +153,7 @@ def test_add_with_git_constraint_with_subdirectory( rev: str | None, tester: CommandTester, repo: TestRepository, -): +) -> None: repo.add_package(Package("pendulum", "2.0.5")) tester.execute(url) @@ -189,7 +189,7 @@ def test_add_existing_plugin_warns_about_no_operation( tester: CommandTester, repo: TestRepository, installed: TestRepository, -): +) -> None: pyproject = SelfCommand.get_default_system_pyproject_file() with open(pyproject, "w", encoding="utf-8", newline="") as f: f.write( @@ -230,7 +230,7 @@ def test_add_existing_plugin_updates_if_requested( tester: CommandTester, repo: TestRepository, installed: TestRepository, -): +) -> None: pyproject = SelfCommand.get_default_system_pyproject_file() with open(pyproject, "w", encoding="utf-8", newline="") as f: f.write( @@ -276,7 +276,7 @@ def test_adding_a_plugin_can_update_poetry_dependencies_if_needed( tester: CommandTester, repo: TestRepository, installed: TestRepository, -): +) -> None: poetry_package = Package("poetry", "1.2.0") poetry_package.add_dependency(Factory.create_dependency("tomlkit", "^0.7.0")) diff --git a/tests/console/commands/self/test_remove_plugins.py b/tests/console/commands/self/test_remove_plugins.py index 17f24df5708..9d1dfbaeae3 100644 --- a/tests/console/commands/self/test_remove_plugins.py +++ b/tests/console/commands/self/test_remove_plugins.py @@ -66,7 +66,7 @@ def install_plugin(installed: Repository) -> None: installed.add_package(plugin) -def test_remove_installed_package(tester: CommandTester): +def test_remove_installed_package(tester: CommandTester) -> None: tester.execute("poetry-plugin") expected = """\ @@ -87,7 +87,7 @@ def test_remove_installed_package(tester: CommandTester): assert not dependencies -def test_remove_installed_package_dry_run(tester: CommandTester): +def test_remove_installed_package_dry_run(tester: CommandTester) -> None: tester.execute("poetry-plugin --dry-run") expected = f"""\ diff --git a/tests/console/commands/self/test_show_plugins.py b/tests/console/commands/self/test_show_plugins.py index c2a93bc632a..410473f9133 100644 --- a/tests/console/commands/self/test_show_plugins.py +++ b/tests/console/commands/self/test_show_plugins.py @@ -153,7 +153,7 @@ def mock_metadata_entry_points( def test_show_displays_installed_plugins( app: PoetryTestApplication, tester: CommandTester, -): +) -> None: tester.execute("") expected = """ @@ -179,7 +179,7 @@ def test_show_displays_installed_plugins( def test_show_displays_installed_plugins_with_multiple_plugins( app: PoetryTestApplication, tester: CommandTester, -): +) -> None: tester.execute("") expected = """ @@ -205,7 +205,7 @@ def test_show_displays_installed_plugins_with_multiple_plugins( def test_show_displays_installed_plugins_with_dependencies( app: PoetryTestApplication, tester: CommandTester, -): +) -> None: tester.execute("") expected = """ diff --git a/tests/console/commands/test_about.py b/tests/console/commands/test_about.py index 8f6902c35b3..4368acfd094 100644 --- a/tests/console/commands/test_about.py +++ b/tests/console/commands/test_about.py @@ -16,7 +16,7 @@ def tester(command_tester_factory: CommandTesterFactory) -> CommandTester: return command_tester_factory("about") -def test_about(tester: CommandTester): +def test_about(tester: CommandTester) -> None: from poetry.utils._compat import metadata tester.execute() diff --git a/tests/console/commands/test_add.py b/tests/console/commands/test_add.py index 706aa637edc..8daf01ea9d9 100644 --- a/tests/console/commands/test_add.py +++ b/tests/console/commands/test_add.py @@ -74,7 +74,7 @@ def old_tester(tester: CommandTester) -> CommandTester: def test_add_no_constraint( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester -): +) -> None: repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(get_package("cachy", "0.2.0")) @@ -104,7 +104,7 @@ def test_add_no_constraint( def test_add_replace_by_constraint( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester -): +) -> None: repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(get_package("cachy", "0.2.0")) @@ -151,7 +151,7 @@ def test_add_replace_by_constraint( def test_add_no_constraint_editable_error( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester -): +) -> None: content = app.poetry.file.read()["tool"]["poetry"] repo.add_package(get_package("cachy", "0.2.0")) @@ -172,7 +172,7 @@ def test_add_no_constraint_editable_error( def test_add_equal_constraint( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester -): +) -> None: repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(get_package("cachy", "0.2.0")) @@ -196,7 +196,7 @@ def test_add_equal_constraint( def test_add_greater_constraint( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester -): +) -> None: repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(get_package("cachy", "0.2.0")) @@ -224,7 +224,7 @@ def test_add_constraint_with_extras( repo: TestRepository, tester: CommandTester, extra_name: str, -): +) -> None: cachy1 = get_package("cachy", "0.1.0") cachy1.extras = {"msgpack": [get_dependency("msgpack-python")]} msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6", optional=True) @@ -255,7 +255,7 @@ def test_add_constraint_with_extras( def test_add_constraint_dependencies( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester -): +) -> None: cachy2 = get_package("cachy", "0.2.0") msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6") cachy2.add_dependency(msgpack_dep) @@ -288,7 +288,7 @@ def test_add_git_constraint( repo: TestRepository, tester: CommandTester, tmp_venv: VirtualEnv, -): +) -> None: tester.command.set_env(tmp_venv) repo.add_package(get_package("pendulum", "1.4.4")) @@ -325,7 +325,7 @@ def test_add_git_constraint_with_poetry( repo: TestRepository, tester: CommandTester, tmp_venv: VirtualEnv, -): +) -> None: tester.command.set_env(tmp_venv) repo.add_package(get_package("pendulum", "1.4.4")) @@ -356,7 +356,7 @@ def test_add_git_constraint_with_extras( tester: CommandTester, tmp_venv: VirtualEnv, extra_name: str, -): +) -> None: tester.command.set_env(tmp_venv) repo.add_package(get_package("pendulum", "1.4.4")) @@ -409,7 +409,7 @@ def test_add_git_constraint_with_subdirectory( repo: TestRepository, tester: CommandTester, env: MockEnv, -): +) -> None: tester.execute(url) expected = """\ @@ -446,7 +446,7 @@ def test_add_git_ssh_constraint( repo: TestRepository, tester: CommandTester, tmp_venv: VirtualEnv, -): +) -> None: tester.command.set_env(tmp_venv) repo.add_package(get_package("pendulum", "1.4.4")) @@ -495,7 +495,7 @@ def test_add_directory_constraint( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester, -): +) -> None: repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cleo", "0.6.5")) @@ -537,7 +537,7 @@ def test_add_directory_with_poetry( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester, -): +) -> None: repo.add_package(get_package("pendulum", "1.4.4")) path = "../git/github.com/demo/pyproject-demo" @@ -569,7 +569,7 @@ def test_add_file_constraint_wheel( repo: TestRepository, tester: CommandTester, poetry: Poetry, -): +) -> None: repo.add_package(get_package("pendulum", "1.4.4")) path = "../distributions/demo-0.1.0-py2.py3-none-any.whl" @@ -605,7 +605,7 @@ def test_add_file_constraint_sdist( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester, -): +) -> None: repo.add_package(get_package("pendulum", "1.4.4")) path = "../distributions/demo-0.1.0.tar.gz" @@ -639,7 +639,7 @@ def test_add_constraint_with_extras_option( repo: TestRepository, tester: CommandTester, extra_name: str, -): +) -> None: cachy2 = get_package("cachy", "0.2.0") cachy2.extras = {"msgpack": [get_dependency("msgpack-python")]} msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6", optional=True) @@ -681,7 +681,7 @@ def test_add_url_constraint_wheel( repo: TestRepository, tester: CommandTester, mocker: MockerFixture, -): +) -> None: p = mocker.patch("pathlib.Path.cwd") p.return_value = Path(__file__) / ".." @@ -723,7 +723,7 @@ def test_add_url_constraint_wheel_with_extras( tester: CommandTester, extra_name: str, mocker: MockerFixture, -): +) -> None: repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cleo", "0.6.5")) repo.add_package(get_package("tomlkit", "0.5.5")) @@ -767,7 +767,7 @@ def test_add_url_constraint_wheel_with_extras( def test_add_constraint_with_python( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester -): +) -> None: cachy2 = get_package("cachy", "0.2.0") repo.add_package(get_package("cachy", "0.1.0")) @@ -801,7 +801,7 @@ def test_add_constraint_with_platform( repo: TestRepository, tester: CommandTester, env: MockEnv, -): +) -> None: platform = sys.platform env._platform = platform env._marker_env = None @@ -842,7 +842,7 @@ def test_add_constraint_with_source( poetry: Poetry, tester: CommandTester, mocker: MockerFixture, -): +) -> None: repo = LegacyRepository(name="my-index", url="https://my-index.fake") repo.add_package(get_package("cachy", "0.2.0")) mocker.patch.object( @@ -890,7 +890,7 @@ def test_add_constraint_with_source( def test_add_constraint_with_source_that_does_not_exist( app: PoetryTestApplication, tester: CommandTester -): +) -> None: with pytest.raises(IndexError) as e: tester.execute("foo --source i-dont-exist") @@ -902,7 +902,7 @@ def test_add_constraint_not_found_with_source( poetry: Poetry, mocker: MockerFixture, tester: CommandTester, -): +) -> None: repo = LegacyRepository(name="my-index", url="https://my-index.fake") mocker.patch.object(repo, "find_packages", return_value=[]) @@ -919,7 +919,7 @@ def test_add_constraint_not_found_with_source( def test_add_to_section_that_does_not_exist_yet( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester -): +) -> None: repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(get_package("cachy", "0.2.0")) @@ -962,7 +962,7 @@ def test_add_to_section_that_does_not_exist_yet( def test_add_to_dev_section_deprecated( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester -): +) -> None: repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(get_package("cachy", "0.2.0")) @@ -997,7 +997,7 @@ def test_add_to_dev_section_deprecated( def test_add_should_not_select_prereleases( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester -): +) -> None: repo.add_package(get_package("pyyaml", "3.13")) repo.add_package(get_package("pyyaml", "4.2b2")) @@ -1027,7 +1027,7 @@ def test_add_should_not_select_prereleases( def test_add_should_skip_when_adding_existing_package_with_no_constraint( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester -): +) -> None: content = app.poetry.file.read() content["tool"]["poetry"]["dependencies"]["foo"] = "^1.0" app.poetry.file.write(content) @@ -1051,7 +1051,7 @@ def test_add_should_skip_when_adding_existing_package_with_no_constraint( def test_add_should_skip_when_adding_canonicalized_existing_package_with_no_constraint( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester -): +) -> None: content = app.poetry.file.read() content["tool"]["poetry"]["dependencies"]["foo-bar"] = "^1.0" app.poetry.file.write(content) @@ -1077,7 +1077,7 @@ def test_add_latest_should_not_create_duplicate_keys( project_factory: ProjectFactory, repo: TestRepository, command_tester_factory: CommandTesterFactory, -): +) -> None: pyproject_content = """\ [tool.poetry] name = "simple-project" @@ -1113,7 +1113,7 @@ def test_add_latest_should_not_create_duplicate_keys( def test_add_should_work_when_adding_existing_package_with_latest_constraint( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester -): +) -> None: content = app.poetry.file.read() content["tool"]["poetry"]["dependencies"]["foo"] = "^1.0" app.poetry.file.write(content) @@ -1145,7 +1145,7 @@ def test_add_should_work_when_adding_existing_package_with_latest_constraint( def test_add_chooses_prerelease_if_only_prereleases_are_available( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester -): +) -> None: repo.add_package(get_package("foo", "1.2.3b0")) repo.add_package(get_package("foo", "1.2.3b1")) @@ -1168,7 +1168,7 @@ def test_add_chooses_prerelease_if_only_prereleases_are_available( def test_add_prefers_stable_releases( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester -): +) -> None: repo.add_package(get_package("foo", "1.2.3")) repo.add_package(get_package("foo", "1.2.4b1")) @@ -1192,7 +1192,7 @@ def test_add_prefers_stable_releases( def test_add_with_lock( app: PoetryTestApplication, repo: TestRepository, tester: CommandTester -): +) -> None: content_hash = app.poetry.locker._get_content_hash() repo.add_package(get_package("cachy", "0.2.0")) @@ -1216,7 +1216,7 @@ def test_add_no_constraint_old_installer( repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(get_package("cachy", "0.2.0")) @@ -1250,7 +1250,7 @@ def test_add_equal_constraint_old_installer( repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(get_package("cachy", "0.2.0")) @@ -1278,7 +1278,7 @@ def test_add_greater_constraint_old_installer( repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(get_package("cachy", "0.2.0")) @@ -1308,7 +1308,7 @@ def test_add_constraint_with_extras_old_installer( installer: NoopInstaller, old_tester: CommandTester, extra_name: str, -): +) -> None: cachy1 = get_package("cachy", "0.1.0") cachy1.extras = {"msgpack": [get_dependency("msgpack-python")]} msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6", optional=True) @@ -1343,7 +1343,7 @@ def test_add_constraint_dependencies_old_installer( repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: cachy2 = get_package("cachy", "0.2.0") msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6") cachy2.add_dependency(msgpack_dep) @@ -1377,7 +1377,7 @@ def test_add_git_constraint_old_installer( repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cleo", "0.6.5")) @@ -1413,7 +1413,7 @@ def test_add_git_constraint_with_poetry_old_installer( repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: repo.add_package(get_package("pendulum", "1.4.4")) old_tester.execute("git+https://github.com/demo/pyproject-demo.git") @@ -1443,7 +1443,7 @@ def test_add_git_constraint_with_extras_old_installer( installer: NoopInstaller, old_tester: CommandTester, extra_name: str, -): +) -> None: repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cleo", "0.6.5")) repo.add_package(get_package("tomlkit", "0.5.5")) @@ -1483,7 +1483,7 @@ def test_add_git_ssh_constraint_old_installer( repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cleo", "0.6.5")) @@ -1524,7 +1524,7 @@ def test_add_directory_constraint_old_installer( repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cleo", "0.6.5")) @@ -1563,7 +1563,7 @@ def test_add_directory_with_poetry_old_installer( repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: repo.add_package(get_package("pendulum", "1.4.4")) path = "../git/github.com/demo/pyproject-demo" @@ -1596,7 +1596,7 @@ def test_add_file_constraint_wheel_old_installer( repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: repo.add_package(get_package("pendulum", "1.4.4")) path = "../distributions/demo-0.1.0-py2.py3-none-any.whl" @@ -1634,7 +1634,7 @@ def test_add_file_constraint_sdist_old_installer( repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: repo.add_package(get_package("pendulum", "1.4.4")) path = "../distributions/demo-0.1.0.tar.gz" @@ -1670,7 +1670,7 @@ def test_add_constraint_with_extras_option_old_installer( installer: NoopInstaller, old_tester: CommandTester, extra_name: str, -): +) -> None: cachy2 = get_package("cachy", "0.2.0") cachy2.extras = {"msgpack": [get_dependency("msgpack-python")]} msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6", optional=True) @@ -1714,7 +1714,7 @@ def test_add_url_constraint_wheel_old_installer( installer: NoopInstaller, mocker: MockerFixture, old_tester: CommandTester, -): +) -> None: p = mocker.patch("pathlib.Path.cwd") p.return_value = Path(__file__) / ".." @@ -1757,7 +1757,7 @@ def test_add_url_constraint_wheel_with_extras_old_installer( installer: NoopInstaller, old_tester: CommandTester, extra_name: str, -): +) -> None: repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cleo", "0.6.5")) repo.add_package(get_package("tomlkit", "0.5.5")) @@ -1803,7 +1803,7 @@ def test_add_constraint_with_python_old_installer( repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: cachy2 = get_package("cachy", "0.2.0") repo.add_package(get_package("cachy", "0.1.0")) @@ -1839,7 +1839,7 @@ def test_add_constraint_with_platform_old_installer( installer: NoopInstaller, env: MockEnv, old_tester: CommandTester, -): +) -> None: platform = sys.platform env._platform = platform env._marker_env = None @@ -1882,7 +1882,7 @@ def test_add_constraint_with_source_old_installer( installer: NoopInstaller, old_tester: CommandTester, mocker: MockerFixture, -): +) -> None: repo = LegacyRepository(name="my-index", url="https://my-index.fake") repo.add_package(get_package("cachy", "0.2.0")) mocker.patch.object( @@ -1931,7 +1931,7 @@ def test_add_constraint_with_source_old_installer( def test_add_constraint_with_source_that_does_not_exist_old_installer( app: PoetryTestApplication, old_tester: CommandTester -): +) -> None: with pytest.raises(IndexError) as e: old_tester.execute("foo --source i-dont-exist") @@ -1943,7 +1943,7 @@ def test_add_constraint_not_found_with_source_old_installer( poetry: Poetry, mocker: MockerFixture, old_tester: CommandTester, -): +) -> None: repo = LegacyRepository(name="my-index", url="https://my-index.fake") mocker.patch.object(repo, "find_packages", return_value=[]) @@ -1963,7 +1963,7 @@ def test_add_to_section_that_does_no_exist_yet_old_installer( repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(get_package("cachy", "0.2.0")) @@ -1997,7 +1997,7 @@ def test_add_should_not_select_prereleases_old_installer( repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: repo.add_package(get_package("pyyaml", "3.13")) repo.add_package(get_package("pyyaml", "4.2b2")) @@ -2031,7 +2031,7 @@ def test_add_should_skip_when_adding_existing_package_with_no_constraint_old_ins repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: content = app.poetry.file.read() content["tool"]["poetry"]["dependencies"]["foo"] = "^1.0" app.poetry.file.write(content) @@ -2059,7 +2059,7 @@ def test_add_should_work_when_adding_existing_package_with_latest_constraint_old repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: content = app.poetry.file.read() content["tool"]["poetry"]["dependencies"]["foo"] = "^1.0" app.poetry.file.write(content) @@ -2094,7 +2094,7 @@ def test_add_chooses_prerelease_if_only_prereleases_are_available_old_installer( repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: repo.add_package(get_package("foo", "1.2.3b0")) repo.add_package(get_package("foo", "1.2.3b1")) @@ -2120,7 +2120,7 @@ def test_add_preferes_stable_releases_old_installer( repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: repo.add_package(get_package("foo", "1.2.3")) repo.add_package(get_package("foo", "1.2.4b1")) @@ -2147,7 +2147,7 @@ def test_add_with_lock_old_installer( repo: TestRepository, installer: NoopInstaller, old_tester: CommandTester, -): +) -> None: repo.add_package(get_package("cachy", "0.2.0")) old_tester.execute("cachy --lock") @@ -2169,7 +2169,7 @@ def test_add_keyboard_interrupt_restore_content( repo: TestRepository, command_tester_factory: CommandTesterFactory, mocker: MockerFixture, -): +) -> None: tester = command_tester_factory("add", poetry=poetry_with_up_to_date_lockfile) mocker.patch( @@ -2202,7 +2202,7 @@ def test_add_with_dry_run_keep_files_intact( poetry_with_up_to_date_lockfile: Poetry, repo: TestRepository, command_tester_factory: CommandTesterFactory, -): +) -> None: tester = command_tester_factory("add", poetry=poetry_with_up_to_date_lockfile) original_pyproject_content = poetry_with_up_to_date_lockfile.file.read() @@ -2224,7 +2224,7 @@ def test_add_should_not_change_lock_file_when_dependency_installation_fail( repo: TestRepository, command_tester_factory: CommandTesterFactory, mocker: MockerFixture, -): +) -> None: tester = command_tester_factory("add", poetry=poetry_with_up_to_date_lockfile) repo.add_package(get_package("docker", "4.3.1")) diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index 975132f4bc0..063200edbf8 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -34,7 +34,7 @@ def tester(command_tester_factory: CommandTesterFactory) -> CommandTester: def test_show_config_with_local_config_file_empty( tester: CommandTester, mocker: MockerFixture -): +) -> None: mocker.patch( "poetry.factory.Factory.create_poetry", side_effect=PyProjectException("[tool.poetry] section not found"), @@ -46,7 +46,7 @@ def test_show_config_with_local_config_file_empty( def test_list_displays_default_value_if_not_set( tester: CommandTester, config: Config, config_cache_dir: Path -): +) -> None: tester.execute("--list") cache_dir = json.dumps(str(config_cache_dir)) @@ -74,7 +74,7 @@ def test_list_displays_default_value_if_not_set( def test_list_displays_set_get_setting( tester: CommandTester, config: Config, config_cache_dir: Path -): +) -> None: tester.execute("virtualenvs.create false") tester.execute("--list") @@ -103,7 +103,7 @@ def test_list_displays_set_get_setting( assert tester.io.fetch_output() == expected -def test_display_single_setting(tester: CommandTester, config: Config): +def test_display_single_setting(tester: CommandTester, config: Config) -> None: tester.execute("virtualenvs.create") expected = """true @@ -114,7 +114,7 @@ def test_display_single_setting(tester: CommandTester, config: Config): def test_display_single_local_setting( command_tester_factory: CommandTesterFactory, fixture_dir: FixtureDirGetter -): +) -> None: tester = command_tester_factory( "config", poetry=Factory().create_poetry(fixture_dir("with_local_config")) ) @@ -128,7 +128,7 @@ def test_display_single_local_setting( def test_list_displays_set_get_local_setting( tester: CommandTester, config: Config, config_cache_dir: Path -): +) -> None: tester.execute("virtualenvs.create false --local") tester.execute("--list") @@ -163,7 +163,7 @@ def test_list_must_not_display_sources_from_pyproject_toml( command_tester_factory: CommandTesterFactory, config: Config, config_cache_dir: Path, -): +) -> None: source = fixture_dir("with_non_default_source_implicit") pyproject_content = (source / "pyproject.toml").read_text(encoding="utf-8") poetry = project_factory("foo", pyproject_content=pyproject_content) @@ -195,7 +195,9 @@ def test_list_must_not_display_sources_from_pyproject_toml( assert tester.io.fetch_output() == expected -def test_set_pypi_token(tester: CommandTester, auth_config_source: DictConfigSource): +def test_set_pypi_token( + tester: CommandTester, auth_config_source: DictConfigSource +) -> None: tester.execute("pypi-token.pypi mytoken") tester.execute("--list") @@ -206,7 +208,7 @@ def test_set_client_cert( tester: CommandTester, auth_config_source: DictConfigSource, mocker: MockerFixture, -): +) -> None: mocker.spy(ConfigSource, "__init__") tester.execute("certificates.foo.client-cert path/to/cert.pem") @@ -231,7 +233,7 @@ def test_set_cert( mocker: MockerFixture, value: str, result: str | bool, -): +) -> None: mocker.spy(ConfigSource, "__init__") tester.execute(f"certificates.foo.cert {value}") @@ -241,7 +243,7 @@ def test_set_cert( def test_config_installer_parallel( tester: CommandTester, command_tester_factory: CommandTesterFactory -): +) -> None: tester.execute("--local installer.parallel") assert tester.io.fetch_output().strip() == "true" diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py index e3da0a989a6..b4bed706b99 100644 --- a/tests/console/commands/test_init.py +++ b/tests/console/commands/test_init.py @@ -95,7 +95,7 @@ def init_basic_toml() -> str: def test_basic_interactive( tester: CommandTester, init_basic_inputs: str, init_basic_toml: str -): +) -> None: tester.execute(inputs=init_basic_inputs) assert init_basic_toml in tester.io.fetch_output() @@ -106,7 +106,7 @@ def test_noninteractive( poetry: Poetry, repo: TestRepository, tmp_path: Path, -): +) -> None: command = app.find("init") command._pool = poetry.pool @@ -128,7 +128,9 @@ def test_noninteractive( assert 'pytest = "^3.6.0"' in toml_content -def test_interactive_with_dependencies(tester: CommandTester, repo: TestRepository): +def test_interactive_with_dependencies( + tester: CommandTester, repo: TestRepository +) -> None: repo.add_package(get_package("django-pendulum", "0.1.6-pre4")) repo.add_package(get_package("pendulum", "2.0.0")) repo.add_package(get_package("pytest", "3.6.0")) @@ -183,7 +185,7 @@ def test_interactive_with_dependencies(tester: CommandTester, repo: TestReposito # Regression test for https://github.com/python-poetry/poetry/issues/2355 def test_interactive_with_dependencies_and_no_selection( tester: CommandTester, repo: TestRepository -): +) -> None: repo.add_package(get_package("django-pendulum", "0.1.6-pre4")) repo.add_package(get_package("pendulum", "2.0.0")) repo.add_package(get_package("pytest", "3.6.0")) @@ -224,7 +226,7 @@ def test_interactive_with_dependencies_and_no_selection( assert expected in tester.io.fetch_output() -def test_empty_license(tester: CommandTester): +def test_empty_license(tester: CommandTester) -> None: inputs = [ "my-package", # Package name "1.2.3", # Version @@ -254,7 +256,9 @@ def test_empty_license(tester: CommandTester): assert expected in tester.io.fetch_output() -def test_interactive_with_git_dependencies(tester: CommandTester, repo: TestRepository): +def test_interactive_with_git_dependencies( + tester: CommandTester, repo: TestRepository +) -> None: repo.add_package(get_package("pendulum", "2.0.0")) repo.add_package(get_package("pytest", "3.6.0")) @@ -332,7 +336,7 @@ def test_generate_choice_list( tester: CommandTester, package_name: str, _generate_choice_list_packages: list[Package], -): +) -> None: init_command = tester.command packages = _generate_choice_list_packages @@ -345,7 +349,7 @@ def test_generate_choice_list( def test_interactive_with_git_dependencies_with_reference( tester: CommandTester, repo: TestRepository -): +) -> None: repo.add_package(get_package("pendulum", "2.0.0")) repo.add_package(get_package("pytest", "3.6.0")) @@ -391,7 +395,7 @@ def test_interactive_with_git_dependencies_with_reference( def test_interactive_with_git_dependencies_and_other_name( tester: CommandTester, repo: TestRepository -): +) -> None: repo.add_package(get_package("pendulum", "2.0.0")) repo.add_package(get_package("pytest", "3.6.0")) @@ -440,7 +444,7 @@ def test_interactive_with_directory_dependency( repo: TestRepository, source_dir: Path, fixture_dir: FixtureDirGetter, -): +) -> None: repo.add_package(get_package("pendulum", "2.0.0")) repo.add_package(get_package("pytest", "3.6.0")) @@ -491,7 +495,7 @@ def test_interactive_with_directory_dependency_and_other_name( repo: TestRepository, source_dir: Path, fixture_dir: FixtureDirGetter, -): +) -> None: repo.add_package(get_package("pendulum", "2.0.0")) repo.add_package(get_package("pytest", "3.6.0")) @@ -543,7 +547,7 @@ def test_interactive_with_file_dependency( repo: TestRepository, source_dir: Path, fixture_dir: FixtureDirGetter, -): +) -> None: repo.add_package(get_package("pendulum", "2.0.0")) repo.add_package(get_package("pytest", "3.6.0")) @@ -592,7 +596,7 @@ def test_interactive_with_file_dependency( def test_interactive_with_wrong_dependency_inputs( tester: CommandTester, repo: TestRepository -): +) -> None: inputs = [ "my-package", # Package name "1.2.3", # Version @@ -636,7 +640,7 @@ def test_interactive_with_wrong_dependency_inputs( assert expected in tester.io.fetch_output() -def test_python_option(tester: CommandTester): +def test_python_option(tester: CommandTester) -> None: inputs = [ "my-package", # Package name "1.2.3", # Version @@ -666,7 +670,7 @@ def test_python_option(tester: CommandTester): assert expected in tester.io.fetch_output() -def test_predefined_dependency(tester: CommandTester, repo: TestRepository): +def test_predefined_dependency(tester: CommandTester, repo: TestRepository) -> None: repo.add_package(get_package("pendulum", "2.0.0")) inputs = [ @@ -702,7 +706,7 @@ def test_predefined_dependency(tester: CommandTester, repo: TestRepository): def test_predefined_and_interactive_dependencies( tester: CommandTester, repo: TestRepository -): +) -> None: repo.add_package(get_package("pendulum", "2.0.0")) repo.add_package(get_package("pyramid", "1.10")) @@ -743,7 +747,7 @@ def test_predefined_and_interactive_dependencies( assert 'pyramid = "^1.10"' in output -def test_predefined_dev_dependency(tester: CommandTester, repo: TestRepository): +def test_predefined_dev_dependency(tester: CommandTester, repo: TestRepository) -> None: repo.add_package(get_package("pytest", "3.6.0")) inputs = [ @@ -782,7 +786,7 @@ def test_predefined_dev_dependency(tester: CommandTester, repo: TestRepository): def test_predefined_and_interactive_dev_dependencies( tester: CommandTester, repo: TestRepository -): +) -> None: repo.add_package(get_package("pytest", "3.6.0")) repo.add_package(get_package("pytest-requests", "0.2.0")) @@ -828,7 +832,7 @@ def test_predefined_and_interactive_dev_dependencies( assert 'pytest = "^3.6.0"' in output -def test_predefined_all_options(tester: CommandTester, repo: TestRepository): +def test_predefined_all_options(tester: CommandTester, repo: TestRepository) -> None: repo.add_package(get_package("pendulum", "2.0.0")) repo.add_package(get_package("pytest", "3.6.0")) @@ -875,7 +879,7 @@ def test_predefined_all_options(tester: CommandTester, repo: TestRepository): assert expected in output -def test_add_package_with_extras_and_whitespace(tester: CommandTester): +def test_add_package_with_extras_and_whitespace(tester: CommandTester) -> None: result = tester.command._parse_requirements(["databases[postgresql, sqlite]"]) assert result[0]["name"] == "databases" @@ -889,7 +893,7 @@ def test_init_existing_pyproject_simple( source_dir: Path, init_basic_inputs: str, init_basic_toml: str, -): +) -> None: pyproject_file = source_dir / "pyproject.toml" existing_section = """ [tool.black] @@ -907,7 +911,7 @@ def test_init_existing_pyproject_consistent_linesep( init_basic_inputs: str, init_basic_toml: str, linesep: str, -): +) -> None: pyproject_file = source_dir / "pyproject.toml" existing_section = """ [tool.black] @@ -929,7 +933,7 @@ def test_init_non_interactive_existing_pyproject_add_dependency( source_dir: Path, init_basic_inputs: str, repo: TestRepository, -): +) -> None: pyproject_file = source_dir / "pyproject.toml" existing_section = """ [tool.black] @@ -967,7 +971,7 @@ def test_init_non_interactive_existing_pyproject_add_dependency( def test_init_existing_pyproject_with_build_system_fails( tester: CommandTester, source_dir: Path, init_basic_inputs: str -): +) -> None: pyproject_file = source_dir / "pyproject.toml" existing_section = """ [build-system] @@ -997,14 +1001,14 @@ def test_init_existing_pyproject_with_build_system_fails( " foo 2.0 ", ], ) -def test__validate_package_valid(name: str | None): +def test_validate_package_valid(name: str | None) -> None: assert InitCommand._validate_package(name) == name @pytest.mark.parametrize( "name", ["foo bar 2.0", " foo bar 2.0 ", "foo bar foobar 2.0"] ) -def test__validate_package_invalid(name: str): +def test_validate_package_invalid(name: str) -> None: with pytest.raises(ValueError): assert InitCommand._validate_package(name) @@ -1023,7 +1027,7 @@ def test_package_include( tester: CommandTester, package_name: str, include: str | None, -): +) -> None: tester.execute( inputs="\n".join( ( @@ -1074,7 +1078,7 @@ def test_respect_prefer_active_on_init( mocker: MockerFixture, tester: CommandTester, source_dir: Path, -): +) -> None: from poetry.utils.env import GET_PYTHON_VERSION_ONELINER orig_check_output = subprocess.check_output diff --git a/tests/console/commands/test_install.py b/tests/console/commands/test_install.py index a1e9379de49..6f4f407dd63 100644 --- a/tests/console/commands/test_install.py +++ b/tests/console/commands/test_install.py @@ -119,7 +119,7 @@ def test_group_options_are_passed_to_the_installer( with_root: bool, tester: CommandTester, mocker: MockerFixture, -): +) -> None: """ Group options are passed properly to the installer. """ @@ -155,7 +155,7 @@ def test_group_options_are_passed_to_the_installer( def test_sync_option_is_passed_to_the_installer( tester: CommandTester, mocker: MockerFixture -): +) -> None: """ The --sync option is passed properly to the installer. """ @@ -169,7 +169,7 @@ def test_sync_option_is_passed_to_the_installer( @pytest.mark.parametrize("compile", [False, True]) def test_compile_option_is_passed_to_the_installer( tester: CommandTester, mocker: MockerFixture, compile: bool -): +) -> None: """ The --compile option is passed properly to the installer. """ @@ -187,7 +187,7 @@ def test_compile_option_is_passed_to_the_installer( @pytest.mark.parametrize("skip_directory_cli_value", [True, False]) def test_no_directory_is_passed_to_installer( tester: CommandTester, mocker: MockerFixture, skip_directory_cli_value: bool -): +) -> None: """ The --no-directory option is passed to the installer. """ @@ -204,7 +204,7 @@ def test_no_directory_is_passed_to_installer( def test_no_all_extras_doesnt_populate_installer( tester: CommandTester, mocker: MockerFixture -): +) -> None: """ Not passing --all-extras means the installer doesn't see any extras. """ @@ -215,7 +215,9 @@ def test_no_all_extras_doesnt_populate_installer( assert not tester.command.installer._extras -def test_all_extras_populates_installer(tester: CommandTester, mocker: MockerFixture): +def test_all_extras_populates_installer( + tester: CommandTester, mocker: MockerFixture +) -> None: """ The --all-extras option results in extras passed to the installer. """ @@ -229,7 +231,7 @@ def test_all_extras_populates_installer(tester: CommandTester, mocker: MockerFix def test_extras_are_parsed_and_populate_installer( tester: CommandTester, mocker: MockerFixture, -): +) -> None: mocker.patch.object(tester.command.installer, "run", return_value=0) tester.execute('--extras "first second third"') @@ -237,7 +239,9 @@ def test_extras_are_parsed_and_populate_installer( assert tester.command.installer._extras == ["first", "second", "third"] -def test_extras_conflicts_all_extras(tester: CommandTester, mocker: MockerFixture): +def test_extras_conflicts_all_extras( + tester: CommandTester, mocker: MockerFixture +) -> None: """ The --extras doesn't make sense with --all-extras. """ @@ -266,7 +270,7 @@ def test_only_root_conflicts_with_without_only( options: str, tester: CommandTester, mocker: MockerFixture, -): +) -> None: mocker.patch.object(tester.command.installer, "run", return_value=0) tester.execute(f"{options} --only-root") @@ -300,7 +304,7 @@ def test_invalid_groups_with_without_only( options: dict[str, str], valid_groups: set[str], should_raise: bool, -): +) -> None: mocker.patch.object(tester.command.installer, "run", return_value=0) cmd_args = " ".join(f"{flag} {groups}" for (flag, groups) in options.items()) @@ -324,7 +328,7 @@ def test_invalid_groups_with_without_only( def test_remove_untracked_outputs_deprecation_warning( tester: CommandTester, mocker: MockerFixture, -): +) -> None: mocker.patch.object(tester.command.installer, "run", return_value=0) tester.execute("--remove-untracked") @@ -337,7 +341,9 @@ def test_remove_untracked_outputs_deprecation_warning( ) -def test_dry_run_populates_installer(tester: CommandTester, mocker: MockerFixture): +def test_dry_run_populates_installer( + tester: CommandTester, mocker: MockerFixture +) -> None: """ The --dry-run option results in extras passed to the installer. """ @@ -349,7 +355,7 @@ def test_dry_run_populates_installer(tester: CommandTester, mocker: MockerFixtur assert tester.command.installer._dry_run is True -def test_dry_run_does_not_build(tester: CommandTester, mocker: MockerFixture): +def test_dry_run_does_not_build(tester: CommandTester, mocker: MockerFixture) -> None: mocker.patch.object(tester.command.installer, "run", return_value=0) mocked_editable_builder = mocker.patch( "poetry.masonry.builders.editable.EditableBuilder" @@ -360,7 +366,7 @@ def test_dry_run_does_not_build(tester: CommandTester, mocker: MockerFixture): assert mocked_editable_builder.return_value.build.call_count == 0 -def test_install_logs_output(tester: CommandTester, mocker: MockerFixture): +def test_install_logs_output(tester: CommandTester, mocker: MockerFixture) -> None: mocker.patch.object(tester.command.installer, "run", return_value=0) mocker.patch("poetry.masonry.builders.editable.EditableBuilder") @@ -373,7 +379,9 @@ def test_install_logs_output(tester: CommandTester, mocker: MockerFixture): ) -def test_install_logs_output_decorated(tester: CommandTester, mocker: MockerFixture): +def test_install_logs_output_decorated( + tester: CommandTester, mocker: MockerFixture +) -> None: mocker.patch.object(tester.command.installer, "run", return_value=0) mocker.patch("poetry.masonry.builders.editable.EditableBuilder") @@ -402,7 +410,7 @@ def test_install_path_dependency_does_not_exist( fixture_dir: FixtureDirGetter, project: str, options: str, -): +) -> None: poetry = _project_factory(project, project_factory, fixture_dir) poetry.locker.locked(True) tester = command_tester_factory("install", poetry=poetry) diff --git a/tests/console/commands/test_lock.py b/tests/console/commands/test_lock.py index 506f82a1776..7a910caccf7 100644 --- a/tests/console/commands/test_lock.py +++ b/tests/console/commands/test_lock.py @@ -93,7 +93,7 @@ def test_lock_check_outdated( command_tester_factory: CommandTesterFactory, poetry_with_outdated_lockfile: Poetry, http: type[httpretty.httpretty], -): +) -> None: http.disable() locker = Locker( @@ -119,7 +119,7 @@ def test_lock_check_up_to_date( command_tester_factory: CommandTesterFactory, poetry_with_up_to_date_lockfile: Poetry, http: type[httpretty.httpretty], -): +) -> None: http.disable() locker = Locker( @@ -141,7 +141,7 @@ def test_lock_no_update( command_tester_factory: CommandTesterFactory, poetry_with_old_lockfile: Poetry, repo: TestRepository, -): +) -> None: repo.add_package(get_package("sampleproject", "1.3.1")) repo.add_package(get_package("sampleproject", "2.0.0")) @@ -178,7 +178,7 @@ def test_lock_no_update_path_dependencies( command_tester_factory: CommandTesterFactory, poetry_with_nested_path_deps_old_lockfile: Poetry, repo: TestRepository, -): +) -> None: """ The lock file contains a variant of the directory dependency "quix" that does not depend on "sampleproject". Although the version of "quix" has not been changed, @@ -214,7 +214,7 @@ def test_lock_path_dependency_does_not_exist( fixture_dir: FixtureDirGetter, project: str, update: bool, -): +) -> None: poetry = _project_factory(project, project_factory, fixture_dir) locker = Locker( lock=poetry.pyproject.file.path.parent / "poetry.lock", @@ -242,7 +242,7 @@ def test_lock_path_dependency_deleted_from_pyproject( fixture_dir: FixtureDirGetter, project: str, update: bool, -): +) -> None: poetry = _project_factory(project, project_factory, fixture_dir) locker = Locker( lock=poetry.pyproject.file.path.parent / "poetry.lock", diff --git a/tests/console/commands/test_new.py b/tests/console/commands/test_new.py index 2b2cc9d83f2..7dd76f7cea7 100644 --- a/tests/console/commands/test_new.py +++ b/tests/console/commands/test_new.py @@ -156,7 +156,7 @@ def test_command_new( include_from: str | None, tester: CommandTester, tmp_path: Path, -): +) -> None: path = tmp_path / directory options.append(str(path)) tester.execute(" ".join(options)) @@ -166,7 +166,7 @@ def test_command_new( @pytest.mark.parametrize(("fmt",), [(None,), ("md",), ("rst",), ("adoc",), ("creole",)]) def test_command_new_with_readme( fmt: str | None, tester: CommandTester, tmp_path: Path -): +) -> None: package = "package" path = tmp_path / package options = [path.as_posix()] @@ -194,7 +194,7 @@ def test_respect_prefer_active_on_new( mocker: MockerFixture, tester: CommandTester, tmp_path: Path, -): +) -> None: from poetry.utils.env import GET_PYTHON_VERSION_ONELINER orig_check_output = subprocess.check_output diff --git a/tests/console/commands/test_publish.py b/tests/console/commands/test_publish.py index f4241a1a870..5f230c01ff3 100644 --- a/tests/console/commands/test_publish.py +++ b/tests/console/commands/test_publish.py @@ -23,7 +23,7 @@ def test_publish_returns_non_zero_code_for_upload_errors( app: PoetryTestApplication, app_tester: ApplicationTester, http: type[httpretty.httpretty], -): +) -> None: http.register_uri( http.POST, "https://upload.pypi.org/legacy/", status=400, body="Bad Request" ) @@ -48,7 +48,7 @@ def test_publish_returns_non_zero_code_for_connection_errors( app: PoetryTestApplication, app_tester: ApplicationTester, http: type[httpretty.httpretty], -): +) -> None: def request_callback(*_: Any, **__: Any) -> None: raise requests.ConnectionError() @@ -65,7 +65,9 @@ def request_callback(*_: Any, **__: Any) -> None: assert expected in app_tester.io.fetch_error() -def test_publish_with_cert(app_tester: ApplicationTester, mocker: MockerFixture): +def test_publish_with_cert( + app_tester: ApplicationTester, mocker: MockerFixture +) -> None: publisher_publish = mocker.patch("poetry.publishing.Publisher.publish") app_tester.execute("publish --cert path/to/ca.pem") @@ -75,7 +77,9 @@ def test_publish_with_cert(app_tester: ApplicationTester, mocker: MockerFixture) ] == publisher_publish.call_args -def test_publish_with_client_cert(app_tester: ApplicationTester, mocker: MockerFixture): +def test_publish_with_client_cert( + app_tester: ApplicationTester, mocker: MockerFixture +) -> None: publisher_publish = mocker.patch("poetry.publishing.Publisher.publish") app_tester.execute("publish --client-cert path/to/client.pem") @@ -94,7 +98,7 @@ def test_publish_with_client_cert(app_tester: ApplicationTester, mocker: MockerF ) def test_publish_dry_run_skip_existing( app_tester: ApplicationTester, http: type[httpretty.httpretty], options: str -): +) -> None: http.register_uri( http.POST, "https://upload.pypi.org/legacy/", status=409, body="Conflict" ) @@ -113,7 +117,7 @@ def test_publish_dry_run_skip_existing( def test_skip_existing_output( app_tester: ApplicationTester, http: type[httpretty.httpretty] -): +) -> None: http.register_uri( http.POST, "https://upload.pypi.org/legacy/", status=409, body="Conflict" ) diff --git a/tests/console/commands/test_remove.py b/tests/console/commands/test_remove.py index 0a2fa1b6c00..68c16edf065 100644 --- a/tests/console/commands/test_remove.py +++ b/tests/console/commands/test_remove.py @@ -48,7 +48,7 @@ def test_remove_without_specific_group_removes_from_all_groups( repo: TestRepository, command_tester_factory: CommandTesterFactory, installed: Repository, -): +) -> None: """ Removing without specifying a group removes packages from all groups. """ @@ -105,7 +105,7 @@ def test_remove_without_specific_group_removes_from_specific_groups( repo: TestRepository, command_tester_factory: CommandTesterFactory, installed: Repository, -): +) -> None: """ Removing with a specific group given removes packages only from this group. """ @@ -162,7 +162,7 @@ def test_remove_does_not_live_empty_groups( repo: TestRepository, command_tester_factory: CommandTesterFactory, installed: Repository, -): +) -> None: """ Empty groups are automatically discarded after package removal. """ @@ -208,7 +208,7 @@ def test_remove_canonicalized_named_removes_dependency_correctly( repo: TestRepository, command_tester_factory: CommandTesterFactory, installed: Repository, -): +) -> None: """ Removing a dependency using a canonicalized named removes the dependency. """ @@ -267,7 +267,7 @@ def test_remove_command_should_not_write_changes_upon_installer_errors( repo: TestRepository, command_tester_factory: CommandTesterFactory, mocker: MockerFixture, -): +) -> None: repo.add_package(Package("foo", "2.0.0")) command_tester_factory("add").execute("foo") @@ -285,7 +285,7 @@ def test_remove_with_dry_run_keep_files_intact( poetry_with_up_to_date_lockfile: Poetry, repo: TestRepository, command_tester_factory: CommandTesterFactory, -): +) -> None: tester = command_tester_factory("remove", poetry=poetry_with_up_to_date_lockfile) original_pyproject_content = poetry_with_up_to_date_lockfile.file.read() diff --git a/tests/console/commands/test_run.py b/tests/console/commands/test_run.py index 7442ac10f37..53c5dbbb3d3 100644 --- a/tests/console/commands/test_run.py +++ b/tests/console/commands/test_run.py @@ -45,14 +45,14 @@ def poetry_with_scripts( ) -def test_run_passes_all_args(app_tester: ApplicationTester, env: MockEnv): +def test_run_passes_all_args(app_tester: ApplicationTester, env: MockEnv) -> None: app_tester.execute("run python -V") assert [["python", "-V"]] == env.executed def test_run_keeps_options_passed_before_command( app_tester: ApplicationTester, env: MockEnv -): +) -> None: app_tester.execute("-V --no-ansi run python", decorated=True) assert not app_tester.io.is_decorated() @@ -64,7 +64,7 @@ def test_run_keeps_options_passed_before_command( def test_run_has_helpful_error_when_command_not_found( app_tester: ApplicationTester, env: MockEnv, capfd: pytest.CaptureFixture[str] -): +) -> None: nonexistent_command = "nonexistent-command" env._execute = True app_tester.execute(f"run {nonexistent_command}") @@ -94,7 +94,7 @@ def test_run_has_helpful_error_when_command_not_found( def test_run_console_scripts_of_editable_dependencies_on_windows( tmp_venv: VirtualEnv, command_tester_factory: CommandTesterFactory, -): +) -> None: """ On Windows, Poetry installs console scripts of editable dependencies by creating in the environment's `Scripts/` directory both: diff --git a/tests/console/commands/test_shell.py b/tests/console/commands/test_shell.py index abdaec46789..a8d0b8407db 100644 --- a/tests/console/commands/test_shell.py +++ b/tests/console/commands/test_shell.py @@ -20,7 +20,7 @@ def tester(command_tester_factory: CommandTesterFactory) -> CommandTester: return command_tester_factory("shell") -def test_shell(tester: CommandTester, mocker: MockerFixture): +def test_shell(tester: CommandTester, mocker: MockerFixture) -> None: shell_activate = mocker.patch("poetry.utils.shell.Shell.activate") tester.execute() @@ -32,7 +32,7 @@ def test_shell(tester: CommandTester, mocker: MockerFixture): assert tester.status_code == 0 -def test_shell_already_active(tester: CommandTester, mocker: MockerFixture): +def test_shell_already_active(tester: CommandTester, mocker: MockerFixture) -> None: os.environ["POETRY_ACTIVE"] = "1" shell_activate = mocker.patch("poetry.utils.shell.Shell.activate") @@ -71,7 +71,7 @@ def test__is_venv_activated( real_prefix: str | None, prefix: str, expected: bool, -): +) -> None: mocker.patch.object(tester.command.env, "_path", Path("foobar")) mocker.patch("sys.prefix", prefix) diff --git a/tests/console/commands/test_update.py b/tests/console/commands/test_update.py index dd3306cf2ba..cb0f0052ac1 100644 --- a/tests/console/commands/test_update.py +++ b/tests/console/commands/test_update.py @@ -40,7 +40,7 @@ def test_update_with_dry_run_keep_files_intact( poetry_with_up_to_date_lockfile: Poetry, repo: TestRepository, command_tester_factory: CommandTesterFactory, -): +) -> None: tester = command_tester_factory("update", poetry=poetry_with_up_to_date_lockfile) original_pyproject_content = poetry_with_up_to_date_lockfile.file.read() diff --git a/tests/console/commands/test_version.py b/tests/console/commands/test_version.py index d578b7fd918..33d5cb3824f 100644 --- a/tests/console/commands/test_version.py +++ b/tests/console/commands/test_version.py @@ -51,31 +51,31 @@ def tester(command_tester_factory: CommandTesterFactory) -> CommandTester: ) def test_increment_version( version: str, rule: str, expected: str, command: VersionCommand -): +) -> None: assert command.increment_version(version, rule).text == expected -def test_version_show(tester: CommandTester): +def test_version_show(tester: CommandTester) -> None: tester.execute() assert tester.io.fetch_output() == "simple-project 1.2.3\n" -def test_short_version_show(tester: CommandTester): +def test_short_version_show(tester: CommandTester) -> None: tester.execute("--short") assert tester.io.fetch_output() == "1.2.3\n" -def test_version_update(tester: CommandTester): +def test_version_update(tester: CommandTester) -> None: tester.execute("2.0.0") assert tester.io.fetch_output() == "Bumping version from 1.2.3 to 2.0.0\n" -def test_short_version_update(tester: CommandTester): +def test_short_version_update(tester: CommandTester) -> None: tester.execute("--short 2.0.0") assert tester.io.fetch_output() == "2.0.0\n" -def test_dry_run(tester: CommandTester): +def test_dry_run(tester: CommandTester) -> None: old_pyproject = tester.command.poetry.file.path.read_text() tester.execute("--dry-run major") diff --git a/tests/console/test_application.py b/tests/console/test_application.py index 9ed2e2aeb54..7bca231d04a 100644 --- a/tests/console/test_application.py +++ b/tests/console/test_application.py @@ -39,7 +39,7 @@ def with_add_command_plugin(mocker: MockerFixture) -> None: mock_metadata_entry_points(mocker, AddCommandPlugin) -def test_application_with_plugins(with_add_command_plugin: None): +def test_application_with_plugins(with_add_command_plugin: None) -> None: app = Application() tester = ApplicationTester(app) @@ -49,7 +49,7 @@ def test_application_with_plugins(with_add_command_plugin: None): assert tester.status_code == 0 -def test_application_with_plugins_disabled(with_add_command_plugin: None): +def test_application_with_plugins_disabled(with_add_command_plugin: None) -> None: app = Application() tester = ApplicationTester(app) @@ -59,7 +59,7 @@ def test_application_with_plugins_disabled(with_add_command_plugin: None): assert tester.status_code == 0 -def test_application_execute_plugin_command(with_add_command_plugin: None): +def test_application_execute_plugin_command(with_add_command_plugin: None) -> None: app = Application() tester = ApplicationTester(app) @@ -71,7 +71,7 @@ def test_application_execute_plugin_command(with_add_command_plugin: None): def test_application_execute_plugin_command_with_plugins_disabled( with_add_command_plugin: None, -): +) -> None: app = Application() tester = ApplicationTester(app) @@ -83,7 +83,7 @@ def test_application_execute_plugin_command_with_plugins_disabled( @pytest.mark.parametrize("disable_cache", [True, False]) -def test_application_verify_source_cache_flag(disable_cache: bool): +def test_application_verify_source_cache_flag(disable_cache: bool) -> None: app = Application() tester = ApplicationTester(app) diff --git a/tests/installation/test_chef.py b/tests/installation/test_chef.py index c03dfd07c4d..ca00ba01e73 100644 --- a/tests/installation/test_chef.py +++ b/tests/installation/test_chef.py @@ -68,7 +68,7 @@ def test_prepare_directory( config_cache_dir: Path, artifact_cache: ArtifactCache, fixture_dir: FixtureDirGetter, -): +) -> None: chef = Chef( artifact_cache, EnvManager.get_system_env(), Factory.create_pool(config) ) @@ -107,7 +107,7 @@ def test_prepare_directory_editable( config_cache_dir: Path, artifact_cache: ArtifactCache, fixture_dir: FixtureDirGetter, -): +) -> None: chef = Chef( artifact_cache, EnvManager.get_system_env(), Factory.create_pool(config) ) diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index c8a529b40a7..18d5cd1b60c 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -203,7 +203,7 @@ def test_execute_executes_a_batch_of_operations( env: MockEnv, copy_wheel: Callable[[], Path], fixture_dir: FixtureDirGetter, -): +) -> None: wheel_install = mocker.patch.object(WheelInstaller, "install") config.merge({"cache-dir": str(tmp_path)}) @@ -314,7 +314,7 @@ def test_execute_prints_warning_for_yanked_package( env: MockEnv, operations: list[Operation], has_warning: bool, -): +) -> None: config.merge({"cache-dir": str(tmp_path)}) executor = Executor(env, pool, config, io) @@ -345,7 +345,7 @@ def test_execute_prints_warning_for_invalid_wheels( tmp_path: Path, mock_file_downloads: None, env: MockEnv, -): +) -> None: config.merge({"cache-dir": str(tmp_path)}) executor = Executor(env, pool, config, io) @@ -404,7 +404,7 @@ def test_execute_shows_skipped_operations_if_verbose( io: BufferedIO, config_cache_dir: Path, env: MockEnv, -): +) -> None: config.merge({"cache-dir": config_cache_dir.as_posix()}) executor = Executor(env, pool, config, io) @@ -432,7 +432,7 @@ def test_execute_should_show_errors( mocker: MockerFixture, io: BufferedIO, env: MockEnv, -): +) -> None: executor = Executor(env, pool, config, io) executor.verbose() @@ -460,7 +460,7 @@ def test_execute_works_with_ansi_output( tmp_path: Path, mock_file_downloads: None, env: MockEnv, -): +) -> None: config.merge({"cache-dir": str(tmp_path)}) executor = Executor(env, pool, config, io_decorated) @@ -497,7 +497,7 @@ def test_execute_works_with_no_ansi_output( tmp_path: Path, mock_file_downloads: None, env: MockEnv, -): +) -> None: config.merge({"cache-dir": str(tmp_path)}) executor = Executor(env, pool, config, io_not_decorated) @@ -525,7 +525,7 @@ def test_execute_should_show_operation_as_cancelled_on_subprocess_keyboard_inter mocker: MockerFixture, io: BufferedIO, env: MockEnv, -): +) -> None: executor = Executor(env, pool, config, io) executor.verbose() @@ -550,7 +550,7 @@ def test_execute_should_gracefully_handle_io_error( mocker: MockerFixture, io: BufferedIO, env: MockEnv, -): +) -> None: executor = Executor(env, pool, config, io) executor.verbose() @@ -584,7 +584,7 @@ def test_executor_should_delete_incomplete_downloads( mock_file_downloads: None, env: MockEnv, fixture_dir: FixtureDirGetter, -): +) -> None: fixture = fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" destination_fixture = tmp_path / "tomlkit-0.5.3-py2.py3-none-any.whl" shutil.copyfile(str(fixture), str(destination_fixture)) @@ -613,7 +613,7 @@ def test_executor_should_delete_incomplete_downloads( def verify_installed_distribution( venv: VirtualEnv, package: Package, url_reference: dict[str, Any] | None = None -): +) -> None: distributions = list(venv.site_packages.distributions(name=package.name)) assert len(distributions) == 1 @@ -660,7 +660,7 @@ def test_executor_should_not_write_pep610_url_references_for_cached_package( pool: RepositoryPool, config: Config, io: BufferedIO, -): +) -> None: link_cached = fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" package.files = [ { @@ -685,7 +685,7 @@ def test_executor_should_write_pep610_url_references_for_wheel_files( config: Config, io: BufferedIO, fixture_dir: FixtureDirGetter, -): +) -> None: url = (fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl").resolve() package = Package("demo", "0.1.0", source_type="file", source_url=url.as_posix()) # Set package.files so the executor will attempt to hash the package @@ -718,7 +718,7 @@ def test_executor_should_write_pep610_url_references_for_non_wheel_files( config: Config, io: BufferedIO, fixture_dir: FixtureDirGetter, -): +) -> None: url = (fixture_dir("distributions") / "demo-0.1.0.tar.gz").resolve() package = Package("demo", "0.1.0", source_type="file", source_url=url.as_posix()) # Set package.files so the executor will attempt to hash the package @@ -754,7 +754,7 @@ def test_executor_should_write_pep610_url_references_for_directories( wheel: Path, fixture_dir: FixtureDirGetter, mocker: MockerFixture, -): +) -> None: url = (fixture_dir("git") / "github.com" / "demo" / "demo").resolve() package = Package( "demo", "0.1.2", source_type="directory", source_url=url.as_posix() @@ -782,7 +782,7 @@ def test_executor_should_write_pep610_url_references_for_editable_directories( wheel: Path, fixture_dir: FixtureDirGetter, mocker: MockerFixture, -): +) -> None: url = (fixture_dir("git") / "github.com" / "demo" / "demo").resolve() package = Package( "demo", @@ -815,7 +815,7 @@ def test_executor_should_write_pep610_url_references_for_wheel_urls( mocker: MockerFixture, fixture_dir: FixtureDirGetter, is_artifact_cached: bool, -): +) -> None: if is_artifact_cached: link_cached = fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" mocker.patch( @@ -887,7 +887,7 @@ def test_executor_should_write_pep610_url_references_for_non_wheel_urls( is_wheel_cached: bool, expect_artifact_building: bool, expect_artifact_download: bool, -): +) -> None: built_wheel = fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" mock_prepare = mocker.patch( "poetry.installation.chef.Chef._prepare", @@ -899,7 +899,9 @@ def test_executor_should_write_pep610_url_references_for_non_wheel_urls( cached_sdist = fixture_dir("distributions") / "demo-0.1.0.tar.gz" cached_wheel = fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" - def mock_get_cached_archive_for_link_func(_: Link, *, strict: bool, **__: Any): + def mock_get_cached_archive_for_link_func( + _: Link, *, strict: bool, **__: Any + ) -> None: if is_wheel_cached and not strict: return cached_wheel if is_sdist_cached: @@ -966,7 +968,7 @@ def test_executor_should_write_pep610_url_references_for_git( mocker: MockerFixture, fixture_dir: FixtureDirGetter, is_artifact_cached: bool, -): +) -> None: if is_artifact_cached: link_cached = fixture_dir("distributions") / "demo-0.1.2-py2.py3-none-any.whl" mocker.patch( @@ -1029,7 +1031,7 @@ def test_executor_should_write_pep610_url_references_for_editable_git( wheel: Path, mocker: MockerFixture, fixture_dir: FixtureDirGetter, -): +) -> None: source_resolved_reference = "123456" source_url = "https://github.com/demo/demo.git" @@ -1106,7 +1108,7 @@ def test_executor_should_write_pep610_url_references_for_git_with_subdirectories io: BufferedIO, mock_file_downloads: None, wheel: Path, -): +) -> None: package = Package( "demo", "0.1.2", @@ -1159,7 +1161,7 @@ def test_executor_should_be_initialized_with_correct_workers( cpu_count: int | None, side_effect: Exception | None, expected_workers: int, -): +) -> None: config.merge({"installer": {"max-workers": max_workers}}) mocker.patch("os.cpu_count", return_value=cpu_count, side_effect=side_effect) @@ -1178,7 +1180,7 @@ def test_executor_fallback_on_poetry_create_error_without_wheel_installer( mock_file_downloads: None, env: MockEnv, fixture_dir: FixtureDirGetter, -): +) -> None: mock_pip_install = mocker.patch("poetry.installation.executor.pip_install") mock_sdist_builder = mocker.patch("poetry.core.masonry.builders.sdist.SdistBuilder") mock_editable_builder = mocker.patch( diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 4d6e975b45a..39e07fb5b06 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -76,12 +76,14 @@ def updates(self) -> list[DependencyPackage]: def removals(self) -> list[DependencyPackage]: return self._uninstalls - def _do_execute_operation(self, operation: Operation) -> None: - super()._do_execute_operation(operation) + def _do_execute_operation(self, operation: Operation) -> int: + ret_val = super()._do_execute_operation(operation) if not operation.skipped: getattr(self, f"_{operation.job_type}s").append(operation.package) + return ret_val + def _execute_install(self, operation: Operation) -> int: return 0 @@ -109,7 +111,7 @@ def __init__(self, lock_path: Path) -> None: self._content_hash = self._get_content_hash() @property - def written_data(self) -> dict | None: + def written_data(self) -> dict[str, Any] | None: return self._written_data def set_lock_path(self, lock: Path) -> Locker: @@ -122,7 +124,7 @@ def locked(self, is_locked: bool = True) -> Locker: return self - def mock_lock_data(self, data: dict) -> None: + def mock_lock_data(self, data: dict[str, Any]) -> None: self._lock_data = data def is_locked(self) -> bool: @@ -134,7 +136,7 @@ def is_fresh(self) -> bool: def _get_content_hash(self) -> str: return "123456789" - def _write_lock_data(self, data: dict) -> None: + def _write_lock_data(self, data: dict[str, Any]) -> None: for package in data["package"]: python_versions = str(package["python-versions"]) package["python-versions"] = python_versions @@ -201,13 +203,14 @@ def installer( return installer -def fixture(name: str) -> dict: +def fixture(name: str) -> dict[str, Any]: file = TOMLFile(Path(__file__).parent / "fixtures" / f"{name}.test") + content: dict[str, Any] = file.read() - return json.loads(json.dumps(file.read())) + return content -def test_run_no_dependencies(installer: Installer, locker: Locker): +def test_run_no_dependencies(installer: Installer, locker: Locker) -> None: result = installer.run() assert result == 0 @@ -217,7 +220,7 @@ def test_run_no_dependencies(installer: Installer, locker: Locker): def test_run_with_dependencies( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -): +) -> None: package_a = get_package("A", "1.0") package_b = get_package("B", "1.1") repo.add_package(package_a) @@ -239,7 +242,7 @@ def test_run_update_after_removing_dependencies( repo: Repository, package: ProjectPackage, installed: CustomInstalledRepository, -): +) -> None: locker.locked(True) locker.mock_lock_data( { @@ -403,7 +406,7 @@ def test_run_install_with_dependency_groups( repo: Repository, package: ProjectPackage, installed: CustomInstalledRepository, -): +) -> None: _configure_run_install_dev( locker, repo, @@ -431,7 +434,7 @@ def test_run_install_does_not_remove_locked_packages_if_installed_but_not_requir repo: Repository, package: ProjectPackage, installed: CustomInstalledRepository, -): +) -> None: package_a = get_package("a", "1.0") package_b = get_package("b", "1.1") package_c = get_package("c", "1.2") @@ -502,7 +505,7 @@ def test_run_install_removes_locked_packages_if_installed_and_synchronization_is repo: Repository, package: ProjectPackage, installed: CustomInstalledRepository, -): +) -> None: package_a = get_package("a", "1.0") package_b = get_package("b", "1.1") package_c = get_package("c", "1.2") @@ -573,7 +576,7 @@ def test_run_install_removes_no_longer_locked_packages_if_installed( repo: Repository, package: ProjectPackage, installed: CustomInstalledRepository, -): +) -> None: package_a = get_package("a", "1.0") package_b = get_package("b", "1.1") package_c = get_package("c", "1.2") @@ -655,7 +658,7 @@ def test_run_install_with_synchronization( repo: Repository, package: ProjectPackage, installed: CustomInstalledRepository, -): +) -> None: package_a = get_package("a", "1.0") package_b = get_package("b", "1.1") package_c = get_package("c", "1.2") @@ -728,7 +731,7 @@ def test_run_install_with_synchronization( def test_run_whitelist_add( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -): +) -> None: locker.locked(True) locker.mock_lock_data( { @@ -777,7 +780,7 @@ def test_run_whitelist_remove( repo: Repository, package: ProjectPackage, installed: CustomInstalledRepository, -): +) -> None: locker.locked(True) locker.mock_lock_data( { @@ -832,7 +835,7 @@ def test_run_whitelist_remove( def test_add_with_sub_dependencies( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -): +) -> None: package_a = get_package("A", "1.0") package_b = get_package("B", "1.1") package_c = get_package("C", "1.2") @@ -857,7 +860,7 @@ def test_add_with_sub_dependencies( def test_run_with_python_versions( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -): +) -> None: package.python_versions = "~2.7 || ^3.4" package_a = get_package("A", "1.0") @@ -885,7 +888,7 @@ def test_run_with_python_versions( def test_run_with_optional_and_python_restricted_dependencies( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -): +) -> None: package.python_versions = "~2.7 || ^3.4" package_a = get_package("A", "1.0") @@ -932,7 +935,7 @@ def test_run_with_optional_and_platform_restricted_dependencies( repo: Repository, package: ProjectPackage, mocker: MockerFixture, -): +) -> None: mocker.patch("sys.platform", "darwin") package_a = get_package("A", "1.0") @@ -975,7 +978,7 @@ def test_run_with_optional_and_platform_restricted_dependencies( def test_run_with_dependencies_extras( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -): +) -> None: package_a = get_package("A", "1.0") package_b = get_package("B", "1.0") package_c = get_package("C", "1.0") @@ -1003,7 +1006,7 @@ def test_run_with_dependencies_extras( def test_run_with_dependencies_nested_extras( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -): +) -> None: package_a = get_package("A", "1.0") package_b = get_package("B", "1.0") package_c = get_package("C", "1.0") @@ -1035,7 +1038,7 @@ def test_run_with_dependencies_nested_extras( def test_run_does_not_install_extras_if_not_requested( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -): +) -> None: package.extras["foo"] = [get_dependency("D")] package_a = get_package("A", "1.0") package_b = get_package("B", "1.0") @@ -1067,7 +1070,7 @@ def test_run_does_not_install_extras_if_not_requested( def test_run_installs_extras_if_requested( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -): +) -> None: package.extras["foo"] = [get_dependency("D")] package_a = get_package("A", "1.0") package_b = get_package("B", "1.0") @@ -1100,7 +1103,7 @@ def test_run_installs_extras_if_requested( def test_run_installs_extras_with_deps_if_requested( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -): +) -> None: package.extras["foo"] = [get_dependency("C")] package_a = get_package("A", "1.0") package_b = get_package("B", "1.0") @@ -1135,7 +1138,7 @@ def test_run_installs_extras_with_deps_if_requested( def test_run_installs_extras_with_deps_if_requested_locked( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -): +) -> None: locker.locked(True) locker.mock_lock_data(fixture("extras-with-dependencies")) package.extras["foo"] = [get_dependency("C")] @@ -1170,7 +1173,7 @@ def test_installer_with_pypi_repository( installed: CustomInstalledRepository, config: Config, env: NullEnv, -): +) -> None: pool = RepositoryPool() pool.add_repository(MockRepository()) @@ -1194,7 +1197,7 @@ def test_run_installs_with_local_file( repo: Repository, package: ProjectPackage, fixture_dir: FixtureDirGetter, -): +) -> None: root_dir = Path(__file__).parent.parent.parent package.root_dir = root_dir locker.set_lock_path(root_dir) @@ -1222,7 +1225,7 @@ def test_run_installs_wheel_with_no_requires_dist( repo: Repository, package: ProjectPackage, fixture_dir: FixtureDirGetter, -): +) -> None: root_dir = Path(__file__).parent.parent.parent package.root_dir = root_dir locker.set_lock_path(root_dir) @@ -1252,7 +1255,7 @@ def test_run_installs_with_local_poetry_directory_and_extras( package: ProjectPackage, tmpdir: Path, fixture_dir: FixtureDirGetter, -): +) -> None: root_dir = Path(__file__).parent.parent.parent package.root_dir = root_dir locker.set_lock_path(root_dir) @@ -1284,7 +1287,7 @@ def test_run_installs_with_local_poetry_directory_and_skip_directory_flag( package: ProjectPackage, fixture_dir: FixtureDirGetter, skip_directory: bool, -): +) -> None: """When we set Installer.skip_directory(True) no path dependencies should be installed (including transitive dependencies). """ @@ -1333,7 +1336,7 @@ def test_run_installs_with_local_poetry_file_transitive( package: ProjectPackage, tmpdir: str, fixture_dir: FixtureDirGetter, -): +) -> None: root_dir = fixture_dir("directory") package.root_dir = root_dir locker.set_lock_path(root_dir) @@ -1368,7 +1371,7 @@ def test_run_installs_with_local_setuptools_directory( package: ProjectPackage, tmpdir: Path, fixture_dir: FixtureDirGetter, -): +) -> None: root_dir = Path(__file__).parent.parent.parent package.root_dir = root_dir locker.set_lock_path(root_dir) @@ -1395,7 +1398,7 @@ def test_run_installs_with_local_setuptools_directory( def test_run_with_prereleases( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -): +) -> None: locker.locked(True) locker.mock_lock_data( { @@ -1487,7 +1490,7 @@ def test_run_changes_category_if_needed( def test_run_update_all_with_lock( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -): +) -> None: locker.locked(True) locker.mock_lock_data( { @@ -1527,7 +1530,7 @@ def test_run_update_all_with_lock( def test_run_update_with_locked_extras( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -): +) -> None: locker.locked(True) locker.mock_lock_data( { @@ -1601,7 +1604,7 @@ def test_run_update_with_locked_extras( def test_run_install_duplicate_dependencies_different_constraints( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -): +) -> None: package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.0") @@ -1645,7 +1648,7 @@ def test_run_install_duplicate_dependencies_different_constraints( def test_run_install_duplicate_dependencies_different_constraints_with_lock( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -): +) -> None: locker.locked(True) locker.mock_lock_data( { @@ -1757,7 +1760,7 @@ def test_run_update_uninstalls_after_removal_transient_dependency( repo: Repository, package: ProjectPackage, installed: CustomInstalledRepository, -): +) -> None: locker.locked(True) locker.mock_lock_data( { @@ -1820,7 +1823,7 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda repo: Repository, package: ProjectPackage, installed: CustomInstalledRepository, -): +) -> None: locker.locked(True) locker.mock_lock_data( { @@ -1933,7 +1936,7 @@ def test_installer_test_solver_finds_compatible_package_for_dependency_python_no repo: Repository, package: ProjectPackage, installed: CustomInstalledRepository, -): +) -> None: package.python_versions = "~2.7 || ^3.4" package.add_dependency( Factory.create_dependency("A", {"version": "^1.0", "python": "^3.5"}) @@ -1965,7 +1968,7 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de env: NullEnv, pool: RepositoryPool, config: Config, -): +) -> None: package.add_dependency(Factory.create_dependency("A", {"version": "^1.0"})) package_a = get_package("A", "1.0.0") @@ -2031,7 +2034,7 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de env: NullEnv, mocker: MockerFixture, config: Config, -): +) -> None: mocker.patch("sys.platform", "darwin") pool = RepositoryPool() @@ -2093,7 +2096,7 @@ def test_installer_required_extras_should_be_installed( installed: CustomInstalledRepository, env: NullEnv, config: Config, -): +) -> None: pool = RepositoryPool() pool.add_repository(MockRepository()) @@ -2145,7 +2148,7 @@ def test_installer_required_extras_should_be_installed( def test_update_multiple_times_with_split_dependencies_is_idempotent( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -): +) -> None: locker.locked(True) locker.mock_lock_data( { @@ -2231,7 +2234,7 @@ def test_installer_can_install_dependencies_from_forced_source( installed: CustomInstalledRepository, env: NullEnv, config: Config, -): +) -> None: package.python_versions = "^3.7" package.add_dependency( Factory.create_dependency("tomlkit", {"version": "^0.5", "source": "legacy"}) @@ -2262,7 +2265,7 @@ def test_installer_can_install_dependencies_from_forced_source( def test_run_installs_with_url_file( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -): +) -> None: url = "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl" package.add_dependency(Factory.create_dependency("demo", {"url": url})) @@ -2330,7 +2333,7 @@ def test_run_installs_with_same_version_url_files( def test_installer_uses_prereleases_if_they_are_compatible( installer: Installer, locker: Locker, package: ProjectPackage, repo: Repository -): +) -> None: package.python_versions = "~2.7 || ^3.4" package.add_dependency( Factory.create_dependency( @@ -2366,7 +2369,7 @@ def test_installer_can_handle_old_lock_files( repo: Repository, installed: CustomInstalledRepository, config: Config, -): +) -> None: pool = RepositoryPool() pool.add_repository(MockRepository()) @@ -2439,7 +2442,7 @@ def test_installer_does_not_write_lock_file_when_installation_fails( repo: Repository, package: ProjectPackage, mocker: MockerFixture, -): +) -> None: repo.add_package(get_package("A", "1.0")) package.add_dependency(Factory.create_dependency("A", "~1.0")) @@ -2463,7 +2466,7 @@ def test_run_with_dependencies_quiet( repo: Repository, package: ProjectPackage, quiet: bool, -): +) -> None: package_a = get_package("A", "1.0") package_b = get_package("B", "1.1") repo.add_package(package_a) @@ -2490,7 +2493,7 @@ def test_run_with_dependencies_quiet( def test_installer_should_use_the_locked_version_of_git_dependencies( installer: Installer, locker: Locker, package: ProjectPackage, repo: Repository -): +) -> None: locker.locked(True) locker.mock_lock_data( { @@ -2559,7 +2562,7 @@ def test_installer_should_use_the_locked_version_of_git_dependencies_with_extras package: ProjectPackage, repo: Repository, is_locked: bool, -): +) -> None: if is_locked: locker.locked(True) locker.mock_lock_data(fixture("with-vcs-dependency-with-extras")) @@ -2602,7 +2605,7 @@ def test_installer_should_use_the_locked_version_of_git_dependencies_without_ref package: ProjectPackage, repo: Repository, is_locked: bool, -): +) -> None: """ If there is no explicit reference (branch or tag or rev) in pyproject.toml, HEAD is used. @@ -2644,7 +2647,7 @@ def test_installer_distinguishes_locked_packages_by_source( repo: Repository, package: ProjectPackage, env_platform: str, -): +) -> None: # Require 1.11.0+cpu from pytorch for most platforms, but specify 1.11.0 and pypi on # darwin. package.add_dependency( diff --git a/tests/integration/test_utils_vcs_git.py b/tests/integration/test_utils_vcs_git.py index ae979edf759..afbc256bd6b 100644 --- a/tests/integration/test_utils_vcs_git.py +++ b/tests/integration/test_utils_vcs_git.py @@ -112,7 +112,7 @@ def remote_default_branch(remote_default_ref: bytes) -> str: # Regression test for https://github.com/python-poetry/poetry/issues/6722 -def test_use_system_git_client_from_environment_variables(): +def test_use_system_git_client_from_environment_variables() -> None: os.environ["POETRY_EXPERIMENTAL_SYSTEM_GIT_CLIENT"] = "true" assert Git.is_using_legacy_client() @@ -132,7 +132,7 @@ def test_git_clone_default_branch_head( remote_refs: FetchPackResult, remote_default_ref: bytes, mocker: MockerFixture, -): +) -> None: spy = mocker.spy(Git, "_clone") spy_legacy = mocker.spy(Git, "_clone_legacy") @@ -143,7 +143,7 @@ def test_git_clone_default_branch_head( spy.assert_called() -def test_git_clone_fails_for_non_existent_branch(source_url: str): +def test_git_clone_fails_for_non_existent_branch(source_url: str) -> None: branch = uuid.uuid4().hex with pytest.raises(PoetryConsoleError) as e: @@ -152,7 +152,7 @@ def test_git_clone_fails_for_non_existent_branch(source_url: str): assert f"Failed to clone {source_url} at '{branch}'" in str(e.value) -def test_git_clone_fails_for_non_existent_revision(source_url: str): +def test_git_clone_fails_for_non_existent_revision(source_url: str) -> None: revision = sha1(uuid.uuid4().bytes).hexdigest() with pytest.raises(PoetryConsoleError) as e: diff --git a/tests/plugins/test_plugin_manager.py b/tests/plugins/test_plugin_manager.py index fb2ce6c94f7..05ebd7aa5b8 100644 --- a/tests/plugins/test_plugin_manager.py +++ b/tests/plugins/test_plugin_manager.py @@ -1,5 +1,6 @@ from __future__ import annotations +from pathlib import Path from typing import TYPE_CHECKING import pytest @@ -17,6 +18,7 @@ if TYPE_CHECKING: + from cleo.io.io import IO from pytest_mock import MockerFixture from tests.conftest import Config @@ -29,9 +31,9 @@ def __call__(self, group: str = Plugin.group) -> PluginManager: class MyPlugin(Plugin): - def activate(self, poetry: Poetry, io: BufferedIO) -> None: + def activate(self, poetry: Poetry, io: IO) -> None: io.write_line("Setting readmes") - poetry.package.readmes = ("README.md",) + poetry.package.readmes = (Path("README.md"),) class MyCommandPlugin(ApplicationPlugin): @@ -39,7 +41,7 @@ class MyCommandPlugin(ApplicationPlugin): class InvalidPlugin: - def activate(self, poetry: Poetry, io: BufferedIO) -> None: + def activate(self, poetry: Poetry, io: IO) -> None: io.write_line("Updating version") poetry.package.version = "9.9.9" @@ -59,7 +61,7 @@ def poetry(fixture_dir: FixtureDirGetter, config: Config) -> Poetry: @pytest.fixture() -def io() -> BufferedIO: +def io() -> IO: return BufferedIO() @@ -86,7 +88,7 @@ def test_load_plugins_and_activate( manager.load_plugins() manager.activate(poetry, io) - assert poetry.package.readmes == ("README.md",) + assert poetry.package.readmes == (Path("README.md"),) assert io.fetch_output() == "Setting readmes\n" diff --git a/tests/puzzle/test_provider.py b/tests/puzzle/test_provider.py index 560ae872f05..1264321b9d0 100644 --- a/tests/puzzle/test_provider.py +++ b/tests/puzzle/test_provider.py @@ -2,6 +2,7 @@ from subprocess import CalledProcessError from typing import TYPE_CHECKING +from typing import Any import pytest @@ -35,7 +36,7 @@ class MockEnv(BaseMockEnv): - def run(self, bin: str, *args: str) -> None: + def run(self, bin: str, *args: str, **kwargs: Any) -> str | int: raise EnvCommandError(CalledProcessError(1, "python", output="")) diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 10e67c9ab6e..9b80652fd68 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -3410,7 +3410,7 @@ def test_solver_should_not_update_same_version_packages_if_installed_has_no_sour def test_solver_should_use_the_python_constraint_from_the_environment_if_available( solver: Solver, repo: Repository, package: ProjectPackage -): +) -> None: set_package_python_versions(solver.provider, "~2.7 || ^3.5") package.add_dependency(Factory.create_dependency("A", "^1.0")) diff --git a/tests/repositories/test_installed_repository.py b/tests/repositories/test_installed_repository.py index 592d99a999a..628852708a1 100644 --- a/tests/repositories/test_installed_repository.py +++ b/tests/repositories/test_installed_repository.py @@ -51,10 +51,10 @@ class MockEnv(BaseMockEnv): @property - def paths(self) -> dict[str, Path]: + def paths(self) -> dict[str, str]: return { - "purelib": SITE_PURELIB, - "platlib": SITE_PLATLIB, + "purelib": SITE_PURELIB.as_posix(), + "platlib": SITE_PLATLIB.as_posix(), } @property @@ -96,7 +96,7 @@ def get_package_from_repository( return None -def test_load_successful(repository: InstalledRepository): +def test_load_successful(repository: InstalledRepository) -> None: assert len(repository.packages) == len(INSTALLED_RESULTS) @@ -119,12 +119,12 @@ def test_load_successful_with_invalid_distribution( assert str(invalid_dist_info) in message -def test_load_ensure_isolation(repository: InstalledRepository): +def test_load_ensure_isolation(repository: InstalledRepository) -> None: package = get_package_from_repository("attrs", repository) assert package is None -def test_load_standard_package(repository: InstalledRepository): +def test_load_standard_package(repository: InstalledRepository) -> None: cleo = get_package_from_repository("cleo", repository) assert cleo is not None assert cleo.name == "cleo" @@ -139,7 +139,7 @@ def test_load_standard_package(repository: InstalledRepository): assert foo.version.text == "0.1.0" -def test_load_git_package(repository: InstalledRepository): +def test_load_git_package(repository: InstalledRepository) -> None: pendulum = get_package_from_repository("pendulum", repository) assert pendulum is not None assert pendulum.name == "pendulum" @@ -153,7 +153,7 @@ def test_load_git_package(repository: InstalledRepository): assert pendulum.source_reference == "bb058f6b78b2d28ef5d9a5e759cfa179a1a713d6" -def test_load_git_package_pth(repository: InstalledRepository): +def test_load_git_package_pth(repository: InstalledRepository) -> None: bender = get_package_from_repository("bender", repository) assert bender is not None assert bender.name == "bender" @@ -161,14 +161,14 @@ def test_load_git_package_pth(repository: InstalledRepository): assert bender.source_type == "git" -def test_load_platlib_package(repository: InstalledRepository): +def test_load_platlib_package(repository: InstalledRepository) -> None: lib64 = get_package_from_repository("lib64", repository) assert lib64 is not None assert lib64.name == "lib64" assert lib64.version.text == "2.3.4" -def test_load_editable_package(repository: InstalledRepository): +def test_load_editable_package(repository: InstalledRepository) -> None: # test editable package with text .pth file editable = get_package_from_repository("editable", repository) assert editable is not None @@ -181,7 +181,7 @@ def test_load_editable_package(repository: InstalledRepository): ) -def test_load_editable_with_import_package(repository: InstalledRepository): +def test_load_editable_with_import_package(repository: InstalledRepository) -> None: # test editable package with executable .pth file editable = get_package_from_repository("editable-with-import", repository) assert editable is not None @@ -191,7 +191,7 @@ def test_load_editable_with_import_package(repository: InstalledRepository): assert editable.source_url is None -def test_load_standard_package_with_pth_file(repository: InstalledRepository): +def test_load_standard_package_with_pth_file(repository: InstalledRepository) -> None: # test standard packages with .pth file is not treated as editable standard = get_package_from_repository("standard", repository) assert standard is not None @@ -201,7 +201,7 @@ def test_load_standard_package_with_pth_file(repository: InstalledRepository): assert standard.source_url is None -def test_load_pep_610_compliant_git_packages(repository: InstalledRepository): +def test_load_pep_610_compliant_git_packages(repository: InstalledRepository) -> None: package = get_package_from_repository("git-pep-610", repository) assert package is not None @@ -215,7 +215,7 @@ def test_load_pep_610_compliant_git_packages(repository: InstalledRepository): def test_load_pep_610_compliant_git_packages_no_requested_version( repository: InstalledRepository, -): +) -> None: package = get_package_from_repository( "git-pep-610-no-requested-version", repository ) @@ -234,7 +234,7 @@ def test_load_pep_610_compliant_git_packages_no_requested_version( def test_load_pep_610_compliant_git_packages_with_subdirectory( repository: InstalledRepository, -): +) -> None: package = get_package_from_repository("git-pep-610-subdirectory", repository) assert package is not None assert package.name == "git-pep-610-subdirectory" @@ -246,7 +246,7 @@ def test_load_pep_610_compliant_git_packages_with_subdirectory( assert package.source_subdirectory == "subdir" -def test_load_pep_610_compliant_url_packages(repository: InstalledRepository): +def test_load_pep_610_compliant_url_packages(repository: InstalledRepository) -> None: package = get_package_from_repository("url-pep-610", repository) assert package is not None @@ -259,7 +259,7 @@ def test_load_pep_610_compliant_url_packages(repository: InstalledRepository): ) -def test_load_pep_610_compliant_file_packages(repository: InstalledRepository): +def test_load_pep_610_compliant_file_packages(repository: InstalledRepository) -> None: package = get_package_from_repository("file-pep-610", repository) assert package is not None @@ -269,7 +269,9 @@ def test_load_pep_610_compliant_file_packages(repository: InstalledRepository): assert package.source_url == "/path/to/distributions/file-pep-610-1.2.3.tar.gz" -def test_load_pep_610_compliant_directory_packages(repository: InstalledRepository): +def test_load_pep_610_compliant_directory_packages( + repository: InstalledRepository, +) -> None: package = get_package_from_repository("directory-pep-610", repository) assert package is not None @@ -282,7 +284,7 @@ def test_load_pep_610_compliant_directory_packages(repository: InstalledReposito def test_load_pep_610_compliant_editable_directory_packages( repository: InstalledRepository, -): +) -> None: package = get_package_from_repository("editable-directory-pep-610", repository) assert package is not None diff --git a/tests/utils/test_authenticator.py b/tests/utils/test_authenticator.py index 05c5490ac11..2a0d1bee564 100644 --- a/tests/utils/test_authenticator.py +++ b/tests/utils/test_authenticator.py @@ -42,12 +42,12 @@ def mock_remote(http: type[httpretty.httpretty]) -> None: @pytest.fixture() -def repo(): +def repo() -> dict[str, dict[str, str]]: return {"foo": {"url": "https://foo.bar/simple/"}} @pytest.fixture -def mock_config(config: Config, repo: dict[str, dict[str, str]]): +def mock_config(config: Config, repo: dict[str, dict[str, str]]) -> Config: config.merge( { "repositories": repo, @@ -60,7 +60,7 @@ def mock_config(config: Config, repo: dict[str, dict[str, str]]): def test_authenticator_uses_url_provided_credentials( mock_config: Config, mock_remote: None, http: type[httpretty.httpretty] -): +) -> None: authenticator = Authenticator(mock_config, NullIO()) authenticator.request("get", "https://foo001:bar002@foo.bar/files/foo-0.1.0.tar.gz") @@ -71,7 +71,7 @@ def test_authenticator_uses_url_provided_credentials( def test_authenticator_uses_credentials_from_config_if_not_provided( mock_config: Config, mock_remote: None, http: type[httpretty.httpretty] -): +) -> None: authenticator = Authenticator(mock_config, NullIO()) authenticator.request("get", "https://foo.bar/files/foo-0.1.0.tar.gz") @@ -85,7 +85,7 @@ def test_authenticator_uses_username_only_credentials( mock_remote: None, http: type[httpretty.httpretty], with_simple_keyring: None, -): +) -> None: authenticator = Authenticator(mock_config, NullIO()) authenticator.request("get", "https://foo001@foo.bar/files/foo-0.1.0.tar.gz") @@ -96,7 +96,7 @@ def test_authenticator_uses_username_only_credentials( def test_authenticator_uses_password_only_credentials( mock_config: Config, mock_remote: None, http: type[httpretty.httpretty] -): +) -> None: authenticator = Authenticator(mock_config, NullIO()) authenticator.request("get", "https://:bar002@foo.bar/files/foo-0.1.0.tar.gz") @@ -111,7 +111,7 @@ def test_authenticator_uses_empty_strings_as_default_password( repo: dict[str, dict[str, str]], http: type[httpretty.httpretty], with_simple_keyring: None, -): +) -> None: config.merge( { "repositories": repo, @@ -132,7 +132,7 @@ def test_authenticator_uses_empty_strings_as_default_username( mock_remote: None, repo: dict[str, dict[str, str]], http: type[httpretty.httpretty], -): +) -> None: config.merge( { "repositories": repo, @@ -155,7 +155,7 @@ def test_authenticator_falls_back_to_keyring_url( http: type[httpretty.httpretty], with_simple_keyring: None, dummy_keyring: DummyBackend, -): +) -> None: config.merge( { "repositories": repo, @@ -163,7 +163,7 @@ def test_authenticator_falls_back_to_keyring_url( ) dummy_keyring.set_password( - "https://foo.bar/simple/", None, SimpleCredential(None, "bar") + "https://foo.bar/simple/", None, SimpleCredential("foo", "bar") ) authenticator = Authenticator(config, NullIO()) @@ -171,7 +171,7 @@ def test_authenticator_falls_back_to_keyring_url( request = http.last_request() - assert request.headers["Authorization"] == "Basic OmJhcg==" + assert request.headers["Authorization"] == "Basic Zm9vOmJhcg==" def test_authenticator_falls_back_to_keyring_netloc( @@ -181,35 +181,35 @@ def test_authenticator_falls_back_to_keyring_netloc( http: type[httpretty.httpretty], with_simple_keyring: None, dummy_keyring: DummyBackend, -): +) -> None: config.merge( { "repositories": repo, } ) - dummy_keyring.set_password("foo.bar", None, SimpleCredential(None, "bar")) + dummy_keyring.set_password("foo.bar", None, SimpleCredential("foo", "bar")) authenticator = Authenticator(config, NullIO()) authenticator.request("get", "https://foo.bar/files/foo-0.1.0.tar.gz") request = http.last_request() - assert request.headers["Authorization"] == "Basic OmJhcg==" + assert request.headers["Authorization"] == "Basic Zm9vOmJhcg==" @pytest.mark.filterwarnings("ignore::pytest.PytestUnhandledThreadExceptionWarning") def test_authenticator_request_retries_on_exception( mocker: MockerFixture, config: Config, http: type[httpretty.httpretty] -): +) -> None: sleep = mocker.patch("time.sleep") sdist_uri = f"https://foo.bar/files/{uuid.uuid4()!s}/foo-0.1.0.tar.gz" content = str(uuid.uuid4()) - seen = [] + seen: list[str] = [] def callback( - request: requests.Request, uri: str, response_headers: dict - ) -> list[int | dict | str]: + request: requests.Request, uri: str, response_headers: dict[str, str] + ) -> list[int | dict[str, str] | str]: if seen.count(uri) < 2: seen.append(uri) raise requests.exceptions.ConnectionError("Disconnected") @@ -226,7 +226,7 @@ def callback( @pytest.mark.filterwarnings("ignore::pytest.PytestUnhandledThreadExceptionWarning") def test_authenticator_request_raises_exception_when_attempts_exhausted( mocker: MockerFixture, config: Config, http: type[httpretty.httpretty] -): +) -> None: sleep = mocker.patch("time.sleep") sdist_uri = f"https://foo.bar/files/{uuid.uuid4()!s}/foo-0.1.0.tar.gz" @@ -246,15 +246,15 @@ def test_authenticator_request_respects_retry_header( mocker: MockerFixture, config: Config, http: type[httpretty.httpretty], -): +) -> None: sleep = mocker.patch("time.sleep") sdist_uri = f"https://foo.bar/files/{uuid.uuid4()!s}/foo-0.1.0.tar.gz" content = str(uuid.uuid4()) - seen = [] + seen: list[str] = [] def callback( - request: requests.Request, uri: str, response_headers: dict - ) -> list[int | dict | str]: + request: requests.Request, uri: str, response_headers: dict[str, str] + ) -> list[int | dict[str, str] | str]: if not seen.count(uri): seen.append(uri) return [429, {"Retry-After": "42"}, "Retry later"] @@ -290,14 +290,14 @@ def test_authenticator_request_retries_on_status_code( http: type[httpretty.httpretty], status: int, attempts: int, -): +) -> None: sleep = mocker.patch("time.sleep") sdist_uri = f"https://foo.bar/files/{uuid.uuid4()!s}/foo-0.1.0.tar.gz" content = str(uuid.uuid4()) def callback( - request: requests.Request, uri: str, response_headers: dict - ) -> list[int | dict | str]: + request: requests.Request, uri: str, response_headers: dict[str, str] + ) -> list[int | dict[str, str] | str]: return [status, response_headers, content] http.register_uri(httpretty.GET, sdist_uri, body=callback) @@ -319,7 +319,7 @@ def test_authenticator_uses_env_provided_credentials( mock_remote: type[httpretty.httpretty], http: type[httpretty.httpretty], monkeypatch: MonkeyPatch, -): +) -> None: monkeypatch.setenv("POETRY_HTTP_BASIC_FOO_USERNAME", "bar") monkeypatch.setenv("POETRY_HTTP_BASIC_FOO_PASSWORD", "baz") @@ -350,7 +350,7 @@ def test_authenticator_uses_certs_from_config_if_not_provided( mocker: MockerFixture, cert: str | None, client_cert: str | None, -): +) -> None: configured_cert = "/path/to/cert" configured_client_cert = "/path/to/client-cert" @@ -380,7 +380,7 @@ def test_authenticator_uses_certs_from_config_if_not_provided( def test_authenticator_uses_credentials_from_config_matched_by_url_path( config: Config, mock_remote: None, http: type[httpretty.httpretty] -): +) -> None: config.merge( { "repositories": { @@ -413,7 +413,7 @@ def test_authenticator_uses_credentials_from_config_matched_by_url_path( def test_authenticator_uses_credentials_from_config_with_at_sign_in_path( config: Config, mock_remote: None, http: type[httpretty.httpretty] -): +) -> None: config.merge( { "repositories": { @@ -439,7 +439,7 @@ def test_authenticator_falls_back_to_keyring_url_matched_by_path( http: type[httpretty.httpretty], with_simple_keyring: None, dummy_keyring: DummyBackend, -): +) -> None: config.merge( { "repositories": { @@ -450,10 +450,10 @@ def test_authenticator_falls_back_to_keyring_url_matched_by_path( ) dummy_keyring.set_password( - "https://foo.bar/alpha/files/simple/", None, SimpleCredential(None, "bar") + "https://foo.bar/alpha/files/simple/", None, SimpleCredential("foo", "bar") ) dummy_keyring.set_password( - "https://foo.bar/beta/files/simple/", None, SimpleCredential(None, "baz") + "https://foo.bar/beta/files/simple/", None, SimpleCredential("foo", "baz") ) authenticator = Authenticator(config, NullIO()) @@ -461,13 +461,13 @@ def test_authenticator_falls_back_to_keyring_url_matched_by_path( authenticator.request("get", "https://foo.bar/alpha/files/simple/foo-0.1.0.tar.gz") request = http.last_request() - basic_auth = base64.b64encode(b":bar").decode() + basic_auth = base64.b64encode(b"foo:bar").decode() assert request.headers["Authorization"] == f"Basic {basic_auth}" authenticator.request("get", "https://foo.bar/beta/files/simple/foo-0.1.0.tar.gz") request = http.last_request() - basic_auth = base64.b64encode(b":baz").decode() + basic_auth = base64.b64encode(b"foo:baz").decode() assert request.headers["Authorization"] == f"Basic {basic_auth}" @@ -477,7 +477,7 @@ def test_authenticator_uses_env_provided_credentials_matched_by_url_path( mock_remote: type[httpretty.httpretty], http: type[httpretty.httpretty], monkeypatch: MonkeyPatch, -): +) -> None: monkeypatch.setenv("POETRY_HTTP_BASIC_FOO_ALPHA_USERNAME", "bar") monkeypatch.setenv("POETRY_HTTP_BASIC_FOO_ALPHA_PASSWORD", "alpha") monkeypatch.setenv("POETRY_HTTP_BASIC_FOO_BETA_USERNAME", "baz") @@ -513,7 +513,7 @@ def test_authenticator_azure_feed_guid_credentials( http: type[httpretty.httpretty], with_simple_keyring: None, dummy_keyring: DummyBackend, -): +) -> None: config.merge( { "repositories": { @@ -558,7 +558,7 @@ def test_authenticator_add_repository( http: type[httpretty.httpretty], with_simple_keyring: None, dummy_keyring: DummyBackend, -): +) -> None: config.merge( { "http-basic": { @@ -594,7 +594,7 @@ def test_authenticator_git_repositories( http: type[httpretty.httpretty], with_simple_keyring: None, dummy_keyring: DummyBackend, -): +) -> None: config.merge( { "repositories": { diff --git a/tests/utils/test_password_manager.py b/tests/utils/test_password_manager.py index 468ef14f689..d09f88b1fdf 100644 --- a/tests/utils/test_password_manager.py +++ b/tests/utils/test_password_manager.py @@ -20,7 +20,7 @@ def test_set_http_password( config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend -): +) -> None: manager = PasswordManager(config) assert manager.keyring.is_available() @@ -35,13 +35,14 @@ def test_set_http_password( def test_get_http_auth( config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend -): +) -> None: dummy_keyring.set_password("poetry-repository-foo", "bar", "baz") config.auth_config_source.add_property("http-basic.foo", {"username": "bar"}) manager = PasswordManager(config) assert manager.keyring.is_available() auth = manager.get_http_auth("foo") + assert auth is not None assert auth["username"] == "bar" assert auth["password"] == "baz" @@ -49,7 +50,7 @@ def test_get_http_auth( def test_delete_http_password( config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend -): +) -> None: dummy_keyring.set_password("poetry-repository-foo", "bar", "baz") config.auth_config_source.add_property("http-basic.foo", {"username": "bar"}) manager = PasswordManager(config) @@ -63,7 +64,7 @@ def test_delete_http_password( def test_set_pypi_token( config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend -): +) -> None: manager = PasswordManager(config) assert manager.keyring.is_available() @@ -76,7 +77,7 @@ def test_set_pypi_token( def test_get_pypi_token( config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend -): +) -> None: dummy_keyring.set_password("poetry-repository-foo", "__token__", "baz") manager = PasswordManager(config) @@ -86,7 +87,7 @@ def test_get_pypi_token( def test_delete_pypi_token( config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend -): +) -> None: dummy_keyring.set_password("poetry-repository-foo", "__token__", "baz") manager = PasswordManager(config) @@ -98,7 +99,7 @@ def test_delete_pypi_token( def test_set_http_password_with_unavailable_backend( config: Config, with_fail_keyring: None -): +) -> None: manager = PasswordManager(config) assert not manager.keyring.is_available() @@ -111,7 +112,7 @@ def test_set_http_password_with_unavailable_backend( def test_get_http_auth_with_unavailable_backend( config: Config, with_fail_keyring: None -): +) -> None: config.auth_config_source.add_property( "http-basic.foo", {"username": "bar", "password": "baz"} ) @@ -119,6 +120,7 @@ def test_get_http_auth_with_unavailable_backend( assert not manager.keyring.is_available() auth = manager.get_http_auth("foo") + assert auth is not None assert auth["username"] == "bar" assert auth["password"] == "baz" @@ -126,7 +128,7 @@ def test_get_http_auth_with_unavailable_backend( def test_delete_http_password_with_unavailable_backend( config: Config, with_fail_keyring: None -): +) -> None: config.auth_config_source.add_property( "http-basic.foo", {"username": "bar", "password": "baz"} ) @@ -140,7 +142,7 @@ def test_delete_http_password_with_unavailable_backend( def test_set_pypi_token_with_unavailable_backend( config: Config, with_fail_keyring: None -): +) -> None: manager = PasswordManager(config) assert not manager.keyring.is_available() @@ -151,7 +153,7 @@ def test_set_pypi_token_with_unavailable_backend( def test_get_pypi_token_with_unavailable_backend( config: Config, with_fail_keyring: None -): +) -> None: config.auth_config_source.add_property("pypi-token.foo", "baz") manager = PasswordManager(config) @@ -161,7 +163,7 @@ def test_get_pypi_token_with_unavailable_backend( def test_delete_pypi_token_with_unavailable_backend( config: Config, with_fail_keyring: None -): +) -> None: config.auth_config_source.add_property("pypi-token.foo", "baz") manager = PasswordManager(config) @@ -173,7 +175,7 @@ def test_delete_pypi_token_with_unavailable_backend( def test_keyring_raises_errors_on_keyring_errors( mocker: MockerFixture, with_fail_keyring: None -): +) -> None: mocker.patch("poetry.utils.password_manager.PoetryKeyring._check") key_ring = PoetryKeyring("poetry") @@ -189,7 +191,7 @@ def test_keyring_raises_errors_on_keyring_errors( def test_keyring_with_chainer_backend_and_fail_keyring_should_be_unavailable( with_chained_fail_keyring: None, -): +) -> None: key_ring = PoetryKeyring("poetry") assert not key_ring.is_available() @@ -197,7 +199,7 @@ def test_keyring_with_chainer_backend_and_fail_keyring_should_be_unavailable( def test_keyring_with_chainer_backend_and_null_keyring_should_be_unavailable( with_chained_null_keyring: None, -): +) -> None: key_ring = PoetryKeyring("poetry") assert not key_ring.is_available() @@ -205,7 +207,7 @@ def test_keyring_with_chainer_backend_and_null_keyring_should_be_unavailable( def test_null_keyring_should_be_unavailable( with_null_keyring: None, -): +) -> None: key_ring = PoetryKeyring("poetry") assert not key_ring.is_available() @@ -213,7 +215,7 @@ def test_null_keyring_should_be_unavailable( def test_fail_keyring_should_be_unavailable( with_fail_keyring: None, -): +) -> None: key_ring = PoetryKeyring("poetry") assert not key_ring.is_available() @@ -221,13 +223,14 @@ def test_fail_keyring_should_be_unavailable( def test_get_http_auth_from_environment_variables( environ: None, config: Config, with_simple_keyring: None -): +) -> None: os.environ["POETRY_HTTP_BASIC_FOO_USERNAME"] = "bar" os.environ["POETRY_HTTP_BASIC_FOO_PASSWORD"] = "baz" manager = PasswordManager(config) auth = manager.get_http_auth("foo") + assert auth is not None assert auth["username"] == "bar" assert auth["password"] == "baz" @@ -238,7 +241,7 @@ def test_get_pypi_token_with_env_var_positive( config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend, -): +) -> None: sample_token = "sampletoken-1234" repo_name = "foo" manager = PasswordManager(config) @@ -252,7 +255,7 @@ def test_get_pypi_token_with_env_var_positive( def test_get_pypi_token_with_env_var_not_available( config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend -): +) -> None: repo_name = "foo" manager = PasswordManager(config) From b1fcfc02014f306a0b55a99829670de4d67b22a3 Mon Sep 17 00:00:00 2001 From: Martin Miglio Date: Sun, 16 Apr 2023 10:40:01 -0400 Subject: [PATCH 147/151] pre-commit matches pyproject.toml in subdirs (#7242) --- .pre-commit-hooks.yaml | 2 +- docs/pre-commit-hooks.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index d2662b8a420..13154790b87 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -5,7 +5,7 @@ language: python language_version: python3 pass_filenames: false - files: ^pyproject.toml$ + files: ^(.*/)?pyproject.toml$ - id: poetry-lock name: poetry-lock diff --git a/docs/pre-commit-hooks.md b/docs/pre-commit-hooks.md index 5fcbd62ef27..ab833dab3f3 100644 --- a/docs/pre-commit-hooks.md +++ b/docs/pre-commit-hooks.md @@ -34,6 +34,9 @@ to make sure the poetry configuration does not get committed in a broken state. The hook takes the same arguments as the poetry command. For more information see the [check command]({{< relref "cli#check" >}}). +{{% note %}} +If the `pyproject.toml` file is not in the root directory, you can specify `args: ["-C", "./subdirectory"]`. +{{% /note %}} ## poetry-lock From aa33315853099aef9449a2d7ba576da99ab53a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82?= <23004737+rafrafek@users.noreply.github.com> Date: Mon, 17 Apr 2023 12:18:49 +0200 Subject: [PATCH 148/151] Remove `language_version` from pre-commit hooks (#6989) --- .pre-commit-hooks.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 13154790b87..08f733c18e8 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -3,7 +3,6 @@ description: run poetry check to validate config entry: poetry check language: python - language_version: python3 pass_filenames: false files: ^(.*/)?pyproject.toml$ @@ -12,7 +11,6 @@ description: run poetry lock to update lock file entry: poetry lock language: python - language_version: python3 pass_filenames: false - id: poetry-export @@ -20,7 +18,6 @@ description: run poetry export to sync lock file with requirements.txt entry: poetry export language: python - language_version: python3 pass_filenames: false files: ^poetry.lock$ args: ["-f", "requirements.txt", "-o", "requirements.txt"] From 152f01e4cf2b45b9868a97c2c762dd0a7c4aad65 Mon Sep 17 00:00:00 2001 From: Alejandro Angulo <5242883+alejandro-angulo@users.noreply.github.com> Date: Mon, 17 Apr 2023 04:03:17 -0700 Subject: [PATCH 149/151] Added environment variables for configurations (#6711) --- docs/configuration.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index cc477f22609..4277cb1a095 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -149,6 +149,8 @@ You can override the Cache directory by setting the `POETRY_CACHE_DIR` environme **Type**: `string` +**Environment Variable**: `POETRY_CACHE_DIR` + The path to the cache directory used by Poetry. Defaults to one of the following directories: @@ -163,6 +165,8 @@ Defaults to one of the following directories: **Default**: `false` +**Environment Variable**: `POETRY_EXPERIMENTAL_SYSTEM_GIT_CLIENT` + *Introduced in 1.2.0* Use system git client backend for git related tasks. @@ -177,6 +181,8 @@ If you encounter any problems with it, set to `true` to use the system git backe **Default**: `number_of_cores + 4` +**Environment Variable**: `POETRY_INSTALLER_MAX_WORKERS` + *Introduced in 1.2.0* Set the maximum number of workers while using the parallel installer. @@ -209,6 +215,8 @@ you encounter on the [issue tracker](https://github.com/python-poetry/poetry/iss **Default**: `false` +**Environment Variable**: `POETRY_INSTALLER_NO_BINARY` + *Introduced in 1.2.0* When set this configuration allows users to configure package distribution format policy for all or @@ -258,6 +266,8 @@ across all your projects if incorrectly set. **Default**: `true` +**Environment Variable**: `POETRY_INSTALLER_PARALLEL` + *Introduced in 1.1.4* Use parallel execution when using the new (`>=1.1.0`) installer. @@ -268,6 +278,8 @@ Use parallel execution when using the new (`>=1.1.0`) installer. **Default**: `true` +**Environment Variable**: `POETRY_VIRTUALENVS_CREATE` + Create a new virtual environment if one doesn't already exist. If set to `false`, Poetry will not create a new virtual environment. If it detects a virtual environment @@ -294,6 +306,8 @@ might contain additional Python packages as well. **Default**: `None` +**Environment Variable**: `POETRY_VIRTUALENVS_IN_PROJECT` + Create the virtualenv inside the project's root directory. If not set explicitly, `poetry` by default will create virtual environment under @@ -310,6 +324,8 @@ If set to `false`, `poetry` will ignore any existing `.venv` directory. **Default**: `false` +**Environment Variable**: `POETRY_VIRTUALENVS_OPTIONS_ALWAYS_COPY` + *Introduced in 1.2.0* If set to `true` the `--always-copy` parameter is passed to `virtualenv` on creation of the virtual environment, so that @@ -321,6 +337,8 @@ all needed files are copied into it instead of symlinked. **Default**: `false` +**Environment Variable**: `POETRY_VIRTUALENVS_OPTIONS_NO_PIP` + *Introduced in 1.2.0* If set to `true` the `--no-pip` parameter is passed to `virtualenv` on creation of the virtual environment. This means @@ -341,6 +359,8 @@ packages. This is desirable for production environments. **Default**: `false` +**Environment Variable**: `POETRY_VIRTUALENVS_OPTIONS_NO_SETUPTOOLS` + *Introduced in 1.2.0* If set to `true` the `--no-setuptools` parameter is passed to `virtualenv` on creation of the virtual environment. This @@ -358,6 +378,8 @@ available within a virtual environment. This can cause some features in these to **Default**: `false` +**Environment Variable**: `POETRY_VIRTUALENVS_OPTIONS_SYSTEM_SITE_PACKAGES` + Give the virtual environment access to the system site-packages directory. Applies on virtualenv creation. @@ -367,6 +389,8 @@ Applies on virtualenv creation. **Default**: `{cache-dir}/virtualenvs` +**Environment Variable**: `POETRY_VIRTUALENVS_PATH` + Directory where virtual environments will be created. {{% note %}} @@ -379,6 +403,8 @@ This setting controls the global virtual environment storage path. It most likel **Default**: `false` +**Environment Variable**: `POETRY_VIRTUALENVS_PREFER_ACTIVE_PYTHON` + *Introduced in 1.2.0* Use currently activated Python version to create a new virtual environment. @@ -390,6 +416,8 @@ If set to `false`, Python version used during Poetry installation is used. **Default**: `{project_name}-py{python_version}` +**Environment Variable**: `POETRY_VIRTUALENVS_PROMPT` + *Introduced in 1.2.0* Format string defining the prompt to be displayed when the virtual environment is activated. @@ -399,12 +427,16 @@ The variables `project_name` and `python_version` are available for formatting. **Type**: `string` +**Environment Variable**: `POETRY_REPOSITORIES_` + Set a new alternative repository. See [Repositories]({{< relref "repositories" >}}) for more information. ### `http-basic.`: **Type**: `(string, string)` +**Environment Variable**: `POETRY_HTTP_BASIC_` + Set repository credentials (`username` and `password`) for ``. See [Repositories - Configuring credentials]({{< relref "repositories#configuring-credentials" >}}) for more information. @@ -413,6 +445,8 @@ for more information. **Type**: `string` +**Environment Variable**: `POETRY_PYPI_TOKEN_` + Set repository credentials (using an API token) for ``. See [Repositories - Configuring credentials]({{< relref "repositories#configuring-credentials" >}}) for more information. @@ -421,6 +455,8 @@ for more information. **Type**: `string | boolean` +**Environment Variable**: `POETRY_CERTIFICATES__CERT` + Set custom certificate authority for repository ``. See [Repositories - Configuring credentials - Custom certificate authority]({{< relref "repositories#custom-certificate-authority-and-mutual-tls-authentication" >}}) for more information. @@ -432,6 +468,8 @@ repository. **Type**: `string` +**Environment Variable**: `POETRY_CERTIFICATES__CLIENT_CERT` + Set client certificate for repository ``. See [Repositories - Configuring credentials - Custom certificate authority]({{< relref "repositories#custom-certificate-authority-and-mutual-tls-authentication" >}}) for more information. From d699b24b4b2fabe8ed676ffc903cb8b17ab63d25 Mon Sep 17 00:00:00 2001 From: Andrea Ghensi Date: Mon, 17 Apr 2023 13:35:52 +0200 Subject: [PATCH 150/151] Add --executable option to env info command (#7547) --- docs/managing-environments.md | 11 +++++++++++ src/poetry/console/commands/env/info.py | 15 ++++++++++++++- tests/console/commands/env/test_info.py | 6 ++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/docs/managing-environments.md b/docs/managing-environments.md index 1716a2f9d50..a3a0cc37f57 100644 --- a/docs/managing-environments.md +++ b/docs/managing-environments.md @@ -39,6 +39,7 @@ pyenv install 3.9.8 pyenv local 3.9.8 # Activate Python 3.9 for the current project poetry install ``` + {{% /note %}} {{% note %}} @@ -106,6 +107,13 @@ to `env info`: poetry env info --path ``` +If you only want to know the path to the python executable (useful for running mypy from a global environment without installing it in the virtual environment), you can pass the `--executable` option +to `env info`: + +```bash +poetry env info --executable +``` + ## Listing the environments associated with the project You can also list all the virtual environments associated with the current project @@ -140,10 +148,13 @@ poetry env remove test-O3eWbxRl-py3.7 ``` You can delete more than one environment at a time. + ```bash poetry env remove python3.6 python3.7 python3.8 ``` + Use the `--all` option to delete all virtual environments at once. + ```bash poetry env remove --all ``` diff --git a/src/poetry/console/commands/env/info.py b/src/poetry/console/commands/env/info.py index abb78f30cda..2de77f73a6d 100644 --- a/src/poetry/console/commands/env/info.py +++ b/src/poetry/console/commands/env/info.py @@ -15,7 +15,12 @@ class EnvInfoCommand(Command): name = "env info" description = "Displays information about the current environment." - options = [option("path", "p", "Only display the environment's path.")] + options = [ + option("path", "p", "Only display the environment's path."), + option( + "executable", "e", "Only display the environment's python executable path." + ), + ] def handle(self) -> int: from poetry.utils.env import EnvManager @@ -30,6 +35,14 @@ def handle(self) -> int: return 0 + if self.option("executable"): + if not env.is_venv(): + return 1 + + self.line(str(env.python)) + + return 0 + self._display_complete_info(env) return 0 diff --git a/tests/console/commands/env/test_info.py b/tests/console/commands/env/test_info.py index e4f5826e49c..bdc8a0eb57b 100644 --- a/tests/console/commands/env/test_info.py +++ b/tests/console/commands/env/test_info.py @@ -58,3 +58,9 @@ def test_env_info_displays_path_only(tester: CommandTester): tester.execute("--path") expected = str(Path("/prefix")) + "\n" assert tester.io.fetch_output() == expected + + +def test_env_info_displays_executable_only(tester: CommandTester): + tester.execute("--executable") + expected = str(sys.executable) + "\n" + assert tester.io.fetch_output() == expected From ec35b837bae2ded6e42a9e96eb2798445b95dcfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Tue, 18 Apr 2023 12:29:47 +0200 Subject: [PATCH 151/151] chore: update poetry-plugin-export (#7806) --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 36bd49a456a..4cf7f64935c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. [[package]] name = "attrs" @@ -1164,14 +1164,14 @@ importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} [[package]] name = "poetry-plugin-export" -version = "1.3.0" +version = "1.3.1" description = "Poetry plugin to export the dependencies to various formats" category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "poetry_plugin_export-1.3.0-py3-none-any.whl", hash = "sha256:6e5919bf84afcb08cdd419a03f909f490d8671f00633a3c6df8ba09b0820dc2f"}, - {file = "poetry_plugin_export-1.3.0.tar.gz", hash = "sha256:61ae5ec1db233aba947a48e1ce54c6ff66afd0e1c87195d6bce64c73a5ae658c"}, + {file = "poetry_plugin_export-1.3.1-py3-none-any.whl", hash = "sha256:941d7ba02a59671d6327b16dc6deecc9262477abbc120d728a500cf125bc1e06"}, + {file = "poetry_plugin_export-1.3.1.tar.gz", hash = "sha256:d949742757a8a5f0b5810495bffaf4ed8a767f2e2ffda9887cf72f896deabf84"}, ] [package.dependencies] @@ -1983,4 +1983,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "fb909b5c273da18b6715b134312d9a97edfa8dbfc2c7807fde3ace3d179c21ff" +content-hash = "60024b1508baf21776d5214a4af23c8a1ad180be8e2197df91971881a4be4824" diff --git a/pyproject.toml b/pyproject.toml index 63e65101815..ae1a50bee98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ generate-setup-file = false python = "^3.7" poetry-core = "1.5.2" -poetry-plugin-export = "^1.3.0" +poetry-plugin-export = "^1.3.1" "backports.cached-property" = { version = "^1.0.2", python = "<3.8" } build = "^0.10.0" cachecontrol = { version = "^0.12.9", extras = ["filecache"] }