From d673342816d71b22d7006730d34a9026fce38944 Mon Sep 17 00:00:00 2001 From: Nicklas Tegner Date: Thu, 1 Oct 2020 21:35:51 +0200 Subject: [PATCH 001/222] fix #1969 (#3027) * fix #1969 * Added newline --- docs/docs/configuration.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index 87c5232f4be..0378f2fca9e 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -107,6 +107,8 @@ Defaults to one of the following directories: Create a new virtual environment if one doesn't already exist. Defaults to `true`. +If set to `false`, poetry will install dependencies into the current python environment. + !!!note: When setting this configuration to `false`, the Python environment used must have `pip` installed and available. @@ -116,7 +118,7 @@ Defaults to `true`. Create the virtualenv inside the project's root directory. Defaults to `None`. -If set to `true`, the virtualenv wil be created and expected in a folder named `.venv` within the root directory of the project. +If set to `true`, the virtualenv will be created and expected in a folder named `.venv` within the root directory of the project. If not set explicitly (default), `poetry` will use the virtualenv from the `.venv` directory when one is available. If set to `false`, `poetry` will ignore any existing `.venv` directory. From 7a5f26010dd9924acaaac65dfa7616872b30a422 Mon Sep 17 00:00:00 2001 From: Remi Rampin Date: Thu, 1 Oct 2020 14:29:22 -0400 Subject: [PATCH 002/222] Don't swallow ImportError in temporary_directory() Fixes #3026 If the context wrapped by the temporary_directory() context manager raised ImportError (for example because distutils.util cannot be imported, #721 #1837), it would previously keep going, causing a RuntimeError from contextlib: RuntimeError: generator didn't stop after throw() --- get-poetry.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/get-poetry.py b/get-poetry.py index 0df27239a9b..c14c9d4d6ec 100644 --- a/get-poetry.py +++ b/get-poetry.py @@ -159,15 +159,15 @@ def colorize(style, text): def temporary_directory(*args, **kwargs): try: from tempfile import TemporaryDirectory - - with TemporaryDirectory(*args, **kwargs) as name: - yield name 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): From 3611523f364b626418173f84a879ef704d395e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 2 Oct 2020 01:23:39 +0200 Subject: [PATCH 003/222] Fix resolution of packages with missing required extras --- poetry/puzzle/provider.py | 16 ++++++-------- poetry/repositories/repository.py | 7 +----- tests/puzzle/test_solver.py | 36 ++++++++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index 44d8b6e3796..cd06b4738af 100755 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -453,23 +453,19 @@ def complete_package( self.search_for_url(r) optional_dependencies = [] - activated_extras = [] - for extra in package.dependency.extras: - if extra not in package.extras: - continue - - activated_extras.append(extra) - optional_dependencies += [d.name for d in package.extras[extra]] - _dependencies = [] # If some extras/features were required, we need to # add a special dependency representing the base package # to the current package if package.dependency.extras: - if activated_extras: - package = package.with_features(activated_extras) + for extra in package.dependency.extras: + if extra not in package.extras: + continue + + optional_dependencies += [d.name for d in package.extras[extra]] + package = package.with_features(list(package.dependency.extras)) _dependencies.append(package.without_features().to_dependency()) for dep in requires: diff --git a/poetry/repositories/repository.py b/poetry/repositories/repository.py index 556c77e9862..1ebe702bb9c 100755 --- a/poetry/repositories/repository.py +++ b/poetry/repositories/repository.py @@ -24,14 +24,9 @@ def name(self): def package(self, name, version, extras=None): name = name.lower() - if extras is None: - extras = [] - for package in self.packages: if name == package.name and package.version.text == version: - package = package.with_features(extras) - - return package + return package.clone() def find_packages(self, dependency): constraint = dependency.constraint diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index bf9075e186a..38ca6a4e292 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -2435,7 +2435,6 @@ def test_solver_can_resolve_transitive_extras(solver, repo, package): requests = get_package("requests", "2.24.0") requests.add_dependency(Factory.create_dependency("certifi", ">=2017.4.17")) dep = get_dependency("PyOpenSSL", ">=0.14") - dep.in_extras.append("security") requests.add_dependency( Factory.create_dependency("PyOpenSSL", {"version": ">=0.14", "optional": True}) ) @@ -2465,6 +2464,41 @@ def test_solver_can_resolve_transitive_extras(solver, repo, package): ) +def test_solver_can_resolve_for_packages_with_missing_extras(solver, repo, package): + package.add_dependency( + Factory.create_dependency( + "django-anymail", {"version": "^6.0", "extras": ["postmark"]} + ) + ) + + django_anymail = get_package("django-anymail", "6.1.0") + django_anymail.add_dependency(Factory.create_dependency("django", ">=2.0")) + django_anymail.add_dependency(Factory.create_dependency("requests", ">=2.4.3")) + django_anymail.add_dependency( + Factory.create_dependency("boto3", {"version": "*", "optional": True}) + ) + django_anymail.extras["amazon_ses"] = [Factory.create_dependency("boto3", "*")] + django = get_package("django", "2.2.0") + boto3 = get_package("boto3", "1.0.0") + requests = get_package("requests", "2.24.0") + + repo.add_package(django_anymail) + repo.add_package(django) + repo.add_package(boto3) + repo.add_package(requests) + + ops = solver.solve() + + check_solver_result( + ops, + [ + {"job": "install", "package": django}, + {"job": "install", "package": requests}, + {"job": "install", "package": django_anymail}, + ], + ) + + def test_solver_can_resolve_python_restricted_package_dependencies( solver, repo, package, locked ): From 5fcdb369869d1c020a67634fe721d1a5e58bf377 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 2 Oct 2020 06:31:13 +0200 Subject: [PATCH 004/222] exporter: handle dev extras correctly (#3038) Resolves: #3018 --- poetry/packages/locker.py | 10 ++++-- poetry/utils/exporter.py | 10 +++--- tests/utils/test_exporter.py | 62 ++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 7 deletions(-) diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index 2b696b3450a..2283a2373d2 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -182,9 +182,9 @@ def locked_repository( return packages def get_project_dependencies( - self, project_requires, pinned_versions=False, with_nested=False - ): # type: (List[Dependency], bool, bool) -> Any - packages = self.locked_repository().packages + self, project_requires, pinned_versions=False, with_nested=False, with_dev=False + ): # type: (List[Dependency], bool, bool, bool) -> Any + packages = self.locked_repository(with_dev).packages # group packages entries by name, this is required because requirement might use # different constraints @@ -230,6 +230,10 @@ def __get_locked_package( # project level dependencies take precedence continue + # we make a copy to avoid any side-effects + requirement = deepcopy(requirement) + requirement._category = pkg.category + if pinned_versions: requirement.set_constraint( __get_locked_package(requirement).to_dependency().constraint diff --git a/poetry/utils/exporter.py b/poetry/utils/exporter.py index 19b48452346..ee8e751f95b 100644 --- a/poetry/utils/exporter.py +++ b/poetry/utils/exporter.py @@ -68,12 +68,14 @@ def _export_requirements_txt( dependency_lines = set() for dependency in self._poetry.locker.get_project_dependencies( - project_requires=self._poetry.package.requires - if not dev - else self._poetry.package.all_requires, + project_requires=self._poetry.package.all_requires, with_nested=True, + with_dev=dev, ): - package = repository.find_packages(dependency=dependency)[0] + try: + package = repository.find_packages(dependency=dependency)[0] + except IndexError: + continue # If a package is optional and we haven't opted in to it, continue if package.optional and package.name not in extra_package_names: diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index c2eee0d1f11..e26f448f6de 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -779,6 +779,68 @@ def test_exporter_exports_requirements_txt_with_legacy_packages(tmp_dir, poetry) assert expected == content +@pytest.mark.parametrize( + ("dev", "expected"), + [ + (True, ["bar==1.2.2", "baz==1.2.3", "foo==1.2.1"]), + (False, ["bar==1.2.2", "foo==1.2.1"]), + ], +) +def test_exporter_exports_requirements_txt_with_dev_extras( + tmp_dir, poetry, dev, expected +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.1", + "category": "main", + "optional": False, + "python-versions": "*", + }, + { + "name": "bar", + "version": "1.2.2", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": { + "baz": { + "version": ">=0.1.0", + "optional": True, + "markers": "extra == 'baz'", + } + }, + "extras": {"baz": ["baz (>=0.1.0)"]}, + }, + { + "name": "baz", + "version": "1.2.3", + "category": "dev", + "optional": False, + "python-versions": "*", + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": [], "bar": [], "baz": []}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=dev) + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + assert content == "{}\n".format("\n".join(expected)) + + def test_exporter_exports_requirements_txt_with_legacy_packages_and_duplicate_sources( tmp_dir, poetry ): From 04833bc42afc7ace61a471045cfface68141a7b6 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 2 Oct 2020 01:18:30 +0200 Subject: [PATCH 005/222] lock: allow for no-update refresh of lock files Relates-to: #3028 --- poetry/console/commands/lock.py | 10 +- poetry/installation/installer.py | 26 +++- .../fixtures/old-lock-refresh.test | 119 ++++++++++++++++++ tests/installation/test_installer.py | 38 ++++++ 4 files changed, 188 insertions(+), 5 deletions(-) create mode 100644 tests/installation/fixtures/old-lock-refresh.test diff --git a/poetry/console/commands/lock.py b/poetry/console/commands/lock.py index ddedb055d87..4157c02c5cc 100644 --- a/poetry/console/commands/lock.py +++ b/poetry/console/commands/lock.py @@ -1,3 +1,5 @@ +from cleo import option + from .installer_command import InstallerCommand @@ -6,6 +8,12 @@ class LockCommand(InstallerCommand): name = "lock" description = "Locks the project dependencies." + options = [ + option( + "no-update", None, "Do not update locked versions, only refresh lock file." + ), + ] + help = """ The lock command reads the pyproject.toml file from the current directory, processes it, and locks the dependencies in the poetry.lock @@ -21,6 +29,6 @@ def handle(self): self.poetry.config.get("experimental.new-installer", False) ) - self._installer.lock() + self._installer.lock(update=not self.option("no-update")) return self._installer.run() diff --git a/poetry/installation/installer.py b/poetry/installation/installer.py index f0c9a62d65d..6956ae85fde 100644 --- a/poetry/installation/installer.py +++ b/poetry/installation/installer.py @@ -85,6 +85,10 @@ def set_locker(self, locker): # type: (Locker) -> Installer return self def run(self): + # Check if refresh + if not self._update and self._lock and self._locker.is_locked(): + return self._do_refresh() + # Force update if there is no lock file present if not self._update and not self._locker.is_locked(): self._update = True @@ -137,11 +141,11 @@ def update(self, update=True): # type: (bool) -> Installer return self - def lock(self): # type: () -> Installer + def lock(self, update=True): # type: (bool) -> Installer """ Prepare the installer for locking only. """ - self.update() + self.update(update=update) self.execute_operations(False) self._lock = True @@ -173,6 +177,20 @@ def use_executor(self, use_executor=True): # type: (bool) -> Installer return self + def _do_refresh(self): + # Checking extras + for extra in self._extras: + if extra not in self._package.extras: + raise ValueError("Extra [{}] is not specified.".format(extra)) + + ops = self._get_operations_from_lock(self._locker.locked_repository(True)) + local_repo = Repository() + self._populate_local_repo(local_repo, ops) + + self._write_lock_file(local_repo, force=True) + + return 0 + def _do_install(self, local_repo): from poetry.puzzle import Solver @@ -285,8 +303,8 @@ def _do_install(self, local_repo): # Execute operations return self._execute(ops) - def _write_lock_file(self, repo): # type: (Repository) -> None - if self._update and self._write_lock: + def _write_lock_file(self, repo, force=True): # type: (Repository, bool) -> None + if force or (self._update and self._write_lock): updated_lock = self._locker.set_lock_data(self._package, repo.packages) if updated_lock: diff --git a/tests/installation/fixtures/old-lock-refresh.test b/tests/installation/fixtures/old-lock-refresh.test new file mode 100644 index 00000000000..40e46ebdd3c --- /dev/null +++ b/tests/installation/fixtures/old-lock-refresh.test @@ -0,0 +1,119 @@ +[[package]] +name = "attrs" +version = "17.4.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +dev = ["coverage", "hypothesis", "pympler", "pytest", "six", "zope.interface", "sphinx", "zope.interface"] +docs = ["sphinx", "zope.interface"] +tests = ["coverage", "hypothesis", "pympler", "pytest", "six", "zope.interface"] + +[[package]] +name = "colorama" +version = "0.3.9" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = "*" + +[[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]] +name = "more-itertools" +version = "4.1.0" +description = "More routines for operating on iterables, beyond itertools" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +six = ">=1.0.0,<2.0.0" + +[[package]] +name = "pluggy" +version = "0.6.0" +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]] +name = "py" +version = "1.5.3" +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.*" + +[[package]] +name = "pytest" +version = "3.5.0" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +attrs = ">=17.4.0" +colorama = "*" +funcsigs = {version = "*", markers = "python_version < \"3.0\""} +more-itertools = ">=4.0.0" +pluggy = ">=0.5,<0.7" +py = ">=1.5.0" +six = ">=1.10.0" + +[[package]] +name = "six" +version = "1.11.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = "*" + +[metadata] +lock-version = "1.1" +python-versions = "*" +content-hash = "123456789" + +[metadata.files] +attrs = [ + {file = "attrs-17.4.0-py2.py3-none-any.whl", hash = "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450"}, + {file = "attrs-17.4.0.tar.gz", hash = "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9"}, +] +colorama = [ + {file = "colorama-0.3.9-py2.py3-none-any.whl", hash = "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda"}, + {file = "colorama-0.3.9.tar.gz", hash = "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"}, +] +funcsigs = [ + {file = "funcsigs-1.0.2-py2.py3-none-any.whl", hash = "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca"}, + {file = "funcsigs-1.0.2.tar.gz", hash = "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"}, +] +more-itertools = [ + {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"}, +] +pluggy = [ + {file = "pluggy-0.6.0.tar.gz", hash = "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff"}, +] +py = [ + {file = "py-1.5.3-py2.py3-none-any.whl", hash = "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a"}, + {file = "py-1.5.3.tar.gz", hash = "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881"}, +] +pytest = [ + {file = "pytest-3.5.0-py2.py3-none-any.whl", hash = "sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c"}, + {file = "pytest-3.5.0.tar.gz", hash = "sha256:fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1"}, +] +six = [ + {file = "six-1.11.0-py2.py3-none-any.whl", hash = "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"}, + {file = "six-1.11.0.tar.gz", hash = "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"}, +] diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 077f6dcab40..95f98dad185 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -1772,6 +1772,44 @@ def test_installer_uses_prereleases_if_they_are_compatible( assert 2 == installer.executor.installations_count +def test_installer_can_refresh_old_lock_files(locker, package, repo, installed, config): + pool = Pool() + pool.add_repository(repo) + + locker.locked(True) + locker.mock_lock_data(fixture("old-lock")) + + for pkg in locker.locked_repository(with_dev_reqs=True).packages: + dependency = Factory.create_dependency( + name=pkg.name, + constraint=pkg.to_dependency().constraint, + category=pkg.category, + ) + package.add_dependency(dependency) + repo.add_package(pkg) + + installer = Installer( + NullIO(), + MockEnv(), + package, + locker, + pool, + config, + installed=None, + executor=Executor(MockEnv(), pool, config, NullIO()), + ) + installer.use_executor() + + installer.lock(update=False) + installer.run() + + assert 0 == installer.executor.installations_count + assert 0 == installer.executor.updates_count + assert 0 == installer.executor.removals_count + + assert locker.written_data == fixture("old-lock-refresh") + + def test_installer_can_handle_old_lock_files( installer, locker, package, repo, installed, config ): From d0d3ebbc7d80344f758d38e12e4619b7b3c72293 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Sat, 3 Oct 2020 10:27:03 +0200 Subject: [PATCH 006/222] install: skip unsupported binary distributions Resolves: #3045 --- poetry/installation/chooser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry/installation/chooser.py b/poetry/installation/chooser.py index db9e0a9c357..6d9e92e0b1f 100644 --- a/poetry/installation/chooser.py +++ b/poetry/installation/chooser.py @@ -63,7 +63,7 @@ def choose_for(self, package): # type: (Package) -> Link ): continue - if link.ext == ".egg": + if link.ext in {".egg", ".exe", ".msi", ".rpm", ".srpm"}: continue links.append(link) From 3f6220dedb6d824e14e0c703485bbf76c32af3db Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Sat, 3 Oct 2020 10:31:31 +0200 Subject: [PATCH 007/222] build: ensure build uses project environment Resolves: #3054 --- poetry/console/commands/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry/console/commands/build.py b/poetry/console/commands/build.py index 118fb210c53..72b7319fcd5 100644 --- a/poetry/console/commands/build.py +++ b/poetry/console/commands/build.py @@ -33,4 +33,4 @@ def handle(self): ) builder = Builder(self.poetry) - builder.build(fmt) + builder.build(fmt, executable=self.env.python) From ae6f574bcb32c42675e34238be6498d585efee4f Mon Sep 17 00:00:00 2001 From: Andreas Peldszus Date: Sun, 4 Oct 2020 16:06:28 +0200 Subject: [PATCH 008/222] Document the CLI 'cache clear' command Resolves: #3065 --- docs/docs/cli.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/docs/cli.md b/docs/docs/cli.md index b051e9346bb..ca94222ef2a 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -491,3 +491,19 @@ The `cache list` command lists Poetry's available caches. ```bash poetry cache list ``` + +### cache clear + +The `cache clear` command removes packages from a cached repository. + +For example, to clear the whole cache of packages from the `pypi` repository, run: + +```bash +poetry cache clear pypi --all +``` + +To only remove a specific package from a cache, you have to specify the cache entry in the following form `cache:package:version`: + +```bash +poetry cache clear pypi:requests:2.24.0 +``` From c3212befa951d8629ccc843dcb3a9f1ec6dc37c4 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Mon, 5 Oct 2020 01:22:00 +0200 Subject: [PATCH 009/222] tests: improve init command test setup This change ensures that we create closer to reality scenarios when testing init command. Relates-to: #3073 --- tests/console/commands/test_init.py | 62 ++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py index c64bc23316a..61052d7e2d5 100644 --- a/tests/console/commands/test_init.py +++ b/tests/console/commands/test_init.py @@ -1,26 +1,42 @@ +import os +import shutil import sys import pytest +from cleo import CommandTester + +from poetry.repositories import Pool from poetry.utils._compat import Path from poetry.utils._compat import decode +from tests.helpers import TestApplication from tests.helpers import get_package @pytest.fixture -def source_dir(tmp_path): # type: (Path) -> Path - yield Path(tmp_path.as_posix()) +def source_dir(tmp_path): # type: (...) -> Path + cwd = os.getcwd() + try: + os.chdir(str(tmp_path)) + yield Path(tmp_path.as_posix()) + finally: + os.chdir(cwd) -@pytest.fixture(autouse=True) -def patches(mocker, source_dir): - patch = mocker.patch("poetry.utils._compat.Path.cwd") - patch.return_value = source_dir + +@pytest.fixture +def patches(mocker, source_dir, repo): + mocker.patch("poetry.utils._compat.Path.cwd", return_value=source_dir) + mocker.patch( + "poetry.console.commands.init.InitCommand._get_pool", return_value=Pool([repo]) + ) @pytest.fixture -def tester(command_tester_factory): - return command_tester_factory("init") +def tester(patches): + # we need a test application without poetry here. + app = TestApplication(None) + return CommandTester(app.find("init")) @pytest.fixture @@ -265,10 +281,13 @@ def test_interactive_with_git_dependencies_and_other_name(tester, repo): assert expected in tester.io.fetch_output() -def test_interactive_with_directory_dependency(tester, repo): +def test_interactive_with_directory_dependency(tester, repo, source_dir, fixture_dir): repo.add_package(get_package("pendulum", "2.0.0")) repo.add_package(get_package("pytest", "3.6.0")) + demo = fixture_dir("git") / "github.com" / "demo" / "demo" + shutil.copytree(str(demo), str(source_dir / "demo")) + inputs = [ "my-package", # Package name "1.2.3", # Version @@ -277,7 +296,7 @@ def test_interactive_with_directory_dependency(tester, repo): "MIT", # License "~2.7 || ^3.6", # Python "", # Interactive packages - "../../fixtures/git/github.com/demo/demo", # Search for package + "./demo", # Search for package "", # Stop searching for packages "", # Interactive dev packages "pytest", # Search for package @@ -298,19 +317,23 @@ def test_interactive_with_directory_dependency(tester, repo): [tool.poetry.dependencies] python = "~2.7 || ^3.6" -demo = {path = "../../fixtures/git/github.com/demo/demo"} +demo = {path = "demo"} [tool.poetry.dev-dependencies] pytest = "^3.6.0" """ - assert expected in tester.io.fetch_output() -def test_interactive_with_directory_dependency_and_other_name(tester, repo): +def test_interactive_with_directory_dependency_and_other_name( + tester, repo, source_dir, fixture_dir +): repo.add_package(get_package("pendulum", "2.0.0")) repo.add_package(get_package("pytest", "3.6.0")) + demo = fixture_dir("git") / "github.com" / "demo" / "pyproject-demo" + shutil.copytree(str(demo), str(source_dir / "pyproject-demo")) + inputs = [ "my-package", # Package name "1.2.3", # Version @@ -319,7 +342,7 @@ def test_interactive_with_directory_dependency_and_other_name(tester, repo): "MIT", # License "~2.7 || ^3.6", # Python "", # Interactive packages - "../../fixtures/git/github.com/demo/pyproject-demo", # Search for package + "./pyproject-demo", # Search for package "", # Stop searching for packages "", # Interactive dev packages "pytest", # Search for package @@ -340,7 +363,7 @@ def test_interactive_with_directory_dependency_and_other_name(tester, repo): [tool.poetry.dependencies] python = "~2.7 || ^3.6" -demo = {path = "../../fixtures/git/github.com/demo/pyproject-demo"} +demo = {path = "pyproject-demo"} [tool.poetry.dev-dependencies] pytest = "^3.6.0" @@ -349,10 +372,13 @@ def test_interactive_with_directory_dependency_and_other_name(tester, repo): assert expected in tester.io.fetch_output() -def test_interactive_with_file_dependency(tester, repo): +def test_interactive_with_file_dependency(tester, repo, source_dir, fixture_dir): repo.add_package(get_package("pendulum", "2.0.0")) repo.add_package(get_package("pytest", "3.6.0")) + demo = fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" + shutil.copyfile(str(demo), str(source_dir / demo.name)) + inputs = [ "my-package", # Package name "1.2.3", # Version @@ -361,7 +387,7 @@ def test_interactive_with_file_dependency(tester, repo): "MIT", # License "~2.7 || ^3.6", # Python "", # Interactive packages - "../../fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl", # Search for package + "./demo-0.1.0-py2.py3-none-any.whl", # Search for package "", # Stop searching for packages "", # Interactive dev packages "pytest", # Search for package @@ -382,7 +408,7 @@ def test_interactive_with_file_dependency(tester, repo): [tool.poetry.dependencies] python = "~2.7 || ^3.6" -demo = {path = "../../fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl"} +demo = {path = "demo-0.1.0-py2.py3-none-any.whl"} [tool.poetry.dev-dependencies] pytest = "^3.6.0" From d75c5a539c4fe256411fad8d0505abd24c5c8952 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Mon, 5 Oct 2020 01:23:16 +0200 Subject: [PATCH 010/222] init: handle pyproject exceptions for existing file Resolves: #3073 --- poetry/console/commands/init.py | 3 ++- tests/console/commands/test_init.py | 36 +++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/poetry/console/commands/init.py b/poetry/console/commands/init.py index d6817bee975..af72318c31a 100644 --- a/poetry/console/commands/init.py +++ b/poetry/console/commands/init.py @@ -13,6 +13,7 @@ from cleo import option from tomlkit import inline_table +from poetry.core.pyproject import PyProjectException from poetry.core.pyproject.toml import PyProjectTOML from poetry.utils._compat import OrderedDict from poetry.utils._compat import Path @@ -378,7 +379,7 @@ def _parse_requirements( try: cwd = self.poetry.file.parent - except RuntimeError: + except (PyProjectException, RuntimeError): cwd = Path.cwd() for requirement in requirements: diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py index 61052d7e2d5..7a9212ab262 100644 --- a/tests/console/commands/test_init.py +++ b/tests/console/commands/test_init.py @@ -621,6 +621,42 @@ def test_init_existing_pyproject_simple( ) +def test_init_non_interactive_existing_pyproject_add_dependency( + tester, source_dir, init_basic_inputs, repo +): + pyproject_file = source_dir / "pyproject.toml" + existing_section = """ +[tool.black] +line-length = 88 +""" + pyproject_file.write_text(decode(existing_section)) + + repo.add_package(get_package("foo", "1.19.2")) + + tester.execute( + "--author 'Your Name ' " + "--name 'my-package' " + "--python '^3.6' " + "--dependency foo", + interactive=False, + ) + + expected = """\ +[tool.poetry] +name = "my-package" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.6" +foo = "^1.19.2" + +[tool.poetry.dev-dependencies] +""" + assert "{}\n{}".format(existing_section, expected) in pyproject_file.read_text() + + def test_init_existing_pyproject_with_build_system_fails( tester, source_dir, init_basic_inputs ): From 733310ca4a97c2ffe741dd6e8e404057b8335e61 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 2 Oct 2020 14:23:46 +0200 Subject: [PATCH 011/222] lock: resolve dependencies on no-update This change ensures that we resolve dependencies to correctly propagate markers when using `lock --no-update`. Resolves: #3048 --- poetry/installation/installer.py | 14 +- tests/console/commands/test_lock.py | 57 +++++++ tests/fixtures/old_lock/poetry.lock | 153 ++++++++++++++++++ tests/fixtures/old_lock/pyproject.toml | 15 ++ tests/fixtures/old_lock/updated.lock | 152 +++++++++++++++++ .../fixtures/old-lock-refresh.test | 119 -------------- tests/installation/test_installer.py | 38 ----- 7 files changed, 390 insertions(+), 158 deletions(-) create mode 100644 tests/console/commands/test_lock.py create mode 100644 tests/fixtures/old_lock/poetry.lock create mode 100644 tests/fixtures/old_lock/pyproject.toml create mode 100644 tests/fixtures/old_lock/updated.lock delete mode 100644 tests/installation/fixtures/old-lock-refresh.test diff --git a/poetry/installation/installer.py b/poetry/installation/installer.py index 6956ae85fde..2164d39f4b1 100644 --- a/poetry/installation/installer.py +++ b/poetry/installation/installer.py @@ -178,12 +178,24 @@ def use_executor(self, use_executor=True): # type: (bool) -> Installer return self def _do_refresh(self): + from poetry.puzzle import Solver + # Checking extras for extra in self._extras: if extra not in self._package.extras: raise ValueError("Extra [{}] is not specified.".format(extra)) - ops = self._get_operations_from_lock(self._locker.locked_repository(True)) + locked_repository = self._locker.locked_repository(True) + solver = Solver( + self._package, + self._pool, + locked_repository, + locked_repository, + self._io, # noqa + ) + + ops = solver.solve(use_latest=[]) + local_repo = Repository() self._populate_local_repo(local_repo, ops) diff --git a/tests/console/commands/test_lock.py b/tests/console/commands/test_lock.py new file mode 100644 index 00000000000..823a8ba4beb --- /dev/null +++ b/tests/console/commands/test_lock.py @@ -0,0 +1,57 @@ +import shutil +import sys + +import pytest + +from poetry.factory import Factory +from poetry.packages import Locker +from poetry.utils._compat import Path + + +@pytest.fixture +def source_dir(tmp_path): # type: (Path) -> Path + yield Path(tmp_path.as_posix()) + + +@pytest.fixture +def tester(command_tester_factory): + return command_tester_factory("lock") + + +@pytest.fixture +def poetry_with_old_lockfile(fixture_dir, source_dir): + project_dir = source_dir / "project" + shutil.copytree(str(fixture_dir("old_lock")), str(project_dir)) + poetry = Factory().create_poetry(cwd=project_dir) + return poetry + + +@pytest.mark.skipif( + sys.platform == "win32", reason="does not work on windows under ci environments" +) +def test_lock_no_update(command_tester_factory, poetry_with_old_lockfile, http): + http.disable() + + locked_repository = poetry_with_old_lockfile.locker.locked_repository( + with_dev_reqs=True + ) + assert ( + poetry_with_old_lockfile.locker.lock_data["metadata"].get("lock-version") + == "1.0" + ) + + tester = command_tester_factory("lock", poetry=poetry_with_old_lockfile) + tester.execute("--no-update") + + locker = Locker( + lock=poetry_with_old_lockfile.pyproject.file.path.parent / "poetry.lock", + local_config={}, + ) + packages = locker.locked_repository(True).packages + + assert len(packages) == len(locked_repository.packages) + + assert locker.lock_data["metadata"].get("lock-version") == "1.1" + + for package in packages: + assert locked_repository.find_packages(package.to_dependency()) diff --git a/tests/fixtures/old_lock/poetry.lock b/tests/fixtures/old_lock/poetry.lock new file mode 100644 index 00000000000..57d585709e8 --- /dev/null +++ b/tests/fixtures/old_lock/poetry.lock @@ -0,0 +1,153 @@ +[[package]] +category = "main" +description = "Python package for providing Mozilla's CA Bundle." +name = "certifi" +optional = false +python-versions = "*" +version = "2020.6.20" + +[[package]] +category = "main" +description = "Universal encoding detector for Python 2 and 3" +name = "chardet" +optional = false +python-versions = "*" +version = "3.0.4" + +[[package]] +category = "main" +description = "A Python library for the Docker Engine API." +name = "docker" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "4.3.1" + +[package.dependencies] +pywin32 = "227" +requests = ">=2.14.2,<2.18.0 || >2.18.0" +six = ">=1.4.0" +websocket-client = ">=0.32.0" + +[package.extras] +ssh = ["paramiko (>=2.4.2)"] +tls = ["pyOpenSSL (>=17.5.0)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] + +[[package]] +category = "main" +description = "Internationalized Domain Names in Applications (IDNA)" +name = "idna" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.10" + +[[package]] +category = "main" +description = "Python for Window Extensions" +marker = "sys_platform == \"win32\"" +name = "pywin32" +optional = false +python-versions = "*" +version = "227" + +[[package]] +category = "main" +description = "Python HTTP for Humans." +name = "requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.24.0" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<4" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] + +[[package]] +category = "main" +description = "Python 2 and 3 compatibility utilities" +name = "six" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +version = "1.15.0" + +[[package]] +category = "main" +description = "HTTP library with thread-safe connection pooling, file post, and more." +name = "urllib3" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +version = "1.25.10" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] + +[[package]] +category = "main" +description = "WebSocket client for Python. hybi13 is supported." +name = "websocket-client" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.57.0" + +[package.dependencies] +six = "*" + +[metadata] +content-hash = "bb4c2f3c089b802c1930b6acbeed04711d93e9cdfd9a003eb17518a6d9f350c6" +lock-version = "1.0" +python-versions = "^3.8" + +[metadata.files] +certifi = [ + {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, + {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, +] +chardet = [ + {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, + {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, +] +docker = [ + {file = "docker-4.3.1-py2.py3-none-any.whl", hash = "sha256:13966471e8bc23b36bfb3a6fb4ab75043a5ef1dac86516274777576bed3b9828"}, + {file = "docker-4.3.1.tar.gz", hash = "sha256:bad94b8dd001a8a4af19ce4becc17f41b09f228173ffe6a4e0355389eef142f2"}, +] +idna = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] +pywin32 = [ + {file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"}, + {file = "pywin32-227-cp27-cp27m-win_amd64.whl", hash = "sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116"}, + {file = "pywin32-227-cp35-cp35m-win32.whl", hash = "sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa"}, + {file = "pywin32-227-cp35-cp35m-win_amd64.whl", hash = "sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4"}, + {file = "pywin32-227-cp36-cp36m-win32.whl", hash = "sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be"}, + {file = "pywin32-227-cp36-cp36m-win_amd64.whl", hash = "sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2"}, + {file = "pywin32-227-cp37-cp37m-win32.whl", hash = "sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507"}, + {file = "pywin32-227-cp37-cp37m-win_amd64.whl", hash = "sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511"}, + {file = "pywin32-227-cp38-cp38-win32.whl", hash = "sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc"}, + {file = "pywin32-227-cp38-cp38-win_amd64.whl", hash = "sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e"}, + {file = "pywin32-227-cp39-cp39-win32.whl", hash = "sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295"}, + {file = "pywin32-227-cp39-cp39-win_amd64.whl", hash = "sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c"}, +] +requests = [ + {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, + {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, +] +six = [ + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, +] +urllib3 = [ + {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"}, + {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, +] +websocket-client = [ + {file = "websocket_client-0.57.0-py2.py3-none-any.whl", hash = "sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549"}, + {file = "websocket_client-0.57.0.tar.gz", hash = "sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010"}, +] diff --git a/tests/fixtures/old_lock/pyproject.toml b/tests/fixtures/old_lock/pyproject.toml new file mode 100644 index 00000000000..56ea6350953 --- /dev/null +++ b/tests/fixtures/old_lock/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "foobar" +version = "0.1.0" +description = "" +authors = ["Poetry Developer "] + +[tool.poetry.dependencies] +python = "^3.8" +docker = "^4.3.1" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/fixtures/old_lock/updated.lock b/tests/fixtures/old_lock/updated.lock new file mode 100644 index 00000000000..22cd049e68f --- /dev/null +++ b/tests/fixtures/old_lock/updated.lock @@ -0,0 +1,152 @@ +[[package]] +name = "certifi" +version = "2020.6.20" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "chardet" +version = "3.0.4" +description = "Universal encoding detector for Python 2 and 3" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "docker" +version = "4.3.1" +description = "A Python library for the Docker Engine API." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +pywin32 = {version = "227", markers = "sys_platform == \"win32\""} +requests = ">=2.14.2,<2.18.0 || >2.18.0" +six = ">=1.4.0" +websocket-client = ">=0.32.0" + +[package.extras] +ssh = ["paramiko (>=2.4.2)"] +tls = ["pyOpenSSL (>=17.5.0)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] + +[[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.*" + +[[package]] +name = "pywin32" +version = "227" +description = "Python for Window Extensions" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "requests" +version = "2.24.0" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<4" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] + +[[package]] +name = "six" +version = "1.15.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "urllib3" +version = "1.25.10" +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.*, <4" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] + +[[package]] +name = "websocket-client" +version = "0.57.0" +description = "WebSocket client for Python. hybi13 is supported." +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +six = "*" + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "bb4c2f3c089b802c1930b6acbeed04711d93e9cdfd9a003eb17518a6d9f350c6" + +[metadata.files] +certifi = [ + {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, + {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, +] +chardet = [ + {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, + {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, +] +docker = [ + {file = "docker-4.3.1-py2.py3-none-any.whl", hash = "sha256:13966471e8bc23b36bfb3a6fb4ab75043a5ef1dac86516274777576bed3b9828"}, + {file = "docker-4.3.1.tar.gz", hash = "sha256:bad94b8dd001a8a4af19ce4becc17f41b09f228173ffe6a4e0355389eef142f2"}, +] +idna = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] +pywin32 = [ + {file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"}, + {file = "pywin32-227-cp27-cp27m-win_amd64.whl", hash = "sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116"}, + {file = "pywin32-227-cp35-cp35m-win32.whl", hash = "sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa"}, + {file = "pywin32-227-cp35-cp35m-win_amd64.whl", hash = "sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4"}, + {file = "pywin32-227-cp36-cp36m-win32.whl", hash = "sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be"}, + {file = "pywin32-227-cp36-cp36m-win_amd64.whl", hash = "sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2"}, + {file = "pywin32-227-cp37-cp37m-win32.whl", hash = "sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507"}, + {file = "pywin32-227-cp37-cp37m-win_amd64.whl", hash = "sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511"}, + {file = "pywin32-227-cp38-cp38-win32.whl", hash = "sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc"}, + {file = "pywin32-227-cp38-cp38-win_amd64.whl", hash = "sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e"}, + {file = "pywin32-227-cp39-cp39-win32.whl", hash = "sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295"}, + {file = "pywin32-227-cp39-cp39-win_amd64.whl", hash = "sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c"}, +] +requests = [ + {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, + {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, +] +six = [ + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, +] +urllib3 = [ + {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"}, + {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, +] +websocket-client = [ + {file = "websocket_client-0.57.0-py2.py3-none-any.whl", hash = "sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549"}, + {file = "websocket_client-0.57.0.tar.gz", hash = "sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010"}, +] diff --git a/tests/installation/fixtures/old-lock-refresh.test b/tests/installation/fixtures/old-lock-refresh.test deleted file mode 100644 index 40e46ebdd3c..00000000000 --- a/tests/installation/fixtures/old-lock-refresh.test +++ /dev/null @@ -1,119 +0,0 @@ -[[package]] -name = "attrs" -version = "17.4.0" -description = "Classes Without Boilerplate" -category = "dev" -optional = false -python-versions = "*" - -[package.extras] -dev = ["coverage", "hypothesis", "pympler", "pytest", "six", "zope.interface", "sphinx", "zope.interface"] -docs = ["sphinx", "zope.interface"] -tests = ["coverage", "hypothesis", "pympler", "pytest", "six", "zope.interface"] - -[[package]] -name = "colorama" -version = "0.3.9" -description = "Cross-platform colored terminal text." -category = "dev" -optional = false -python-versions = "*" - -[[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]] -name = "more-itertools" -version = "4.1.0" -description = "More routines for operating on iterables, beyond itertools" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -six = ">=1.0.0,<2.0.0" - -[[package]] -name = "pluggy" -version = "0.6.0" -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]] -name = "py" -version = "1.5.3" -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.*" - -[[package]] -name = "pytest" -version = "3.5.0" -description = "pytest: simple powerful testing with Python" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -attrs = ">=17.4.0" -colorama = "*" -funcsigs = {version = "*", markers = "python_version < \"3.0\""} -more-itertools = ">=4.0.0" -pluggy = ">=0.5,<0.7" -py = ">=1.5.0" -six = ">=1.10.0" - -[[package]] -name = "six" -version = "1.11.0" -description = "Python 2 and 3 compatibility utilities" -category = "dev" -optional = false -python-versions = "*" - -[metadata] -lock-version = "1.1" -python-versions = "*" -content-hash = "123456789" - -[metadata.files] -attrs = [ - {file = "attrs-17.4.0-py2.py3-none-any.whl", hash = "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450"}, - {file = "attrs-17.4.0.tar.gz", hash = "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9"}, -] -colorama = [ - {file = "colorama-0.3.9-py2.py3-none-any.whl", hash = "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda"}, - {file = "colorama-0.3.9.tar.gz", hash = "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"}, -] -funcsigs = [ - {file = "funcsigs-1.0.2-py2.py3-none-any.whl", hash = "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca"}, - {file = "funcsigs-1.0.2.tar.gz", hash = "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"}, -] -more-itertools = [ - {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"}, -] -pluggy = [ - {file = "pluggy-0.6.0.tar.gz", hash = "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff"}, -] -py = [ - {file = "py-1.5.3-py2.py3-none-any.whl", hash = "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a"}, - {file = "py-1.5.3.tar.gz", hash = "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881"}, -] -pytest = [ - {file = "pytest-3.5.0-py2.py3-none-any.whl", hash = "sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c"}, - {file = "pytest-3.5.0.tar.gz", hash = "sha256:fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1"}, -] -six = [ - {file = "six-1.11.0-py2.py3-none-any.whl", hash = "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"}, - {file = "six-1.11.0.tar.gz", hash = "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"}, -] diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 95f98dad185..077f6dcab40 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -1772,44 +1772,6 @@ def test_installer_uses_prereleases_if_they_are_compatible( assert 2 == installer.executor.installations_count -def test_installer_can_refresh_old_lock_files(locker, package, repo, installed, config): - pool = Pool() - pool.add_repository(repo) - - locker.locked(True) - locker.mock_lock_data(fixture("old-lock")) - - for pkg in locker.locked_repository(with_dev_reqs=True).packages: - dependency = Factory.create_dependency( - name=pkg.name, - constraint=pkg.to_dependency().constraint, - category=pkg.category, - ) - package.add_dependency(dependency) - repo.add_package(pkg) - - installer = Installer( - NullIO(), - MockEnv(), - package, - locker, - pool, - config, - installed=None, - executor=Executor(MockEnv(), pool, config, NullIO()), - ) - installer.use_executor() - - installer.lock(update=False) - installer.run() - - assert 0 == installer.executor.installations_count - assert 0 == installer.executor.updates_count - assert 0 == installer.executor.removals_count - - assert locker.written_data == fixture("old-lock-refresh") - - def test_installer_can_handle_old_lock_files( installer, locker, package, repo, installed, config ): From e200a288f471355f7da44d3dfb91ffc2d9548c30 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 2 Oct 2020 12:10:39 +0200 Subject: [PATCH 012/222] publish: ensure config url is preferred --- poetry/factory.py | 4 +++- tests/publishing/test_publisher.py | 24 ++++++++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/poetry/factory.py b/poetry/factory.py index 90028a110f2..fd863ba5991 100755 --- a/poetry/factory.py +++ b/poetry/factory.py @@ -51,11 +51,13 @@ def create_poetry( # Load local sources repositories = {} + existing_repositories = config.get("repositories", {}) for source in base_poetry.pyproject.poetry_config.get("source", []): name = source.get("name") url = source.get("url") if name and url: - repositories[name] = {"url": url} + if name not in existing_repositories: + repositories[name] = {"url": url} config.merge({"repositories": repositories}) diff --git a/tests/publishing/test_publisher.py b/tests/publishing/test_publisher.py index a86c482226e..ceb9e7d4da0 100644 --- a/tests/publishing/test_publisher.py +++ b/tests/publishing/test_publisher.py @@ -29,28 +29,36 @@ def test_publish_publishes_to_pypi_by_default(fixture_dir, mocker, config): ] == uploader_upload.call_args -def test_publish_can_publish_to_given_repository(fixture_dir, mocker, config): +@pytest.mark.parametrize( + ("fixture_name",), [("sample_project",), ("with_default_source",)] +) +def test_publish_can_publish_to_given_repository( + fixture_dir, mocker, config, fixture_name +): 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")) - poetry._config = config - poetry.config.merge( + + config.merge( { - "repositories": {"my-repo": {"url": "http://foo.bar"}}, - "http-basic": {"my-repo": {"username": "foo", "password": "bar"}}, + "repositories": {"foo": {"url": "http://foo.bar"}}, + "http-basic": {"foo": {"username": "foo", "password": "bar"}}, } ) + + mocker.patch("poetry.factory.Factory.create_config", return_value=config) + poetry = Factory().create_poetry(fixture_dir(fixture_name)) + io = BufferedIO() publisher = Publisher(poetry, io) - publisher.publish("my-repo", None, None) + publisher.publish("foo", None, None) assert [("foo", "bar")] == uploader_auth.call_args assert [ ("http://foo.bar",), {"cert": None, "client_cert": None, "dry_run": False}, ] == uploader_upload.call_args - assert "Publishing my-package (1.2.3) to my-repo" in io.fetch_output() + assert "Publishing my-package (1.2.3) to foo" in io.fetch_output() def test_publish_raises_error_for_undefined_repository(fixture_dir, mocker, config): From b0b4a39f3e4558fb9975ec50662165ace6acf7d4 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 2 Oct 2020 11:16:37 +0200 Subject: [PATCH 013/222] provider: ensure ony activated extras are used Resolves: #3022 --- poetry/puzzle/provider.py | 14 ++-- tests/puzzle/test_solver.py | 157 ++++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+), 7 deletions(-) diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index cd06b4738af..e2838370a79 100755 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -432,7 +432,7 @@ def complete_package( self._pool.package( package.name, package.version.text, - extras=package.dependency.extras, + extras=list(package.dependency.extras), repository=package.dependency.source_name, ), ) @@ -478,12 +478,12 @@ def complete_package( if self._env and not dep.marker.validate(self._env.marker_env): continue - if ( - dep.is_optional() - and dep.name not in optional_dependencies - and not package.is_root() - ): - continue + if not package.is_root(): + if (dep.is_optional() and dep.name not in optional_dependencies) or ( + dep.in_extras + and not set(dep.in_extras).intersection(package.dependency.extras) + ): + continue _dependencies.append(dep) diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 38ca6a4e292..f1ab2197898 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -449,6 +449,163 @@ def test_solver_returns_extras_if_requested(solver, repo, package): assert ops[0].package.marker.is_any() +@pytest.mark.parametrize(("enabled_extra",), [("one",), ("two",), (None,)]) +def test_solver_returns_extras_only_requested(solver, repo, package, enabled_extra): + extras = [enabled_extra] if enabled_extra is not None else [] + + package.add_dependency(Factory.create_dependency("A", "*")) + package.add_dependency( + Factory.create_dependency("B", {"version": "*", "extras": extras}) + ) + + package_a = get_package("A", "1.0") + package_b = get_package("B", "1.0") + package_c10 = get_package("C", "1.0") + package_c20 = get_package("C", "2.0") + + dep10 = get_dependency("C", "1.0", optional=True) + dep10._in_extras.append("one") + dep10.marker = parse_marker("extra == 'one'") + + dep20 = get_dependency("C", "2.0", optional=True) + dep20._in_extras.append("two") + dep20.marker = parse_marker("extra == 'two'") + + package_b.extras = {"one": [dep10], "two": [dep20]} + + package_b.requires.append(dep10) + package_b.requires.append(dep20) + + repo.add_package(package_a) + repo.add_package(package_b) + repo.add_package(package_c10) + repo.add_package(package_c20) + + ops = solver.solve() + + expected = [ + {"job": "install", "package": package_a}, + {"job": "install", "package": package_b}, + ] + + if enabled_extra is not None: + expected.insert( + 0, + { + "job": "install", + "package": package_c10 if enabled_extra == "one" else package_c20, + }, + ) + + check_solver_result( + ops, expected, + ) + + assert ops[-1].package.marker.is_any() + assert ops[0].package.marker.is_any() + + +@pytest.mark.parametrize(("enabled_extra",), [("one",), ("two",), (None,)]) +def test_solver_returns_extras_when_multiple_extras_use_same_dependency( + solver, repo, package, enabled_extra +): + package.add_dependency(Factory.create_dependency("A", "*")) + + package_a = get_package("A", "1.0") + package_b = get_package("B", "1.0") + package_c = get_package("C", "1.0") + + dep = get_dependency("C", "*", optional=True) + dep._in_extras.append("one") + dep._in_extras.append("two") + + package_b.extras = {"one": [dep], "two": [dep]} + + package_b.requires.append(dep) + + extras = [enabled_extra] if enabled_extra is not None else [] + package_a.add_dependency( + Factory.create_dependency("B", {"version": "*", "extras": extras}) + ) + + repo.add_package(package_a) + repo.add_package(package_b) + repo.add_package(package_c) + + ops = solver.solve() + + expected = [ + {"job": "install", "package": package_b}, + {"job": "install", "package": package_a}, + ] + + if enabled_extra is not None: + expected.insert(0, {"job": "install", "package": package_c}) + + check_solver_result( + ops, expected, + ) + + assert ops[-1].package.marker.is_any() + assert ops[0].package.marker.is_any() + + +@pytest.mark.parametrize(("enabled_extra",), [("one",), ("two",), (None,)]) +def test_solver_returns_extras_only_requested_nested( + solver, repo, package, enabled_extra +): + package.add_dependency(Factory.create_dependency("A", "*")) + + package_a = get_package("A", "1.0") + package_b = get_package("B", "1.0") + package_c10 = get_package("C", "1.0") + package_c20 = get_package("C", "2.0") + + dep10 = get_dependency("C", "1.0", optional=True) + dep10._in_extras.append("one") + dep10.marker = parse_marker("extra == 'one'") + + dep20 = get_dependency("C", "2.0", optional=True) + dep20._in_extras.append("two") + dep20.marker = parse_marker("extra == 'two'") + + package_b.extras = {"one": [dep10], "two": [dep20]} + + package_b.requires.append(dep10) + package_b.requires.append(dep20) + + extras = [enabled_extra] if enabled_extra is not None else [] + package_a.add_dependency( + Factory.create_dependency("B", {"version": "*", "extras": extras}) + ) + + repo.add_package(package_a) + repo.add_package(package_b) + repo.add_package(package_c10) + repo.add_package(package_c20) + + ops = solver.solve() + + expected = [ + {"job": "install", "package": package_b}, + {"job": "install", "package": package_a}, + ] + + if enabled_extra is not None: + expected.insert( + 0, + { + "job": "install", + "package": package_c10 if enabled_extra == "one" else package_c20, + }, + ) + + check_solver_result(ops, expected) + + assert ops[-1].package.marker.is_any() + assert ops[0].package.marker.is_any() + + def test_solver_returns_prereleases_if_requested(solver, repo, package): package.add_dependency(Factory.create_dependency("A", "*")) package.add_dependency(Factory.create_dependency("B", "*")) From b02e66c291106548c53b689dd87323c27eb60e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Tue, 6 Oct 2020 00:15:50 +0200 Subject: [PATCH 014/222] Remove executable bit from files without shebangs --- poetry/factory.py | 0 poetry/mixology/partial_solution.py | 0 poetry/mixology/term.py | 0 poetry/mixology/version_solver.py | 0 poetry/puzzle/provider.py | 0 poetry/repositories/legacy_repository.py | 0 poetry/repositories/pool.py | 0 poetry/repositories/pypi_repository.py | 0 poetry/repositories/repository.py | 0 tests/fixtures/git/github.com/demo/demo/demo.egg-info/PKG-INFO | 0 tests/fixtures/git/github.com/demo/demo/demo.egg-info/SOURCES.txt | 0 .../git/github.com/demo/demo/demo.egg-info/dependency_links.txt | 0 .../fixtures/git/github.com/demo/demo/demo.egg-info/requires.txt | 0 .../fixtures/git/github.com/demo/demo/demo.egg-info/top_level.txt | 0 14 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 poetry/factory.py mode change 100755 => 100644 poetry/mixology/partial_solution.py mode change 100755 => 100644 poetry/mixology/term.py mode change 100755 => 100644 poetry/mixology/version_solver.py mode change 100755 => 100644 poetry/puzzle/provider.py mode change 100755 => 100644 poetry/repositories/legacy_repository.py mode change 100755 => 100644 poetry/repositories/pool.py mode change 100755 => 100644 poetry/repositories/pypi_repository.py mode change 100755 => 100644 poetry/repositories/repository.py mode change 100755 => 100644 tests/fixtures/git/github.com/demo/demo/demo.egg-info/PKG-INFO mode change 100755 => 100644 tests/fixtures/git/github.com/demo/demo/demo.egg-info/SOURCES.txt mode change 100755 => 100644 tests/fixtures/git/github.com/demo/demo/demo.egg-info/dependency_links.txt mode change 100755 => 100644 tests/fixtures/git/github.com/demo/demo/demo.egg-info/requires.txt mode change 100755 => 100644 tests/fixtures/git/github.com/demo/demo/demo.egg-info/top_level.txt diff --git a/poetry/factory.py b/poetry/factory.py old mode 100755 new mode 100644 diff --git a/poetry/mixology/partial_solution.py b/poetry/mixology/partial_solution.py old mode 100755 new mode 100644 diff --git a/poetry/mixology/term.py b/poetry/mixology/term.py old mode 100755 new mode 100644 diff --git a/poetry/mixology/version_solver.py b/poetry/mixology/version_solver.py old mode 100755 new mode 100644 diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py old mode 100755 new mode 100644 diff --git a/poetry/repositories/legacy_repository.py b/poetry/repositories/legacy_repository.py old mode 100755 new mode 100644 diff --git a/poetry/repositories/pool.py b/poetry/repositories/pool.py old mode 100755 new mode 100644 diff --git a/poetry/repositories/pypi_repository.py b/poetry/repositories/pypi_repository.py old mode 100755 new mode 100644 diff --git a/poetry/repositories/repository.py b/poetry/repositories/repository.py old mode 100755 new mode 100644 diff --git a/tests/fixtures/git/github.com/demo/demo/demo.egg-info/PKG-INFO b/tests/fixtures/git/github.com/demo/demo/demo.egg-info/PKG-INFO old mode 100755 new mode 100644 diff --git a/tests/fixtures/git/github.com/demo/demo/demo.egg-info/SOURCES.txt b/tests/fixtures/git/github.com/demo/demo/demo.egg-info/SOURCES.txt old mode 100755 new mode 100644 diff --git a/tests/fixtures/git/github.com/demo/demo/demo.egg-info/dependency_links.txt b/tests/fixtures/git/github.com/demo/demo/demo.egg-info/dependency_links.txt old mode 100755 new mode 100644 diff --git a/tests/fixtures/git/github.com/demo/demo/demo.egg-info/requires.txt b/tests/fixtures/git/github.com/demo/demo/demo.egg-info/requires.txt old mode 100755 new mode 100644 diff --git a/tests/fixtures/git/github.com/demo/demo/demo.egg-info/top_level.txt b/tests/fixtures/git/github.com/demo/demo/demo.egg-info/top_level.txt old mode 100755 new mode 100644 From 27858e82a94e89a9000ccaba5110fa9db0792dc1 Mon Sep 17 00:00:00 2001 From: "Yury V. Zaytsev" Date: Tue, 6 Oct 2020 11:49:01 +0200 Subject: [PATCH 015/222] export: fix handling of nested git dependencies --- poetry/utils/exporter.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/poetry/utils/exporter.py b/poetry/utils/exporter.py index ee8e751f95b..4200abd146d 100644 --- a/poetry/utils/exporter.py +++ b/poetry/utils/exporter.py @@ -134,11 +134,14 @@ def _export_requirements_txt( # If we have extra indexes, we add them to the beginning of the output indexes_header = "" for index in sorted(indexes): - repository = [ + repositories = [ r for r in self._poetry.pool.repositories if r.url == index.rstrip("/") - ][0] + ] + if not repositories: + continue + repository = repositories[0] if ( self._poetry.pool.has_default() and repository is self._poetry.pool.repositories[0] From 08ab6ca43d4de4aa3e4df4d46fb027dc46c1fcea Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 6 Oct 2020 11:38:04 +0200 Subject: [PATCH 016/222] executor: execute parallel unsafe ops serially Resolves: #2658 #3086 --- poetry/installation/executor.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index 9effaf3ebeb..8c1d621af03 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -107,14 +107,33 @@ def execute(self, operations): # type: (Operation) -> int self._sections = OrderedDict() for _, group in groups: tasks = [] + serial_operations = [] for operation in group: if self._shutdown: break + # Some operations are unsafe, we mus execute them serially in a group + # https://github.com/python-poetry/poetry/issues/3086 + # https://github.com/python-poetry/poetry/issues/2658 + # + # We need to explicitly check source type here, see: + # https://github.com/python-poetry/poetry-core/pull/98 + is_parallel_unsafe = operation.job_type == "uninstall" or ( + operation.package.develop + and operation.package.source_type in {"directory", "git"} + ) + if not operation.skipped and is_parallel_unsafe: + serial_operations.append(operation) + continue + tasks.append(self._executor.submit(self._execute_operation, operation)) try: wait(tasks) + + for operation in serial_operations: + wait([self._executor.submit(self._execute_operation, operation)]) + except KeyboardInterrupt: self._shutdown = True From 746d741d81f7b2dd39798ac95e867052d8a4d9d0 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 6 Oct 2020 17:19:45 +0200 Subject: [PATCH 017/222] info: enhance local path poetry project support This change ensures that package inspection will handle rich core metadata available for poetry managed projects. This will allow, nested local path dependencies to use "develop" mode packages. Resolves: #3098 --- poetry/inspection/info.py | 9 ++++ tests/conftest.py | 9 +++- .../bar/pyproject.toml | 11 ++++ .../foo/pyproject.toml | 11 ++++ .../project_with_nested_local/pyproject.toml | 12 +++++ .../quix/pyproject.toml | 10 ++++ .../with-directory-dependency-poetry.test | 2 +- tests/installation/test_installer.py | 35 ++++++------ tests/installation/test_installer_old.py | 33 ++++++------ tests/puzzle/test_solver.py | 54 +++++++++++++++++++ 10 files changed, 147 insertions(+), 39 deletions(-) create mode 100644 tests/fixtures/project_with_nested_local/bar/pyproject.toml create mode 100644 tests/fixtures/project_with_nested_local/foo/pyproject.toml create mode 100644 tests/fixtures/project_with_nested_local/pyproject.toml create mode 100644 tests/fixtures/project_with_nested_local/quix/pyproject.toml diff --git a/poetry/inspection/info.py b/poetry/inspection/info.py index a2d3cbeb545..73034de16d3 100644 --- a/poetry/inspection/info.py +++ b/poetry/inspection/info.py @@ -156,6 +156,15 @@ def to_package( package.python_versions = self.requires_python or "*" package.files = self.files + if root_dir or (self._source_type in {"directory"} and self._source_url): + # this is a local poetry project, this means we can extract "richer" requirement information + # eg: development requirements etc. + poetry_package = self._get_poetry_package(path=root_dir or self._source_url) + if poetry_package: + package.extras = poetry_package.extras + package.requires = poetry_package.requires + return package + for req in self.requires_dist or []: try: # Attempt to parse the PEP-508 requirement string diff --git a/tests/conftest.py b/tests/conftest.py index 15f7b56e567..e2b73936924 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -141,9 +141,14 @@ def http(): @pytest.fixture -def fixture_dir(): +def fixture_base(): + return Path(__file__).parent / "fixtures" + + +@pytest.fixture +def fixture_dir(fixture_base): def _fixture_dir(name): - return Path(__file__).parent / "fixtures" / name + return fixture_base / name return _fixture_dir diff --git a/tests/fixtures/project_with_nested_local/bar/pyproject.toml b/tests/fixtures/project_with_nested_local/bar/pyproject.toml new file mode 100644 index 00000000000..bf058b8813a --- /dev/null +++ b/tests/fixtures/project_with_nested_local/bar/pyproject.toml @@ -0,0 +1,11 @@ +[tool.poetry] +name = "bar" +version = "1.2.3" +description = "Some description." +authors = ["Poetry Maintainer "] +license = "MIT" + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.4" +quix = { path = "../quix", develop = true } diff --git a/tests/fixtures/project_with_nested_local/foo/pyproject.toml b/tests/fixtures/project_with_nested_local/foo/pyproject.toml new file mode 100644 index 00000000000..1aba06effe1 --- /dev/null +++ b/tests/fixtures/project_with_nested_local/foo/pyproject.toml @@ -0,0 +1,11 @@ +[tool.poetry] +name = "foo" +version = "1.2.3" +description = "Some description." +authors = ["Poetry Maintainer "] +license = "MIT" + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.4" +bar = { path = "../bar", develop = true } diff --git a/tests/fixtures/project_with_nested_local/pyproject.toml b/tests/fixtures/project_with_nested_local/pyproject.toml new file mode 100644 index 00000000000..c6eb2c6b46c --- /dev/null +++ b/tests/fixtures/project_with_nested_local/pyproject.toml @@ -0,0 +1,12 @@ +[tool.poetry] +name = "project-with-nested-local" +version = "1.2.3" +description = "Some description." +authors = ["Poetry Maintainer "] +license = "MIT" + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.4" +foo = { path = "./foo", develop = true } +bar = { path = "./bar", develop = true } diff --git a/tests/fixtures/project_with_nested_local/quix/pyproject.toml b/tests/fixtures/project_with_nested_local/quix/pyproject.toml new file mode 100644 index 00000000000..a90d9bcc41f --- /dev/null +++ b/tests/fixtures/project_with_nested_local/quix/pyproject.toml @@ -0,0 +1,10 @@ +[tool.poetry] +name = "quix" +version = "1.2.3" +description = "Some description." +authors = ["Poetry Maintainer "] +license = "MIT" + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.4" diff --git a/tests/installation/fixtures/with-directory-dependency-poetry.test b/tests/installation/fixtures/with-directory-dependency-poetry.test index 32530cb97f0..12431f62185 100644 --- a/tests/installation/fixtures/with-directory-dependency-poetry.test +++ b/tests/installation/fixtures/with-directory-dependency-poetry.test @@ -16,7 +16,7 @@ python-versions = "*" version = "1.2.3" [package.dependencies] -pendulum = {version = ">=1.4.4", optional = true, markers = "extra == \"extras_a\""} +pendulum = {version = ">=1.4.4", optional = true} [package.extras] extras_a = ["pendulum (>=1.4.4)"] diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 077f6dcab40..7a1660670ee 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -29,9 +29,6 @@ from tests.repositories.test_pypi_repository import MockRepository -fixtures_dir = Path("tests/fixtures") - - class Installer(BaseInstaller): def _get_installer(self): return NoopInstaller() @@ -779,8 +776,8 @@ def test_installer_with_pypi_repository(package, locker, installed, config): assert locker.written_data == expected -def test_run_installs_with_local_file(installer, locker, repo, package): - file_path = fixtures_dir / "distributions/demo-0.1.0-py2.py3-none-any.whl" +def test_run_installs_with_local_file(installer, locker, repo, package, fixture_dir): + file_path = fixture_dir("distributions/demo-0.1.0-py2.py3-none-any.whl") package.add_dependency(Factory.create_dependency("demo", {"file": str(file_path)})) repo.add_package(get_package("pendulum", "1.4.4")) @@ -793,9 +790,11 @@ def test_run_installs_with_local_file(installer, locker, repo, package): assert 2 == installer.executor.installations_count -def test_run_installs_wheel_with_no_requires_dist(installer, locker, repo, package): - file_path = ( - fixtures_dir / "wheel_with_no_requires_dist/demo-0.1.0-py2.py3-none-any.whl" +def test_run_installs_wheel_with_no_requires_dist( + installer, locker, repo, package, fixture_dir +): + file_path = fixture_dir( + "wheel_with_no_requires_dist/demo-0.1.0-py2.py3-none-any.whl" ) package.add_dependency(Factory.create_dependency("demo", {"file": str(file_path)})) @@ -809,31 +808,29 @@ def test_run_installs_wheel_with_no_requires_dist(installer, locker, repo, packa def test_run_installs_with_local_poetry_directory_and_extras( - installer, locker, repo, package, tmpdir + installer, locker, repo, package, tmpdir, fixture_dir ): - file_path = fixtures_dir / "project_with_extras" + file_path = fixture_dir("project_with_extras") package.add_dependency( Factory.create_dependency( "project-with-extras", {"path": str(file_path), "extras": ["extras_a"]} ) ) - print(package.requires[0].develop) repo.add_package(get_package("pendulum", "1.4.4")) installer.run() expected = fixture("with-directory-dependency-poetry") - assert locker.written_data == expected assert 2 == installer.executor.installations_count def test_run_installs_with_local_poetry_directory_transitive( - installer, locker, repo, package, tmpdir + installer, locker, repo, package, tmpdir, fixture_dir ): - root_dir = fixtures_dir.joinpath("directory") + root_dir = fixture_dir("directory") package.root_dir = root_dir locker.set_lock_path(root_dir) directory = root_dir.joinpath("project_with_transitive_directory_dependencies") @@ -858,12 +855,12 @@ def test_run_installs_with_local_poetry_directory_transitive( def test_run_installs_with_local_poetry_file_transitive( - installer, locker, repo, package, tmpdir + installer, locker, repo, package, tmpdir, fixture_dir ): - root_dir = fixtures_dir.joinpath("directory") + root_dir = fixture_dir("directory") package.root_dir = root_dir locker.set_lock_path(root_dir) - directory = fixtures_dir.joinpath("directory").joinpath( + directory = fixture_dir("directory").joinpath( "project_with_transitive_file_dependencies" ) package.add_dependency( @@ -887,9 +884,9 @@ def test_run_installs_with_local_poetry_file_transitive( def test_run_installs_with_local_setuptools_directory( - installer, locker, repo, package, tmpdir + installer, locker, repo, package, tmpdir, fixture_dir ): - file_path = fixtures_dir / "project_with_setup/" + file_path = fixture_dir("project_with_setup/") package.add_dependency( Factory.create_dependency("project-with-setup", {"path": str(file_path)}) ) diff --git a/tests/installation/test_installer_old.py b/tests/installation/test_installer_old.py index f8f5f62a469..b92bdce460e 100644 --- a/tests/installation/test_installer_old.py +++ b/tests/installation/test_installer_old.py @@ -27,9 +27,6 @@ from tests.repositories.test_pypi_repository import MockRepository -fixtures_dir = Path("tests/fixtures") - - class Installer(BaseInstaller): def _get_installer(self): return NoopInstaller() @@ -749,8 +746,8 @@ def test_installer_with_pypi_repository(package, locker, installed, config): assert locker.written_data == expected -def test_run_installs_with_local_file(installer, locker, repo, package): - file_path = fixtures_dir / "distributions/demo-0.1.0-py2.py3-none-any.whl" +def test_run_installs_with_local_file(installer, locker, repo, package, fixture_dir): + file_path = fixture_dir("distributions/demo-0.1.0-py2.py3-none-any.whl") package.add_dependency(Factory.create_dependency("demo", {"file": str(file_path)})) repo.add_package(get_package("pendulum", "1.4.4")) @@ -764,9 +761,11 @@ def test_run_installs_with_local_file(installer, locker, repo, package): assert len(installer.installer.installs) == 2 -def test_run_installs_wheel_with_no_requires_dist(installer, locker, repo, package): - file_path = ( - fixtures_dir / "wheel_with_no_requires_dist/demo-0.1.0-py2.py3-none-any.whl" +def test_run_installs_wheel_with_no_requires_dist( + installer, locker, repo, package, fixture_dir +): + file_path = fixture_dir( + "wheel_with_no_requires_dist/demo-0.1.0-py2.py3-none-any.whl" ) package.add_dependency(Factory.create_dependency("demo", {"file": str(file_path)})) @@ -780,9 +779,9 @@ def test_run_installs_wheel_with_no_requires_dist(installer, locker, repo, packa def test_run_installs_with_local_poetry_directory_and_extras( - installer, locker, repo, package, tmpdir + installer, locker, repo, package, tmpdir, fixture_dir ): - file_path = fixtures_dir / "project_with_extras" + file_path = fixture_dir("project_with_extras") package.add_dependency( Factory.create_dependency( "project-with-extras", {"path": str(file_path), "extras": ["extras_a"]} @@ -801,9 +800,9 @@ def test_run_installs_with_local_poetry_directory_and_extras( def test_run_installs_with_local_poetry_directory_transitive( - installer, locker, repo, package, tmpdir + installer, locker, repo, package, tmpdir, fixture_dir ): - root_dir = fixtures_dir.joinpath("directory") + root_dir = fixture_dir("directory") package.root_dir = root_dir locker.set_lock_path(root_dir) directory = root_dir.joinpath("project_with_transitive_directory_dependencies") @@ -828,16 +827,16 @@ def test_run_installs_with_local_poetry_directory_transitive( def test_run_installs_with_local_poetry_file_transitive( - installer, locker, repo, package, tmpdir + installer, locker, repo, package, tmpdir, fixture_dir ): - root_dir = fixtures_dir.joinpath("directory") + root_dir = fixture_dir("directory") package.root_dir = root_dir locker.set_lock_path(root_dir) directory = root_dir.joinpath("project_with_transitive_file_dependencies") package.add_dependency( Factory.create_dependency( "project-with-transitive-file-dependencies", - {"path": str(directory.relative_to(fixtures_dir.joinpath("directory")))}, + {"path": str(directory.relative_to(fixture_dir("directory")))}, root_dir=root_dir, ) ) @@ -855,9 +854,9 @@ def test_run_installs_with_local_poetry_file_transitive( def test_run_installs_with_local_setuptools_directory( - installer, locker, repo, package, tmpdir + installer, locker, repo, package, tmpdir, fixture_dir ): - file_path = fixtures_dir / "project_with_setup/" + file_path = fixture_dir("project_with_setup/") package.add_dependency( Factory.create_dependency("project-with-setup", {"path": str(file_path)}) ) diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index f1ab2197898..1393b13f1ef 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -1672,6 +1672,60 @@ def test_solver_can_resolve_directory_dependencies(solver, repo, package): assert op.package.source_url == path +def test_solver_can_resolve_directory_dependencies_nested_editable( + solver, repo, pool, installed, locked, io +): + base = Path(__file__).parent.parent / "fixtures" / "project_with_nested_local" + poetry = Factory().create_poetry(cwd=base) + package = poetry.package + + solver = Solver( + package, pool, installed, locked, io, provider=Provider(package, pool, io) + ) + + ops = solver.solve() + + check_solver_result( + ops, + [ + { + "job": "install", + "package": Package( + "quix", + "1.2.3", + source_type="directory", + source_url=(base / "quix").as_posix(), + ), + "skipped": False, + }, + { + "job": "install", + "package": Package( + "bar", + "1.2.3", + source_type="directory", + source_url=(base / "bar").as_posix(), + ), + "skipped": False, + }, + { + "job": "install", + "package": Package( + "foo", + "1.2.3", + source_type="directory", + source_url=(base / "foo").as_posix(), + ), + "skipped": False, + }, + ], + ) + + for op in ops: + assert op.package.source_type == "directory" + assert op.package.develop is True + + def test_solver_can_resolve_directory_dependencies_with_extras(solver, repo, package): pendulum = get_package("pendulum", "2.0.3") cleo = get_package("cleo", "1.0.0") From ca59f50f6e897136c50a125ac926d2036765224f Mon Sep 17 00:00:00 2001 From: finswimmer Date: Wed, 7 Oct 2020 23:18:46 +0200 Subject: [PATCH 018/222] application: ensure warning is written to stderr Resolves: #2754 --- poetry/console/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry/console/application.py b/poetry/console/application.py index e1d7c2aa525..8fb32480f49 100644 --- a/poetry/console/application.py +++ b/poetry/console/application.py @@ -56,7 +56,7 @@ def __init__(self): "See https://python-poetry.org/docs/managing-environments/ " "for more information." ).format(python_version, poetry_feature_release, python_version) - self._preliminary_io.write_line("{}\n".format(message)) + self._preliminary_io.error_line("{}\n".format(message)) @property def poetry(self): From 495a6ed0612a884239c34bfb472673ddb2f54bb3 Mon Sep 17 00:00:00 2001 From: Jair Henrique Date: Thu, 8 Oct 2020 13:32:00 -0300 Subject: [PATCH 019/222] ci/actions: add python 3.9 to test matrix --- .github/workflows/code-quality.yaml | 4 ++-- .github/workflows/main.yml | 4 ++-- .github/workflows/skip.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/code-quality.yaml b/.github/workflows/code-quality.yaml index 4c1e4d566ed..86f4939283a 100644 --- a/.github/workflows/code-quality.yaml +++ b/.github/workflows/code-quality.yaml @@ -16,6 +16,6 @@ jobs: name: Linting runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions/setup-python@v1 + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 - uses: pre-commit/action@v2.0.0 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 119916ce3d6..32379cdf8b2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,12 +22,12 @@ jobs: strategy: matrix: os: [Ubuntu, MacOS, Windows] - python-version: [2.7, 3.5, 3.6, 3.7, 3.8] + python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/skip.yml b/.github/workflows/skip.yml index a9b384e3500..aea8d88274c 100644 --- a/.github/workflows/skip.yml +++ b/.github/workflows/skip.yml @@ -32,6 +32,6 @@ jobs: strategy: matrix: os: [Ubuntu, MacOS, Windows] - python-version: [2.7, 3.5, 3.6, 3.7, 3.8] + python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9] steps: - run: exit 0 From 6a96dd22f4cf22f6f6d5bdb36c44c02097d57bee Mon Sep 17 00:00:00 2001 From: Michel Albert Date: Thu, 8 Oct 2020 16:35:20 +0200 Subject: [PATCH 020/222] Fix issue when PATH is missing in os.environ Closes #3144 --- poetry/utils/env.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry/utils/env.py b/poetry/utils/env.py index 0a027bd668a..ccd855b5c60 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -1212,7 +1212,7 @@ def unset_env(self, key): del os.environ[key] def _updated_path(self): - return os.pathsep.join([str(self._bin_dir), os.environ["PATH"]]) + return os.pathsep.join([str(self._bin_dir), os.environ.get("PATH", "")]) class NullEnv(SystemEnv): From 056857fa546ef31be5183ec005c6b34169934e5a Mon Sep 17 00:00:00 2001 From: Clinton Roy Date: Fri, 9 Oct 2020 20:19:41 +1100 Subject: [PATCH 021/222] Correct minor typo in a comment. --- poetry/installation/executor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index 8c1d621af03..a3184b718d5 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -112,7 +112,7 @@ def execute(self, operations): # type: (Operation) -> int if self._shutdown: break - # Some operations are unsafe, we mus execute them serially in a group + # Some operations are unsafe, we must execute them serially in a group # https://github.com/python-poetry/poetry/issues/3086 # https://github.com/python-poetry/poetry/issues/2658 # From 53cadad5ba9203b312ee8645838c4d48d397203b Mon Sep 17 00:00:00 2001 From: Konstantin Shcherban Date: Thu, 8 Oct 2020 17:27:10 +0200 Subject: [PATCH 022/222] Fix options heading in cli docs --- docs/docs/cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/cli.md b/docs/docs/cli.md index ca94222ef2a..1a2615a9d9d 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -446,7 +446,7 @@ The table below illustrates the effect of these rules with concrete examples. | prerelease | 1.0.3-alpha.0 | 1.0.3-alpha.1 | | prerelease | 1.0.3-beta.0 | 1.0.3-beta.1 | -## Options +### Options * `--short (-s)`: Output the version number only. From 2db820d509f5680cb1fb2309aa1275a58775d386 Mon Sep 17 00:00:00 2001 From: Etty Date: Sat, 10 Oct 2020 16:13:32 +0100 Subject: [PATCH 023/222] Catch PyProjectException when pyproject.toml is empty --- poetry/console/commands/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/poetry/console/commands/config.py b/poetry/console/commands/config.py index 551876912c2..70b6c7673f1 100644 --- a/poetry/console/commands/config.py +++ b/poetry/console/commands/config.py @@ -4,6 +4,7 @@ from cleo import argument from cleo import option +from poetry.core.pyproject import PyProjectException from poetry.core.toml.file import TOMLFile from poetry.factory import Factory @@ -80,7 +81,7 @@ def handle(self): local_config_file = TOMLFile(self.poetry.file.parent / "poetry.toml") if local_config_file.exists(): config.merge(local_config_file.read()) - except RuntimeError: + except (RuntimeError, PyProjectException): local_config_file = TOMLFile(Path.cwd() / "poetry.toml") if self.option("local"): From 3b3791d3cac8b7d583699ac28086c10dc850b370 Mon Sep 17 00:00:00 2001 From: Etty Date: Sat, 10 Oct 2020 16:14:51 +0100 Subject: [PATCH 024/222] Add test --- tests/console/commands/test_config.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index 4bf102073d5..bb13a268e2f 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -4,6 +4,7 @@ import pytest from poetry.config.config_source import ConfigSource +from poetry.core.pyproject import PyProjectException from poetry.factory import Factory @@ -12,6 +13,16 @@ def tester(command_tester_factory): return command_tester_factory("config") +def test_show_config_with_local_config_file_empty(tester, mocker): + mocker.patch( + "poetry.factory.Factory.create_poetry", + side_effect=PyProjectException("[tool.poetry] section not found"), + ) + tester.execute() + + assert "" == tester.io.fetch_output() + + def test_list_displays_default_value_if_not_set(tester, config): tester.execute("--list") From d1e76021211610a4b2552826e755334aa3448758 Mon Sep 17 00:00:00 2001 From: KGB33 Date: Sat, 10 Oct 2020 10:17:00 -0600 Subject: [PATCH 025/222] added python3.9 to tox envs --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 190670191da..17834f2e709 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] minversion = 3.3.0 isolated_build = True -envlist = py27, py35, py36, py37, py38, doc +envlist = py27, py35, py36, py37, py38, py39, doc [testenv] whitelist_externals = poetry From d1905045ef97da2d5ad3583b14b211cafacd61cc Mon Sep 17 00:00:00 2001 From: Paul Sanders Date: Mon, 12 Oct 2020 10:07:48 -0400 Subject: [PATCH 026/222] tests: refactor fixtures for commands.debug Relates-to: #3155 --- tests/console/commands/debug/test_resolve.py | 28 +++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/tests/console/commands/debug/test_resolve.py b/tests/console/commands/debug/test_resolve.py index ad7e94321d2..ffc1e509aef 100644 --- a/tests/console/commands/debug/test_resolve.py +++ b/tests/console/commands/debug/test_resolve.py @@ -9,14 +9,20 @@ def tester(command_tester_factory): return command_tester_factory("debug resolve") -def test_debug_resolve_gives_resolution_results(tester, repo): - cachy2 = get_package("cachy", "0.2.0") - cachy2.add_dependency(Factory.create_dependency("msgpack-python", ">=0.5 <0.6")) +@pytest.fixture(autouse=True) +def __add_packages(repo): + cachy020 = get_package("cachy", "0.2.0") + cachy020.add_dependency(Factory.create_dependency("msgpack-python", ">=0.5 <0.6")) repo.add_package(get_package("cachy", "0.1.0")) - repo.add_package(cachy2) + repo.add_package(cachy020) repo.add_package(get_package("msgpack-python", "0.5.3")) + repo.add_package(get_package("pendulum", "2.0.3")) + repo.add_package(get_package("cleo", "0.6.5")) + + +def test_debug_resolve_gives_resolution_results(tester): tester.execute("cachy") expected = """\ @@ -31,14 +37,7 @@ def test_debug_resolve_gives_resolution_results(tester, repo): assert expected == tester.io.fetch_output() -def test_debug_resolve_tree_option_gives_the_dependency_tree(tester, repo): - cachy2 = get_package("cachy", "0.2.0") - cachy2.add_dependency(Factory.create_dependency("msgpack-python", ">=0.5 <0.6")) - - repo.add_package(get_package("cachy", "0.1.0")) - repo.add_package(cachy2) - repo.add_package(get_package("msgpack-python", "0.5.3")) - +def test_debug_resolve_tree_option_gives_the_dependency_tree(tester): tester.execute("cachy --tree") expected = """\ @@ -53,10 +52,7 @@ def test_debug_resolve_tree_option_gives_the_dependency_tree(tester, repo): assert expected == tester.io.fetch_output() -def test_debug_resolve_git_dependency(tester, repo): - repo.add_package(get_package("pendulum", "2.0.3")) - repo.add_package(get_package("cleo", "0.6.5")) - +def test_debug_resolve_git_dependency(tester): tester.execute("git+https://github.com/demo/demo.git") expected = """\ From 3e42be108443e0891e6acb4b2d9175c9ea614502 Mon Sep 17 00:00:00 2001 From: ObserverOfTime Date: Wed, 14 Oct 2020 01:27:47 +0300 Subject: [PATCH 027/222] publish: raise an error on redirects Resolves: #3069 --- poetry/publishing/uploader.py | 13 ++++++++++++- tests/publishing/test_uploader.py | 12 ++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/poetry/publishing/uploader.py b/poetry/publishing/uploader.py index b817676f956..7c0783d1cac 100644 --- a/poetry/publishing/uploader.py +++ b/poetry/publishing/uploader.py @@ -265,13 +265,24 @@ def _upload_file( allow_redirects=False, headers={"Content-Type": monitor.content_type}, ) - if dry_run or resp.ok: + if dry_run or 200 <= resp.status_code < 300: bar.set_format( " - Uploading {0} %percent%%".format( file.name ) ) bar.finish() + elif resp.status_code == 301: + if self._io.output.supports_ansi(): + self._io.overwrite( + " - Uploading {0} {1}".format( + file.name, "FAILED" + ) + ) + raise UploadError( + "Redirects are not supported. " + "Is the URL missing a trailing slash?" + ) except (requests.ConnectionError, requests.HTTPError) as e: if self._io.output.supports_ansi(): self._io.overwrite( diff --git a/tests/publishing/test_uploader.py b/tests/publishing/test_uploader.py index 8c46057bf17..0b32e77d864 100644 --- a/tests/publishing/test_uploader.py +++ b/tests/publishing/test_uploader.py @@ -34,6 +34,18 @@ def test_uploader_properly_handles_403_errors(http): assert "HTTP Error 403: Forbidden" == str(e.value) +def test_uploader_properly_handles_301_redirects(http): + http.register_uri(http.POST, "https://foo.com", status=301, body="Redirect") + uploader = Uploader(Factory().create_poetry(project("simple_project")), NullIO()) + + with pytest.raises(UploadError) as e: + uploader.upload("https://foo.com") + + assert "Redirects are not supported. Is the URL missing a trailing slash?" == str( + e.value + ) + + def test_uploader_registers_for_appropriate_400_errors(mocker, http): register = mocker.patch("poetry.publishing.uploader.Uploader._register") http.register_uri( From b2d14274c4edbc66e2109795fc2b58b775fe4099 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Thu, 8 Oct 2020 16:46:23 +0200 Subject: [PATCH 028/222] inspection: ad each extra package as a dependency Resolves: #3129 (cherry picked from commit 38fddf05b36ec64647e66bf459578f9e0b41442d) --- poetry/inspection/info.py | 7 ++++++- tests/repositories/test_legacy_repository.py | 4 ++-- tests/repositories/test_pypi_repository.py | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/poetry/inspection/info.py b/poetry/inspection/info.py index 73034de16d3..251dac95f91 100644 --- a/poetry/inspection/info.py +++ b/poetry/inspection/info.py @@ -165,6 +165,8 @@ def to_package( package.requires = poetry_package.requires return package + seen_requirements = set() + for req in self.requires_dist or []: try: # Attempt to parse the PEP-508 requirement string @@ -191,8 +193,11 @@ def to_package( package.extras[extra].append(dependency) - if dependency not in package.requires: + req = dependency.to_pep_508(with_extras=True) + + if req not in seen_requirements: package.requires.append(dependency) + seen_requirements.add(req) return package diff --git a/tests/repositories/test_legacy_repository.py b/tests/repositories/test_legacy_repository.py index 7fd131c70e4..47ccc1052f9 100644 --- a/tests/repositories/test_legacy_repository.py +++ b/tests/repositories/test_legacy_repository.py @@ -117,7 +117,7 @@ def test_get_package_information_skips_dependencies_with_invalid_constraints(): package.description == "Python Language Server for the Language Server Protocol" ) - assert 19 == len(package.requires) + assert 25 == len(package.requires) assert sorted( [r for r in package.requires if not r.is_optional()], key=lambda r: r.name ) == [ @@ -216,7 +216,7 @@ def test_get_package_from_both_py2_and_py3_specific_wheels(): assert "ipython" == package.name assert "5.7.0" == package.version.text assert "*" == package.python_versions - assert 26 == len(package.requires) + assert 41 == len(package.requires) expected = [ Dependency("appnope", "*"), diff --git a/tests/repositories/test_pypi_repository.py b/tests/repositories/test_pypi_repository.py index ef094f3f1d5..55afdd39485 100644 --- a/tests/repositories/test_pypi_repository.py +++ b/tests/repositories/test_pypi_repository.py @@ -165,7 +165,7 @@ def test_pypi_repository_supports_reading_bz2_files(): package = repo.package("twisted", "18.9.0") assert package.name == "twisted" - assert 28 == len(package.requires) + assert 71 == len(package.requires) assert sorted( [r for r in package.requires if not r.is_optional()], key=lambda r: r.name ) == [ From 4738c9a1ebf8f11d8062584d5aba0b83a8fd7e6f Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Wed, 7 Oct 2020 15:36:30 +0200 Subject: [PATCH 029/222] show: ignore dependency source when finding package Resolves: #3116 (cherry picked from commit 50e06289a40819afe68f9b9cfecd7ccf08763476) --- poetry/console/commands/show.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry/console/commands/show.py b/poetry/console/commands/show.py index 808122e64dc..f6f42f42654 100644 --- a/poetry/console/commands/show.py +++ b/poetry/console/commands/show.py @@ -68,7 +68,7 @@ def handle(self): table = self.table(style="compact") # table.style.line_vc_char = "" locked_packages = locked_repo.packages - pool = Pool() + pool = Pool(ignore_repository_names=True) pool.add_repository(locked_repo) solver = Solver( self.poetry.package, From 9f7c9f8b15f8228cde459f143aea721dc835eb17 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Wed, 7 Oct 2020 15:51:26 +0200 Subject: [PATCH 030/222] locker: move export logic into locker for reuse This change, moves common functionality from exporter into the locker to allow for sharing of logic that can generate `DependencyPackage` instances given a a list of requirements using the lock data. (cherry picked from commit c2adb3283c0b76e290a8f25053bf035500b47cb2) --- poetry/packages/locker.py | 44 +++++++++++++++++++++++++++++++++++++-- poetry/utils/exporter.py | 34 +++++++----------------------- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index 2283a2373d2..a486bf1c5d6 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -6,9 +6,11 @@ from copy import deepcopy from hashlib import sha256 -from typing import Any +from typing import Iterator from typing import List from typing import Optional +from typing import Sequence +from typing import Union from tomlkit import array from tomlkit import document @@ -25,8 +27,10 @@ from poetry.core.semver.version import Version from poetry.core.toml.file import TOMLFile from poetry.core.version.markers import parse_marker +from poetry.packages import DependencyPackage from poetry.utils._compat import OrderedDict from poetry.utils._compat import Path +from poetry.utils.extras import get_extra_package_names logger = logging.getLogger(__name__) @@ -183,7 +187,7 @@ def locked_repository( def get_project_dependencies( self, project_requires, pinned_versions=False, with_nested=False, with_dev=False - ): # type: (List[Dependency], bool, bool, bool) -> Any + ): # type: (List[Dependency], bool, bool, bool) -> List[Dependency] packages = self.locked_repository(with_dev).packages # group packages entries by name, this is required because requirement might use @@ -263,6 +267,42 @@ def __get_locked_package( key=lambda x: x.name.lower(), ) + def get_project_dependency_packages( + self, project_requires, dev=False, extras=None + ): # type: (List[Dependency], bool, Optional[Union[bool, Sequence[str]]]) -> Iterator[DependencyPackage] + repository = self.locked_repository(with_dev_reqs=dev) + + # Build a set of all packages required by our selected extras + extra_package_names = ( + None if (isinstance(extras, bool) and extras is True) else () + ) + + if extra_package_names is not None: + extra_package_names = set( + get_extra_package_names( + repository.packages, self.lock_data.get("extras", {}), extras or (), + ) + ) + + for dependency in self.get_project_dependencies( + project_requires=project_requires, with_nested=True, with_dev=dev, + ): + try: + package = repository.find_packages(dependency=dependency)[0] + except IndexError: + continue + + # If a package is optional and we haven't opted in to it, continue + if extra_package_names is not None and ( + package.optional and package.name not in extra_package_names + ): + continue + + for extra in dependency.extras: + package.requires_extras.append(extra) + + yield DependencyPackage(dependency=dependency, package=package) + def set_lock_data(self, root, packages): # type: (...) -> bool files = table() packages = self._lock_packages(packages) diff --git a/poetry/utils/exporter.py b/poetry/utils/exporter.py index 4200abd146d..ae85c29cc0b 100644 --- a/poetry/utils/exporter.py +++ b/poetry/utils/exporter.py @@ -1,3 +1,5 @@ +from typing import Optional +from typing import Sequence from typing import Union from clikit.api.io import IO @@ -5,7 +7,6 @@ from poetry.poetry import Poetry from poetry.utils._compat import Path from poetry.utils._compat import decode -from poetry.utils.extras import get_extra_package_names class Exporter(object): @@ -51,38 +52,19 @@ def _export_requirements_txt( dev=False, extras=None, with_credentials=False, - ): # type: (Path, Union[IO, str], bool, bool, bool) -> None + ): # type: (Path, Union[IO, str], bool, bool, Optional[Union[bool, Sequence[str]]], bool) -> None indexes = set() content = "" - repository = self._poetry.locker.locked_repository(dev) - - # Build a set of all packages required by our selected extras - extra_package_names = set( - get_extra_package_names( - repository.packages, - self._poetry.locker.lock_data.get("extras", {}), - extras or (), - ) - ) - dependency_lines = set() - for dependency in self._poetry.locker.get_project_dependencies( - project_requires=self._poetry.package.all_requires, - with_nested=True, - with_dev=dev, + for dependency_package in self._poetry.locker.get_project_dependency_packages( + project_requires=self._poetry.package.all_requires, dev=dev, extras=extras ): - try: - package = repository.find_packages(dependency=dependency)[0] - except IndexError: - continue - - # If a package is optional and we haven't opted in to it, continue - if package.optional and package.name not in extra_package_names: - continue - line = "" + dependency = dependency_package.dependency + package = dependency_package.package + if package.develop: line += "-e " From 02307ba118ce96437b4ad50b8e376adb88a1901b Mon Sep 17 00:00:00 2001 From: "Yury V. Zaytsev" Date: Wed, 7 Oct 2020 17:54:40 +0200 Subject: [PATCH 031/222] locker: reuse locked metadata for nested deps Resolves: #3115 (cherry picked from commit 04967db7ab20558b593e6b47ef5258e95a1b642f) --- poetry/packages/locker.py | 11 +++++-- tests/utils/test_exporter.py | 60 ++++++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index a486bf1c5d6..2a82fef816a 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -234,8 +234,15 @@ def __get_locked_package( # project level dependencies take precedence continue - # we make a copy to avoid any side-effects - requirement = deepcopy(requirement) + locked_package = __get_locked_package(requirement) + if locked_package: + # create dependency from locked package to retain dependency metadata + # if this is not done, we can end-up with incorrect nested dependencies + requirement = locked_package.to_dependency() + else: + # we make a copy to avoid any side-effects + requirement = deepcopy(requirement) + requirement._category = pkg.category if pinned_versions: diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index e26f448f6de..a75fb3da502 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -59,13 +59,18 @@ def poetry(fixture_dir, locker): return p -def set_package_requires(poetry): +def set_package_requires(poetry, skip=None): + skip = skip or set() packages = poetry.locker.locked_repository(with_dev_reqs=True).packages poetry.package.requires = [ - pkg.to_dependency() for pkg in packages if pkg.category == "main" + pkg.to_dependency() + for pkg in packages + if pkg.category == "main" and pkg.name not in skip ] poetry.package.dev_requires = [ - pkg.to_dependency() for pkg in packages if pkg.category == "dev" + pkg.to_dependency() + for pkg in packages + if pkg.category == "dev" and pkg.name not in skip ] @@ -503,6 +508,55 @@ def test_exporter_can_export_requirements_txt_with_git_packages(tmp_dir, poetry) assert expected == content +def test_exporter_can_export_requirements_txt_with_nested_packages(tmp_dir, poetry): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + "source": { + "type": "git", + "url": "https://github.com/foo/foo.git", + "reference": "123456", + }, + }, + { + "name": "bar", + "version": "4.5.6", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"foo": "rev 123456"}, + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": [], "bar": []}, + }, + } + ) + set_package_requires(poetry, skip={"foo"}) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +bar==4.5.6 +foo @ git+https://github.com/foo/foo.git@123456 +""" + + assert expected == content + + def test_exporter_can_export_requirements_txt_with_git_packages_and_markers( tmp_dir, poetry ): From 1188b31287df42de615d25b1642ec894c2bb0c12 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 9 Oct 2020 06:34:43 +0200 Subject: [PATCH 032/222] locker: propagate cumulative markers to nested deps This change ensures that markers are propagated from top level dependencies to the deepest level by walking top to bottom instead of iterating over all available packages. In addition, we also compress any dependencies with the same name and constraint to provide a more concise representation. Resolves: #3112 #3160 (cherry picked from commit e78a67ba139c70ee1856834711ddaf14de0c926a) --- poetry/packages/locker.py | 86 ++++++++++++++------- tests/utils/test_exporter.py | 140 +++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+), 27 deletions(-) diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index 2a82fef816a..26c2b584504 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -215,10 +215,18 @@ def __get_locked_package( for dependency in project_requires: dependency = deepcopy(dependency) - if pinned_versions: - locked_package = __get_locked_package(dependency) - if locked_package: - dependency.set_constraint(locked_package.to_dependency().constraint) + locked_package = __get_locked_package(dependency) + if locked_package: + locked_dependency = locked_package.to_dependency() + locked_dependency.marker = dependency.marker.intersect( + locked_package.marker + ) + + if not pinned_versions: + locked_dependency.set_constraint(dependency.constraint) + + dependency = locked_dependency + project_level_dependencies.add(dependency.name) dependencies.append(dependency) @@ -226,19 +234,43 @@ def __get_locked_package( # return only with project level dependencies return dependencies - nested_dependencies = list() + nested_dependencies = dict() - for pkg in packages: # type: Package - for requirement in pkg.requires: # type: Dependency - if requirement.name in project_level_dependencies: + def __walk_level( + __dependencies, __level + ): # type: (List[Dependency], int) -> None + if not __dependencies: + return + + __next_level = [] + + for requirement in __dependencies: + __locked_package = __get_locked_package(requirement) + + if __locked_package: + for require in __locked_package.requires: + if require.marker.is_empty(): + require.marker = requirement.marker + else: + require.marker = require.marker.intersect( + requirement.marker + ) + + require.marker = require.marker.intersect( + __locked_package.marker + ) + __next_level.append(require) + + if requirement.name in project_level_dependencies and __level == 0: # project level dependencies take precedence continue - locked_package = __get_locked_package(requirement) - if locked_package: + if __locked_package: # create dependency from locked package to retain dependency metadata # if this is not done, we can end-up with incorrect nested dependencies - requirement = locked_package.to_dependency() + marker = requirement.marker + requirement = __locked_package.to_dependency() + requirement.marker = requirement.marker.intersect(marker) else: # we make a copy to avoid any side-effects requirement = deepcopy(requirement) @@ -251,26 +283,26 @@ def __get_locked_package( ) # dependencies use extra to indicate that it was activated via parent - # package's extras - marker = requirement.marker.without_extras() - for project_requirement in project_requires: - if ( - pkg.name == project_requirement.name - and project_requirement.constraint.allows(pkg.version) - ): - requirement.marker = marker.intersect( - project_requirement.marker - ) - break + # package's extras, this is not required for nested exports as we assume + # the resolver already selected this dependency + requirement.marker = requirement.marker.without_extras().intersect( + pkg.marker + ) + + key = (requirement.name, requirement.pretty_constraint) + if key not in nested_dependencies: + nested_dependencies[key] = requirement else: - # this dependency was not from a project requirement - requirement.marker = marker.intersect(pkg.marker) + nested_dependencies[key].marker = nested_dependencies[ + key + ].marker.intersect(requirement.marker) + + return __walk_level(__next_level, __level + 1) - if requirement not in nested_dependencies: - nested_dependencies.append(requirement) + __walk_level(dependencies, 0) return sorted( - itertools.chain(dependencies, nested_dependencies), + itertools.chain(dependencies, nested_dependencies.values()), key=lambda x: x.name.lower(), ) diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index a75fb3da502..66b9e81ff53 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -2,6 +2,7 @@ import pytest +from poetry.core.packages import dependency_from_pep_508 from poetry.core.toml.file import TOMLFile from poetry.factory import Factory from poetry.packages import Locker as BaseLocker @@ -175,6 +176,145 @@ def test_exporter_can_export_requirements_txt_with_standard_packages_and_markers assert expected == content +def test_exporter_can_export_requirements_txt_with_nested_packages_and_markers( + tmp_dir, poetry +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "a", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + "marker": "python_version < '3.7'", + "dependencies": {"b": ">=0.0.0", "c": ">=0.0.0"}, + }, + { + "name": "b", + "version": "4.5.6", + "category": "main", + "optional": False, + "python-versions": "*", + "marker": "platform_system == 'Windows'", + "dependencies": {"d": ">=0.0.0"}, + }, + { + "name": "c", + "version": "7.8.9", + "category": "main", + "optional": False, + "python-versions": "*", + "marker": "sys_platform == 'win32'", + "dependencies": {"d": ">=0.0.0"}, + }, + { + "name": "d", + "version": "0.0.1", + "category": "main", + "optional": False, + "python-versions": "*", + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"a": [], "b": [], "c": [], "d": []}, + }, + } + ) + set_package_requires(poetry, skip={"b", "c", "d"}) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = { + "a": dependency_from_pep_508("a==1.2.3; python_version < '3.7'"), + "b": dependency_from_pep_508( + "b==4.5.6; platform_system == 'Windows' and python_version < '3.7'" + ), + "c": dependency_from_pep_508( + "c==7.8.9; sys_platform == 'win32' and python_version < '3.7'" + ), + "d": dependency_from_pep_508( + "d==0.0.1; python_version < '3.7' and platform_system == 'Windows' and sys_platform == 'win32'" + ), + } + + for line in content.strip().split("\n"): + dependency = dependency_from_pep_508(line) + assert dependency.name in expected + expected_dependency = expected.pop(dependency.name) + assert dependency == expected_dependency + assert dependency.marker == expected_dependency.marker + + assert expected == {} + + +def test_exporter_can_export_requirements_txt_with_nested_packages_and_markers_any( + tmp_dir, poetry +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "a", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + }, + { + "name": "b", + "version": "4.5.6", + "category": "dev", + "optional": False, + "python-versions": "*", + "dependencies": {"a": ">=1.2.3"}, + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"a": [], "b": []}, + }, + } + ) + + poetry.package.requires = [ + Factory.create_dependency( + name="a", constraint=dict(version="^1.2.3", python="<3.8") + ), + ] + poetry.package.dev_requires = [ + Factory.create_dependency( + name="b", constraint=dict(version="^4.5.6"), category="dev" + ), + Factory.create_dependency(name="a", constraint=dict(version="^1.2.3")), + ] + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True) + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + assert ( + content + == """\ +a==1.2.3 +a==1.2.3; python_version < "3.8" +b==4.5.6 +""" + ) + + def test_exporter_can_export_requirements_txt_with_standard_packages_and_hashes( tmp_dir, poetry ): From 3d51aa310107332c3900187594f5e71d1c53b289 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 13 Oct 2020 02:44:14 +0200 Subject: [PATCH 033/222] utils/exporter: fix type hint for export() (cherry picked from commit b29450c19034cf34856cf6e2a610507411833e36) --- poetry/utils/exporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry/utils/exporter.py b/poetry/utils/exporter.py index ae85c29cc0b..5f2d6303817 100644 --- a/poetry/utils/exporter.py +++ b/poetry/utils/exporter.py @@ -31,7 +31,7 @@ def export( dev=False, extras=None, with_credentials=False, - ): # type: (str, Path, Union[IO, str], bool, bool, bool) -> None + ): # type: (str, Path, Union[IO, str], bool, bool, Optional[Union[bool, Sequence[str]]], bool) -> None if fmt not in self.ACCEPTED_FORMATS: raise ValueError("Invalid export format: {}".format(fmt)) From ed8a3f97826affd2cf0fdd6767c50299083b017c Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 13 Oct 2020 02:46:38 +0200 Subject: [PATCH 034/222] locker: remove redundant lock data processing (cherry picked from commit a7d66767a089c5604803a0a2d96ddc5fcb41c043) --- poetry/packages/locker.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index 26c2b584504..8eccacd0c97 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -185,15 +185,14 @@ def locked_repository( return packages + @classmethod def get_project_dependencies( - self, project_requires, pinned_versions=False, with_nested=False, with_dev=False - ): # type: (List[Dependency], bool, bool, bool) -> List[Dependency] - packages = self.locked_repository(with_dev).packages - + cls, project_requires, locked_packages, pinned_versions=False, with_nested=False + ): # type: (List[Dependency], List[Package], bool, bool) -> List[Dependency] # group packages entries by name, this is required because requirement might use # different constraints packages_by_name = {} - for pkg in packages: + for pkg in locked_packages: if pkg.name not in packages_by_name: packages_by_name[pkg.name] = [] packages_by_name[pkg.name].append(pkg) @@ -275,8 +274,6 @@ def __walk_level( # we make a copy to avoid any side-effects requirement = deepcopy(requirement) - requirement._category = pkg.category - if pinned_versions: requirement.set_constraint( __get_locked_package(requirement).to_dependency().constraint @@ -285,9 +282,7 @@ def __walk_level( # dependencies use extra to indicate that it was activated via parent # package's extras, this is not required for nested exports as we assume # the resolver already selected this dependency - requirement.marker = requirement.marker.without_extras().intersect( - pkg.marker - ) + requirement.marker = requirement.marker.without_extras() key = (requirement.name, requirement.pretty_constraint) if key not in nested_dependencies: @@ -324,7 +319,9 @@ def get_project_dependency_packages( ) for dependency in self.get_project_dependencies( - project_requires=project_requires, with_nested=True, with_dev=dev, + project_requires=project_requires, + locked_packages=repository.packages, + with_nested=True, ): try: package = repository.find_packages(dependency=dependency)[0] From a46b5c2fa6937eab4953c97080206ca236c6f375 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 13 Oct 2020 02:53:56 +0200 Subject: [PATCH 035/222] locker: ensure correct handling of extras export Previously, when determining nested dependencies, the check for activated extras/features of top level dependencies were done after the nested dependencies were processed. This lead to exports containing in active extras. This change resolves this by pre-selecting top level packages prior to identifying nested dependencies. (cherry picked from commit 2b6f82e9494b99bdb04d0dac90f87eb23548e4a5) --- poetry/packages/locker.py | 22 ++++++++++++++++------ tests/utils/test_exporter.py | 27 +++++++++++++++------------ 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index 8eccacd0c97..50074a4417a 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -318,20 +318,30 @@ def get_project_dependency_packages( ) ) - for dependency in self.get_project_dependencies( - project_requires=project_requires, - locked_packages=repository.packages, - with_nested=True, - ): + # If a package is optional and we haven't opted in to it, do not select + selected = [] + for dependency in project_requires: try: package = repository.find_packages(dependency=dependency)[0] except IndexError: continue - # If a package is optional and we haven't opted in to it, continue if extra_package_names is not None and ( package.optional and package.name not in extra_package_names ): + # a package is locked as optional, but is not activated via extras + continue + + selected.append(dependency) + + for dependency in self.get_project_dependencies( + project_requires=selected, + locked_packages=repository.packages, + with_nested=True, + ): + try: + package = repository.find_packages(dependency=dependency)[0] + except IndexError: continue for extra in dependency.extras: diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index 66b9e81ff53..0cf6f9a202e 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -544,8 +544,17 @@ def test_exporter_exports_requirements_txt_without_optional_packages(tmp_dir, po assert expected == content -def test_exporter_exports_requirements_txt_with_optional_packages_if_opted_in( - tmp_dir, poetry +@pytest.mark.parametrize( + "extras,lines", + [ + (None, ["foo==1.2.3"]), + (False, ["foo==1.2.3"]), + (True, ["bar==4.5.6", "foo==1.2.3", "spam==0.1.0"]), + (["feature_bar"], ["bar==4.5.6", "foo==1.2.3", "spam==0.1.0"]), + ], +) +def test_exporter_exports_requirements_txt_with_optional_packages( + tmp_dir, poetry, extras, lines ): poetry.locker.mock_lock_data( { @@ -590,22 +599,16 @@ def test_exporter_exports_requirements_txt_with_optional_packages_if_opted_in( Path(tmp_dir), "requirements.txt", dev=True, - extras=["feature_bar"], + with_hashes=False, + extras=extras, ) with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: content = f.read() - expected = """\ -bar==4.5.6 \\ - --hash=sha256:67890 -foo==1.2.3 \\ - --hash=sha256:12345 -spam==0.1.0 \\ - --hash=sha256:abcde -""" + expected = "\n".join(lines) - assert expected == content + assert content.strip() == expected def test_exporter_can_export_requirements_txt_with_git_packages(tmp_dir, poetry): From 18abfd7b3d0867f5314cba392061c062a0bfcf94 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 13 Oct 2020 20:01:18 +0200 Subject: [PATCH 036/222] locker: unify duplicate dependencies on export (cherry picked from commit 733736cd1c2ea6189a77e34ab640059dbff2be23) --- poetry/packages/locker.py | 19 +++++++++++++------ tests/utils/test_exporter.py | 18 +++++++----------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index 50074a4417a..9113a303673 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -1,4 +1,3 @@ -import itertools import json import logging import os @@ -6,6 +5,7 @@ from copy import deepcopy from hashlib import sha256 +from typing import Iterable from typing import Iterator from typing import List from typing import Optional @@ -188,7 +188,7 @@ def locked_repository( @classmethod def get_project_dependencies( cls, project_requires, locked_packages, pinned_versions=False, with_nested=False - ): # type: (List[Dependency], List[Package], bool, bool) -> List[Dependency] + ): # type: (List[Dependency], List[Package], bool, bool) -> Iterable[Dependency] # group packages entries by name, this is required because requirement might use # different constraints packages_by_name = {} @@ -296,10 +296,17 @@ def __walk_level( __walk_level(dependencies, 0) - return sorted( - itertools.chain(dependencies, nested_dependencies.values()), - key=lambda x: x.name.lower(), - ) + # Merge same dependencies using marker union + for requirement in dependencies: + key = (requirement.name, requirement.pretty_constraint) + if key not in nested_dependencies: + nested_dependencies[key] = requirement + else: + nested_dependencies[key].marker = nested_dependencies[key].marker.union( + requirement.marker + ) + + return sorted(nested_dependencies.values(), key=lambda x: x.name.lower()) def get_project_dependency_packages( self, project_requires, dev=False, extras=None diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index 0cf6f9a202e..d810bb8b08b 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -256,8 +256,12 @@ def test_exporter_can_export_requirements_txt_with_nested_packages_and_markers( assert expected == {} +@pytest.mark.parametrize( + "dev,lines", + [(False, ['a==1.2.3; python_version < "3.8"']), (True, ["a==1.2.3", "b==4.5.6"])], +) def test_exporter_can_export_requirements_txt_with_nested_packages_and_markers_any( - tmp_dir, poetry + tmp_dir, poetry, dev, lines ): poetry.locker.mock_lock_data( { @@ -295,24 +299,16 @@ def test_exporter_can_export_requirements_txt_with_nested_packages_and_markers_a Factory.create_dependency( name="b", constraint=dict(version="^4.5.6"), category="dev" ), - Factory.create_dependency(name="a", constraint=dict(version="^1.2.3")), ] exporter = Exporter(poetry) - exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True) + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=dev) with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: content = f.read() - assert ( - content - == """\ -a==1.2.3 -a==1.2.3; python_version < "3.8" -b==4.5.6 -""" - ) + assert content.strip() == "\n".join(lines) def test_exporter_can_export_requirements_txt_with_standard_packages_and_hashes( From 83ec6e9ff8381fd66574882cd491a92127c5f100 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 13 Oct 2020 20:34:42 +0200 Subject: [PATCH 037/222] locker: refactor to reduce code complexity (cherry picked from commit ed43d94b423a7bf53dfb530a549b686d016ab891) --- poetry/packages/locker.py | 174 +++++++++++++++++++++----------------- 1 file changed, 97 insertions(+), 77 deletions(-) diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index 9113a303673..9dd75e66519 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -5,11 +5,14 @@ from copy import deepcopy from hashlib import sha256 +from typing import Dict from typing import Iterable from typing import Iterator from typing import List from typing import Optional from typing import Sequence +from typing import Set +from typing import Tuple from typing import Union from tomlkit import array @@ -185,36 +188,107 @@ def locked_repository( return packages + @staticmethod + def __get_locked_package( + _dependency, packages_by_name + ): # type: (Dependency, Dict[str, List[Package]]) -> Optional[Package] + """ + Internal helper to identify corresponding locked package using dependency + version constraints. + """ + for _package in packages_by_name.get(_dependency.name, []): + if _dependency.constraint.allows(_package.version): + return _package + return None + + @classmethod + def __walk_dependency_level( + cls, + dependencies, + level, + pinned_versions, + packages_by_name, + project_level_dependencies, + nested_dependencies, + ): # type: (List[Dependency], int, bool, Dict[str, List[Package]], Set[str], Dict[Tuple[str, str], Dependency]) -> Dict[Tuple[str, str], Dependency] + if not dependencies: + return nested_dependencies + + next_level_dependencies = [] + + for requirement in dependencies: + locked_package = cls.__get_locked_package(requirement, packages_by_name) + + if locked_package: + for require in locked_package.requires: + if require.marker.is_empty(): + require.marker = requirement.marker + else: + require.marker = require.marker.intersect(requirement.marker) + + require.marker = require.marker.intersect(locked_package.marker) + next_level_dependencies.append(require) + + if requirement.name in project_level_dependencies and level == 0: + # project level dependencies take precedence + continue + + if locked_package: + # create dependency from locked package to retain dependency metadata + # if this is not done, we can end-up with incorrect nested dependencies + marker = requirement.marker + requirement = locked_package.to_dependency() + requirement.marker = requirement.marker.intersect(marker) + else: + # we make a copy to avoid any side-effects + requirement = deepcopy(requirement) + + if pinned_versions: + requirement.set_constraint( + cls.__get_locked_package(requirement, packages_by_name) + .to_dependency() + .constraint + ) + + # dependencies use extra to indicate that it was activated via parent + # package's extras, this is not required for nested exports as we assume + # the resolver already selected this dependency + requirement.marker = requirement.marker.without_extras() + + key = (requirement.name, requirement.pretty_constraint) + if key not in nested_dependencies: + nested_dependencies[key] = requirement + else: + nested_dependencies[key].marker = nested_dependencies[ + key + ].marker.intersect(requirement.marker) + + return cls.__walk_dependency_level( + dependencies=next_level_dependencies, + level=level + 1, + pinned_versions=pinned_versions, + packages_by_name=packages_by_name, + project_level_dependencies=project_level_dependencies, + nested_dependencies=nested_dependencies, + ) + @classmethod def get_project_dependencies( cls, project_requires, locked_packages, pinned_versions=False, with_nested=False ): # type: (List[Dependency], List[Package], bool, bool) -> Iterable[Dependency] - # group packages entries by name, this is required because requirement might use - # different constraints + # group packages entries by name, this is required because requirement might use different constraints packages_by_name = {} for pkg in locked_packages: if pkg.name not in packages_by_name: packages_by_name[pkg.name] = [] packages_by_name[pkg.name].append(pkg) - def __get_locked_package( - _dependency, - ): # type: (Dependency) -> Optional[Package] - """ - Internal helper to identify corresponding locked package using dependency - version constraints. - """ - for _package in packages_by_name.get(_dependency.name, []): - if _dependency.constraint.allows(_package.version): - return _package - return None - project_level_dependencies = set() dependencies = [] for dependency in project_requires: dependency = deepcopy(dependency) - locked_package = __get_locked_package(dependency) + locked_package = cls.__get_locked_package(dependency, packages_by_name) if locked_package: locked_dependency = locked_package.to_dependency() locked_dependency.marker = dependency.marker.intersect( @@ -233,68 +307,14 @@ def __get_locked_package( # return only with project level dependencies return dependencies - nested_dependencies = dict() - - def __walk_level( - __dependencies, __level - ): # type: (List[Dependency], int) -> None - if not __dependencies: - return - - __next_level = [] - - for requirement in __dependencies: - __locked_package = __get_locked_package(requirement) - - if __locked_package: - for require in __locked_package.requires: - if require.marker.is_empty(): - require.marker = requirement.marker - else: - require.marker = require.marker.intersect( - requirement.marker - ) - - require.marker = require.marker.intersect( - __locked_package.marker - ) - __next_level.append(require) - - if requirement.name in project_level_dependencies and __level == 0: - # project level dependencies take precedence - continue - - if __locked_package: - # create dependency from locked package to retain dependency metadata - # if this is not done, we can end-up with incorrect nested dependencies - marker = requirement.marker - requirement = __locked_package.to_dependency() - requirement.marker = requirement.marker.intersect(marker) - else: - # we make a copy to avoid any side-effects - requirement = deepcopy(requirement) - - if pinned_versions: - requirement.set_constraint( - __get_locked_package(requirement).to_dependency().constraint - ) - - # dependencies use extra to indicate that it was activated via parent - # package's extras, this is not required for nested exports as we assume - # the resolver already selected this dependency - requirement.marker = requirement.marker.without_extras() - - key = (requirement.name, requirement.pretty_constraint) - if key not in nested_dependencies: - nested_dependencies[key] = requirement - else: - nested_dependencies[key].marker = nested_dependencies[ - key - ].marker.intersect(requirement.marker) - - return __walk_level(__next_level, __level + 1) - - __walk_level(dependencies, 0) + nested_dependencies = cls.__walk_dependency_level( + dependencies=dependencies, + level=0, + pinned_versions=pinned_versions, + packages_by_name=packages_by_name, + project_level_dependencies=project_level_dependencies, + nested_dependencies=dict(), + ) # Merge same dependencies using marker union for requirement in dependencies: From 8f6274cd6ebdab06883b0865f10973134c66853b Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Wed, 14 Oct 2020 18:33:08 +0200 Subject: [PATCH 038/222] Update CHANGELOG.md --- CHANGELOG.md | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e375aeeffb6..ec01b75abde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,45 @@ # Change Log +## [1.1.3] - 2020-10-14 + +### Changed + +- Python version support deprecation warning is now written to `stderr`. ([#3131](https://github.com/python-poetry/poetry/pull/3131)) + +### Fixed + +- Fixed `KeyError` when `PATH` is not defined in environment variables. ([#3159](https://github.com/python-poetry/poetry/pull/3159)) +- Fixed error when using `config` command in a directory with an existing `pyproject.toml` without any Poetry configuration. ([#3172](https://github.com/python-poetry/poetry/pull/3172)) +- Fixed incorrect inspection of package requirements when same dependency is specified multiple times with unique markers. ([#3147](https://github.com/python-poetry/poetry/pull/3147)) +- Fixed `show` command to use already resolved package metadata. ([#3117](https://github.com/python-poetry/poetry/pull/3117)) +- Fixed multiple issues with `export` command output when using `requirements.txt` format. ([#3119](https://github.com/python-poetry/poetry/pull/3119)) + +## [1.1.2] - 2020-10-06 + +### Changed +- Dependency installation of editable packages and all uninstall operations are now performed serially within their corresponding priority groups. ([#3099](https://github.com/python-poetry/poetry/pull/3099)) +- Improved package metadata inspection of nested poetry projects within project path dependencies. ([#3105](https://github.com/python-poetry/poetry/pull/3105)) + +### Fixed + +- Fixed export of `requirements.txt` when project dependency contains git dependencies. ([#3100](https://github.com/python-poetry/poetry/pull/3100)) + +## [1.1.1] - 2020-10-05 + +### Added + +- Added `--no-update` option to `lock` command. ([#3034](https://github.com/python-poetry/poetry/pull/3034)) + +### Fixed + +- Fixed resolution of packages with missing required extras. ([#3035](https://github.com/python-poetry/poetry/pull/3035)) +- Fixed export of `requirements.txt` dependencies to include development dependencies. ([#3024](https://github.com/python-poetry/poetry/pull/3024)) +- Fixed incorrect selection of unsupported binary distribution formats when selecting a package artifact to install. ([#3058](https://github.com/python-poetry/poetry/pull/3058)) +- Fixed incorrect use of system executable when building package distributions via `build` command. ([#3056](https://github.com/python-poetry/poetry/pull/3056)) +- Fixed errors in `init` command when specifying `--dependency` in non-interactive mode when a `pyproject.toml` file already exists. ([#3076](https://github.com/python-poetry/poetry/pull/3076)) +- Fixed incorrect selection of configured source url when a publish repository url configuration with the same name already exists. ([#3047](https://github.com/python-poetry/poetry/pull/3047)) +- Fixed dependency resolution issues when the same package is specified in multiple dependency extras. ([#3046](https://github.com/python-poetry/poetry/pull/3046)) + ## [1.1.0] - 2020-10-01 ### Changed @@ -1023,7 +1063,10 @@ Initial release -[Unreleased]: https://github.com/python-poetry/poetry/compare/1.1.0...master +[Unreleased]: https://github.com/python-poetry/poetry/compare/1.1.3...master +[1.1.3]: https://github.com/python-poetry/poetry/compare/1.1.3 +[1.1.2]: https://github.com/python-poetry/poetry/releases/tag/1.1.2 +[1.1.1]: https://github.com/python-poetry/poetry/releases/tag/1.1.1 [1.1.0]: https://github.com/python-poetry/poetry/releases/tag/1.1.0 [1.1.0rc1]: https://github.com/python-poetry/poetry/releases/tag/1.1.0rc1 [1.1.0b4]: https://github.com/python-poetry/poetry/releases/tag/1.1.0b4 From 20a3161b0e3f417f04b833c192dcb72076098a4b Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Wed, 14 Oct 2020 18:35:34 +0200 Subject: [PATCH 039/222] Bump version to 1.2.0a0 --- poetry/__version__.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/poetry/__version__.py b/poetry/__version__.py index 6849410aae0..bc0be1b6fa5 100644 --- a/poetry/__version__.py +++ b/poetry/__version__.py @@ -1 +1 @@ -__version__ = "1.1.0" +__version__ = "1.2.0a0" diff --git a/pyproject.toml b/pyproject.toml index f099a4cac80..59c49f74cf7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "poetry" -version = "1.1.0" +version = "1.2.0a0" description = "Python dependency management and packaging made easy." authors = [ "Sébastien Eustace " From 9c8155c6a15376e41e01f4867f239b0299a8da35 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 13 Oct 2020 14:24:57 +0200 Subject: [PATCH 040/222] Drop Python 2.7/3.5 support validation This change drops support for Python 2.7 and 3.5 in tox, ci and release workflows in preparation for dropping support for these versions in Poetry 1.2.0. --- .cirrus.yml | 8 +++----- .github/workflows/main.yml | 2 +- .github/workflows/release.yml | 15 ++------------- .github/workflows/skip.yml | 2 +- sonnet | 2 -- tox.ini | 2 +- 6 files changed, 8 insertions(+), 23 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index e2341cdf4b7..daf4efc9203 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -7,8 +7,8 @@ test_task: skip: "!changesInclude('.cirrus.yml', 'poetry.lock', 'pyproject.toml', '**.json','**.py')" env: matrix: - - PYTHON: python2.7 - - PYTHON: python3.7 + - PYTHON: python3.6 + - PYTHON: python3.8 python_script: - PYPACKAGE=$(printf '%s' $PYTHON | tr -d '.') - SQLPACKAGE=$(printf '%s-sqlite3' $PYPACKAGE | sed 's/thon//') @@ -31,7 +31,6 @@ release_task: env: GITHUB_TOKEN: ENCRYPTED[2b573a2d28a03523ac6fb5b3c2f513a41c0a98db81e40e50e1d103b171f85c57e58ae38d957499dbf7fd7635cfcfd7be] PYTHON: python3.8 - PYTHON27: python2.7 PYTHON36: python3.6 PYTHON37: python3.7 PYTHON38: python3.8 @@ -40,9 +39,8 @@ release_task: - image_family: freebsd-12-1-snap - image_family: freebsd-13-0-snap - image_family: freebsd-11-4-snap - python_script: pkg install -y curl bash jq python3 python27 python36 python37 python38 + python_script: pkg install -y curl bash jq python3 python36 python37 python38 pip_script: - - python2.7 -m ensurepip - python3.6 -m ensurepip - python3.7 -m ensurepip - python3.8 -m ensurepip diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 32379cdf8b2..4c9bdd876c7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: os: [Ubuntu, MacOS, Windows] - python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 43e7d357cd1..f6a15041ae0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,14 +53,10 @@ jobs: poetry install --no-dev - name: Preparing Python executables run: | - curl -L https://github.com/sdispater/python-binaries/releases/download/2.7.17/python-2.7.17.macos.tar.xz -o python-2.7.17.tar.xz - curl -L https://github.com/sdispater/python-binaries/releases/download/3.5.9/python-3.5.9.macos.tar.xz -o python-3.5.9.tar.xz curl -L https://github.com/sdispater/python-binaries/releases/download/3.6.8/python-3.6.8.macos.tar.xz -o python-3.6.8.tar.xz curl -L https://github.com/sdispater/python-binaries/releases/download/3.7.6/python-3.7.6.macos.tar.xz -o python-3.7.6.tar.xz curl -L https://github.com/sdispater/python-binaries/releases/download/3.8.3/python-3.8.3.macos.tar.xz -o python-3.8.3.tar.xz curl -L https://github.com/sdispater/python-binaries/releases/download/3.9.0b4/python-3.9.0b4.macos.tar.xz -o python-3.9.0b4.tar.xz - tar -zxf python-2.7.17.tar.xz - tar -zxf python-3.5.9.tar.xz tar -zxf python-3.6.8.tar.xz tar -zxf python-3.7.6.tar.xz tar -zxf python-3.8.3.tar.xz @@ -68,7 +64,7 @@ jobs: - name: Build specific release run: | source $HOME/.poetry/env - poetry run python sonnet make release --ansi -P "2.7:python-2.7.17/bin/python" -P "3.5:python-3.5.9/bin/python" -P "3.6:python-3.6.8/bin/python" -P "3.7:python-3.7.6/bin/python" -P "3.8:python-3.8.3/bin/python" -P "3.9:python-3.9.0b4/bin/python" + poetry run python sonnet make release --ansi -P "3.6:python-3.6.8/bin/python" -P "3.7:python-3.7.6/bin/python" -P "3.8:python-3.8.3/bin/python" -P "3.9:python-3.9.0b4/bin/python" - name: Upload release file uses: actions/upload-artifact@v1 with: @@ -104,21 +100,14 @@ jobs: poetry install --no-dev - name: Preparing Python executables run: | - Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/2.7.17/python-2.7.17.windows.tar.xz -O python-2.7.17.tar.xz - Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.5.4/python-3.5.4.windows.tar.xz -O python-3.5.4.tar.xz Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.6.8/python-3.6.8.windows.tar.xz -O python-3.6.8.tar.xz Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.7.6/python-3.7.6.windows.tar.xz -O python-3.7.6.tar.xz Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.8.3/python-3.8.3.windows.tar.xz -O python-3.8.3.tar.xz Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.9.0b4/python-3.9.0b4.windows.tar.xz -O python-3.9.0b4.tar.xz - 7z x python-2.7.17.tar.xz - 7z x python-3.5.4.tar.xz 7z x python-3.6.8.tar.xz 7z x python-3.7.6.tar.xz 7z x python-3.8.3.tar.xz 7z x python-3.9.0b4.tar.xz - 7z x python-2.7.17.tar - 7z x python-3.4.4.tar - 7z x python-3.5.4.tar 7z x python-3.6.8.tar 7z x python-3.7.6.tar 7z x python-3.8.3.tar @@ -126,7 +115,7 @@ jobs: - name: Build specific release run: | $env:Path += ";$env:Userprofile\.poetry\bin" - poetry run python sonnet make release --ansi -P "2.7:python-2.7.17\python.exe" -P "3.5:python-3.5.4\python.exe" -P "3.6:python-3.6.8\python.exe" -P "3.7:python-3.7.6\python.exe" -P "3.8:python-3.8.3\python.exe" -P "3.9:python-3.9.0b4\python.exe" + poetry run python sonnet make release --ansi -P "3.6:python-3.6.8\python.exe" -P "3.7:python-3.7.6\python.exe" -P "3.8:python-3.8.3\python.exe" -P "3.9:python-3.9.0b4\python.exe" - name: Upload release file uses: actions/upload-artifact@v1 with: diff --git a/.github/workflows/skip.yml b/.github/workflows/skip.yml index aea8d88274c..752ed7fa911 100644 --- a/.github/workflows/skip.yml +++ b/.github/workflows/skip.yml @@ -32,6 +32,6 @@ jobs: strategy: matrix: os: [Ubuntu, MacOS, Windows] - python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9] steps: - run: exit 0 diff --git a/sonnet b/sonnet index ecfda99037b..facbe34e280 100755 --- a/sonnet +++ b/sonnet @@ -25,8 +25,6 @@ class MakeReleaseCommand(Command): """ PYTHON = { - "2.7": "python2.7", - "3.5": "python3.5", "3.6": "python3.6", "3.7": "python3.7", "3.8": "python3.8", diff --git a/tox.ini b/tox.ini index 17834f2e709..722c0e1adf1 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] minversion = 3.3.0 isolated_build = True -envlist = py27, py35, py36, py37, py38, py39, doc +envlist = py36, py37, py38, py39, doc [testenv] whitelist_externals = poetry From cb34c3d097ad21aee5d1731a739b0c70f5cd351d Mon Sep 17 00:00:00 2001 From: finswimmer Date: Fri, 16 Oct 2020 14:51:25 +0200 Subject: [PATCH 041/222] config: support virtualenv `--always-copy` option Mitigates: #3134 --- docs/docs/configuration.md | 7 +++++ poetry/config/config.py | 19 ++++++++++-- poetry/console/commands/config.py | 5 ++++ poetry/utils/env.py | 41 ++++++++++++++++++-------- tests/console/commands/env/helpers.py | 4 ++- tests/console/commands/env/test_use.py | 4 ++- tests/console/commands/test_config.py | 3 ++ tests/utils/test_env.py | 32 +++++++++++++++----- 8 files changed, 90 insertions(+), 25 deletions(-) diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index 0378f2fca9e..782c2f9ac87 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -34,6 +34,7 @@ which will give you something similar to this: cache-dir = "/path/to/cache/directory" virtualenvs.create = true virtualenvs.in-project = null +virtualenvs.options.always-copy = true virtualenvs.path = "{cache-dir}/virtualenvs" # /path/to/cache/directory/virtualenvs ``` @@ -128,6 +129,12 @@ If not set explicitly (default), `poetry` will use the virtualenv from the `.ven Directory where virtual environments will be created. Defaults to `{cache-dir}/virtualenvs` (`{cache-dir}\virtualenvs` on Windows). +### `virtualenvs.options.always-copy`: boolean + +If set to `true` the `--always-copy` parameter is passed to `virtualenv` on creation of the venv. Thus all needed files are copied into the venv instead of symlinked. +Defaults to `false`. + + ### `repositories.`: string Set a new alternative repository. See [Repositories](/docs/repositories/) for more information. diff --git a/poetry/config/config.py b/poetry/config/config.py index be585575c05..d1fb421e53c 100644 --- a/poetry/config/config.py +++ b/poetry/config/config.py @@ -36,6 +36,7 @@ class Config(object): "create": True, "in-project": None, "path": os.path.join("{cache-dir}", "virtualenvs"), + "options": {"always-copy": False}, }, "experimental": {"new-installer": True}, } @@ -87,7 +88,11 @@ def _all(config, parent_key=""): for key in config: value = self.get(parent_key + key) if isinstance(value, dict): - all_[key] = _all(config[key], parent_key=key + ".") + if parent_key != "": + current_parent = parent_key + key + "." + else: + current_parent = key + "." + all_[key] = _all(config[key], parent_key=current_parent) continue all_[key] = value @@ -131,14 +136,22 @@ def process(self, value): # type: (Any) -> Any return re.sub(r"{(.+?)}", lambda m: self.get(m.group(1)), value) def _get_validator(self, name): # type: (str) -> Callable - if name in {"virtualenvs.create", "virtualenvs.in-project"}: + if name in { + "virtualenvs.create", + "virtualenvs.in-project", + "virtualenvs.options.always-copy", + }: return boolean_validator if name == "virtualenvs.path": return str def _get_normalizer(self, name): # type: (str) -> Callable - if name in {"virtualenvs.create", "virtualenvs.in-project"}: + if name in { + "virtualenvs.create", + "virtualenvs.in-project", + "virtualenvs.options.always-copy", + }: return boolean_normalizer if name == "virtualenvs.path": diff --git a/poetry/console/commands/config.py b/poetry/console/commands/config.py index 70b6c7673f1..52e1cf125ab 100644 --- a/poetry/console/commands/config.py +++ b/poetry/console/commands/config.py @@ -64,6 +64,11 @@ def unique_config_values(self): boolean_normalizer, True, ), + "virtualenvs.options.always-copy": ( + boolean_validator, + boolean_normalizer, + False, + ), } return unique_config_values diff --git a/poetry/utils/env.py b/poetry/utils/env.py index ccd855b5c60..0e3df320d6a 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -642,7 +642,11 @@ def create_venv( "Creating virtualenv {} in {}".format(name, str(venv_path)) ) - self.build_venv(venv, executable=executable) + self.build_venv( + venv, + executable=executable, + flags=self._poetry.config.get("virtualenvs.options"), + ) else: if force: if not env.is_sane(): @@ -655,7 +659,11 @@ def create_venv( "Recreating virtualenv {} in {}".format(name, str(venv)) ) self.remove_venv(venv) - self.build_venv(venv, executable=executable) + self.build_venv( + venv, + executable=executable, + flags=self._poetry.config.get("virtualenvs.options"), + ) elif io.is_very_verbose(): io.write_line("Virtualenv {} already exists.".format(name)) @@ -679,19 +687,26 @@ def create_venv( @classmethod def build_venv( - cls, path, executable=None - ): # type: (Union[Path,str], Optional[Union[str, Path]]) -> virtualenv.run.session.Session + cls, path, executable=None, flags=None + ): # type: (Union[Path,str], Optional[Union[str, Path]], Dict[str, bool]) -> virtualenv.run.session.Session + flags = flags or {} + if isinstance(executable, Path): executable = executable.resolve().as_posix() - return virtualenv.cli_run( - [ - "--no-download", - "--no-periodic-update", - "--python", - executable or sys.executable, - str(path), - ] - ) + + args = [ + "--no-download", + "--no-periodic-update", + "--python", + executable or sys.executable, + str(path), + ] + + for flag, value in flags.items(): + if value is True: + args.insert(0, "--{}".format(flag)) + + return virtualenv.cli_run(args) @classmethod def remove_venv(cls, path): # type: (Union[Path,str]) -> None diff --git a/tests/console/commands/env/helpers.py b/tests/console/commands/env/helpers.py index 17d4c2ac274..1c7e64dc17e 100644 --- a/tests/console/commands/env/helpers.py +++ b/tests/console/commands/env/helpers.py @@ -5,7 +5,9 @@ from poetry.utils._compat import Path -def build_venv(path, executable=None): # type: (Union[Path,str], Optional[str]) -> () +def build_venv( + path, executable=None, flags=None +): # type: (Union[Path,str], Optional[str], bool) -> () Path(path).mkdir(parents=True, exist_ok=True) diff --git a/tests/console/commands/env/test_use.py b/tests/console/commands/env/test_use.py index 563e0ac7ffe..b3ea3458d06 100644 --- a/tests/console/commands/env/test_use.py +++ b/tests/console/commands/env/test_use.py @@ -50,7 +50,9 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( tester.execute("3.7") venv_py37 = venv_cache / "{}-py3.7".format(venv_name) - mock_build_env.assert_called_with(venv_py37, executable="python3.7") + mock_build_env.assert_called_with( + venv_py37, executable="python3.7", flags={"always-copy": False} + ) envs_file = TOMLFile(venv_cache / "envs.toml") assert envs_file.exists() diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index bb13a268e2f..9923f1b77af 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -30,6 +30,7 @@ def test_list_displays_default_value_if_not_set(tester, config): experimental.new-installer = true virtualenvs.create = true virtualenvs.in-project = null +virtualenvs.options.always-copy = false virtualenvs.path = {path} # /foo{sep}virtualenvs """.format( path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), sep=os.path.sep @@ -47,6 +48,7 @@ def test_list_displays_set_get_setting(tester, config): experimental.new-installer = true virtualenvs.create = false virtualenvs.in-project = null +virtualenvs.options.always-copy = false virtualenvs.path = {path} # /foo{sep}virtualenvs """.format( path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), sep=os.path.sep @@ -86,6 +88,7 @@ def test_list_displays_set_get_local_setting(tester, config): experimental.new-installer = true virtualenvs.create = false virtualenvs.in-project = null +virtualenvs.options.always-copy = false virtualenvs.path = {path} # /foo{sep}virtualenvs """.format( path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), sep=os.path.sep diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 3779623839f..8c816a2d32a 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -118,7 +118,9 @@ def test_env_get_venv_with_venv_folder_present( assert venv.path == in_project_venv_dir -def build_venv(path, executable=None): # type: (Union[Path,str], Optional[str]) -> () +def build_venv( + path, executable=None, flags=None +): # type: (Union[Path,str], Optional[str], bool) -> () os.mkdir(str(path)) @@ -156,7 +158,9 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( venv_name = EnvManager.generate_env_name("simple-project", str(poetry.file.parent)) m.assert_called_with( - Path(tmp_dir) / "{}-py3.7".format(venv_name), executable="python3.7" + Path(tmp_dir) / "{}-py3.7".format(venv_name), + executable="python3.7", + flags={"always-copy": False}, ) envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") @@ -274,7 +278,9 @@ def test_activate_activates_different_virtualenv_with_envs_file( env = manager.activate("python3.6", NullIO()) m.assert_called_with( - Path(tmp_dir) / "{}-py3.6".format(venv_name), executable="python3.6" + Path(tmp_dir) / "{}-py3.6".format(venv_name), + executable="python3.6", + flags={"always-copy": False}, ) assert envs_file.exists() @@ -326,7 +332,9 @@ def test_activate_activates_recreates_for_different_patch( env = manager.activate("python3.7", NullIO()) build_venv_m.assert_called_with( - Path(tmp_dir) / "{}-py3.7".format(venv_name), executable="python3.7" + Path(tmp_dir) / "{}-py3.7".format(venv_name), + executable="python3.7", + flags={"always-copy": False}, ) remove_venv_m.assert_called_with(Path(tmp_dir) / "{}-py3.7".format(venv_name)) @@ -654,7 +662,9 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ manager.create_venv(NullIO()) m.assert_called_with( - Path("/foo/virtualenvs/{}-py3.7".format(venv_name)), executable="python3" + Path("/foo/virtualenvs/{}-py3.7".format(venv_name)), + executable="python3", + flags={"always-copy": False}, ) @@ -678,7 +688,9 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific manager.create_venv(NullIO()) m.assert_called_with( - Path("/foo/virtualenvs/{}-py3.9".format(venv_name)), executable="python3.9" + Path("/foo/virtualenvs/{}-py3.9".format(venv_name)), + executable="python3.9", + flags={"always-copy": False}, ) @@ -767,6 +779,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility( ) ), executable=None, + flags={"always-copy": False}, ) @@ -804,6 +817,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable( ) ), executable="python{}.{}".format(version.major, version.minor - 1), + flags={"always-copy": False}, ) @@ -834,7 +848,11 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( manager.activate("python3.7", NullIO()) - m.assert_called_with(poetry.file.parent / ".venv", executable="python3.7") + m.assert_called_with( + poetry.file.parent / ".venv", + executable="python3.7", + flags={"always-copy": False}, + ) envs_file = TOMLFile(Path(tmp_dir) / "virtualenvs" / "envs.toml") assert not envs_file.exists() From 9c085a585c093a722717fec318bfc6610e93a9f1 Mon Sep 17 00:00:00 2001 From: Arjan Keeman Date: Fri, 16 Oct 2020 14:03:57 +0200 Subject: [PATCH 042/222] Ensure editable builder generate valid scripts --- poetry/masonry/builders/editable.py | 2 +- tests/fixtures/simple_project/pyproject.toml | 1 + tests/masonry/builders/test_editable_builder.py | 16 ++++++++++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/poetry/masonry/builders/editable.py b/poetry/masonry/builders/editable.py index 11bda4efcbd..99136ecbd3e 100644 --- a/poetry/masonry/builders/editable.py +++ b/poetry/masonry/builders/editable.py @@ -139,7 +139,7 @@ def _add_scripts(self): for script in scripts: name, script = script.split(" = ") module, callable_ = script.split(":") - callable_holder = callable_.rsplit(".", 1)[0] + callable_holder = callable_.split(".", 1)[0] script_file = scripts_path.joinpath(name) self._debug( diff --git a/tests/fixtures/simple_project/pyproject.toml b/tests/fixtures/simple_project/pyproject.toml index 0b41162c6f4..0fd938e41a0 100644 --- a/tests/fixtures/simple_project/pyproject.toml +++ b/tests/fixtures/simple_project/pyproject.toml @@ -27,3 +27,4 @@ python = "~2.7 || ^3.4" [tool.poetry.scripts] foo = "foo:bar" baz = "bar:baz.boom.bim" +fox = "fuz.foo:bar.baz" diff --git a/tests/masonry/builders/test_editable_builder.py b/tests/masonry/builders/test_editable_builder.py index e2aac5beb41..3aee74e7c77 100644 --- a/tests/masonry/builders/test_editable_builder.py +++ b/tests/masonry/builders/test_editable_builder.py @@ -92,7 +92,7 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ assert "poetry" == dist_info.joinpath("INSTALLER").read_text() assert ( - "[console_scripts]\nbaz=bar:baz.boom.bim\nfoo=foo:bar\n\n" + "[console_scripts]\nbaz=bar:baz.boom.bim\nfoo=foo:bar\nfox=fuz.foo:bar.baz\n\n" == dist_info.joinpath("entry_points.txt").read_text() ) @@ -140,7 +140,7 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ baz_script = """\ #!{python} -from bar import baz.boom +from bar import baz if __name__ == '__main__': baz.boom.bim() @@ -162,6 +162,18 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ assert foo_script == tmp_venv._bin_dir.joinpath("foo").read_text() + fox_script = """\ +#!{python} +from fuz.foo import bar + +if __name__ == '__main__': + bar.baz() +""".format( + python=tmp_venv._bin("python") + ) + + assert fox_script == tmp_venv._bin_dir.joinpath("fox").read_text() + def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts( extended_poetry, From 559687837cca46fba773c2d45be12387a1490e8f Mon Sep 17 00:00:00 2001 From: Cere Blanco <743526+cereblanco@users.noreply.github.com> Date: Mon, 19 Oct 2020 20:16:31 +0800 Subject: [PATCH 043/222] tests: re-enable lock --no-update cases on windows --- tests/console/commands/test_lock.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/console/commands/test_lock.py b/tests/console/commands/test_lock.py index 823a8ba4beb..44c72967b32 100644 --- a/tests/console/commands/test_lock.py +++ b/tests/console/commands/test_lock.py @@ -1,5 +1,4 @@ import shutil -import sys import pytest @@ -26,9 +25,6 @@ def poetry_with_old_lockfile(fixture_dir, source_dir): return poetry -@pytest.mark.skipif( - sys.platform == "win32", reason="does not work on windows under ci environments" -) def test_lock_no_update(command_tester_factory, poetry_with_old_lockfile, http): http.disable() From a66033bd8fa99488fccd21b90afb006ebe430706 Mon Sep 17 00:00:00 2001 From: Etty Date: Mon, 19 Oct 2020 22:49:59 +0100 Subject: [PATCH 044/222] add: fix revert for dry-run and interrupts This change correctly reverts pyproject.toml changes for dry runs as well as keyboard interrupts. Resolves: #2933 #318 --- poetry/console/commands/add.py | 33 +++++++++++++++--------------- tests/console/commands/test_add.py | 23 +++++++++++++++++++++ 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/poetry/console/commands/add.py b/poetry/console/commands/add.py index 3ad77ad686b..29cf07d2a26 100644 --- a/poetry/console/commands/add.py +++ b/poetry/console/commands/add.py @@ -100,7 +100,6 @@ def handle(self): packages = [name for name in packages if name not in existing_packages] if not packages: - self.poetry.file.write(content) self.line("Nothing to add.") return 0 @@ -152,29 +151,29 @@ def handle(self): poetry_content[section][_constraint["name"]] = constraint - # Write new content - self.poetry.file.write(content) + try: + # Write new content + self.poetry.file.write(content) - # Cosmetic new line - self.line("") + # Cosmetic new line + self.line("") - # Update packages - self.reset_poetry() + # Update packages + self.reset_poetry() - self._installer.set_package(self.poetry.package) - self._installer.dry_run(self.option("dry-run")) - self._installer.verbose(self._io.is_verbose()) - self._installer.update(True) - if self.option("lock"): - self._installer.lock() + self._installer.set_package(self.poetry.package) + self._installer.dry_run(self.option("dry-run")) + self._installer.verbose(self._io.is_verbose()) + self._installer.update(True) + if self.option("lock"): + self._installer.lock() - self._installer.whitelist([r["name"] for r in requirements]) + self._installer.whitelist([r["name"] for r in requirements]) - try: status = self._installer.run() - except Exception: + except BaseException: + # Using BaseException here as some exceptions, eg: KeyboardInterrupt, do not inherit from Exception self.poetry.file.write(original_content) - raise if status != 0 or self.option("dry-run"): diff --git a/tests/console/commands/test_add.py b/tests/console/commands/test_add.py index 09fae525aec..19998592ac3 100644 --- a/tests/console/commands/test_add.py +++ b/tests/console/commands/test_add.py @@ -1630,3 +1630,26 @@ def test_add_with_lock_old_installer(app, repo, installer, old_tester): """ assert expected == old_tester.io.fetch_output() + + +def test_add_keyboard_interrupt_restore_content(app, repo, installer, tester, mocker): + mocker.patch( + "poetry.installation.installer.Installer.run", side_effect=KeyboardInterrupt() + ) + original_content = app.poetry.file.read() + + repo.add_package(get_package("cachy", "0.2.0")) + + tester.execute("cachy --dry-run") + + assert original_content == app.poetry.file.read() + + +def test_dry_run_restore_original_content(app, repo, installer, tester): + original_content = app.poetry.file.read() + + repo.add_package(get_package("cachy", "0.2.0")) + + tester.execute("cachy --dry-run") + + assert original_content == app.poetry.file.read() From 68d6939f2e9cac1178fc5ffbcc3bac7a9db43fce Mon Sep 17 00:00:00 2001 From: Etty Date: Mon, 19 Oct 2020 22:57:47 +0100 Subject: [PATCH 045/222] export: use --trusted-host for non HTTPS index Resolves: #1894 --- poetry/utils/exporter.py | 4 ++++ tests/utils/test_exporter.py | 46 ++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/poetry/utils/exporter.py b/poetry/utils/exporter.py index 5f2d6303817..f7c40f6975f 100644 --- a/poetry/utils/exporter.py +++ b/poetry/utils/exporter.py @@ -7,6 +7,7 @@ from poetry.poetry import Poetry from poetry.utils._compat import Path from poetry.utils._compat import decode +from poetry.utils._compat import urlparse class Exporter(object): @@ -139,6 +140,9 @@ def _export_requirements_txt( url = ( repository.authenticated_url if with_credentials else repository.url ) + parsed_url = urlparse.urlsplit(url) + if parsed_url.scheme == "http": + indexes_header += "--trusted-host {}\n".format(parsed_url.netloc) indexes_header += "--extra-index-url {}\n".format(url) content = indexes_header + "\n" + content diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index d810bb8b08b..c5ce0214ff7 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -972,6 +972,52 @@ def test_exporter_exports_requirements_txt_with_legacy_packages(tmp_dir, poetry) assert expected == content +def test_exporter_exports_requirements_txt_with_legacy_packages_trusted_host( + tmp_dir, poetry +): + poetry.pool.add_repository(LegacyRepository("custom", "http://example.com/simple",)) + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "bar", + "version": "4.5.6", + "category": "dev", + "optional": False, + "python-versions": "*", + "source": { + "type": "legacy", + "url": "http://example.com/simple", + "reference": "", + }, + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"bar": ["67890"]}, + }, + } + ) + set_package_requires(poetry) + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True) + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +--trusted-host example.com +--extra-index-url http://example.com/simple + +bar==4.5.6 \\ + --hash=sha256:67890 +""" + + assert expected == content + + @pytest.mark.parametrize( ("dev", "expected"), [ From a9387815dbb6297c8caf51a37b4a466269029e49 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Mon, 5 Oct 2020 19:26:59 +0200 Subject: [PATCH 046/222] config: add support for installer.parallel This change allows users to disable parallel execution while using the new installer. Resolves: #3087 --- docs/docs/configuration.md | 9 +++++++++ poetry/config/config.py | 3 +++ poetry/console/commands/config.py | 1 + poetry/installation/executor.py | 5 ++++- tests/config/test_config.py | 27 ++++++++++++++++++++------- tests/console/commands/test_config.py | 27 +++++++++++++++++++++++++++ 6 files changed, 64 insertions(+), 8 deletions(-) diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index 782c2f9ac87..72310886ca7 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -103,6 +103,15 @@ Defaults to one of the following directories: - Windows: `C:\Users\\AppData\Local\pypoetry\Cache` - Unix: `~/.cache/pypoetry` +### `installer.parallel`: boolean + +Use parallel execution when using the new (`>=1.1.0`) installer. +Defaults to `true`. + +!!!note: + This configuration will be ignored, and parallel execution disabled when running + Python 2.7 under Windows. + ### `virtualenvs.create`: boolean Create a new virtual environment if one doesn't already exist. diff --git a/poetry/config/config.py b/poetry/config/config.py index d1fb421e53c..6f18127e8ff 100644 --- a/poetry/config/config.py +++ b/poetry/config/config.py @@ -39,6 +39,7 @@ class Config(object): "options": {"always-copy": False}, }, "experimental": {"new-installer": True}, + "installer": {"parallel": True}, } def __init__( @@ -140,6 +141,7 @@ def _get_validator(self, name): # type: (str) -> Callable "virtualenvs.create", "virtualenvs.in-project", "virtualenvs.options.always-copy", + "installer.parallel", }: return boolean_validator @@ -151,6 +153,7 @@ def _get_normalizer(self, name): # type: (str) -> Callable "virtualenvs.create", "virtualenvs.in-project", "virtualenvs.options.always-copy", + "installer.parallel", }: return boolean_normalizer diff --git a/poetry/console/commands/config.py b/poetry/console/commands/config.py index 52e1cf125ab..934b2c818eb 100644 --- a/poetry/console/commands/config.py +++ b/poetry/console/commands/config.py @@ -69,6 +69,7 @@ def unique_config_values(self): boolean_normalizer, False, ), + "installer.parallel": (boolean_validator, boolean_normalizer, True,), } return unique_config_values diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index a3184b718d5..a65dbb4a0e1 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -32,7 +32,7 @@ class Executor(object): - def __init__(self, env, pool, config, io, parallel=True): + def __init__(self, env, pool, config, io, parallel=None): self._env = env self._io = io self._dry_run = False @@ -42,6 +42,9 @@ def __init__(self, env, pool, config, io, parallel=True): self._chef = Chef(config, self._env) self._chooser = Chooser(pool, self._env) + if parallel is None: + parallel = config.get("installer.parallel", True) + if parallel and not (PY2 and WINDOWS): # This should be directly handled by ThreadPoolExecutor # however, on some systems the number of CPUs cannot be determined diff --git a/tests/config/test_config.py b/tests/config/test_config.py index 07373ade0b4..4bd0cd048da 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -1,16 +1,29 @@ import os +import pytest -def test_config_get_default_value(config): - assert config.get("virtualenvs.create") is True + +@pytest.mark.parametrize( + ("name", "value"), [("installer.parallel", True), ("virtualenvs.create", True)] +) +def test_config_get_default_value(config, name, value): + assert config.get(name) is value def test_config_get_processes_depended_on_values(config): assert os.path.join("/foo", "virtualenvs") == config.get("virtualenvs.path") -def test_config_get_from_environment_variable(config, environ): - assert config.get("virtualenvs.create") - - os.environ["POETRY_VIRTUALENVS_CREATE"] = "false" - assert not config.get("virtualenvs.create") +@pytest.mark.parametrize( + ("name", "env_value", "value"), + [ + ("installer.parallel", "true", True), + ("installer.parallel", "false", False), + ("virtualenvs.create", "true", True), + ("virtualenvs.create", "false", False), + ], +) +def test_config_get_from_environment_variable(config, environ, name, env_value, value): + env_var = "POETRY_{}".format("_".join(k.upper() for k in name.split("."))) + os.environ[env_var] = env_value + assert config.get(name) is value diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index 9923f1b77af..4d0b9ada0c8 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -6,6 +6,8 @@ from poetry.config.config_source import ConfigSource from poetry.core.pyproject import PyProjectException from poetry.factory import Factory +from poetry.utils._compat import PY2 +from poetry.utils._compat import WINDOWS @pytest.fixture() @@ -28,6 +30,7 @@ def test_list_displays_default_value_if_not_set(tester, config): expected = """cache-dir = "/foo" experimental.new-installer = true +installer.parallel = true virtualenvs.create = true virtualenvs.in-project = null virtualenvs.options.always-copy = false @@ -46,6 +49,7 @@ def test_list_displays_set_get_setting(tester, config): expected = """cache-dir = "/foo" experimental.new-installer = true +installer.parallel = true virtualenvs.create = false virtualenvs.in-project = null virtualenvs.options.always-copy = false @@ -86,6 +90,7 @@ def test_list_displays_set_get_local_setting(tester, config): expected = """cache-dir = "/foo" experimental.new-installer = true +installer.parallel = true virtualenvs.create = false virtualenvs.in-project = null virtualenvs.options.always-copy = false @@ -122,3 +127,25 @@ def test_set_cert(tester, auth_config_source, mocker): tester.execute("certificates.foo.cert path/to/ca.pem") assert "path/to/ca.pem" == auth_config_source.config["certificates"]["foo"]["cert"] + + +def test_config_installer_parallel(tester, command_tester_factory): + serial_enforced = PY2 and WINDOWS + + tester.execute("--local installer.parallel") + assert tester.io.fetch_output().strip() == "true" + + workers = command_tester_factory( + "install" + )._command._installer._executor._max_workers + assert workers > 1 or (serial_enforced and workers == 1) + + tester.io.clear_output() + tester.execute("--local installer.parallel false") + tester.execute("--local installer.parallel") + assert tester.io.fetch_output().strip() == "false" + + workers = command_tester_factory( + "install" + )._command._installer._executor._max_workers + assert workers == 1 From 4c81bcc60c99963fc67cea41883913a3d3a4cc65 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 6 Oct 2020 21:07:04 +0200 Subject: [PATCH 047/222] utils/env: better support system site packages dir This change improves handling of site-packages under system env, by gracefully handling fallbacks to user site when required and possible. Resolves: #3079 --- poetry/installation/pip_installer.py | 4 +- poetry/masonry/builders/editable.py | 61 +++++---- poetry/utils/env.py | 129 +++++++++++++++++- poetry/utils/helpers.py | 5 + tests/installation/test_pip_installer.py | 4 +- .../masonry/builders/test_editable_builder.py | 10 +- tests/utils/test_env.py | 4 +- tests/utils/test_env_site.py | 41 ++++++ 8 files changed, 218 insertions(+), 40 deletions(-) create mode 100644 tests/utils/test_env_site.py diff --git a/poetry/installation/pip_installer.py b/poetry/installation/pip_installer.py index f5de6642f2d..e44d7a61297 100644 --- a/poetry/installation/pip_installer.py +++ b/poetry/installation/pip_installer.py @@ -113,7 +113,9 @@ def remove(self, package): raise # This is a workaround for https://github.com/pypa/pip/issues/4176 - nspkg_pth_file = self._env.site_packages / "{}-nspkg.pth".format(package.name) + nspkg_pth_file = self._env.site_packages.path / "{}-nspkg.pth".format( + package.name + ) if nspkg_pth_file.exists(): nspkg_pth_file.unlink() diff --git a/poetry/masonry/builders/editable.py b/poetry/masonry/builders/editable.py index 99136ecbd3e..c31f657b656 100644 --- a/poetry/masonry/builders/editable.py +++ b/poetry/masonry/builders/editable.py @@ -94,7 +94,6 @@ def _setup_build(self): os.remove(str(setup)) def _add_pth(self): - pth_file = Path(self._module.name).with_suffix(".pth") paths = set() for include in self._module.includes: if isinstance(include, PackageInclude) and ( @@ -106,29 +105,25 @@ def _add_pth(self): for path in paths: content += decode(path + os.linesep) - for site_package in [self._env.site_packages, self._env.usersite]: - if not site_package: - continue - - try: - site_package.mkdir(parents=True, exist_ok=True) - path = site_package.joinpath(pth_file) - self._debug( - " - Adding {} to {} for {}".format( - path.name, site_package, self._poetry.file.parent - ) + pth_file = Path(self._module.name).with_suffix(".pth") + try: + pth_file = self._env.site_packages.write_text( + pth_file, content, encoding="utf-8" + ) + self._debug( + " - Adding {} to {} for {}".format( + pth_file.name, pth_file.parent, self._poetry.file.parent ) - path.write_text(content, encoding="utf-8") - return [path] - except PermissionError: - self._debug("- {} is not writable trying next available site") - - self._io.error_line( - " - Failed to create {} for {}".format( - pth_file.name, self._poetry.file.parent ) - ) - return [] + return [pth_file] + except OSError: + # TODO: Replace with PermissionError + self._io.error_line( + " - Failed to create {} for {}".format( + pth_file.name, self._poetry.file.parent + ) + ) + return [] def _add_scripts(self): added = [] @@ -187,19 +182,27 @@ def _add_dist_info(self, added_files): added_files = added_files[:] builder = WheelBuilder(self._poetry) - dist_info = self._env.site_packages.joinpath(builder.dist_info) + + dist_info_path = Path(builder.dist_info) + for dist_info in self._env.site_packages.find( + dist_info_path, writable_only=True + ): + if dist_info.exists(): + self._debug( + " - Removing existing {} directory from {}".format( + dist_info.name, dist_info.parent + ) + ) + shutil.rmtree(str(dist_info)) + + dist_info = self._env.site_packages.mkdir(dist_info_path) self._debug( " - Adding the {} directory to {}".format( - dist_info.name, self._env.site_packages + dist_info.name, dist_info.parent ) ) - if dist_info.exists(): - shutil.rmtree(str(dist_info)) - - dist_info.mkdir() - with dist_info.joinpath("METADATA").open("w", encoding="utf-8") as f: builder._write_metadata_file(f) diff --git a/poetry/utils/env.py b/poetry/utils/env.py index 0e3df320d6a..a81de75458d 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -7,6 +7,7 @@ import shutil import sys import sysconfig +import tempfile import textwrap from contextlib import contextmanager @@ -39,6 +40,7 @@ from poetry.utils._compat import encode from poetry.utils._compat import list_to_shell_command from poetry.utils._compat import subprocess +from poetry.utils.helpers import paths_csv GET_ENVIRONMENT_INFO = """\ @@ -143,6 +145,125 @@ def _version_nodot(version): """ +class SitePackages: + def __init__( + self, path, fallbacks=None, skip_write_checks=False + ): # type: (Path, List[Path], bool) -> None + self._path = path + self._fallbacks = fallbacks or [] + self._skip_write_checks = skip_write_checks + self._candidates = [self._path] + self._fallbacks + self._writable_candidates = None if not skip_write_checks else self._candidates + + @property + def path(self): # type: () -> Path + return self._path + + @property + def candidates(self): # type: () -> List[Path] + return self._candidates + + @property + def writable_candidates(self): # type: () -> List[Path] + if self._writable_candidates is not None: + return self._writable_candidates + + self._writable_candidates = [] + for candidate in self._candidates: + try: + if not candidate.exists(): + continue + + with tempfile.TemporaryFile(dir=str(candidate)): + self._writable_candidates.append(candidate) + except (IOError, OSError): + pass + + return self._writable_candidates + + def make_candidates( + self, path, writable_only=False + ): # type: (Path, bool) -> List[Path] + candidates = self._candidates if not writable_only else self.writable_candidates + if path.is_absolute(): + for candidate in candidates: + try: + path.relative_to(candidate) + return [path] + except ValueError: + pass + else: + raise ValueError( + "{} is not relative to any discovered {}sites".format( + path, "writable " if writable_only else "" + ) + ) + + return [candidate / path for candidate in candidates if candidate] + + def _path_method_wrapper( + self, path, method, *args, **kwargs + ): # type: (Path, str, *Any, **Any) -> Union[Tuple[Path, Any], List[Tuple[Path, Any]]] + + # TODO: Move to parameters after dropping Python 2.7 + return_first = kwargs.pop("return_first", True) + writable_only = kwargs.pop("writable_only", False) + + candidates = self.make_candidates(path, writable_only=writable_only) + + if not candidates: + raise RuntimeError( + 'Unable to find a suitable destination for "{}" in {}'.format( + str(path), paths_csv(self._candidates) + ) + ) + + results = [] + + for candidate in candidates: + try: + result = candidate, getattr(candidate, method)(*args, **kwargs) + if return_first: + return result + else: + results.append(result) + except (IOError, OSError): + # TODO: Replace with PermissionError + pass + + if results: + return results + + raise OSError("Unable to access any of {}".format(paths_csv(candidates))) + + def write_text(self, path, *args, **kwargs): # type: (Path, *Any, **Any) -> Path + return self._path_method_wrapper(path, "write_text", *args, **kwargs)[0] + + def mkdir(self, path, *args, **kwargs): # type: (Path, *Any, **Any) -> Path + return self._path_method_wrapper(path, "mkdir", *args, **kwargs)[0] + + def exists(self, path): # type: (Path) -> bool + return any( + value[-1] + for value in self._path_method_wrapper(path, "exists", return_first=False) + ) + + def find(self, path, writable_only=False): # type: (Path, bool) -> List[Path] + return [ + value[0] + for value in self._path_method_wrapper( + path, "exists", return_first=False, writable_only=writable_only + ) + if value[-1] is True + ] + + def __getattr__(self, item): + try: + return super(SitePackages, self).__getattribute__(item) + except AttributeError: + return getattr(self.path, item) + + class EnvError(Exception): pass @@ -825,9 +946,13 @@ def pip_version(self): return self._pip_version @property - def site_packages(self): # type: () -> Path + def site_packages(self): # type: () -> SitePackages if self._site_packages is None: - self._site_packages = self.purelib + # we disable write checks if no user site exist + fallbacks = [self.usersite] if self.usersite else [] + self._site_packages = SitePackages( + self.purelib, fallbacks, skip_write_checks=False if fallbacks else True + ) return self._site_packages @property diff --git a/poetry/utils/helpers.py b/poetry/utils/helpers.py index 180a90d50f3..e6162d508db 100644 --- a/poetry/utils/helpers.py +++ b/poetry/utils/helpers.py @@ -5,6 +5,7 @@ import tempfile from contextlib import contextmanager +from typing import List from typing import Optional import requests @@ -113,3 +114,7 @@ def get_package_version_display_string( ) return package.full_pretty_version + + +def paths_csv(paths): # type: (List[Path]) -> str + return ", ".join('"{}"'.format(str(c)) for c in paths) diff --git a/tests/installation/test_pip_installer.py b/tests/installation/test_pip_installer.py index 206bcce59cc..d0e2e5a4dcd 100644 --- a/tests/installation/test_pip_installer.py +++ b/tests/installation/test_pip_installer.py @@ -189,7 +189,9 @@ def test_uninstall_git_package_nspkg_pth_cleanup(mocker, tmp_venv, pool): ) # we do this here because the virtual env might not be usable if failure case is triggered - pth_file_candidate = tmp_venv.site_packages / "{}-nspkg.pth".format(package.name) + pth_file_candidate = tmp_venv.site_packages.path / "{}-nspkg.pth".format( + package.name + ) # in order to reproduce the scenario where the git source is removed prior to proper # clean up of nspkg.pth file, we need to make sure the fixture is copied and not diff --git a/tests/masonry/builders/test_editable_builder.py b/tests/masonry/builders/test_editable_builder.py index 3aee74e7c77..daeff0e7777 100644 --- a/tests/masonry/builders/test_editable_builder.py +++ b/tests/masonry/builders/test_editable_builder.py @@ -76,14 +76,14 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ builder.build() assert tmp_venv._bin_dir.joinpath("foo").exists() - assert tmp_venv.site_packages.joinpath("simple_project.pth").exists() - assert simple_poetry.file.parent.resolve().as_posix() == tmp_venv.site_packages.joinpath( + assert tmp_venv.site_packages.path.joinpath("simple_project.pth").exists() + assert simple_poetry.file.parent.resolve().as_posix() == tmp_venv.site_packages.path.joinpath( "simple_project.pth" ).read_text().strip( os.linesep ) - dist_info = tmp_venv.site_packages.joinpath("simple_project-1.2.3.dist-info") + dist_info = tmp_venv.site_packages.path.joinpath("simple_project-1.2.3.dist-info") assert dist_info.exists() assert dist_info.joinpath("INSTALLER").exists() assert dist_info.joinpath("METADATA").exists() @@ -130,7 +130,7 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ assert metadata == dist_info.joinpath("METADATA").read_text(encoding="utf-8") records = dist_info.joinpath("RECORD").read_text() - assert str(tmp_venv.site_packages.joinpath("simple_project.pth")) in records + assert str(tmp_venv.site_packages.path.joinpath("simple_project.pth")) in records assert str(tmp_venv._bin_dir.joinpath("foo")) in records assert str(tmp_venv._bin_dir.joinpath("baz")) in records assert str(dist_info.joinpath("METADATA")) in records @@ -202,7 +202,7 @@ def test_builder_installs_proper_files_when_packages_configured( builder = EditableBuilder(project_with_include, tmp_venv, NullIO()) builder.build() - pth_file = tmp_venv.site_packages.joinpath("with_include.pth") + pth_file = tmp_venv.site_packages.path.joinpath("with_include.pth") assert pth_file.is_file() paths = set() diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 8c816a2d32a..8934ae43427 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -866,7 +866,7 @@ def test_system_env_has_correct_paths(): assert paths.get("purelib") is not None assert paths.get("platlib") is not None assert paths.get("scripts") is not None - assert env.site_packages == Path(paths["purelib"]) + assert env.site_packages.path == Path(paths["purelib"]) @pytest.mark.parametrize( @@ -886,4 +886,4 @@ def test_venv_has_correct_paths(tmp_venv): assert paths.get("purelib") is not None assert paths.get("platlib") is not None assert paths.get("scripts") is not None - assert tmp_venv.site_packages == Path(paths["purelib"]) + assert tmp_venv.site_packages.path == Path(paths["purelib"]) diff --git a/tests/utils/test_env_site.py b/tests/utils/test_env_site.py new file mode 100644 index 00000000000..a13089160e6 --- /dev/null +++ b/tests/utils/test_env_site.py @@ -0,0 +1,41 @@ +import uuid + +from poetry.utils._compat import Path +from poetry.utils._compat import decode +from poetry.utils.env import SitePackages + + +def test_env_site_simple(tmp_dir): + site_packages = SitePackages(Path("/non-existent"), fallbacks=[Path(tmp_dir)]) + candidates = site_packages.make_candidates(Path("hello.txt"), writable_only=True) + hello = Path(tmp_dir) / "hello.txt" + + assert len(candidates) == 1 + assert candidates[0].as_posix() == hello.as_posix() + + content = decode(str(uuid.uuid4())) + site_packages.write_text(Path("hello.txt"), content, encoding="utf-8") + + assert hello.read_text(encoding="utf-8") == content + + assert not (site_packages.path / "hello.txt").exists() + + +def test_env_site_select_first(tmp_dir): + path = Path(tmp_dir) + fallback = path / "fallback" + fallback.mkdir(parents=True) + + site_packages = SitePackages(path, fallbacks=[fallback]) + candidates = site_packages.make_candidates(Path("hello.txt"), writable_only=True) + + assert len(candidates) == 2 + assert len(site_packages.find(Path("hello.txt"))) == 0 + + content = decode(str(uuid.uuid4())) + site_packages.write_text(Path("hello.txt"), content, encoding="utf-8") + + assert (site_packages.path / "hello.txt").exists() + assert not (fallback / "hello.txt").exists() + + assert len(site_packages.find(Path("hello.txt"))) == 1 From e9e6b324ef2ea1d8745a805acbba3493bce02e2b Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 23 Oct 2020 03:23:06 +0200 Subject: [PATCH 048/222] editable: use writable script dir for system env This change ensures that, When using system environment, poetry falls back to `userbase` if default location is not writable. --- poetry/masonry/builders/editable.py | 19 +++++++++++---- poetry/utils/env.py | 38 +++++++++++++++++++++-------- poetry/utils/helpers.py | 15 ++++++++++++ 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/poetry/masonry/builders/editable.py b/poetry/masonry/builders/editable.py index c31f657b656..0493423cdd2 100644 --- a/poetry/masonry/builders/editable.py +++ b/poetry/masonry/builders/editable.py @@ -13,6 +13,7 @@ from poetry.utils._compat import WINDOWS from poetry.utils._compat import Path from poetry.utils._compat import decode +from poetry.utils.helpers import is_dir_writable SCRIPT_TEMPLATE = """\ @@ -128,7 +129,17 @@ def _add_pth(self): def _add_scripts(self): added = [] entry_points = self.convert_entry_points() - scripts_path = Path(self._env.paths["scripts"]) + + for scripts_path in self._env.script_dirs: + if is_dir_writable(scripts_path): + break + else: + self._io.error_line( + " - Failed to find a suitable script installation directory for {}".format( + self._poetry.file.parent + ) + ) + return [] scripts = entry_points.get("console_scripts", []) for script in scripts: @@ -146,7 +157,7 @@ def _add_scripts(self): f.write( decode( SCRIPT_TEMPLATE.format( - python=self._env._bin("python"), + python=self._env.python, module=module, callable_holder=callable_holder, callable_=callable_, @@ -160,9 +171,7 @@ def _add_scripts(self): if WINDOWS: cmd_script = script_file.with_suffix(".cmd") - cmd = WINDOWS_CMD_TEMPLATE.format( - python=self._env._bin("python"), script=name - ) + cmd = WINDOWS_CMD_TEMPLATE.format(python=self._env.python, script=name) self._debug( " - Adding the {} script wrapper to {}".format( cmd_script.name, scripts_path diff --git a/poetry/utils/env.py b/poetry/utils/env.py index a81de75458d..33886926d4b 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -7,7 +7,6 @@ import shutil import sys import sysconfig -import tempfile import textwrap from contextlib import contextmanager @@ -40,6 +39,7 @@ from poetry.utils._compat import encode from poetry.utils._compat import list_to_shell_command from poetry.utils._compat import subprocess +from poetry.utils.helpers import is_dir_writable from poetry.utils.helpers import paths_csv @@ -170,14 +170,9 @@ def writable_candidates(self): # type: () -> List[Path] self._writable_candidates = [] for candidate in self._candidates: - try: - if not candidate.exists(): - continue - - with tempfile.TemporaryFile(dir=str(candidate)): - self._writable_candidates.append(candidate) - except (IOError, OSError): - pass + if not is_dir_writable(candidate): + continue + self._writable_candidates.append(candidate) return self._writable_candidates @@ -892,6 +887,7 @@ def __init__(self, path, base=None): # type: (Path, Optional[Path]) -> None self._supported_tags = None self._purelib = None self._platlib = None + self._script_dirs = None @property def path(self): # type: () -> Path @@ -960,6 +956,11 @@ def usersite(self): # type: () -> Optional[Path] if "usersite" in self.paths: return Path(self.paths["usersite"]) + @property + def userbase(self): # type: () -> Optional[Path] + if "userbase" in self.paths: + return Path(self.paths["userbase"]) + @property def purelib(self): # type: () -> Path if self._purelib is None: @@ -1106,6 +1107,18 @@ def execute(self, bin, *args, **kwargs): def is_venv(self): # type: () -> bool raise NotImplementedError() + @property + def script_dirs(self): # type: () -> List[Path] + if self._script_dirs is None: + self._script_dirs = ( + [Path(self.paths["scripts"])] + if "scripts" in self.paths + else self._bin_dir + ) + if self.userbase: + self._script_dirs.append(self.userbase / self._script_dirs[0].name) + return self._script_dirs + def _bin(self, bin): # type: (str) -> str """ Return path to the given executable. @@ -1141,6 +1154,10 @@ class SystemEnv(Env): A system (i.e. not a virtualenv) Python environment. """ + @property + def python(self): # type: () -> str + return sys.executable + @property def sys_path(self): # type: () -> List[str] return sys.path @@ -1181,6 +1198,7 @@ def get_paths(self): # type: () -> Dict[str, str] if site.check_enableusersite() and hasattr(obj, "install_usersite"): paths["usersite"] = getattr(obj, "install_usersite") + paths["userbase"] = getattr(obj, "install_userbase") return paths @@ -1316,7 +1334,7 @@ def is_venv(self): # type: () -> bool def is_sane(self): # A virtualenv is considered sane if both "python" and "pip" exist. - return os.path.exists(self._bin("python")) and os.path.exists(self._bin("pip")) + return os.path.exists(self.python) and os.path.exists(self._bin("pip")) def _run(self, cmd, **kwargs): with self.temp_environ(): diff --git a/poetry/utils/helpers.py b/poetry/utils/helpers.py index e6162d508db..232e65b7d44 100644 --- a/poetry/utils/helpers.py +++ b/poetry/utils/helpers.py @@ -118,3 +118,18 @@ def get_package_version_display_string( def paths_csv(paths): # type: (List[Path]) -> str return ", ".join('"{}"'.format(str(c)) for c in paths) + + +def is_dir_writable(path, create=False): # type: (Path, bool) -> bool + try: + if not path.exists(): + if not create: + return False + path.mkdir(parents=True, exist_ok=True) + + with tempfile.TemporaryFile(dir=str(path)): + pass + except (IOError, OSError): + return False + else: + return True From 5d0c7965a9c1afacd96faeacf722ae361983af36 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 23 Oct 2020 03:34:53 +0200 Subject: [PATCH 049/222] utils/env: ensure user directories are created --- poetry/masonry/builders/editable.py | 2 +- poetry/utils/env.py | 2 +- tests/utils/test_env_site.py | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/poetry/masonry/builders/editable.py b/poetry/masonry/builders/editable.py index 0493423cdd2..74d1f69c886 100644 --- a/poetry/masonry/builders/editable.py +++ b/poetry/masonry/builders/editable.py @@ -131,7 +131,7 @@ def _add_scripts(self): entry_points = self.convert_entry_points() for scripts_path in self._env.script_dirs: - if is_dir_writable(scripts_path): + if is_dir_writable(path=scripts_path, create=True): break else: self._io.error_line( diff --git a/poetry/utils/env.py b/poetry/utils/env.py index 33886926d4b..c247bf014f7 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -170,7 +170,7 @@ def writable_candidates(self): # type: () -> List[Path] self._writable_candidates = [] for candidate in self._candidates: - if not is_dir_writable(candidate): + if not is_dir_writable(path=candidate, create=True): continue self._writable_candidates.append(candidate) diff --git a/tests/utils/test_env_site.py b/tests/utils/test_env_site.py index a13089160e6..f25e2142193 100644 --- a/tests/utils/test_env_site.py +++ b/tests/utils/test_env_site.py @@ -5,7 +5,9 @@ from poetry.utils.env import SitePackages -def test_env_site_simple(tmp_dir): +def test_env_site_simple(tmp_dir, mocker): + # emulate permission error when creating directory + mocker.patch("poetry.utils._compat.Path.mkdir", side_effect=OSError()) site_packages = SitePackages(Path("/non-existent"), fallbacks=[Path(tmp_dir)]) candidates = site_packages.make_candidates(Path("hello.txt"), writable_only=True) hello = Path(tmp_dir) / "hello.txt" From 68f2cc7032d738981a4af52fdff159df95e55a12 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Thu, 15 Oct 2020 01:52:30 +0200 Subject: [PATCH 050/222] Handle non-editable packages with pth files When detecting installed packages, this change ensures that packages with .pth files are not incorrectly marked as editable. A package is considered editable only if at least one of the paths detected is not in the environment site. Resolves: #3077 --- poetry/repositories/installed_repository.py | 12 ++++++++-- .../standard-1.2.3.dist-info/METADATA | 22 +++++++++++++++++++ .../lib/python3.7/site-packages/standard.pth | 1 + .../repositories/test_installed_repository.py | 11 ++++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 tests/repositories/fixtures/installed/lib/python3.7/site-packages/standard-1.2.3.dist-info/METADATA create mode 100644 tests/repositories/fixtures/installed/lib/python3.7/site-packages/standard.pth diff --git a/poetry/repositories/installed_repository.py b/poetry/repositories/installed_repository.py index a0630116f53..1320fdd6698 100644 --- a/poetry/repositories/installed_repository.py +++ b/poetry/repositories/installed_repository.py @@ -138,14 +138,22 @@ def load(cls, env): # type: (Env) -> InstalledRepository if path.name.endswith(".dist-info"): paths = cls.get_package_paths(env=env, name=package.pretty_name) if paths: + is_editable_package = False for src in paths: if cls.is_vcs_package(src, env): cls.set_package_vcs_properties(package, env) break + + if not ( + is_editable_package + or env.is_path_relative_to_lib(src) + ): + is_editable_package = True else: # TODO: handle multiple source directories? - package._source_type = "directory" - package._source_url = paths.pop().as_posix() + if is_editable_package: + package._source_type = "directory" + package._source_url = paths.pop().as_posix() continue if cls.is_vcs_package(path, env): diff --git a/tests/repositories/fixtures/installed/lib/python3.7/site-packages/standard-1.2.3.dist-info/METADATA b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/standard-1.2.3.dist-info/METADATA new file mode 100644 index 00000000000..245121d496d --- /dev/null +++ b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/standard-1.2.3.dist-info/METADATA @@ -0,0 +1,22 @@ +Metadata-Version: 2.1 +Name: standard +Version: 1.2.3 +Summary: Standard description. +License: MIT +Keywords: cli,commands +Author: Foo Bar +Author-email: foo@bar.com +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Description-Content-Type: text/x-rst + +Editable +#### diff --git a/tests/repositories/fixtures/installed/lib/python3.7/site-packages/standard.pth b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/standard.pth new file mode 100644 index 00000000000..aa0bc074b62 --- /dev/null +++ b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/standard.pth @@ -0,0 +1 @@ +standard \ No newline at end of file diff --git a/tests/repositories/test_installed_repository.py b/tests/repositories/test_installed_repository.py index 79ac262671c..3caa702a09c 100644 --- a/tests/repositories/test_installed_repository.py +++ b/tests/repositories/test_installed_repository.py @@ -26,6 +26,7 @@ zipp.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"), metadata.PathDistribution(SITE_PLATLIB / "lib64-2.3.4.dist-info"), @@ -158,3 +159,13 @@ def test_load_editable_with_import_package(repository): assert editable.version.text == "2.3.4" assert editable.source_type is None assert editable.source_url is None + + +def test_load_standard_package_with_pth_file(repository): + # test standard packages with .pth file is not treated as editable + standard = get_package_from_repository("standard", repository) + assert standard is not None + assert standard.name == "standard" + assert standard.version.text == "1.2.3" + assert standard.source_type is None + assert standard.source_url is None From 89e1d7c11c7566e5735173e17a041a4894f870a4 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 16 Oct 2020 23:42:10 +0200 Subject: [PATCH 051/222] locker: handle nested extras requirement Previously, when using locked repository, incorrect dependency instance was created when a dependency's extra requirement activated a nested extra. This change ensures that these are correctly loaded. As part of this change new lock files write PEP 508 serialised form of extra dependencies in order to reuse core logic to parse specification of extra requirement. Resolves: #3224 --- poetry/packages/locker.py | 23 +++- .../fixtures/with-dependencies-extras.test | 2 +- .../with-dependencies-nested-extras.test | 45 ++++++ tests/installation/test_installer.py | 29 ++++ tests/packages/test_locker.py | 130 ++++++++++++++++++ 5 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 tests/installation/fixtures/with-dependencies-nested-extras.test diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index 9dd75e66519..ac791537450 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -24,12 +24,14 @@ import poetry.repositories +from poetry.core.packages import dependency_from_pep_508 from poetry.core.packages.package import Dependency from poetry.core.packages.package import Package from poetry.core.semver import parse_constraint from poetry.core.semver.version import Version from poetry.core.toml.file import TOMLFile from poetry.core.version.markers import parse_marker +from poetry.core.version.requirements import InvalidRequirement from poetry.packages import DependencyPackage from poetry.utils._compat import OrderedDict from poetry.utils._compat import Path @@ -142,11 +144,18 @@ def locked_repository( package.extras[name] = [] for dep in deps: - m = re.match(r"^(.+?)(?:\s+\((.+)\))?$", dep) - dep_name = m.group(1) - constraint = m.group(2) or "*" - - package.extras[name].append(Dependency(dep_name, constraint)) + try: + dependency = dependency_from_pep_508(dep) + except InvalidRequirement: + # handle lock files with invalid PEP 508 + m = re.match(r"^(.+?)(?:\[(.+?)])?(?:\s+\((.+)\))?$", dep) + dep_name = m.group(1) + extras = m.group(2) or "" + constraint = m.group(3) or "*" + dependency = Dependency( + dep_name, constraint, extras=extras.split(",") + ) + package.extras[name].append(dependency) if "marker" in info: package.marker = parse_marker(info["marker"]) @@ -543,8 +552,10 @@ def _dump_package(self, package): # type: (Package) -> dict if package.extras: extras = {} for name, deps in package.extras.items(): + # TODO: This should use dep.to_pep_508() once this is fixed + # https://github.com/python-poetry/poetry-core/pull/102 extras[name] = [ - str(dep) if not dep.constraint.is_any() else dep.name + dep.base_pep_508_name if not dep.constraint.is_any() else dep.name for dep in deps ] diff --git a/tests/installation/fixtures/with-dependencies-extras.test b/tests/installation/fixtures/with-dependencies-extras.test index 63560bb4793..042e29670e1 100644 --- a/tests/installation/fixtures/with-dependencies-extras.test +++ b/tests/installation/fixtures/with-dependencies-extras.test @@ -18,7 +18,7 @@ python-versions = "*" C = {version = "^1.0", optional = true} [package.extras] -foo = ["C (^1.0)"] +foo = ["C (>=1.0,<2.0)"] [[package]] name = "C" diff --git a/tests/installation/fixtures/with-dependencies-nested-extras.test b/tests/installation/fixtures/with-dependencies-nested-extras.test new file mode 100644 index 00000000000..48a22a7c7f3 --- /dev/null +++ b/tests/installation/fixtures/with-dependencies-nested-extras.test @@ -0,0 +1,45 @@ +[[package]] +name = "A" +version = "1.0" +description = "" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +B = {version = "^1.0", optional = true, extras = ["C"]} + +[package.extras] +B = ["B[C] (>=1.0,<2.0)"] + +[[package]] +name = "B" +version = "1.0" +description = "" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +C = {version = "^1.0", optional = true} + +[package.extras] +C = ["C (>=1.0,<2.0)"] + +[[package]] +name = "C" +version = "1.0" +description = "" +category = "main" +optional = false +python-versions = "*" + +[metadata] +python-versions = "*" +lock-version = "1.1" +content-hash = "123456789" + +[metadata.files] +"A" = [] +"B" = [] +"C" = [] diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 7a1660670ee..106efde6e9c 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -639,6 +639,35 @@ def test_run_with_dependencies_extras(installer, locker, repo, package): assert locker.written_data == expected +def test_run_with_dependencies_nested_extras(installer, locker, repo, package): + package_a = get_package("A", "1.0") + package_b = get_package("B", "1.0") + package_c = get_package("C", "1.0") + + dependency_c = Factory.create_dependency("C", {"version": "^1.0", "optional": True}) + dependency_b = Factory.create_dependency( + "B", {"version": "^1.0", "optional": True, "extras": ["C"]} + ) + dependency_a = Factory.create_dependency("A", {"version": "^1.0", "extras": ["B"]}) + + package_b.extras = {"C": [dependency_c]} + package_b.add_dependency(dependency_c) + + package_a.add_dependency(dependency_b) + package_a.extras = {"B": [dependency_b]} + + repo.add_package(package_a) + repo.add_package(package_b) + repo.add_package(package_c) + + package.add_dependency(dependency_a) + + installer.run() + expected = fixture("with-dependencies-nested-extras") + + assert locker.written_data == expected + + def test_run_does_not_install_extras_if_not_requested(installer, locker, repo, package): package.extras["foo"] = [get_dependency("D")] package_a = get_package("A", "1.0") diff --git a/tests/packages/test_locker.py b/tests/packages/test_locker.py index 35c584f2767..a4aa17971f8 100644 --- a/tests/packages/test_locker.py +++ b/tests/packages/test_locker.py @@ -142,6 +142,136 @@ def test_locker_properly_loads_extras(locker): assert lockfile_dep.name == "lockfile" +def test_locker_properly_loads_nested_extras(locker): + content = """\ +[[package]] +name = "a" +version = "1.0" +description = "" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +b = {version = "^1.0", optional = true, extras = "c"} + +[package.extras] +b = ["b[c] (>=1.0,<2.0)"] + +[[package]] +name = "b" +version = "1.0" +description = "" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +c = {version = "^1.0", optional = true} + +[package.extras] +c = ["c (>=1.0,<2.0)"] + +[[package]] +name = "c" +version = "1.0" +description = "" +category = "main" +optional = false +python-versions = "*" + +[metadata] +python-versions = "*" +lock-version = "1.1" +content-hash = "123456789" + +[metadata.files] +"a" = [] +"b" = [] +"c" = [] +""" + + locker.lock.write(tomlkit.parse(content)) + + repository = locker.locked_repository() + assert 3 == len(repository.packages) + + packages = repository.find_packages(get_dependency("a", "1.0")) + assert len(packages) == 1 + + package = packages[0] + assert len(package.requires) == 1 + assert len(package.extras) == 1 + + dependency_b = package.extras["b"][0] + assert dependency_b.name == "b" + assert dependency_b.extras == frozenset({"c"}) + + packages = repository.find_packages(dependency_b) + assert len(packages) == 1 + + package = packages[0] + assert len(package.requires) == 1 + assert len(package.extras) == 1 + + dependency_c = package.extras["c"][0] + assert dependency_c.name == "c" + assert dependency_c.extras == frozenset() + + packages = repository.find_packages(dependency_c) + assert len(packages) == 1 + + +def test_locker_properly_loads_extras_legacy(locker): + content = """\ +[[package]] +name = "a" +version = "1.0" +description = "" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +b = {version = "^1.0", optional = true} + +[package.extras] +b = ["b (^1.0)"] + +[[package]] +name = "b" +version = "1.0" +description = "" +category = "main" +optional = false +python-versions = "*" + +[metadata] +python-versions = "*" +lock-version = "1.1" +content-hash = "123456789" + +[metadata.files] +"a" = [] +"b" = [] +""" + + locker.lock.write(tomlkit.parse(content)) + + repository = locker.locked_repository() + assert 2 == len(repository.packages) + + packages = repository.find_packages(get_dependency("a", "1.0")) + assert len(packages) == 1 + + package = packages[0] + assert len(package.requires) == 1 + assert len(package.extras) == 1 + + dependency_b = package.extras["b"][0] + assert dependency_b.name == "b" + + def test_lock_packages_with_null_description(locker, root): package_a = get_package("A", "1.0.0") package_a.description = None From e878a5d4e7aeaf022201a55254d5878d1972b63c Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Sun, 18 Oct 2020 00:14:48 +0200 Subject: [PATCH 052/222] locker: handle cyclic dependencies during walk Resolves: #3213 --- poetry/packages/locker.py | 52 ++++++++++++++++----------------- tests/utils/test_exporter.py | 56 ++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 27 deletions(-) diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index ac791537450..d0d03a56779 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -226,45 +226,43 @@ def __walk_dependency_level( next_level_dependencies = [] for requirement in dependencies: + key = (requirement.name, requirement.pretty_constraint) locked_package = cls.__get_locked_package(requirement, packages_by_name) if locked_package: - for require in locked_package.requires: - if require.marker.is_empty(): - require.marker = requirement.marker - else: - require.marker = require.marker.intersect(requirement.marker) + # create dependency from locked package to retain dependency metadata + # if this is not done, we can end-up with incorrect nested dependencies + marker = requirement.marker + requirement = locked_package.to_dependency() + requirement.marker = requirement.marker.intersect(marker) + + key = (requirement.name, requirement.pretty_constraint) + + if pinned_versions: + requirement.set_constraint( + locked_package.to_dependency().constraint + ) + + if key not in nested_dependencies: + for require in locked_package.requires: + if require.marker.is_empty(): + require.marker = requirement.marker + else: + require.marker = require.marker.intersect( + requirement.marker + ) - require.marker = require.marker.intersect(locked_package.marker) - next_level_dependencies.append(require) + require.marker = require.marker.intersect(locked_package.marker) + next_level_dependencies.append(require) if requirement.name in project_level_dependencies and level == 0: # project level dependencies take precedence continue - if locked_package: - # create dependency from locked package to retain dependency metadata - # if this is not done, we can end-up with incorrect nested dependencies - marker = requirement.marker - requirement = locked_package.to_dependency() - requirement.marker = requirement.marker.intersect(marker) - else: + if not locked_package: # we make a copy to avoid any side-effects requirement = deepcopy(requirement) - if pinned_versions: - requirement.set_constraint( - cls.__get_locked_package(requirement, packages_by_name) - .to_dependency() - .constraint - ) - - # dependencies use extra to indicate that it was activated via parent - # package's extras, this is not required for nested exports as we assume - # the resolver already selected this dependency - requirement.marker = requirement.marker.without_extras() - - key = (requirement.name, requirement.pretty_constraint) if key not in nested_dependencies: nested_dependencies[key] = requirement else: diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index c5ce0214ff7..1e660ce69cd 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -696,6 +696,62 @@ def test_exporter_can_export_requirements_txt_with_nested_packages(tmp_dir, poet assert expected == content +def test_exporter_can_export_requirements_txt_with_nested_packages_cyclic( + tmp_dir, poetry +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"bar": {"version": "4.5.6"}}, + }, + { + "name": "bar", + "version": "4.5.6", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"baz": {"version": "7.8.9"}}, + }, + { + "name": "baz", + "version": "7.8.9", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"foo": {"version": "1.2.3"}}, + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": [], "bar": [], "baz": []}, + }, + } + ) + set_package_requires(poetry, skip={"bar", "baz"}) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +bar==4.5.6 +baz==7.8.9 +foo==1.2.3 +""" + + assert expected == content + + def test_exporter_can_export_requirements_txt_with_git_packages_and_markers( tmp_dir, poetry ): From 3067fd4af68addc13aad37048637617a0c68e322 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 20 Oct 2020 01:42:39 +0200 Subject: [PATCH 053/222] pool: ensure sources are prioritised over PyPI When a project specifies non default sources, PyPI gets added as the default source. This will prioritise packages available in PyPI when the package exists in both index. This change ensures that PyPI is only used as a default when no other sources are provided. Resolves: #1677 #2564 #3238 --- poetry/factory.py | 6 +++-- .../with_non_default_source/pyproject.toml | 18 +++++++++++++ tests/test_factory.py | 27 +++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/with_non_default_source/pyproject.toml diff --git a/poetry/factory.py b/poetry/factory.py index fd863ba5991..08a6faca170 100644 --- a/poetry/factory.py +++ b/poetry/factory.py @@ -70,7 +70,8 @@ def create_poetry( ) # Configuring sources - for source in poetry.local_config.get("source", []): + sources = poetry.local_config.get("source", []) + for source in sources: repository = self.create_legacy_repository(source, config) is_default = source.get("default", False) is_secondary = source.get("secondary", False) @@ -90,7 +91,8 @@ def create_poetry( # Always put PyPI last to prefer private repositories # but only if we have no other default source if not poetry.pool.has_default(): - poetry.pool.add_repository(PyPiRepository(), True) + has_sources = bool(sources) + poetry.pool.add_repository(PyPiRepository(), not has_sources, has_sources) else: if io.is_debug(): io.write_line("Deactivating the PyPI repository") diff --git a/tests/fixtures/with_non_default_source/pyproject.toml b/tests/fixtures/with_non_default_source/pyproject.toml new file mode 100644 index 00000000000..d36abb55a25 --- /dev/null +++ b/tests/fixtures/with_non_default_source/pyproject.toml @@ -0,0 +1,18 @@ +[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/" diff --git a/tests/test_factory.py b/tests/test_factory.py index d7758e8deaa..b2c232b20e7 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -6,6 +6,8 @@ from poetry.core.toml.file import TOMLFile from poetry.factory import Factory +from poetry.repositories.legacy_repository import LegacyRepository +from poetry.repositories.pypi_repository import PyPiRepository from poetry.utils._compat import PY2 from poetry.utils._compat import Path @@ -150,6 +152,31 @@ def test_poetry_with_default_source(): assert 1 == len(poetry.pool.repositories) +def test_poetry_with_non_default_source(): + poetry = Factory().create_poetry(fixtures_dir / "with_non_default_source") + + assert len(poetry.pool.repositories) == 2 + + assert not poetry.pool.has_default() + + assert poetry.pool.repositories[0].name == "foo" + assert isinstance(poetry.pool.repositories[0], LegacyRepository) + + assert poetry.pool.repositories[1].name == "PyPI" + assert isinstance(poetry.pool.repositories[1], PyPiRepository) + + +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_default() + + assert poetry.pool.repositories[0].name == "PyPI" + assert isinstance(poetry.pool.repositories[0], PyPiRepository) + + def test_poetry_with_two_default_sources(): with pytest.raises(ValueError) as e: Factory().create_poetry(fixtures_dir / "with_two_default_sources") From 7c728f0aacb0abf1084c7cc189f2ed24e43386b3 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Wed, 21 Oct 2020 21:59:42 +0200 Subject: [PATCH 054/222] Ensure vcs dependencies preserve editable flag Resolves: #3263 --- poetry/packages/locker.py | 2 +- poetry/puzzle/provider.py | 1 + tests/packages/test_locker.py | 1 + tests/puzzle/test_provider.py | 9 +++++++++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index d0d03a56779..f1637407068 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -582,7 +582,7 @@ def _dump_package(self, package): # type: (Package) -> dict if package.source_resolved_reference: data["source"]["resolved_reference"] = package.source_resolved_reference - if package.source_type == "directory": + if package.source_type in ["directory", "git"]: data["develop"] = package.develop return data diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index e2838370a79..c05efbd6844 100644 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -168,6 +168,7 @@ def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package] rev=dependency.rev, name=dependency.name, ) + package.develop = dependency.develop dependency._constraint = package.version dependency._pretty_constraint = package.version.text diff --git a/tests/packages/test_locker.py b/tests/packages/test_locker.py index a4aa17971f8..7fa89a44962 100644 --- a/tests/packages/test_locker.py +++ b/tests/packages/test_locker.py @@ -73,6 +73,7 @@ def test_lock_file_data_is_ordered(locker, root): category = "main" optional = false python-versions = "*" +develop = true [package.source] type = "git" diff --git a/tests/puzzle/test_provider.py b/tests/puzzle/test_provider.py index 693470156f4..ecab7f3ab2d 100644 --- a/tests/puzzle/test_provider.py +++ b/tests/puzzle/test_provider.py @@ -47,6 +47,15 @@ def provider(root, pool): return Provider(root, pool, NullIO()) +@pytest.mark.parametrize("value", [True, False]) +def test_search_for_vcs_retains_develop_flag(provider, value): + dependency = VCSDependency( + "demo", "git", "https://github.com/demo/demo.git", develop=value + ) + package = provider.search_for_vcs(dependency)[0] + assert package.develop == value + + def test_search_for_vcs_setup_egg_info(provider): dependency = VCSDependency("demo", "git", "https://github.com/demo/demo.git") From 5daeb89b0800dbc282d2b9ef1105fb20e7e712c2 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 23 Oct 2020 23:08:33 +0200 Subject: [PATCH 055/222] Add 1.1.4 changelog --- CHANGELOG.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec01b75abde..168944d8f4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Change Log +## [1.1.4] - 2020-10-23 + +### Added + +- Added `installer.parallel` boolean flag (defaults to `true`) configuration to enable/disable parallel execution of operations when using the new installer. ([#3088](https://github.com/python-poetry/poetry/pull/3088)) + +### Changed + +- When using system environments as an unprivileged user, user site and bin directories are created if they do not already exist. ([#3107](https://github.com/python-poetry/poetry/pull/3107)) + +### Fixed + +- Fixed editable installation of poetry projects when using system environments. ([#3107](https://github.com/python-poetry/poetry/pull/3107)) +- Fixed locking of nested extra activations. If you were affected by this issue, you will need to regenerate the lock file using `poetry lock --no-update`. ([#3229](https://github.com/python-poetry/poetry/pull/3229)) +- Fixed prioritisation of non-default custom package sources. ([#3251](https://github.com/python-poetry/poetry/pull/3251)) +- Fixed detection of installed editable packages when non-poetry managed `.pth` file exists. ([#3210](https://github.com/python-poetry/poetry/pull/3210)) +- Fixed scripts generated by editable builder to use valid import statements. ([#3214](https://github.com/python-poetry/poetry/pull/3214)) +- Fixed recursion error when locked dependencies contain cyclic dependencies. ([#3237](https://github.com/python-poetry/poetry/pull/3237)) +- Fixed propagation of editable flag for VCS dependencies. ([#3264](https://github.com/python-poetry/poetry/pull/3264)) + ## [1.1.3] - 2020-10-14 ### Changed @@ -1063,7 +1083,8 @@ Initial release -[Unreleased]: https://github.com/python-poetry/poetry/compare/1.1.3...master +[Unreleased]: https://github.com/python-poetry/poetry/compare/1.1.4...master +[1.1.3]: https://github.com/python-poetry/poetry/compare/1.1.4 [1.1.3]: https://github.com/python-poetry/poetry/compare/1.1.3 [1.1.2]: https://github.com/python-poetry/poetry/releases/tag/1.1.2 [1.1.1]: https://github.com/python-poetry/poetry/releases/tag/1.1.1 From d1529a19118c0aead054ef9d4f1dbf1210bad153 Mon Sep 17 00:00:00 2001 From: Eric Engestrom Date: Wed, 28 Oct 2020 12:30:43 +0100 Subject: [PATCH 056/222] show --tree: stop ignoring --no-dev (#3296) --- poetry/console/commands/show.py | 4 +- tests/console/commands/test_show.py | 67 +++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/poetry/console/commands/show.py b/poetry/console/commands/show.py index f6f42f42654..86be1ae7a36 100644 --- a/poetry/console/commands/show.py +++ b/poetry/console/commands/show.py @@ -55,7 +55,9 @@ def handle(self): # Show tree view if requested if self.option("tree") and not package: - requires = self.poetry.package.requires + self.poetry.package.dev_requires + requires = self.poetry.package.requires + if include_dev: + requires += self.poetry.package.dev_requires packages = locked_repo.packages for package in packages: for require in requires: diff --git a/tests/console/commands/test_show.py b/tests/console/commands/test_show.py index cf4dffcacc4..56e964abfe4 100644 --- a/tests/console/commands/test_show.py +++ b/tests/console/commands/test_show.py @@ -1152,3 +1152,70 @@ def test_show_tree(tester, poetry, installed): """ assert expected == tester.io.fetch_output() + + +def test_show_tree_no_dev(tester, poetry, installed): + poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.2.0")) + poetry.package.add_dependency( + Factory.create_dependency("pytest", "^6.1.0", category="dev") + ) + + cachy2 = get_package("cachy", "0.2.0") + cachy2.add_dependency(Factory.create_dependency("msgpack-python", ">=0.5 <0.6")) + installed.add_package(cachy2) + + pytest = get_package("pytest", "6.1.1") + installed.add_package(pytest) + + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "cachy", + "version": "0.2.0", + "description": "", + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + "dependencies": {"msgpack-python": ">=0.5 <0.6"}, + }, + { + "name": "msgpack-python", + "version": "0.5.1", + "description": "", + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + { + "name": "pytest", + "version": "6.1.1", + "description": "", + "category": "dev", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "hashes": {"cachy": [], "msgpack-python": [], "pytest": []}, + }, + } + ) + + tester.execute("--tree --no-dev") + + expected = """\ +cachy 0.2.0 +`-- msgpack-python >=0.5 <0.6 +""" + + assert expected == tester.io.fetch_output() From 3ef60e71ce5ca2c17d0169c5b2c98be28264096e Mon Sep 17 00:00:00 2001 From: Gabriel Simonetto Date: Sat, 17 Oct 2020 14:35:59 -0300 Subject: [PATCH 057/222] Add no interaction flag to website --- docs/docs/cli.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/cli.md b/docs/docs/cli.md index 1a2615a9d9d..bf207e8dc42 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -14,6 +14,7 @@ then `--help` combined with any of those can give you more information. * `--ansi`: Force ANSI output. * `--no-ansi`: Disable ANSI output. * `--version (-V)`: Display this application version. +* `--no-interaction (-n)`: Do not ask any interactive question. ## new From 19cafe30893a71117b086ae3769ddd41462dd3de Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 23 Oct 2020 23:50:00 +0200 Subject: [PATCH 058/222] pip installer: fix incorrect method call Resolves: #3272 --- poetry/installation/pip_installer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry/installation/pip_installer.py b/poetry/installation/pip_installer.py index e44d7a61297..b8fb97314be 100644 --- a/poetry/installation/pip_installer.py +++ b/poetry/installation/pip_installer.py @@ -229,7 +229,7 @@ def install_directory(self, package): args.append(req) - return self.run_pip(*args) + return self.run(*args) if package.develop: args.append("-e") From 6ddd58f81287c1e63d155c120325066c71692b41 Mon Sep 17 00:00:00 2001 From: Cere Blanco <743526+cereblanco@users.noreply.github.com> Date: Fri, 30 Oct 2020 12:57:14 +0800 Subject: [PATCH 059/222] Improve caret version constraint documentation (#3280) * Add context to caret version constraint documentation --- docs/docs/dependency-specification.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/docs/dependency-specification.md b/docs/docs/dependency-specification.md index 2a1b63afdae..f71480dfa41 100644 --- a/docs/docs/dependency-specification.md +++ b/docs/docs/dependency-specification.md @@ -7,12 +7,7 @@ of the dependency and on the optional constraints that might be needed for it to ### Caret requirements -**Caret requirements** allow SemVer compatible updates to a specified version. -An update is allowed if the new version number does not modify the left-most non-zero digit in the major, minor, patch grouping. -In this case, if we ran `poetry update requests`, poetry would update us to version `2.14.0` if it was available, -but would not update us to `3.0.0`. -If instead we had specified the version string as `^0.1.13`, poetry would update to `0.1.14` but not `0.2.0`. -`0.0.x` is not considered compatible with any other version. +**Caret requirements** allow [SemVer](https://semver.org/) compatible updates to a specified version. An update is allowed if the new version number does not modify the left-most non-zero digit in the major, minor, patch grouping. For instance, if we previously ran `poetry add requests@^2.13.0` and wanted to update the library and ran `poetry update requests`, poetry would update us to version `2.14.0` if it was available, but would not update us to `3.0.0`. If instead we had specified the version string as `^0.1.13`, poetry would update to `0.1.14` but not `0.2.0`. `0.0.x` is not considered compatible with any other version. Here are some more examples of caret requirements and the versions that would be allowed with them: From bf61dd56399b5d0cfadf66fed72b4d63062a827f Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 20 Oct 2020 18:32:36 +0200 Subject: [PATCH 060/222] tests: cleanup cache and http usage - ensure tests rely on temporary cache directory - remove external http call requirement for lock --no-update Relates-to: #1645 --- tests/config/test_config.py | 4 +- tests/conftest.py | 21 ++- tests/console/commands/test_config.py | 30 ++-- tests/console/commands/test_lock.py | 29 ++-- tests/fixtures/old_lock/poetry.lock | 150 +----------------- tests/fixtures/old_lock/pyproject.toml | 2 +- tests/installation/test_chef.py | 7 +- tests/installation/test_executor.py | 6 +- .../masonry/builders/test_editable_builder.py | 8 +- tests/utils/test_env.py | 26 ++- 10 files changed, 90 insertions(+), 193 deletions(-) diff --git a/tests/config/test_config.py b/tests/config/test_config.py index 4bd0cd048da..f3b13f23003 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -10,8 +10,8 @@ def test_config_get_default_value(config, name, value): assert config.get(name) is value -def test_config_get_processes_depended_on_values(config): - assert os.path.join("/foo", "virtualenvs") == config.get("virtualenvs.path") +def test_config_get_processes_depended_on_values(config, config_cache_dir): + assert str(config_cache_dir / "virtualenvs") == config.get("virtualenvs.path") @pytest.mark.parametrize( diff --git a/tests/conftest.py b/tests/conftest.py index e2b73936924..51128f764bb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -54,9 +54,21 @@ def all(self): # type: () -> Dict[str, Any] @pytest.fixture -def config_source(): +def config_cache_dir(tmp_dir): + path = Path(tmp_dir) / ".cache" / "pypoetry" + path.mkdir(parents=True) + return path + + +@pytest.fixture +def config_virtualenvs_path(config_cache_dir): + return config_cache_dir / "virtualenvs" + + +@pytest.fixture +def config_source(config_cache_dir): source = DictConfigSource() - source.add_property("cache-dir", "/foo") + source.add_property("cache-dir", str(config_cache_dir)) return source @@ -226,6 +238,7 @@ def _factory( dependencies=None, dev_dependencies=None, pyproject_content=None, + poetry_lock_content=None, install_deps=True, ): project_dir = workspace / "poetry-fixture-{}".format(name) @@ -249,6 +262,10 @@ def _factory( dev_dependencies=dev_dependencies, ).create(project_dir, with_tests=False) + if poetry_lock_content: + lock_file = project_dir / "poetry.lock" + lock_file.write_text(data=poetry_lock_content, encoding="utf-8") + poetry = Factory().create_poetry(project_dir) locker = TestLocker( diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index 4d0b9ada0c8..58bc8662bf5 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -25,37 +25,41 @@ def test_show_config_with_local_config_file_empty(tester, mocker): assert "" == tester.io.fetch_output() -def test_list_displays_default_value_if_not_set(tester, config): +def test_list_displays_default_value_if_not_set(tester, config, config_cache_dir): tester.execute("--list") - expected = """cache-dir = "/foo" + expected = """cache-dir = {cache} experimental.new-installer = true installer.parallel = true virtualenvs.create = true virtualenvs.in-project = null virtualenvs.options.always-copy = false -virtualenvs.path = {path} # /foo{sep}virtualenvs +virtualenvs.path = {path} # {virtualenvs} """.format( - path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), sep=os.path.sep + cache=json.dumps(str(config_cache_dir)), + path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), + virtualenvs=str(config_cache_dir / "virtualenvs"), ) assert expected == tester.io.fetch_output() -def test_list_displays_set_get_setting(tester, config): +def test_list_displays_set_get_setting(tester, config, config_cache_dir): tester.execute("virtualenvs.create false") tester.execute("--list") - expected = """cache-dir = "/foo" + expected = """cache-dir = {cache} experimental.new-installer = true installer.parallel = true virtualenvs.create = false virtualenvs.in-project = null virtualenvs.options.always-copy = false -virtualenvs.path = {path} # /foo{sep}virtualenvs +virtualenvs.path = {path} # {virtualenvs} """.format( - path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), sep=os.path.sep + cache=json.dumps(str(config_cache_dir)), + path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), + virtualenvs=str(config_cache_dir / "virtualenvs"), ) assert 0 == config.set_config_source.call_count @@ -83,20 +87,22 @@ def test_display_single_local_setting(command_tester_factory, fixture_dir): assert expected == tester.io.fetch_output() -def test_list_displays_set_get_local_setting(tester, config): +def test_list_displays_set_get_local_setting(tester, config, config_cache_dir): tester.execute("virtualenvs.create false --local") tester.execute("--list") - expected = """cache-dir = "/foo" + expected = """cache-dir = {cache} experimental.new-installer = true installer.parallel = true virtualenvs.create = false virtualenvs.in-project = null virtualenvs.options.always-copy = false -virtualenvs.path = {path} # /foo{sep}virtualenvs +virtualenvs.path = {path} # {virtualenvs} """.format( - path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), sep=os.path.sep + cache=json.dumps(str(config_cache_dir)), + path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), + virtualenvs=str(config_cache_dir / "virtualenvs"), ) assert 1 == config.set_config_source.call_count diff --git a/tests/console/commands/test_lock.py b/tests/console/commands/test_lock.py index 44c72967b32..c05ba257882 100644 --- a/tests/console/commands/test_lock.py +++ b/tests/console/commands/test_lock.py @@ -1,10 +1,8 @@ -import shutil - import pytest -from poetry.factory import Factory from poetry.packages import Locker from poetry.utils._compat import Path +from tests.helpers import get_package @pytest.fixture @@ -18,15 +16,26 @@ def tester(command_tester_factory): @pytest.fixture -def poetry_with_old_lockfile(fixture_dir, source_dir): - project_dir = source_dir / "project" - shutil.copytree(str(fixture_dir("old_lock")), str(project_dir)) - poetry = Factory().create_poetry(cwd=project_dir) - return poetry +def poetry_with_old_lockfile(project_factory, fixture_dir, source_dir): + source = fixture_dir("old_lock") + 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, + ) + +def test_lock_no_update(command_tester_factory, poetry_with_old_lockfile, repo): + repo.add_package(get_package("sampleproject", "1.3.1")) + repo.add_package(get_package("sampleproject", "2.0.0")) -def test_lock_no_update(command_tester_factory, poetry_with_old_lockfile, http): - http.disable() + locker = Locker( + lock=poetry_with_old_lockfile.pyproject.file.path.parent / "poetry.lock", + local_config=poetry_with_old_lockfile.locker._local_config, + ) + poetry_with_old_lockfile.set_locker(locker) locked_repository = poetry_with_old_lockfile.locker.locked_repository( with_dev_reqs=True diff --git a/tests/fixtures/old_lock/poetry.lock b/tests/fixtures/old_lock/poetry.lock index 57d585709e8..498df2edc9f 100644 --- a/tests/fixtures/old_lock/poetry.lock +++ b/tests/fixtures/old_lock/poetry.lock @@ -1,153 +1,19 @@ [[package]] category = "main" -description = "Python package for providing Mozilla's CA Bundle." -name = "certifi" -optional = false -python-versions = "*" -version = "2020.6.20" - -[[package]] -category = "main" -description = "Universal encoding detector for Python 2 and 3" -name = "chardet" -optional = false -python-versions = "*" -version = "3.0.4" - -[[package]] -category = "main" -description = "A Python library for the Docker Engine API." -name = "docker" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "4.3.1" - -[package.dependencies] -pywin32 = "227" -requests = ">=2.14.2,<2.18.0 || >2.18.0" -six = ">=1.4.0" -websocket-client = ">=0.32.0" - -[package.extras] -ssh = ["paramiko (>=2.4.2)"] -tls = ["pyOpenSSL (>=17.5.0)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] - -[[package]] -category = "main" -description = "Internationalized Domain Names in Applications (IDNA)" -name = "idna" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.10" - -[[package]] -category = "main" -description = "Python for Window Extensions" -marker = "sys_platform == \"win32\"" -name = "pywin32" -optional = false -python-versions = "*" -version = "227" - -[[package]] -category = "main" -description = "Python HTTP for Humans." -name = "requests" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.24.0" - -[package.dependencies] -certifi = ">=2017.4.17" -chardet = ">=3.0.2,<4" -idna = ">=2.5,<3" -urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" - -[package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] - -[[package]] -category = "main" -description = "Python 2 and 3 compatibility utilities" -name = "six" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.15.0" - -[[package]] -category = "main" -description = "HTTP library with thread-safe connection pooling, file post, and more." -name = "urllib3" +description = "A sample Python project" +name = "sampleproject" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "1.25.10" - -[package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] - -[[package]] -category = "main" -description = "WebSocket client for Python. hybi13 is supported." -name = "websocket-client" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.57.0" - -[package.dependencies] -six = "*" +version = "1.3.1" [metadata] -content-hash = "bb4c2f3c089b802c1930b6acbeed04711d93e9cdfd9a003eb17518a6d9f350c6" +content-hash = "c8c2c9d899e47bac3972e029ef0e71b75d5df98a28eebef25a75640a19aac177" lock-version = "1.0" python-versions = "^3.8" [metadata.files] -certifi = [ - {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, - {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, -] -chardet = [ - {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, - {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, -] -docker = [ - {file = "docker-4.3.1-py2.py3-none-any.whl", hash = "sha256:13966471e8bc23b36bfb3a6fb4ab75043a5ef1dac86516274777576bed3b9828"}, - {file = "docker-4.3.1.tar.gz", hash = "sha256:bad94b8dd001a8a4af19ce4becc17f41b09f228173ffe6a4e0355389eef142f2"}, -] -idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, -] -pywin32 = [ - {file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"}, - {file = "pywin32-227-cp27-cp27m-win_amd64.whl", hash = "sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116"}, - {file = "pywin32-227-cp35-cp35m-win32.whl", hash = "sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa"}, - {file = "pywin32-227-cp35-cp35m-win_amd64.whl", hash = "sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4"}, - {file = "pywin32-227-cp36-cp36m-win32.whl", hash = "sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be"}, - {file = "pywin32-227-cp36-cp36m-win_amd64.whl", hash = "sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2"}, - {file = "pywin32-227-cp37-cp37m-win32.whl", hash = "sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507"}, - {file = "pywin32-227-cp37-cp37m-win_amd64.whl", hash = "sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511"}, - {file = "pywin32-227-cp38-cp38-win32.whl", hash = "sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc"}, - {file = "pywin32-227-cp38-cp38-win_amd64.whl", hash = "sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e"}, - {file = "pywin32-227-cp39-cp39-win32.whl", hash = "sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295"}, - {file = "pywin32-227-cp39-cp39-win_amd64.whl", hash = "sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c"}, -] -requests = [ - {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, - {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, -] -six = [ - {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, - {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, -] -urllib3 = [ - {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"}, - {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, -] -websocket-client = [ - {file = "websocket_client-0.57.0-py2.py3-none-any.whl", hash = "sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549"}, - {file = "websocket_client-0.57.0.tar.gz", hash = "sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010"}, +sampleproject = [ + {file = "sampleproject-1.3.1-py2.py3-none-any.whl", hash = "sha256:26c9172e08244873b0e09c574a229bf2c251c67723a05e08fd3ec0c5ee423796"}, + {file = "sampleproject-1.3.1-py3-none-any.whl", hash = "sha256:75bb5bb4e74a1b77dc0cff25ebbacb54fe1318aaf99a86a036cefc86ed885ced"}, + {file = "sampleproject-1.3.1.tar.gz", hash = "sha256:3593ca2f1e057279d70d6144b14472fb28035b1da213dde60906b703d6f82c55"}, ] diff --git a/tests/fixtures/old_lock/pyproject.toml b/tests/fixtures/old_lock/pyproject.toml index 56ea6350953..377aa676be9 100644 --- a/tests/fixtures/old_lock/pyproject.toml +++ b/tests/fixtures/old_lock/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Poetry Developer "] [tool.poetry.dependencies] python = "^3.8" -docker = "^4.3.1" +sampleproject = ">=1.3.1" [tool.poetry.dev-dependencies] diff --git a/tests/installation/test_chef.py b/tests/installation/test_chef.py index 9fcbbea184c..4e59b608a24 100644 --- a/tests/installation/test_chef.py +++ b/tests/installation/test_chef.py @@ -60,7 +60,7 @@ def test_get_cached_archives_for_link(config, mocker): } -def test_get_cache_directory_for_link(config): +def test_get_cache_directory_for_link(config, config_cache_dir): chef = Chef( config, MockEnv( @@ -71,8 +71,11 @@ def test_get_cache_directory_for_link(config): directory = chef.get_cache_directory_for_link( Link("https://files.python-poetry.org/poetry-1.1.0.tar.gz") ) + expected = Path( - "/foo/artifacts/ba/63/13/283a3b3b7f95f05e9e6f84182d276f7bb0951d5b0cc24422b33f7a4648" + "{}/artifacts/ba/63/13/283a3b3b7f95f05e9e6f84182d276f7bb0951d5b0cc24422b33f7a4648".format( + config_cache_dir.as_posix() + ) ) assert expected == directory diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index bb659321d0f..3dfd818be21 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -126,9 +126,11 @@ def test_execute_executes_a_batch_of_operations( assert 5 == len(env.executed) -def test_execute_shows_skipped_operations_if_verbose(config, pool, io): +def test_execute_shows_skipped_operations_if_verbose( + config, pool, io, config_cache_dir +): config = Config() - config.merge({"cache-dir": "/foo"}) + config.merge({"cache-dir": config_cache_dir.as_posix()}) env = MockEnv() executor = Executor(env, pool, config, io) diff --git a/tests/masonry/builders/test_editable_builder.py b/tests/masonry/builders/test_editable_builder.py index daeff0e7777..3bf1e59c98e 100644 --- a/tests/masonry/builders/test_editable_builder.py +++ b/tests/masonry/builders/test_editable_builder.py @@ -176,9 +176,9 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts( - extended_poetry, + extended_poetry, tmp_dir ): - env = MockEnv(path=Path("/foo")) + env = MockEnv(path=Path(tmp_dir) / "foo") builder = EditableBuilder(extended_poetry, env, NullIO()) builder.build() @@ -219,8 +219,8 @@ def test_builder_installs_proper_files_when_packages_configured( assert len(paths) == len(expected) -def test_builder_should_execute_build_scripts(extended_without_setup_poetry): - env = MockEnv(path=Path("/foo")) +def test_builder_should_execute_build_scripts(extended_without_setup_poetry, tmp_dir): + env = MockEnv(path=Path(tmp_dir) / "foo") builder = EditableBuilder(extended_without_setup_poetry, env, NullIO()) builder.build() diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 8934ae43427..9c1e2c623d9 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -642,7 +642,7 @@ def test_run_with_input_non_zero_return(tmp_dir, tmp_venv): def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ones_first( - manager, poetry, config, mocker + manager, poetry, config, mocker, config_virtualenvs_path ): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -662,14 +662,14 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ manager.create_venv(NullIO()) m.assert_called_with( - Path("/foo/virtualenvs/{}-py3.7".format(venv_name)), + config_virtualenvs_path / "{}-py3.7".format(venv_name), executable="python3", flags={"always-copy": False}, ) def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific_ones( - manager, poetry, config, mocker + manager, poetry, config, mocker, config_virtualenvs_path ): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -688,7 +688,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific manager.create_venv(NullIO()) m.assert_called_with( - Path("/foo/virtualenvs/{}-py3.9".format(venv_name)), + config_virtualenvs_path / "{}-py3.9".format(venv_name), executable="python3.9", flags={"always-copy": False}, ) @@ -749,7 +749,7 @@ def test_create_venv_does_not_try_to_find_compatible_versions_with_executable( def test_create_venv_uses_patch_version_to_detect_compatibility( - manager, poetry, config, mocker + manager, poetry, config, mocker, config_virtualenvs_path ): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -773,18 +773,15 @@ def test_create_venv_uses_patch_version_to_detect_compatibility( assert not check_output.called m.assert_called_with( - Path( - "/foo/virtualenvs/{}-py{}.{}".format( - venv_name, version.major, version.minor - ) - ), + config_virtualenvs_path + / "{}-py{}.{}".format(venv_name, version.major, version.minor), executable=None, flags={"always-copy": False}, ) def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable( - manager, poetry, config, mocker + manager, poetry, config, mocker, config_virtualenvs_path ): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] @@ -811,11 +808,8 @@ def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable( assert check_output.called m.assert_called_with( - Path( - "/foo/virtualenvs/{}-py{}.{}".format( - venv_name, version.major, version.minor - 1 - ) - ), + config_virtualenvs_path + / "{}-py{}.{}".format(venv_name, version.major, version.minor - 1), executable="python{}.{}".format(version.major, version.minor - 1), flags={"always-copy": False}, ) From 4a14ebae1803c4ed46592e70236a298d5cc287c8 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 20 Oct 2020 22:32:48 +0200 Subject: [PATCH 061/222] tests: remove scripts for extended fixtures --- tests/fixtures/extended_project/pyproject.toml | 3 --- tests/fixtures/extended_project_without_setup/pyproject.toml | 3 --- 2 files changed, 6 deletions(-) diff --git a/tests/fixtures/extended_project/pyproject.toml b/tests/fixtures/extended_project/pyproject.toml index 954b12b3497..ecb7deb9107 100644 --- a/tests/fixtures/extended_project/pyproject.toml +++ b/tests/fixtures/extended_project/pyproject.toml @@ -25,6 +25,3 @@ build = "build.py" # Requirements [tool.poetry.dependencies] python = "~2.7 || ^3.4" - -[tool.poetry.scripts] -foo = "foo:bar" diff --git a/tests/fixtures/extended_project_without_setup/pyproject.toml b/tests/fixtures/extended_project_without_setup/pyproject.toml index 42375c03c25..5c9dc2774c7 100644 --- a/tests/fixtures/extended_project_without_setup/pyproject.toml +++ b/tests/fixtures/extended_project_without_setup/pyproject.toml @@ -27,6 +27,3 @@ generate-setup-file = false # Requirements [tool.poetry.dependencies] python = "~2.7 || ^3.4" - -[tool.poetry.scripts] -foo = "foo:bar" From 21abbfc43141e551507acee2051d4a11997fc2ba Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 20 Oct 2020 23:18:37 +0200 Subject: [PATCH 062/222] tests: ensure mock env uses accessible paths --- tests/console/conftest.py | 6 ++++-- tests/installation/test_executor.py | 25 +++++++++++++------------ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/tests/console/conftest.py b/tests/console/conftest.py index 8a3a5cba8ae..6d7ef0fa675 100644 --- a/tests/console/conftest.py +++ b/tests/console/conftest.py @@ -22,8 +22,10 @@ def installer(): @pytest.fixture -def env(): - return MockEnv(path=Path("/prefix"), base=Path("/base/prefix"), is_venv=True) +def env(tmp_dir): + path = Path(tmp_dir) / ".venv" + path.mkdir(parents=True) + return MockEnv(path=path, is_venv=True) @pytest.fixture(autouse=True) diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 3dfd818be21..e192f92df40 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -22,6 +22,13 @@ from tests.repositories.test_pypi_repository import MockRepository +@pytest.fixture +def env(tmp_dir): + path = Path(tmp_dir) / ".venv" + path.mkdir(parents=True) + return MockEnv(path=path, is_venv=True) + + @pytest.fixture() def io(): io = BufferedIO() @@ -57,12 +64,11 @@ def callback(request, uri, headers): def test_execute_executes_a_batch_of_operations( - config, pool, io, tmp_dir, mock_file_downloads + config, pool, io, tmp_dir, mock_file_downloads, env ): config = Config() config.merge({"cache-dir": tmp_dir}) - env = MockEnv(path=Path(tmp_dir)) executor = Executor(env, pool, config, io) file_package = Package( @@ -127,12 +133,11 @@ def test_execute_executes_a_batch_of_operations( def test_execute_shows_skipped_operations_if_verbose( - config, pool, io, config_cache_dir + config, pool, io, config_cache_dir, env ): config = Config() config.merge({"cache-dir": config_cache_dir.as_posix()}) - env = MockEnv() executor = Executor(env, pool, config, io) executor.verbose() @@ -152,8 +157,7 @@ def test_execute_shows_skipped_operations_if_verbose( @pytest.mark.skipif( not PY36, reason="Improved error rendering is only available on Python >=3.6" ) -def test_execute_should_show_errors(config, mocker, io): - env = MockEnv() +def test_execute_should_show_errors(config, mocker, io, env): executor = Executor(env, pool, config, io) executor.verbose() @@ -175,9 +179,8 @@ def test_execute_should_show_errors(config, mocker, io): def test_execute_should_show_operation_as_cancelled_on_subprocess_keyboard_interrupt( - config, mocker, io + config, mocker, io, env ): - env = MockEnv() executor = Executor(env, pool, config, io) executor.verbose() @@ -196,8 +199,7 @@ def test_execute_should_show_operation_as_cancelled_on_subprocess_keyboard_inter assert expected == io.fetch_output() -def test_execute_should_gracefully_handle_io_error(config, mocker, io): - env = MockEnv() +def test_execute_should_gracefully_handle_io_error(config, mocker, io, env): executor = Executor(env, pool, config, io) executor.verbose() @@ -223,7 +225,7 @@ def write_line(string, flags=None): def test_executor_should_delete_incomplete_downloads( - config, io, tmp_dir, mocker, pool, mock_file_downloads + config, io, tmp_dir, mocker, pool, mock_file_downloads, env ): fixture = Path(__file__).parent.parent.joinpath( "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl" @@ -246,7 +248,6 @@ def test_executor_should_delete_incomplete_downloads( config = Config() config.merge({"cache-dir": tmp_dir}) - env = MockEnv(path=Path(tmp_dir)) executor = Executor(env, pool, config, io) with pytest.raises(Exception, match="Download error"): From e43bc9ad1dccdcb1dcc25a3e4b670500ef289c20 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 20 Oct 2020 23:19:48 +0200 Subject: [PATCH 063/222] tests/executor: verify return code after output --- tests/installation/test_executor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index e192f92df40..53ad3677350 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -101,7 +101,7 @@ def test_execute_executes_a_batch_of_operations( source_url="https://github.com/demo/demo.git", ) - assert 0 == executor.execute( + return_code = executor.execute( [ Install(Package("pytest", "3.5.2")), Uninstall(Package("attrs", "17.4.0")), @@ -130,6 +130,7 @@ def test_execute_executes_a_batch_of_operations( output = set(io.fetch_output().splitlines()) assert expected == output assert 5 == len(env.executed) + assert 0 == return_code def test_execute_shows_skipped_operations_if_verbose( From 5b4c6a5eb47ac1eb1ea34e7a5d87e13e577024bb Mon Sep 17 00:00:00 2001 From: Lucas Cavalcante de Sousa Date: Sat, 31 Oct 2020 16:56:56 -0300 Subject: [PATCH 064/222] Remove unused functions Resolves: #3236 \#3236 states that some functions are defined but never used, thus this PR remove then. While removing them, this also makes sure that there is no trouble with: ```bash $ poetry run pytest tests/ $ poetry run pre-commit run --all-files ``` as stated at `CONTRIBUTING.md`. The functions removed were: - `poetry/config/config.py`: + `_get_validator` - `poetry/console/commands/config.py`: + `_list_setting` + `_get_formatted_value` - `poetry/console/commands/run.py` : + `_module` - `poetry/console/commands/self/update.py : + `_bin_path` - `poetry/mixology/version_solver.py`: + `_excludes_single_version` - `poetry/puzzle/provider.py`: + `_formatter_elapsed` - `poetry/utils/setup_reader.py`: + `_is_empty_result` By removing some of those functions Flack announced that there is no more need to do some includes, so they were removed too: - `poetry/mixology/version_solver.py`: + `from typing import Any` + `from poetry.core.semver import Version` + `from poetry.core.semver import VersionRange` - `poetry/puzzle/provider.py`: + `import time` --- poetry/config/config.py | 12 ------------ poetry/console/commands/config.py | 16 ---------------- poetry/console/commands/run.py | 11 ----------- poetry/console/commands/self/update.py | 8 -------- poetry/mixology/version_solver.py | 6 ------ poetry/puzzle/provider.py | 6 +----- poetry/utils/setup_reader.py | 8 -------- 7 files changed, 1 insertion(+), 66 deletions(-) diff --git a/poetry/config/config.py b/poetry/config/config.py index 6f18127e8ff..666f2e74ce2 100644 --- a/poetry/config/config.py +++ b/poetry/config/config.py @@ -136,18 +136,6 @@ def process(self, value): # type: (Any) -> Any return re.sub(r"{(.+?)}", lambda m: self.get(m.group(1)), value) - def _get_validator(self, name): # type: (str) -> Callable - if name in { - "virtualenvs.create", - "virtualenvs.in-project", - "virtualenvs.options.always-copy", - "installer.parallel", - }: - return boolean_validator - - if name == "virtualenvs.path": - return str - def _get_normalizer(self, name): # type: (str) -> Callable if name in { "virtualenvs.create", diff --git a/poetry/console/commands/config.py b/poetry/console/commands/config.py index 934b2c818eb..2e84429ec36 100644 --- a/poetry/console/commands/config.py +++ b/poetry/console/commands/config.py @@ -302,14 +302,6 @@ def _list_configuration(self, config, raw, k=""): self.line(message) - def _list_setting(self, contents, setting=None, k=None, default=None): - values = self._get_setting(contents, setting, k, default) - - for value in values: - self.line( - "{} = {}".format(value[0], value[1]) - ) - def _get_setting(self, contents, setting=None, k=None, default=None): orig_k = k @@ -351,11 +343,3 @@ def _get_setting(self, contents, setting=None, k=None, default=None): values.append(((k or "") + key, value)) return values - - def _get_formatted_value(self, value): - if isinstance(value, list): - value = [json.dumps(val) if isinstance(val, list) else val for val in value] - - value = "[{}]".format(", ".join(value)) - - return json.dumps(value) diff --git a/poetry/console/commands/run.py b/poetry/console/commands/run.py index fda01114426..46202c52c37 100644 --- a/poetry/console/commands/run.py +++ b/poetry/console/commands/run.py @@ -47,14 +47,3 @@ def run_script(self, script, args): ] return self.env.execute(*cmd) - - @property - def _module(self): - from poetry.core.masonry.utils.module import Module - - poetry = self.poetry - package = poetry.package - path = poetry.file.parent - module = Module(package.name, path.as_posix(), package.packages) - - return module diff --git a/poetry/console/commands/self/update.py b/poetry/console/commands/self/update.py index 8660bea2f09..f98da89632f 100644 --- a/poetry/console/commands/self/update.py +++ b/poetry/console/commands/self/update.py @@ -257,14 +257,6 @@ def _get_release_name(self, version): return "poetry-{}-{}".format(version, platform) - def _bin_path(self, base_path, bin): - from poetry.utils._compat import WINDOWS - - if WINDOWS: - return (base_path / "Scripts" / bin).with_suffix(".exe") - - return base_path / "bin" / bin - def make_bin(self): from poetry.utils._compat import WINDOWS diff --git a/poetry/mixology/version_solver.py b/poetry/mixology/version_solver.py index 679ecf07c2d..cee323ea367 100644 --- a/poetry/mixology/version_solver.py +++ b/poetry/mixology/version_solver.py @@ -2,7 +2,6 @@ import time from typing import TYPE_CHECKING -from typing import Any from typing import Dict from typing import List from typing import Union @@ -10,8 +9,6 @@ from poetry.core.packages import Dependency from poetry.core.packages import Package from poetry.core.packages import ProjectPackage -from poetry.core.semver import Version -from poetry.core.semver import VersionRange from .failure import SolveFailure from .incompatibility import Incompatibility @@ -423,9 +420,6 @@ def _get_min(dependency): return dependency.complete_name - def _excludes_single_version(self, constraint): # type: (Any) -> bool - return isinstance(VersionRange().difference(constraint), Version) - def _result(self): # type: () -> SolverResult """ Creates a #SolverResult from the decisions in _solution diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index c05efbd6844..d158a31dc2d 100644 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -1,7 +1,6 @@ import logging import os import re -import time from contextlib import contextmanager from tempfile import mkdtemp @@ -44,10 +43,7 @@ class Indicator(ProgressIndicator): - def _formatter_elapsed(self): - elapsed = time.time() - self._start_time - - return "{:.1f}s".format(elapsed) + pass class Provider: diff --git a/poetry/utils/setup_reader.py b/poetry/utils/setup_reader.py index 6a45103c1f4..3a1ce2f170d 100644 --- a/poetry/utils/setup_reader.py +++ b/poetry/utils/setup_reader.py @@ -59,14 +59,6 @@ def read_from_directory( return result - @classmethod - def _is_empty_result(cls, result): # type: (Dict[str, Any]) -> bool - return ( - not result["install_requires"] - and not result["extras_require"] - and not result["python_requires"] - ) - def read_setup_py( self, filepath ): # type: (Union[basestring, Path]) -> Dict[str, Union[List, Dict]] From cc195f1dd086d1c4d12a3acc8d6766981ba431ac Mon Sep 17 00:00:00 2001 From: Anatoli Babenia Date: Sat, 10 Oct 2020 13:01:30 +0300 Subject: [PATCH 065/222] Use `.zshrc` instead of `.zprofile` Because `.zprofile` is only run for login shells, it is not used by graphical terminals. `.zprofile` is also not created by default. Fixes #2620 --- get-poetry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/get-poetry.py b/get-poetry.py index c14c9d4d6ec..73b818c7a5c 100644 --- a/get-poetry.py +++ b/get-poetry.py @@ -903,7 +903,7 @@ def get_unix_profiles(self): if "zsh" in SHELL: zdotdir = os.getenv("ZDOTDIR", HOME) - profiles.append(os.path.join(zdotdir, ".zprofile")) + profiles.append(os.path.join(zdotdir, ".zshrc")) bash_profile = os.path.join(HOME, ".bash_profile") if os.path.exists(bash_profile): From 73585b600dc528ffb0904872befd94f9ea0b9d02 Mon Sep 17 00:00:00 2001 From: Rayan Das Date: Sat, 31 Oct 2020 13:32:29 +0530 Subject: [PATCH 066/222] consider using if expression Signed-off-by: Rayan Das --- poetry/utils/appdirs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry/utils/appdirs.py b/poetry/utils/appdirs.py index 5b9da0cdde3..ab99506a028 100644 --- a/poetry/utils/appdirs.py +++ b/poetry/utils/appdirs.py @@ -92,7 +92,7 @@ def user_data_dir(appname, roaming=False): That means, by default "~/.local/share/". """ if WINDOWS: - const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" + const = "CSIDL_APPDATA" if roaming else "CSIDL_LOCAL_APPDATA" path = os.path.join(os.path.normpath(_get_win_folder(const)), appname) elif sys.platform == "darwin": path = os.path.join(expanduser("~/Library/Application Support/"), appname) From f5f6efca250bdc3df530d1764943976ccbd58db0 Mon Sep 17 00:00:00 2001 From: Rayan Das Date: Sat, 31 Oct 2020 13:35:20 +0530 Subject: [PATCH 067/222] iterate the dictionary directly instead of calling .keys() Signed-off-by: Rayan Das --- poetry/puzzle/provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index d158a31dc2d..333dfc60cf3 100644 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -101,7 +101,7 @@ def search_for(self, dependency): # type: (Dependency) -> List[Package] if dependency.is_root: return PackageCollection(dependency, [self._package]) - for constraint in self._search_for.keys(): + for constraint in self._search_for: if ( constraint.is_same_package_as(dependency) and constraint.constraint.intersect(dependency.constraint) From e35f0819e3a9d6ea8c87be935d39db948d91f105 Mon Sep 17 00:00:00 2001 From: Rayan Das Date: Sat, 31 Oct 2020 13:44:14 +0530 Subject: [PATCH 068/222] the built-in function being used does not require comprehension and can work directly with a generator expression Signed-off-by: Rayan Das --- poetry/mixology/incompatibility.py | 2 +- poetry/mixology/version_solver.py | 8 +++----- poetry/utils/password_manager.py | 8 +++----- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/poetry/mixology/incompatibility.py b/poetry/mixology/incompatibility.py index 3cbc867874a..a5be2dadbdf 100644 --- a/poetry/mixology/incompatibility.py +++ b/poetry/mixology/incompatibility.py @@ -23,7 +23,7 @@ def __init__( if ( len(terms) != 1 and isinstance(cause, ConflictCause) - and any([term.is_positive() and term.dependency.is_root for term in terms]) + and any(term.is_positive() and term.dependency.is_root for term in terms) ): terms = [ term diff --git a/poetry/mixology/version_solver.py b/poetry/mixology/version_solver.py index cee323ea367..8d70a0df288 100644 --- a/poetry/mixology/version_solver.py +++ b/poetry/mixology/version_solver.py @@ -403,11 +403,9 @@ def _get_min(dependency): # We'll continue adding its dependencies, then go back to # unit propagation which will guide us to choose a better version. conflict = conflict or all( - [ - term.dependency.complete_name == dependency.complete_name - or self._solution.satisfies(term) - for term in incompatibility.terms - ] + term.dependency.complete_name == dependency.complete_name + or self._solution.satisfies(term) + for term in incompatibility.terms ) if not conflict: diff --git a/poetry/utils/password_manager.py b/poetry/utils/password_manager.py index 24a615a46bf..6c993840282 100644 --- a/poetry/utils/password_manager.py +++ b/poetry/utils/password_manager.py @@ -101,11 +101,9 @@ def _check(self): backends = keyring.backend.get_all_keyring() self._is_available = any( - [ - b.name.split(" ")[0] not in ["chainer", "fail"] - and "plaintext" not in b.name.lower() - for b in backends - ] + b.name.split(" ")[0] not in ["chainer", "fail"] + and "plaintext" not in b.name.lower() + for b in backends ) except Exception: self._is_available = False From d646d45d8776a88bff6ad8126f52b92cd944f8b9 Mon Sep 17 00:00:00 2001 From: Steph Samson Date: Mon, 2 Nov 2020 23:29:48 +0100 Subject: [PATCH 069/222] doc: mention Conda's dependency resolver Resolves: #477 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3ad7b97d5f1..bc392a15eaa 100644 --- a/README.md +++ b/README.md @@ -195,8 +195,8 @@ dependency management, packaging and publishing. It takes inspiration in tools that exist in other languages, like `composer` (PHP) or `cargo` (Rust). -And, finally, there is no reliable tool to properly resolve dependencies in Python, so I started `poetry` -to bring an exhaustive dependency resolver to the Python community. +And, finally, I started `poetry` to bring another exhaustive dependency resolver to the Python community apart from +[Conda's](https://conda.io). ### What about Pipenv? From 369ced14be5218a6237d8eb0a579f6592fd922ee Mon Sep 17 00:00:00 2001 From: adnan-st <72813655+adnan-st@users.noreply.github.com> Date: Fri, 13 Nov 2020 00:43:44 -0500 Subject: [PATCH 070/222] Fixed missing space in env activation 'one-liner' (#3350) `source`poetry env info --path`/bin/activate` replaced with: `source `poetry env info --path`/bin/activate` --- docs/docs/basic-usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/basic-usage.md b/docs/docs/basic-usage.md index 178c75954d6..c8a4e0a7e3a 100644 --- a/docs/docs/basic-usage.md +++ b/docs/docs/basic-usage.md @@ -120,7 +120,7 @@ To deactivate this virtual environment simply use `deactivate`. |-------------------|------------------------------------------------|---------------------------------------------|-----------------| | New Shell | `poetry shell` | `poetry shell` | `exit` | | Manual Activation | `source {path_to_venv}/bin/activate` | `source {path_to_venv}\Scripts\activate.bat`| `deactivate` | -| One-liner | ```source`poetry env info --path`/bin/activate```| | `deactivate` | +| One-liner | ```source `poetry env info --path`/bin/activate```| | `deactivate` | ### Version constraints From 639d5e05773ed8b91b80ec03c2ee64f500534c10 Mon Sep 17 00:00:00 2001 From: Murad Bashirov <66242799+spitfire-hash@users.noreply.github.com> Date: Thu, 19 Nov 2020 00:07:41 +0400 Subject: [PATCH 071/222] Fixed the command for activating venv in Windows (#3373) * Fixed the command(deleted the `source` because you don't need `source` in order to run batch files * Made code of the table look better, nothing changed in client. --- docs/docs/basic-usage.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/docs/basic-usage.md b/docs/docs/basic-usage.md index c8a4e0a7e3a..eed4817379f 100644 --- a/docs/docs/basic-usage.md +++ b/docs/docs/basic-usage.md @@ -111,15 +111,15 @@ To deactivate the virtual environment without leaving the shell use `deactivate` Alternatively, to avoid creating a new shell, you can manually activate the -virtual environment by running `source {path_to_venv}/bin/activate` (`source {path_to_venv}\Scripts\activate.bat` on Windows). +virtual environment by running `source {path_to_venv}/bin/activate` (`{path_to_venv}\Scripts\activate.bat` on Windows). To get the path to your virtual environment run `poetry env info --path`. You can also combine these into a nice one-liner, `source $(poetry env info --path)/bin/activate` To deactivate this virtual environment simply use `deactivate`. -| | POSIX Shell | Windows | Exit/Deactivate | -|-------------------|------------------------------------------------|---------------------------------------------|-----------------| -| New Shell | `poetry shell` | `poetry shell` | `exit` | -| Manual Activation | `source {path_to_venv}/bin/activate` | `source {path_to_venv}\Scripts\activate.bat`| `deactivate` | +| | POSIX Shell | Windows | Exit/Deactivate | +|-------------------|---------------------------------------------------|---------------------------------------------|-----------------| +| New Shell | `poetry shell` | `poetry shell` | `exit` | +| Manual Activation | `source {path_to_venv}/bin/activate` | `{path_to_venv}\Scripts\activate.bat` | `deactivate` | | One-liner | ```source `poetry env info --path`/bin/activate```| | `deactivate` | From bf30ca696aa47bcc42937910d620c95c95c0741e Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 24 Nov 2020 13:55:55 +0100 Subject: [PATCH 072/222] Remove python 2.7/3.5 compatibility code (#3405) * Remove python 2.7/3.5 compatibility code This change removes code, dependencies and tests related to python 2.7 and 3.5 compatibility. The minimum supported runtime python version for poetry is now 3.6. * tests: mitigate httpretty urllib incompatibility Relates-to: #3404 --- poetry.lock | 570 +++--------------- poetry/config/config.py | 5 +- poetry/console/application.py | 3 +- poetry/console/commands/check.py | 3 +- poetry/console/commands/config.py | 13 +- poetry/console/commands/init.py | 20 +- poetry/console/commands/new.py | 3 +- poetry/console/commands/publish.py | 4 +- poetry/console/commands/self/update.py | 4 +- poetry/console/config/application_config.py | 13 +- poetry/factory.py | 2 +- poetry/inspection/info.py | 14 +- poetry/installation/authenticator.py | 8 +- poetry/installation/chef.py | 2 +- poetry/installation/executor.py | 14 +- poetry/installation/pip_installer.py | 9 +- poetry/locations.py | 3 +- poetry/masonry/builders/editable.py | 2 +- poetry/mixology/partial_solution.py | 7 +- poetry/packages/locker.py | 9 +- poetry/poetry.py | 3 +- poetry/publishing/publisher.py | 2 +- poetry/publishing/uploader.py | 2 +- poetry/puzzle/provider.py | 11 +- poetry/repositories/installed_repository.py | 2 +- poetry/repositories/legacy_repository.py | 12 +- poetry/repositories/pypi_repository.py | 21 +- poetry/utils/_compat.py | 249 +------- poetry/utils/env.py | 6 +- poetry/utils/exporter.py | 7 +- poetry/utils/helpers.py | 2 +- poetry/utils/setup_reader.py | 25 +- poetry/utils/shell.py | 3 +- pyproject.toml | 34 +- tests/compat.py | 4 + tests/conftest.py | 4 +- tests/console/commands/env/conftest.py | 3 +- tests/console/commands/env/helpers.py | 2 +- tests/console/commands/env/test_info.py | 3 +- tests/console/commands/env/test_remove.py | 2 +- tests/console/commands/env/test_use.py | 10 +- tests/console/commands/self/test_update.py | 3 +- tests/console/commands/test_add.py | 23 +- tests/console/commands/test_cache.py | 4 +- tests/console/commands/test_check.py | 14 +- tests/console/commands/test_config.py | 6 +- tests/console/commands/test_init.py | 5 +- tests/console/commands/test_lock.py | 3 +- tests/console/commands/test_publish.py | 34 +- tests/console/commands/test_search.py | 4 +- tests/console/conftest.py | 3 +- tests/helpers.py | 20 +- tests/inspection/test_info.py | 22 +- tests/installation/test_chef.py | 3 +- tests/installation/test_chooser.py | 3 +- tests/installation/test_executor.py | 7 +- tests/installation/test_installer.py | 15 +- tests/installation/test_installer_old.py | 15 +- tests/installation/test_pip_installer.py | 3 +- .../masonry/builders/test_editable_builder.py | 3 +- ...st_python_requirement_solution_provider.py | 9 - .../test_python_requirement_solution.py | 6 - tests/publishing/test_publisher.py | 3 +- tests/publishing/test_uploader.py | 3 +- tests/puzzle/conftest.py | 4 +- tests/puzzle/test_provider.py | 8 +- tests/puzzle/test_solver.py | 3 +- .../repositories/test_installed_repository.py | 8 +- tests/repositories/test_legacy_repository.py | 14 +- tests/repositories/test_pypi_repository.py | 4 +- tests/test_factory.py | 26 +- tests/utils/test_env.py | 78 +-- tests/utils/test_env_site.py | 5 +- tests/utils/test_exporter.py | 7 +- tests/utils/test_helpers.py | 3 +- tests/utils/test_setup_reader.py | 8 - 76 files changed, 343 insertions(+), 1151 deletions(-) create mode 100644 tests/compat.py diff --git a/poetry.lock b/poetry.lock index 973733a6d61..b066ade9a7c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -16,30 +16,18 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "20.2.0" +version = "20.3.0" description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"] -docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] -[[package]] -name = "backports.functools-lru-cache" -version = "1.6.1" -description = "Backport of functools.lru_cache" -category = "dev" -optional = false -python-versions = ">=2.6" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov"] - [[package]] name = "cachecontrol" version = "0.12.6" @@ -72,7 +60,7 @@ msgpack = ["msgpack-python (>=0.5,<0.6)"] [[package]] name = "certifi" -version = "2020.6.20" +version = "2020.11.8" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -126,40 +114,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] crashtest = {version = ">=0.3.0,<0.4.0", markers = "python_version >= \"3.6\" and python_version < \"4.0\""} -enum34 = {version = ">=1.1,<2.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\""} pastel = ">=0.2.0,<0.3.0" pylev = ">=1.3,<2.0" -typing = {version = ">=3.6,<4.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\""} -typing-extensions = {version = ">=3.6,<4.0", markers = "python_version >= \"3.5\" and python_full_version < \"3.5.4\""} [[package]] name = "colorama" -version = "0.4.3" +version = "0.4.4" description = "Cross-platform colored terminal text." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -[[package]] -name = "configparser" -version = "4.0.2" -description = "Updated configparser from Python 3.7 for Python 2.6+." -category = "main" -optional = false -python-versions = ">=2.6" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2)", "pytest-flake8", "pytest-black-multipy"] - -[[package]] -name = "contextlib2" -version = "0.6.0.post1" -description = "Backports and enhancements for the contextlib module" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - [[package]] name = "coverage" version = "5.3" @@ -181,7 +146,7 @@ python-versions = ">=3.6,<4.0" [[package]] name = "cryptography" -version = "3.1.1" +version = "3.2.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false @@ -189,16 +154,14 @@ python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" [package.dependencies] cffi = ">=1.8,<1.11.3 || >1.11.3" -enum34 = {version = "*", markers = "python_version < \"3\""} -ipaddress = {version = "*", markers = "python_version < \"3\""} six = ">=1.4.1" [package.extras] -docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0,<3.1.0 || >3.1.0,<3.1.1 || >3.1.1)", "sphinx-rtd-theme"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] +test = ["pytest (>=3.6.0,!=3.9.0,!=3.9.1,!=3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] name = "distlib" @@ -208,25 +171,6 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "entrypoints" -version = "0.3" -description = "Discover and load entry points from installed packages." -category = "main" -optional = false -python-versions = ">=2.7" - -[package.dependencies] -configparser = {version = ">=3.5", markers = "python_version == \"2.7\""} - -[[package]] -name = "enum34" -version = "1.1.10" -description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "filelock" version = "3.0.12" @@ -235,38 +179,6 @@ category = "main" optional = false python-versions = "*" -[[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]] -name = "functools32" -version = "3.2.3-2" -description = "Backport of the functools module from Python 3.2.3 for use on 2.7 and PyPy." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "futures" -version = "3.3.0" -description = "Backport of the concurrent.futures package from Python 3" -category = "main" -optional = false -python-versions = ">=2.6, <3" - -[[package]] -name = "glob2" -version = "0.6" -description = "Version of the glob module that can capture patterns and supports recursive wildcards" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "html5lib" version = "1.1" @@ -287,18 +199,15 @@ lxml = ["lxml"] [[package]] name = "httpretty" -version = "0.9.7" +version = "1.0.2" description = "HTTP client mock for Python" category = "dev" optional = false -python-versions = "*" - -[package.dependencies] -six = "*" +python-versions = ">=3" [[package]] name = "identify" -version = "1.5.5" +version = "1.5.10" description = "File identification library for Python" category = "dev" optional = false @@ -324,9 +233,6 @@ optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] -configparser = {version = ">=3.5", markers = "python_version < \"3\""} -contextlib2 = {version = "*", markers = "python_version < \"3\""} -pathlib2 = {version = "*", markers = "python_version < \"3\""} zipp = ">=0.5" [package.extras] @@ -335,92 +241,46 @@ testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] [[package]] name = "importlib-resources" -version = "3.0.0" +version = "3.3.0" description = "Read resources from Python packages" category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" [package.dependencies] -contextlib2 = {version = "*", markers = "python_version < \"3\""} -pathlib2 = {version = "*", markers = "python_version < \"3\""} -singledispatch = {version = "*", markers = "python_version < \"3.4\""} -typing = {version = "*", markers = "python_version < \"3.5\""} zipp = {version = ">=0.4", markers = "python_version < \"3.8\""} [package.extras] docs = ["sphinx", "rst.linker", "jaraco.packaging"] -[[package]] -name = "ipaddress" -version = "1.0.23" -description = "IPv4/IPv6 manipulation library" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "jeepney" -version = "0.4.3" +version = "0.6.0" description = "Low-level, pure Python DBus protocol wrapper." category = "main" optional = false -python-versions = ">=3.5" - -[package.extras] -dev = ["testpath"] - -[[package]] -name = "keyring" -version = "18.0.1" -description = "Store and access your passwords safely." -category = "main" -optional = false -python-versions = ">=2.7" - -[package.dependencies] -entrypoints = "*" -pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""} -secretstorage = {version = "<3", markers = "(sys_platform == \"linux2\" or sys_platform == \"linux\") and python_version < \"3.5\""} - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs", "pytest-flake8"] - -[[package]] -name = "keyring" -version = "20.0.1" -description = "Store and access your passwords safely." -category = "main" -optional = false -python-versions = ">=3.5" - -[package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} -pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""} -secretstorage = {version = "*", markers = "sys_platform == \"linux\""} +python-versions = ">=3.6" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov"] +test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio"] [[package]] name = "keyring" -version = "21.4.0" +version = "21.5.0" description = "Store and access your passwords safely." category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +importlib-metadata = {version = ">=1", markers = "python_version < \"3.8\""} 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", markers = "sys_platform == \"linux\""} +SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black (>=0.3.7)", "pytest-cov", "pytest-mypy"] +testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "pytest-black (>=0.3.7)", "pytest-mypy"] [[package]] name = "lockfile" @@ -430,37 +290,9 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "mock" -version = "3.0.5" -description = "Rolling backport of unittest.mock for all Pythons" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -funcsigs = {version = ">=1", markers = "python_version < \"3.3\""} -six = "*" - -[package.extras] -build = ["twine", "wheel", "blurb"] -docs = ["sphinx"] -test = ["pytest", "pytest-cov"] - [[package]] name = "more-itertools" -version = "5.0.0" -description = "More routines for operating on iterables, beyond itertools" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -six = ">=1.0.0,<2.0.0" - -[[package]] -name = "more-itertools" -version = "8.5.0" +version = "8.6.0" description = "More routines for operating on iterables, beyond itertools" category = "dev" optional = false @@ -502,18 +334,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "pathlib2" -version = "2.3.5" -description = "Object-oriented filesystem paths" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -scandir = {version = "*", markers = "python_version < \"3.5\""} -six = "*" - [[package]] name = "pexpect" version = "4.8.0" @@ -527,7 +347,7 @@ ptyprocess = ">=0.5" [[package]] name = "pkginfo" -version = "1.5.0.1" +version = "1.6.1" description = "Query metadatdata from sdists / bdists / installed packages." category = "main" optional = false @@ -559,15 +379,11 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] -enum34 = {version = ">=1.1.10,<2.0.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\""} -functools32 = {version = ">=3.2.3-2,<4.0.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\""} importlib-metadata = {version = ">=1.7.0,<2.0.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.5\" and python_version < \"3.8\""} -pathlib2 = {version = ">=2.3.5,<3.0.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\""} -typing = {version = ">=3.7.4.1,<4.0.0.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\""} [[package]] name = "pre-commit" -version = "2.7.1" +version = "2.9.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false @@ -623,34 +439,6 @@ category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -[[package]] -name = "pytest" -version = "4.6.11" -description = "pytest: simple powerful testing with Python" -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" - -[package.dependencies] -atomicwrites = ">=1.0" -attrs = ">=17.4.0" -colorama = {version = "*", markers = "sys_platform == \"win32\" and python_version != \"3.4\""} -funcsigs = {version = ">=1.0", markers = "python_version < \"3.0\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -more-itertools = [ - {version = ">=4.0.0,<6.0.0", markers = "python_version <= \"2.7\""}, - {version = ">=4.0.0", markers = "python_version > \"2.7\""}, -] -packaging = "*" -pathlib2 = {version = ">=2.2.0", markers = "python_version < \"3.6\""} -pluggy = ">=0.12,<1.0" -py = ">=1.5.0" -six = ">=1.10.0" -wcwidth = "*" - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "nose", "requests", "mock"] - [[package]] name = "pytest" version = "5.4.3" @@ -666,13 +454,12 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} more-itertools = ">=4.0.0" packaging = "*" -pathlib2 = {version = ">=2.2.0", markers = "python_version < \"3.6\""} pluggy = ">=0.12,<1.0" py = ">=1.5.0" wcwidth = "*" [package.extras] -checkqa-mypy = ["mypy (v0.761)"] +checkqa-mypy = ["mypy (==v0.761)"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] @@ -688,7 +475,7 @@ coverage = ">=4.4" pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"] +testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-mock" @@ -699,7 +486,6 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] -mock = {version = "*", markers = "python_version < \"3.0\""} pytest = ">=2.7" [package.extras] @@ -736,7 +522,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "requests" -version = "2.24.0" +version = "2.25.0" description = "Python HTTP for Humans." category = "main" optional = false @@ -746,11 +532,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" certifi = ">=2017.4.17" chardet = ">=3.0.2,<4" idna = ">=2.5,<3" -urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" +urllib3 = ">=1.21.1,<1.27" [package.extras] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] name = "requests-toolbelt" @@ -763,38 +549,16 @@ python-versions = "*" [package.dependencies] requests = ">=2.0.1,<3.0.0" -[[package]] -name = "scandir" -version = "1.10.0" -description = "scandir, a better directory iterator and faster os.walk()" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "secretstorage" -version = "2.3.1" -description = "Python bindings to FreeDesktop.org Secret Service API" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -cryptography = "*" - -[package.extras] -dbus-python = ["dbus-python"] - [[package]] name = "secretstorage" -version = "3.1.2" +version = "3.2.0" description = "Python bindings to FreeDesktop.org Secret Service API" category = "main" optional = false python-versions = ">=3.5" [package.dependencies] -cryptography = "*" +cryptography = ">=2.0" jeepney = ">=0.4.2" [[package]] @@ -805,17 +569,6 @@ category = "main" optional = false python-versions = "!=3.0,!=3.1,!=3.2,!=3.3,>=2.6" -[[package]] -name = "singledispatch" -version = "3.4.0.3" -description = "This library brings functools.singledispatch from Python 3.4 to Python 2.6-3.3." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -six = "*" - [[package]] name = "six" version = "1.15.0" @@ -824,14 +577,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -[[package]] -name = "subprocess32" -version = "3.5.4" -description = "A backport of the subprocess module from Python 3 for use on 2.x." -category = "main" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4" - [[package]] name = "termcolor" version = "1.1.0" @@ -842,11 +587,11 @@ python-versions = "*" [[package]] name = "toml" -version = "0.10.1" +version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" category = "dev" optional = false -python-versions = "*" +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomlkit" @@ -856,14 +601,9 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -[package.dependencies] -enum34 = {version = ">=1.1,<2.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\""} -functools32 = {version = ">=3.2.3,<4.0.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\""} -typing = {version = ">=3.6,<4.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\""} - [[package]] name = "tox" -version = "3.20.0" +version = "3.20.1" description = "tox is a generic virtualenv management and test command line tool" category = "dev" optional = false @@ -872,7 +612,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} filelock = ">=3.0.0" -importlib-metadata = {version = ">=0.12,<2", markers = "python_version < \"3.8\""} +importlib-metadata = {version = ">=0.12,<3", markers = "python_version < \"3.8\""} packaging = ">=14" pluggy = ">=0.12.0" py = ">=1.4.17" @@ -884,25 +624,6 @@ virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2, docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "pytest-xdist (>=1.22.2)"] -[[package]] -name = "typing" -version = "3.7.4.3" -description = "Type Hints for Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "typing-extensions" -version = "3.7.4.3" -description = "Backported and Experimental Type Hints for Python 3.5+" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -typing = {version = ">=3.7.4", markers = "python_version < \"3.5\""} - [[package]] name = "urllib3" version = "1.25.10" @@ -914,11 +635,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] brotli = ["brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.0.31" +version = "20.2.1" description = "Virtual Python Environment builder" category = "main" optional = false @@ -928,14 +649,13 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" appdirs = ">=1.4.3,<2" distlib = ">=0.3.1,<1" filelock = ">=3.0.0,<4" -importlib-metadata = {version = ">=0.12,<2", markers = "python_version < \"3.8\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} importlib-resources = {version = ">=1.0", markers = "python_version < \"3.7\""} -pathlib2 = {version = ">=2.3.3,<3", markers = "python_version < \"3.4\" and sys_platform != \"win32\""} six = ">=1.9.0,<2" [package.extras] docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] -testing = ["coverage (>=5)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "pytest-xdist (>=1.31.0)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "pytest-xdist (>=1.31.0)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] [[package]] name = "wcwidth" @@ -945,9 +665,6 @@ category = "dev" optional = false python-versions = "*" -[package.dependencies] -"backports.functools-lru-cache" = {version = ">=1.2.1", markers = "python_version < \"3.2\""} - [[package]] name = "webencodings" version = "0.5.1" @@ -958,23 +675,20 @@ python-versions = "*" [[package]] name = "zipp" -version = "1.2.0" +version = "3.4.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false -python-versions = ">=2.7" - -[package.dependencies] -contextlib2 = {version = "*", markers = "python_version < \"3.4\""} +python-versions = ">=3.6" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pathlib2", "unittest2", "jaraco.itertools", "func-timeout"] +testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] lock-version = "1.1" -python-versions = "~2.7 || ^3.5" -content-hash = "1e774c9d8b7f6812d721cff08b51554f9a0cd051e2ae0e884421bcb56718d131" +python-versions = "^3.6" +content-hash = "077bd512e57f2e31d9f8b72b9a8b3a918f6844afdd3c7e25c2babc7f95fdfc4e" [metadata.files] appdirs = [ @@ -986,12 +700,8 @@ atomicwrites = [ {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-20.2.0-py2.py3-none-any.whl", hash = "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"}, - {file = "attrs-20.2.0.tar.gz", hash = "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594"}, -] -"backports.functools-lru-cache" = [ - {file = "backports.functools_lru_cache-1.6.1-py2.py3-none-any.whl", hash = "sha256:0bada4c2f8a43d533e4ecb7a12214d9420e66eb206d54bf2d682581ca4b80848"}, - {file = "backports.functools_lru_cache-1.6.1.tar.gz", hash = "sha256:8fde5f188da2d593bd5bc0be98d9abc46c95bb8a9dde93429570192ee6cc2d4a"}, + {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, + {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] cachecontrol = [ {file = "CacheControl-0.12.6-py2.py3-none-any.whl", hash = "sha256:10d056fa27f8563a271b345207402a6dcce8efab7e5b377e270329c62471b10d"}, @@ -1002,8 +712,8 @@ cachy = [ {file = "cachy-0.3.0.tar.gz", hash = "sha256:186581f4ceb42a0bbe040c407da73c14092379b1e4c0e327fdb72ae4a9b269b1"}, ] certifi = [ - {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, - {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, + {file = "certifi-2020.11.8-py2.py3-none-any.whl", hash = "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd"}, + {file = "certifi-2020.11.8.tar.gz", hash = "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"}, ] cffi = [ {file = "cffi-1.14.3-2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc"}, @@ -1060,16 +770,8 @@ clikit = [ {file = "clikit-0.6.2.tar.gz", hash = "sha256:442ee5db9a14120635c5990bcdbfe7c03ada5898291f0c802f77be71569ded59"}, ] colorama = [ - {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, - {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, -] -configparser = [ - {file = "configparser-4.0.2-py2.py3-none-any.whl", hash = "sha256:254c1d9c79f60c45dfde850850883d5aaa7f19a23f13561243a050d5a7c3fe4c"}, - {file = "configparser-4.0.2.tar.gz", hash = "sha256:c7d282687a5308319bf3d2e7706e575c635b0a470342641c93bea0ea3b5331df"}, -] -contextlib2 = [ - {file = "contextlib2-0.6.0.post1-py2.py3-none-any.whl", hash = "sha256:3355078a159fbb44ee60ea80abd0d87b80b78c248643b49aa6d94673b413609b"}, - {file = "contextlib2-0.6.0.post1.tar.gz", hash = "sha256:01f490098c18b19d2bd5bb5dc445b2054d2fa97f09a4280ba2c5f3c394c8162e"}, + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ {file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"}, @@ -1112,71 +814,47 @@ crashtest = [ {file = "crashtest-0.3.1.tar.gz", hash = "sha256:42ca7b6ce88b6c7433e2ce47ea884e91ec93104a4b754998be498a8e6c3d37dd"}, ] cryptography = [ - {file = "cryptography-3.1.1-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:65beb15e7f9c16e15934569d29fb4def74ea1469d8781f6b3507ab896d6d8719"}, - {file = "cryptography-3.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:983c0c3de4cb9fcba68fd3f45ed846eb86a2a8b8d8bc5bb18364c4d00b3c61fe"}, - {file = "cryptography-3.1.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:e97a3b627e3cb63c415a16245d6cef2139cca18bb1183d1b9375a1c14e83f3b3"}, - {file = "cryptography-3.1.1-cp27-cp27m-win32.whl", hash = "sha256:cb179acdd4ae1e4a5a160d80b87841b3d0e0be84af46c7bb2cd7ece57a39c4ba"}, - {file = "cryptography-3.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:b372026ebf32fe2523159f27d9f0e9f485092e43b00a5adacf732192a70ba118"}, - {file = "cryptography-3.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:680da076cad81cdf5ffcac50c477b6790be81768d30f9da9e01960c4b18a66db"}, - {file = "cryptography-3.1.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5d52c72449bb02dd45a773a203196e6d4fae34e158769c896012401f33064396"}, - {file = "cryptography-3.1.1-cp35-abi3-macosx_10_10_x86_64.whl", hash = "sha256:f0e099fc4cc697450c3dd4031791559692dd941a95254cb9aeded66a7aa8b9bc"}, - {file = "cryptography-3.1.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:a7597ffc67987b37b12e09c029bd1dc43965f75d328076ae85721b84046e9ca7"}, - {file = "cryptography-3.1.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:4549b137d8cbe3c2eadfa56c0c858b78acbeff956bd461e40000b2164d9167c6"}, - {file = "cryptography-3.1.1-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:89aceb31cd5f9fc2449fe8cf3810797ca52b65f1489002d58fe190bfb265c536"}, - {file = "cryptography-3.1.1-cp35-cp35m-win32.whl", hash = "sha256:559d622aef2a2dff98a892eef321433ba5bc55b2485220a8ca289c1ecc2bd54f"}, - {file = "cryptography-3.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:451cdf60be4dafb6a3b78802006a020e6cd709c22d240f94f7a0696240a17154"}, - {file = "cryptography-3.1.1-cp36-abi3-win32.whl", hash = "sha256:762bc5a0df03c51ee3f09c621e1cee64e3a079a2b5020de82f1613873d79ee70"}, - {file = "cryptography-3.1.1-cp36-abi3-win_amd64.whl", hash = "sha256:b12e715c10a13ca1bd27fbceed9adc8c5ff640f8e1f7ea76416352de703523c8"}, - {file = "cryptography-3.1.1-cp36-cp36m-win32.whl", hash = "sha256:21b47c59fcb1c36f1113f3709d37935368e34815ea1d7073862e92f810dc7499"}, - {file = "cryptography-3.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:48ee615a779ffa749d7d50c291761dc921d93d7cf203dca2db663b4f193f0e49"}, - {file = "cryptography-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:b2bded09c578d19e08bd2c5bb8fed7f103e089752c9cf7ca7ca7de522326e921"}, - {file = "cryptography-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f99317a0fa2e49917689b8cf977510addcfaaab769b3f899b9c481bbd76730c2"}, - {file = "cryptography-3.1.1-cp38-cp38-win32.whl", hash = "sha256:ab010e461bb6b444eaf7f8c813bb716be2d78ab786103f9608ffd37a4bd7d490"}, - {file = "cryptography-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:99d4984aabd4c7182050bca76176ce2dbc9fa9748afe583a7865c12954d714ba"}, - {file = "cryptography-3.1.1.tar.gz", hash = "sha256:9d9fc6a16357965d282dd4ab6531013935425d0dc4950df2e0cf2a1b1ac1017d"}, + {file = "cryptography-3.2.1-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7"}, + {file = "cryptography-3.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d"}, + {file = "cryptography-3.2.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33"}, + {file = "cryptography-3.2.1-cp27-cp27m-win32.whl", hash = "sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297"}, + {file = "cryptography-3.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4"}, + {file = "cryptography-3.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8"}, + {file = "cryptography-3.2.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e"}, + {file = "cryptography-3.2.1-cp35-abi3-macosx_10_10_x86_64.whl", hash = "sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b"}, + {file = "cryptography-3.2.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df"}, + {file = "cryptography-3.2.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77"}, + {file = "cryptography-3.2.1-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7"}, + {file = "cryptography-3.2.1-cp35-cp35m-win32.whl", hash = "sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b"}, + {file = "cryptography-3.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7"}, + {file = "cryptography-3.2.1-cp36-abi3-win32.whl", hash = "sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f"}, + {file = "cryptography-3.2.1-cp36-abi3-win_amd64.whl", hash = "sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538"}, + {file = "cryptography-3.2.1-cp36-cp36m-win32.whl", hash = "sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b"}, + {file = "cryptography-3.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13"}, + {file = "cryptography-3.2.1-cp37-cp37m-win32.whl", hash = "sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e"}, + {file = "cryptography-3.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb"}, + {file = "cryptography-3.2.1-cp38-cp38-win32.whl", hash = "sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b"}, + {file = "cryptography-3.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851"}, + {file = "cryptography-3.2.1.tar.gz", hash = "sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3"}, ] distlib = [ {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"}, {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"}, ] -entrypoints = [ - {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"}, - {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"}, -] -enum34 = [ - {file = "enum34-1.1.10-py2-none-any.whl", hash = "sha256:a98a201d6de3f2ab3db284e70a33b0f896fbf35f8086594e8c9e74b909058d53"}, - {file = "enum34-1.1.10-py3-none-any.whl", hash = "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328"}, - {file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"}, -] filelock = [ {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, ] -funcsigs = [ - {file = "funcsigs-1.0.2-py2.py3-none-any.whl", hash = "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca"}, - {file = "funcsigs-1.0.2.tar.gz", hash = "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"}, -] -functools32 = [ - {file = "functools32-3.2.3-2.tar.gz", hash = "sha256:f6253dfbe0538ad2e387bd8fdfd9293c925d63553f5813c4e587745416501e6d"}, - {file = "functools32-3.2.3-2.zip", hash = "sha256:89d824aa6c358c421a234d7f9ee0bd75933a67c29588ce50aaa3acdf4d403fa0"}, -] -futures = [ - {file = "futures-3.3.0-py2-none-any.whl", hash = "sha256:49b3f5b064b6e3afc3316421a3f25f66c137ae88f068abbf72830170033c5e16"}, - {file = "futures-3.3.0.tar.gz", hash = "sha256:7e033af76a5e35f58e56da7a91e687706faf4e7bdfb2cbc3f2cca6b9bcda9794"}, -] -glob2 = [ - {file = "glob2-0.6.tar.gz", hash = "sha256:f5b0a686ff21f820c4d3f0c4edd216704cea59d79d00fa337e244a2f2ff83ed6"}, -] 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-0.9.7.tar.gz", hash = "sha256:66216f26b9d2c52e81808f3e674a6fb65d4bf719721394a1a9be926177e55fbe"}, + {file = "httpretty-1.0.2.tar.gz", hash = "sha256:24a6fd2fe1c76e94801b74db8f52c0fb42718dc4a199a861b305b1a492b9d868"}, ] identify = [ - {file = "identify-1.5.5-py2.py3-none-any.whl", hash = "sha256:da683bfb7669fa749fc7731f378229e2dbf29a1d1337cbde04106f02236eb29d"}, - {file = "identify-1.5.5.tar.gz", hash = "sha256:7c22c384a2c9b32c5cc891d13f923f6b2653aa83e2d75d8f79be240d6c86c4f4"}, + {file = "identify-1.5.10-py2.py3-none-any.whl", hash = "sha256:cc86e6a9a390879dcc2976cef169dd9cc48843ed70b7380f321d1b118163c60e"}, + {file = "identify-1.5.10.tar.gz", hash = "sha256:943cd299ac7f5715fcb3f684e2fc1594c1e0f22a90d15398e5888143bd4144b5"}, ] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, @@ -1187,39 +865,24 @@ importlib-metadata = [ {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"}, ] importlib-resources = [ - {file = "importlib_resources-3.0.0-py2.py3-none-any.whl", hash = "sha256:d028f66b66c0d5732dae86ba4276999855e162a749c92620a38c1d779ed138a7"}, - {file = "importlib_resources-3.0.0.tar.gz", hash = "sha256:19f745a6eca188b490b1428c8d1d4a0d2368759f32370ea8fb89cad2ab1106c3"}, -] -ipaddress = [ - {file = "ipaddress-1.0.23-py2.py3-none-any.whl", hash = "sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc"}, - {file = "ipaddress-1.0.23.tar.gz", hash = "sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2"}, + {file = "importlib_resources-3.3.0-py2.py3-none-any.whl", hash = "sha256:a3d34a8464ce1d5d7c92b0ea4e921e696d86f2aa212e684451cb1482c8d84ed5"}, + {file = "importlib_resources-3.3.0.tar.gz", hash = "sha256:7b51f0106c8ec564b1bef3d9c588bc694ce2b92125bbb6278f4f2f5b54ec3592"}, ] jeepney = [ - {file = "jeepney-0.4.3-py3-none-any.whl", hash = "sha256:d6c6b49683446d2407d2fe3acb7a368a77ff063f9182fe427da15d622adc24cf"}, - {file = "jeepney-0.4.3.tar.gz", hash = "sha256:3479b861cc2b6407de5188695fa1a8d57e5072d7059322469b62628869b8e36e"}, + {file = "jeepney-0.6.0-py3-none-any.whl", hash = "sha256:aec56c0eb1691a841795111e184e13cad504f7703b9a64f63020816afa79a8ae"}, + {file = "jeepney-0.6.0.tar.gz", hash = "sha256:7d59b6622675ca9e993a6bd38de845051d315f8b0c72cca3aef733a20b648657"}, ] keyring = [ - {file = "keyring-18.0.1-py2.py3-none-any.whl", hash = "sha256:7b29ebfcf8678c4da531b2478a912eea01e80007e5ddca9ee0c7038cb3489ec6"}, - {file = "keyring-18.0.1.tar.gz", hash = "sha256:67d6cc0132bd77922725fae9f18366bb314fd8f95ff4d323a4df41890a96a838"}, - {file = "keyring-20.0.1-py2.py3-none-any.whl", hash = "sha256:c674f032424b4bffc62abeac5523ec49cc84aed07a480c3233e0baf618efc15c"}, - {file = "keyring-20.0.1.tar.gz", hash = "sha256:963bfa7f090269d30bdc5e25589e5fd9dad2cf2a7c6f176a7f2386910e5d0d8d"}, - {file = "keyring-21.4.0-py3-none-any.whl", hash = "sha256:4e34ea2fdec90c1c43d6610b5a5fafa1b9097db1802948e90caf5763974b8f8d"}, - {file = "keyring-21.4.0.tar.gz", hash = "sha256:9aeadd006a852b78f4b4ef7c7556c2774d2432bbef8ee538a3e9089ac8b11466"}, + {file = "keyring-21.5.0-py3-none-any.whl", hash = "sha256:12de23258a95f3b13e5b167f7a641a878e91eab8ef16fafc077720a95e6115bb"}, + {file = "keyring-21.5.0.tar.gz", hash = "sha256:207bd66f2a9881c835dad653da04e196c678bf104f8252141d2d3c4f31051579"}, ] lockfile = [ {file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"}, {file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"}, ] -mock = [ - {file = "mock-3.0.5-py2.py3-none-any.whl", hash = "sha256:d157e52d4e5b938c550f39eb2fd15610db062441a9c2747d3dbfa9298211d0f8"}, - {file = "mock-3.0.5.tar.gz", hash = "sha256:83657d894c90d5681d62155c82bda9c1187827525880eda8ff5df4ec813437c3"}, -] more-itertools = [ - {file = "more-itertools-5.0.0.tar.gz", hash = "sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4"}, - {file = "more_itertools-5.0.0-py2-none-any.whl", hash = "sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc"}, - {file = "more_itertools-5.0.0-py3-none-any.whl", hash = "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9"}, - {file = "more-itertools-8.5.0.tar.gz", hash = "sha256:6f83822ae94818eae2612063a5101a7311e68ae8002005b5e05f03fd74a86a20"}, - {file = "more_itertools-8.5.0-py3-none-any.whl", hash = "sha256:9b30f12df9393f0d28af9210ff8efe48d10c94f73e5daf886f10c4b0b0b4f03c"}, + {file = "more-itertools-8.6.0.tar.gz", hash = "sha256:b3a9005928e5bed54076e6e549c792b306fddfe72b2d1d22dd63d42d5d3899cf"}, + {file = "more_itertools-8.6.0-py3-none-any.whl", hash = "sha256:8e1a2a43b2f2727425f2b5839587ae37093f19153dc26c0927d1048ff6557330"}, ] msgpack = [ {file = "msgpack-1.0.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:cec8bf10981ed70998d98431cd814db0ecf3384e6b113366e7f36af71a0fca08"}, @@ -1253,17 +916,13 @@ pastel = [ {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"}, {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, ] -pathlib2 = [ - {file = "pathlib2-2.3.5-py2.py3-none-any.whl", hash = "sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db"}, - {file = "pathlib2-2.3.5.tar.gz", hash = "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"}, -] 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.5.0.1-py2.py3-none-any.whl", hash = "sha256:a6d9e40ca61ad3ebd0b72fbadd4fba16e4c0e4df0428c041e01e06eb6ee71f32"}, - {file = "pkginfo-1.5.0.1.tar.gz", hash = "sha256:7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb"}, + {file = "pkginfo-1.6.1-py2.py3-none-any.whl", hash = "sha256:ce14d7296c673dc4c61c759a0b6c14bae34e34eb819c0017bb6ca5b7292c56e9"}, + {file = "pkginfo-1.6.1.tar.gz", hash = "sha256:a6a4ac943b496745cec21f14f021bbd869d5e9b4f6ec06918cffea5a2f4b9193"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, @@ -1274,8 +933,8 @@ poetry-core = [ {file = "poetry_core-1.0.0-py2.py3-none-any.whl", hash = "sha256:769288e0e1b88dfcceb3185728f0b7388b26d5f93d6c22d2dcae372da51d200d"}, ] pre-commit = [ - {file = "pre_commit-2.7.1-py2.py3-none-any.whl", hash = "sha256:810aef2a2ba4f31eed1941fc270e72696a1ad5590b9751839c90807d0fff6b9a"}, - {file = "pre_commit-2.7.1.tar.gz", hash = "sha256:c54fd3e574565fe128ecc5e7d2f91279772ddb03f8729645fa812fe809084a70"}, + {file = "pre_commit-2.9.0-py2.py3-none-any.whl", hash = "sha256:4aee0db4808fa48d2458cedd5b9a084ef24dda1a0fa504432a11977a4d1cfd0a"}, + {file = "pre_commit-2.9.0.tar.gz", hash = "sha256:b2d106d51c6ba6217e859d81774aae33fd825fe7de0dcf0c46e2586333d7a92e"}, ] ptyprocess = [ {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, @@ -1298,8 +957,6 @@ pyparsing = [ {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pytest = [ - {file = "pytest-4.6.11-py2.py3-none-any.whl", hash = "sha256:a00a7d79cbbdfa9d21e7d0298392a8dd4123316bfac545075e6f8f24c94d8c97"}, - {file = "pytest-4.6.11.tar.gz", hash = "sha256:50fa82392f2120cc3ec2ca0a75ee615be4c479e66669789771f1758332be4353"}, {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, ] @@ -1332,78 +989,47 @@ pyyaml = [ {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, ] requests = [ - {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, - {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, + {file = "requests-2.25.0-py2.py3-none-any.whl", hash = "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"}, + {file = "requests-2.25.0.tar.gz", hash = "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8"}, ] 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"}, ] -scandir = [ - {file = "scandir-1.10.0-cp27-cp27m-win32.whl", hash = "sha256:92c85ac42f41ffdc35b6da57ed991575bdbe69db895507af88b9f499b701c188"}, - {file = "scandir-1.10.0-cp27-cp27m-win_amd64.whl", hash = "sha256:cb925555f43060a1745d0a321cca94bcea927c50114b623d73179189a4e100ac"}, - {file = "scandir-1.10.0-cp34-cp34m-win32.whl", hash = "sha256:2c712840c2e2ee8dfaf36034080108d30060d759c7b73a01a52251cc8989f11f"}, - {file = "scandir-1.10.0-cp34-cp34m-win_amd64.whl", hash = "sha256:2586c94e907d99617887daed6c1d102b5ca28f1085f90446554abf1faf73123e"}, - {file = "scandir-1.10.0-cp35-cp35m-win32.whl", hash = "sha256:2b8e3888b11abb2217a32af0766bc06b65cc4a928d8727828ee68af5a967fa6f"}, - {file = "scandir-1.10.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8c5922863e44ffc00c5c693190648daa6d15e7c1207ed02d6f46a8dcc2869d32"}, - {file = "scandir-1.10.0-cp36-cp36m-win32.whl", hash = "sha256:2ae41f43797ca0c11591c0c35f2f5875fa99f8797cb1a1fd440497ec0ae4b022"}, - {file = "scandir-1.10.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7d2d7a06a252764061a020407b997dd036f7bd6a175a5ba2b345f0a357f0b3f4"}, - {file = "scandir-1.10.0-cp37-cp37m-win32.whl", hash = "sha256:67f15b6f83e6507fdc6fca22fedf6ef8b334b399ca27c6b568cbfaa82a364173"}, - {file = "scandir-1.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b24086f2375c4a094a6b51e78b4cf7ca16c721dcee2eddd7aa6494b42d6d519d"}, - {file = "scandir-1.10.0.tar.gz", hash = "sha256:4d4631f6062e658e9007ab3149a9b914f3548cb38bfb021c64f39a025ce578ae"}, -] secretstorage = [ - {file = "SecretStorage-2.3.1.tar.gz", hash = "sha256:3af65c87765323e6f64c83575b05393f9e003431959c9395d1791d51497f29b6"}, - {file = "SecretStorage-3.1.2-py3-none-any.whl", hash = "sha256:b5ec909dde94d4ae2fa26af7c089036997030f0cf0a5cb372b4cccabd81c143b"}, - {file = "SecretStorage-3.1.2.tar.gz", hash = "sha256:15da8a989b65498e29be338b3b279965f1b8f09b9668bd8010da183024c8bff6"}, + {file = "SecretStorage-3.2.0-py3-none-any.whl", hash = "sha256:ed5279d788af258e4676fa26b6efb6d335a31f1f9f529b6f1e200f388fac33e1"}, + {file = "SecretStorage-3.2.0.tar.gz", hash = "sha256:46305c3847ee3f7252b284e0eee5590fa6341c891104a2fd2313f8798c615a82"}, ] shellingham = [ {file = "shellingham-1.3.2-py2.py3-none-any.whl", hash = "sha256:7f6206ae169dc1a03af8a138681b3f962ae61cc93ade84d0585cca3aaf770044"}, {file = "shellingham-1.3.2.tar.gz", hash = "sha256:576c1982bea0ba82fb46c36feb951319d7f42214a82634233f58b40d858a751e"}, ] -singledispatch = [ - {file = "singledispatch-3.4.0.3-py2.py3-none-any.whl", hash = "sha256:833b46966687b3de7f438c761ac475213e53b306740f1abfaa86e1d1aae56aa8"}, - {file = "singledispatch-3.4.0.3.tar.gz", hash = "sha256:5b06af87df13818d14f08a028e42f566640aef80805c3b50c5056b086e3c2b9c"}, -] six = [ {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, ] -subprocess32 = [ - {file = "subprocess32-3.5.4-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:88e37c1aac5388df41cc8a8456bb49ebffd321a3ad4d70358e3518176de3a56b"}, - {file = "subprocess32-3.5.4.tar.gz", hash = "sha256:eb2937c80497978d181efa1b839ec2d9622cf9600a039a79d0e108d1f9aec79d"}, -] termcolor = [ {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"}, ] toml = [ - {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, - {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomlkit = [ {file = "tomlkit-0.7.0-py2.py3-none-any.whl", hash = "sha256:6babbd33b17d5c9691896b0e68159215a9387ebfa938aa3ac42f4a4beeb2b831"}, {file = "tomlkit-0.7.0.tar.gz", hash = "sha256:ac57f29693fab3e309ea789252fcce3061e19110085aa31af5446ca749325618"}, ] tox = [ - {file = "tox-3.20.0-py2.py3-none-any.whl", hash = "sha256:e6318f404aff16522ff5211c88cab82b39af121735a443674e4e2e65f4e4637b"}, - {file = "tox-3.20.0.tar.gz", hash = "sha256:eb629ddc60e8542fd4a1956b2462e3b8771d49f1ff630cecceacaa0fbfb7605a"}, -] -typing = [ - {file = "typing-3.7.4.3-py2-none-any.whl", hash = "sha256:283d868f5071ab9ad873e5e52268d611e851c870a2ba354193026f2dfb29d8b5"}, - {file = "typing-3.7.4.3.tar.gz", hash = "sha256:1187fb9c82fd670d10aa07bbb6cfcfe4bdda42d6fab8d5134f04e8c4d0b71cc9"}, -] -typing-extensions = [ - {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, - {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, - {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, + {file = "tox-3.20.1-py2.py3-none-any.whl", hash = "sha256:42ce19ce5dc2f6d6b1fdc5666c476e1f1e2897359b47e0aa3a5b774f335d57c2"}, + {file = "tox-3.20.1.tar.gz", hash = "sha256:4321052bfe28f9d85082341ca8e233e3ea901fdd14dab8a5d3fbd810269fbaf6"}, ] urllib3 = [ {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"}, {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, ] virtualenv = [ - {file = "virtualenv-20.0.31-py2.py3-none-any.whl", hash = "sha256:e0305af10299a7fb0d69393d8f04cb2965dda9351140d11ac8db4e5e3970451b"}, - {file = "virtualenv-20.0.31.tar.gz", hash = "sha256:43add625c53c596d38f971a465553f6318decc39d98512bc100fa1b1e839c8dc"}, + {file = "virtualenv-20.2.1-py2.py3-none-any.whl", hash = "sha256:07cff122e9d343140366055f31be4dcd61fd598c69d11cd33a9d9c8df4546dd7"}, + {file = "virtualenv-20.2.1.tar.gz", hash = "sha256:e0aac7525e880a429764cefd3aaaff54afb5d9f25c82627563603f5d7de5a6e5"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -1414,6 +1040,6 @@ webencodings = [ {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, ] zipp = [ - {file = "zipp-1.2.0-py2.py3-none-any.whl", hash = "sha256:e0d9e63797e483a30d27e09fffd308c59a700d365ec34e93cc100844168bf921"}, - {file = "zipp-1.2.0.tar.gz", hash = "sha256:c70410551488251b0fee67b460fb9a536af8d6f9f008ad10ac51f615b6a521b1"}, + {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, + {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, ] diff --git a/poetry/config/config.py b/poetry/config/config.py index 666f2e74ce2..6be9457cffe 100644 --- a/poetry/config/config.py +++ b/poetry/config/config.py @@ -4,14 +4,13 @@ import re from copy import deepcopy +from pathlib import Path from typing import Any from typing import Callable from typing import Dict from typing import Optional from poetry.locations import CACHE_DIR -from poetry.utils._compat import Path -from poetry.utils._compat import basestring from .config_source import ConfigSource from .dict_config_source import DictConfigSource @@ -131,7 +130,7 @@ def get(self, setting_name, default=None): # type: (str, Any) -> Any return self.process(value) def process(self, value): # type: (Any) -> Any - if not isinstance(value, basestring): + if not isinstance(value, str): return value return re.sub(r"{(.+?)}", lambda m: self.get(m.group(1)), value) diff --git a/poetry/console/application.py b/poetry/console/application.py index 8fb32480f49..027fec85776 100644 --- a/poetry/console/application.py +++ b/poetry/console/application.py @@ -60,8 +60,9 @@ def __init__(self): @property def poetry(self): + from pathlib import Path + from poetry.factory import Factory - from poetry.utils._compat import Path if self._poetry is not None: return self._poetry diff --git a/poetry/console/commands/check.py b/poetry/console/commands/check.py index bb97da14640..72c7ca4d947 100644 --- a/poetry/console/commands/check.py +++ b/poetry/console/commands/check.py @@ -1,6 +1,7 @@ +from pathlib import Path + from poetry.core.pyproject.toml import PyProjectTOML from poetry.factory import Factory -from poetry.utils._compat import Path from .command import Command diff --git a/poetry/console/commands/config.py b/poetry/console/commands/config.py index 2e84429ec36..310524d4ee2 100644 --- a/poetry/console/commands/config.py +++ b/poetry/console/commands/config.py @@ -41,10 +41,11 @@ class ConfigCommand(Command): @property def unique_config_values(self): + from pathlib import Path + from poetry.config.config import boolean_normalizer from poetry.config.config import boolean_validator from poetry.locations import CACHE_DIR - from poetry.utils._compat import Path unique_config_values = { "cache-dir": ( @@ -75,10 +76,10 @@ def unique_config_values(self): return unique_config_values def handle(self): + from pathlib import Path + from poetry.config.file_config_source import FileConfigSource from poetry.locations import CONFIG_DIR - from poetry.utils._compat import Path - from poetry.utils._compat import basestring config = Factory.create_config(self.io) config_file = TOMLFile(Path(CONFIG_DIR) / "config.toml") @@ -134,7 +135,7 @@ def handle(self): value = config.get(setting_key) - if not isinstance(value, basestring): + if not isinstance(value, str): value = json.dumps(value) self.line(value) @@ -267,8 +268,6 @@ def _handle_single_value(self, source, key, callbacks, values): return 0 def _list_configuration(self, config, raw, k=""): - from poetry.utils._compat import basestring - orig_k = k for key, value in sorted(config.items()): if k + key in self.LIST_PROHIBITED_SETTINGS: @@ -293,7 +292,7 @@ def _list_configuration(self, config, raw, k=""): message = "{} = {}".format( k + key, json.dumps(raw_val) ) - elif isinstance(raw_val, basestring) and raw_val != value: + elif isinstance(raw_val, str) and raw_val != value: message = "{} = {} # {}".format( k + key, json.dumps(raw_val), value ) diff --git a/poetry/console/commands/init.py b/poetry/console/commands/init.py index af72318c31a..ef56484abd1 100644 --- a/poetry/console/commands/init.py +++ b/poetry/console/commands/init.py @@ -4,7 +4,9 @@ import os import re import sys +import urllib.parse +from pathlib import Path from typing import Dict from typing import List from typing import Tuple @@ -15,9 +17,6 @@ from poetry.core.pyproject import PyProjectException from poetry.core.pyproject.toml import PyProjectTOML -from poetry.utils._compat import OrderedDict -from poetry.utils._compat import Path -from poetry.utils._compat import urlparse from .command import Command from .env_command import EnvCommand @@ -63,9 +62,10 @@ def __init__(self): self._pool = None def handle(self): + from pathlib import Path + from poetry.core.vcs.git import GitConfig from poetry.layouts import layout - from poetry.utils._compat import Path from poetry.utils.env import SystemEnv pyproject = PyProjectTOML(Path.cwd() / "pyproject.toml") @@ -390,7 +390,7 @@ def _parse_requirements( extras = [e.strip() for e in extras_m.group(1).split(",")] requirement, _ = requirement.split("[") - url_parsed = urlparse.urlparse(requirement) + url_parsed = urllib.parse.urlparse(requirement) if url_parsed.scheme and url_parsed.netloc: # Url if url_parsed.scheme in ["git+https", "git+ssh"]: @@ -400,7 +400,7 @@ def _parse_requirements( parsed = ParsedUrl.parse(requirement) url = Git.normalize_url(requirement) - pair = OrderedDict([("name", parsed.name), ("git", url.url)]) + pair = dict([("name", parsed.name), ("git", url.url)]) if parsed.rev: pair["rev"] = url.revision @@ -417,9 +417,7 @@ def _parse_requirements( elif url_parsed.scheme in ["http", "https"]: package = Provider.get_package_from_url(requirement) - pair = OrderedDict( - [("name", package.name), ("url", package.source_url)] - ) + pair = dict([("name", package.name), ("url", package.source_url)]) if extras: pair["extras"] = extras @@ -435,7 +433,7 @@ def _parse_requirements( package = Provider.get_package_from_directory(path) result.append( - OrderedDict( + dict( [ ("name", package.name), ("path", path.relative_to(cwd).as_posix()), @@ -451,7 +449,7 @@ def _parse_requirements( ) pair = pair.strip() - require = OrderedDict() + require = dict() if " " in pair: name, version = pair.split(" ", 2) extras_m = re.search(r"\[([\w\d,-_]+)\]$", name) diff --git a/poetry/console/commands/new.py b/poetry/console/commands/new.py index 481b0577bcc..4856ff69c96 100644 --- a/poetry/console/commands/new.py +++ b/poetry/console/commands/new.py @@ -20,10 +20,11 @@ class NewCommand(Command): ] def handle(self): + from pathlib import Path + from poetry.core.semver import parse_constraint from poetry.core.vcs.git import GitConfig from poetry.layouts import layout - from poetry.utils._compat import Path from poetry.utils.env import SystemEnv if self.option("src"): diff --git a/poetry/console/commands/publish.py b/poetry/console/commands/publish.py index 557cd1d7ab7..98d4165fd8d 100644 --- a/poetry/console/commands/publish.py +++ b/poetry/console/commands/publish.py @@ -1,6 +1,6 @@ -from cleo import option +from pathlib import Path -from poetry.utils._compat import Path +from cleo import option from .command import Command diff --git a/poetry/console/commands/self/update.py b/poetry/console/commands/self/update.py index f98da89632f..7d477ddce16 100644 --- a/poetry/console/commands/self/update.py +++ b/poetry/console/commands/self/update.py @@ -62,7 +62,7 @@ class SelfUpdateCommand(Command): @property def home(self): - from poetry.utils._compat import Path + from pathlib import Path return Path(os.environ.get("POETRY_HOME", "~/.poetry")).expanduser() @@ -239,7 +239,7 @@ def process(self, *args): return subprocess.check_output(list(args), stderr=subprocess.STDOUT) def _check_recommended_installation(self): - from poetry.utils._compat import Path + from pathlib import Path current = Path(__file__) try: diff --git a/poetry/console/config/application_config.py b/poetry/console/config/application_config.py index 09cc2cb1b44..492a2137259 100644 --- a/poetry/console/config/application_config.py +++ b/poetry/console/config/application_config.py @@ -30,7 +30,7 @@ from poetry.console.commands.installer_command import InstallerCommand from poetry.console.logging.io_formatter import IOFormatter from poetry.console.logging.io_handler import IOHandler -from poetry.utils._compat import PY36 +from poetry.mixology.solutions.providers import PythonRequirementSolutionProvider class ApplicationConfig(BaseApplicationConfig): @@ -55,14 +55,9 @@ def configure(self): self.add_event_listener(PRE_HANDLE, self.set_env) self.add_event_listener(PRE_HANDLE, self.set_installer) - if PY36: - from poetry.mixology.solutions.providers import ( - PythonRequirementSolutionProvider, - ) - - self._solution_provider_repository.register_solution_providers( - [PythonRequirementSolutionProvider] - ) + self._solution_provider_repository.register_solution_providers( + [PythonRequirementSolutionProvider] + ) def register_command_loggers( self, event, event_name, _ diff --git a/poetry/factory.py b/poetry/factory.py index 08a6faca170..b38f6da25ae 100644 --- a/poetry/factory.py +++ b/poetry/factory.py @@ -1,6 +1,7 @@ from __future__ import absolute_import from __future__ import unicode_literals +from pathlib import Path from typing import Dict from typing import Optional @@ -16,7 +17,6 @@ from .packages.locker import Locker from .poetry import Poetry from .repositories.pypi_repository import PyPiRepository -from .utils._compat import Path class Factory(BaseFactory): diff --git a/poetry/inspection/info.py b/poetry/inspection/info.py index 251dac95f91..94ac097aeac 100644 --- a/poetry/inspection/info.py +++ b/poetry/inspection/info.py @@ -4,6 +4,7 @@ import tarfile import zipfile +from pathlib import Path from typing import Dict from typing import Iterator from typing import List @@ -17,8 +18,6 @@ from poetry.core.packages import ProjectPackage from poetry.core.packages import dependency_from_pep_508 from poetry.core.pyproject.toml import PyProjectTOML -from poetry.core.utils._compat import PY35 -from poetry.core.utils._compat import Path from poetry.core.utils.helpers import parse_requires from poetry.core.utils.helpers import temporary_directory from poetry.core.version.markers import InvalidMarker @@ -364,13 +363,10 @@ def _find_dist_info(path): # type: (Path) -> Iterator[Path] :param path: Path to search. """ pattern = "**/*.*-info" - if PY35: - # Sometimes pathlib will fail on recursive symbolic links, so we need to workaround it - # and use the glob module instead. Note that this does not happen with pathlib2 - # so it's safe to use it for Python < 3.4. - directories = glob.iglob(path.joinpath(pattern).as_posix(), recursive=True) - else: - directories = path.glob(pattern) + # Sometimes pathlib will fail on recursive symbolic links, so we need to workaround it + # and use the glob module instead. Note that this does not happen with pathlib2 + # so it's safe to use it for Python < 3.4. + directories = glob.iglob(path.joinpath(pattern).as_posix(), recursive=True) for d in directories: yield Path(d) diff --git a/poetry/installation/authenticator.py b/poetry/installation/authenticator.py index 69adb844809..1e7a72e751a 100644 --- a/poetry/installation/authenticator.py +++ b/poetry/installation/authenticator.py @@ -1,5 +1,6 @@ import logging import time +import urllib.parse from typing import TYPE_CHECKING @@ -8,7 +9,6 @@ import requests.exceptions from poetry.exceptions import PoetryException -from poetry.utils._compat import urlparse from poetry.utils.password_manager import PasswordManager @@ -107,7 +107,7 @@ def request( def get_credentials_for_url( self, url ): # type: (str) -> Tuple[Optional[str], Optional[str]] - parsed_url = urlparse.urlsplit(url) + parsed_url = urllib.parse.urlsplit(url) netloc = parsed_url.netloc @@ -130,7 +130,7 @@ def get_credentials_for_url( credentials = auth, None credentials = tuple( - None if x is None else urlparse.unquote(x) for x in credentials + None if x is None else urllib.parse.unquote(x) for x in credentials ) if credentials[0] is not None or credentials[1] is not None: @@ -156,7 +156,7 @@ def _get_credentials_for_netloc_from_config( if not url: continue - parsed_url = urlparse.urlsplit(url) + parsed_url = urllib.parse.urlsplit(url) if netloc == parsed_url.netloc: auth = self._password_manager.get_http_auth(repository_name) diff --git a/poetry/installation/chef.py b/poetry/installation/chef.py index 669ce17768e..6009ac0a98d 100644 --- a/poetry/installation/chef.py +++ b/poetry/installation/chef.py @@ -1,10 +1,10 @@ import hashlib import json +from pathlib import Path from typing import TYPE_CHECKING from poetry.core.packages.utils.link import Link -from poetry.utils._compat import Path from .chooser import InvalidWheelName from .chooser import Wheel diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index a65dbb4a0e1..5523ed2813f 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -7,17 +7,13 @@ from concurrent.futures import ThreadPoolExecutor from concurrent.futures import wait +from pathlib import Path from subprocess import CalledProcessError from poetry.core.packages.file_dependency import FileDependency from poetry.core.packages.utils.link import Link from poetry.core.pyproject.toml import PyProjectTOML from poetry.io.null_io import NullIO -from poetry.utils._compat import PY2 -from poetry.utils._compat import WINDOWS -from poetry.utils._compat import OrderedDict -from poetry.utils._compat import Path -from poetry.utils._compat import cpu_count from poetry.utils._compat import decode from poetry.utils.env import EnvCommandError from poetry.utils.helpers import safe_rmtree @@ -45,13 +41,13 @@ def __init__(self, env, pool, config, io, parallel=None): if parallel is None: parallel = config.get("installer.parallel", True) - if parallel and not (PY2 and WINDOWS): + if parallel: # This should be directly handled by ThreadPoolExecutor # however, on some systems the number of CPUs cannot be determined # (it raises a NotImplementedError), so, in this case, we assume # that the system only has one CPU. try: - self._max_workers = cpu_count() + 4 + self._max_workers = os.cpu_count() + 4 except NotImplementedError: self._max_workers = 5 else: @@ -62,7 +58,7 @@ def __init__(self, env, pool, config, io, parallel=None): self._executed_operations = 0 self._executed = {"install": 0, "update": 0, "uninstall": 0} self._skipped = {"install": 0, "update": 0, "uninstall": 0} - self._sections = OrderedDict() + self._sections = dict() self._lock = threading.Lock() self._shutdown = False @@ -107,7 +103,7 @@ def execute(self, operations): # type: (Operation) -> int # We group operations by priority groups = itertools.groupby(operations, key=lambda o: -o.priority) - self._sections = OrderedDict() + self._sections = dict() for _, group in groups: tasks = [] serial_operations = [] diff --git a/poetry/installation/pip_installer.py b/poetry/installation/pip_installer.py index b8fb97314be..df1249737a1 100644 --- a/poetry/installation/pip_installer.py +++ b/poetry/installation/pip_installer.py @@ -1,5 +1,6 @@ import os import tempfile +import urllib.parse from subprocess import CalledProcessError @@ -14,12 +15,6 @@ from .base_installer import BaseInstaller -try: - import urllib.parse as urlparse -except ImportError: - import urlparse - - class PipInstaller(BaseInstaller): def __init__(self, env, io, pool): # type: (Env, IO, Pool) -> None self._env = env @@ -44,7 +39,7 @@ def install(self, package, update=False): and package.source_url ): repository = self._pool.repository(package.source_reference) - parsed = urlparse.urlparse(package.source_url) + parsed = urllib.parse.urlparse(package.source_url) if parsed.scheme == "http": self._io.error( " Installing from unsecure host: {}".format( diff --git a/poetry/locations.py b/poetry/locations.py index 003950d500d..5bd4b7feb17 100644 --- a/poetry/locations.py +++ b/poetry/locations.py @@ -1,4 +1,5 @@ -from .utils._compat import Path +from pathlib import Path + from .utils.appdirs import user_cache_dir from .utils.appdirs import user_config_dir diff --git a/poetry/masonry/builders/editable.py b/poetry/masonry/builders/editable.py index 74d1f69c886..dc4be9af4cc 100644 --- a/poetry/masonry/builders/editable.py +++ b/poetry/masonry/builders/editable.py @@ -5,13 +5,13 @@ import shutil from base64 import urlsafe_b64encode +from pathlib import Path from poetry.core.masonry.builders.builder import Builder from poetry.core.masonry.builders.sdist import SdistBuilder from poetry.core.masonry.utils.package_include import PackageInclude from poetry.core.semver.version import Version from poetry.utils._compat import WINDOWS -from poetry.utils._compat import Path from poetry.utils._compat import decode from poetry.utils.helpers import is_dir_writable diff --git a/poetry/mixology/partial_solution.py b/poetry/mixology/partial_solution.py index df17f7184b2..55230425ce8 100644 --- a/poetry/mixology/partial_solution.py +++ b/poetry/mixology/partial_solution.py @@ -1,4 +1,3 @@ -from collections import OrderedDict from typing import Dict from typing import List @@ -26,13 +25,13 @@ def __init__(self): self._assignments = [] # type: List[Assignment] # The decisions made for each package. - self._decisions = OrderedDict() # type: Dict[str, Package] + self._decisions = dict() # type: Dict[str, Package] # The intersection of all positive Assignments for each package, minus any # negative Assignments that refer to that package. # # This is derived from self._assignments. - self._positive = OrderedDict() # type: Dict[str, Term] + self._positive = dict() # type: Dict[str, Term] # The union of all negative Assignments for each package. # @@ -40,7 +39,7 @@ def __init__(self): # map. # # This is derived from self._assignments. - self._negative = OrderedDict() # type: Dict[str, Dict[str, Term]] + self._negative = dict() # type: Dict[str, Dict[str, Term]] # The number of distinct solutions that have been attempted so far. self._attempted_solutions = 1 diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index f1637407068..8cb6d91f916 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -5,6 +5,7 @@ from copy import deepcopy from hashlib import sha256 +from pathlib import Path from typing import Dict from typing import Iterable from typing import Iterator @@ -33,8 +34,6 @@ from poetry.core.version.markers import parse_marker from poetry.core.version.requirements import InvalidRequirement from poetry.packages import DependencyPackage -from poetry.utils._compat import OrderedDict -from poetry.utils._compat import Path from poetry.utils.extras import get_extra_package_names @@ -412,7 +411,7 @@ def set_lock_data(self, root, packages): # type: (...) -> bool for extra, deps in sorted(root.extras.items()) } - lock["metadata"] = OrderedDict( + lock["metadata"] = dict( [ ("lock-version", self._VERSION), ("python-versions", root.python_versions), @@ -525,7 +524,7 @@ def _dump_package(self, package): # type: (Package) -> dict constraint["version"] for constraint in constraints ] - data = OrderedDict( + data = dict( [ ("name", package.pretty_name), ("version", package.pretty_version), @@ -569,7 +568,7 @@ def _dump_package(self, package): # type: (Package) -> dict ) ).as_posix() - data["source"] = OrderedDict() + data["source"] = dict() if package.source_type: data["source"]["type"] = package.source_type diff --git a/poetry/poetry.py b/poetry/poetry.py index 4878f0a22aa..325c6074df3 100644 --- a/poetry/poetry.py +++ b/poetry/poetry.py @@ -1,6 +1,8 @@ from __future__ import absolute_import from __future__ import unicode_literals +from pathlib import Path + from poetry.core.packages import ProjectPackage from poetry.core.poetry import Poetry as BasePoetry @@ -8,7 +10,6 @@ from .config.config import Config from .packages import Locker from .repositories.pool import Pool -from .utils._compat import Path class Poetry(BasePoetry): diff --git a/poetry/publishing/publisher.py b/poetry/publishing/publisher.py index 67515f77f64..5cec7eca2fe 100644 --- a/poetry/publishing/publisher.py +++ b/poetry/publishing/publisher.py @@ -1,8 +1,8 @@ import logging +from pathlib import Path from typing import Optional -from poetry.utils._compat import Path from poetry.utils.helpers import get_cert from poetry.utils.helpers import get_client_cert from poetry.utils.password_manager import PasswordManager diff --git a/poetry/publishing/uploader.py b/poetry/publishing/uploader.py index 7c0783d1cac..bb1673e3bff 100644 --- a/poetry/publishing/uploader.py +++ b/poetry/publishing/uploader.py @@ -1,6 +1,7 @@ import hashlib import io +from pathlib import Path from typing import Any from typing import Dict from typing import List @@ -21,7 +22,6 @@ from poetry.core.masonry.metadata import Metadata from poetry.core.masonry.utils.helpers import escape_name from poetry.core.masonry.utils.helpers import escape_version -from poetry.utils._compat import Path from poetry.utils.helpers import normalize_version from poetry.utils.patterns import wheel_file_re diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index 333dfc60cf3..e9719a924d9 100644 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -1,8 +1,10 @@ import logging import os import re +import urllib.parse from contextlib import contextmanager +from pathlib import Path from tempfile import mkdtemp from typing import Any from typing import List @@ -30,9 +32,6 @@ from poetry.packages.package_collection import PackageCollection from poetry.puzzle.exceptions import OverrideNeeded from poetry.repositories import Pool -from poetry.utils._compat import OrderedDict -from poetry.utils._compat import Path -from poetry.utils._compat import urlparse from poetry.utils.env import Env from poetry.utils.helpers import download_file from poetry.utils.helpers import safe_rmtree @@ -324,7 +323,7 @@ def search_for_url(self, dependency): # type: (URLDependency) -> List[Package] def get_package_from_url(cls, url): # type: (str) -> Package with temporary_directory() as temp_dir: temp_dir = Path(temp_dir) - file_name = os.path.basename(urlparse.urlparse(url).path) + file_name = os.path.basename(urllib.parse.urlparse(url).path) download_file(url, str(temp_dir / file_name)) package = cls.get_package_from_file(temp_dir / file_name) @@ -517,7 +516,7 @@ def complete_package( # An example of this is: # - pypiwin32 (220); sys_platform == "win32" and python_version >= "3.6" # - pypiwin32 (219); sys_platform == "win32" and python_version < "3.6" - duplicates = OrderedDict() + duplicates = dict() for dep in dependencies: if dep.name not in duplicates: duplicates[dep.name] = [] @@ -533,7 +532,7 @@ def complete_package( self.debug("Duplicate dependencies for {}".format(dep_name)) # Regrouping by constraint - by_constraint = OrderedDict() + by_constraint = dict() for dep in deps: if dep.constraint not in by_constraint: by_constraint[dep.constraint] = [] diff --git a/poetry/repositories/installed_repository.py b/poetry/repositories/installed_repository.py index 1320fdd6698..29424dbaeb5 100644 --- a/poetry/repositories/installed_repository.py +++ b/poetry/repositories/installed_repository.py @@ -1,11 +1,11 @@ import itertools +from pathlib import Path from typing import Set from typing import Union from poetry.core.packages import Package from poetry.core.utils.helpers import module_name -from poetry.utils._compat import Path from poetry.utils._compat import metadata from poetry.utils.env import Env diff --git a/poetry/repositories/legacy_repository.py b/poetry/repositories/legacy_repository.py index f9963ddc4d4..e059c3bb8d4 100644 --- a/poetry/repositories/legacy_repository.py +++ b/poetry/repositories/legacy_repository.py @@ -1,8 +1,10 @@ import cgi import re +import urllib.parse import warnings from collections import defaultdict +from pathlib import Path from typing import Generator from typing import Optional from typing import Union @@ -21,7 +23,6 @@ from poetry.core.semver import VersionRange from poetry.core.semver import parse_constraint from poetry.locations import REPOSITORY_CACHE_DIR -from poetry.utils._compat import Path from poetry.utils.helpers import canonicalize_name from poetry.utils.patterns import wheel_file_re @@ -33,11 +34,6 @@ from .pypi_repository import PyPiRepository -try: - import urllib.parse as urlparse -except ImportError: - import urlparse - try: from html import unescape except ImportError: @@ -115,7 +111,7 @@ def links(self): # type: () -> Generator[Link] for anchor in self._parsed.findall(".//a"): if anchor.get("href"): href = anchor.get("href") - url = self.clean_link(urlparse.urljoin(self._url, href)) + url = self.clean_link(urllib.parse.urljoin(self._url, href)) pyrequire = anchor.get("data-requires-python") pyrequire = unescape(pyrequire) if pyrequire else None @@ -219,7 +215,7 @@ def authenticated_url(self): # type: () -> str if not self._session.auth: return self.url - parsed = urlparse.urlparse(self.url) + parsed = urllib.parse.urlparse(self.url) return "{scheme}://{username}:{password}@{netloc}{path}".format( scheme=parsed.scheme, diff --git a/poetry/repositories/pypi_repository.py b/poetry/repositories/pypi_repository.py index 16105992a1c..715ba557379 100644 --- a/poetry/repositories/pypi_repository.py +++ b/poetry/repositories/pypi_repository.py @@ -1,7 +1,9 @@ import logging import os +import urllib.parse from collections import defaultdict +from pathlib import Path from typing import Dict from typing import List from typing import Union @@ -24,7 +26,6 @@ from poetry.core.semver.exceptions import ParseVersionError from poetry.core.version.markers import parse_marker from poetry.locations import REPOSITORY_CACHE_DIR -from poetry.utils._compat import Path from poetry.utils._compat import to_str from poetry.utils.helpers import download_file from poetry.utils.helpers import temporary_directory @@ -35,12 +36,6 @@ from .remote_repository import RemoteRepository -try: - import urllib.parse as urlparse -except ImportError: - import urlparse - - cache_control_logger.setLevel(logging.ERROR) logger = logging.getLogger(__name__) @@ -424,11 +419,13 @@ def _get_info_from_urls(self, urls): # type: (Dict[str, List[str]]) -> PackageI def _get_info_from_wheel(self, url): # type: (str) -> PackageInfo self._log( - "Downloading wheel: {}".format(urlparse.urlparse(url).path.rsplit("/")[-1]), + "Downloading wheel: {}".format( + urllib.parse.urlparse(url).path.rsplit("/")[-1] + ), level="debug", ) - filename = os.path.basename(urlparse.urlparse(url).path.rsplit("/")[-1]) + filename = os.path.basename(urllib.parse.urlparse(url).path.rsplit("/")[-1]) with temporary_directory() as temp_dir: filepath = Path(temp_dir) / filename @@ -438,11 +435,13 @@ def _get_info_from_wheel(self, url): # type: (str) -> PackageInfo def _get_info_from_sdist(self, url): # type: (str) -> PackageInfo self._log( - "Downloading sdist: {}".format(urlparse.urlparse(url).path.rsplit("/")[-1]), + "Downloading sdist: {}".format( + urllib.parse.urlparse(url).path.rsplit("/")[-1] + ), level="debug", ) - filename = os.path.basename(urlparse.urlparse(url).path) + filename = os.path.basename(urllib.parse.urlparse(url).path) with temporary_directory() as temp_dir: filepath = Path(temp_dir) / filename diff --git a/poetry/utils/_compat.py b/poetry/utils/_compat.py index 937f9b300e6..1ede84dcd3f 100644 --- a/poetry/utils/_compat.py +++ b/poetry/utils/_compat.py @@ -2,236 +2,16 @@ try: - from functools32 import lru_cache -except ImportError: - from functools import lru_cache - -try: - from glob2 import glob -except ImportError: - from glob import glob - -try: - import zipfile as zipp - from importlib import metadata except ImportError: - import importlib_metadata as metadata - import zipp - -try: - import urllib.parse as urlparse -except ImportError: - import urlparse - -try: - from os import cpu_count -except ImportError: # Python 2 - from multiprocessing import cpu_count - -try: # Python 2 - long = long - unicode = unicode - basestring = basestring -except NameError: # Python 3 - long = int - unicode = str - basestring = str - - -PY2 = sys.version_info[0] == 2 -PY34 = sys.version_info >= (3, 4) -PY35 = sys.version_info >= (3, 5) -PY36 = sys.version_info >= (3, 6) + # compatibility for python <3.8 + import importlib_metadata as metadata # noqa WINDOWS = sys.platform == "win32" -try: - from shlex import quote -except ImportError: - # PY2 - from pipes import quote # noqa - -if PY34: - from importlib.machinery import EXTENSION_SUFFIXES -else: - from imp import get_suffixes - - EXTENSION_SUFFIXES = [suffix[0] for suffix in get_suffixes()] - - -if PY35: - from pathlib import Path -else: - from pathlib2 import Path - -if not PY36: - from collections import OrderedDict -else: - OrderedDict = dict - - -if PY35: - import subprocess as subprocess - - from subprocess import CalledProcessError -else: - import subprocess32 as subprocess - - from subprocess32 import CalledProcessError - - -if PY34: - # subprocess32 pass the calls directly to subprocess - # on Python 3.3+ but Python 3.4 does not provide run() - # so we backport it - import signal - - from subprocess import PIPE - from subprocess import Popen - from subprocess import SubprocessError - from subprocess import TimeoutExpired - - class CalledProcessError(SubprocessError): - """Raised when run() is called with check=True and the process - returns a non-zero exit status. - - Attributes: - cmd, returncode, stdout, stderr, output - """ - - def __init__(self, returncode, cmd, output=None, stderr=None): - self.returncode = returncode - self.cmd = cmd - self.output = output - self.stderr = stderr - - def __str__(self): - if self.returncode and self.returncode < 0: - try: - return "Command '%s' died with %r." % ( - self.cmd, - signal.Signals(-self.returncode), - ) - except ValueError: - return "Command '%s' died with unknown signal %d." % ( - self.cmd, - -self.returncode, - ) - else: - return "Command '%s' returned non-zero exit status %d." % ( - self.cmd, - self.returncode, - ) - - @property - def stdout(self): - """Alias for output attribute, to match stderr""" - return self.output - - @stdout.setter - def stdout(self, value): - # There's no obvious reason to set this, but allow it anyway so - # .stdout is a transparent alias for .output - self.output = value - - class CompletedProcess(object): - """A process that has finished running. - This is returned by run(). - Attributes: - args: The list or str args passed to run(). - returncode: The exit code of the process, negative for signals. - stdout: The standard output (None if not captured). - stderr: The standard error (None if not captured). - """ - - def __init__(self, args, returncode, stdout=None, stderr=None): - self.args = args - self.returncode = returncode - self.stdout = stdout - self.stderr = stderr - - def __repr__(self): - args = [ - "args={!r}".format(self.args), - "returncode={!r}".format(self.returncode), - ] - if self.stdout is not None: - args.append("stdout={!r}".format(self.stdout)) - if self.stderr is not None: - args.append("stderr={!r}".format(self.stderr)) - return "{}({})".format(type(self).__name__, ", ".join(args)) - - def check_returncode(self): - """Raise CalledProcessError if the exit code is non-zero.""" - if self.returncode: - raise CalledProcessError( - self.returncode, self.args, self.stdout, self.stderr - ) - - def run(*popenargs, **kwargs): - """Run command with arguments and return a CompletedProcess instance. - The returned instance will have attributes args, returncode, stdout and - stderr. By default, stdout and stderr are not captured, and those attributes - will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them. - If check is True and the exit code was non-zero, it raises a - CalledProcessError. The CalledProcessError object will have the return code - in the returncode attribute, and output & stderr attributes if those streams - were captured. - If timeout is given, and the process takes too long, a TimeoutExpired - exception will be raised. - There is an optional argument "input", allowing you to - pass a string to the subprocess's stdin. If you use this argument - you may not also use the Popen constructor's "stdin" argument, as - it will be used internally. - The other arguments are the same as for the Popen constructor. - If universal_newlines=True is passed, the "input" argument must be a - string and stdout/stderr in the returned object will be strings rather than - bytes. - """ - input = kwargs.pop("input", None) - timeout = kwargs.pop("timeout", None) - check = kwargs.pop("check", False) - if input is not None: - if "stdin" in kwargs: - raise ValueError("stdin and input arguments may not both be used.") - kwargs["stdin"] = PIPE - - process = Popen(*popenargs, **kwargs) - try: - process.__enter__() # No-Op really... illustrate "with in 2.4" - try: - stdout, stderr = process.communicate(input, timeout=timeout) - except TimeoutExpired: - process.kill() - stdout, stderr = process.communicate() - raise TimeoutExpired( - process.args, timeout, output=stdout, stderr=stderr - ) - except: - process.kill() - process.wait() - raise - retcode = process.poll() - if check and retcode: - raise CalledProcessError( - retcode, process.args, output=stdout, stderr=stderr - ) - finally: - # None because our context manager __exit__ does not use them. - process.__exit__(None, None, None) - - return CompletedProcess(process.args, retcode, stdout, stderr) - - subprocess.run = run - subprocess.CalledProcessError = CalledProcessError - def decode(string, encodings=None): - if not PY2 and not isinstance(string, bytes): - return string - - if PY2 and isinstance(string, unicode): + if not isinstance(string, bytes): return string encodings = encodings or ["utf-8", "latin1", "ascii"] @@ -246,10 +26,7 @@ def decode(string, encodings=None): def encode(string, encodings=None): - if not PY2 and isinstance(string, bytes): - return string - - if PY2 and isinstance(string, str): + if isinstance(string, bytes): return string encodings = encodings or ["utf-8", "latin1", "ascii"] @@ -264,23 +41,7 @@ def encode(string, encodings=None): def to_str(string): - if isinstance(string, str) or not isinstance(string, (unicode, bytes)): - return string - - if PY2: - method = "encode" - else: - method = "decode" - - encodings = ["utf-8", "latin1", "ascii"] - - for encoding in encodings: - try: - return getattr(string, method)(encoding) - except (UnicodeEncodeError, UnicodeDecodeError): - pass - - return getattr(string, method)(encodings[0], errors="ignore") + return decode(string) def list_to_shell_command(cmd): diff --git a/poetry/utils/env.py b/poetry/utils/env.py index c247bf014f7..22db246911e 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -5,11 +5,14 @@ import platform import re import shutil +import subprocess import sys import sysconfig import textwrap from contextlib import contextmanager +from pathlib import Path +from subprocess import CalledProcessError from typing import Any from typing import Dict from typing import List @@ -33,12 +36,9 @@ from poetry.core.version.markers import BaseMarker from poetry.locations import CACHE_DIR from poetry.poetry import Poetry -from poetry.utils._compat import CalledProcessError -from poetry.utils._compat import Path 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 subprocess from poetry.utils.helpers import is_dir_writable from poetry.utils.helpers import paths_csv diff --git a/poetry/utils/exporter.py b/poetry/utils/exporter.py index f7c40f6975f..f86fdcf19ee 100644 --- a/poetry/utils/exporter.py +++ b/poetry/utils/exporter.py @@ -1,3 +1,6 @@ +import urllib.parse + +from pathlib import Path from typing import Optional from typing import Sequence from typing import Union @@ -5,9 +8,7 @@ from clikit.api.io import IO from poetry.poetry import Poetry -from poetry.utils._compat import Path from poetry.utils._compat import decode -from poetry.utils._compat import urlparse class Exporter(object): @@ -140,7 +141,7 @@ def _export_requirements_txt( url = ( repository.authenticated_url if with_credentials else repository.url ) - parsed_url = urlparse.urlsplit(url) + parsed_url = urllib.parse.urlsplit(url) if parsed_url.scheme == "http": indexes_header += "--trusted-host {}\n".format(parsed_url.netloc) indexes_header += "--extra-index-url {}\n".format(url) diff --git a/poetry/utils/helpers.py b/poetry/utils/helpers.py index 232e65b7d44..909ec3c1438 100644 --- a/poetry/utils/helpers.py +++ b/poetry/utils/helpers.py @@ -5,6 +5,7 @@ import tempfile from contextlib import contextmanager +from pathlib import Path from typing import List from typing import Optional @@ -13,7 +14,6 @@ from poetry.config.config import Config from poetry.core.packages.package import Package from poetry.core.version import Version -from poetry.utils._compat import Path try: diff --git a/poetry/utils/setup_reader.py b/poetry/utils/setup_reader.py index 3a1ce2f170d..498210f45f5 100644 --- a/poetry/utils/setup_reader.py +++ b/poetry/utils/setup_reader.py @@ -1,5 +1,7 @@ import ast +from configparser import ConfigParser +from pathlib import Path from typing import Any from typing import Dict from typing import Iterable @@ -10,16 +12,6 @@ from poetry.core.semver import Version -from ._compat import PY35 -from ._compat import Path -from ._compat import basestring - - -try: - from configparser import ConfigParser -except ImportError: - from ConfigParser import ConfigParser - class SetupReader(object): """ @@ -39,8 +31,8 @@ class SetupReader(object): @classmethod def read_from_directory( cls, directory - ): # type: (Union[basestring, Path]) -> Dict[str, Union[List, Dict]] - if isinstance(directory, basestring): + ): # type: (Union[str, Path]) -> Dict[str, Union[List, Dict]] + if isinstance(directory, str): directory = Path(directory) result = cls.DEFAULT.copy() @@ -61,11 +53,8 @@ def read_from_directory( def read_setup_py( self, filepath - ): # type: (Union[basestring, Path]) -> Dict[str, Union[List, Dict]] - if not PY35: - return self.DEFAULT - - if isinstance(filepath, basestring): + ): # type: (Union[str, Path]) -> Dict[str, Union[List, Dict]] + if isinstance(filepath, str): filepath = Path(filepath) with filepath.open(encoding="utf-8") as f: @@ -92,7 +81,7 @@ def read_setup_py( def read_setup_cfg( self, filepath - ): # type: (Union[basestring, Path]) -> Dict[str, Union[List, Dict]] + ): # type: (Union[str, Path]) -> Dict[str, Union[List, Dict]] parser = ConfigParser() parser.read(str(filepath)) diff --git a/poetry/utils/shell.py b/poetry/utils/shell.py index 2b2fe91f946..3c0e5fbe92c 100644 --- a/poetry/utils/shell.py +++ b/poetry/utils/shell.py @@ -2,6 +2,8 @@ import signal import sys +from pathlib import Path + import pexpect from clikit.utils.terminal import Terminal @@ -9,7 +11,6 @@ from shellingham import detect_shell from ._compat import WINDOWS -from ._compat import Path from .env import VirtualEnv diff --git a/pyproject.toml b/pyproject.toml index 59c49f74cf7..412ce84c639 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,12 +22,12 @@ classifiers = [ # Requirements [tool.poetry.dependencies] -python = "~2.7 || ^3.5" +python = "^3.6" poetry-core = "^1.0.0" cleo = "^0.8.1" clikit = "^0.6.2" -crashtest = { version = "^0.3.0", python = "^3.6" } +crashtest = "^0.3.0" requests = "^2.18" cachy = "^0.3.0" requests-toolbelt = "^0.9.1" @@ -39,38 +39,20 @@ tomlkit = ">=0.7.0,<1.0.0" pexpect = "^4.7.0" packaging = "^20.4" virtualenv = { version = "^20.0.26" } - -# The typing module is not in the stdlib in Python 2.7 -typing = { version = "^3.6", python = "~2.7" } - -# Use pathlib2 for Python 2.7 -pathlib2 = { version = "^2.3", python = "~2.7" } -# Use futures on Python 2.7 -futures = { version = "^3.3.0", python = "~2.7" } -# Use glob2 for Python 2.7 and 3.4 -glob2 = { version = "^0.6", python = "~2.7" } -# functools32 is needed for Python 2.7 -functools32 = { version = "^3.2.3", python = "~2.7" } -keyring = [ - { version = "^18.0.1", python = "~2.7" }, - { version = "^20.0.1", python = "~3.5" }, - { version = "^21.2.0", python = "^3.6" } -] -# Use subprocess32 for Python 2.7 -subprocess32 = { version = "^3.5", python = "~2.7" } +keyring = "^21.2.0" importlib-metadata = {version = "^1.6.0", python = "<3.8"} [tool.poetry.dev-dependencies] -pytest = [ - {version = "^4.1", python = "<3.5"}, - {version = "^5.4.3", python = ">=3.5"} -] +pytest = "^5.4.3" pytest-cov = "^2.5" pytest-mock = "^1.9" pre-commit = { version = "^2.6", python = "^3.6.1" } tox = "^3.0" pytest-sugar = "^0.9.2" -httpretty = "^0.9.6" +httpretty = "^1.0" +zipp = { version = "^3.4", python = "<3.8"} +# temporary workaround for https://github.com/python-poetry/poetry/issues/3404 +urllib3 = "1.25.10" [tool.poetry.scripts] poetry = "poetry.console:main" diff --git a/tests/compat.py b/tests/compat.py new file mode 100644 index 00000000000..13c5bb7da98 --- /dev/null +++ b/tests/compat.py @@ -0,0 +1,4 @@ +try: + import zipp +except ImportError: + import zipfile as zipp # noqa diff --git a/tests/conftest.py b/tests/conftest.py index 51128f764bb..178a4632b90 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,6 +4,7 @@ import sys import tempfile +from pathlib import Path from typing import Any from typing import Dict @@ -21,7 +22,6 @@ from poetry.layouts import layout from poetry.repositories import Pool from poetry.repositories import Repository -from poetry.utils._compat import Path from poetry.utils.env import EnvManager from poetry.utils.env import SystemEnv from poetry.utils.env import VirtualEnv @@ -184,7 +184,7 @@ def mocked_open(self, *args, **kwargs): return mocker.MagicMock() return original(self, *args, **kwargs) - mocker.patch("poetry.utils._compat.Path.open", mocked_open) + mocker.patch("pathlib.Path.open", mocked_open) yield files diff --git a/tests/console/commands/env/conftest.py b/tests/console/commands/env/conftest.py index 5fbddf1aa6f..22b12f0d44b 100644 --- a/tests/console/commands/env/conftest.py +++ b/tests/console/commands/env/conftest.py @@ -1,8 +1,9 @@ import os +from pathlib import Path + import pytest -from poetry.utils._compat import Path from poetry.utils.env import EnvManager diff --git a/tests/console/commands/env/helpers.py b/tests/console/commands/env/helpers.py index 1c7e64dc17e..0c65e4f14fb 100644 --- a/tests/console/commands/env/helpers.py +++ b/tests/console/commands/env/helpers.py @@ -1,8 +1,8 @@ +from pathlib import Path from typing import Optional from typing import Union from poetry.core.semver import Version -from poetry.utils._compat import Path def build_venv( diff --git a/tests/console/commands/env/test_info.py b/tests/console/commands/env/test_info.py index 9d1a0c8426a..123835ba7b9 100644 --- a/tests/console/commands/env/test_info.py +++ b/tests/console/commands/env/test_info.py @@ -1,6 +1,7 @@ +from pathlib import Path + import pytest -from poetry.utils._compat import Path from poetry.utils.env import MockEnv diff --git a/tests/console/commands/env/test_remove.py b/tests/console/commands/env/test_remove.py index 2b4f3ae775c..b95419bb6fb 100644 --- a/tests/console/commands/env/test_remove.py +++ b/tests/console/commands/env/test_remove.py @@ -13,7 +13,7 @@ def test_remove_by_python_version( mocker, tester, venvs_in_cache_dirs, venv_name, venv_cache ): check_output = mocker.patch( - "poetry.utils._compat.subprocess.check_output", + "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) diff --git a/tests/console/commands/env/test_use.py b/tests/console/commands/env/test_use.py index b3ea3458d06..4ec106b434e 100644 --- a/tests/console/commands/env/test_use.py +++ b/tests/console/commands/env/test_use.py @@ -1,11 +1,12 @@ import os +from pathlib import Path + import pytest import tomlkit from poetry.core.semver import Version from poetry.core.toml.file import TOMLFile -from poetry.utils._compat import Path 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 @@ -21,11 +22,11 @@ def setup(mocker): @pytest.fixture(autouse=True) def mock_subprocess_calls(setup, current_python, mocker): mocker.patch( - "poetry.utils._compat.subprocess.check_output", + "subprocess.check_output", side_effect=check_output_wrapper(Version(*current_python)), ) mocker.patch( - "poetry.utils._compat.subprocess.Popen.communicate", + "subprocess.Popen.communicate", side_effect=[("/prefix", None), ("/prefix", None), ("/prefix", None)], ) @@ -39,8 +40,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( mocker, tester, venv_cache, venv_name, venvs_in_cache_config ): mocker.patch( - "poetry.utils._compat.subprocess.check_output", - side_effect=check_output_wrapper(), + "subprocess.check_output", side_effect=check_output_wrapper(), ) mock_build_env = mocker.patch( diff --git a/tests/console/commands/self/test_update.py b/tests/console/commands/self/test_update.py index 6e094111ad0..1598f47865a 100644 --- a/tests/console/commands/self/test_update.py +++ b/tests/console/commands/self/test_update.py @@ -1,12 +1,13 @@ import os +from pathlib import Path + import pytest from poetry.__version__ import __version__ from poetry.core.packages.package import Package from poetry.core.semver.version import Version from poetry.utils._compat import WINDOWS -from poetry.utils._compat import Path FIXTURES = Path(__file__).parent.joinpath("fixtures") diff --git a/tests/console/commands/test_add.py b/tests/console/commands/test_add.py index 19998592ac3..a0811c1c965 100644 --- a/tests/console/commands/test_add.py +++ b/tests/console/commands/test_add.py @@ -3,11 +3,12 @@ import sys +from pathlib import Path + import pytest from poetry.core.semver import Version from poetry.repositories.legacy_repository import LegacyRepository -from poetry.utils._compat import Path from tests.helpers import get_dependency from tests.helpers import get_package @@ -279,7 +280,7 @@ def test_add_git_ssh_constraint(app, repo, tester, tmp_venv): def test_add_directory_constraint(app, repo, tester, mocker): - p = mocker.patch("poetry.utils._compat.Path.cwd") + p = mocker.patch("pathlib.Path.cwd") p.return_value = Path(__file__).parent repo.add_package(get_package("pendulum", "1.4.4")) @@ -313,7 +314,7 @@ def test_add_directory_constraint(app, repo, tester, mocker): def test_add_directory_with_poetry(app, repo, tester, mocker): - p = mocker.patch("poetry.utils._compat.Path.cwd") + p = mocker.patch("pathlib.Path.cwd") p.return_value = Path(__file__) / ".." repo.add_package(get_package("pendulum", "1.4.4")) @@ -341,7 +342,7 @@ def test_add_directory_with_poetry(app, repo, tester, mocker): def test_add_file_constraint_wheel(app, repo, tester, mocker, poetry): - p = mocker.patch("poetry.utils._compat.Path.cwd") + p = mocker.patch("pathlib.Path.cwd") p.return_value = poetry.file.parent repo.add_package(get_package("pendulum", "1.4.4")) @@ -376,7 +377,7 @@ def test_add_file_constraint_wheel(app, repo, tester, mocker, poetry): def test_add_file_constraint_sdist(app, repo, tester, mocker): - p = mocker.patch("poetry.utils._compat.Path.cwd") + p = mocker.patch("pathlib.Path.cwd") p.return_value = Path(__file__) / ".." repo.add_package(get_package("pendulum", "1.4.4")) @@ -448,7 +449,7 @@ def test_add_constraint_with_extras_option(app, repo, tester): def test_add_url_constraint_wheel(app, repo, tester, mocker): - p = mocker.patch("poetry.utils._compat.Path.cwd") + p = mocker.patch("pathlib.Path.cwd") p.return_value = Path(__file__) / ".." repo.add_package(get_package("pendulum", "1.4.4")) @@ -1069,7 +1070,7 @@ def test_add_git_ssh_constraint_old_installer(app, repo, installer, old_tester): def test_add_directory_constraint_old_installer( app, repo, installer, mocker, old_tester ): - p = mocker.patch("poetry.utils._compat.Path.cwd") + p = mocker.patch("pathlib.Path.cwd") p.return_value = Path(__file__) / ".." repo.add_package(get_package("pendulum", "1.4.4")) @@ -1106,7 +1107,7 @@ def test_add_directory_constraint_old_installer( def test_add_directory_with_poetry_old_installer( app, repo, installer, mocker, old_tester ): - p = mocker.patch("poetry.utils._compat.Path.cwd") + p = mocker.patch("pathlib.Path.cwd") p.return_value = Path(__file__) / ".." repo.add_package(get_package("pendulum", "1.4.4")) @@ -1137,7 +1138,7 @@ def test_add_directory_with_poetry_old_installer( def test_add_file_constraint_wheel_old_installer( app, repo, installer, mocker, old_tester ): - p = mocker.patch("poetry.utils._compat.Path.cwd") + p = mocker.patch("pathlib.Path.cwd") p.return_value = Path(__file__) / ".." repo.add_package(get_package("pendulum", "1.4.4")) @@ -1175,7 +1176,7 @@ def test_add_file_constraint_wheel_old_installer( def test_add_file_constraint_sdist_old_installer( app, repo, installer, mocker, old_tester ): - p = mocker.patch("poetry.utils._compat.Path.cwd") + p = mocker.patch("pathlib.Path.cwd") p.return_value = Path(__file__) / ".." repo.add_package(get_package("pendulum", "1.4.4")) @@ -1253,7 +1254,7 @@ def test_add_constraint_with_extras_option_old_installer( def test_add_url_constraint_wheel_old_installer( app, repo, installer, mocker, old_tester ): - p = mocker.patch("poetry.utils._compat.Path.cwd") + p = mocker.patch("pathlib.Path.cwd") p.return_value = Path(__file__) / ".." repo.add_package(get_package("pendulum", "1.4.4")) diff --git a/tests/console/commands/test_cache.py b/tests/console/commands/test_cache.py index a8d47842d1e..f33fa9ad31a 100644 --- a/tests/console/commands/test_cache.py +++ b/tests/console/commands/test_cache.py @@ -5,9 +5,9 @@ @pytest.fixture def repository_cache_dir(monkeypatch, tmpdir): - import poetry.locations + from pathlib import Path - from poetry.utils._compat import Path + import poetry.locations path = Path(str(tmpdir)) monkeypatch.setattr(poetry.locations, "REPOSITORY_CACHE_DIR", path) diff --git a/tests/console/commands/test_check.py b/tests/console/commands/test_check.py index caa5485154f..90225bd6857 100644 --- a/tests/console/commands/test_check.py +++ b/tests/console/commands/test_check.py @@ -1,7 +1,6 @@ -import pytest +from pathlib import Path -from poetry.utils._compat import PY2 -from poetry.utils._compat import Path +import pytest @pytest.fixture() @@ -30,14 +29,7 @@ def test_check_invalid(mocker, tester): tester.execute() - if PY2: - expected = """\ -Error: u'description' is a required property -Warning: A wildcard Python dependency is ambiguous. Consider specifying a more explicit one. -Warning: The "pendulum" dependency specifies the "allows-prereleases" property, which is deprecated. Use "allow-prereleases" instead. -""" - else: - expected = """\ + expected = """\ Error: 'description' is a required property Warning: A wildcard Python dependency is ambiguous. Consider specifying a more explicit one. Warning: The "pendulum" dependency specifies the "allows-prereleases" property, which is deprecated. Use "allow-prereleases" instead. diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index 58bc8662bf5..ad20da54d39 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -6,8 +6,6 @@ from poetry.config.config_source import ConfigSource from poetry.core.pyproject import PyProjectException from poetry.factory import Factory -from poetry.utils._compat import PY2 -from poetry.utils._compat import WINDOWS @pytest.fixture() @@ -136,15 +134,13 @@ def test_set_cert(tester, auth_config_source, mocker): def test_config_installer_parallel(tester, command_tester_factory): - serial_enforced = PY2 and WINDOWS - tester.execute("--local installer.parallel") assert tester.io.fetch_output().strip() == "true" workers = command_tester_factory( "install" )._command._installer._executor._max_workers - assert workers > 1 or (serial_enforced and workers == 1) + assert workers > 1 tester.io.clear_output() tester.execute("--local installer.parallel false") diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py index 7a9212ab262..812cc23ab96 100644 --- a/tests/console/commands/test_init.py +++ b/tests/console/commands/test_init.py @@ -2,12 +2,13 @@ import shutil import sys +from pathlib import Path + import pytest from cleo import CommandTester from poetry.repositories import Pool -from poetry.utils._compat import Path from poetry.utils._compat import decode from tests.helpers import TestApplication from tests.helpers import get_package @@ -26,7 +27,7 @@ def source_dir(tmp_path): # type: (...) -> Path @pytest.fixture def patches(mocker, source_dir, repo): - mocker.patch("poetry.utils._compat.Path.cwd", return_value=source_dir) + mocker.patch("pathlib.Path.cwd", return_value=source_dir) mocker.patch( "poetry.console.commands.init.InitCommand._get_pool", return_value=Pool([repo]) ) diff --git a/tests/console/commands/test_lock.py b/tests/console/commands/test_lock.py index c05ba257882..51e56d155ca 100644 --- a/tests/console/commands/test_lock.py +++ b/tests/console/commands/test_lock.py @@ -1,7 +1,8 @@ +from pathlib import Path + import pytest from poetry.packages import Locker -from poetry.utils._compat import Path from tests.helpers import get_package diff --git a/tests/console/commands/test_publish.py b/tests/console/commands/test_publish.py index dccc43ee87d..bcbf4c50006 100644 --- a/tests/console/commands/test_publish.py +++ b/tests/console/commands/test_publish.py @@ -1,14 +1,10 @@ -import pytest +from pathlib import Path + import requests from poetry.publishing.uploader import UploadError -from poetry.utils._compat import PY36 -from poetry.utils._compat import Path -@pytest.mark.skipif( - not PY36, reason="Improved error rendering is only available on Python >=3.6" -) def test_publish_returns_non_zero_code_for_upload_errors(app, app_tester, http): http.register_uri( http.POST, "https://upload.pypi.org/legacy/", status=400, body="Bad Request" @@ -47,32 +43,6 @@ def request_callback(*_, **__): assert expected in app_tester.io.fetch_output() -@pytest.mark.skipif( - PY36, reason="Improved error rendering is not available on Python <3.6" -) -def test_publish_returns_non_zero_code_for_upload_errors_older_python( - app, app_tester, http -): - http.register_uri( - http.POST, "https://upload.pypi.org/legacy/", status=400, body="Bad Request" - ) - - exit_code = app_tester.execute("publish --username foo --password bar") - - assert 1 == exit_code - - expected = """ -Publishing simple-project (1.2.3) to PyPI - - -UploadError - -HTTP Error 400: Bad Request -""" - - assert app_tester.io.fetch_output() == expected - - def test_publish_with_cert(app_tester, mocker): publisher_publish = mocker.patch("poetry.publishing.Publisher.publish") diff --git a/tests/console/commands/test_search.py b/tests/console/commands/test_search.py index 9b61476c0b5..18e094ff1ae 100644 --- a/tests/console/commands/test_search.py +++ b/tests/console/commands/test_search.py @@ -1,6 +1,6 @@ -import pytest +from pathlib import Path -from poetry.utils._compat import Path +import pytest TESTS_DIRECTORY = Path(__file__).parent.parent.parent diff --git a/tests/console/conftest.py b/tests/console/conftest.py index 6d7ef0fa675..a9b06f6c3b2 100644 --- a/tests/console/conftest.py +++ b/tests/console/conftest.py @@ -1,5 +1,7 @@ import os +from pathlib import Path + import pytest from cleo import ApplicationTester @@ -8,7 +10,6 @@ from poetry.installation.noop_installer import NoopInstaller from poetry.io.null_io import NullIO from poetry.repositories import Pool -from poetry.utils._compat import Path from poetry.utils.env import MockEnv from tests.helpers import TestApplication from tests.helpers import TestExecutor diff --git a/tests/helpers.py b/tests/helpers.py index f380bb8de16..8fc2a381c0f 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,5 +1,8 @@ import os import shutil +import urllib.parse + +from pathlib import Path from poetry.console import Application from poetry.core.masonry.utils.helpers import escape_name @@ -14,10 +17,7 @@ from poetry.packages import Locker from poetry.repositories import Repository from poetry.repositories.exceptions import PackageNotFound -from poetry.utils._compat import PY2 from poetry.utils._compat import WINDOWS -from poetry.utils._compat import Path -from poetry.utils._compat import urlparse FIXTURE_PATH = Path(__file__).parent / "fixtures" @@ -59,19 +59,13 @@ def copy_or_symlink(source, dest): # os.symlink requires either administrative privileges or developer mode on Win10, # throwing an OSError if neither is active. if WINDOWS: - if PY2: + try: + os.symlink(str(source), str(dest), target_is_directory=source.is_dir()) + except OSError: if source.is_dir(): shutil.copytree(str(source), str(dest)) else: shutil.copyfile(str(source), str(dest)) - else: - try: - os.symlink(str(source), str(dest), target_is_directory=source.is_dir()) - except OSError: - if source.is_dir(): - shutil.copytree(str(source), str(dest)) - else: - shutil.copyfile(str(source), str(dest)) else: os.symlink(str(source), str(dest)) @@ -92,7 +86,7 @@ def mock_clone(_, source, dest): def mock_download(url, dest, **__): - parts = urlparse.urlparse(url) + parts = urllib.parse.urlparse(url) fixtures = Path(__file__).parent / "fixtures" fixture = fixtures / parts.path.lstrip("/") diff --git a/tests/inspection/test_info.py b/tests/inspection/test_info.py index a128469a23f..e04bd52a5c3 100644 --- a/tests/inspection/test_info.py +++ b/tests/inspection/test_info.py @@ -1,12 +1,11 @@ +from pathlib import Path +from subprocess import CalledProcessError from typing import Set import pytest from poetry.inspection.info import PackageInfo from poetry.inspection.info import PackageInfoError -from poetry.utils._compat import PY35 -from poetry.utils._compat import CalledProcessError -from poetry.utils._compat import Path from poetry.utils._compat import decode from poetry.utils.env import EnvCommandError from poetry.utils.env import VirtualEnv @@ -134,13 +133,11 @@ def test_info_from_requires_txt(): demo_check_info(info) -@pytest.mark.skipif(not PY35, reason="Parsing of setup.py is skipped for Python < 3.5") def test_info_from_setup_py(demo_setup): info = PackageInfo.from_setup_files(demo_setup) demo_check_info(info, requires_dist={"package"}) -@pytest.mark.skipif(not PY35, reason="Parsing of setup.cfg is skipped for Python < 3.5") def test_info_from_setup_cfg(demo_setup_cfg): info = PackageInfo.from_setup_files(demo_setup_cfg) demo_check_info(info, requires_dist={"package"}) @@ -155,7 +152,6 @@ def test_info_no_setup_pkg_info_no_deps(): assert info.requires_dist is None -@pytest.mark.skipif(not PY35, reason="Parsing of setup.py is skipped for Python < 3.5") def test_info_setup_simple(mocker, demo_setup): spy = mocker.spy(VirtualEnv, "run") info = PackageInfo.from_directory(demo_setup) @@ -163,18 +159,6 @@ def test_info_setup_simple(mocker, demo_setup): demo_check_info(info, requires_dist={"package"}) -@pytest.mark.skipif( - PY35, - reason="For projects with setup.py using Python < 3.5 fallback to pep517 build", -) -def test_info_setup_simple_py2(mocker, demo_setup): - spy = mocker.spy(VirtualEnv, "run") - info = PackageInfo.from_directory(demo_setup) - assert spy.call_count == 2 - demo_check_info(info, requires_dist={"package"}) - - -@pytest.mark.skipif(not PY35, reason="Parsing of setup.cfg is skipped for Python < 3.5") def test_info_setup_cfg(mocker, demo_setup_cfg): spy = mocker.spy(VirtualEnv, "run") info = PackageInfo.from_directory(demo_setup_cfg) @@ -203,7 +187,6 @@ def test_info_setup_complex_pep517_legacy(demo_setup_complex_pep517_legacy): demo_check_info(info, requires_dist={"package"}) -@pytest.mark.skipif(not PY35, reason="Parsing of setup.py is skipped for Python < 3.5") def test_info_setup_complex_disable_build(mocker, demo_setup_complex): spy = mocker.spy(VirtualEnv, "run") info = PackageInfo.from_directory(demo_setup_complex, disable_build=True) @@ -213,7 +196,6 @@ def test_info_setup_complex_disable_build(mocker, demo_setup_complex): assert info.requires_dist is None -@pytest.mark.skipif(not PY35, reason="Parsing of setup.py is skipped for Python < 3.5") @pytest.mark.parametrize("missing", ["version", "name", "install_requires"]) def test_info_setup_missing_mandatory_should_trigger_pep517( mocker, source_dir, missing diff --git a/tests/installation/test_chef.py b/tests/installation/test_chef.py index 4e59b608a24..d25f276748d 100644 --- a/tests/installation/test_chef.py +++ b/tests/installation/test_chef.py @@ -1,8 +1,9 @@ +from pathlib import Path + from packaging.tags import Tag from poetry.core.packages.utils.link import Link from poetry.installation.chef import Chef -from poetry.utils._compat import Path from poetry.utils.env import MockEnv diff --git a/tests/installation/test_chooser.py b/tests/installation/test_chooser.py index cf3f931b942..75fd52c7926 100644 --- a/tests/installation/test_chooser.py +++ b/tests/installation/test_chooser.py @@ -1,5 +1,7 @@ import re +from pathlib import Path + import pytest from packaging.tags import Tag @@ -9,7 +11,6 @@ from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.pool import Pool from poetry.repositories.pypi_repository import PyPiRepository -from poetry.utils._compat import Path from poetry.utils.env import MockEnv diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 53ad3677350..ddf6faa2d66 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -4,6 +4,8 @@ import re import shutil +from pathlib import Path + import pytest from clikit.api.formatter.style import Style @@ -16,8 +18,6 @@ from poetry.installation.operations import Uninstall from poetry.installation.operations import Update from poetry.repositories.pool import Pool -from poetry.utils._compat import PY36 -from poetry.utils._compat import Path from poetry.utils.env import MockEnv from tests.repositories.test_pypi_repository import MockRepository @@ -155,9 +155,6 @@ def test_execute_shows_skipped_operations_if_verbose( assert 0 == len(env.executed) -@pytest.mark.skipif( - not PY36, reason="Improved error rendering is only available on Python >=3.6" -) def test_execute_should_show_errors(config, mocker, io, env): executor = Executor(env, pool, config, io) executor.verbose() diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 106efde6e9c..2adcb13cd25 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -3,6 +3,8 @@ import json import sys +from pathlib import Path + import pytest from clikit.io import NullIO @@ -17,8 +19,6 @@ from poetry.repositories import Pool from poetry.repositories import Repository from poetry.repositories.installed_repository import InstalledRepository -from poetry.utils._compat import PY2 -from poetry.utils._compat import Path from poetry.utils.env import MockEnv from poetry.utils.env import NullEnv from tests.helpers import get_dependency @@ -112,15 +112,6 @@ def _get_content_hash(self): def _write_lock_data(self, data): for package in data["package"]: python_versions = str(package["python-versions"]) - if PY2: - python_versions = python_versions.decode() - if "requirements" in package: - requirements = {} - for key, value in package["requirements"].items(): - requirements[key.decode()] = value.decode() - - package["requirements"] = requirements - package["python-versions"] = python_versions self._written_data = json.loads(json.dumps(data)) @@ -1583,7 +1574,7 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de installer.whitelist(["pytest"]) installer.run() - assert (6 if not PY2 else 7) == installer.executor.installations_count + assert 6 == installer.executor.installations_count assert 0 == installer.executor.updates_count assert 0 == installer.executor.removals_count diff --git a/tests/installation/test_installer_old.py b/tests/installation/test_installer_old.py index b92bdce460e..97c1b163636 100644 --- a/tests/installation/test_installer_old.py +++ b/tests/installation/test_installer_old.py @@ -2,6 +2,8 @@ import sys +from pathlib import Path + import pytest from clikit.io import NullIO @@ -15,8 +17,6 @@ from poetry.repositories import Pool from poetry.repositories import Repository from poetry.repositories.installed_repository import InstalledRepository -from poetry.utils._compat import PY2 -from poetry.utils._compat import Path from poetry.utils.env import MockEnv from poetry.utils.env import NullEnv from tests.helpers import get_dependency @@ -74,15 +74,6 @@ def _get_content_hash(self): def _write_lock_data(self, data): for package in data["package"]: python_versions = str(package["python-versions"]) - if PY2: - python_versions = python_versions.decode() - if "requirements" in package: - requirements = {} - for key, value in package["requirements"].items(): - requirements[key.decode()] = value.decode() - - package["requirements"] = requirements - package["python-versions"] = python_versions self._written_data = data @@ -1514,7 +1505,7 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de installer.whitelist(["pytest"]) installer.run() - assert len(installer.installer.installs) == 6 if not PY2 else 7 + assert len(installer.installer.installs) == 6 assert len(installer.installer.updates) == 0 assert len(installer.installer.removals) == 0 diff --git a/tests/installation/test_pip_installer.py b/tests/installation/test_pip_installer.py index d0e2e5a4dcd..64c64409051 100644 --- a/tests/installation/test_pip_installer.py +++ b/tests/installation/test_pip_installer.py @@ -1,5 +1,7 @@ import shutil +from pathlib import Path + import pytest from poetry.core.packages.package import Package @@ -7,7 +9,6 @@ from poetry.io.null_io import NullIO from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.pool import Pool -from poetry.utils._compat import Path from poetry.utils.env import NullEnv diff --git a/tests/masonry/builders/test_editable_builder.py b/tests/masonry/builders/test_editable_builder.py index 3bf1e59c98e..5500bc49a4d 100644 --- a/tests/masonry/builders/test_editable_builder.py +++ b/tests/masonry/builders/test_editable_builder.py @@ -4,12 +4,13 @@ import os import shutil +from pathlib import Path + import pytest from poetry.factory import Factory from poetry.io.null_io import NullIO from poetry.masonry.builders.editable import EditableBuilder -from poetry.utils._compat import Path from poetry.utils.env import EnvManager from poetry.utils.env import MockEnv from poetry.utils.env import VirtualEnv diff --git a/tests/mixology/solutions/providers/test_python_requirement_solution_provider.py b/tests/mixology/solutions/providers/test_python_requirement_solution_provider.py index 81c11d215d3..14f1b3cb6e1 100644 --- a/tests/mixology/solutions/providers/test_python_requirement_solution_provider.py +++ b/tests/mixology/solutions/providers/test_python_requirement_solution_provider.py @@ -1,5 +1,3 @@ -import pytest - from poetry.core.packages.dependency import Dependency from poetry.mixology.failure import SolveFailure from poetry.mixology.incompatibility import Incompatibility @@ -7,12 +5,8 @@ from poetry.mixology.incompatibility_cause import PythonCause from poetry.mixology.term import Term from poetry.puzzle.exceptions import SolverProblemError -from poetry.utils._compat import PY36 -@pytest.mark.skipif( - not PY36, reason="Error solutions are only available for Python ^3.6" -) def test_it_can_solve_python_incompatibility_solver_errors(): from poetry.mixology.solutions.providers import PythonRequirementSolutionProvider from poetry.mixology.solutions.solutions import PythonRequirementSolution @@ -27,9 +21,6 @@ def test_it_can_solve_python_incompatibility_solver_errors(): assert isinstance(provider.get_solutions(exception)[0], PythonRequirementSolution) -@pytest.mark.skipif( - not PY36, reason="Error solutions are only available for Python ^3.6" -) def test_it_cannot_solve_other_solver_errors(): from poetry.mixology.solutions.providers import PythonRequirementSolutionProvider diff --git a/tests/mixology/solutions/solutions/test_python_requirement_solution.py b/tests/mixology/solutions/solutions/test_python_requirement_solution.py index e264ad8d04b..8ea58a4078d 100644 --- a/tests/mixology/solutions/solutions/test_python_requirement_solution.py +++ b/tests/mixology/solutions/solutions/test_python_requirement_solution.py @@ -1,5 +1,3 @@ -import pytest - from clikit.io.buffered_io import BufferedIO from poetry.core.packages.dependency import Dependency @@ -8,12 +6,8 @@ from poetry.mixology.incompatibility_cause import PythonCause from poetry.mixology.term import Term from poetry.puzzle.exceptions import SolverProblemError -from poetry.utils._compat import PY36 -@pytest.mark.skipif( - not PY36, reason="Error solutions are only available for Python ^3.6" -) def test_it_provides_the_correct_solution(): from poetry.mixology.solutions.solutions import PythonRequirementSolution diff --git a/tests/publishing/test_publisher.py b/tests/publishing/test_publisher.py index ceb9e7d4da0..786d659b0db 100644 --- a/tests/publishing/test_publisher.py +++ b/tests/publishing/test_publisher.py @@ -1,5 +1,7 @@ import os +from pathlib import Path + import pytest from cleo.io import BufferedIO @@ -7,7 +9,6 @@ from poetry.factory import Factory from poetry.io.null_io import NullIO from poetry.publishing.publisher import Publisher -from poetry.utils._compat import Path def test_publish_publishes_to_pypi_by_default(fixture_dir, mocker, config): diff --git a/tests/publishing/test_uploader.py b/tests/publishing/test_uploader.py index 0b32e77d864..ae0eb041b7b 100644 --- a/tests/publishing/test_uploader.py +++ b/tests/publishing/test_uploader.py @@ -1,10 +1,11 @@ +from pathlib import Path + import pytest from poetry.factory import Factory from poetry.io.null_io import NullIO from poetry.publishing.uploader import Uploader from poetry.publishing.uploader import UploadError -from poetry.utils._compat import Path fixtures_dir = Path(__file__).parent.parent / "fixtures" diff --git a/tests/puzzle/conftest.py b/tests/puzzle/conftest.py index e3812530bf5..5848294b765 100644 --- a/tests/puzzle/conftest.py +++ b/tests/puzzle/conftest.py @@ -1,8 +1,8 @@ import shutil -import pytest +from pathlib import Path -from poetry.utils._compat import Path +import pytest try: diff --git a/tests/puzzle/test_provider.py b/tests/puzzle/test_provider.py index ecab7f3ab2d..2d0c4557bdb 100644 --- a/tests/puzzle/test_provider.py +++ b/tests/puzzle/test_provider.py @@ -1,3 +1,4 @@ +from pathlib import Path from subprocess import CalledProcessError import pytest @@ -12,8 +13,6 @@ from poetry.puzzle.provider import Provider from poetry.repositories.pool import Pool from poetry.repositories.repository import Repository -from poetry.utils._compat import PY35 -from poetry.utils._compat import Path from poetry.utils.env import EnvCommandError from poetry.utils.env import MockEnv as BaseMockEnv from tests.helpers import get_dependency @@ -94,7 +93,6 @@ def test_search_for_vcs_setup_egg_info_with_extras(provider): } -@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4") def test_search_for_vcs_read_setup(provider, mocker): mocker.patch("poetry.utils.env.EnvManager.get", return_value=MockEnv()) @@ -115,7 +113,6 @@ def test_search_for_vcs_read_setup(provider, mocker): } -@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4") def test_search_for_vcs_read_setup_with_extras(provider, mocker): mocker.patch("poetry.utils.env.EnvManager.get", return_value=MockEnv()) @@ -241,7 +238,6 @@ def test_search_for_directory_setup_with_base(provider, directory): ) -@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4") def test_search_for_directory_setup_read_setup(provider, mocker): mocker.patch("poetry.utils.env.EnvManager.get", return_value=MockEnv()) @@ -270,7 +266,6 @@ def test_search_for_directory_setup_read_setup(provider, mocker): } -@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4") def test_search_for_directory_setup_read_setup_with_extras(provider, mocker): mocker.patch("poetry.utils.env.EnvManager.get", return_value=MockEnv()) @@ -300,7 +295,6 @@ def test_search_for_directory_setup_read_setup_with_extras(provider, mocker): } -@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4") def test_search_for_directory_setup_read_setup_with_no_dependencies(provider): dependency = DirectoryDependency( "demo", diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 1393b13f1ef..6e2a53a5c22 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -1,3 +1,5 @@ +from pathlib import Path + import pytest from clikit.io import NullIO @@ -13,7 +15,6 @@ from poetry.repositories.installed_repository import InstalledRepository from poetry.repositories.pool import Pool from poetry.repositories.repository import Repository -from poetry.utils._compat import Path from poetry.utils.env import MockEnv from tests.helpers import get_dependency from tests.helpers import get_package diff --git a/tests/repositories/test_installed_repository.py b/tests/repositories/test_installed_repository.py index 3caa702a09c..5097d3bc4a0 100644 --- a/tests/repositories/test_installed_repository.py +++ b/tests/repositories/test_installed_repository.py @@ -1,3 +1,4 @@ +from pathlib import Path from typing import Optional import pytest @@ -6,11 +7,9 @@ from poetry.core.packages import Package from poetry.repositories.installed_repository import InstalledRepository -from poetry.utils._compat import PY36 -from poetry.utils._compat import Path from poetry.utils._compat import metadata -from poetry.utils._compat import zipp from poetry.utils.env import MockEnv as BaseMockEnv +from tests.compat import zipp FIXTURES_DIR = Path(__file__).parent / "fixtures" @@ -135,9 +134,6 @@ def test_load_platlib_package(repository): assert lib64.version.text == "2.3.4" -@pytest.mark.skipif( - not PY36, reason="pathlib.resolve() does not support strict argument" -) def test_load_editable_package(repository): # test editable package with text .pth file editable = get_package_from_repository("editable", repository) diff --git a/tests/repositories/test_legacy_repository.py b/tests/repositories/test_legacy_repository.py index 47ccc1052f9..1402f4799af 100644 --- a/tests/repositories/test_legacy_repository.py +++ b/tests/repositories/test_legacy_repository.py @@ -1,5 +1,7 @@ import shutil +from pathlib import Path + import pytest from poetry.core.packages import Dependency @@ -8,8 +10,6 @@ from poetry.repositories.exceptions import RepositoryError from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.legacy_repository import Page -from poetry.utils._compat import PY35 -from poetry.utils._compat import Path try: @@ -95,16 +95,6 @@ def test_get_package_information_fallback_read_setup(): == "Jupyter metapackage. Install all the Jupyter components in one go." ) - if PY35: - assert package.requires == [ - Dependency("notebook", "*"), - Dependency("qtconsole", "*"), - Dependency("jupyter-console", "*"), - Dependency("nbconvert", "*"), - Dependency("ipykernel", "*"), - Dependency("ipywidgets", "*"), - ] - def test_get_package_information_skips_dependencies_with_invalid_constraints(): repo = MockRepository() diff --git a/tests/repositories/test_pypi_repository.py b/tests/repositories/test_pypi_repository.py index 55afdd39485..cdd8484cc34 100644 --- a/tests/repositories/test_pypi_repository.py +++ b/tests/repositories/test_pypi_repository.py @@ -2,6 +2,7 @@ import shutil from io import BytesIO +from pathlib import Path import pytest @@ -11,8 +12,6 @@ from poetry.core.packages import Dependency from poetry.factory import Factory from poetry.repositories.pypi_repository import PyPiRepository -from poetry.utils._compat import PY35 -from poetry.utils._compat import Path from poetry.utils._compat import encode @@ -136,7 +135,6 @@ def test_fallback_inspects_sdist_first_if_no_matching_wheels_can_be_found(): assert dep.python_versions == "~2.7" -@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4") def test_fallback_can_read_setup_to_get_dependencies(): repo = MockRepository(fallback=True) diff --git a/tests/test_factory.py b/tests/test_factory.py index b2c232b20e7..d213d808405 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -2,14 +2,14 @@ from __future__ import absolute_import from __future__ import unicode_literals +from pathlib import Path + import pytest from poetry.core.toml.file import TOMLFile from poetry.factory import Factory from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.pypi_repository import PyPiRepository -from poetry.utils._compat import PY2 -from poetry.utils._compat import Path fixtures_dir = Path(__file__).parent / "fixtures" @@ -196,16 +196,10 @@ def test_validate_fails(): content = complete.read()["tool"]["poetry"] content["this key is not in the schema"] = "" - if PY2: - expected = ( - "Additional properties are not allowed " - "(u'this key is not in the schema' was unexpected)" - ) - else: - expected = ( - "Additional properties are not allowed " - "('this key is not in the schema' was unexpected)" - ) + expected = ( + "Additional properties are not allowed " + "('this key is not in the schema' was unexpected)" + ) assert Factory.validate(content) == {"errors": [expected], "warnings": []} @@ -216,13 +210,7 @@ def test_create_poetry_fails_on_invalid_configuration(): Path(__file__).parent / "fixtures" / "invalid_pyproject" / "pyproject.toml" ) - if PY2: - expected = """\ -The Poetry configuration is invalid: - - u'description' is a required property -""" - else: - expected = """\ + expected = """\ The Poetry configuration is invalid: - 'description' is a required property """ diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 9c1e2c623d9..f0e2bd21a45 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -2,6 +2,7 @@ import shutil import sys +from pathlib import Path from typing import Optional from typing import Union @@ -13,8 +14,6 @@ from poetry.core.semver import Version from poetry.core.toml.file import TOMLFile from poetry.factory import Factory -from poetry.utils._compat import PY2 -from poetry.utils._compat import Path from poetry.utils.env import GET_BASE_PREFIX from poetry.utils.env import EnvCommandError from poetry.utils.env import EnvManager @@ -145,11 +144,10 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( - "poetry.utils._compat.subprocess.check_output", - side_effect=check_output_wrapper(), + "subprocess.check_output", side_effect=check_output_wrapper(), ) mocker.patch( - "poetry.utils._compat.subprocess.Popen.communicate", + "subprocess.Popen.communicate", side_effect=[("/prefix", None), ("/prefix", None)], ) m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv) @@ -186,12 +184,10 @@ def test_activate_activates_existing_virtualenv_no_envs_file( config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( - "poetry.utils._compat.subprocess.check_output", - side_effect=check_output_wrapper(), + "subprocess.check_output", side_effect=check_output_wrapper(), ) mocker.patch( - "poetry.utils._compat.subprocess.Popen.communicate", - side_effect=[("/prefix", None)], + "subprocess.Popen.communicate", side_effect=[("/prefix", None)], ) m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv) @@ -227,12 +223,10 @@ def test_activate_activates_same_virtualenv_with_envs_file( config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( - "poetry.utils._compat.subprocess.check_output", - side_effect=check_output_wrapper(), + "subprocess.check_output", side_effect=check_output_wrapper(), ) mocker.patch( - "poetry.utils._compat.subprocess.Popen.communicate", - side_effect=[("/prefix", None)], + "subprocess.Popen.communicate", side_effect=[("/prefix", None)], ) m = mocker.patch("poetry.utils.env.EnvManager.create_venv") @@ -266,11 +260,11 @@ def test_activate_activates_different_virtualenv_with_envs_file( config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( - "poetry.utils._compat.subprocess.check_output", + "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) mocker.patch( - "poetry.utils._compat.subprocess.Popen.communicate", + "subprocess.Popen.communicate", side_effect=[("/prefix", None), ("/prefix", None), ("/prefix", None)], ) m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv) @@ -309,11 +303,10 @@ def test_activate_activates_recreates_for_different_patch( config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( - "poetry.utils._compat.subprocess.check_output", - side_effect=check_output_wrapper(), + "subprocess.check_output", side_effect=check_output_wrapper(), ) mocker.patch( - "poetry.utils._compat.subprocess.Popen.communicate", + "subprocess.Popen.communicate", side_effect=[ ("/prefix", None), ('{"version_info": [3, 7, 0]}', None), @@ -366,11 +359,11 @@ def test_activate_does_not_recreate_when_switching_minor( config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( - "poetry.utils._compat.subprocess.check_output", + "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) mocker.patch( - "poetry.utils._compat.subprocess.Popen.communicate", + "subprocess.Popen.communicate", side_effect=[("/prefix", None), ("/prefix", None), ("/prefix", None)], ) build_venv_m = mocker.patch( @@ -411,8 +404,7 @@ def test_deactivate_non_activated_but_existing( config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( - "poetry.utils._compat.subprocess.check_output", - side_effect=check_output_wrapper(), + "subprocess.check_output", side_effect=check_output_wrapper(), ) manager.deactivate(NullIO()) @@ -450,8 +442,7 @@ def test_deactivate_activated(tmp_dir, manager, poetry, config, mocker): config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( - "poetry.utils._compat.subprocess.check_output", - side_effect=check_output_wrapper(), + "subprocess.check_output", side_effect=check_output_wrapper(), ) manager.deactivate(NullIO()) @@ -482,12 +473,10 @@ def test_get_prefers_explicitly_activated_virtualenvs_over_env_var( envs_file.write(doc) mocker.patch( - "poetry.utils._compat.subprocess.check_output", - side_effect=check_output_wrapper(), + "subprocess.check_output", side_effect=check_output_wrapper(), ) mocker.patch( - "poetry.utils._compat.subprocess.Popen.communicate", - side_effect=[("/prefix", None)], + "subprocess.Popen.communicate", side_effect=[("/prefix", None)], ) env = manager.get() @@ -518,7 +507,7 @@ def test_remove_by_python_version(tmp_dir, manager, poetry, config, mocker): (Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir() mocker.patch( - "poetry.utils._compat.subprocess.check_output", + "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) @@ -536,7 +525,7 @@ def test_remove_by_name(tmp_dir, manager, poetry, config, mocker): (Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir() mocker.patch( - "poetry.utils._compat.subprocess.check_output", + "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) @@ -554,7 +543,7 @@ def test_remove_also_deactivates(tmp_dir, manager, poetry, config, mocker): (Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir() mocker.patch( - "poetry.utils._compat.subprocess.check_output", + "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) @@ -591,7 +580,7 @@ def test_remove_keeps_dir_if_not_deleteable(tmp_dir, manager, poetry, config, mo file2_path.touch(exist_ok=False) mocker.patch( - "poetry.utils._compat.subprocess.check_output", + "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) @@ -619,9 +608,7 @@ def err_on_rm_venv_only(path, *args, **kwargs): m.side_effect = original_rmtree # Avoid teardown using `err_on_rm_venv_only` -@pytest.mark.skipif( - os.name == "nt" or PY2, reason="Symlinks are not support for Windows" -) +@pytest.mark.skipif(os.name == "nt", reason="Symlinks are not support for Windows") def test_env_has_symlinks_on_nix(tmp_dir, tmp_venv): assert os.path.islink(tmp_venv.python) @@ -652,7 +639,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ mocker.patch("sys.version_info", (2, 7, 16)) mocker.patch( - "poetry.utils._compat.subprocess.check_output", + "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.7.5")), ) m = mocker.patch( @@ -678,9 +665,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) mocker.patch("sys.version_info", (2, 7, 16)) - mocker.patch( - "poetry.utils._compat.subprocess.check_output", side_effect=["3.5.3", "3.9.0"] - ) + mocker.patch("subprocess.check_output", side_effect=["3.5.3", "3.9.0"]) m = mocker.patch( "poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: "" ) @@ -702,9 +687,7 @@ def test_create_venv_fails_if_no_compatible_python_version_could_be_found( poetry.package.python_versions = "^4.8" - mocker.patch( - "poetry.utils._compat.subprocess.check_output", side_effect=["", "", "", ""] - ) + mocker.patch("subprocess.check_output", side_effect=["", "", "", ""]) m = mocker.patch( "poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: "" ) @@ -730,7 +713,7 @@ def test_create_venv_does_not_try_to_find_compatible_versions_with_executable( poetry.package.python_versions = "^4.8" - mocker.patch("poetry.utils._compat.subprocess.check_output", side_effect=["3.8.0"]) + mocker.patch("subprocess.check_output", side_effect=["3.8.0"]) m = mocker.patch( "poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: "" ) @@ -762,7 +745,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility( mocker.patch("sys.version_info", (version.major, version.minor, version.patch + 1)) check_output = mocker.patch( - "poetry.utils._compat.subprocess.check_output", + "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.9")), ) m = mocker.patch( @@ -793,7 +776,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable( venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) check_output = mocker.patch( - "poetry.utils._compat.subprocess.check_output", + "subprocess.check_output", side_effect=check_output_wrapper( Version.parse("{}.{}.0".format(version.major, version.minor - 1)) ), @@ -831,11 +814,10 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( ) mocker.patch( - "poetry.utils._compat.subprocess.check_output", - side_effect=check_output_wrapper(), + "subprocess.check_output", side_effect=check_output_wrapper(), ) mocker.patch( - "poetry.utils._compat.subprocess.Popen.communicate", + "subprocess.Popen.communicate", side_effect=[("/prefix", None), ("/prefix", None)], ) m = mocker.patch("poetry.utils.env.EnvManager.build_venv") diff --git a/tests/utils/test_env_site.py b/tests/utils/test_env_site.py index f25e2142193..01abb113771 100644 --- a/tests/utils/test_env_site.py +++ b/tests/utils/test_env_site.py @@ -1,13 +1,14 @@ import uuid -from poetry.utils._compat import Path +from pathlib import Path + from poetry.utils._compat import decode from poetry.utils.env import SitePackages def test_env_site_simple(tmp_dir, mocker): # emulate permission error when creating directory - mocker.patch("poetry.utils._compat.Path.mkdir", side_effect=OSError()) + mocker.patch("pathlib.Path.mkdir", side_effect=OSError()) site_packages = SitePackages(Path("/non-existent"), fallbacks=[Path(tmp_dir)]) candidates = site_packages.make_candidates(Path("hello.txt"), writable_only=True) hello = Path(tmp_dir) / "hello.txt" diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index 1e660ce69cd..f15e2dc53a3 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -1,5 +1,7 @@ import sys +from pathlib import Path + import pytest from poetry.core.packages import dependency_from_pep_508 @@ -7,7 +9,6 @@ from poetry.factory import Factory from poetry.packages import Locker as BaseLocker from poetry.repositories.legacy_repository import LegacyRepository -from poetry.utils._compat import Path from poetry.utils.exporter import Exporter @@ -42,9 +43,7 @@ def working_directory(): @pytest.fixture(autouse=True) def mock_path_cwd(mocker, working_directory): - yield mocker.patch( - "poetry.core.utils._compat.Path.cwd", return_value=working_directory - ) + yield mocker.patch("pathlib.Path.cwd", return_value=working_directory) @pytest.fixture() diff --git a/tests/utils/test_helpers.py b/tests/utils/test_helpers.py index 83bf2030780..fe105df6b74 100644 --- a/tests/utils/test_helpers.py +++ b/tests/utils/test_helpers.py @@ -1,5 +1,6 @@ +from pathlib import Path + from poetry.core.utils.helpers import parse_requires -from poetry.utils._compat import Path from poetry.utils.helpers import get_cert from poetry.utils.helpers import get_client_cert diff --git a/tests/utils/test_setup_reader.py b/tests/utils/test_setup_reader.py index 68fc005a496..ccde90ac253 100644 --- a/tests/utils/test_setup_reader.py +++ b/tests/utils/test_setup_reader.py @@ -3,7 +3,6 @@ import pytest from poetry.core.semver.exceptions import ParseVersionError -from poetry.utils._compat import PY35 from poetry.utils.setup_reader import SetupReader @@ -15,7 +14,6 @@ def _setup(name): return _setup -@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4") def test_setup_reader_read_first_level_setup_call_with_direct_types(setup): result = SetupReader.read_from_directory(setup("flask")) @@ -48,7 +46,6 @@ def test_setup_reader_read_first_level_setup_call_with_direct_types(setup): assert expected_python_requires == result["python_requires"] -@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4") def test_setup_reader_read_first_level_setup_call_with_variables(setup): result = SetupReader.read_from_directory(setup("requests")) @@ -74,7 +71,6 @@ def test_setup_reader_read_first_level_setup_call_with_variables(setup): assert expected_python_requires == result["python_requires"] -@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4") def test_setup_reader_read_sub_level_setup_call_with_direct_types(setup): result = SetupReader.read_from_directory(setup("sqlalchemy")) @@ -123,7 +119,6 @@ def test_setup_reader_read_setup_cfg_with_attr(setup): SetupReader.read_from_directory(setup("with-setup-cfg-attr")) -@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4") def test_setup_reader_read_setup_kwargs(setup): result = SetupReader.read_from_directory(setup("pendulum")) @@ -140,7 +135,6 @@ def test_setup_reader_read_setup_kwargs(setup): assert expected_python_requires == result["python_requires"] -@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4") def test_setup_reader_read_setup_call_in_main(setup): result = SetupReader.read_from_directory(setup("pyyaml")) @@ -157,7 +151,6 @@ def test_setup_reader_read_setup_call_in_main(setup): assert expected_python_requires == result["python_requires"] -@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4") def test_setup_reader_read_extras_require_with_variables(setup): result = SetupReader.read_from_directory(setup("extras_require_with_vars")) @@ -174,7 +167,6 @@ def test_setup_reader_read_extras_require_with_variables(setup): assert expected_python_requires == result["python_requires"] -@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4") def test_setup_reader_setuptools(setup): result = SetupReader.read_from_directory(setup("setuptools_setup")) From 2aab9bcd495e11e5f4491aa72a9510773cc4a90e Mon Sep 17 00:00:00 2001 From: Vladislav Goncharenko Date: Sat, 26 Dec 2020 17:39:51 +0300 Subject: [PATCH 073/222] Added missing Option in install command description (#3500) * Added missing Option in install command description --- docs/docs/cli.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/docs/cli.md b/docs/docs/cli.md index bf207e8dc42..d2a56788de0 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -146,6 +146,8 @@ poetry install --no-root * `--no-dev`: Do not install dev dependencies. * `--no-root`: Do not install the root package (your project). +* `--dry-run`: Output the operations but do not execute anything (implicitly enables --verbose). +* `--remove-untracked`: Remove dependencies not presented in the lock file * `--extras (-E)`: Features to install (multiple values allowed). ## update From 6cd4e05eaacba4756ec3b5b4e44f3471cf6506dc Mon Sep 17 00:00:00 2001 From: Torsten Rudolf Date: Tue, 5 Jan 2021 00:49:24 +1100 Subject: [PATCH 074/222] document ssh connection for git dependencies (#3389) Co-authored-by: Steph Samson --- docs/docs/dependency-specification.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/docs/dependency-specification.md b/docs/docs/dependency-specification.md index f71480dfa41..d90067d31e3 100644 --- a/docs/docs/dependency-specification.md +++ b/docs/docs/dependency-specification.md @@ -98,6 +98,13 @@ flask = { git = "https://github.com/pallets/flask.git", rev = "38eb5d3b" } numpy = { git = "https://github.com/numpy/numpy.git", tag = "v0.13.2" } ``` +To use an SSH connection, for example in the case of private repositories, use the following example syntax: + +```toml +[tool.poetry.dependencies] +requests = { git = "git@github.com:requests/requests.git" } +``` + ## `path` dependencies To depend on a library located in a local directory or file, From 529c5985c1e12be700d20927289d126955ff0b80 Mon Sep 17 00:00:00 2001 From: atanas argirov Date: Tue, 5 Jan 2021 18:07:58 +0000 Subject: [PATCH 075/222] * added additional description for poetry add covering git+ssh URL use cases (#3521) --- docs/docs/cli.md | 9 +++++++++ poetry/console/commands/add.py | 2 ++ 2 files changed, 11 insertions(+) diff --git a/docs/docs/cli.md b/docs/docs/cli.md index d2a56788de0..018c1d9c3cd 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -215,6 +215,10 @@ or use ssh instead of https: ```bash poetry add git+ssh://git@github.com/sdispater/pendulum.git + +or alternatively: + +poetry add git+ssh://git@github.com:sdispater/pendulum.git ``` If you need to checkout a specific branch, tag or revision, @@ -223,6 +227,11 @@ you can specify it when using `add`: ```bash poetry add git+https://github.com/sdispater/pendulum.git#develop poetry add git+https://github.com/sdispater/pendulum.git#2.0.5 + +or using SSH instead: + +poetry add git+ssh://github.com/sdispater/pendulum.git#develop +poetry add git+ssh://github.com/sdispater/pendulum.git#2.0.5 ``` or make them point to a local directory or file: diff --git a/poetry/console/commands/add.py b/poetry/console/commands/add.py index 29cf07d2a26..72ddeb015c1 100644 --- a/poetry/console/commands/add.py +++ b/poetry/console/commands/add.py @@ -59,6 +59,8 @@ class AddCommand(InstallerCommand, InitCommand): " - A name and a constraint (requests@^2.23.0)\n" " - A git url (git+https://github.com/python-poetry/poetry.git)\n" " - A git url with a revision (git+https://github.com/python-poetry/poetry.git#develop)\n" + " - A git SSH url (git+ssh://github.com/python-poetry/poetry.git)\n" + " - A git SSH url with a revision (git+ssh://github.com/python-poetry/poetry.git#develop)\n" " - A file path (../my-package/my-package.whl)\n" " - A directory (../my-package/)\n" " - A url (https://example.com/packages/my-package-0.1.0.tar.gz)\n" From e36fb739ee7be9748e1176bd10030760473e880c Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Tue, 5 Jan 2021 19:22:07 +0100 Subject: [PATCH 076/222] Remove non-existing --path option, add missing options (#3522) * Remove non-existing --path option * Add missing options --- docs/docs/cli.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/docs/cli.md b/docs/docs/cli.md index 018c1d9c3cd..9ceb059bfe7 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -266,10 +266,14 @@ poetry add "git+https://github.com/pallets/flask.git@1.1.1[dotenv,dev]" ### Options * `--dev (-D)`: Add package as development dependency. -* `--path`: The path to a dependency. -* `--optional` : Add as an optional dependency. -* `--dry-run` : Outputs the operations but will not execute anything (implicitly enables --verbose). -* `--lock` : Do not perform install (only update the lockfile). +* `--extras (-E)`: Extras to activate for the dependency. (multiple values allowed) +* `--optional`: Add as an optional dependency. +* `--python`: Python version for which the dependency must be installed. +* `--platform`: Platforms for which the dependency must be installed. +* `--source`: Name of the source to use to install the package. +* `---allow-prereleases`: Accept prereleases. +* `--dry-run`: Outputs the operations but will not execute anything (implicitly enables --verbose). +* `--lock`: Do not perform install (only update the lockfile). ## remove From b8f9547aa19efbd9cb81dfb6fffde1bd392d332d Mon Sep 17 00:00:00 2001 From: John Peter Yamauchi Date: Tue, 5 Jan 2021 17:55:26 -0600 Subject: [PATCH 077/222] legacy repository: support redirected url --- poetry/repositories/legacy_repository.py | 13 +++++++++++-- tests/repositories/test_legacy_repository.py | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/poetry/repositories/legacy_repository.py b/poetry/repositories/legacy_repository.py index e059c3bb8d4..94e1dbf4d7b 100644 --- a/poetry/repositories/legacy_repository.py +++ b/poetry/repositories/legacy_repository.py @@ -387,8 +387,17 @@ def _get(self, endpoint): # type: (str) -> Union[Page, None] if response.status_code in (401, 403): self._log( - "Authorization error accessing {url}".format(url=url), level="warn" + "Authorization error accessing {url}".format(url=response.url), + level="warn", ) return - return Page(url, response.content, response.headers) + if response.url != url: + self._log( + "Response URL {response_url} differs from request URL {url}".format( + response_url=response.url, url=url + ), + level="debug", + ) + + return Page(response.url, response.content, response.headers) diff --git a/tests/repositories/test_legacy_repository.py b/tests/repositories/test_legacy_repository.py index 1402f4799af..9de70520728 100644 --- a/tests/repositories/test_legacy_repository.py +++ b/tests/repositories/test_legacy_repository.py @@ -3,6 +3,7 @@ from pathlib import Path import pytest +import requests from poetry.core.packages import Dependency from poetry.factory import Factory @@ -322,3 +323,17 @@ def test_get_4xx_and_5xx_raises(http): for endpoint in endpoints: with pytest.raises(RepositoryError): repo._get(endpoint) + + +def test_get_redirected_response_url(http, monkeypatch): + repo = MockHttpRepository({"/foo": 200}, http) + redirect_url = "http://legacy.redirect.bar" + + def get_mock(url): + response = requests.Response() + response.status_code = 200 + response.url = redirect_url + "/foo" + return response + + monkeypatch.setattr(repo.session, "get", get_mock) + assert repo._get("/foo")._url == "http://legacy.redirect.bar/foo/" From de0b32c245c72568cf546090600d4626917cd3a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Wed, 6 Jan 2021 14:10:09 +0100 Subject: [PATCH 078/222] tests: fix a typo in mocker autospec --- tests/inspection/test_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/inspection/test_info.py b/tests/inspection/test_info.py index e04bd52a5c3..ac0c4504e2c 100644 --- a/tests/inspection/test_info.py +++ b/tests/inspection/test_info.py @@ -174,7 +174,7 @@ def test_info_setup_complex(demo_setup_complex): def test_info_setup_complex_pep517_error(mocker, demo_setup_complex): mocker.patch( "poetry.utils.env.VirtualEnv.run", - auto_spec=True, + autospec=True, side_effect=EnvCommandError(CalledProcessError(1, "mock", output="mock")), ) From a106cb21205568126432266160a9743885e13443 Mon Sep 17 00:00:00 2001 From: sprt Date: Sat, 16 Jan 2021 23:50:12 +0100 Subject: [PATCH 079/222] version: take --short into account when updating Fixes #3577. This makes `poetry version --short ` output only the updated version number, similarly to `poetry version --short`. --- poetry/console/commands/version.py | 11 +++++++---- tests/console/commands/test_version.py | 10 ++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/poetry/console/commands/version.py b/poetry/console/commands/version.py index 21ed676f54f..5ac5c666afb 100644 --- a/poetry/console/commands/version.py +++ b/poetry/console/commands/version.py @@ -48,11 +48,14 @@ def handle(self): self.poetry.package.pretty_version, version ) - self.line( - "Bumping version from {} to {}".format( - self.poetry.package.pretty_version, version + if self.option("short"): + self.line("{}".format(version)) + else: + self.line( + "Bumping version from {} to {}".format( + self.poetry.package.pretty_version, version + ) ) - ) content = self.poetry.file.read() poetry_content = content["tool"]["poetry"] diff --git a/tests/console/commands/test_version.py b/tests/console/commands/test_version.py index 77f6d8aa41e..ac91df1690c 100644 --- a/tests/console/commands/test_version.py +++ b/tests/console/commands/test_version.py @@ -51,3 +51,13 @@ def test_version_show(tester): def test_short_version_show(tester): tester.execute("--short") assert "1.2.3\n" == tester.io.fetch_output() + + +def test_version_update(tester): + tester.execute("2.0.0") + assert "Bumping version from 1.2.3 to 2.0.0\n" == tester.io.fetch_output() + + +def test_short_version_update(tester): + tester.execute("--short 2.0.0") + assert "2.0.0\n" == tester.io.fetch_output() From 1346497b2bb8c1d1353243a153f6a85b41a57728 Mon Sep 17 00:00:00 2001 From: finswimmer Date: Fri, 30 Oct 2020 23:03:19 +0100 Subject: [PATCH 080/222] fix and add several type hints --- poetry/config/config.py | 10 +- poetry/config/file_config_source.py | 5 +- poetry/console/__init__.py | 2 +- poetry/console/application.py | 10 +- poetry/console/commands/about.py | 2 +- poetry/console/commands/add.py | 2 +- poetry/console/commands/build.py | 2 +- poetry/console/commands/cache/cache.py | 2 +- poetry/console/commands/cache/clear.py | 2 +- poetry/console/commands/cache/list.py | 4 +- poetry/console/commands/check.py | 2 +- poetry/console/commands/command.py | 8 +- poetry/console/commands/config.py | 25 ++++- poetry/console/commands/debug/info.py | 2 +- poetry/console/commands/debug/resolve.py | 4 +- poetry/console/commands/env/info.py | 11 ++- poetry/console/commands/env/list.py | 2 +- poetry/console/commands/env/remove.py | 2 +- poetry/console/commands/env/use.py | 2 +- poetry/console/commands/env_command.py | 12 ++- poetry/console/commands/export.py | 2 +- poetry/console/commands/init.py | 15 +-- poetry/console/commands/install.py | 2 +- poetry/console/commands/installer_command.py | 4 +- poetry/console/commands/lock.py | 2 +- poetry/console/commands/new.py | 2 +- poetry/console/commands/publish.py | 3 +- poetry/console/commands/remove.py | 2 +- poetry/console/commands/run.py | 7 +- poetry/console/commands/search.py | 2 +- poetry/console/commands/self/self.py | 2 +- poetry/console/commands/self/update.py | 32 ++++--- poetry/console/commands/shell.py | 2 +- poetry/console/commands/show.py | 48 +++++++--- poetry/console/commands/update.py | 2 +- poetry/console/commands/version.py | 4 +- poetry/console/config/application_config.py | 2 +- poetry/console/logging/io_formatter.py | 8 +- poetry/console/logging/io_handler.py | 12 ++- poetry/factory.py | 2 +- poetry/inspection/info.py | 4 +- poetry/installation/base_installer.py | 13 ++- poetry/installation/chooser.py | 5 +- poetry/installation/executor.py | 72 ++++++++++----- poetry/installation/installer.py | 32 ++++--- poetry/installation/noop_installer.py | 21 +++-- poetry/installation/operations/__init__.py | 5 + poetry/installation/operations/install.py | 19 +++- poetry/installation/operations/operation.py | 17 ++-- poetry/installation/operations/uninstall.py | 19 +++- poetry/installation/operations/update.py | 23 +++-- poetry/installation/pip_installer.py | 23 +++-- poetry/io/null_io.py | 4 +- poetry/layouts/layout.py | 34 +++---- poetry/layouts/src.py | 7 +- poetry/layouts/standard.py | 6 +- poetry/masonry/builders/editable.py | 26 ++++-- poetry/mixology/__init__.py | 16 +++- poetry/mixology/assignment.py | 20 +++- poetry/mixology/failure.py | 11 ++- poetry/mixology/incompatibility.py | 30 +++--- poetry/mixology/incompatibility_cause.py | 31 ++++--- poetry/mixology/partial_solution.py | 24 +++-- poetry/mixology/result.py | 17 +++- .../solutions/python_requirement_solution.py | 15 ++- poetry/mixology/term.py | 23 +++-- poetry/mixology/version_solver.py | 12 +-- poetry/packages/dependency_package.py | 16 ++-- poetry/packages/locker.py | 20 ++-- poetry/packages/package_collection.py | 15 ++- poetry/publishing/publisher.py | 16 +++- poetry/publishing/uploader.py | 26 ++++-- poetry/puzzle/exceptions.py | 12 ++- poetry/puzzle/provider.py | 17 ++-- poetry/puzzle/solver.py | 64 +++++++++---- poetry/repositories/base_repository.py | 24 +++-- poetry/repositories/legacy_repository.py | 26 ++++-- poetry/repositories/pool.py | 13 ++- poetry/repositories/pypi_repository.py | 12 ++- poetry/repositories/repository.py | 34 +++++-- poetry/utils/appdirs.py | 24 +++-- poetry/utils/env.py | 91 +++++++++++-------- poetry/utils/extras.py | 3 +- poetry/utils/helpers.py | 14 ++- poetry/utils/password_manager.py | 39 +++++--- poetry/utils/setup_reader.py | 4 +- poetry/utils/shell.py | 7 +- poetry/version/version_selector.py | 16 +++- 88 files changed, 859 insertions(+), 429 deletions(-) diff --git a/poetry/config/config.py b/poetry/config/config.py index 6be9457cffe..79c7a75e95a 100644 --- a/poetry/config/config.py +++ b/poetry/config/config.py @@ -19,11 +19,11 @@ _NOT_SET = object() -def boolean_validator(val): +def boolean_validator(val): # type: (str) -> bool return val in {"true", "false", "1", "0"} -def boolean_normalizer(val): +def boolean_normalizer(val): # type: (str) -> bool return val in ["true", "1"] @@ -51,11 +51,11 @@ def __init__( self._auth_config_source = DictConfigSource() @property - def name(self): + def name(self): # type: () -> str return str(self._file.path) @property - def config(self): + def config(self): # type: () -> Dict return self._config @property @@ -82,7 +82,7 @@ def merge(self, config): # type: (Dict[str, Any]) -> None merge_dicts(self._config, config) def all(self): # type: () -> Dict[str, Any] - def _all(config, parent_key=""): + def _all(config, parent_key=""): # type: (Dict, str) -> Dict all_ = {} for key in config: diff --git a/poetry/config/file_config_source.py b/poetry/config/file_config_source.py index ed4e3a8522e..3e7cf71a5d9 100644 --- a/poetry/config/file_config_source.py +++ b/poetry/config/file_config_source.py @@ -1,6 +1,7 @@ from contextlib import contextmanager from typing import TYPE_CHECKING from typing import Any +from typing import Generator from tomlkit import document from tomlkit import table @@ -9,6 +10,8 @@ if TYPE_CHECKING: + from tomlkit.toml_document import TOMLDocument # noqa + from poetry.core.toml.file import TOMLFile # noqa @@ -56,7 +59,7 @@ def remove_property(self, key): # type: (str) -> None current_config = current_config[key] @contextmanager - def secure(self): + def secure(self): # type: () -> Generator["TOMLDocument"] if self.file.exists(): initial_config = self.file.read() config = self.file.read() diff --git a/poetry/console/__init__.py b/poetry/console/__init__.py index c0c25738482..56adf27e2ee 100644 --- a/poetry/console/__init__.py +++ b/poetry/console/__init__.py @@ -1,5 +1,5 @@ from .application import Application -def main(): +def main(): # type: () -> int return Application().run() diff --git a/poetry/console/application.py b/poetry/console/application.py index 027fec85776..1fd9dbc0bc3 100644 --- a/poetry/console/application.py +++ b/poetry/console/application.py @@ -1,5 +1,7 @@ import sys +from typing import TYPE_CHECKING + from cleo import Application as BaseApplication from poetry.__version__ import __version__ @@ -29,8 +31,12 @@ from .config import ApplicationConfig +if TYPE_CHECKING: + from poetry.poetry import Poetry # noqa + + class Application(BaseApplication): - def __init__(self): + def __init__(self): # type: () -> None super(Application, self).__init__( "poetry", __version__, config=ApplicationConfig("poetry", __version__) ) @@ -59,7 +65,7 @@ def __init__(self): self._preliminary_io.error_line("{}\n".format(message)) @property - def poetry(self): + def poetry(self): # type: () -> "Poetry" from pathlib import Path from poetry.factory import Factory diff --git a/poetry/console/commands/about.py b/poetry/console/commands/about.py index a84a2b6fec6..c4415591814 100644 --- a/poetry/console/commands/about.py +++ b/poetry/console/commands/about.py @@ -7,7 +7,7 @@ class AboutCommand(Command): description = "Shows information about Poetry." - def handle(self): + def handle(self): # type: () -> None self.line( """Poetry - Package Management for Python diff --git a/poetry/console/commands/add.py b/poetry/console/commands/add.py index 72ddeb015c1..a3868b4bff8 100644 --- a/poetry/console/commands/add.py +++ b/poetry/console/commands/add.py @@ -68,7 +68,7 @@ class AddCommand(InstallerCommand, InitCommand): loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"] - def handle(self): + def handle(self): # type: () -> int from tomlkit import inline_table from poetry.core.semver import parse_constraint diff --git a/poetry/console/commands/build.py b/poetry/console/commands/build.py index 72b7319fcd5..bd938764726 100644 --- a/poetry/console/commands/build.py +++ b/poetry/console/commands/build.py @@ -18,7 +18,7 @@ class BuildCommand(EnvCommand): "poetry.core.masonry.builders.wheel", ] - def handle(self): + def handle(self): # type: () -> None from poetry.core.masonry import Builder fmt = "all" diff --git a/poetry/console/commands/cache/cache.py b/poetry/console/commands/cache/cache.py index 695e27e0af7..ff04fecb041 100644 --- a/poetry/console/commands/cache/cache.py +++ b/poetry/console/commands/cache/cache.py @@ -11,5 +11,5 @@ class CacheCommand(Command): commands = [CacheClearCommand(), CacheListCommand()] - def handle(self): + def handle(self): # type: () -> int return self.call("help", self._config.name) diff --git a/poetry/console/commands/cache/clear.py b/poetry/console/commands/cache/clear.py index 42e71091526..9969ebbaa1c 100644 --- a/poetry/console/commands/cache/clear.py +++ b/poetry/console/commands/cache/clear.py @@ -14,7 +14,7 @@ class CacheClearCommand(Command): arguments = [argument("cache", description="The name of the cache to clear.")] options = [option("all", description="Clear all entries in the cache.")] - def handle(self): + def handle(self): # type: () -> int from cachy import CacheManager from poetry.locations import REPOSITORY_CACHE_DIR diff --git a/poetry/console/commands/cache/list.py b/poetry/console/commands/cache/list.py index 6a030fa2eba..090cba601df 100644 --- a/poetry/console/commands/cache/list.py +++ b/poetry/console/commands/cache/list.py @@ -1,5 +1,7 @@ import os +from typing import Optional + from ..command import Command @@ -8,7 +10,7 @@ class CacheListCommand(Command): name = "list" description = "List Poetry's caches." - def handle(self): + def handle(self): # type: () -> Optional[int] from poetry.locations import REPOSITORY_CACHE_DIR if os.path.exists(str(REPOSITORY_CACHE_DIR)): diff --git a/poetry/console/commands/check.py b/poetry/console/commands/check.py index 72c7ca4d947..62af8379163 100644 --- a/poetry/console/commands/check.py +++ b/poetry/console/commands/check.py @@ -11,7 +11,7 @@ class CheckCommand(Command): name = "check" description = "Checks the validity of the pyproject.toml file." - def handle(self): + def handle(self): # type: () -> int # Load poetry config and display errors, if any poetry_file = Factory.locate(Path.cwd()) config = PyProjectTOML(poetry_file).poetry_config diff --git a/poetry/console/commands/command.py b/poetry/console/commands/command.py index 1e22142341a..a575f46044a 100644 --- a/poetry/console/commands/command.py +++ b/poetry/console/commands/command.py @@ -1,12 +1,18 @@ +from typing import TYPE_CHECKING + from cleo import Command as BaseCommand +if TYPE_CHECKING: + from poetry.poetry import Poetry # noqa + + class Command(BaseCommand): loggers = [] @property - def poetry(self): + def poetry(self): # type: () -> "Poetry" return self.application.poetry def reset_poetry(self): # type: () -> None diff --git a/poetry/console/commands/config.py b/poetry/console/commands/config.py index 310524d4ee2..5b35eb5a26d 100644 --- a/poetry/console/commands/config.py +++ b/poetry/console/commands/config.py @@ -1,6 +1,13 @@ import json import re +from typing import TYPE_CHECKING +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import Tuple + from cleo import argument from cleo import option @@ -11,6 +18,10 @@ from .command import Command +if TYPE_CHECKING: + from poetry.config.config_source import ConfigSource # noqa + + class ConfigCommand(Command): name = "config" @@ -40,7 +51,7 @@ class ConfigCommand(Command): LIST_PROHIBITED_SETTINGS = {"http-basic", "pypi-token"} @property - def unique_config_values(self): + def unique_config_values(self): # type: () -> Dict[str, Tuple[Any, Any, Any]] from pathlib import Path from poetry.config.config import boolean_normalizer @@ -75,7 +86,7 @@ def unique_config_values(self): return unique_config_values - def handle(self): + def handle(self): # type: () -> Optional[int] from pathlib import Path from poetry.config.file_config_source import FileConfigSource @@ -253,7 +264,9 @@ def handle(self): raise ValueError("Setting {} does not exist".format(self.argument("key"))) - def _handle_single_value(self, source, key, callbacks, values): + def _handle_single_value( + self, source, key, callbacks, values + ): # type: ("ConfigSource", str, Tuple[Any, Any, Any], List[Any]) -> int validator, normalizer, _ = callbacks if len(values) > 1: @@ -267,7 +280,7 @@ def _handle_single_value(self, source, key, callbacks, values): return 0 - def _list_configuration(self, config, raw, k=""): + def _list_configuration(self, config, raw, k=""): # type: (Dict, Dict, str) -> None orig_k = k for key, value in sorted(config.items()): if k + key in self.LIST_PROHIBITED_SETTINGS: @@ -301,7 +314,9 @@ def _list_configuration(self, config, raw, k=""): self.line(message) - def _get_setting(self, contents, setting=None, k=None, default=None): + def _get_setting( + self, contents, setting=None, k=None, default=None + ): # type: (Dict, Optional[str], Optional[str], Optional[Any]) -> List[Tuple[str, str]] orig_k = k if setting and setting.split(".")[0] not in contents: diff --git a/poetry/console/commands/debug/info.py b/poetry/console/commands/debug/info.py index 81096a6ffc8..8950a3e5710 100644 --- a/poetry/console/commands/debug/info.py +++ b/poetry/console/commands/debug/info.py @@ -10,7 +10,7 @@ class DebugInfoCommand(Command): name = "info" description = "Shows debug information." - def handle(self): + def handle(self): # type: () -> int poetry_python_version = ".".join(str(s) for s in sys.version_info[:3]) self.line("") diff --git a/poetry/console/commands/debug/resolve.py b/poetry/console/commands/debug/resolve.py index 52ae1951b21..743ef1d7443 100644 --- a/poetry/console/commands/debug/resolve.py +++ b/poetry/console/commands/debug/resolve.py @@ -1,3 +1,5 @@ +from typing import Optional + from cleo import argument from cleo import option @@ -27,7 +29,7 @@ class DebugResolveCommand(InitCommand): loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"] - def handle(self): + def handle(self): # type: () -> Optional[int] from poetry.core.packages.project_package import ProjectPackage from poetry.factory import Factory from poetry.io.null_io import NullIO diff --git a/poetry/console/commands/env/info.py b/poetry/console/commands/env/info.py index 301d88f9520..fc0e4a75936 100644 --- a/poetry/console/commands/env/info.py +++ b/poetry/console/commands/env/info.py @@ -1,8 +1,15 @@ +from typing import TYPE_CHECKING +from typing import Optional + from cleo import option from ..command import Command +if TYPE_CHECKING: + from poetry.utils.env import Env # noqa + + class EnvInfoCommand(Command): name = "info" @@ -10,7 +17,7 @@ class EnvInfoCommand(Command): options = [option("path", "p", "Only display the environment's path.")] - def handle(self): + def handle(self): # type: () -> Optional[int] from poetry.utils.env import EnvManager env = EnvManager(self.poetry).get() @@ -25,7 +32,7 @@ def handle(self): self._display_complete_info(env) - def _display_complete_info(self, env): + def _display_complete_info(self, env): # type: ("Env") -> None env_python_version = ".".join(str(s) for s in env.version_info[:3]) self.line("") self.line("Virtualenv") diff --git a/poetry/console/commands/env/list.py b/poetry/console/commands/env/list.py index 272a853b976..46423d142c7 100644 --- a/poetry/console/commands/env/list.py +++ b/poetry/console/commands/env/list.py @@ -10,7 +10,7 @@ class EnvListCommand(Command): options = [option("full-path", None, "Output the full paths of the virtualenvs.")] - def handle(self): + def handle(self): # type: () -> None from poetry.utils.env import EnvManager manager = EnvManager(self.poetry) diff --git a/poetry/console/commands/env/remove.py b/poetry/console/commands/env/remove.py index 5f208851deb..4b4bca29c9d 100644 --- a/poetry/console/commands/env/remove.py +++ b/poetry/console/commands/env/remove.py @@ -12,7 +12,7 @@ class EnvRemoveCommand(Command): argument("python", "The python executable to remove the virtualenv for.") ] - def handle(self): + def handle(self): # type: () -> None from poetry.utils.env import EnvManager manager = EnvManager(self.poetry) diff --git a/poetry/console/commands/env/use.py b/poetry/console/commands/env/use.py index ef9cf3def6b..57db24c97bd 100644 --- a/poetry/console/commands/env/use.py +++ b/poetry/console/commands/env/use.py @@ -10,7 +10,7 @@ class EnvUseCommand(Command): arguments = [argument("python", "The python executable to use.")] - def handle(self): + def handle(self): # type: () -> None from poetry.utils.env import EnvManager manager = EnvManager(self.poetry) diff --git a/poetry/console/commands/env_command.py b/poetry/console/commands/env_command.py index 2fb298d7bb2..a7ae5e7bdda 100644 --- a/poetry/console/commands/env_command.py +++ b/poetry/console/commands/env_command.py @@ -1,15 +1,21 @@ +from typing import TYPE_CHECKING + from .command import Command +if TYPE_CHECKING: + from poetry.utils.env import VirtualEnv # noqa + + class EnvCommand(Command): - def __init__(self): + def __init__(self): # type: () -> None self._env = None super(EnvCommand, self).__init__() @property - def env(self): + def env(self): # type: () -> "VirtualEnv" return self._env - def set_env(self, env): + def set_env(self, env): # type: ("VirtualEnv") -> None self._env = env diff --git a/poetry/console/commands/export.py b/poetry/console/commands/export.py index 126b657b937..11ef2a37377 100644 --- a/poetry/console/commands/export.py +++ b/poetry/console/commands/export.py @@ -31,7 +31,7 @@ class ExportCommand(Command): option("with-credentials", None, "Include credentials for extra indices."), ] - def handle(self): + def handle(self): # type: () -> None fmt = self.option("format") if fmt not in Exporter.ACCEPTED_FORMATS: diff --git a/poetry/console/commands/init.py b/poetry/console/commands/init.py index ef56484abd1..92870cb614a 100644 --- a/poetry/console/commands/init.py +++ b/poetry/console/commands/init.py @@ -9,6 +9,7 @@ from pathlib import Path from typing import Dict from typing import List +from typing import Optional from typing import Tuple from typing import Union @@ -56,12 +57,12 @@ class InitCommand(Command): The init command creates a basic pyproject.toml file in the current directory. """ - def __init__(self): + def __init__(self): # type: () -> None super(InitCommand, self).__init__() self._pool = None - def handle(self): + def handle(self): # type: () -> int from pathlib import Path from poetry.core.vcs.git import GitConfig @@ -227,7 +228,7 @@ def handle(self): def _determine_requirements( self, requires, allow_prereleases=False, source=None - ): # type: (List[str], bool) -> List[Dict[str, str]] + ): # type: (List[str], bool, Optional[str]) -> List[Dict[str, Union[str, List[str]]]] if not requires: requires = [] @@ -354,7 +355,7 @@ def _determine_requirements( def _find_best_version_for_package( self, name, required_version=None, allow_prereleases=False, source=None - ): # type: (...) -> Tuple[str, str] + ): # type: (str, Optional[str], bool, Optional[str]) -> Tuple[str, str] from poetry.version.version_selector import VersionSelector selector = VersionSelector(self._get_pool()) @@ -505,7 +506,7 @@ def _format_requirements( return requires - def _validate_author(self, author, default): + def _validate_author(self, author, default): # type: (str, str) -> Optional[str] from poetry.core.packages.package import AUTHOR_REGEX author = author or default @@ -522,7 +523,7 @@ def _validate_author(self, author, default): return author - def _validate_license(self, license): + def _validate_license(self, license): # type: (str) -> str from poetry.core.spdx import license_by_id if license: @@ -530,7 +531,7 @@ def _validate_license(self, license): return license - def _get_pool(self): + def _get_pool(self): # type: () -> "Pool" from poetry.repositories import Pool from poetry.repositories.pypi_repository import PyPiRepository diff --git a/poetry/console/commands/install.py b/poetry/console/commands/install.py index 6a9ef2cb41d..96e2dca0ad6 100644 --- a/poetry/console/commands/install.py +++ b/poetry/console/commands/install.py @@ -47,7 +47,7 @@ class InstallCommand(InstallerCommand): _loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"] - def handle(self): + def handle(self): # type: () -> int from poetry.core.masonry.utils.module import ModuleOrPackageNotFound from poetry.masonry.builders import EditableBuilder diff --git a/poetry/console/commands/installer_command.py b/poetry/console/commands/installer_command.py index 51647eff471..dd8ca1a8842 100644 --- a/poetry/console/commands/installer_command.py +++ b/poetry/console/commands/installer_command.py @@ -9,12 +9,12 @@ class InstallerCommand(EnvCommand): - def __init__(self): + def __init__(self): # type: () -> None self._installer = None # type: Optional[Installer] super(InstallerCommand, self).__init__() - def reset_poetry(self): + def reset_poetry(self): # type: () -> None super(InstallerCommand, self).reset_poetry() self._installer.set_package(self.poetry.package) diff --git a/poetry/console/commands/lock.py b/poetry/console/commands/lock.py index 4157c02c5cc..59beb1386e2 100644 --- a/poetry/console/commands/lock.py +++ b/poetry/console/commands/lock.py @@ -24,7 +24,7 @@ class LockCommand(InstallerCommand): loggers = ["poetry.repositories.pypi_repository"] - def handle(self): + def handle(self): # type: () -> int self._installer.use_executor( self.poetry.config.get("experimental.new-installer", False) ) diff --git a/poetry/console/commands/new.py b/poetry/console/commands/new.py index 4856ff69c96..8d709fb6c4a 100644 --- a/poetry/console/commands/new.py +++ b/poetry/console/commands/new.py @@ -19,7 +19,7 @@ class NewCommand(Command): option("src", None, "Use the src layout for the project."), ] - def handle(self): + def handle(self): # type: () -> None from pathlib import Path from poetry.core.semver import parse_constraint diff --git a/poetry/console/commands/publish.py b/poetry/console/commands/publish.py index 98d4165fd8d..cedbca85468 100644 --- a/poetry/console/commands/publish.py +++ b/poetry/console/commands/publish.py @@ -1,4 +1,5 @@ from pathlib import Path +from typing import Optional from cleo import option @@ -40,7 +41,7 @@ class PublishCommand(Command): loggers = ["poetry.masonry.publishing.publisher"] - def handle(self): + def handle(self): # type: () -> Optional[int] from poetry.publishing.publisher import Publisher publisher = Publisher(self.poetry, self.io) diff --git a/poetry/console/commands/remove.py b/poetry/console/commands/remove.py index d9a289cba7f..3fb42713ced 100644 --- a/poetry/console/commands/remove.py +++ b/poetry/console/commands/remove.py @@ -27,7 +27,7 @@ class RemoveCommand(InstallerCommand): loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"] - def handle(self): + def handle(self): # type: () -> int packages = self.argument("packages") is_dev = self.option("dev") diff --git a/poetry/console/commands/run.py b/poetry/console/commands/run.py index 46202c52c37..bafa5ce2cea 100644 --- a/poetry/console/commands/run.py +++ b/poetry/console/commands/run.py @@ -1,3 +1,6 @@ +from typing import Any +from typing import Union + from cleo import argument from .env_command import EnvCommand @@ -19,7 +22,7 @@ def __init__(self): # type: () -> None self.config.set_args_parser(RunArgsParser()) - def handle(self): + def handle(self): # type: () -> Any args = self.argument("args") script = args[0] scripts = self.poetry.local_config.get("scripts") @@ -29,7 +32,7 @@ def handle(self): return self.env.execute(*args) - def run_script(self, script, args): + def run_script(self, script, args): # type: (Union[str, dict], str) -> Any if isinstance(script, dict): script = script["callable"] diff --git a/poetry/console/commands/search.py b/poetry/console/commands/search.py index 299dee6a96a..3ca8ac8e94d 100644 --- a/poetry/console/commands/search.py +++ b/poetry/console/commands/search.py @@ -10,7 +10,7 @@ class SearchCommand(Command): arguments = [argument("tokens", "The tokens to search for.", multiple=True)] - def handle(self): + def handle(self): # type: () -> None from poetry.repositories.pypi_repository import PyPiRepository results = PyPiRepository().search(self.argument("tokens")) diff --git a/poetry/console/commands/self/self.py b/poetry/console/commands/self/self.py index 3e5cafa9180..29a0214c230 100644 --- a/poetry/console/commands/self/self.py +++ b/poetry/console/commands/self/self.py @@ -9,5 +9,5 @@ class SelfCommand(Command): commands = [SelfUpdateCommand()] - def handle(self): + def handle(self): # type: () -> int return self.call("help", self._config.name) diff --git a/poetry/console/commands/self/update.py b/poetry/console/commands/self/update.py index 7d477ddce16..279de274188 100644 --- a/poetry/console/commands/self/update.py +++ b/poetry/console/commands/self/update.py @@ -11,6 +11,8 @@ from functools import cmp_to_key from gzip import GzipFile +from typing import TYPE_CHECKING +from typing import Any from cleo import argument from cleo import option @@ -20,6 +22,12 @@ from ..command import Command +if TYPE_CHECKING: + from poetry.core.packages import Package # noqa + from poetry.core.semver import Version + from poetry.utils._compat import Path + + try: from urllib.error import HTTPError from urllib.request import urlopen @@ -61,24 +69,24 @@ class SelfUpdateCommand(Command): BASE_URL = REPOSITORY_URL + "/releases/download" @property - def home(self): + def home(self): # type: () -> Path from pathlib import Path return Path(os.environ.get("POETRY_HOME", "~/.poetry")).expanduser() @property - def bin(self): + def bin(self): # type: () -> Path return self.home / "bin" @property - def lib(self): + def lib(self): # type: () -> Path return self.home / "lib" @property - def lib_backup(self): + def lib_backup(self): # type: () -> Path return self.home / "lib-backup" - def handle(self): + def handle(self): # type: () -> None from poetry.__version__ import __version__ from poetry.core.semver import Version from poetry.repositories.pypi_repository import PyPiRepository @@ -129,7 +137,7 @@ def handle(self): self.update(release) - def update(self, release): + def update(self, release): # type: ("Package") -> None version = release.version self.line("Updating to {}".format(version)) @@ -165,7 +173,7 @@ def update(self, release): ) ) - def _update(self, version): + def _update(self, version): # type: ("Version") -> None from poetry.utils.helpers import temporary_directory release_name = self._get_release_name(version) @@ -235,10 +243,10 @@ def _update(self, version): finally: gz.close() - def process(self, *args): + def process(self, *args): # type: (*Any) -> str return subprocess.check_output(list(args), stderr=subprocess.STDOUT) - def _check_recommended_installation(self): + def _check_recommended_installation(self): # type: () -> None from pathlib import Path current = Path(__file__) @@ -250,14 +258,14 @@ def _check_recommended_installation(self): "Cannot update automatically." ) - def _get_release_name(self, version): + def _get_release_name(self, version): # type: ("Version") -> str platform = sys.platform if platform == "linux2": platform = "linux" return "poetry-{}-{}".format(version, platform) - def make_bin(self): + def make_bin(self): # type: () -> None from poetry.utils._compat import WINDOWS self.bin.mkdir(0o755, parents=True, exist_ok=True) @@ -286,7 +294,7 @@ def make_bin(self): st = os.stat(str(self.bin.joinpath("poetry"))) os.chmod(str(self.bin.joinpath("poetry")), st.st_mode | stat.S_IEXEC) - def _which_python(self): + def _which_python(self): # type: () -> str """ Decides which python executable we'll embed in the launcher script. """ diff --git a/poetry/console/commands/shell.py b/poetry/console/commands/shell.py index 033ab207e31..35269d38d3d 100644 --- a/poetry/console/commands/shell.py +++ b/poetry/console/commands/shell.py @@ -16,7 +16,7 @@ class ShellCommand(EnvCommand): If one doesn't exist yet, it will be created. """ - def handle(self): + def handle(self): # type: () -> None from poetry.utils.shell import Shell # Check if it's already activated or doesn't exist and won't be created diff --git a/poetry/console/commands/show.py b/poetry/console/commands/show.py index 86be1ae7a36..545717be8ee 100644 --- a/poetry/console/commands/show.py +++ b/poetry/console/commands/show.py @@ -1,10 +1,24 @@ # -*- coding: utf-8 -*- +from typing import TYPE_CHECKING +from typing import List +from typing import Optional +from typing import Union + from cleo import argument from cleo import option from .env_command import EnvCommand +if TYPE_CHECKING: + from clikit.api.io import IO # noqa + + from poetry.core.packages import Dependency # noqa + from poetry.core.packages import Package # noqa + from poetry.repositories import Repository + from poetry.repositories.installed_repository import InstalledRepository + + class ShowCommand(EnvCommand): name = "show" @@ -32,7 +46,7 @@ class ShowCommand(EnvCommand): colors = ["cyan", "yellow", "green", "magenta", "blue"] - def handle(self): + def handle(self): # type: () -> Optional[int] from clikit.utils.terminal import Terminal from poetry.io.null_io import NullIO @@ -257,7 +271,9 @@ def handle(self): self.line(line) - def display_package_tree(self, io, package, installed_repo): + def display_package_tree( + self, io, package, installed_repo + ): # type: ("IO", "Package", "Repository") -> None io.write("{}".format(package.pretty_name)) description = "" if package.description: @@ -294,13 +310,13 @@ def display_package_tree(self, io, package, installed_repo): def _display_tree( self, - io, - dependency, - installed_repo, - packages_in_tree, - previous_tree_bar="├", - level=1, - ): + io, # type: "IO" + dependency, # type: "Dependency" + installed_repo, # type: "Repository" + packages_in_tree, # type: List[str] + previous_tree_bar="├", # type: str + level=1, # type: int + ): # type: (...) -> None previous_tree_bar = previous_tree_bar.replace("├", "│") dependencies = [] @@ -345,7 +361,7 @@ def _display_tree( io, dependency, installed_repo, current_tree, tree_bar, level + 1 ) - def _write_tree_line(self, io, line): + def _write_tree_line(self, io, line): # type: ("IO", str) -> None if not io.output.supports_ansi(): line = line.replace("└", "`-") line = line.replace("├", "|-") @@ -354,7 +370,7 @@ def _write_tree_line(self, io, line): io.write_line(line) - def init_styles(self, io): + def init_styles(self, io): # type: ("IO") -> None from clikit.api.formatter import Style for color in self.colors: @@ -362,7 +378,9 @@ def init_styles(self, io): io.output.formatter.add_style(style) io.error_output.formatter.add_style(style) - def find_latest_package(self, package, include_dev): + def find_latest_package( + self, package, include_dev + ): # type: ("Package", bool) -> Union["Package", bool] from clikit.io import NullIO from poetry.puzzle.provider import Provider @@ -390,7 +408,7 @@ def find_latest_package(self, package, include_dev): return selector.find_best_candidate(name, ">={}".format(package.pretty_version)) - def get_update_status(self, latest, package): + def get_update_status(self, latest, package): # type: ("Package", "Package") -> str from poetry.core.semver import parse_constraint if latest.full_pretty_version == package.full_pretty_version: @@ -405,7 +423,9 @@ def get_update_status(self, latest, package): # it needs an upgrade but has potential BC breaks so is not urgent return "update-possible" - def get_installed_status(self, locked, installed_repo): + def get_installed_status( + self, locked, installed_repo + ): # type: ("Package", "InstalledRepository") -> str for package in installed_repo.packages: if locked.name == package.name: return "installed" diff --git a/poetry/console/commands/update.py b/poetry/console/commands/update.py index 9e18feb78b6..77c0adf52cc 100644 --- a/poetry/console/commands/update.py +++ b/poetry/console/commands/update.py @@ -27,7 +27,7 @@ class UpdateCommand(InstallerCommand): loggers = ["poetry.repositories.pypi_repository"] - def handle(self): + def handle(self): # type: () -> int packages = self.argument("packages") self._installer.use_executor( diff --git a/poetry/console/commands/version.py b/poetry/console/commands/version.py index 5ac5c666afb..403c357390a 100644 --- a/poetry/console/commands/version.py +++ b/poetry/console/commands/version.py @@ -40,7 +40,7 @@ class VersionCommand(Command): "prerelease", } - def handle(self): + def handle(self): # type: () -> None version = self.argument("version") if version: @@ -72,7 +72,7 @@ def handle(self): ) ) - def increment_version(self, version, rule): + def increment_version(self, version, rule): # type: (str, str) -> "Version" from poetry.core.semver import Version try: diff --git a/poetry/console/config/application_config.py b/poetry/console/config/application_config.py index 492a2137259..563e6b55db0 100644 --- a/poetry/console/config/application_config.py +++ b/poetry/console/config/application_config.py @@ -34,7 +34,7 @@ class ApplicationConfig(BaseApplicationConfig): - def configure(self): + def configure(self): # type: () -> None super(ApplicationConfig, self).configure() self.add_style(Style("c1").fg("cyan")) diff --git a/poetry/console/logging/io_formatter.py b/poetry/console/logging/io_formatter.py index 9ff57fec761..68d15691edd 100644 --- a/poetry/console/logging/io_formatter.py +++ b/poetry/console/logging/io_formatter.py @@ -1,8 +1,14 @@ import logging +from typing import TYPE_CHECKING + from .formatters import FORMATTERS +if TYPE_CHECKING: + from logging import LogRecord # noqa + + class IOFormatter(logging.Formatter): _colors = { @@ -12,7 +18,7 @@ class IOFormatter(logging.Formatter): "info": "fg=blue", } - def format(self, record): + def format(self, record): # type: ("LogRecord") -> str if not record.exc_info: level = record.levelname.lower() msg = record.msg diff --git a/poetry/console/logging/io_handler.py b/poetry/console/logging/io_handler.py index 14fd176986c..03d9607c8e5 100644 --- a/poetry/console/logging/io_handler.py +++ b/poetry/console/logging/io_handler.py @@ -1,13 +1,21 @@ import logging +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from logging import LogRecord # noqa + + from clikit.api.io import IO # noqa + class IOHandler(logging.Handler): - def __init__(self, io): + def __init__(self, io): # type: ("IO") -> None self._io = io super(IOHandler, self).__init__() - def emit(self, record): + def emit(self, record): # type: ("LogRecord") -> None try: msg = self.format(record) level = record.levelname.lower() diff --git a/poetry/factory.py b/poetry/factory.py index b38f6da25ae..0e3ef0f6046 100644 --- a/poetry/factory.py +++ b/poetry/factory.py @@ -137,7 +137,7 @@ def create_config(cls, io=None): # type: (Optional[IO]) -> Config def create_legacy_repository( self, source, auth_config - ): # type: (Dict[str, str], Config) -> LegacyRepository + ): # type: (Dict[str, str], Config) -> "LegacyRepository" from .repositories.legacy_repository import LegacyRepository from .utils.helpers import get_cert from .utils.helpers import get_client_cert diff --git a/poetry/inspection/info.py b/poetry/inspection/info.py index 94ac097aeac..495a62ca3c0 100644 --- a/poetry/inspection/info.py +++ b/poetry/inspection/info.py @@ -120,7 +120,7 @@ def load( return cls(cache_version=cache_version, **data) @classmethod - def _log(cls, msg, level="info"): + def _log(cls, msg, level="info"): # type: (str, str) -> None """Internal helper method to log information.""" getattr(logger, level)("{}: {}".format(cls.__name__, msg)) @@ -436,7 +436,7 @@ def _get_poetry_package(path): # type: (Path) -> Optional[ProjectPackage] return Factory().create_poetry(path).package @classmethod - def _pep517_metadata(cls, path): # type (Path) -> PackageInfo + def _pep517_metadata(cls, path): # type: (Path) -> PackageInfo """ Helper method to use PEP-517 library to build and read package metadata. diff --git a/poetry/installation/base_installer.py b/poetry/installation/base_installer.py index 1e068d076cd..4f600e880d4 100644 --- a/poetry/installation/base_installer.py +++ b/poetry/installation/base_installer.py @@ -1,9 +1,16 @@ +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from poetry.core.packages import Package # noqa + + class BaseInstaller: - def install(self, package): + def install(self, package): # type: ("Package") -> None raise NotImplementedError - def update(self, source, target): + def update(self, source, target): # type: ("Package", "Package") -> None raise NotImplementedError - def remove(self, package): + def remove(self, package): # type: ("Package") -> None raise NotImplementedError diff --git a/poetry/installation/chooser.py b/poetry/installation/chooser.py index 6d9e92e0b1f..1762f2ce546 100644 --- a/poetry/installation/chooser.py +++ b/poetry/installation/chooser.py @@ -1,6 +1,7 @@ import re from typing import List +from typing import Optional from typing import Tuple from packaging.tags import Tag @@ -34,12 +35,12 @@ def __init__(self, filename): # type: (str) -> None 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): + def get_minimum_supported_index(self, tags): # type: (List[Tag]) -> Optional[int] 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): + def is_supported_by_environment(self, env): # type: (Env) -> bool return bool(set(env.supported_tags).intersection(self.tags)) diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index 5523ed2813f..8123c2d6956 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -9,6 +9,10 @@ from concurrent.futures import wait from pathlib import Path from subprocess import CalledProcessError +from typing import TYPE_CHECKING +from typing import Any +from typing import List +from typing import Union from poetry.core.packages.file_dependency import FileDependency from poetry.core.packages.utils.link import Link @@ -27,8 +31,20 @@ from .operations.update import Update +if TYPE_CHECKING: + from clikit.api.io import IO # noqa + + from poetry.config.config import Config # noqa + from poetry.repositories import Pool # noqa + from poetry.utils.env import Env # noqa + + from .operations import OperationTypes # noqa + + class Executor(object): - def __init__(self, env, pool, config, io, parallel=None): + def __init__( + self, env, pool, config, io, parallel=None + ): # type: ("Env", "Pool", "Config", "IO", bool) -> None self._env = env self._io = io self._dry_run = False @@ -77,22 +93,22 @@ def removals_count(self): # type: () -> int def supports_fancy_output(self): # type: () -> bool return self._io.supports_ansi() and not self._dry_run - def disable(self): + def disable(self): # type: () -> "Executor" self._enabled = False return self - def dry_run(self, dry_run=True): + def dry_run(self, dry_run=True): # type: (bool) -> Executor self._dry_run = dry_run return self - def verbose(self, verbose=True): + def verbose(self, verbose=True): # type: (bool) -> Executor self._verbose = verbose return self - def execute(self, operations): # type: (Operation) -> int + def execute(self, operations): # type: (List["OperationTypes"]) -> int self._total_operations = len(operations) for job_type in self._executed: self._executed[job_type] = 0 @@ -145,7 +161,7 @@ def execute(self, operations): # type: (Operation) -> int return 1 if self._shutdown else 0 - def _write(self, operation, line): + def _write(self, operation, line): # type: ("OperationTypes", str) -> None if not self.supports_fancy_output() or not self._should_write_operation( operation ): @@ -163,7 +179,7 @@ def _write(self, operation, line): section.output.clear() section.write(line) - def _execute_operation(self, operation): + def _execute_operation(self, operation): # type: ("OperationTypes") -> None try: if self.supports_fancy_output(): if id(operation) not in self._sections: @@ -240,7 +256,7 @@ def _execute_operation(self, operation): with self._lock: self._shutdown = True - def _do_execute_operation(self, operation): + def _do_execute_operation(self, operation): # type: ("OperationTypes") -> int method = operation.job_type operation_message = self.get_operation_message(operation) @@ -283,7 +299,9 @@ def _do_execute_operation(self, operation): return result - def _increment_operations_count(self, operation, executed): + def _increment_operations_count( + self, operation, executed + ): # type: ("OperationTypes", bool) -> None with self._lock: if executed: self._executed_operations += 1 @@ -291,7 +309,7 @@ def _increment_operations_count(self, operation, executed): else: self._skipped[operation.job_type] += 1 - def run_pip(self, *args, **kwargs): # type: (...) -> int + def run_pip(self, *args, **kwargs): # type: (*Any, **Any) -> int try: self._env.run_pip(*args, **kwargs) except EnvCommandError as e: @@ -306,7 +324,9 @@ def run_pip(self, *args, **kwargs): # type: (...) -> int return 0 - def get_operation_message(self, operation, done=False, error=False, warning=False): + def get_operation_message( + self, operation, done=False, error=False, warning=False + ): # type: ("OperationTypes", bool, bool, bool) -> str base_tag = "fg=default" operation_color = "c2" source_operation_color = "c2" @@ -360,7 +380,7 @@ def get_operation_message(self, operation, done=False, error=False, warning=Fals return "" - def _display_summary(self, operations): + def _display_summary(self, operations): # type: (List["OperationTypes"]) -> None installs = 0 updates = 0 uninstalls = 0 @@ -403,13 +423,13 @@ def _display_summary(self, operations): ) self._io.write_line("") - def _execute_install(self, operation): # type: (Install) -> None + def _execute_install(self, operation): # type: (Union[Install, Update]) -> int return self._install(operation) - def _execute_update(self, operation): # type: (Update) -> None + def _execute_update(self, operation): # type: (Union[Install, Update]) -> int return self._update(operation) - def _execute_uninstall(self, operation): # type: (Uninstall) -> None + def _execute_uninstall(self, operation): # type: (Uninstall) -> int message = " • {message}: Removing...".format( message=self.get_operation_message(operation), ) @@ -417,7 +437,7 @@ def _execute_uninstall(self, operation): # type: (Uninstall) -> None return self._remove(operation) - def _install(self, operation): + def _install(self, operation): # type: (Union[Install, Update]) -> int package = operation.package if package.source_type == "directory": return self._install_directory(operation) @@ -444,10 +464,10 @@ def _install(self, operation): return self.run_pip(*args) - def _update(self, operation): + def _update(self, operation): # type: (Union[Install, Update]) -> int return self._install(operation) - def _remove(self, operation): + def _remove(self, operation): # type: (Uninstall) -> int package = operation.package # If we have a VCS package, remove its source directory @@ -464,7 +484,7 @@ def _remove(self, operation): raise - def _prepare_file(self, operation): + def _prepare_file(self, operation): # type: (Union[Install, Update]) -> Path package = operation.package message = " • {message}: Preparing...".format( @@ -480,7 +500,7 @@ def _prepare_file(self, operation): return archive - def _install_directory(self, operation): + def _install_directory(self, operation): # type: (Union[Install, Update]) -> int from poetry.factory import Factory package = operation.package @@ -544,7 +564,7 @@ def _install_directory(self, operation): return self.run_pip(*args) - def _install_git(self, operation): + def _install_git(self, operation): # type: (Union[Install, Update]) -> int from poetry.core.vcs import Git package = operation.package @@ -570,12 +590,14 @@ def _install_git(self, operation): return self._install_directory(operation) - def _download(self, operation): # type: (Operation) -> Path + def _download(self, operation): # type: (Union[Install, Update]) -> Link link = self._chooser.choose_for(operation.package) return self._download_link(operation, link) - def _download_link(self, operation, link): + def _download_link( + self, operation, link + ): # type: (Union[Install, Update], Link) -> Link package = operation.package archive = self._chef.get_cached_archive_for_link(link) @@ -607,7 +629,9 @@ def _download_link(self, operation, link): return archive - def _download_archive(self, operation, link): # type: (Operation, Link) -> Path + def _download_archive( + self, operation, link + ): # type: (Union[Install, Update], Link) -> Path response = self._authenticator.request( "get", link.url, stream=True, io=self._sections.get(id(operation), self._io) ) diff --git a/poetry/installation/installer.py b/poetry/installation/installer.py index 2164d39f4b1..f7eea2cdcd0 100644 --- a/poetry/installation/installer.py +++ b/poetry/installation/installer.py @@ -1,3 +1,5 @@ +from typing import TYPE_CHECKING +from typing import Iterable from typing import List from typing import Optional from typing import Union @@ -23,11 +25,17 @@ from .pip_installer import PipInstaller +if TYPE_CHECKING: + from poetry.utils.env import Env # noqa + + from .operations import OperationTypes # noqa + + class Installer: def __init__( self, io, # type: IO - env, + env, # type: "Env" package, # type: ProjectPackage locker, # type: Locker pool, # type: Pool @@ -67,11 +75,11 @@ def __init__( self._installed_repository = installed @property - def executor(self): + def executor(self): # type: () -> Executor return self._executor @property - def installer(self): + def installer(self): # type: () -> BaseInstaller return self._installer def set_package(self, package): # type: (ProjectPackage) -> Installer @@ -84,7 +92,7 @@ def set_locker(self, locker): # type: (Locker) -> Installer return self - def run(self): + def run(self): # type: () -> int # Check if refresh if not self._update and self._lock and self._locker.is_locked(): return self._do_refresh() @@ -162,7 +170,7 @@ def execute_operations(self, execute=True): # type: (bool) -> Installer return self - def whitelist(self, packages): # type: (dict) -> Installer + def whitelist(self, packages): # type: (Iterable[str]) -> Installer self._whitelist = [canonicalize_name(p) for p in packages] return self @@ -177,7 +185,7 @@ def use_executor(self, use_executor=True): # type: (bool) -> Installer return self - def _do_refresh(self): + def _do_refresh(self): # type: () -> int from poetry.puzzle import Solver # Checking extras @@ -203,7 +211,7 @@ def _do_refresh(self): return 0 - def _do_install(self, local_repo): + def _do_install(self, local_repo): # type: (Repository) -> int from poetry.puzzle import Solver locked_repository = Repository() @@ -323,7 +331,7 @@ def _write_lock_file(self, repo, force=True): # type: (Repository, bool) -> Non self._io.write_line("") self._io.write_line("Writing lock file") - def _execute(self, operations): + def _execute(self, operations): # type: (List["OperationTypes"]) -> int if self._use_executor: return self._executor.execute(operations) @@ -459,7 +467,9 @@ def _execute_uninstall(self, operation): # type: (Uninstall) -> None self._installer.remove(operation.package) - def _populate_local_repo(self, local_repo, ops): + def _populate_local_repo( + self, local_repo, ops + ): # type: (Repository, List[Operation]) -> None for op in ops: if isinstance(op, Uninstall): continue @@ -472,8 +482,8 @@ def _populate_local_repo(self, local_repo, ops): local_repo.add_package(package) def _get_operations_from_lock( - self, locked_repository # type: Repository - ): # type: (...) -> List[Operation] + self, locked_repository + ): # type: (Repository) -> List[Operation] installed_repo = self._installed_repository ops = [] diff --git a/poetry/installation/noop_installer.py b/poetry/installation/noop_installer.py index 0f0c6cda007..8b39543671f 100644 --- a/poetry/installation/noop_installer.py +++ b/poetry/installation/noop_installer.py @@ -1,29 +1,36 @@ +from typing import TYPE_CHECKING +from typing import List + from .base_installer import BaseInstaller +if TYPE_CHECKING: + from poetry.core.packages import Package # noqa + + class NoopInstaller(BaseInstaller): - def __init__(self): + def __init__(self): # type: () -> None self._installs = [] self._updates = [] self._removals = [] @property - def installs(self): + def installs(self): # type: () -> List["Package"] return self._installs @property - def updates(self): + def updates(self): # type: () -> List["Package"] return self._updates @property - def removals(self): + def removals(self): # type: () -> List["Package"] return self._removals - def install(self, package): + def install(self, package): # type: ("Package") -> None self._installs.append(package) - def update(self, source, target): + def update(self, source, target): # type: ("Package", "Package") -> None self._updates.append((source, target)) - def remove(self, package): + def remove(self, package): # type: ("Package") -> None self._removals.append(package) diff --git a/poetry/installation/operations/__init__.py b/poetry/installation/operations/__init__.py index 42573c10e8e..d7b27fe2a20 100644 --- a/poetry/installation/operations/__init__.py +++ b/poetry/installation/operations/__init__.py @@ -1,3 +1,8 @@ +from typing import Union + from .install import Install from .uninstall import Uninstall from .update import Update + + +OperationTypes = Union[Install, Uninstall, Update] diff --git a/poetry/installation/operations/install.py b/poetry/installation/operations/install.py index 48097c7c6ce..381f8f48f30 100644 --- a/poetry/installation/operations/install.py +++ b/poetry/installation/operations/install.py @@ -1,26 +1,35 @@ +from typing import TYPE_CHECKING +from typing import Optional + from .operation import Operation +if TYPE_CHECKING: + from poetry.core.packages import Package # noqa + + class Install(Operation): - def __init__(self, package, reason=None, priority=0): + def __init__( + self, package, reason=None, priority=0 + ): # type: ("Package", Optional[str], int) -> None super(Install, self).__init__(reason, priority=priority) self._package = package @property - def package(self): + def package(self): # type: () -> "Package" return self._package @property - def job_type(self): + def job_type(self): # type: () -> str return "install" - def __str__(self): + def __str__(self): # type: () -> str return "Installing {} ({})".format( self.package.pretty_name, self.format_version(self.package) ) - def __repr__(self): + def __repr__(self): # type: () -> str return "".format( self.package.pretty_name, self.format_version(self.package) ) diff --git a/poetry/installation/operations/operation.py b/poetry/installation/operations/operation.py index 0c72cc8c044..847510c80f8 100644 --- a/poetry/installation/operations/operation.py +++ b/poetry/installation/operations/operation.py @@ -1,12 +1,15 @@ # -*- coding: utf-8 -*- -from typing import Union +from typing import TYPE_CHECKING +from typing import Optional + + +if TYPE_CHECKING: + from poetry.core.packages import Package # noqa class Operation(object): - def __init__( - self, reason=None, priority=0 - ): # type: (Union[str, None], int) -> None + def __init__(self, reason=None, priority=0): # type: (Optional[str], int) -> None self._reason = reason self._skipped = False @@ -26,7 +29,7 @@ def skipped(self): # type: () -> bool return self._skipped @property - def skip_reason(self): # type: () -> Union[str, None] + def skip_reason(self): # type: () -> Optional[str] return self._skip_reason @property @@ -34,10 +37,10 @@ def priority(self): # type: () -> int return self._priority @property - def package(self): + def package(self): # type: () -> "Package" raise NotImplementedError() - def format_version(self, package): # type: (...) -> str + def format_version(self, package): # type: ("Package") -> str return package.full_pretty_version def skip(self, reason): # type: (str) -> Operation diff --git a/poetry/installation/operations/uninstall.py b/poetry/installation/operations/uninstall.py index b7e40bc606e..32c6e4e3583 100644 --- a/poetry/installation/operations/uninstall.py +++ b/poetry/installation/operations/uninstall.py @@ -1,26 +1,35 @@ +from typing import TYPE_CHECKING +from typing import Optional + from .operation import Operation +if TYPE_CHECKING: + from poetry.core.packages import Package # noqa + + class Uninstall(Operation): - def __init__(self, package, reason=None, priority=float("inf")): + def __init__( + self, package, reason=None, priority=float("inf") + ): # type: ("Package", Optional[str], int) -> None super(Uninstall, self).__init__(reason, priority=priority) self._package = package @property - def package(self): + def package(self): # type: () -> "Package" return self._package @property - def job_type(self): + def job_type(self): # type: () -> str return "uninstall" - def __str__(self): + def __str__(self): # type: () -> str return "Uninstalling {} ({})".format( self.package.pretty_name, self.format_version(self._package) ) - def __repr__(self): + def __repr__(self): # type: () -> str return "".format( self.package.pretty_name, self.format_version(self.package) ) diff --git a/poetry/installation/operations/update.py b/poetry/installation/operations/update.py index 87803fd7a23..02cd86ccfb8 100644 --- a/poetry/installation/operations/update.py +++ b/poetry/installation/operations/update.py @@ -1,30 +1,39 @@ +from typing import TYPE_CHECKING +from typing import Optional + from .operation import Operation +if TYPE_CHECKING: + from poetry.core.packages import Package # noqa + + class Update(Operation): - def __init__(self, initial, target, reason=None, priority=0): + def __init__( + self, initial, target, reason=None, priority=0 + ): # type: ("Package", "Package", Optional[str], int) -> None self._initial_package = initial self._target_package = target super(Update, self).__init__(reason, priority=priority) @property - def initial_package(self): + def initial_package(self): # type: () -> "Package" return self._initial_package @property - def target_package(self): + def target_package(self): # type: () -> "Package" return self._target_package @property - def package(self): + def package(self): # type: () -> "Package" return self._target_package @property - def job_type(self): + def job_type(self): # type: () -> str return "update" - def __str__(self): + def __str__(self): # type: () -> str return "Updating {} ({}) to {} ({})".format( self.initial_package.pretty_name, self.format_version(self.initial_package), @@ -32,7 +41,7 @@ def __str__(self): self.format_version(self.target_package), ) - def __repr__(self): + def __repr__(self): # type: () -> str return "".format( self.initial_package.pretty_name, self.format_version(self.initial_package), diff --git a/poetry/installation/pip_installer.py b/poetry/installation/pip_installer.py index df1249737a1..e4a89349d0a 100644 --- a/poetry/installation/pip_installer.py +++ b/poetry/installation/pip_installer.py @@ -3,6 +3,9 @@ import urllib.parse from subprocess import CalledProcessError +from typing import TYPE_CHECKING +from typing import Any +from typing import Union from clikit.api.io import IO @@ -15,13 +18,17 @@ from .base_installer import BaseInstaller +if TYPE_CHECKING: + from poetry.core.packages import Package # noqa + + class PipInstaller(BaseInstaller): def __init__(self, env, io, pool): # type: (Env, IO, Pool) -> None self._env = env self._io = io self._pool = pool - def install(self, package, update=False): + def install(self, package, update=False): # type: ("Package", bool) -> None if package.source_type == "directory": self.install_directory(package) @@ -90,7 +97,7 @@ def install(self, package, update=False): self.run(*args) - def update(self, package, target): + def update(self, package, target): # type: ("Package", "Package") -> None if package.source_type != target.source_type: # If the source type has changed, we remove the current # package to avoid perpetual updates in some cases @@ -98,7 +105,7 @@ def update(self, package, target): self.install(target, update=True) - def remove(self, package): + def remove(self, package): # type: ("Package") -> None try: self.run("uninstall", package.name, "-y") except CalledProcessError as e: @@ -120,10 +127,10 @@ def remove(self, package): if src_dir.exists(): safe_rmtree(str(src_dir)) - def run(self, *args, **kwargs): # type: (...) -> str + def run(self, *args, **kwargs): # type: (*Any,**Any) -> str return self._env.run_pip(*args, **kwargs) - def requirement(self, package, formatted=False): + def requirement(self, package, formatted=False): # type: ("Package", bool) -> str if formatted and not package.source_type: req = "{}=={}".format(package.name, package.version) for f in package.files: @@ -164,7 +171,7 @@ def requirement(self, package, formatted=False): return "{}=={}".format(package.name, package.version) - def create_temporary_requirement(self, package): + def create_temporary_requirement(self, package): # type: ("Package") -> str fd, name = tempfile.mkstemp( "reqs.txt", "{}-{}".format(package.name, package.version) ) @@ -176,7 +183,7 @@ def create_temporary_requirement(self, package): return name - def install_directory(self, package): + def install_directory(self, package): # type: ("Package") -> Union[str, int] from poetry.factory import Factory from poetry.io.null_io import NullIO @@ -233,7 +240,7 @@ def install_directory(self, package): return self.run(*args) - def install_git(self, package): + def install_git(self, package): # type: ("Package") -> None from poetry.core.packages import Package from poetry.core.vcs import Git diff --git a/poetry/io/null_io.py b/poetry/io/null_io.py index d81cd595527..786b188a4e2 100644 --- a/poetry/io/null_io.py +++ b/poetry/io/null_io.py @@ -1,3 +1,5 @@ +from typing import Any + from cleo.io.io_mixin import IOMixin from clikit.io import NullIO as BaseNullIO @@ -7,5 +9,5 @@ class NullIO(IOMixin, BaseNullIO): A wrapper around CliKit's NullIO. """ - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs): # type: (*Any, **Any) -> None super(NullIO, self).__init__(*args, **kwargs) diff --git a/poetry/layouts/layout.py b/poetry/layouts/layout.py index 8a74060f06e..af5b48ad746 100644 --- a/poetry/layouts/layout.py +++ b/poetry/layouts/layout.py @@ -1,4 +1,5 @@ from typing import TYPE_CHECKING +from typing import Dict from typing import Optional from tomlkit import dumps @@ -9,7 +10,8 @@ if TYPE_CHECKING: - from poetry.core.pyproject.toml import PyProjectTOML + from poetry.core.pyproject.toml import PyProjectTOML # noqa + from poetry.utils._compat import Path # noqa TESTS_DEFAULT = u"""from {package_name} import __version__ @@ -45,21 +47,21 @@ def test_version(): """ BUILD_SYSTEM_MIN_VERSION = "1.0.0" -BUILD_SYSTEM_MAX_VERSION = None +BUILD_SYSTEM_MAX_VERSION = None # type: Optional[str] class Layout(object): def __init__( self, - project, - version="0.1.0", - description="", - readme_format="md", - author=None, - license=None, - python="*", - dependencies=None, - dev_dependencies=None, + project, # type: str + version="0.1.0", # type: str + description="", # type: str + readme_format="md", # type: str + author=None, # type: Optional[str] + license=None, # type: Optional[str] + python="*", # type: str + dependencies=None, # type: Optional[Dict[str, str]] + dev_dependencies=None, # type: Optional[Dict[str, str]] ): self._project = project self._package_name = module_name(project) @@ -76,7 +78,7 @@ def __init__( self._author = author - def create(self, path, with_tests=True): + def create(self, path, with_tests=True): # type: (Path, bool) -> None path.mkdir(parents=True, exist_ok=True) self._create_default(path) @@ -129,10 +131,10 @@ def generate_poetry_content( return content - def _create_default(self, path, src=True): + def _create_default(self, path, src=True): # type: (Path, bool) -> None raise NotImplementedError() - def _create_readme(self, path): + def _create_readme(self, path): # type: (Path) -> None if self._readme_format == "rst": readme_file = path / "README.rst" else: @@ -140,7 +142,7 @@ def _create_readme(self, path): readme_file.touch() - def _create_tests(self, path): + def _create_tests(self, path): # type: (Path) -> None tests = path / "tests" tests_init = tests / "__init__.py" tests_default = tests / "test_{}.py".format(self._package_name) @@ -155,7 +157,7 @@ def _create_tests(self, path): ) ) - def _write_poetry(self, path): + def _write_poetry(self, path): # type: ("Path") -> None content = self.generate_poetry_content() poetry = path / "pyproject.toml" diff --git a/poetry/layouts/src.py b/poetry/layouts/src.py index 06db7a71f92..a703ed2f2c2 100644 --- a/poetry/layouts/src.py +++ b/poetry/layouts/src.py @@ -1,14 +1,19 @@ # -*- coding: utf-8 -*- +from typing import TYPE_CHECKING + from .layout import Layout +if TYPE_CHECKING: + from poetry.utils._compat import Path # noqa + DEFAULT = u"""__version__ = '{version}' """ class SrcLayout(Layout): - def _create_default(self, path): + def _create_default(self, path): # type: ("Path") -> None package_path = path / "src" / self._package_name package_init = package_path / "__init__.py" diff --git a/poetry/layouts/standard.py b/poetry/layouts/standard.py index eca4c435c40..4372d71d6b1 100644 --- a/poetry/layouts/standard.py +++ b/poetry/layouts/standard.py @@ -1,14 +1,18 @@ # -*- coding: utf-8 -*- +from typing import TYPE_CHECKING + from .layout import Layout +if TYPE_CHECKING: + from poetry.utils._compat import Path # noqa DEFAULT = u"""__version__ = '{version}' """ class StandardLayout(Layout): - def _create_default(self, path): + def _create_default(self, path): # type: ("Path") -> None package_path = path / self._package_name package_init = package_path / "__init__.py" diff --git a/poetry/masonry/builders/editable.py b/poetry/masonry/builders/editable.py index dc4be9af4cc..4f4c059ce10 100644 --- a/poetry/masonry/builders/editable.py +++ b/poetry/masonry/builders/editable.py @@ -6,6 +6,8 @@ from base64 import urlsafe_b64encode from pathlib import Path +from typing import TYPE_CHECKING +from typing import List from poetry.core.masonry.builders.builder import Builder from poetry.core.masonry.builders.sdist import SdistBuilder @@ -16,6 +18,12 @@ from poetry.utils.helpers import is_dir_writable +if TYPE_CHECKING: + from clikit.api.io import IO # noqa + + from poetry.core.poetry import Poetry # noqa + from poetry.utils.env import Env # noqa + SCRIPT_TEMPLATE = """\ #!{python} from {module} import {callable_holder} @@ -30,13 +38,13 @@ class EditableBuilder(Builder): - def __init__(self, poetry, env, io): + def __init__(self, poetry, env, io): # type: ("Poetry", "Env", "IO") -> None super(EditableBuilder, self).__init__(poetry) self._env = env self._io = io - def build(self): + def build(self): # type: () -> None self._debug( " - Building package {} in editable mode".format( self._package.name @@ -58,11 +66,11 @@ def build(self): added_files += self._add_scripts() self._add_dist_info(added_files) - def _run_build_script(self, build_script): + def _run_build_script(self, build_script): # type: (Path) -> None self._debug(" - Executing build script: {}".format(build_script)) self._env.run("python", str(self._path.joinpath(build_script)), call=True) - def _setup_build(self): + def _setup_build(self): # type: () -> None builder = SdistBuilder(self._poetry) setup = self._path / "setup.py" has_setup = setup.exists() @@ -94,7 +102,7 @@ def _setup_build(self): if not has_setup: os.remove(str(setup)) - def _add_pth(self): + def _add_pth(self): # type: () -> List[Path] paths = set() for include in self._module.includes: if isinstance(include, PackageInclude) and ( @@ -126,7 +134,7 @@ def _add_pth(self): ) return [] - def _add_scripts(self): + def _add_scripts(self): # type: () -> List[Path] added = [] entry_points = self.convert_entry_points() @@ -185,7 +193,7 @@ def _add_scripts(self): return added - def _add_dist_info(self, added_files): + def _add_dist_info(self, added_files): # type: (List[Path]) -> None from poetry.core.masonry.builders.wheel import WheelBuilder added_files = added_files[:] @@ -239,7 +247,7 @@ def _add_dist_info(self, added_files): # RECORD itself is recorded with no hash or size f.write("{},,\n".format(dist_info.joinpath("RECORD"))) - def _get_file_hash(self, filepath): + def _get_file_hash(self, filepath): # type: (Path) -> str hashsum = hashlib.sha256() with filepath.open("rb") as src: while True: @@ -252,6 +260,6 @@ def _get_file_hash(self, filepath): return urlsafe_b64encode(hashsum.digest()).decode("ascii").rstrip("=") - def _debug(self, msg): + def _debug(self, msg): # type: (str) -> None if self._io.is_debug(): self._io.write_line(msg) diff --git a/poetry/mixology/__init__.py b/poetry/mixology/__init__.py index 50fbffb27cb..8dd76488438 100644 --- a/poetry/mixology/__init__.py +++ b/poetry/mixology/__init__.py @@ -1,7 +1,21 @@ +from typing import TYPE_CHECKING +from typing import Dict +from typing import List + from .version_solver import VersionSolver -def resolve_version(root, provider, locked=None, use_latest=None): +if TYPE_CHECKING: + from poetry.core.packages import DependencyPackage # noqa + from poetry.core.packages import ProjectPackage # noqa + from poetry.puzzle.provider import Provider # noqa + + from .result import SolverResult # noqa + + +def resolve_version( + root, provider, locked=None, use_latest=None +): # type: ("ProjectPackage", "Provider", Dict[str, "DependencyPackage"],List[str]) -> "SolverResult" solver = VersionSolver(root, provider, locked=locked, use_latest=use_latest) return solver.solve() diff --git a/poetry/mixology/assignment.py b/poetry/mixology/assignment.py index e288c5da520..21765a22fe5 100644 --- a/poetry/mixology/assignment.py +++ b/poetry/mixology/assignment.py @@ -1,15 +1,25 @@ +from typing import TYPE_CHECKING from typing import Any +from typing import Optional -from .incompatibility import Incompatibility from .term import Term +if TYPE_CHECKING: + from poetry.core.packages import Dependency # noqa + from poetry.core.packages import Package # noqa + + from .incompatibility import Incompatibility # noqa + + class Assignment(Term): """ A term in a PartialSolution that tracks some additional metadata. """ - def __init__(self, dependency, is_positive, decision_level, index, cause=None): + def __init__( + self, dependency, is_positive, decision_level, index, cause=None + ): # type: ("Dependency", bool, int, int, Optional["Incompatibility"]) -> None super(Assignment, self).__init__(dependency, is_positive) self._decision_level = decision_level @@ -25,19 +35,19 @@ def index(self): # type: () -> int return self._index @property - def cause(self): # type: () -> Incompatibility + def cause(self): # type: () -> "Incompatibility" return self._cause @classmethod def decision( cls, package, decision_level, index - ): # type: (Any, int, int) -> Assignment + ): # type: ("Package", int, int) -> Assignment return cls(package.to_dependency(), True, decision_level, index) @classmethod def derivation( cls, dependency, is_positive, cause, decision_level, index - ): # type: (Any, bool, Incompatibility, int, int) -> Assignment + ): # type: (Any, bool, "Incompatibility", int, int) -> "Assignment" return cls(dependency, is_positive, decision_level, index, cause) def is_decision(self): # type: () -> bool diff --git a/poetry/mixology/failure.py b/poetry/mixology/failure.py index afa332085b3..daffd12b94b 100644 --- a/poetry/mixology/failure.py +++ b/poetry/mixology/failure.py @@ -1,5 +1,6 @@ from typing import Dict from typing import List +from typing import Optional from typing import Tuple from poetry.core.semver import parse_constraint @@ -14,10 +15,10 @@ def __init__(self, incompatibility): # type: (Incompatibility) -> None self._incompatibility = incompatibility @property - def message(self): + def message(self): # type: () -> str return str(self) - def __str__(self): + def __str__(self): # type: () -> str return _Writer(self._incompatibility).write() @@ -25,12 +26,12 @@ class _Writer: def __init__(self, root): # type: (Incompatibility) -> None self._root = root self._derivations = {} # type: Dict[Incompatibility, int] - self._lines = [] # type: List[Tuple[str, int]] + self._lines = [] # type: List[Tuple[str, Optional[int]]] self._line_numbers = {} # type: Dict[Incompatibility, int] self._count_derivations(self._root) - def write(self): + def write(self): # type: () -> str buffer = [] required_python_version_notification = False @@ -113,7 +114,7 @@ def _visit( conjunction = "So," if conclusion or incompatibility == self._root else "And" incompatibility_string = str(incompatibility) - cause = incompatibility.cause # type: ConflictCause + cause = incompatibility.cause details_for_cause = {} if isinstance(cause.conflict.cause, ConflictCause) and isinstance( cause.other.cause, ConflictCause diff --git a/poetry/mixology/incompatibility.py b/poetry/mixology/incompatibility.py index a5be2dadbdf..1e9a20440f3 100644 --- a/poetry/mixology/incompatibility.py +++ b/poetry/mixology/incompatibility.py @@ -1,6 +1,8 @@ from typing import Dict from typing import Generator from typing import List +from typing import Optional +from typing import Union from .incompatibility_cause import ConflictCause from .incompatibility_cause import DependencyCause @@ -82,11 +84,15 @@ def terms(self): # type: () -> List[Term] return self._terms @property - def cause(self): # type: () -> IncompatibilityCause + def cause( + self, + ): # type: () -> Union[RootCause, NoVersionsCause, DependencyCause, ConflictCause, PythonCause, PlatformCause, PackageNotFoundCause] return self._cause @property - def external_incompatibilities(self): # type: () -> Generator[Incompatibility] + def external_incompatibilities( + self, + ): # type: () -> Generator[Union[ConflictCause, Incompatibility]] """ Returns all external incompatibilities in this incompatibility's derivation graph. @@ -106,7 +112,7 @@ def is_failure(self): # type: () -> bool len(self._terms) == 1 and self._terms[0].dependency.is_root ) - def __str__(self): + def __str__(self): # type: () -> str if isinstance(self._cause, DependencyCause): assert len(self._terms) == 2 @@ -222,7 +228,7 @@ def __str__(self): def and_to_string( self, other, details, this_line, other_line - ): # type: (Incompatibility, dict, int, int) -> str + ): # type: (Incompatibility, dict, Optional[int], Optional[int]) -> str requires_both = self._try_requires_both(other, details, this_line, other_line) if requires_both is not None: return requires_both @@ -241,18 +247,18 @@ def and_to_string( buffer = [str(self)] if this_line is not None: - buffer.append(" " + this_line) + buffer.append(" " + str(this_line)) buffer.append(" and {}".format(str(other))) if other_line is not None: - buffer.append(" " + other_line) + buffer.append(" " + str(other_line)) return "\n".join(buffer) def _try_requires_both( self, other, details, this_line, other_line - ): # type: (Incompatibility, dict, int, int) -> str + ): # type: (Incompatibility, dict, Optional[int], Optional[int]) -> Optional[str] if len(self._terms) == 1 or len(other.terms) == 1: return @@ -298,7 +304,7 @@ def _try_requires_both( def _try_requires_through( self, other, details, this_line, other_line - ): # type: (Incompatibility, dict, int, int) -> str + ): # type: (Incompatibility, dict, int, int) -> Optional[str] if len(self._terms) == 1 or len(other.terms) == 1: return @@ -376,7 +382,7 @@ def _try_requires_through( def _try_requires_forbidden( self, other, details, this_line, other_line - ): # type: (Incompatibility, dict, int, int) -> str + ): # type: (Incompatibility, dict, int, int) -> Optional[str] if len(self._terms) != 1 and len(other.terms) != 1: return None @@ -430,13 +436,13 @@ def _try_requires_forbidden( return "".join(buffer) - def _terse(self, term, allow_every=False): + def _terse(self, term, allow_every=False): # type: (Term, bool) -> str if allow_every and term.constraint.is_any(): return "every version of {}".format(term.dependency.complete_name) return str(term.dependency) - def _single_term_where(self, callable): # type: (callable) -> Term + def _single_term_where(self, callable): # type: (callable) -> Optional[Term] found = None for term in self._terms: if not callable(term): @@ -449,5 +455,5 @@ def _single_term_where(self, callable): # type: (callable) -> Term return found - def __repr__(self): + def __repr__(self): # type: () -> str return "".format(str(self)) diff --git a/poetry/mixology/incompatibility_cause.py b/poetry/mixology/incompatibility_cause.py index 8156b4fa42b..227b9dd0e81 100644 --- a/poetry/mixology/incompatibility_cause.py +++ b/poetry/mixology/incompatibility_cause.py @@ -1,3 +1,10 @@ +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from poetry.mixology.incompatibility import Incompatibility # noqa + + class IncompatibilityCause(Exception): """ The reason and Incompatibility's terms are incompatible. @@ -25,19 +32,21 @@ class ConflictCause(IncompatibilityCause): during conflict resolution. """ - def __init__(self, conflict, other): + def __init__( + self, conflict, other + ): # type: ("Incompatibility", "Incompatibility") -> None self._conflict = conflict self._other = other @property - def conflict(self): + def conflict(self): # type: () -> "Incompatibility" return self._conflict @property - def other(self): + def other(self): # type: () -> "Incompatibility" return self._other - def __str__(self): + def __str__(self): # type: () -> str return str(self._conflict) @@ -48,16 +57,16 @@ class PythonCause(IncompatibilityCause): with the current python version. """ - def __init__(self, python_version, root_python_version): + def __init__(self, python_version, root_python_version): # type: (str, str) -> None self._python_version = python_version self._root_python_version = root_python_version @property - def python_version(self): + def python_version(self): # type: () -> str return self._python_version @property - def root_python_version(self): + def root_python_version(self): # type: () -> str return self._root_python_version @@ -67,11 +76,11 @@ class PlatformCause(IncompatibilityCause): (OS most likely) being incompatible with the current platform. """ - def __init__(self, platform): + def __init__(self, platform): # type: (str) -> None self._platform = platform @property - def platform(self): + def platform(self): # type: () -> str return self._platform @@ -81,9 +90,9 @@ class PackageNotFoundCause(IncompatibilityCause): source. """ - def __init__(self, error): + def __init__(self, error): # type: (Exception) -> None self._error = error @property - def error(self): + def error(self): # type: () -> Exception return self._error diff --git a/poetry/mixology/partial_solution.py b/poetry/mixology/partial_solution.py index 55230425ce8..93a6f73a2fb 100644 --- a/poetry/mixology/partial_solution.py +++ b/poetry/mixology/partial_solution.py @@ -1,15 +1,19 @@ +from typing import TYPE_CHECKING from typing import Dict from typing import List -from poetry.core.packages import Dependency -from poetry.core.packages import Package - from .assignment import Assignment from .incompatibility import Incompatibility from .set_relation import SetRelation from .term import Term +if TYPE_CHECKING: + from poetry.core.packages import Dependency # noqa + from poetry.core.packages import Package # noqa + from poetry.packages import DependencyPackage # noqa + + class PartialSolution: """ # A list of Assignments that represent the solver's current best guess about @@ -19,13 +23,13 @@ class PartialSolution: # See https://github.com/dart-lang/mixology/tree/master/doc/solver.md#partial-solution. """ - def __init__(self): + def __init__(self): # type: () -> None # The assignments that have been made so far, in the order they were # assigned. self._assignments = [] # type: List[Assignment] # The decisions made for each package. - self._decisions = dict() # type: Dict[str, Package] + self._decisions = dict() # type: Dict[str, "Package"] # The intersection of all positive Assignments for each package, minus any # negative Assignments that refer to that package. @@ -48,7 +52,7 @@ def __init__(self): self._backtracking = False @property - def decisions(self): # type: () -> List[Package] + def decisions(self): # type: () -> List["Package"] return list(self._decisions.values()) @property @@ -60,14 +64,14 @@ def attempted_solutions(self): # type: () -> int return self._attempted_solutions @property - def unsatisfied(self): # type: () -> List[Dependency] + def unsatisfied(self): # type: () -> List["Dependency"] return [ term.dependency for term in self._positive.values() if term.dependency.complete_name not in self._decisions ] - def decide(self, package): # type: (Package) -> None + def decide(self, package): # type: ("Package") -> None """ Adds an assignment of package as a decision and increments the decision level. @@ -88,7 +92,7 @@ def decide(self, package): # type: (Package) -> None def derive( self, dependency, is_positive, cause - ): # type: (Dependency, bool, Incompatibility) -> None + ): # type: ("Dependency", bool, Incompatibility) -> None """ Adds an assignment of package as a derivation. """ @@ -170,7 +174,7 @@ def satisfier(self, term): # type: (Term) -> Assignment Returns the first Assignment in this solution such that the sublist of assignments up to and including that entry collectively satisfies term. """ - assigned_term = None # type: Term + assigned_term = None for assignment in self._assignments: if assignment.dependency.complete_name != term.dependency.complete_name: diff --git a/poetry/mixology/result.py b/poetry/mixology/result.py index 5eadeb75ddc..62a5029e5e3 100644 --- a/poetry/mixology/result.py +++ b/poetry/mixology/result.py @@ -1,13 +1,24 @@ +from typing import TYPE_CHECKING +from typing import List + + +if TYPE_CHECKING: + from poetry.core.packages import Package # noqa + from poetry.core.packages import ProjectPackage # noqa + + class SolverResult: - def __init__(self, root, packages, attempted_solutions): + def __init__( + self, root, packages, attempted_solutions + ): # type: ("ProjectPackage", List["Package"], int) -> None self._root = root self._packages = packages self._attempted_solutions = attempted_solutions @property - def packages(self): + def packages(self): # type: () -> List["Package"] return self._packages @property - def attempted_solutions(self): + def attempted_solutions(self): # type: () -> int return self._attempted_solutions diff --git a/poetry/mixology/solutions/solutions/python_requirement_solution.py b/poetry/mixology/solutions/solutions/python_requirement_solution.py index 9ec7cf22301..b24f7f09915 100644 --- a/poetry/mixology/solutions/solutions/python_requirement_solution.py +++ b/poetry/mixology/solutions/solutions/python_requirement_solution.py @@ -1,8 +1,15 @@ +from typing import TYPE_CHECKING +from typing import List + from crashtest.contracts.solution import Solution +if TYPE_CHECKING: + from poetry.mixology.incompatibility_cause import PackageNotFoundCause # noqa + + class PythonRequirementSolution(Solution): - def __init__(self, exception): + def __init__(self, exception): # type: ("PackageNotFoundCause") -> None from poetry.core.semver import parse_constraint from poetry.mixology.incompatibility_cause import PythonCause @@ -37,15 +44,15 @@ def __init__(self, exception): self._description = description @property - def solution_title(self) -> str: + def solution_title(self): # type: () -> str return self._title @property - def solution_description(self): + def solution_description(self): # type: () -> str return self._description @property - def documentation_links(self): + def documentation_links(self): # type: () -> List[str] return [ "https://python-poetry.org/docs/dependency-specification/#python-restricted-dependencies", "https://python-poetry.org/docs/dependency-specification/#using-environment-markers", diff --git a/poetry/mixology/term.py b/poetry/mixology/term.py index 0889e984a3b..b232e268af0 100644 --- a/poetry/mixology/term.py +++ b/poetry/mixology/term.py @@ -1,11 +1,16 @@ # -*- coding: utf-8 -*- -from typing import Union +from typing import TYPE_CHECKING +from typing import Optional from poetry.core.packages import Dependency from .set_relation import SetRelation +if TYPE_CHECKING: + from poetry.core.semver import VersionTypes # noqa + + class Term(object): """ A statement about a package which is true or false for a given selection of @@ -23,11 +28,11 @@ def inverse(self): # type: () -> Term return Term(self._dependency, not self.is_positive()) @property - def dependency(self): + def dependency(self): # type: () -> Dependency return self._dependency @property - def constraint(self): + def constraint(self): # type: () -> "VersionTypes" return self._dependency.constraint def is_positive(self): # type: () -> bool @@ -106,7 +111,7 @@ def relation(self, other): # type: (Term) -> int # not foo ^1.5.0 is a superset of not foo ^1.0.0 return SetRelation.OVERLAPPING - def intersect(self, other): # type: (Term) -> Union[Term, None] + def intersect(self, other): # type: (Term) -> Optional[Term] """ Returns a Term that represents the packages allowed by both this term and another @@ -147,21 +152,23 @@ def difference(self, other): # type: (Term) -> Term """ return self.intersect(other.inverse) - def _compatible_dependency(self, other): + def _compatible_dependency(self, other): # type: (Term) -> bool return ( self.dependency.is_root or other.is_root or other.is_same_package_as(self.dependency) ) - def _non_empty_term(self, constraint, is_positive): + def _non_empty_term( + self, constraint, is_positive + ): # type: ("VersionTypes", bool) -> Optional[Term] if constraint.is_empty(): return return Term(self.dependency.with_constraint(constraint), is_positive) - def __str__(self): + def __str__(self): # type: () -> str return "{}{}".format("not " if not self.is_positive() else "", self._dependency) - def __repr__(self): + def __repr__(self): # type: () -> str return "".format(str(self)) diff --git a/poetry/mixology/version_solver.py b/poetry/mixology/version_solver.py index 8d70a0df288..1d1d19d2d55 100644 --- a/poetry/mixology/version_solver.py +++ b/poetry/mixology/version_solver.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING from typing import Dict from typing import List -from typing import Union +from typing import Optional from poetry.core.packages import Dependency from poetry.core.packages import Package @@ -130,7 +130,7 @@ def _propagate(self, package): # type: (str) -> None def _propagate_incompatibility( self, incompatibility - ): # type: (Incompatibility) -> Union[str, _conflict, None] + ): # type: (Incompatibility) -> Optional[str, _conflict] """ If incompatibility is almost satisfied by _solution, adds the negation of the unsatisfied term to _solution. @@ -317,7 +317,7 @@ def _resolve_conflict( raise SolveFailure(incompatibility) - def _choose_package_version(self): # type: () -> Union[str, None] + def _choose_package_version(self): # type: () -> Optional[str] """ Tries to select a version of a required package. @@ -331,7 +331,7 @@ def _choose_package_version(self): # type: () -> Union[str, None] # Prefer packages with as few remaining versions as possible, # so that if a conflict is necessary it's forced quickly. - def _get_min(dependency): + def _get_min(dependency): # type: (Dependency) -> int if dependency.name in self._use_latest: # If we're forced to use the latest version of a package, it effectively # only has one version to choose from. @@ -447,7 +447,7 @@ def _add_incompatibility(self, incompatibility): # type: (Incompatibility) -> N incompatibility ) - def _get_locked(self, dependency): # type: (Dependency) -> Union[Package, None] + def _get_locked(self, dependency): # type: (Dependency) -> Optional[Package] if dependency.name in self._use_latest: return @@ -460,5 +460,5 @@ def _get_locked(self, dependency): # type: (Dependency) -> Union[Package, None] return locked - def _log(self, text): + def _log(self, text): # type: (str) -> None self._provider.debug(text, self._solution.attempted_solutions) diff --git a/poetry/packages/dependency_package.py b/poetry/packages/dependency_package.py index 60c51007ed8..e375118a5b8 100644 --- a/poetry/packages/dependency_package.py +++ b/poetry/packages/dependency_package.py @@ -1,4 +1,6 @@ +from typing import Any from typing import List +from typing import Union from poetry.core.packages.dependency import Dependency from poetry.core.packages.package import Package @@ -17,7 +19,7 @@ def dependency(self): # type: () -> Dependency def package(self): # type: () -> Package return self._package - def clone(self): # type: () -> DependencyPackage + def clone(self): # type: () -> "DependencyPackage" return self.__class__(self._dependency, self._package.clone()) def with_features(self, features): # type: (List[str]) -> "DependencyPackage" @@ -26,25 +28,25 @@ def with_features(self, features): # type: (List[str]) -> "DependencyPackage" def without_features(self): # type: () -> "DependencyPackage" return self.with_features([]) - def __getattr__(self, name): + def __getattr__(self, name): # type: (str) -> Any return getattr(self._package, name) - def __setattr__(self, key, value): + def __setattr__(self, key, value): # type: (str, Any) -> None if key in {"_dependency", "_package"}: return super(DependencyPackage, self).__setattr__(key, value) setattr(self._package, key, value) - def __str__(self): + def __str__(self): # type: () -> str return str(self._package) - def __repr__(self): + def __repr__(self): # type: () -> str return repr(self._package) - def __hash__(self): + def __hash__(self): # type: () -> int return hash(self._package) - def __eq__(self, other): + def __eq__(self, other): # type: (Union[Package, "DependencyPackage"]) -> bool if isinstance(other, DependencyPackage): other = other.package diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index 8cb6d91f916..80350f66fe4 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -6,6 +6,7 @@ from copy import deepcopy from hashlib import sha256 from pathlib import Path +from typing import TYPE_CHECKING from typing import Dict from typing import Iterable from typing import Iterator @@ -26,7 +27,7 @@ import poetry.repositories from poetry.core.packages import dependency_from_pep_508 -from poetry.core.packages.package import Dependency +from poetry.core.packages.dependency import Dependency from poetry.core.packages.package import Package from poetry.core.semver import parse_constraint from poetry.core.semver.version import Version @@ -37,6 +38,9 @@ from poetry.utils.extras import get_extra_package_names +if TYPE_CHECKING: + from ptomlkit.toml_document import TOMLDocument # noqa + logger = logging.getLogger(__name__) @@ -46,7 +50,7 @@ class Locker(object): _relevant_keys = ["dependencies", "dev-dependencies", "source", "extras"] - def __init__(self, lock, local_config): # type: (Path, dict) -> None + def __init__(self, lock, local_config): # type: (Union[str, Path], dict) -> None self._lock = TOMLFile(lock) self._local_config = local_config self._lock_data = None @@ -57,7 +61,7 @@ def lock(self): # type: () -> TOMLFile return self._lock @property - def lock_data(self): + def lock_data(self): # type: () -> TOMLDocument if self._lock_data is None: self._lock_data = self._get_lock_data() @@ -382,7 +386,7 @@ def get_project_dependency_packages( yield DependencyPackage(dependency=dependency, package=package) - def set_lock_data(self, root, packages): # type: (...) -> bool + def set_lock_data(self, root, packages): # type: (Package, List[Package]) -> bool files = table() packages = self._lock_packages(packages) # Retrieving hashes @@ -427,7 +431,7 @@ def set_lock_data(self, root, packages): # type: (...) -> bool return False - def _write_lock_data(self, data): + def _write_lock_data(self, data): # type: ("TOMLDocument") -> None self.lock.write(data) # Checking lock file data consistency @@ -452,7 +456,7 @@ def _get_content_hash(self): # type: () -> str return content_hash - def _get_lock_data(self): # type: () -> dict + def _get_lock_data(self): # type: () -> "TOMLDocument" if not self._lock.exists(): raise RuntimeError("No lockfile found. Unable to read locked packages") @@ -484,9 +488,7 @@ def _get_lock_data(self): # type: () -> dict return lock_data - def _lock_packages( - self, packages - ): # type: (List['poetry.packages.Package']) -> list + def _lock_packages(self, packages): # type: (List[Package]) -> list locked = [] for package in sorted(packages, key=lambda x: x.name): diff --git a/poetry/packages/package_collection.py b/poetry/packages/package_collection.py index e10ea635bca..e572dcbfdeb 100644 --- a/poetry/packages/package_collection.py +++ b/poetry/packages/package_collection.py @@ -1,8 +1,19 @@ +from typing import TYPE_CHECKING +from typing import List +from typing import Union + from .dependency_package import DependencyPackage +if TYPE_CHECKING: + from poetry.core.packages import Dependency # noqa + from poetry.core.packages import Package # noqa + + class PackageCollection(list): - def __init__(self, dependency, packages=None): + def __init__( + self, dependency, packages=None + ): # type: (Dependency, List[Union["Package", DependencyPackage]]) -> None self._dependency = dependency if packages is None: @@ -13,7 +24,7 @@ def __init__(self, dependency, packages=None): for package in packages: self.append(package) - def append(self, package): + def append(self, package): # type: (Union["Package", DependencyPackage]) -> None if isinstance(package, DependencyPackage): package = package.package diff --git a/poetry/publishing/publisher.py b/poetry/publishing/publisher.py index 5cec7eca2fe..c6855deee2d 100644 --- a/poetry/publishing/publisher.py +++ b/poetry/publishing/publisher.py @@ -1,7 +1,10 @@ import logging from pathlib import Path +from typing import TYPE_CHECKING +from typing import List from typing import Optional +from typing import Union from poetry.utils.helpers import get_cert from poetry.utils.helpers import get_client_cert @@ -10,6 +13,13 @@ from .uploader import Uploader +if TYPE_CHECKING: + from cleo.io import BufferedIO # noqa + from cleo.io import ConsoleIO # noqa + from clikit.io import NullIO # noqa + + from ..poetry import Poetry # noqa + logger = logging.getLogger(__name__) @@ -18,7 +28,9 @@ class Publisher: Registers and publishes packages to remote repositories. """ - def __init__(self, poetry, io): + def __init__( + self, poetry, io + ): # type: ("Poetry", Union["ConsoleIO", "BufferedIO", "NullIO"]) -> None self._poetry = poetry self._package = poetry.package self._io = io @@ -26,7 +38,7 @@ def __init__(self, poetry, io): self._password_manager = PasswordManager(poetry.config) @property - def files(self): + def files(self): # type: () -> List[Path] return self._uploader.files def publish( diff --git a/poetry/publishing/uploader.py b/poetry/publishing/uploader.py index bb1673e3bff..43f4589b383 100644 --- a/poetry/publishing/uploader.py +++ b/poetry/publishing/uploader.py @@ -2,10 +2,12 @@ import io from pathlib import Path +from typing import TYPE_CHECKING from typing import Any from typing import Dict from typing import List from typing import Optional +from typing import Tuple from typing import Union import requests @@ -26,11 +28,17 @@ from poetry.utils.patterns import wheel_file_re +if TYPE_CHECKING: + from cleo.io import ConsoleIO # noqa + from clikit.io import NullIO # noqa + + from poetry.poetry import Poetry # noqa + _has_blake2 = hasattr(hashlib, "blake2b") class UploadError(Exception): - def __init__(self, error): # type: (Union[ConnectionError, HTTPError]) -> None + def __init__(self, error): # type: (Union[ConnectionError, HTTPError, str]) -> None if isinstance(error, HTTPError): message = "HTTP Error {}: {}".format( error.response.status_code, error.response.reason @@ -46,7 +54,9 @@ def __init__(self, error): # type: (Union[ConnectionError, HTTPError]) -> None class Uploader: - def __init__(self, poetry, io): + def __init__( + self, poetry, io + ): # type: ("Poetry", Union["ConsoleIO", "NullIO"]) -> None self._poetry = poetry self._package = poetry.package self._io = io @@ -54,11 +64,11 @@ def __init__(self, poetry, io): self._password = None @property - def user_agent(self): + def user_agent(self): # type: () -> str return user_agent("poetry", __version__) @property - def adapter(self): + def adapter(self): # type: () -> adapters.HTTPAdapter retry = util.Retry( connect=5, total=10, @@ -86,7 +96,7 @@ def files(self): # type: () -> List[Path] return sorted(wheels + tars) - def auth(self, username, password): + def auth(self, username, password): # type: (str, str) -> None self._username = username self._password = password @@ -101,7 +111,7 @@ def make_session(self): # type: () -> requests.Session return session - def is_authenticated(self): + def is_authenticated(self): # type: () -> bool return self._username is not None and self._password is not None def upload( @@ -326,7 +336,7 @@ def _register( return resp - def _prepare_data(self, data): + def _prepare_data(self, data): # type: (Dict) -> List[Tuple[str, str]] data_to_send = [] for key, value in data.items(): if not isinstance(value, (list, tuple)): @@ -337,7 +347,7 @@ def _prepare_data(self, data): return data_to_send - def _get_type(self, file): + def _get_type(self, file): # type: (Path) -> str exts = file.suffixes if exts[-1] == ".whl": return "bdist_wheel" diff --git a/poetry/puzzle/exceptions.py b/poetry/puzzle/exceptions.py index e2e0b0dcce7..8ae381a88ba 100644 --- a/poetry/puzzle/exceptions.py +++ b/poetry/puzzle/exceptions.py @@ -1,18 +1,22 @@ +from typing import Dict +from typing import Tuple + + class SolverProblemError(Exception): - def __init__(self, error): + def __init__(self, error): # type: (Exception) -> None self._error = error super(SolverProblemError, self).__init__(str(error)) @property - def error(self): + def error(self): # type: () -> Exception return self._error class OverrideNeeded(Exception): - def __init__(self, *overrides): + def __init__(self, *overrides): # type: (*Dict) -> None self._overrides = overrides @property - def overrides(self): + def overrides(self): # type: () -> Tuple[Dict] return self._overrides diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index e9719a924d9..43d6ed63df3 100644 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -7,8 +7,11 @@ from pathlib import Path from tempfile import mkdtemp from typing import Any +from typing import Dict +from typing import Iterator from typing import List from typing import Optional +from typing import Union from clikit.ui.components import ProgressIndicator @@ -68,10 +71,10 @@ def __init__( def pool(self): # type: () -> Pool return self._pool - def is_debugging(self): + def is_debugging(self): # type: () -> bool return self._is_debugging - def set_overrides(self, overrides): + def set_overrides(self, overrides): # type: (Dict) -> None self._overrides = overrides def load_deferred(self, load_deferred): # type: (bool) -> None @@ -90,7 +93,9 @@ def use_environment(self, env): # type: (Env) -> Provider self._env = original_env self._python_constraint = original_python_constraint - def search_for(self, dependency): # type: (Dependency) -> List[Package] + def search_for( + self, dependency + ): # type: (Union[Dependency, VCSDependency, FileDependency, DirectoryDependency, URLDependency]) -> List[DependencyPackage] """ Search for the specifications that match the given dependency. @@ -175,7 +180,7 @@ def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package] @classmethod def get_package_from_vcs( cls, vcs, url, branch=None, tag=None, rev=None, name=None - ): # type: (str, str, Optional[str], Optional[str]) -> Package + ): # type: (str, str, Optional[str], Optional[str], Optional[str], Optional[str]) -> Package if vcs != "git": raise ValueError("Unsupported VCS dependency {}".format(vcs)) @@ -684,7 +689,7 @@ def complete_package( return package - def debug(self, message, depth=0): + def debug(self, message, depth=0): # type: (str, int) -> None if not (self._io.is_very_verbose() or self._io.is_debug()): return @@ -771,7 +776,7 @@ def debug(self, message, depth=0): self._io.write(debug_info) @contextmanager - def progress(self): + def progress(self): # type: () -> Iterator[None] if not self._io.output.supports_ansi() or self.is_debugging(): self._io.write_line("Resolving dependencies...") yield diff --git a/poetry/puzzle/solver.py b/poetry/puzzle/solver.py index 31858bb3a1d..47e3e8153d6 100644 --- a/poetry/puzzle/solver.py +++ b/poetry/puzzle/solver.py @@ -3,17 +3,21 @@ from collections import defaultdict from contextlib import contextmanager +from typing import TYPE_CHECKING +from typing import Callable +from typing import Dict from typing import List from typing import Optional +from typing import Tuple +from typing import Union -from clikit.io import ConsoleIO +from clikit.api.io import IO from poetry.core.packages import Package from poetry.core.packages.project_package import ProjectPackage from poetry.installation.operations import Install from poetry.installation.operations import Uninstall from poetry.installation.operations import Update -from poetry.installation.operations.operation import Operation from poetry.mixology import resolve_version from poetry.mixology.failure import SolveFailure from poetry.packages import DependencyPackage @@ -26,6 +30,15 @@ from .provider import Provider +if TYPE_CHECKING: + from poetry.core.packages import Dependency # noqa + from poetry.core.packages import DirectoryDependency # noqa + from poetry.core.packages import FileDependency # noqa + from poetry.core.packages import URLDependency # noqa + from poetry.core.packages import VCSDependency # noqa + from poetry.installation.operations import OperationTypes # noqa + + class Solver: def __init__( self, @@ -33,7 +46,7 @@ def __init__( pool, # type: Pool installed, # type: Repository locked, # type: Repository - io, # type: ConsoleIO + io, # type: IO remove_untracked=False, # type: bool provider=None, # type: Optional[Provider] ): @@ -59,7 +72,7 @@ def use_environment(self, env): # type: (Env) -> None with self.provider.use_environment(env): yield - def solve(self, use_latest=None): # type: (...) -> List[Operation] + def solve(self, use_latest=None): # type: (List[str]) -> List["OperationTypes"] with self._provider.progress(): start = time.time() packages, depths = self._solve(use_latest=use_latest) @@ -191,7 +204,9 @@ def solve(self, use_latest=None): # type: (...) -> List[Operation] operations, key=lambda o: (-o.priority, o.package.name, o.package.version,), ) - def solve_in_compatibility_mode(self, overrides, use_latest=None): + def solve_in_compatibility_mode( + self, overrides, use_latest=None + ): # type: (Tuple[Dict], List[str]) -> Tuple[List["Package"], List[int]] locked = {} for package in self._locked.packages: locked[package.name] = DependencyPackage(package.to_dependency(), package) @@ -221,7 +236,9 @@ def solve_in_compatibility_mode(self, overrides, use_latest=None): return packages, depths - def _solve(self, use_latest=None): + def _solve( + self, use_latest=None + ): # type: (List[str]) -> Tuple[List[Package], List[int]] if self._provider._overrides: self._overrides.append(self._provider._overrides) @@ -274,18 +291,20 @@ def _solve(self, use_latest=None): class DFSNode(object): - def __init__(self, id, name, base_name): + def __init__( + self, id, name, base_name + ): # type: (Tuple[str, str, bool], str, str) -> None self.id = id self.name = name self.base_name = base_name - def reachable(self): + def reachable(self): # type: () -> List return [] - def visit(self, parents): + def visit(self, parents): # type: (List[PackageNode]) -> None pass - def __str__(self): + def __str__(self): # type: () -> str return str(self.id) @@ -295,7 +314,9 @@ class VisitedState(enum.Enum): Visited = 2 -def depth_first_search(source, aggregator): +def depth_first_search( + source, aggregator +): # type: (PackageNode, Callable) -> List[Tuple[Package, int]] back_edges = defaultdict(list) visited = {} topo_sorted_nodes = [] @@ -322,7 +343,9 @@ def depth_first_search(source, aggregator): return results -def dfs_visit(node, back_edges, visited, sorted_nodes): +def dfs_visit( + node, back_edges, visited, sorted_nodes +): # type: (PackageNode, Dict[str, List[PackageNode]], Dict[str, VisitedState], List[PackageNode]) -> bool if visited.get(node.id, VisitedState.Unvisited) == VisitedState.Visited: return True if visited.get(node.id, VisitedState.Unvisited) == VisitedState.PartiallyVisited: @@ -343,8 +366,13 @@ def dfs_visit(node, back_edges, visited, sorted_nodes): class PackageNode(DFSNode): def __init__( - self, package, packages, previous=None, previous_dep=None, dep=None, - ): + self, + package, # type: Package + packages, # type: List[Package] + previous=None, # type: Optional[PackageNode] + previous_dep=None, # type: Optional[Union["DirectoryDependency", "FileDependency", "URLDependency", "VCSDependency", "Dependency"]] + dep=None, # type: Optional[Union["DirectoryDependency", "FileDependency", "URLDependency", "VCSDependency", "Dependency"]] + ): # type: (...) -> None self.package = package self.packages = packages @@ -366,7 +394,7 @@ def __init__( package.name, ) - def reachable(self): + def reachable(self): # type: () -> List[PackageNode] children = [] # type: List[PackageNode] if ( @@ -413,7 +441,7 @@ def reachable(self): return children - def visit(self, parents): + def visit(self, parents): # type: (PackageNode) -> None # The root package, which has no parents, is defined as having depth -1 # So that the root package's top-level dependencies have depth 0. self.depth = 1 + max( @@ -425,7 +453,9 @@ def visit(self, parents): ) -def aggregate_package_nodes(nodes, children): +def aggregate_package_nodes( + nodes, children +): # type: (List[PackageNode], List[PackageNode]) -> Tuple[Package, int] package = nodes[0].package depth = max(node.depth for node in nodes) category = ( diff --git a/poetry/repositories/base_repository.py b/poetry/repositories/base_repository.py index 46422ca0edc..801056661a1 100644 --- a/poetry/repositories/base_repository.py +++ b/poetry/repositories/base_repository.py @@ -1,19 +1,31 @@ +from typing import TYPE_CHECKING +from typing import List +from typing import Optional + + +if TYPE_CHECKING: + from poetry.core.packages import Dependency # noqa + from poetry.core.packages import Package # noqa + + class BaseRepository(object): - def __init__(self): + def __init__(self): # type: () -> None self._packages = [] @property - def packages(self): + def packages(self): # type: () -> List["Package"] return self._packages - def has_package(self, package): + def has_package(self, package): # type: ("Package") -> None raise NotImplementedError() - def package(self, name, version, extras=None): + def package( + self, name, version, extras=None + ): # type: (str, str, Optional[List[str]]) -> None raise NotImplementedError() - def find_packages(self, dependency): + def find_packages(self, dependency): # type: ("Dependency") -> None raise NotImplementedError() - def search(self, query): + def search(self, query): # type: (str) -> None raise NotImplementedError() diff --git a/poetry/repositories/legacy_repository.py b/poetry/repositories/legacy_repository.py index 94e1dbf4d7b..aa870562e8b 100644 --- a/poetry/repositories/legacy_repository.py +++ b/poetry/repositories/legacy_repository.py @@ -5,9 +5,12 @@ from collections import defaultdict from pathlib import Path +from typing import TYPE_CHECKING +from typing import Any +from typing import Dict from typing import Generator +from typing import List from typing import Optional -from typing import Union import requests import requests.auth @@ -34,6 +37,9 @@ from .pypi_repository import PyPiRepository +if TYPE_CHECKING: + from poetry.core.packages import Dependency # noqa + try: from html import unescape except ImportError: @@ -69,7 +75,9 @@ class Page: ".tar", ] - def __init__(self, url, content, headers): + def __init__( + self, url, content, headers + ): # type: (str, str, Dict[str, Any]) -> None if not url.endswith("/"): url += "/" @@ -127,7 +135,7 @@ def links_for_version(self, version): # type: (Version) -> Generator[Link] if self.link_version(link) == version: yield link - def link_version(self, link): # type: (Link) -> Union[Version, None] + def link_version(self, link): # type: (Link) -> Optional[Version] m = wheel_file_re.match(link.filename) if m: version = m.group("ver") @@ -148,7 +156,7 @@ def link_version(self, link): # type: (Link) -> Union[Version, None] _clean_re = re.compile(r"[^a-z0-9$&+,/:;=?@.#%_\\|-]", re.I) - def clean_link(self, url): + def clean_link(self, url): # type: (str) -> str """Makes sure a link is fully encoded. That is, if a ' ' shows up in the link, it will be rewritten to %20 (while not over-quoting % or other characters).""" @@ -225,7 +233,7 @@ def authenticated_url(self): # type: () -> str path=parsed.path, ) - def find_packages(self, dependency): + def find_packages(self, dependency): # type: ("Dependency") -> List[Package] packages = [] constraint = dependency.constraint @@ -296,7 +304,9 @@ def find_packages(self, dependency): return packages - def package(self, name, version, extras=None): # type: (...) -> Package + def package( + self, name, version, extras=None + ): # type: (str, str, Optional[List[str]]) -> Package """ Retrieve the release information. @@ -320,7 +330,7 @@ def package(self, name, version, extras=None): # type: (...) -> Package return package - def find_links_for_package(self, package): + def find_links_for_package(self, package): # type: (Package) -> List[Link] page = self._get("/{}/".format(package.name.replace(".", "-"))) if page is None: return [] @@ -375,7 +385,7 @@ def _get_release_info(self, name, version): # type: (str, str) -> dict return data.asdict() - def _get(self, endpoint): # type: (str) -> Union[Page, None] + def _get(self, endpoint): # type: (str) -> Optional[Page] url = self._url + endpoint try: response = self.session.get(url) diff --git a/poetry/repositories/pool.py b/poetry/repositories/pool.py index ac712831d3a..d8181ba06c0 100644 --- a/poetry/repositories/pool.py +++ b/poetry/repositories/pool.py @@ -9,7 +9,8 @@ if TYPE_CHECKING: - from poetry.core.packages import Package + from poetry.core.packages import Dependency # noqa + from poetry.core.packages import Package # noqa class Pool(BaseRepository): @@ -108,12 +109,12 @@ def remove_repository(self, repository_name): # type: (str) -> Pool return self - def has_package(self, package): + def has_package(self, package): # type: ("Package") -> bool raise NotImplementedError() def package( self, name, version, extras=None, repository=None - ): # type: (str, str, List[str], str) -> Package + ): # type: (str, str, List[str], str) -> "Package" if repository is not None: repository = repository.lower() @@ -143,9 +144,7 @@ def package( raise PackageNotFound("Package {} ({}) not found.".format(name, version)) - def find_packages( - self, dependency, - ): + def find_packages(self, dependency): # type: ("Dependency") -> List["Package"] repository = dependency.source_name if repository is not None: repository = repository.lower() @@ -166,7 +165,7 @@ def find_packages( return packages - def search(self, query): + def search(self, query): # type: (str) -> List["Package"] from .legacy_repository import LegacyRepository results = [] diff --git a/poetry/repositories/pypi_repository.py b/poetry/repositories/pypi_repository.py index 715ba557379..951f305dbfc 100644 --- a/poetry/repositories/pypi_repository.py +++ b/poetry/repositories/pypi_repository.py @@ -45,7 +45,9 @@ class PyPiRepository(RemoteRepository): CACHE_VERSION = parse_constraint("1.0.0") - def __init__(self, url="https://pypi.org/", disable_cache=False, fallback=True): + def __init__( + self, url="https://pypi.org/", disable_cache=False, fallback=True + ): # type: (str, bool, bool) -> None super(PyPiRepository, self).__init__(url.rstrip("/") + "/simple/") self._base_url = url @@ -72,7 +74,7 @@ def __init__(self, url="https://pypi.org/", disable_cache=False, fallback=True): self._name = "PyPI" @property - def session(self): + def session(self): # type: () -> CacheControl return self._session def find_packages(self, dependency): # type: (Dependency) -> List[Package] @@ -156,7 +158,7 @@ def package( ): # type: (...) -> Package return self.get_release_info(name, version).to_package(name=name, extras=extras) - def search(self, query): + def search(self, query): # type: (str) -> List[Package] results = [] search = {"q": query} @@ -236,7 +238,7 @@ def get_release_info(self, name, version): # type: (str, str) -> PackageInfo return PackageInfo.load(cached) - def find_links_for_package(self, package): + def find_links_for_package(self, package): # type: (Package) -> List[Link] json_data = self._get("pypi/{}/{}/json".format(package.name, package.version)) if json_data is None: return [] @@ -452,5 +454,5 @@ def _get_info_from_sdist(self, url): # type: (str) -> PackageInfo def _download(self, url, dest): # type: (str, str) -> None return download_file(url, dest, session=self.session) - def _log(self, msg, level="info"): + def _log(self, msg, level="info"): # type: (str, str) -> None getattr(logger, level)("{}: {}".format(self._name, msg)) diff --git a/poetry/repositories/repository.py b/poetry/repositories/repository.py index 1ebe702bb9c..f65e44bafc8 100644 --- a/poetry/repositories/repository.py +++ b/poetry/repositories/repository.py @@ -1,3 +1,7 @@ +from typing import TYPE_CHECKING +from typing import List +from typing import Optional + from poetry.core.semver import VersionConstraint from poetry.core.semver import VersionRange from poetry.core.semver import parse_constraint @@ -5,8 +9,16 @@ from .base_repository import BaseRepository +if TYPE_CHECKING: + from poetry.core.packages import Dependency # noqa + from poetry.core.packages import Link # noqa + from poetry.core.packages import Package # noqa + + class Repository(BaseRepository): - def __init__(self, packages=None, name=None): + def __init__( + self, packages=None, name=None + ): # type: (List["Package"], str) -> None super(Repository, self).__init__() self._name = name @@ -18,17 +30,19 @@ def __init__(self, packages=None, name=None): self.add_package(package) @property - def name(self): + def name(self): # type: () -> str return self._name - def package(self, name, version, extras=None): + def package( + self, name, version, extras=None + ): # type: (str, str, Optional[List[str]]) -> "Package" name = name.lower() for package in self.packages: if name == package.name and package.version.text == version: return package.clone() - def find_packages(self, dependency): + def find_packages(self, dependency): # type: ("Dependency") -> List["Package"] constraint = dependency.constraint packages = [] ignored_pre_release_packages = [] @@ -71,7 +85,7 @@ def find_packages(self, dependency): return packages or ignored_pre_release_packages - def has_package(self, package): + def has_package(self, package): # type: ("Package") -> bool package_id = package.unique_name for repo_package in self.packages: @@ -80,10 +94,10 @@ def has_package(self, package): return False - def add_package(self, package): + def add_package(self, package): # type: ("Package") -> None self._packages.append(package) - def remove_package(self, package): + def remove_package(self, package): # type: ("Package") -> None package_id = package.unique_name index = None @@ -95,10 +109,10 @@ def remove_package(self, package): if index is not None: del self._packages[index] - def find_links_for_package(self, package): + def find_links_for_package(self, package): # type: ("Package") -> List["Link"] return [] - def search(self, query): + def search(self, query): # type: (str) -> List["Package"] results = [] for package in self.packages: @@ -107,5 +121,5 @@ def search(self, query): return results - def __len__(self): + def __len__(self): # type: () -> int return len(self._packages) diff --git a/poetry/utils/appdirs.py b/poetry/utils/appdirs.py index ab99506a028..aac3ad36076 100644 --- a/poetry/utils/appdirs.py +++ b/poetry/utils/appdirs.py @@ -5,11 +5,19 @@ import os import sys +from typing import TYPE_CHECKING +from typing import List +from typing import Union + + +if TYPE_CHECKING: + from poetry.utils._compat import Path # noqa + WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") -def expanduser(path): +def expanduser(path): # type: (Union[str, "Path"]) -> str """ Expand ~ and ~user constructions. @@ -21,7 +29,7 @@ def expanduser(path): return expanded -def user_cache_dir(appname): +def user_cache_dir(appname): # type: (str) -> str r""" Return full path to the user-specific cache dir for this application. @@ -64,7 +72,7 @@ def user_cache_dir(appname): return path -def user_data_dir(appname, roaming=False): +def user_data_dir(appname, roaming=False): # type: (str, bool) -> str r""" Return full path to the user-specific data dir for this application. @@ -104,7 +112,7 @@ def user_data_dir(appname, roaming=False): return path -def user_config_dir(appname, roaming=True): +def user_config_dir(appname, roaming=True): # type: (str, bool) -> str """Return full path to the user-specific config dir for this application. "appname" is the name of application. @@ -137,7 +145,7 @@ def user_config_dir(appname, roaming=True): # for the discussion regarding site_config_dirs locations # see -def site_config_dirs(appname): +def site_config_dirs(appname): # type: (str) -> List[str] r"""Return a list of potential user-shared config dirs for this application. "appname" is the name of application. @@ -178,7 +186,7 @@ def site_config_dirs(appname): # -- Windows support functions -- -def _get_win_folder_from_registry(csidl_name): +def _get_win_folder_from_registry(csidl_name): # type: (str) -> str """ This is a fallback technique at best. I'm not sure if using the registry for this guarantees us the correct answer for all CSIDL_* @@ -200,7 +208,7 @@ def _get_win_folder_from_registry(csidl_name): return directory -def _get_win_folder_with_ctypes(csidl_name): +def _get_win_folder_with_ctypes(csidl_name): # type: (str) -> str csidl_const = { "CSIDL_APPDATA": 26, "CSIDL_COMMON_APPDATA": 35, @@ -234,7 +242,7 @@ def _get_win_folder_with_ctypes(csidl_name): _get_win_folder = _get_win_folder_from_registry -def _win_path_to_bytes(path): +def _win_path_to_bytes(path): # type: (str) -> Union[str, bytes] """Encode Windows paths to bytes. Only used on Python 2. Motivation is to be consistent with other operating systems where paths diff --git a/poetry/utils/env.py b/poetry/utils/env.py index 22db246911e..77a185130d5 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -15,6 +15,7 @@ from subprocess import CalledProcessError from typing import Any from typing import Dict +from typing import Iterator from typing import List from typing import Optional from typing import Tuple @@ -252,7 +253,7 @@ def find(self, path, writable_only=False): # type: (Path, bool) -> List[Path] if value[-1] is True ] - def __getattr__(self, item): + def __getattr__(self, item): # type: (str) -> Any try: return super(SitePackages, self).__getattribute__(item) except AttributeError: @@ -265,7 +266,9 @@ class EnvError(Exception): class EnvCommandError(EnvError): - def __init__(self, e, input=None): # type: (CalledProcessError) -> None + def __init__( + self, e, input=None + ): # type: (CalledProcessError, Optional[str]) -> None self.e = e message = "Command {} errored with the following return code {}, and output: \n{}".format( @@ -277,7 +280,7 @@ def __init__(self, e, input=None): # type: (CalledProcessError) -> None class NoCompatiblePythonVersionFound(EnvError): - def __init__(self, expected, given=None): + def __init__(self, expected, given=None): # type: (str, Optional[str]) -> None if given: message = ( "The specified Python version ({}) " @@ -618,7 +621,7 @@ def remove(self, python): # type: (str) -> Env def create_venv( self, io, name=None, executable=None, force=False - ): # type: (IO, Optional[str], Optional[str], bool) -> Env + ): # type: (IO, Optional[str], Optional[str], bool) -> VirtualEnv if self._env is not None and not force: return self._env @@ -849,12 +852,12 @@ def remove_venv(cls, path): # type: (Union[Path,str]) -> None def get_base_prefix(self): # type: () -> Path if hasattr(sys, "real_prefix"): - return sys.real_prefix + return Path(sys.real_prefix) if hasattr(sys, "base_prefix"): - return sys.base_prefix + return Path(sys.base_prefix) - return sys.prefix + return Path(sys.prefix) @classmethod def generate_env_name(cls, name, cwd): # type: (str, str) -> str @@ -913,7 +916,7 @@ def python(self): # type: () -> str return self._bin("python") @property - def marker_env(self): + def marker_env(self): # type: () -> Dict[str, Any] if self._marker_env is None: self._marker_env = self.get_marker_env() @@ -935,7 +938,7 @@ def os(self): # type: () -> str return os.name @property - def pip_version(self): + def pip_version(self): # type: () -> Version if self._pip_version is None: self._pip_version = self.get_pip_version() @@ -1009,12 +1012,12 @@ def supported_tags(self): # type: () -> List[Tag] @classmethod def get_base_prefix(cls): # type: () -> Path if hasattr(sys, "real_prefix"): - return sys.real_prefix + return Path(sys.real_prefix) if hasattr(sys, "base_prefix"): - return sys.base_prefix + return Path(sys.base_prefix) - return sys.prefix + return Path(sys.prefix) def get_version_info(self): # type: () -> Tuple[int] raise NotImplementedError() @@ -1046,17 +1049,17 @@ def is_sane(self): # type: () -> bool """ return True - def run(self, bin, *args, **kwargs): + def run(self, bin, *args, **kwargs): # type: (str, *str, **Any) -> Union[str, int] bin = self._bin(bin) cmd = [bin] + list(args) return self._run(cmd, **kwargs) - def run_pip(self, *args, **kwargs): + def run_pip(self, *args, **kwargs): # type: (*str, **Any) -> Union[int, str] pip = self.get_pip_command() cmd = pip + list(args) return self._run(cmd, **kwargs) - def _run(self, cmd, **kwargs): + def _run(self, cmd, **kwargs): # type: (List[str], **Any) -> Union[int, str] """ Run a command inside the Python environment. """ @@ -1077,7 +1080,7 @@ def _run(self, cmd, **kwargs): stderr=subprocess.STDOUT, input=encode(input_), check=True, - **kwargs + **kwargs, ).stdout elif call: return subprocess.call(cmd, stderr=subprocess.STDOUT, **kwargs) @@ -1090,7 +1093,9 @@ def _run(self, cmd, **kwargs): return decode(output) - def execute(self, bin, *args, **kwargs): + def execute( + self, bin, *args, **kwargs + ): # type: (str, *str, **Any) -> Optional[int] bin = self._bin(bin) if not self._is_windows: @@ -1145,7 +1150,7 @@ def _bin(self, bin): # type: (str) -> str def __eq__(self, other): # type: (Env) -> bool return other.__class__ == self.__class__ and other.path == self.path - def __repr__(self): + def __repr__(self): # type: () -> str return '{}("{}")'.format(self.__class__.__name__, self._path) @@ -1332,11 +1337,11 @@ def get_paths(self): # type: () -> Dict[str, str] def is_venv(self): # type: () -> bool return True - def is_sane(self): + def is_sane(self): # type: () -> bool # A virtualenv is considered sane if both "python" and "pip" exist. return os.path.exists(self.python) and os.path.exists(self._bin("pip")) - def _run(self, cmd, **kwargs): + def _run(self, cmd, **kwargs): # type: (List[str], **Any) -> Optional[int] with self.temp_environ(): os.environ["PATH"] = self._updated_path() os.environ["VIRTUAL_ENV"] = str(self._path) @@ -1346,7 +1351,9 @@ def _run(self, cmd, **kwargs): return super(VirtualEnv, self)._run(cmd, **kwargs) - def execute(self, bin, *args, **kwargs): + def execute( + self, bin, *args, **kwargs + ): # type: (str, *str, **Any) -> Optional[int] with self.temp_environ(): os.environ["PATH"] = self._updated_path() os.environ["VIRTUAL_ENV"] = str(self._path) @@ -1357,7 +1364,7 @@ def execute(self, bin, *args, **kwargs): return super(VirtualEnv, self).execute(bin, *args, **kwargs) @contextmanager - def temp_environ(self): + def temp_environ(self): # type: () -> Iterator[None] environ = dict(os.environ) try: yield @@ -1365,16 +1372,18 @@ def temp_environ(self): os.environ.clear() os.environ.update(environ) - def unset_env(self, key): + def unset_env(self, key): # type: (str) -> None if key in os.environ: del os.environ[key] - def _updated_path(self): + def _updated_path(self): # type: () -> str return os.pathsep.join([str(self._bin_dir), os.environ.get("PATH", "")]) class NullEnv(SystemEnv): - def __init__(self, path=None, base=None, execute=False): + def __init__( + self, path=None, base=None, execute=False + ): # type: (Path, Optional[Path], bool) -> None if path is None: path = Path(sys.prefix) @@ -1386,35 +1395,37 @@ def __init__(self, path=None, base=None, execute=False): def get_pip_command(self): # type: () -> List[str] return [self._bin("python"), "-m", "pip"] - def _run(self, cmd, **kwargs): + def _run(self, cmd, **kwargs): # type: (List[str], **Any) -> int self.executed.append(cmd) if self._execute: return super(NullEnv, self)._run(cmd, **kwargs) - def execute(self, bin, *args, **kwargs): + def execute( + self, bin, *args, **kwargs + ): # type: (str, *str, **Any) -> Optional[int] self.executed.append([bin] + list(args)) if self._execute: return super(NullEnv, self).execute(bin, *args, **kwargs) - def _bin(self, bin): + def _bin(self, bin): # type: (str) -> str return bin class MockEnv(NullEnv): def __init__( self, - version_info=(3, 7, 0), - python_implementation="CPython", - platform="darwin", - os_name="posix", - is_venv=False, - pip_version="19.1", - sys_path=None, - marker_env=None, - supported_tags=None, - **kwargs + version_info=(3, 7, 0), # type: Tuple[int, int, int] + python_implementation="CPython", # type: str + platform="darwin", # type: str + os_name="posix", # type: str + is_venv=False, # type: bool + pip_version="19.1", # type: str + sys_path=None, # type: Optional[List[str]] + marker_env=None, # type: Dict[str, Any] + supported_tags=None, # type: List[Tag] + **kwargs, # type: Any ): super(MockEnv, self).__init__(**kwargs) @@ -1437,11 +1448,11 @@ def os(self): # type: () -> str return self._os_name @property - def pip_version(self): + def pip_version(self): # type: () -> Version return self._pip_version @property - def sys_path(self): + def sys_path(self): # type: () -> List[str] if self._sys_path is None: return super(MockEnv, self).sys_path diff --git a/poetry/utils/extras.py b/poetry/utils/extras.py index c97d6ee79e5..cff3f5a1ff6 100644 --- a/poetry/utils/extras.py +++ b/poetry/utils/extras.py @@ -1,3 +1,4 @@ +from typing import Iterable from typing import Iterator from typing import List from typing import Mapping @@ -36,7 +37,7 @@ def get_extra_package_names( # keep record of packages seen during recursion in order to avoid recursion error seen_package_names = set() - def _extra_packages(package_names): + def _extra_packages(package_names): # type: (Iterable[str]) -> Iterator[str] """Recursively find dependencies for packages names""" # for each extra pacakge name for package_name in package_names: diff --git a/poetry/utils/helpers.py b/poetry/utils/helpers.py index 909ec3c1438..b7bc0429045 100644 --- a/poetry/utils/helpers.py +++ b/poetry/utils/helpers.py @@ -6,6 +6,10 @@ from contextlib import contextmanager from pathlib import Path +from typing import Any +from typing import Callable +from typing import Dict +from typing import Iterator from typing import List from typing import Optional @@ -37,13 +41,13 @@ def normalize_version(version): # type: (str) -> str return str(Version(version)) -def _del_ro(action, name, exc): +def _del_ro(action, name, exc): # type: (Callable, str, Exception) -> None os.chmod(name, stat.S_IWRITE) os.remove(name) @contextmanager -def temporary_directory(*args, **kwargs): +def temporary_directory(*args, **kwargs): # type: (*Any, **Any) -> Iterator[str] name = tempfile.mkdtemp(*args, **kwargs) yield name @@ -67,7 +71,7 @@ def get_client_cert(config, repository_name): # type: (Config, str) -> Optional return None -def _on_rm_error(func, path, exc_info): +def _on_rm_error(func, path, exc_info): # type: (Callable, str, Exception) -> None if not os.path.exists(path): return @@ -75,14 +79,14 @@ def _on_rm_error(func, path, exc_info): func(path) -def safe_rmtree(path): +def safe_rmtree(path): # type: (str) -> None if Path(path).is_symlink(): return os.unlink(str(path)) shutil.rmtree(path, onerror=_on_rm_error) -def merge_dicts(d1, d2): +def merge_dicts(d1, d2): # type: (Dict, Dict) -> None for k, v in d2.items(): if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], Mapping): merge_dicts(d1[k], d2[k]) diff --git a/poetry/utils/password_manager.py b/poetry/utils/password_manager.py index 6c993840282..e6cc11d65e2 100644 --- a/poetry/utils/password_manager.py +++ b/poetry/utils/password_manager.py @@ -1,5 +1,12 @@ import logging +from typing import TYPE_CHECKING +from typing import Dict +from typing import Optional + + +if TYPE_CHECKING: + from poetry.config.config import Config # noqa logger = logging.getLogger(__name__) @@ -15,16 +22,16 @@ class KeyRingError(Exception): class KeyRing: - def __init__(self, namespace): + def __init__(self, namespace): # type: (str) -> None self._namespace = namespace self._is_available = True self._check() - def is_available(self): + def is_available(self): # type: () -> bool return self._is_available - def get_password(self, name, username): + def get_password(self, name, username): # type: (str, str) -> Optional[str] if not self.is_available(): return @@ -40,7 +47,7 @@ def get_password(self, name, username): "Unable to retrieve the password for {} from the key ring".format(name) ) - def set_password(self, name, username, password): + def set_password(self, name, username, password): # type: (str, str, str) -> None if not self.is_available(): return @@ -58,7 +65,7 @@ def set_password(self, name, username, password): ) ) - def delete_password(self, name, username): + def delete_password(self, name, username): # type: (str, str) -> None if not self.is_available(): return @@ -74,10 +81,10 @@ def delete_password(self, name, username): "Unable to delete the password for {} from the key ring".format(name) ) - def get_entry_name(self, name): + def get_entry_name(self, name): # type: (str) -> str return "{}-{}".format(self._namespace, name) - def _check(self): + def _check(self): # type: () -> None try: import keyring except Exception as e: @@ -113,12 +120,12 @@ def _check(self): class PasswordManager: - def __init__(self, config): + def __init__(self, config): # type: ("Config") -> None self._config = config self._keyring = None @property - def keyring(self): + def keyring(self): # type: () -> KeyRing if self._keyring is None: self._keyring = KeyRing("poetry-repository") if not self._keyring.is_available(): @@ -128,7 +135,7 @@ def keyring(self): return self._keyring - def set_pypi_token(self, name, token): + def set_pypi_token(self, name, token): # type: (str, str) -> None if not self.keyring.is_available(): self._config.auth_config_source.add_property( "pypi-token.{}".format(name), token @@ -136,13 +143,13 @@ def set_pypi_token(self, name, token): else: self.keyring.set_password(name, "__token__", token) - def get_pypi_token(self, name): + def get_pypi_token(self, name): # type: (str) -> str if not self.keyring.is_available(): return self._config.get("pypi-token.{}".format(name)) return self.keyring.get_password(name, "__token__") - def delete_pypi_token(self, name): + def delete_pypi_token(self, name): # type: (str) -> None if not self.keyring.is_available(): return self._config.auth_config_source.remove_property( "pypi-token.{}".format(name) @@ -150,7 +157,7 @@ def delete_pypi_token(self, name): self.keyring.delete_password(name, "__token__") - def get_http_auth(self, name): + def get_http_auth(self, name): # type: (str) -> Optional[Dict[str, str]] auth = self._config.get("http-basic.{}".format(name)) if not auth: username = self._config.get("http-basic.{}.username".format(name)) @@ -167,7 +174,9 @@ def get_http_auth(self, name): "password": password, } - def set_http_password(self, name, username, password): + def set_http_password( + self, name, username, password + ): # type: (str, str, str) -> None auth = {"username": username} if not self.keyring.is_available(): @@ -177,7 +186,7 @@ def set_http_password(self, name, username, password): self._config.auth_config_source.add_property("http-basic.{}".format(name), auth) - def delete_http_password(self, name): + def delete_http_password(self, name): # type: (str) -> None auth = self.get_http_auth(name) if not auth or "username" not in auth: return diff --git a/poetry/utils/setup_reader.py b/poetry/utils/setup_reader.py index 498210f45f5..48d511f9252 100644 --- a/poetry/utils/setup_reader.py +++ b/poetry/utils/setup_reader.py @@ -356,7 +356,9 @@ def _find_variable_in_body( if target.id == name: return elem.value - def _find_in_dict(self, dict_, name): # type: (ast.Call, str) -> Optional[Any] + def _find_in_dict( + self, dict_, name + ): # type: (Union[ast.Dict, ast.Call], str) -> Optional[Any] for key, val in zip(dict_.keys, dict_.values): if isinstance(key, ast.Str) and key.s == name: return val diff --git a/poetry/utils/shell.py b/poetry/utils/shell.py index 3c0e5fbe92c..e009903a6de 100644 --- a/poetry/utils/shell.py +++ b/poetry/utils/shell.py @@ -3,6 +3,7 @@ import sys from pathlib import Path +from typing import Any import pexpect @@ -78,7 +79,7 @@ def activate(self, env): # type: (VirtualEnv) -> None activate_path = env.path / bin_dir / activate_script c.sendline("{} {}".format(self._get_source_command(), activate_path)) - def resize(sig, data): + def resize(sig, data): # type: (Any, Any) -> None terminal = Terminal() c.setwinsize(terminal.height, terminal.width) @@ -90,7 +91,7 @@ def resize(sig, data): sys.exit(c.exitstatus) - def _get_activate_script(self): + def _get_activate_script(self): # type: () -> str if "fish" == self._name: suffix = ".fish" elif "csh" == self._name: @@ -102,7 +103,7 @@ def _get_activate_script(self): return "activate" + suffix - def _get_source_command(self): + def _get_source_command(self): # type: () -> str if "fish" == self._name: return "source" elif "csh" == self._name: diff --git a/poetry/version/version_selector.py b/poetry/version/version_selector.py index ea002860938..350e3ec7ca5 100644 --- a/poetry/version/version_selector.py +++ b/poetry/version/version_selector.py @@ -1,19 +1,25 @@ +from typing import TYPE_CHECKING +from typing import Optional from typing import Union from poetry.core.packages import Package from poetry.core.semver import Version +if TYPE_CHECKING: + from poetry.repositories import Pool # noqa + + class VersionSelector(object): - def __init__(self, pool): + def __init__(self, pool): # type: ("Pool") -> None self._pool = pool def find_best_candidate( self, package_name, # type: str - target_package_version=None, # type: Union[str, None] + target_package_version=None, # type: Optional[str] allow_prereleases=False, # type: bool - source=None, # type: str + source=None, # type: Optional[str] ): # type: (...) -> Union[Package, bool] """ Given a package name and optional version, @@ -52,12 +58,12 @@ def find_best_candidate( return False return package - def find_recommended_require_version(self, package): + def find_recommended_require_version(self, package): # type: (Package) -> str version = package.version return self._transform_version(version.text, package.pretty_version) - def _transform_version(self, version, pretty_version): + def _transform_version(self, version, pretty_version): # type: (str, str) -> str try: parsed = Version.parse(version) parts = [parsed.major, parsed.minor, parsed.patch] From d2485b8f849f0bbfd8f6933b625ea07f11f80615 Mon Sep 17 00:00:00 2001 From: Ryan Opel Date: Mon, 14 Sep 2020 10:36:57 -0700 Subject: [PATCH 081/222] Add option to install only dev dependencies Add an option to poetry install to install only dev dependencies, e.g. poetry install --dev-only. Fixes https://github.com/python-poetry/poetry/issues/2572 --- docs/docs/cli.md | 11 ++++++++++ poetry/console/commands/install.py | 4 +++- poetry/installation/installer.py | 12 +++++++++++ tests/installation/test_installer.py | 32 +++++++++++++++++++++++++++- 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/docs/docs/cli.md b/docs/docs/cli.md index 9ceb059bfe7..4675ce1023b 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -109,6 +109,13 @@ the `--no-dev` option. poetry install --no-dev ``` +Conversely, you can specify to the command that you only want to install the development dependencies +by passing the `--dev-only` option. Note that `--no-dev` takes priority if both options are passed. + +```bash +poetry install --dev-only +``` + If you want to remove old dependencies no longer present in the lock file, use the `--remove-untracked` option. @@ -142,9 +149,13 @@ If you want to skip this installation, use the `--no-root` option. poetry install --no-root ``` +Installation of your project's package is also skipped when the `--dev-only` +option is passed. + ### Options * `--no-dev`: Do not install dev dependencies. +* `--dev-only`: Only install dev dependencies. * `--no-root`: Do not install the root package (your project). * `--dry-run`: Output the operations but do not execute anything (implicitly enables --verbose). * `--remove-untracked`: Remove dependencies not presented in the lock file diff --git a/poetry/console/commands/install.py b/poetry/console/commands/install.py index 6a9ef2cb41d..af05e6a14f7 100644 --- a/poetry/console/commands/install.py +++ b/poetry/console/commands/install.py @@ -10,6 +10,7 @@ class InstallCommand(InstallerCommand): options = [ option("no-dev", None, "Do not install the development dependencies."), + option("dev-only", None, "Only install the development dependencies."), option( "no-root", None, "Do not install the root package (the current project)." ), @@ -64,6 +65,7 @@ def handle(self): self._installer.extras(extras) self._installer.dev_mode(not self.option("no-dev")) + self._installer.dev_only(self.option("dev-only")) self._installer.dry_run(self.option("dry-run")) self._installer.remove_untracked(self.option("remove-untracked")) self._installer.verbose(self._io.is_verbose()) @@ -73,7 +75,7 @@ def handle(self): if return_code != 0: return return_code - if self.option("no-root"): + if self.option("no-root") or self.option("dev-only"): return 0 try: diff --git a/poetry/installation/installer.py b/poetry/installation/installer.py index 2164d39f4b1..ae566ef776e 100644 --- a/poetry/installation/installer.py +++ b/poetry/installation/installer.py @@ -47,6 +47,7 @@ def __init__( self._verbose = False self._write_lock = True self._dev_mode = True + self._dev_only = False self._execute_operations = True self._lock = False @@ -136,6 +137,14 @@ def dev_mode(self, dev_mode=True): # type: (bool) -> Installer def is_dev_mode(self): # type: () -> bool return self._dev_mode + def dev_only(self, dev_only=False): # type: (bool) -> Installer + self._dev_only = dev_only + + return self + + def is_dev_only(self): # type: () -> bool + return self._dev_only + def update(self, update=True): # type: (bool) -> Installer self._update = update @@ -270,6 +279,9 @@ def _do_install(self, local_repo): if not self.is_dev_mode(): root = root.clone() del root.dev_requires[:] + elif self.is_dev_only(): + root = root.clone() + del root.requires[:] if self._io.is_verbose(): self._io.write_line("") diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 2adcb13cd25..67ee09b0c2c 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -267,7 +267,10 @@ def test_run_update_after_removing_dependencies( assert 1 == installer.executor.removals_count -def test_run_install_no_dev(installer, locker, repo, package, installed): +def _configure_run_install_dev(locker, repo, package, installed): + """ + Perform common test setup for `test_run_install_*dev*()` methods. + """ locker.locked(True) locker.mock_lock_data( { @@ -323,7 +326,34 @@ def test_run_install_no_dev(installer, locker, repo, package, installed): package.add_dependency(Factory.create_dependency("B", "~1.1")) package.add_dependency(Factory.create_dependency("C", "~1.2", category="dev")) + +def test_run_install_no_dev(installer, locker, repo, package, installed): + _configure_run_install_dev(locker, repo, package, installed) + + installer.dev_mode(False) + installer.run() + + assert 0 == installer.executor.installations_count + assert 0 == installer.executor.updates_count + assert 1 == installer.executor.removals_count + + +def test_run_install_dev_only(installer, locker, repo, package, installed): + _configure_run_install_dev(locker, repo, package, installed) + + installer.dev_only(True) + installer.run() + + assert 0 == installer.executor.installations_count + assert 0 == installer.executor.updates_count + assert 2 == installer.executor.removals_count + + +def test_run_install_no_dev_and_dev_only(installer, locker, repo, package, installed): + _configure_run_install_dev(locker, repo, package, installed) + installer.dev_mode(False) + installer.dev_only(True) installer.run() assert 0 == installer.executor.installations_count From a8d02ef59f60c001458f27b7cb2557a346cfb6b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 22 Jan 2021 18:21:28 +0100 Subject: [PATCH 082/222] Migrate to the new version of Cleo --- .flake8 | 4 +- .pre-commit-config.yaml | 14 +- poetry.lock | 42 +-- poetry/__main__.py | 2 +- poetry/console/__init__.py | 5 - poetry/console/application.py | 321 +++++++++++++----- poetry/console/commands/__init__.py | 18 - poetry/console/commands/add.py | 4 +- poetry/console/commands/build.py | 2 +- poetry/console/commands/cache/cache.py | 15 - poetry/console/commands/cache/clear.py | 6 +- poetry/console/commands/cache/list.py | 2 +- poetry/console/commands/command.py | 2 +- poetry/console/commands/config.py | 10 +- poetry/console/commands/debug/debug.py | 11 - poetry/console/commands/debug/info.py | 9 +- poetry/console/commands/debug/resolve.py | 23 +- poetry/console/commands/env/env.py | 16 - poetry/console/commands/env/info.py | 4 +- poetry/console/commands/env/list.py | 4 +- poetry/console/commands/env/remove.py | 4 +- poetry/console/commands/env/use.py | 4 +- poetry/console/commands/export.py | 4 +- poetry/console/commands/init.py | 2 +- poetry/console/commands/install.py | 10 +- poetry/console/commands/lock.py | 2 +- poetry/console/commands/new.py | 4 +- poetry/console/commands/publish.py | 2 +- poetry/console/commands/remove.py | 4 +- poetry/console/commands/run.py | 20 +- poetry/console/commands/search.py | 2 +- poetry/console/commands/self/self.py | 13 - poetry/console/commands/self/update.py | 13 +- poetry/console/commands/show.py | 27 +- poetry/console/commands/update.py | 4 +- poetry/console/commands/version.py | 4 +- poetry/console/config/__init__.py | 1 - poetry/console/config/application_config.py | 244 ------------- poetry/console/exceptions.py | 6 + poetry/{ => console}/io/__init__.py | 0 poetry/console/io/inputs/__init__.py | 0 poetry/console/io/inputs/run_argv_input.py | 81 +++++ poetry/factory.py | 4 +- poetry/installation/authenticator.py | 2 +- poetry/installation/executor.py | 58 ++-- poetry/installation/installer.py | 4 +- poetry/installation/pip_installer.py | 5 +- poetry/io/null_io.py | 13 - poetry/packages/locker.py | 4 +- poetry/publishing/uploader.py | 8 +- poetry/puzzle/provider.py | 16 +- poetry/puzzle/solver.py | 9 +- poetry/repositories/installed_repository.py | 6 +- poetry/utils/env.py | 2 +- poetry/utils/exporter.py | 2 +- pyproject.toml | 5 +- sonnet | 52 +-- tests/conftest.py | 15 +- tests/console/commands/debug/test_resolve.py | 12 +- tests/console/commands/env/test_use.py | 11 +- tests/console/commands/self/test_update.py | 2 +- tests/console/commands/test_add.py | 52 +-- tests/console/commands/test_init.py | 4 +- tests/console/commands/test_publish.py | 11 +- tests/console/commands/test_run.py | 14 +- tests/console/commands/test_search.py | 3 +- tests/console/commands/test_show.py | 20 +- tests/console/commands/test_version.py | 2 +- tests/console/conftest.py | 5 +- tests/helpers.py | 2 +- tests/inspection/test_info.py | 3 +- tests/installation/test_authenticator.py | 6 +- tests/installation/test_chef.py | 4 +- tests/installation/test_chooser.py | 20 +- tests/installation/test_executor.py | 20 +- tests/installation/test_installer.py | 14 +- tests/installation/test_installer_old.py | 2 +- tests/installation/test_pip_installer.py | 3 +- .../pep_561_stub_only/pkg-stubs/module.pyi | 1 + .../pkg-stubs/module.pyi | 1 + .../src/pkg-stubs/module.pyi | 1 + .../masonry/builders/test_editable_builder.py | 12 +- .../test_python_requirement_solution.py | 2 +- tests/mixology/version_solver/conftest.py | 2 +- tests/publishing/test_publisher.py | 4 +- tests/publishing/test_uploader.py | 3 +- tests/puzzle/test_provider.py | 2 +- tests/puzzle/test_solver.py | 14 +- tests/utils/test_env.py | 38 ++- tests/utils/test_exporter.py | 24 +- 90 files changed, 753 insertions(+), 731 deletions(-) delete mode 100644 poetry/console/commands/cache/cache.py delete mode 100644 poetry/console/commands/debug/debug.py delete mode 100644 poetry/console/commands/env/env.py delete mode 100644 poetry/console/commands/self/self.py delete mode 100644 poetry/console/config/__init__.py delete mode 100644 poetry/console/config/application_config.py create mode 100644 poetry/console/exceptions.py rename poetry/{ => console}/io/__init__.py (100%) create mode 100644 poetry/console/io/inputs/__init__.py create mode 100644 poetry/console/io/inputs/run_argv_input.py delete mode 100644 poetry/io/null_io.py diff --git a/.flake8 b/.flake8 index 130c44c6752..6005a30d32d 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,9 @@ [flake8] max-line-length = 88 ignore = E501, E203, W503 -per-file-ignores = __init__.py:F401 +per-file-ignores = + __init__.py:F401 + tests/console/commands/debug/test_resolve.py:W291 exclude = .git __pycache__ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7766f56e108..9377e9a6992 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,26 +1,30 @@ repos: - repo: https://github.com/psf/black - rev: 19.10b0 + rev: 20.8b1 hooks: - id: black - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.3 + rev: 3.8.4 hooks: - id: flake8 - repo: https://github.com/timothycrosley/isort - rev: 5.4.2 + rev: 5.7.0 hooks: - id: isort additional_dependencies: [toml] exclude: ^.*/?setup\.py$ - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 + rev: v3.4.0 hooks: - id: trailing-whitespace - exclude: ^tests/.*/fixtures/.* + exclude: | + (?x)( + ^tests/.*/fixtures/.* + | ^tests/console/commands/debug/test_resolve.py + ) - id: end-of-file-fixer exclude: ^tests/.*/fixtures/.* - id: debug-statements diff --git a/poetry.lock b/poetry.lock index b066ade9a7c..6a04096460f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -95,27 +95,15 @@ python-versions = "*" [[package]] name = "cleo" -version = "0.8.1" +version = "1.0.0a1" description = "Cleo allows you to create beautiful and testable command-line interfaces." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -clikit = ">=0.6.0,<0.7.0" - -[[package]] -name = "clikit" -version = "0.6.2" -description = "CliKit is a group of utilities to build beautiful and testable command line interfaces." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6,<4.0" [package.dependencies] -crashtest = {version = ">=0.3.0,<0.4.0", markers = "python_version >= \"3.6\" and python_version < \"4.0\""} -pastel = ">=0.2.0,<0.3.0" -pylev = ">=1.3,<2.0" +crashtest = ">=0.3.1,<0.4.0" +pylev = ">=1.3.0,<2.0.0" [[package]] name = "colorama" @@ -326,14 +314,6 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" pyparsing = ">=2.0.2" six = "*" -[[package]] -name = "pastel" -version = "0.2.1" -description = "Bring colors to your terminal." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - [[package]] name = "pexpect" version = "4.8.0" @@ -688,7 +668,7 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "077bd512e57f2e31d9f8b72b9a8b3a918f6844afdd3c7e25c2babc7f95fdfc4e" +content-hash = "c495920c853f794d4046d2d4cc47410ea076d4647e54fd3364e46051d5f18da3" [metadata.files] appdirs = [ @@ -762,12 +742,8 @@ chardet = [ {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, ] cleo = [ - {file = "cleo-0.8.1-py2.py3-none-any.whl", hash = "sha256:141cda6dc94a92343be626bb87a0b6c86ae291dfc732a57bf04310d4b4201753"}, - {file = "cleo-0.8.1.tar.gz", hash = "sha256:3d0e22d30117851b45970b6c14aca4ab0b18b1b53c8af57bed13208147e4069f"}, -] -clikit = [ - {file = "clikit-0.6.2-py2.py3-none-any.whl", hash = "sha256:71268e074e68082306e23d7369a7b99f824a0ef926e55ba2665e911f7208489e"}, - {file = "clikit-0.6.2.tar.gz", hash = "sha256:442ee5db9a14120635c5990bcdbfe7c03ada5898291f0c802f77be71569ded59"}, + {file = "cleo-1.0.0a1-py3-none-any.whl", hash = "sha256:e4a45adc6b56a04d350e7b4893352fdcc07d89d35991e5df16753e05a7c78c2b"}, + {file = "cleo-1.0.0a1.tar.gz", hash = "sha256:45bc5f04278c2f183c7ab77b3ec20f5204711fecb37ae688424c39ea8badf3fe"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, @@ -912,10 +888,6 @@ packaging = [ {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, ] -pastel = [ - {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"}, - {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, -] pexpect = [ {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, diff --git a/poetry/__main__.py b/poetry/__main__.py index b280ed84e4a..1c442536107 100644 --- a/poetry/__main__.py +++ b/poetry/__main__.py @@ -2,6 +2,6 @@ if __name__ == "__main__": - from .console import main + from .console.application import main sys.exit(main()) diff --git a/poetry/console/__init__.py b/poetry/console/__init__.py index 56adf27e2ee..e69de29bb2d 100644 --- a/poetry/console/__init__.py +++ b/poetry/console/__init__.py @@ -1,5 +0,0 @@ -from .application import Application - - -def main(): # type: () -> int - return Application().run() diff --git a/poetry/console/application.py b/poetry/console/application.py index 1fd9dbc0bc3..75d759efdc9 100644 --- a/poetry/console/application.py +++ b/poetry/console/application.py @@ -1,34 +1,77 @@ -import sys +import logging +import re +from importlib import import_module from typing import TYPE_CHECKING +from typing import Any +from typing import Callable +from typing import Optional +from typing import cast -from cleo import Application as BaseApplication +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.formatters.style import Style +from cleo.io.inputs.argv_input import ArgvInput +from cleo.io.inputs.input import Input +from cleo.io.io import IO +from cleo.io.outputs.output import Output +from cleo.loaders.factory_command_loader import FactoryCommandLoader from poetry.__version__ import __version__ -from .commands.about import AboutCommand -from .commands.add import AddCommand -from .commands.build import BuildCommand -from .commands.cache.cache import CacheCommand -from .commands.check import CheckCommand -from .commands.config import ConfigCommand -from .commands.debug.debug import DebugCommand -from .commands.env.env import EnvCommand -from .commands.export import ExportCommand -from .commands.init import InitCommand -from .commands.install import InstallCommand -from .commands.lock import LockCommand -from .commands.new import NewCommand -from .commands.publish import PublishCommand -from .commands.remove import RemoveCommand -from .commands.run import RunCommand -from .commands.search import SearchCommand -from .commands.self.self import SelfCommand -from .commands.shell import ShellCommand -from .commands.show import ShowCommand -from .commands.update import UpdateCommand -from .commands.version import VersionCommand -from .config import ApplicationConfig +from .commands.command import Command + + +def load_command(name: str) -> Callable: + def _load(): + module = import_module( + "poetry.console.commands.{}".format(".".join(name.split(" "))) + ) + command_class = getattr( + module, "{}Command".format("".join(c.title() for c in name.split(" "))) + ) + + return command_class() + + return _load + + +COMMANDS = [ + "about", + "add", + "build", + "check", + "config", + "export", + "init", + "install", + "lock", + "new", + "publish", + "remove", + "run", + "search", + "shell", + "show", + "update", + "version", + # Cache commands + "cache clear", + "cache list", + # Debug commands + "debug info", + "debug resolve", + # Env commands + "env info", + "env list", + "env remove", + "env use", + # Self commands + "self update", +] if TYPE_CHECKING: @@ -36,36 +79,24 @@ class Application(BaseApplication): - def __init__(self): # type: () -> None - super(Application, self).__init__( - "poetry", __version__, config=ApplicationConfig("poetry", __version__) - ) + def __init__(self) -> None: + super(Application, self).__init__("poetry", __version__) self._poetry = None - for command in self.get_default_commands(): - self.add(command) - - if sys.version_info[:2] < (3, 6): - python_version = "{}".format( - ".".join(str(v) for v in sys.version_info[:2]) - ) - poetry_feature_release = "1.2" - message = ( - "\n" - "Python {} will no longer be supported " - "in the next feature release of Poetry ({}).\n" - "You should consider updating your Python version to a supported one.\n\n" - "" - "Note that you will still be able to manage Python {} projects " - "by using the env command.\n" - "See https://python-poetry.org/docs/managing-environments/ " - "for more information." - ).format(python_version, poetry_feature_release, python_version) - self._preliminary_io.error_line("{}\n".format(message)) + dispatcher = EventDispatcher() + dispatcher.add_listener(COMMAND, self.register_command_loggers) + dispatcher.add_listener(COMMAND, self.set_env) + dispatcher.add_listener(COMMAND, self.set_installer) + self.set_event_dispatcher(dispatcher) + + command_loader = FactoryCommandLoader( + {name: load_command(name) for name in COMMANDS} + ) + self.set_command_loader(command_loader) @property - def poetry(self): # type: () -> "Poetry" + def poetry(self) -> "Poetry": from pathlib import Path from poetry.factory import Factory @@ -77,45 +108,173 @@ def poetry(self): # type: () -> "Poetry" return self._poetry - def reset_poetry(self): # type: () -> None + def reset_poetry(self) -> None: self._poetry = None - def get_default_commands(self): # type: () -> list - commands = [ - AboutCommand(), - AddCommand(), - BuildCommand(), - CheckCommand(), - ConfigCommand(), - ExportCommand(), - InitCommand(), - InstallCommand(), - LockCommand(), - NewCommand(), - PublishCommand(), - RemoveCommand(), - RunCommand(), - SearchCommand(), - ShellCommand(), - ShowCommand(), - UpdateCommand(), - VersionCommand(), + def create_io( + self, + input: Optional[Input] = None, + output: Optional[Output] = None, + error_output: Optional[Output] = None, + ) -> IO: + io = super(Application, self).create_io(input, output, error_output) + + # Set our own CLI styles + formatter = io.output.formatter + formatter.set_style("c1", Style("cyan")) + formatter.set_style("c2", Style("default", options=["bold"])) + formatter.set_style("info", Style("blue")) + formatter.set_style("comment", Style("green")) + formatter.set_style("warning", Style("yellow")) + formatter.set_style("debug", Style("default", options=["dark"])) + formatter.set_style("success", Style("green")) + + # Dark variants + formatter.set_style("c1_dark", Style("cyan", options=["dark"])) + formatter.set_style("c2_dark", Style("default", options=["bold", "dark"])) + formatter.set_style("success_dark", Style("green", options=["dark"])) + + io.output.set_formatter(formatter) + io.error_output.set_formatter(formatter) + + return io + + def _configure_io(self, io: IO) -> None: + # We need to check if the command being run + # is the "run" command. + definition = self.definition + try: + io.input.bind(definition) + except CleoException: + pass + + name = io.input.first_argument + if name == "run": + from .io.inputs.run_argv_input import RunArgvInput + + input = cast(ArgvInput, io.input) + run_input = RunArgvInput([self._name or ""] + input._tokens) + # For the run command reset the definition + # with only the set options (i.e. the options given before the command) + for option_name, value in input.options.items(): + if value: + option = definition.option(option_name) + run_input.add_parameter_option("--" + option.name) + if option.shortcut: + shortcuts = re.split(r"\|-?", option.shortcut.lstrip("-")) + shortcuts = [s for s in shortcuts if s] + for shortcut in shortcuts: + run_input.add_parameter_option("-" + shortcut.lstrip("-")) + + try: + run_input.bind(definition) + except CleoException: + pass + + for option_name, value in input.options.items(): + if value: + run_input.set_option(option_name, value) + + io.set_input(run_input) + + return super()._configure_io(io) + + def register_command_loggers( + self, event: ConsoleCommandEvent, event_name: str, _: Any + ) -> None: + from .logging.io_formatter import IOFormatter + from .logging.io_handler import IOHandler + + command = event.command + if not isinstance(command, Command): + return + + io = event.io + + loggers = [ + "poetry.packages.locker", + "poetry.packages.package", + "poetry.utils.password_manager", ] - # Cache commands - commands += [CacheCommand()] + loggers += command.loggers + + handler = IOHandler(io) + handler.setFormatter(IOFormatter()) + + for logger in loggers: + logger = logging.getLogger(logger) + + logger.handlers = [handler] + + level = logging.WARNING + # The builders loggers are special and we can actually + # start at the INFO level. + if logger.name.startswith("poetry.core.masonry.builders"): + level = logging.INFO + + if io.is_debug(): + level = logging.DEBUG + elif io.is_very_verbose() or io.is_verbose(): + level = logging.INFO + + logger.setLevel(level) + + def set_env(self, event: ConsoleCommandEvent, event_name: str, _: Any): + from .commands.env_command import EnvCommand - # Debug command - commands += [DebugCommand()] + command: EnvCommand = cast(EnvCommand, event.command) + if not isinstance(command, EnvCommand): + return - # Env command - commands += [EnvCommand()] + if command.env is not None: + return + + from poetry.utils.env import EnvManager + + io = event.io + poetry = command.poetry + + env_manager = EnvManager(poetry) + env = env_manager.create_venv(io) + + if env.is_venv() and io.is_verbose(): + io.write_line("Using virtualenv: {}".format(env.path)) + + command.set_env(env) + + def set_installer( + self, event: ConsoleCommandEvent, event_name: str, _: Any + ) -> None: + from .commands.installer_command import InstallerCommand + + command: InstallerCommand = cast(InstallerCommand, event.command) + if not isinstance(command, InstallerCommand): + return + + # If the command already has an installer + # we skip this step + if command.installer is not None: + return + + from poetry.installation.installer import Installer + + poetry = command.poetry + installer = Installer( + event.io, + command.env, + poetry.package, + poetry.locker, + poetry.pool, + poetry.config, + ) + installer.use_executor(poetry.config.get("experimental.new-installer", False)) + command.set_installer(installer) - # Self commands - commands += [SelfCommand()] - return commands +def main(): + return Application().run() if __name__ == "__main__": - Application().run() + main() diff --git a/poetry/console/commands/__init__.py b/poetry/console/commands/__init__.py index b8cb3f4e1f4..e69de29bb2d 100644 --- a/poetry/console/commands/__init__.py +++ b/poetry/console/commands/__init__.py @@ -1,18 +0,0 @@ -from .about import AboutCommand -from .add import AddCommand -from .build import BuildCommand -from .check import CheckCommand -from .config import ConfigCommand -from .export import ExportCommand -from .init import InitCommand -from .install import InstallCommand -from .lock import LockCommand -from .new import NewCommand -from .publish import PublishCommand -from .remove import RemoveCommand -from .run import RunCommand -from .search import SearchCommand -from .shell import ShellCommand -from .show import ShowCommand -from .update import UpdateCommand -from .version import VersionCommand diff --git a/poetry/console/commands/add.py b/poetry/console/commands/add.py index a3868b4bff8..abfcc5b15f6 100644 --- a/poetry/console/commands/add.py +++ b/poetry/console/commands/add.py @@ -2,8 +2,8 @@ from typing import Dict from typing import List -from cleo import argument -from cleo import option +from cleo.helpers import argument +from cleo.helpers import option from .init import InitCommand from .installer_command import InstallerCommand diff --git a/poetry/console/commands/build.py b/poetry/console/commands/build.py index bd938764726..82b5b0f86c7 100644 --- a/poetry/console/commands/build.py +++ b/poetry/console/commands/build.py @@ -1,4 +1,4 @@ -from cleo import option +from cleo.helpers import option from .env_command import EnvCommand diff --git a/poetry/console/commands/cache/cache.py b/poetry/console/commands/cache/cache.py deleted file mode 100644 index ff04fecb041..00000000000 --- a/poetry/console/commands/cache/cache.py +++ /dev/null @@ -1,15 +0,0 @@ -from poetry.console.commands.cache.list import CacheListCommand - -from ..command import Command -from .clear import CacheClearCommand - - -class CacheCommand(Command): - - name = "cache" - description = "Interact with Poetry's cache" - - commands = [CacheClearCommand(), CacheListCommand()] - - def handle(self): # type: () -> int - return self.call("help", self._config.name) diff --git a/poetry/console/commands/cache/clear.py b/poetry/console/commands/cache/clear.py index 9969ebbaa1c..581f83c041d 100644 --- a/poetry/console/commands/cache/clear.py +++ b/poetry/console/commands/cache/clear.py @@ -1,14 +1,14 @@ import os -from cleo import argument -from cleo import option +from cleo.helpers import argument +from cleo.helpers import option from ..command import Command class CacheClearCommand(Command): - name = "clear" + name = "cache clear" description = "Clears Poetry's cache." arguments = [argument("cache", description="The name of the cache to clear.")] diff --git a/poetry/console/commands/cache/list.py b/poetry/console/commands/cache/list.py index 090cba601df..e6346f98ebb 100644 --- a/poetry/console/commands/cache/list.py +++ b/poetry/console/commands/cache/list.py @@ -7,7 +7,7 @@ class CacheListCommand(Command): - name = "list" + name = "cache list" description = "List Poetry's caches." def handle(self): # type: () -> Optional[int] diff --git a/poetry/console/commands/command.py b/poetry/console/commands/command.py index a575f46044a..cb13c867909 100644 --- a/poetry/console/commands/command.py +++ b/poetry/console/commands/command.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING -from cleo import Command as BaseCommand +from cleo.commands.command import Command as BaseCommand if TYPE_CHECKING: diff --git a/poetry/console/commands/config.py b/poetry/console/commands/config.py index 5b35eb5a26d..30dc11ecf50 100644 --- a/poetry/console/commands/config.py +++ b/poetry/console/commands/config.py @@ -8,8 +8,8 @@ from typing import Optional from typing import Tuple -from cleo import argument -from cleo import option +from cleo.helpers import argument +from cleo.helpers import option from poetry.core.pyproject import PyProjectException from poetry.core.toml.file import TOMLFile @@ -81,7 +81,11 @@ def unique_config_values(self): # type: () -> Dict[str, Tuple[Any, Any, Any]] boolean_normalizer, False, ), - "installer.parallel": (boolean_validator, boolean_normalizer, True,), + "installer.parallel": ( + boolean_validator, + boolean_normalizer, + True, + ), } return unique_config_values diff --git a/poetry/console/commands/debug/debug.py b/poetry/console/commands/debug/debug.py deleted file mode 100644 index 468e2faad1f..00000000000 --- a/poetry/console/commands/debug/debug.py +++ /dev/null @@ -1,11 +0,0 @@ -from ..command import Command -from .info import DebugInfoCommand -from .resolve import DebugResolveCommand - - -class DebugCommand(Command): - - name = "debug" - description = "Debug various elements of Poetry." - - commands = [DebugInfoCommand().default(), DebugResolveCommand()] diff --git a/poetry/console/commands/debug/info.py b/poetry/console/commands/debug/info.py index 8950a3e5710..140b26725f6 100644 --- a/poetry/console/commands/debug/info.py +++ b/poetry/console/commands/debug/info.py @@ -1,13 +1,11 @@ import sys -from clikit.args import StringArgs - from ..command import Command class DebugInfoCommand(Command): - name = "info" + name = "debug info" description = "Shows debug information." def handle(self): # type: () -> int @@ -25,7 +23,6 @@ def handle(self): # type: () -> int ] ) ) - args = StringArgs("") - command = self.application.get_command("env").get_sub_command("info") + command = self.application.get("env info") - return command.run(args, self._io) + return command.run(self._io) diff --git a/poetry/console/commands/debug/resolve.py b/poetry/console/commands/debug/resolve.py index 743ef1d7443..ef60cbf134a 100644 --- a/poetry/console/commands/debug/resolve.py +++ b/poetry/console/commands/debug/resolve.py @@ -1,14 +1,15 @@ from typing import Optional -from cleo import argument -from cleo import option +from cleo.helpers import argument +from cleo.helpers import option +from cleo.io.outputs.output import Verbosity from ..init import InitCommand class DebugResolveCommand(InitCommand): - name = "resolve" + name = "debug resolve" description = "Debugs dependency resolution." arguments = [ @@ -30,9 +31,10 @@ class DebugResolveCommand(InitCommand): loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"] def handle(self): # type: () -> Optional[int] + from cleo.io.null_io import NullIO + from poetry.core.packages.project_package import ProjectPackage from poetry.factory import Factory - from poetry.io.null_io import NullIO from poetry.puzzle import Solver from poetry.repositories.pool import Pool from poetry.repositories.repository import Repository @@ -51,14 +53,12 @@ def handle(self): # type: () -> Optional[int] ) # Silencing output - is_quiet = self.io.output.is_quiet() - if not is_quiet: - self.io.output.set_quiet(True) + verbosity = self.io.output.verbosity + self.io.output.set_verbosity(Verbosity.QUIET) requirements = self._determine_requirements(packages) - if not is_quiet: - self.io.output.set_quiet(False) + self.io.output.set_verbosity(verbosity) for constraint in requirements: name = constraint.pop("name") @@ -103,7 +103,8 @@ def handle(self): # type: () -> Optional[int] return 0 - table = self.table([], style="borderless") + table = self.table([], style="compact") + table.style.set_vertical_border_chars("", " ") rows = [] if self.option("install"): @@ -136,4 +137,4 @@ def handle(self): # type: () -> Optional[int] rows.append(row) table.set_rows(rows) - table.render(self.io) + table.render() diff --git a/poetry/console/commands/env/env.py b/poetry/console/commands/env/env.py deleted file mode 100644 index f979b66e436..00000000000 --- a/poetry/console/commands/env/env.py +++ /dev/null @@ -1,16 +0,0 @@ -from ..command import Command -from .info import EnvInfoCommand -from .list import EnvListCommand -from .remove import EnvRemoveCommand -from .use import EnvUseCommand - - -class EnvCommand(Command): - - name = "env" - description = "Interact with Poetry's project environments." - - commands = [EnvInfoCommand(), EnvListCommand(), EnvRemoveCommand(), EnvUseCommand()] - - def handle(self): # type: () -> int - return self.call("help", self._config.name) diff --git a/poetry/console/commands/env/info.py b/poetry/console/commands/env/info.py index fc0e4a75936..dea2ade994a 100644 --- a/poetry/console/commands/env/info.py +++ b/poetry/console/commands/env/info.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING from typing import Optional -from cleo import option +from cleo.helpers import option from ..command import Command @@ -12,7 +12,7 @@ class EnvInfoCommand(Command): - name = "info" + name = "env info" description = "Displays information about the current environment." options = [option("path", "p", "Only display the environment's path.")] diff --git a/poetry/console/commands/env/list.py b/poetry/console/commands/env/list.py index 46423d142c7..f422cc8ef2a 100644 --- a/poetry/console/commands/env/list.py +++ b/poetry/console/commands/env/list.py @@ -1,11 +1,11 @@ -from cleo import option +from cleo.helpers import option from ..command import Command class EnvListCommand(Command): - name = "list" + name = "env list" description = "Lists all virtualenvs associated with the current project." options = [option("full-path", None, "Output the full paths of the virtualenvs.")] diff --git a/poetry/console/commands/env/remove.py b/poetry/console/commands/env/remove.py index 4b4bca29c9d..c8e5f1360e1 100644 --- a/poetry/console/commands/env/remove.py +++ b/poetry/console/commands/env/remove.py @@ -1,11 +1,11 @@ -from cleo import argument +from cleo.helpers import argument from ..command import Command class EnvRemoveCommand(Command): - name = "remove" + name = "env remove" description = "Removes a specific virtualenv associated with the project." arguments = [ diff --git a/poetry/console/commands/env/use.py b/poetry/console/commands/env/use.py index 57db24c97bd..13728c1d9ee 100644 --- a/poetry/console/commands/env/use.py +++ b/poetry/console/commands/env/use.py @@ -1,11 +1,11 @@ -from cleo import argument +from cleo.helpers import argument from ..command import Command class EnvUseCommand(Command): - name = "use" + name = "env use" description = "Activates or creates a new virtualenv for the current project." arguments = [argument("python", "The python executable to use.")] diff --git a/poetry/console/commands/export.py b/poetry/console/commands/export.py index 11ef2a37377..6d4f2a830fc 100644 --- a/poetry/console/commands/export.py +++ b/poetry/console/commands/export.py @@ -1,4 +1,4 @@ -from cleo import option +from cleo.helpers import option from poetry.utils.exporter import Exporter @@ -50,7 +50,7 @@ def handle(self): # type: () -> None elif self.io.is_verbose(): options.append(("-v", None)) - self.call("lock", options) + self.call("lock", " ".join(options)) if not locker.is_fresh(): self.line( diff --git a/poetry/console/commands/init.py b/poetry/console/commands/init.py index 92870cb614a..110f97a9a9c 100644 --- a/poetry/console/commands/init.py +++ b/poetry/console/commands/init.py @@ -13,7 +13,7 @@ from typing import Tuple from typing import Union -from cleo import option +from cleo.helpers import option from tomlkit import inline_table from poetry.core.pyproject import PyProjectException diff --git a/poetry/console/commands/install.py b/poetry/console/commands/install.py index f8d1ab0c560..d07c5cef93f 100644 --- a/poetry/console/commands/install.py +++ b/poetry/console/commands/install.py @@ -1,4 +1,4 @@ -from cleo import option +from cleo.helpers import option from .installer_command import InstallerCommand @@ -21,7 +21,9 @@ class InstallCommand(InstallerCommand): "(implicitly enables --verbose).", ), option( - "remove-untracked", None, "Removes packages not present in the lock file.", + "remove-untracked", + None, + "Removes packages not present in the lock file.", ), option( "extras", @@ -87,7 +89,7 @@ def handle(self): # type: () -> int return 0 self.line("") - if not self._io.supports_ansi() or self.io.is_debug(): + if not self._io.output.is_decorated() or self.io.is_debug(): self.line( "Installing the current project: {} ({})".format( self.poetry.package.pretty_name, self.poetry.package.pretty_version @@ -106,7 +108,7 @@ def handle(self): # type: () -> int builder.build() - if self._io.supports_ansi() and not self.io.is_debug(): + if self._io.output.is_decorated() and not self.io.is_debug(): self.overwrite( "Installing the current project: {} ({})".format( self.poetry.package.pretty_name, self.poetry.package.pretty_version diff --git a/poetry/console/commands/lock.py b/poetry/console/commands/lock.py index 59beb1386e2..d2895e8bee6 100644 --- a/poetry/console/commands/lock.py +++ b/poetry/console/commands/lock.py @@ -1,4 +1,4 @@ -from cleo import option +from cleo.helpers import option from .installer_command import InstallerCommand diff --git a/poetry/console/commands/new.py b/poetry/console/commands/new.py index 8d709fb6c4a..3276ee7d9fd 100644 --- a/poetry/console/commands/new.py +++ b/poetry/console/commands/new.py @@ -1,7 +1,7 @@ import sys -from cleo import argument -from cleo import option +from cleo.helpers import argument +from cleo.helpers import option from poetry.utils.helpers import module_name diff --git a/poetry/console/commands/publish.py b/poetry/console/commands/publish.py index cedbca85468..724e868a58a 100644 --- a/poetry/console/commands/publish.py +++ b/poetry/console/commands/publish.py @@ -1,7 +1,7 @@ from pathlib import Path from typing import Optional -from cleo import option +from cleo.helpers import option from .command import Command diff --git a/poetry/console/commands/remove.py b/poetry/console/commands/remove.py index 3fb42713ced..15e05de2860 100644 --- a/poetry/console/commands/remove.py +++ b/poetry/console/commands/remove.py @@ -1,5 +1,5 @@ -from cleo import argument -from cleo import option +from cleo.helpers import argument +from cleo.helpers import option from .installer_command import InstallerCommand diff --git a/poetry/console/commands/run.py b/poetry/console/commands/run.py index bafa5ce2cea..9879b3ed09c 100644 --- a/poetry/console/commands/run.py +++ b/poetry/console/commands/run.py @@ -1,7 +1,7 @@ from typing import Any from typing import Union -from cleo import argument +from cleo.helpers import argument from .env_command import EnvCommand @@ -15,13 +15,6 @@ class RunCommand(EnvCommand): argument("args", "The command and arguments/options to run.", multiple=True) ] - def __init__(self): # type: () -> None - from poetry.console.args.run_args_parser import RunArgsParser - - super(RunCommand, self).__init__() - - self.config.set_args_parser(RunArgsParser()) - def handle(self): # type: () -> Any args = self.argument("args") script = args[0] @@ -32,6 +25,17 @@ def handle(self): # type: () -> Any return self.env.execute(*args) + @property + def _module(self): + from poetry.core.masonry.utils.module import Module + + poetry = self.poetry + package = poetry.package + path = poetry.file.parent + module = Module(package.name, path.as_posix(), package.packages) + + return module + def run_script(self, script, args): # type: (Union[str, dict], str) -> Any if isinstance(script, dict): script = script["callable"] diff --git a/poetry/console/commands/search.py b/poetry/console/commands/search.py index 3ca8ac8e94d..27c4671a45b 100644 --- a/poetry/console/commands/search.py +++ b/poetry/console/commands/search.py @@ -1,4 +1,4 @@ -from cleo import argument +from cleo.helpers import argument from .command import Command diff --git a/poetry/console/commands/self/self.py b/poetry/console/commands/self/self.py deleted file mode 100644 index 29a0214c230..00000000000 --- a/poetry/console/commands/self/self.py +++ /dev/null @@ -1,13 +0,0 @@ -from ..command import Command -from .update import SelfUpdateCommand - - -class SelfCommand(Command): - - name = "self" - description = "Interact with Poetry directly." - - commands = [SelfUpdateCommand()] - - def handle(self): # type: () -> int - return self.call("help", self._config.name) diff --git a/poetry/console/commands/self/update.py b/poetry/console/commands/self/update.py index 279de274188..d0d00ddbefd 100644 --- a/poetry/console/commands/self/update.py +++ b/poetry/console/commands/self/update.py @@ -14,9 +14,10 @@ from typing import TYPE_CHECKING from typing import Any -from cleo import argument -from cleo import option +from cleo.helpers import argument +from cleo.helpers import option +from poetry.console.exceptions import PoetrySimpleConsoleException from poetry.core.packages import Dependency from ..command import Command @@ -59,7 +60,7 @@ class SelfUpdateCommand(Command): - name = "update" + name = "self update" description = "Updates Poetry to the latest version." arguments = [argument("version", "The version to update to.", optional=True)] @@ -253,9 +254,9 @@ def _check_recommended_installation(self): # type: () -> None try: current.relative_to(self.home) except ValueError: - raise RuntimeError( - "Poetry was not installed with the recommended installer. " - "Cannot update automatically." + raise PoetrySimpleConsoleException( + "Poetry was not installed with the recommended installer, " + "so it cannot be updated automatically." ) def _get_release_name(self, version): # type: ("Version") -> str diff --git a/poetry/console/commands/show.py b/poetry/console/commands/show.py index 545717be8ee..66c505f5545 100644 --- a/poetry/console/commands/show.py +++ b/poetry/console/commands/show.py @@ -4,8 +4,8 @@ from typing import Optional from typing import Union -from cleo import argument -from cleo import option +from cleo.helpers import argument +from cleo.helpers import option from .env_command import EnvCommand @@ -47,9 +47,9 @@ class ShowCommand(EnvCommand): colors = ["cyan", "yellow", "green", "magenta", "blue"] def handle(self): # type: () -> Optional[int] - from clikit.utils.terminal import Terminal + from cleo.io.null_io import NullIO + from cleo.terminal import Terminal - from poetry.io.null_io import NullIO from poetry.puzzle.solver import Solver from poetry.repositories.installed_repository import InstalledRepository from poetry.repositories.pool import Pool @@ -62,7 +62,7 @@ def handle(self): # type: () -> Optional[int] self.init_styles(self.io) if self.option("outdated"): - self._args.set_option("latest", True) + self._io.input.set_option("latest", True) include_dev = not self.option("no-dev") locked_repo = self.poetry.locker.locked_repository(True) @@ -82,7 +82,6 @@ def handle(self): # type: () -> Optional[int] return 0 table = self.table(style="compact") - # table.style.line_vc_char = "" locked_packages = locked_repo.packages pool = Pool(ignore_repository_names=True) pool.add_repository(locked_repo) @@ -155,7 +154,7 @@ def handle(self): # type: () -> Optional[int] continue current_length = len(locked.pretty_name) - if not self._io.output.supports_ansi(): + if not self._io.output.is_decorated(): installed_status = self.get_installed_status(locked, installed_repo) if installed_status == "not-installed": @@ -218,7 +217,7 @@ def handle(self): # type: () -> Optional[int] if installed_status == "not-installed": color = "red" - if not self._io.output.supports_ansi(): + if not self._io.output.is_decorated(): # Non installed in non decorated mode install_marker = " (!)" @@ -362,7 +361,7 @@ def _display_tree( ) def _write_tree_line(self, io, line): # type: ("IO", str) -> None - if not io.output.supports_ansi(): + if not io.output.supports_utf8(): line = line.replace("└", "`-") line = line.replace("├", "|-") line = line.replace("──", "-") @@ -371,17 +370,17 @@ def _write_tree_line(self, io, line): # type: ("IO", str) -> None io.write_line(line) def init_styles(self, io): # type: ("IO") -> None - from clikit.api.formatter import Style + from cleo.formatters.style import Style for color in self.colors: - style = Style(color).fg(color) - io.output.formatter.add_style(style) - io.error_output.formatter.add_style(style) + style = Style(color) + io.output.formatter.set_style(color, style) + io.error_output.formatter.set_style(color, style) def find_latest_package( self, package, include_dev ): # type: ("Package", bool) -> Union["Package", bool] - from clikit.io import NullIO + from cleo.io.null_io import NullIO from poetry.puzzle.provider import Provider from poetry.version.version_selector import VersionSelector diff --git a/poetry/console/commands/update.py b/poetry/console/commands/update.py index 77c0adf52cc..42bb64aba37 100644 --- a/poetry/console/commands/update.py +++ b/poetry/console/commands/update.py @@ -1,5 +1,5 @@ -from cleo import argument -from cleo import option +from cleo.helpers import argument +from cleo.helpers import option from .installer_command import InstallerCommand diff --git a/poetry/console/commands/version.py b/poetry/console/commands/version.py index 403c357390a..2fadaac4b39 100644 --- a/poetry/console/commands/version.py +++ b/poetry/console/commands/version.py @@ -1,5 +1,5 @@ -from cleo import argument -from cleo import option +from cleo.helpers import argument +from cleo.helpers import option from .command import Command diff --git a/poetry/console/config/__init__.py b/poetry/console/config/__init__.py deleted file mode 100644 index 14e86b4365b..00000000000 --- a/poetry/console/config/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .application_config import ApplicationConfig diff --git a/poetry/console/config/application_config.py b/poetry/console/config/application_config.py deleted file mode 100644 index 563e6b55db0..00000000000 --- a/poetry/console/config/application_config.py +++ /dev/null @@ -1,244 +0,0 @@ -import logging - -from typing import Any - -from cleo.config import ApplicationConfig as BaseApplicationConfig -from clikit.api.application.application import Application -from clikit.api.args.raw_args import RawArgs -from clikit.api.event import PRE_HANDLE -from clikit.api.event import PreHandleEvent -from clikit.api.event import PreResolveEvent -from clikit.api.event.event_dispatcher import EventDispatcher -from clikit.api.exceptions import CliKitException -from clikit.api.formatter import Style -from clikit.api.io import Input -from clikit.api.io import InputStream -from clikit.api.io import Output -from clikit.api.io import OutputStream -from clikit.api.io.flags import DEBUG -from clikit.api.io.flags import VERBOSE -from clikit.api.io.flags import VERY_VERBOSE -from clikit.api.io.io import IO -from clikit.formatter import AnsiFormatter -from clikit.formatter import PlainFormatter -from clikit.io.input_stream import StandardInputStream -from clikit.io.output_stream import ErrorOutputStream -from clikit.io.output_stream import StandardOutputStream - -from poetry.console.commands.command import Command -from poetry.console.commands.env_command import EnvCommand -from poetry.console.commands.installer_command import InstallerCommand -from poetry.console.logging.io_formatter import IOFormatter -from poetry.console.logging.io_handler import IOHandler -from poetry.mixology.solutions.providers import PythonRequirementSolutionProvider - - -class ApplicationConfig(BaseApplicationConfig): - def configure(self): # type: () -> None - super(ApplicationConfig, self).configure() - - self.add_style(Style("c1").fg("cyan")) - self.add_style(Style("c2").fg("default").bold()) - self.add_style(Style("info").fg("blue")) - self.add_style(Style("comment").fg("green")) - self.add_style(Style("error").fg("red").bold()) - self.add_style(Style("warning").fg("yellow").bold()) - self.add_style(Style("debug").fg("default").dark()) - self.add_style(Style("success").fg("green")) - - # Dark variants - self.add_style(Style("c1_dark").fg("cyan").dark()) - self.add_style(Style("c2_dark").fg("default").bold().dark()) - self.add_style(Style("success_dark").fg("green").dark()) - - self.add_event_listener(PRE_HANDLE, self.register_command_loggers) - self.add_event_listener(PRE_HANDLE, self.set_env) - self.add_event_listener(PRE_HANDLE, self.set_installer) - - self._solution_provider_repository.register_solution_providers( - [PythonRequirementSolutionProvider] - ) - - def register_command_loggers( - self, event, event_name, _ - ): # type: (PreHandleEvent, str, Any) -> None - command = event.command.config.handler - if not isinstance(command, Command): - return - - io = event.io - - loggers = [ - "poetry.packages.locker", - "poetry.packages.package", - "poetry.utils.password_manager", - ] - - loggers += command.loggers - - handler = IOHandler(io) - handler.setFormatter(IOFormatter()) - - for logger in loggers: - logger = logging.getLogger(logger) - - logger.handlers = [handler] - - level = logging.WARNING - # The builders loggers are special and we can actually - # start at the INFO level. - if logger.name.startswith("poetry.core.masonry.builders"): - level = logging.INFO - - if io.is_debug(): - level = logging.DEBUG - elif io.is_very_verbose() or io.is_verbose(): - level = logging.INFO - - logger.setLevel(level) - - def set_env(self, event, event_name, _): # type: (PreHandleEvent, str, Any) -> None - from poetry.utils.env import EnvManager - - command = event.command.config.handler # type: EnvCommand - if not isinstance(command, EnvCommand): - return - - if command.env is not None: - return - - io = event.io - poetry = command.poetry - - env_manager = EnvManager(poetry) - env = env_manager.create_venv(io) - - if env.is_venv() and io.is_verbose(): - io.write_line("Using virtualenv: {}".format(env.path)) - - command.set_env(env) - - def set_installer( - self, event, event_name, _ - ): # type: (PreHandleEvent, str, Any) -> None - command = event.command.config.handler # type: InstallerCommand - if not isinstance(command, InstallerCommand): - return - - # If the command already has an installer - # we skip this step - if command.installer is not None: - return - - from poetry.installation.installer import Installer - - poetry = command.poetry - installer = Installer( - event.io, - command.env, - poetry.package, - poetry.locker, - poetry.pool, - poetry.config, - ) - installer.use_executor(poetry.config.get("experimental.new-installer", False)) - command.set_installer(installer) - - def resolve_help_command( - self, event, event_name, dispatcher - ): # type: (PreResolveEvent, str, EventDispatcher) -> None - args = event.raw_args - application = event.application - - if args.has_option_token("-h") or args.has_option_token("--help"): - from clikit.api.resolver import ResolvedCommand - - try: - resolved_command = self.command_resolver.resolve(args, application) - except CliKitException: - # We weren't able to resolve the command, - # due to a parse error most likely, - # so we fall back on the default behavior - return super(ApplicationConfig, self).resolve_help_command( - event, event_name, dispatcher - ) - - # If the current command is the run one, skip option - # check and interpret them as part of the executed command - if resolved_command.command.name == "run": - event.set_resolved_command(resolved_command) - - return event.stop_propagation() - - command = application.get_command("help") - - # Enable lenient parsing - parsed_args = command.parse(args, True) - - event.set_resolved_command(ResolvedCommand(command, parsed_args)) - event.stop_propagation() - - def create_io( - self, - application, - args, - input_stream=None, - output_stream=None, - error_stream=None, - ): # type: (Application, RawArgs, InputStream, OutputStream, OutputStream) -> IO - if input_stream is None: - input_stream = StandardInputStream() - - if output_stream is None: - output_stream = StandardOutputStream() - - if error_stream is None: - error_stream = ErrorOutputStream() - - style_set = application.config.style_set - - if output_stream.supports_ansi(): - output_formatter = AnsiFormatter(style_set) - else: - output_formatter = PlainFormatter(style_set) - - if error_stream.supports_ansi(): - error_formatter = AnsiFormatter(style_set) - else: - error_formatter = PlainFormatter(style_set) - - io = self.io_class( - Input(input_stream), - Output(output_stream, output_formatter), - Output(error_stream, error_formatter), - ) - - resolved_command = application.resolve_command(args) - # If the current command is the run one, skip option - # check and interpret them as part of the executed command - if resolved_command.command.name == "run": - return io - - if args.has_option_token("--no-ansi"): - formatter = PlainFormatter(style_set) - io.output.set_formatter(formatter) - io.error_output.set_formatter(formatter) - elif args.has_option_token("--ansi"): - formatter = AnsiFormatter(style_set, True) - io.output.set_formatter(formatter) - io.error_output.set_formatter(formatter) - - if args.has_option_token("-vvv") or self.is_debug(): - io.set_verbosity(DEBUG) - elif args.has_option_token("-vv"): - io.set_verbosity(VERY_VERBOSE) - elif args.has_option_token("-v"): - io.set_verbosity(VERBOSE) - - if args.has_option_token("--quiet") or args.has_option_token("-q"): - io.set_quiet(True) - - if args.has_option_token("--no-interaction") or args.has_option_token("-n"): - io.set_interactive(False) - - return io diff --git a/poetry/console/exceptions.py b/poetry/console/exceptions.py new file mode 100644 index 00000000000..04e2d84ffa7 --- /dev/null +++ b/poetry/console/exceptions.py @@ -0,0 +1,6 @@ +from cleo.exceptions import CleoSimpleException + + +class PoetrySimpleConsoleException(CleoSimpleException): + + pass diff --git a/poetry/io/__init__.py b/poetry/console/io/__init__.py similarity index 100% rename from poetry/io/__init__.py rename to poetry/console/io/__init__.py diff --git a/poetry/console/io/inputs/__init__.py b/poetry/console/io/inputs/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/poetry/console/io/inputs/run_argv_input.py b/poetry/console/io/inputs/run_argv_input.py new file mode 100644 index 00000000000..e052f3e9c9a --- /dev/null +++ b/poetry/console/io/inputs/run_argv_input.py @@ -0,0 +1,81 @@ +from typing import List +from typing import Optional +from typing import Union + +from cleo.io.inputs.argv_input import ArgvInput +from cleo.io.inputs.definition import Definition + + +class RunArgvInput(ArgvInput): + def __init__( + self, argv: Optional[List[str]] = None, definition: Optional[Definition] = None + ) -> None: + super().__init__(argv, definition=definition) + + self._parameter_options = [] + + @property + def first_argument(self) -> Optional[str]: + return "run" + + def add_parameter_option(self, name: str) -> None: + self._parameter_options.append(name) + + def has_parameter_option( + self, values: Union[str, List[str]], only_params: bool = False + ) -> bool: + if not isinstance(values, list): + values = [values] + + for token in self._tokens: + if only_params and token == "--": + return False + + for value in values: + if value not in self._parameter_options: + continue + + # 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 + + if token == value or leading != "" and token.find(leading) == 0: + return True + + return False + + def _parse(self) -> None: + parse_options = True + self._parsed = self._tokens[:] + + try: + token = self._parsed.pop(0) + except IndexError: + token = None + + while token is not None: + if parse_options and token == "": + self._parse_argument(token) + elif parse_options and token == "--": + parse_options = False + elif parse_options and token.find("--") == 0: + if token in self._parameter_options: + self._parse_long_option(token) + else: + self._parse_argument(token) + elif parse_options and token[0] == "-" and token != "-": + if token in self._parameter_options: + self._parse_short_option(token) + else: + self._parse_argument(token) + else: + self._parse_argument(token) + + try: + token = self._parsed.pop(0) + except IndexError: + token = None diff --git a/poetry/factory.py b/poetry/factory.py index 0e3ef0f6046..d8a9da7ed77 100644 --- a/poetry/factory.py +++ b/poetry/factory.py @@ -5,14 +5,14 @@ from typing import Dict from typing import Optional -from clikit.api.io.io import IO +from cleo.io.io import IO +from cleo.io.null_io import NullIO from poetry.core.factory import Factory as BaseFactory from poetry.core.toml.file import TOMLFile from .config.config import Config from .config.file_config_source import FileConfigSource -from .io.null_io import NullIO from .locations import CONFIG_DIR from .packages.locker import Locker from .poetry import Poetry diff --git a/poetry/installation/authenticator.py b/poetry/installation/authenticator.py index 1e7a72e751a..f40c03d2b7e 100644 --- a/poetry/installation/authenticator.py +++ b/poetry/installation/authenticator.py @@ -17,7 +17,7 @@ from typing import Optional from typing import Tuple - from clikit.api.io import IO + from cleo.io.io import IO from poetry.config.config import Config diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index 8123c2d6956..ecfd2f7ce12 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -14,10 +14,11 @@ from typing import List from typing import Union +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 -from poetry.io.null_io import NullIO from poetry.utils._compat import decode from poetry.utils.env import EnvCommandError from poetry.utils.helpers import safe_rmtree @@ -91,7 +92,7 @@ def removals_count(self): # type: () -> int return self._executed["uninstall"] def supports_fancy_output(self): # type: () -> bool - return self._io.supports_ansi() and not self._dry_run + return self._io.output.is_decorated() and not self._dry_run def disable(self): # type: () -> "Executor" self._enabled = False @@ -176,7 +177,7 @@ def _write(self, operation, line): # type: ("OperationTypes", str) -> None with self._lock: section = self._sections[id(operation)] - section.output.clear() + section.clear() section.write(line) def _execute_operation(self, operation): # type: ("OperationTypes") -> None @@ -225,13 +226,15 @@ def _execute_operation(self, operation): # type: ("OperationTypes") -> None raise KeyboardInterrupt except Exception as e: try: - from clikit.ui.components.exception_trace import ExceptionTrace + from cleo.ui.exception_trace import ExceptionTrace if not self.supports_fancy_output(): io = self._io else: - message = " {message}: Failed".format( - message=self.get_operation_message(operation, error=True), + message = ( + " {message}: Failed".format( + message=self.get_operation_message(operation, error=True), + ) ) self._write(operation, message) io = self._sections.get(id(operation), self._io) @@ -268,7 +271,8 @@ def _do_execute_operation(self, operation): # type: ("OperationTypes") -> int "Skipped " "for the following reason: " "{reason}".format( - message=operation_message, reason=operation.skip_reason, + message=operation_message, + reason=operation.skip_reason, ), ) @@ -430,8 +434,10 @@ def _execute_update(self, operation): # type: (Union[Install, Update]) -> int return self._update(operation) def _execute_uninstall(self, operation): # type: (Uninstall) -> int - message = " • {message}: Removing...".format( - message=self.get_operation_message(operation), + message = ( + " • {message}: Removing...".format( + message=self.get_operation_message(operation), + ) ) self._write(operation, message) @@ -453,8 +459,10 @@ def _install(self, operation): # type: (Union[Install, Update]) -> int archive = self._download(operation) operation_message = self.get_operation_message(operation) - message = " • {message}: Installing...".format( - message=operation_message, + message = ( + " • {message}: Installing...".format( + message=operation_message, + ) ) self._write(operation, message) @@ -487,8 +495,10 @@ def _remove(self, operation): # type: (Uninstall) -> int def _prepare_file(self, operation): # type: (Union[Install, Update]) -> Path package = operation.package - message = " • {message}: Preparing...".format( - message=self.get_operation_message(operation), + message = ( + " • {message}: Preparing...".format( + message=self.get_operation_message(operation), + ) ) self._write(operation, message) @@ -506,8 +516,10 @@ def _install_directory(self, operation): # type: (Union[Install, Update]) -> in package = operation.package operation_message = self.get_operation_message(operation) - message = " • {message}: Building...".format( - message=operation_message, + message = ( + " • {message}: Building...".format( + message=operation_message, + ) ) self._write(operation, message) @@ -570,8 +582,10 @@ def _install_git(self, operation): # type: (Union[Install, Update]) -> int package = operation.package operation_message = self.get_operation_message(operation) - message = " • {message}: Cloning...".format( - message=operation_message, + message = ( + " • {message}: Cloning...".format( + message=operation_message, + ) ) self._write(operation, message) @@ -637,18 +651,20 @@ def _download_archive( ) wheel_size = response.headers.get("content-length") operation_message = self.get_operation_message(operation) - message = " • {message}: Downloading...".format( - message=operation_message, + message = ( + " • {message}: Downloading...".format( + message=operation_message, + ) ) progress = None if self.supports_fancy_output(): if wheel_size is None: self._write(operation, message) else: - from clikit.ui.components.progress_bar import ProgressBar + from cleo.ui.progress_bar import ProgressBar progress = ProgressBar( - self._sections[id(operation)].output, max=int(wheel_size) + self._sections[id(operation)], max=int(wheel_size) ) progress.set_format(message + " %percent%%") diff --git a/poetry/installation/installer.py b/poetry/installation/installer.py index ace3ee5bcf0..ecd0ba1ad78 100644 --- a/poetry/installation/installer.py +++ b/poetry/installation/installer.py @@ -4,11 +4,11 @@ from typing import Optional from typing import Union -from clikit.api.io import IO +from cleo.io.io import IO +from cleo.io.null_io import NullIO from poetry.config.config import Config from poetry.core.packages.project_package import ProjectPackage -from poetry.io.null_io import NullIO from poetry.packages import Locker from poetry.repositories import Pool from poetry.repositories import Repository diff --git a/poetry/installation/pip_installer.py b/poetry/installation/pip_installer.py index e4a89349d0a..6e5f7bdcc48 100644 --- a/poetry/installation/pip_installer.py +++ b/poetry/installation/pip_installer.py @@ -7,7 +7,7 @@ from typing import Any from typing import Union -from clikit.api.io import IO +from cleo.io.io import IO from poetry.core.pyproject.toml import PyProjectTOML from poetry.repositories.pool import Pool @@ -184,8 +184,9 @@ def create_temporary_requirement(self, package): # type: ("Package") -> str return name def install_directory(self, package): # type: ("Package") -> Union[str, int] + from cleo.io.null_io import NullIO + from poetry.factory import Factory - from poetry.io.null_io import NullIO if package.root_dir: req = (package.root_dir / package.source_url).as_posix() diff --git a/poetry/io/null_io.py b/poetry/io/null_io.py deleted file mode 100644 index 786b188a4e2..00000000000 --- a/poetry/io/null_io.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Any - -from cleo.io.io_mixin import IOMixin -from clikit.io import NullIO as BaseNullIO - - -class NullIO(IOMixin, BaseNullIO): - """ - A wrapper around CliKit's NullIO. - """ - - def __init__(self, *args, **kwargs): # type: (*Any, **Any) -> None - super(NullIO, self).__init__(*args, **kwargs) diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index 80350f66fe4..4b3adb19542 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -351,7 +351,9 @@ def get_project_dependency_packages( if extra_package_names is not None: extra_package_names = set( get_extra_package_names( - repository.packages, self.lock_data.get("extras", {}), extras or (), + repository.packages, + self.lock_data.get("extras", {}), + extras or (), ) ) diff --git a/poetry/publishing/uploader.py b/poetry/publishing/uploader.py index 43f4589b383..9c712213158 100644 --- a/poetry/publishing/uploader.py +++ b/poetry/publishing/uploader.py @@ -239,6 +239,8 @@ def _do_upload( def _upload_file( self, session, url, file, dry_run=False ): # type: (requests.Session, str, Path, Optional[bool]) -> requests.Response + from cleo.ui.progress_bar import ProgressBar + data = self.post_data(file) data.update( { @@ -255,7 +257,7 @@ def _upload_file( ("content", (file.name, fp, "application/octet-stream")) ) encoder = MultipartEncoder(data_to_send) - bar = self._io.progress_bar(encoder.len) + bar = ProgressBar(self._io, max=encoder.len) bar.set_format( " - Uploading {0} %percent%%".format(file.name) ) @@ -283,7 +285,7 @@ def _upload_file( ) bar.finish() elif resp.status_code == 301: - if self._io.output.supports_ansi(): + if self._io.output.is_decorated(): self._io.overwrite( " - Uploading {0} {1}".format( file.name, "FAILED" @@ -294,7 +296,7 @@ def _upload_file( "Is the URL missing a trailing slash?" ) except (requests.ConnectionError, requests.HTTPError) as e: - if self._io.output.supports_ansi(): + if self._io.output.is_decorated(): self._io.overwrite( " - Uploading {0} {1}".format( file.name, "FAILED" diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index 43d6ed63df3..63ce89a4c8d 100644 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -1,6 +1,7 @@ import logging import os import re +import time import urllib.parse from contextlib import contextmanager @@ -13,7 +14,7 @@ from typing import Optional from typing import Union -from clikit.ui.components import ProgressIndicator +from cleo.ui.progress_indicator import ProgressIndicator from poetry.core.packages import Dependency from poetry.core.packages import DirectoryDependency @@ -45,7 +46,10 @@ class Indicator(ProgressIndicator): - pass + def _formatter_elapsed(self): + elapsed = time.time() - self._start_time + + return "{:.1f}s".format(elapsed) class Provider: @@ -663,8 +667,10 @@ def complete_package( clean_dependencies = [] for dep in dependencies: if not package.dependency.transitive_marker.without_extras().is_any(): - marker_intersection = package.dependency.transitive_marker.without_extras().intersect( - dep.marker.without_extras() + marker_intersection = ( + package.dependency.transitive_marker.without_extras().intersect( + dep.marker.without_extras() + ) ) if marker_intersection.is_empty(): # The dependency is not needed, since the markers specified @@ -777,7 +783,7 @@ def debug(self, message, depth=0): # type: (str, int) -> None @contextmanager def progress(self): # type: () -> Iterator[None] - if not self._io.output.supports_ansi() or self.is_debugging(): + if not self._io.output.is_decorated() or self.is_debugging(): self._io.write_line("Resolving dependencies...") yield else: diff --git a/poetry/puzzle/solver.py b/poetry/puzzle/solver.py index 47e3e8153d6..f78c07d71d5 100644 --- a/poetry/puzzle/solver.py +++ b/poetry/puzzle/solver.py @@ -11,7 +11,7 @@ from typing import Tuple from typing import Union -from clikit.api.io import IO +from cleo.io.io import IO from poetry.core.packages import Package from poetry.core.packages.project_package import ProjectPackage @@ -201,7 +201,12 @@ def solve(self, use_latest=None): # type: (List[str]) -> List["OperationTypes"] operations.append(Uninstall(installed)) return sorted( - operations, key=lambda o: (-o.priority, o.package.name, o.package.version,), + operations, + key=lambda o: ( + -o.priority, + o.package.name, + o.package.version, + ), ) def solve_in_compatibility_mode( diff --git a/poetry/repositories/installed_repository.py b/poetry/repositories/installed_repository.py index 29424dbaeb5..1202d7f6612 100644 --- a/poetry/repositories/installed_repository.py +++ b/poetry/repositories/installed_repository.py @@ -44,7 +44,8 @@ def get_package_paths(cls, env, name): # type: (Env, str) -> Set[Path] # where the pth file for foo-bar might have been installed as either foo-bar.pth or # foo_bar.pth (expected) in either pure or platform lib directories. candidates = itertools.product( - {env.purelib, env.platlib}, {name, module_name(name)}, + {env.purelib, env.platlib}, + {name, module_name(name)}, ) for lib, module in candidates: @@ -110,7 +111,8 @@ def load(cls, env): # type: (Env) -> InstalledRepository for entry in reversed(env.sys_path): for distribution in sorted( - metadata.distributions(path=[entry]), key=lambda d: str(d._path), + metadata.distributions(path=[entry]), + key=lambda d: str(d._path), ): name = distribution.metadata["name"] path = Path(str(distribution._path)) diff --git a/poetry/utils/env.py b/poetry/utils/env.py index 77a185130d5..36851104831 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -25,7 +25,7 @@ import tomlkit import virtualenv -from clikit.api.io import IO +from cleo.io.io import IO from packaging.tags import Tag from packaging.tags import interpreter_name from packaging.tags import interpreter_version diff --git a/poetry/utils/exporter.py b/poetry/utils/exporter.py index f86fdcf19ee..23f7f0f1503 100644 --- a/poetry/utils/exporter.py +++ b/poetry/utils/exporter.py @@ -5,7 +5,7 @@ from typing import Sequence from typing import Union -from clikit.api.io import IO +from cleo.io.io import IO from poetry.poetry import Poetry from poetry.utils._compat import decode diff --git a/pyproject.toml b/pyproject.toml index 412ce84c639..562bfe68901 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,8 +25,7 @@ classifiers = [ python = "^3.6" poetry-core = "^1.0.0" -cleo = "^0.8.1" -clikit = "^0.6.2" +cleo = "^1.0.0a1" crashtest = "^0.3.0" requests = "^2.18" cachy = "^0.3.0" @@ -55,7 +54,7 @@ zipp = { version = "^3.4", python = "<3.8"} urllib3 = "1.25.10" [tool.poetry.scripts] -poetry = "poetry.console:main" +poetry = "poetry.console.application:main" [build-system] diff --git a/sonnet b/sonnet index facbe34e280..090e31ecda2 100755 --- a/sonnet +++ b/sonnet @@ -7,10 +7,31 @@ import sys import tarfile from gzip import GzipFile +from pathlib import Path +from typing import Optional -from cleo import Application -from cleo import Command -from clikit.api.formatter import Style +from cleo.application import Application as BaseApplication +from cleo.commands.command import Command +from cleo.formatters.style import Style +from cleo.helpers import option +from cleo.io.inputs.input import Input +from cleo.io.io import IO +from cleo.io.outputs.output import Output + + +class Application(BaseApplication): + def create_io( + self, + input: Optional[Input] = None, + output: Optional[Output] = None, + error_output: Optional[Output] = None, + ) -> IO: + io = super(Application, self).create_io(input, output, error_output) + + io.output.formatter.set_style("debug", Style("default", options=["dark"])) + io.error_output.formatter.set_style("debug", Style("default", options=["dark"])) + + return io WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") @@ -19,11 +40,12 @@ WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name = class MakeReleaseCommand(Command): """ Makes a self-contained package of Poetry. - - release - {--P|python=?* : Python version to use} """ + name = "make release" + + options = [option("--python", "-P", flag=False, multiple=True)] + PYTHON = { "3.6": "python3.6", "3.7": "python3.7", @@ -52,7 +74,6 @@ class MakeReleaseCommand(Command): from poetry.puzzle import Solver from poetry.repositories.pool import Pool from poetry.repositories.repository import Repository - from poetry.utils._compat import Path from poetry.utils.env import EnvManager from poetry.utils.env import VirtualEnv from poetry.utils.helpers import temporary_directory @@ -244,23 +265,8 @@ class MakeReleaseCommand(Command): return vendor_dir -class MakeCommand(Command): - """ - Build poetry releases. - - make - """ - - commands = [MakeReleaseCommand()] - - def handle(self): - return self.call("help", self.config.name) - - app = Application("sonnet") -app.config.add_style(Style("debug").fg("default").dark()) - -app.add(MakeCommand()) +app.add(MakeReleaseCommand()) if __name__ == "__main__": app.run() diff --git a/tests/conftest.py b/tests/conftest.py index 178a4632b90..d2773c95d56 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,7 +11,7 @@ import httpretty import pytest -from cleo import CommandTester +from cleo.testers.command_tester import CommandTester from poetry.config.config import Config as BaseConfig from poetry.config.dict_config_source import DictConfigSource @@ -118,7 +118,8 @@ def _pep517_metadata(cls, path): return PackageInfo(name="demo", version="0.1.2") mocker.patch( - "poetry.inspection.info.PackageInfo._pep517_metadata", _pep517_metadata, + "poetry.inspection.info.PackageInfo._pep517_metadata", + _pep517_metadata, ) @@ -224,7 +225,8 @@ def default_python(current_python): @pytest.fixture def repo(http): http.register_uri( - http.GET, re.compile("^https?://foo.bar/(.+?)$"), + http.GET, + re.compile("^https?://foo.bar/(.+?)$"), ) return TestRepository(name="foo") @@ -299,6 +301,13 @@ def _tester(command, poetry=None, installer=None, executor=None, environment=Non command = app.find(command) tester = CommandTester(command) + # Setting the formatter from the application + # TODO: Find a better way to do this in Cleo + app_io = app.create_io() + formatter = app_io.output.formatter + tester.io.output.set_formatter(formatter) + tester.io.error_output.set_formatter(formatter) + if poetry: app._poetry = poetry diff --git a/tests/console/commands/debug/test_resolve.py b/tests/console/commands/debug/test_resolve.py index ffc1e509aef..f3f0db24c6e 100644 --- a/tests/console/commands/debug/test_resolve.py +++ b/tests/console/commands/debug/test_resolve.py @@ -10,7 +10,7 @@ def tester(command_tester_factory): @pytest.fixture(autouse=True) -def __add_packages(repo): +def _add_packages(repo): cachy020 = get_package("cachy", "0.2.0") cachy020.add_dependency(Factory.create_dependency("msgpack-python", ">=0.5 <0.6")) @@ -30,8 +30,8 @@ def test_debug_resolve_gives_resolution_results(tester): Resolution results: -msgpack-python 0.5.3 -cachy 0.2.0 +msgpack-python 0.5.3 +cachy 0.2.0 """ assert expected == tester.io.fetch_output() @@ -46,7 +46,7 @@ def test_debug_resolve_tree_option_gives_the_dependency_tree(tester): Resolution results: cachy 0.2.0 -`-- msgpack-python >=0.5 <0.6 +└── msgpack-python >=0.5 <0.6 """ assert expected == tester.io.fetch_output() @@ -60,8 +60,8 @@ def test_debug_resolve_git_dependency(tester): Resolution results: -pendulum 2.0.3 -demo 0.1.2 +pendulum 2.0.3 +demo 0.1.2 """ assert expected == tester.io.fetch_output() diff --git a/tests/console/commands/env/test_use.py b/tests/console/commands/env/test_use.py index 4ec106b434e..e288bd66003 100644 --- a/tests/console/commands/env/test_use.py +++ b/tests/console/commands/env/test_use.py @@ -40,7 +40,8 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( mocker, tester, venv_cache, venv_name, venvs_in_cache_config ): mocker.patch( - "subprocess.check_output", side_effect=check_output_wrapper(), + "subprocess.check_output", + side_effect=check_output_wrapper(), ) mock_build_env = mocker.patch( @@ -64,7 +65,9 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( Creating virtualenv {} in {} Using virtualenv: {} """.format( - venv_py37.name, venv_py37.parent, venv_py37, + venv_py37.name, + venv_py37.parent, + venv_py37, ) assert expected == tester.io.fetch_output() @@ -122,7 +125,9 @@ def test_get_prefers_explicitly_activated_non_existing_virtualenvs_over_env_var( Creating virtualenv {} in {} Using virtualenv: {} """.format( - venv_dir.name, venv_dir.parent, venv_dir, + venv_dir.name, + venv_dir.parent, + venv_dir, ) assert expected == tester.io.fetch_output() diff --git a/tests/console/commands/self/test_update.py b/tests/console/commands/self/test_update.py index 1598f47865a..2512654dea9 100644 --- a/tests/console/commands/self/test_update.py +++ b/tests/console/commands/self/test_update.py @@ -23,7 +23,7 @@ def test_self_update_should_install_all_necessary_elements( ): os.environ["POETRY_HOME"] = tmp_dir - command = tester._command + command = tester.command version = Version.parse(__version__).next_minor.text mocker.patch( diff --git a/tests/console/commands/test_add.py b/tests/console/commands/test_add.py index a0811c1c965..6405bc7431c 100644 --- a/tests/console/commands/test_add.py +++ b/tests/console/commands/test_add.py @@ -20,7 +20,7 @@ def tester(command_tester_factory): @pytest.fixture() def old_tester(tester): - tester._command.installer.use_executor(False) + tester.command.installer.use_executor(False) return tester @@ -45,7 +45,7 @@ def test_add_no_constraint(app, repo, tester): """ assert expected == tester.io.fetch_output() - assert 1 == tester._command.installer.executor.installations_count + assert 1 == tester.command.installer.executor.installations_count content = app.poetry.file.read()["tool"]["poetry"] @@ -72,7 +72,7 @@ def test_add_equal_constraint(app, repo, tester): """ assert expected == tester.io.fetch_output() - assert 1 == tester._command.installer.executor.installations_count + assert 1 == tester.command.installer.executor.installations_count def test_add_greater_constraint(app, repo, tester): @@ -94,7 +94,7 @@ def test_add_greater_constraint(app, repo, tester): """ assert expected == tester.io.fetch_output() - assert 1 == tester._command.installer.executor.installations_count + assert 1 == tester.command.installer.executor.installations_count def test_add_constraint_with_extras(app, repo, tester): @@ -123,7 +123,7 @@ def test_add_constraint_with_extras(app, repo, tester): """ assert expected == tester.io.fetch_output() - assert 2 == tester._command.installer.executor.installations_count + assert 2 == tester.command.installer.executor.installations_count def test_add_constraint_dependencies(app, repo, tester): @@ -151,11 +151,11 @@ def test_add_constraint_dependencies(app, repo, tester): """ assert expected == tester.io.fetch_output() - assert 2 == tester._command.installer.executor.installations_count + assert 2 == tester.command.installer.executor.installations_count def test_add_git_constraint(app, repo, tester, tmp_venv): - tester._command.set_env(tmp_venv) + tester.command.set_env(tmp_venv) repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cleo", "0.6.5")) @@ -176,7 +176,7 @@ def test_add_git_constraint(app, repo, tester, tmp_venv): """ assert expected == tester.io.fetch_output() - assert 2 == tester._command.installer.executor.installations_count + assert 2 == tester.command.installer.executor.installations_count content = app.poetry.file.read()["tool"]["poetry"] @@ -187,7 +187,7 @@ def test_add_git_constraint(app, repo, tester, tmp_venv): def test_add_git_constraint_with_poetry(app, repo, tester, tmp_venv): - tester._command.set_env(tmp_venv) + tester.command.set_env(tmp_venv) repo.add_package(get_package("pendulum", "1.4.4")) @@ -207,11 +207,11 @@ def test_add_git_constraint_with_poetry(app, repo, tester, tmp_venv): """ assert expected == tester.io.fetch_output() - assert 2 == tester._command.installer.executor.installations_count + assert 2 == tester.command.installer.executor.installations_count def test_add_git_constraint_with_extras(app, repo, tester, tmp_venv): - tester._command.set_env(tmp_venv) + tester.command.set_env(tmp_venv) repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cleo", "0.6.5")) @@ -235,7 +235,7 @@ def test_add_git_constraint_with_extras(app, repo, tester, tmp_venv): """ assert expected.strip() == tester.io.fetch_output().strip() - assert 4 == tester._command.installer.executor.installations_count + assert 4 == tester.command.installer.executor.installations_count content = app.poetry.file.read()["tool"]["poetry"] @@ -247,7 +247,7 @@ def test_add_git_constraint_with_extras(app, repo, tester, tmp_venv): def test_add_git_ssh_constraint(app, repo, tester, tmp_venv): - tester._command.set_env(tmp_venv) + tester.command.set_env(tmp_venv) repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cleo", "0.6.5")) @@ -268,7 +268,7 @@ def test_add_git_ssh_constraint(app, repo, tester, tmp_venv): """ assert expected == tester.io.fetch_output() - assert 2 == tester._command.installer.executor.installations_count + assert 2 == tester.command.installer.executor.installations_count content = app.poetry.file.read()["tool"]["poetry"] @@ -305,7 +305,7 @@ def test_add_directory_constraint(app, repo, tester, mocker): ) assert expected == tester.io.fetch_output() - assert 2 == tester._command.installer.executor.installations_count + assert 2 == tester.command.installer.executor.installations_count content = app.poetry.file.read()["tool"]["poetry"] @@ -338,7 +338,7 @@ def test_add_directory_with_poetry(app, repo, tester, mocker): ) assert expected == tester.io.fetch_output() - assert 2 == tester._command.installer.executor.installations_count + assert 2 == tester.command.installer.executor.installations_count def test_add_file_constraint_wheel(app, repo, tester, mocker, poetry): @@ -366,7 +366,7 @@ def test_add_file_constraint_wheel(app, repo, tester, mocker, poetry): ) assert expected == tester.io.fetch_output() - assert 2 == tester._command.installer.executor.installations_count + assert 2 == tester.command.installer.executor.installations_count content = app.poetry.file.read()["tool"]["poetry"] @@ -401,7 +401,7 @@ def test_add_file_constraint_sdist(app, repo, tester, mocker): ) assert expected == tester.io.fetch_output() - assert 2 == tester._command.installer.executor.installations_count + assert 2 == tester.command.installer.executor.installations_count content = app.poetry.file.read()["tool"]["poetry"] @@ -437,7 +437,7 @@ def test_add_constraint_with_extras_option(app, repo, tester): """ assert expected == tester.io.fetch_output() - assert 2 == tester._command.installer.executor.installations_count + assert 2 == tester.command.installer.executor.installations_count content = app.poetry.file.read()["tool"]["poetry"] @@ -472,7 +472,7 @@ def test_add_url_constraint_wheel(app, repo, tester, mocker): """ assert expected == tester.io.fetch_output() - assert 2 == tester._command.installer.executor.installations_count + assert 2 == tester.command.installer.executor.installations_count content = app.poetry.file.read()["tool"]["poetry"] @@ -509,7 +509,7 @@ def test_add_url_constraint_wheel_with_extras(app, repo, tester, mocker): expected = set(expected.splitlines()) output = set(tester.io.fetch_output().splitlines()) assert expected == output - assert 4 == tester._command.installer.executor.installations_count + assert 4 == tester.command.installer.executor.installations_count content = app.poetry.file.read()["tool"]["poetry"] @@ -541,7 +541,7 @@ def test_add_constraint_with_python(app, repo, tester): """ assert expected == tester.io.fetch_output() - assert 1 == tester._command.installer.executor.installations_count + assert 1 == tester.command.installer.executor.installations_count content = app.poetry.file.read()["tool"]["poetry"] @@ -573,7 +573,7 @@ def test_add_constraint_with_platform(app, repo, tester, env): """ assert expected == tester.io.fetch_output() - assert 1 == tester._command.installer.executor.installations_count + assert 1 == tester.command.installer.executor.installations_count content = app.poetry.file.read()["tool"]["poetry"] @@ -606,7 +606,7 @@ def test_add_constraint_with_source(app, poetry, tester): """ assert expected == tester.io.fetch_output() - assert 1 == tester._command.installer.executor.installations_count + assert 1 == tester.command.installer.executor.installations_count content = app.poetry.file.read()["tool"]["poetry"] @@ -659,7 +659,7 @@ def test_add_to_section_that_does_no_exist_yet(app, repo, tester): """ assert expected == tester.io.fetch_output() - assert 1 == tester._command.installer.executor.installations_count + assert 1 == tester.command.installer.executor.installations_count content = app.poetry.file.read()["tool"]["poetry"] @@ -687,7 +687,7 @@ def test_add_should_not_select_prereleases(app, repo, tester): """ assert expected == tester.io.fetch_output() - assert 1 == tester._command.installer.executor.installations_count + assert 1 == tester.command.installer.executor.installations_count content = app.poetry.file.read()["tool"]["poetry"] diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py index 812cc23ab96..442a0123284 100644 --- a/tests/console/commands/test_init.py +++ b/tests/console/commands/test_init.py @@ -6,7 +6,7 @@ import pytest -from cleo import CommandTester +from cleo.testers.command_tester import CommandTester from poetry.repositories import Pool from poetry.utils._compat import decode @@ -599,7 +599,7 @@ def test_predefined_and_interactive_dev_dependencies(tester, repo): def test_add_package_with_extras_and_whitespace(tester): - result = tester._command._parse_requirements(["databases[postgresql, sqlite]"]) + result = tester.command._parse_requirements(["databases[postgresql, sqlite]"]) assert result[0]["name"] == "databases" assert len(result[0]["extras"]) == 2 diff --git a/tests/console/commands/test_publish.py b/tests/console/commands/test_publish.py index bcbf4c50006..62671eda624 100644 --- a/tests/console/commands/test_publish.py +++ b/tests/console/commands/test_publish.py @@ -14,16 +14,17 @@ def test_publish_returns_non_zero_code_for_upload_errors(app, app_tester, http): assert 1 == exit_code - expected = """ + expected_output = """ Publishing simple-project (1.2.3) to PyPI - - +""" + expected_error_output = """\ UploadError HTTP Error 400: Bad Request """ - assert expected in app_tester.io.fetch_output() + assert expected_output in app_tester.io.fetch_output() + assert expected_error_output in app_tester.io.fetch_error() def test_publish_returns_non_zero_code_for_connection_errors(app, app_tester, http): @@ -40,7 +41,7 @@ def request_callback(*_, **__): expected = str(UploadError(error=requests.ConnectionError())) - assert expected in app_tester.io.fetch_output() + assert expected in app_tester.io.fetch_error() def test_publish_with_cert(app_tester, mocker): diff --git a/tests/console/commands/test_run.py b/tests/console/commands/test_run.py index 351d869d1a9..33314fa53c0 100644 --- a/tests/console/commands/test_run.py +++ b/tests/console/commands/test_run.py @@ -11,6 +11,16 @@ def patches(mocker, env): mocker.patch("poetry.utils.env.EnvManager.get", return_value=env) -def test_run_passes_all_args(tester, env): - tester.execute("python -V") +def test_run_passes_all_args(app_tester, env): + app_tester.execute("run python -V") assert [["python", "-V"]] == env.executed + + +def test_run_keeps_options_passed_before_command(app_tester, env): + app_tester.execute("-V --no-ansi run python", decorated=True) + + assert not app_tester.io.is_decorated() + assert app_tester.io.fetch_output() == app_tester.io.remove_format( + app_tester.application.long_version + "\n" + ) + assert [] == env.executed diff --git a/tests/console/commands/test_search.py b/tests/console/commands/test_search.py index 18e094ff1ae..ef668454084 100644 --- a/tests/console/commands/test_search.py +++ b/tests/console/commands/test_search.py @@ -21,7 +21,8 @@ def tester(command_tester_factory): def test_search( - tester, http, + tester, + http, ): tester.execute("sqlalchemy") diff --git a/tests/console/commands/test_show.py b/tests/console/commands/test_show.py index 56e964abfe4..f31c82882ea 100644 --- a/tests/console/commands/test_show.py +++ b/tests/console/commands/test_show.py @@ -1,7 +1,5 @@ import pytest -from clikit.formatter.ansi_formatter import AnsiFormatter - from poetry.factory import Factory from tests.helpers import get_package @@ -186,12 +184,11 @@ def test_show_basic_with_not_installed_packages_decorated(tester, poetry, instal } ) - tester.io.set_formatter(AnsiFormatter(forced=True)) - tester.execute() + tester.execute(decorated=True) expected = """\ -\033[36mcachy \033[0m \033[1m0.1.0\033[0m Cachy package -\033[31mpendulum\033[0m \033[1m2.0.0\033[0m Pendulum package +\033[36mcachy \033[39m \033[39;1m0.1.0\033[39;22m Cachy package +\033[31mpendulum\033[39m \033[39;1m2.0.0\033[39;22m Pendulum package """ assert expected == tester.io.fetch_output() @@ -317,12 +314,11 @@ def test_show_latest_decorated(tester, poetry, installed, repo): } ) - tester.io.set_formatter(AnsiFormatter(forced=True)) - tester.execute("--latest") + tester.execute("--latest", decorated=True) expected = """\ -\033[36mcachy \033[0m \033[1m0.1.0\033[0m \033[33m0.2.0\033[0m Cachy package -\033[36mpendulum\033[0m \033[1m2.0.0\033[0m \033[31m2.0.1\033[0m Pendulum package +\033[36mcachy \033[39m \033[39;1m0.1.0\033[39;22m \033[33m0.2.0\033[39m Cachy package +\033[36mpendulum\033[39m \033[39;1m2.0.0\033[39;22m \033[31m2.0.1\033[39m Pendulum package """ assert expected == tester.io.fetch_output() @@ -1144,7 +1140,7 @@ def test_show_tree(tester, poetry, installed): } ) - tester.execute("--tree") + tester.execute("--tree", supports_utf8=False) expected = """\ cachy 0.2.0 @@ -1215,7 +1211,7 @@ def test_show_tree_no_dev(tester, poetry, installed): expected = """\ cachy 0.2.0 -`-- msgpack-python >=0.5 <0.6 +└── msgpack-python >=0.5 <0.6 """ assert expected == tester.io.fetch_output() diff --git a/tests/console/commands/test_version.py b/tests/console/commands/test_version.py index ac91df1690c..f868d9f1060 100644 --- a/tests/console/commands/test_version.py +++ b/tests/console/commands/test_version.py @@ -1,6 +1,6 @@ import pytest -from poetry.console.commands import VersionCommand +from poetry.console.commands.version import VersionCommand @pytest.fixture() diff --git a/tests/console/conftest.py b/tests/console/conftest.py index a9b06f6c3b2..82143358d4d 100644 --- a/tests/console/conftest.py +++ b/tests/console/conftest.py @@ -4,11 +4,11 @@ import pytest -from cleo import ApplicationTester +from cleo.io.null_io import NullIO +from cleo.testers.application_tester import ApplicationTester from poetry.factory import Factory from poetry.installation.noop_installer import NoopInstaller -from poetry.io.null_io import NullIO from poetry.repositories import Pool from poetry.utils.env import MockEnv from tests.helpers import TestApplication @@ -98,7 +98,6 @@ def poetry(repo, project_directory, config): @pytest.fixture def app(poetry): app_ = TestApplication(poetry) - app_.config.set_terminate_after_run(False) return app_ diff --git a/tests/helpers.py b/tests/helpers.py index 8fc2a381c0f..078e15b6143 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -4,7 +4,7 @@ from pathlib import Path -from poetry.console import Application +from poetry.console.application import Application from poetry.core.masonry.utils.helpers import escape_name from poetry.core.masonry.utils.helpers import escape_version from poetry.core.packages import Dependency diff --git a/tests/inspection/test_info.py b/tests/inspection/test_info.py index ac0c4504e2c..a1edea4a39b 100644 --- a/tests/inspection/test_info.py +++ b/tests/inspection/test_info.py @@ -145,7 +145,8 @@ def test_info_from_setup_cfg(demo_setup_cfg): def test_info_no_setup_pkg_info_no_deps(): info = PackageInfo.from_directory( - FIXTURE_DIR_INSPECTIONS / "demo_no_setup_pkg_info_no_deps", disable_build=True, + FIXTURE_DIR_INSPECTIONS / "demo_no_setup_pkg_info_no_deps", + disable_build=True, ) assert info.name == "demo" assert info.version == "0.1.0" diff --git a/tests/installation/test_authenticator.py b/tests/installation/test_authenticator.py index d19364741d1..5f756c34f91 100644 --- a/tests/installation/test_authenticator.py +++ b/tests/installation/test_authenticator.py @@ -5,14 +5,16 @@ import pytest import requests +from cleo.io.null_io import NullIO + from poetry.installation.authenticator import Authenticator -from poetry.io.null_io import NullIO @pytest.fixture() def mock_remote(http): http.register_uri( - http.GET, re.compile("^https?://foo.bar/(.+?)$"), + http.GET, + re.compile("^https?://foo.bar/(.+?)$"), ) diff --git a/tests/installation/test_chef.py b/tests/installation/test_chef.py index d25f276748d..332d9c4794f 100644 --- a/tests/installation/test_chef.py +++ b/tests/installation/test_chef.py @@ -48,7 +48,9 @@ def test_get_cached_archives_for_link(config, mocker): distributions = Path(__file__).parent.parent.joinpath("fixtures/distributions") mocker.patch.object( - chef, "get_cache_directory_for_link", return_value=distributions, + chef, + "get_cache_directory_for_link", + return_value=distributions, ) archives = chef.get_cached_archives_for_link( diff --git a/tests/installation/test_chooser.py b/tests/installation/test_chooser.py index 75fd52c7926..64d58eab95d 100644 --- a/tests/installation/test_chooser.py +++ b/tests/installation/test_chooser.py @@ -50,7 +50,9 @@ def callback(request, uri, headers): return [200, headers, f.read()] http.register_uri( - http.GET, re.compile("^https://pypi.org/(.+?)/(.+?)/json$"), body=callback, + http.GET, + re.compile("^https://pypi.org/(.+?)/(.+?)/json$"), + body=callback, ) @@ -66,7 +68,9 @@ def callback(request, uri, headers): return [200, headers, f.read()] http.register_uri( - http.GET, re.compile("^https://foo.bar/simple/(.+?)$"), body=callback, + http.GET, + re.compile("^https://foo.bar/simple/(.+?)$"), + body=callback, ) @@ -150,7 +154,11 @@ def test_chooser_chooses_system_specific_wheel_link_if_available( @pytest.mark.parametrize("source_type", ["", "legacy"]) def test_chooser_chooses_sdist_if_no_compatible_wheel_link_is_available( - env, mock_pypi, mock_legacy, source_type, pool, + env, + mock_pypi, + mock_legacy, + source_type, + pool, ): chooser = Chooser(pool, env) @@ -171,7 +179,11 @@ def test_chooser_chooses_sdist_if_no_compatible_wheel_link_is_available( @pytest.mark.parametrize("source_type", ["", "legacy"]) def test_chooser_chooses_distributions_that_match_the_package_hashes( - env, mock_pypi, mock_legacy, source_type, pool, + env, + mock_pypi, + mock_legacy, + source_type, + pool, ): chooser = Chooser(pool, env) diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index ddf6faa2d66..4b1596aeb5e 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -8,8 +8,8 @@ import pytest -from clikit.api.formatter.style import Style -from clikit.io.buffered_io import BufferedIO +from cleo.formatters.style import Style +from cleo.io.buffered_io import BufferedIO from poetry.config.config import Config from poetry.core.packages.package import Package @@ -32,10 +32,10 @@ def env(tmp_dir): @pytest.fixture() def io(): io = BufferedIO() - io.formatter.add_style(Style("c1_dark").fg("cyan").dark()) - io.formatter.add_style(Style("c2_dark").fg("default").bold().dark()) - io.formatter.add_style(Style("success_dark").fg("green").dark()) - io.formatter.add_style(Style("warning").fg("yellow")) + io.output.formatter.set_style("c1_dark", Style("cyan", options=["dark"])) + io.output.formatter.set_style("c2_dark", Style("default", options=["bold", "dark"])) + io.output.formatter.set_style("success_dark", Style("green", options=["dark"])) + io.output.formatter.set_style("warning", Style("yellow")) return io @@ -59,7 +59,9 @@ def callback(request, uri, headers): return [200, headers, f.read()] http.register_uri( - http.GET, re.compile("^https://files.pythonhosted.org/.*$"), body=callback, + http.GET, + re.compile("^https://files.pythonhosted.org/.*$"), + body=callback, ) @@ -203,10 +205,10 @@ def test_execute_should_gracefully_handle_io_error(config, mocker, io, env): original_write_line = executor._io.write_line - def write_line(string, flags=None): + def write_line(string, **kwargs): # Simulate UnicodeEncodeError string.encode("ascii") - original_write_line(string, flags) + original_write_line(string, **kwargs) mocker.patch.object(io, "write_line", side_effect=write_line) diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 67ee09b0c2c..32eb63149ea 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -7,7 +7,7 @@ import pytest -from clikit.io import NullIO +from cleo.io.null_io import NullIO from poetry.core.packages import ProjectPackage from poetry.core.toml.file import TOMLFile @@ -1854,7 +1854,12 @@ def test_installer_can_handle_old_lock_files( pool, config, installed=installed, - executor=Executor(MockEnv(version_info=(2, 7, 18)), pool, config, NullIO(),), + executor=Executor( + MockEnv(version_info=(2, 7, 18)), + pool, + config, + NullIO(), + ), ) installer.use_executor() @@ -1872,7 +1877,10 @@ def test_installer_can_handle_old_lock_files( config, installed=installed, executor=Executor( - MockEnv(version_info=(2, 7, 18), platform="win32"), pool, config, NullIO(), + MockEnv(version_info=(2, 7, 18), platform="win32"), + pool, + config, + NullIO(), ), ) installer.use_executor() diff --git a/tests/installation/test_installer_old.py b/tests/installation/test_installer_old.py index 97c1b163636..21c66e2a176 100644 --- a/tests/installation/test_installer_old.py +++ b/tests/installation/test_installer_old.py @@ -6,7 +6,7 @@ import pytest -from clikit.io import NullIO +from cleo.io.null_io import NullIO from poetry.core.packages import ProjectPackage from poetry.core.toml.file import TOMLFile diff --git a/tests/installation/test_pip_installer.py b/tests/installation/test_pip_installer.py index 64c64409051..34c9cc6b4bd 100644 --- a/tests/installation/test_pip_installer.py +++ b/tests/installation/test_pip_installer.py @@ -4,9 +4,10 @@ import pytest +from cleo.io.null_io import NullIO + from poetry.core.packages.package import Package from poetry.installation.pip_installer import PipInstaller -from poetry.io.null_io import NullIO from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.pool import Pool from poetry.utils.env import NullEnv diff --git a/tests/masonry/builders/fixtures/pep_561_stub_only/pkg-stubs/module.pyi b/tests/masonry/builders/fixtures/pep_561_stub_only/pkg-stubs/module.pyi index d79e6e39ee0..f85a07d465a 100644 --- a/tests/masonry/builders/fixtures/pep_561_stub_only/pkg-stubs/module.pyi +++ b/tests/masonry/builders/fixtures/pep_561_stub_only/pkg-stubs/module.pyi @@ -1,4 +1,5 @@ """Example module""" from typing import Tuple + version_info = Tuple[int, int, int] diff --git a/tests/masonry/builders/fixtures/pep_561_stub_only_partial/pkg-stubs/module.pyi b/tests/masonry/builders/fixtures/pep_561_stub_only_partial/pkg-stubs/module.pyi index d79e6e39ee0..f85a07d465a 100644 --- a/tests/masonry/builders/fixtures/pep_561_stub_only_partial/pkg-stubs/module.pyi +++ b/tests/masonry/builders/fixtures/pep_561_stub_only_partial/pkg-stubs/module.pyi @@ -1,4 +1,5 @@ """Example module""" from typing import Tuple + version_info = Tuple[int, int, int] diff --git a/tests/masonry/builders/fixtures/pep_561_stub_only_src/src/pkg-stubs/module.pyi b/tests/masonry/builders/fixtures/pep_561_stub_only_src/src/pkg-stubs/module.pyi index d79e6e39ee0..f85a07d465a 100644 --- a/tests/masonry/builders/fixtures/pep_561_stub_only_src/src/pkg-stubs/module.pyi +++ b/tests/masonry/builders/fixtures/pep_561_stub_only_src/src/pkg-stubs/module.pyi @@ -1,4 +1,5 @@ """Example module""" from typing import Tuple + version_info = Tuple[int, int, int] diff --git a/tests/masonry/builders/test_editable_builder.py b/tests/masonry/builders/test_editable_builder.py index 5500bc49a4d..8e236b9b188 100644 --- a/tests/masonry/builders/test_editable_builder.py +++ b/tests/masonry/builders/test_editable_builder.py @@ -8,8 +8,9 @@ import pytest +from cleo.io.null_io import NullIO + from poetry.factory import Factory -from poetry.io.null_io import NullIO from poetry.masonry.builders.editable import EditableBuilder from poetry.utils.env import EnvManager from poetry.utils.env import MockEnv @@ -78,10 +79,11 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ assert tmp_venv._bin_dir.joinpath("foo").exists() assert tmp_venv.site_packages.path.joinpath("simple_project.pth").exists() - assert simple_poetry.file.parent.resolve().as_posix() == tmp_venv.site_packages.path.joinpath( - "simple_project.pth" - ).read_text().strip( - os.linesep + assert ( + simple_poetry.file.parent.resolve().as_posix() + == tmp_venv.site_packages.path.joinpath("simple_project.pth") + .read_text() + .strip(os.linesep) ) dist_info = tmp_venv.site_packages.path.joinpath("simple_project-1.2.3.dist-info") diff --git a/tests/mixology/solutions/solutions/test_python_requirement_solution.py b/tests/mixology/solutions/solutions/test_python_requirement_solution.py index 8ea58a4078d..46646b5ec81 100644 --- a/tests/mixology/solutions/solutions/test_python_requirement_solution.py +++ b/tests/mixology/solutions/solutions/test_python_requirement_solution.py @@ -1,4 +1,4 @@ -from clikit.io.buffered_io import BufferedIO +from cleo.io.buffered_io import BufferedIO from poetry.core.packages.dependency import Dependency from poetry.mixology.failure import SolveFailure diff --git a/tests/mixology/version_solver/conftest.py b/tests/mixology/version_solver/conftest.py index b31634b85db..ad0dacfbe75 100644 --- a/tests/mixology/version_solver/conftest.py +++ b/tests/mixology/version_solver/conftest.py @@ -1,6 +1,6 @@ import pytest -from clikit.io import NullIO +from cleo.io.null_io import NullIO from poetry.core.packages.project_package import ProjectPackage from poetry.puzzle.provider import Provider as BaseProvider diff --git a/tests/publishing/test_publisher.py b/tests/publishing/test_publisher.py index 786d659b0db..d35dcf8f2ca 100644 --- a/tests/publishing/test_publisher.py +++ b/tests/publishing/test_publisher.py @@ -4,10 +4,10 @@ import pytest -from cleo.io import BufferedIO +from cleo.io.buffered_io import BufferedIO +from cleo.io.null_io import NullIO from poetry.factory import Factory -from poetry.io.null_io import NullIO from poetry.publishing.publisher import Publisher diff --git a/tests/publishing/test_uploader.py b/tests/publishing/test_uploader.py index ae0eb041b7b..8f5ec1b9e7a 100644 --- a/tests/publishing/test_uploader.py +++ b/tests/publishing/test_uploader.py @@ -2,8 +2,9 @@ import pytest +from cleo.io.null_io import NullIO + from poetry.factory import Factory -from poetry.io.null_io import NullIO from poetry.publishing.uploader import Uploader from poetry.publishing.uploader import UploadError diff --git a/tests/puzzle/test_provider.py b/tests/puzzle/test_provider.py index 2d0c4557bdb..d7ab8f8788a 100644 --- a/tests/puzzle/test_provider.py +++ b/tests/puzzle/test_provider.py @@ -3,7 +3,7 @@ import pytest -from clikit.io import NullIO +from cleo.io.null_io import NullIO from poetry.core.packages import ProjectPackage from poetry.core.packages.directory_dependency import DirectoryDependency diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 6e2a53a5c22..75b5c565ed5 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -2,7 +2,7 @@ import pytest -from clikit.io import NullIO +from cleo.io.null_io import NullIO from poetry.core.packages import Package from poetry.core.packages import ProjectPackage @@ -499,7 +499,8 @@ def test_solver_returns_extras_only_requested(solver, repo, package, enabled_ext ) check_solver_result( - ops, expected, + ops, + expected, ) assert ops[-1].package.marker.is_any() @@ -544,7 +545,8 @@ def test_solver_returns_extras_when_multiple_extras_use_same_dependency( expected.insert(0, {"job": "install", "package": package_c}) check_solver_result( - ops, expected, + ops, + expected, ) assert ops[-1].package.marker.is_any() @@ -2353,7 +2355,8 @@ def test_ignore_python_constraint_no_overlap_dependencies(solver, repo, package) ops = solver.solve() check_solver_result( - ops, [{"job": "install", "package": pytest}], + ops, + [{"job": "install", "package": pytest}], ) @@ -2570,7 +2573,8 @@ def test_solver_should_use_the_python_constraint_from_the_environment_if_availab ops = solver.solve() check_solver_result( - ops, [{"job": "install", "package": b}, {"job": "install", "package": a}], + ops, + [{"job": "install", "package": b}, {"job": "install", "package": a}], ) diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index f0e2bd21a45..da810307a9f 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -9,7 +9,7 @@ import pytest import tomlkit -from clikit.io import NullIO +from cleo.io.null_io import NullIO from poetry.core.semver import Version from poetry.core.toml.file import TOMLFile @@ -144,7 +144,8 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( - "subprocess.check_output", side_effect=check_output_wrapper(), + "subprocess.check_output", + side_effect=check_output_wrapper(), ) mocker.patch( "subprocess.Popen.communicate", @@ -184,10 +185,12 @@ def test_activate_activates_existing_virtualenv_no_envs_file( config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( - "subprocess.check_output", side_effect=check_output_wrapper(), + "subprocess.check_output", + side_effect=check_output_wrapper(), ) mocker.patch( - "subprocess.Popen.communicate", side_effect=[("/prefix", None)], + "subprocess.Popen.communicate", + side_effect=[("/prefix", None)], ) m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv) @@ -223,10 +226,12 @@ def test_activate_activates_same_virtualenv_with_envs_file( config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( - "subprocess.check_output", side_effect=check_output_wrapper(), + "subprocess.check_output", + side_effect=check_output_wrapper(), ) mocker.patch( - "subprocess.Popen.communicate", side_effect=[("/prefix", None)], + "subprocess.Popen.communicate", + side_effect=[("/prefix", None)], ) m = mocker.patch("poetry.utils.env.EnvManager.create_venv") @@ -303,7 +308,8 @@ def test_activate_activates_recreates_for_different_patch( config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( - "subprocess.check_output", side_effect=check_output_wrapper(), + "subprocess.check_output", + side_effect=check_output_wrapper(), ) mocker.patch( "subprocess.Popen.communicate", @@ -404,7 +410,8 @@ def test_deactivate_non_activated_but_existing( config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( - "subprocess.check_output", side_effect=check_output_wrapper(), + "subprocess.check_output", + side_effect=check_output_wrapper(), ) manager.deactivate(NullIO()) @@ -442,7 +449,8 @@ def test_deactivate_activated(tmp_dir, manager, poetry, config, mocker): config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( - "subprocess.check_output", side_effect=check_output_wrapper(), + "subprocess.check_output", + side_effect=check_output_wrapper(), ) manager.deactivate(NullIO()) @@ -473,10 +481,12 @@ def test_get_prefers_explicitly_activated_virtualenvs_over_env_var( envs_file.write(doc) mocker.patch( - "subprocess.check_output", side_effect=check_output_wrapper(), + "subprocess.check_output", + side_effect=check_output_wrapper(), ) mocker.patch( - "subprocess.Popen.communicate", side_effect=[("/prefix", None)], + "subprocess.Popen.communicate", + side_effect=[("/prefix", None)], ) env = manager.get() @@ -814,7 +824,8 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( ) mocker.patch( - "subprocess.check_output", side_effect=check_output_wrapper(), + "subprocess.check_output", + side_effect=check_output_wrapper(), ) mocker.patch( "subprocess.Popen.communicate", @@ -846,7 +857,8 @@ def test_system_env_has_correct_paths(): @pytest.mark.parametrize( - ("enabled",), [(True,), (False,)], + ("enabled",), + [(True,), (False,)], ) def test_system_env_usersite(mocker, enabled): mocker.patch("site.check_enableusersite", return_value=enabled) diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index f15e2dc53a3..5fed10d1c99 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -974,7 +974,10 @@ def test_exporter_can_export_requirements_txt_with_file_packages_and_markers( def test_exporter_exports_requirements_txt_with_legacy_packages(tmp_dir, poetry): poetry.pool.add_repository( - LegacyRepository("custom", "https://example.com/simple",) + LegacyRepository( + "custom", + "https://example.com/simple", + ) ) poetry.locker.mock_lock_data( { @@ -1030,7 +1033,12 @@ def test_exporter_exports_requirements_txt_with_legacy_packages(tmp_dir, poetry) def test_exporter_exports_requirements_txt_with_legacy_packages_trusted_host( tmp_dir, poetry ): - poetry.pool.add_repository(LegacyRepository("custom", "http://example.com/simple",)) + poetry.pool.add_repository( + LegacyRepository( + "custom", + "http://example.com/simple", + ) + ) poetry.locker.mock_lock_data( { "package": [ @@ -1139,9 +1147,17 @@ def test_exporter_exports_requirements_txt_with_legacy_packages_and_duplicate_so tmp_dir, poetry ): poetry.pool.add_repository( - LegacyRepository("custom", "https://example.com/simple",) + LegacyRepository( + "custom", + "https://example.com/simple", + ) + ) + poetry.pool.add_repository( + LegacyRepository( + "custom", + "https://foobaz.com/simple", + ) ) - poetry.pool.add_repository(LegacyRepository("custom", "https://foobaz.com/simple",)) poetry.locker.mock_lock_data( { "package": [ From 18a9e2dfad36332c9b6c53f7a1cf980b66325fe4 Mon Sep 17 00:00:00 2001 From: finswimmer Date: Wed, 3 Feb 2021 20:06:09 +0100 Subject: [PATCH 083/222] fix: replace clikit by cleo in several places (#3634) --- poetry/console/args/__init__.py | 0 poetry/console/args/run_args_parser.py | 41 -------------------------- poetry/console/commands/show.py | 2 +- poetry/console/logging/io_handler.py | 4 +-- poetry/installation/executor.py | 2 +- poetry/masonry/builders/editable.py | 2 +- poetry/publishing/publisher.py | 7 ++--- poetry/publishing/uploader.py | 7 ++--- poetry/utils/shell.py | 2 +- 9 files changed, 11 insertions(+), 56 deletions(-) delete mode 100644 poetry/console/args/__init__.py delete mode 100644 poetry/console/args/run_args_parser.py diff --git a/poetry/console/args/__init__.py b/poetry/console/args/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/poetry/console/args/run_args_parser.py b/poetry/console/args/run_args_parser.py deleted file mode 100644 index 9f8cff8b209..00000000000 --- a/poetry/console/args/run_args_parser.py +++ /dev/null @@ -1,41 +0,0 @@ -from clikit.api.args import Args -from clikit.api.args import RawArgs -from clikit.api.args.format import ArgsFormat -from clikit.api.args.format import ArgsFormatBuilder -from clikit.args import DefaultArgsParser - - -class RunArgsParser(DefaultArgsParser): - """ - Parser that just parses command names and leave the rest - alone to be passed to the command. - """ - - def parse( - self, args, fmt, lenient=False - ): # type: (RawArgs, ArgsFormat, bool) -> Args - builder = ArgsFormatBuilder() - builder.set_command_names(*fmt.get_command_names()) - builder.set_arguments(*fmt.get_arguments().values()) - fmt = builder.format - - return super(RunArgsParser, self).parse(args, fmt, True) - - def _parse( - self, raw_args, fmt, lenient - ): # type: (RawArgs, ArgsFormat, bool) -> None - """ - Parse everything as a single, multi-valued argument. - """ - tokens = raw_args.tokens[:] - - last_arg = list(fmt.get_arguments().values())[-1] - self._arguments[last_arg.name] = [] - - while True: - try: - token = tokens.pop(0) - except IndexError: - break - - self._arguments[last_arg.name].append(token) diff --git a/poetry/console/commands/show.py b/poetry/console/commands/show.py index 66c505f5545..07a58cd0754 100644 --- a/poetry/console/commands/show.py +++ b/poetry/console/commands/show.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: - from clikit.api.io import IO # noqa + from cleo.io.io import IO # noqa from poetry.core.packages import Dependency # noqa from poetry.core.packages import Package # noqa diff --git a/poetry/console/logging/io_handler.py b/poetry/console/logging/io_handler.py index 03d9607c8e5..03804c3a669 100644 --- a/poetry/console/logging/io_handler.py +++ b/poetry/console/logging/io_handler.py @@ -6,7 +6,7 @@ if TYPE_CHECKING: from logging import LogRecord # noqa - from clikit.api.io import IO # noqa + from cleo.io.io import IO # noqa class IOHandler(logging.Handler): @@ -21,7 +21,7 @@ def emit(self, record): # type: ("LogRecord") -> None level = record.levelname.lower() err = level in ("warning", "error", "exception", "critical") if err: - self._io.error_line(msg) + self._io.write_error_line(msg) else: self._io.write_line(msg) except Exception: diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index ecfd2f7ce12..2ffca66a1e7 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -33,7 +33,7 @@ if TYPE_CHECKING: - from clikit.api.io import IO # noqa + from cleo.io.io import IO # noqa from poetry.config.config import Config # noqa from poetry.repositories import Pool # noqa diff --git a/poetry/masonry/builders/editable.py b/poetry/masonry/builders/editable.py index 4f4c059ce10..1756a0f0f15 100644 --- a/poetry/masonry/builders/editable.py +++ b/poetry/masonry/builders/editable.py @@ -19,7 +19,7 @@ if TYPE_CHECKING: - from clikit.api.io import IO # noqa + from cleo.io.io import IO # noqa from poetry.core.poetry import Poetry # noqa from poetry.utils.env import Env # noqa diff --git a/poetry/publishing/publisher.py b/poetry/publishing/publisher.py index c6855deee2d..f8a87a20388 100644 --- a/poetry/publishing/publisher.py +++ b/poetry/publishing/publisher.py @@ -14,9 +14,8 @@ if TYPE_CHECKING: - from cleo.io import BufferedIO # noqa - from cleo.io import ConsoleIO # noqa - from clikit.io import NullIO # noqa + from cleo.io.buffered_io import BufferedIO # noqa + from cleo.io.null_io import NullIO # noqa from ..poetry import Poetry # noqa @@ -30,7 +29,7 @@ class Publisher: def __init__( self, poetry, io - ): # type: ("Poetry", Union["ConsoleIO", "BufferedIO", "NullIO"]) -> None + ): # type: ("Poetry", Union["BufferedIO", "NullIO"]) -> None self._poetry = poetry self._package = poetry.package self._io = io diff --git a/poetry/publishing/uploader.py b/poetry/publishing/uploader.py index 9c712213158..8dccb4ef390 100644 --- a/poetry/publishing/uploader.py +++ b/poetry/publishing/uploader.py @@ -29,8 +29,7 @@ if TYPE_CHECKING: - from cleo.io import ConsoleIO # noqa - from clikit.io import NullIO # noqa + from cleo.io.null_io import NullIO # noqa from poetry.poetry import Poetry # noqa @@ -54,9 +53,7 @@ def __init__(self, error): # type: (Union[ConnectionError, HTTPError, str]) -> class Uploader: - def __init__( - self, poetry, io - ): # type: ("Poetry", Union["ConsoleIO", "NullIO"]) -> None + def __init__(self, poetry, io): # type: ("Poetry", "NullIO") -> None self._poetry = poetry self._package = poetry.package self._io = io diff --git a/poetry/utils/shell.py b/poetry/utils/shell.py index e009903a6de..a0f539fdece 100644 --- a/poetry/utils/shell.py +++ b/poetry/utils/shell.py @@ -7,7 +7,7 @@ import pexpect -from clikit.utils.terminal import Terminal +from cleo.terminal import Terminal from shellingham import ShellDetectionFailure from shellingham import detect_shell From cddd6759f707d5a9d4865d07fddb0ff8107670e3 Mon Sep 17 00:00:00 2001 From: finswimmer Date: Fri, 5 Feb 2021 11:38:35 +0100 Subject: [PATCH 084/222] convert type comments to type hints (#3638) --- poetry/config/config.py | 34 +-- poetry/config/config_source.py | 4 +- poetry/config/dict_config_source.py | 8 +- poetry/config/file_config_source.py | 18 +- poetry/console/application.py | 9 +- poetry/console/commands/about.py | 2 +- poetry/console/commands/add.py | 10 +- poetry/console/commands/build.py | 2 +- poetry/console/commands/cache/clear.py | 2 +- poetry/console/commands/cache/list.py | 2 +- poetry/console/commands/check.py | 2 +- poetry/console/commands/command.py | 15 +- poetry/console/commands/config.py | 24 +- poetry/console/commands/debug/info.py | 2 +- poetry/console/commands/debug/resolve.py | 9 +- poetry/console/commands/env/info.py | 6 +- poetry/console/commands/env/list.py | 2 +- poetry/console/commands/env/remove.py | 2 +- poetry/console/commands/env/use.py | 2 +- poetry/console/commands/env_command.py | 8 +- poetry/console/commands/export.py | 2 +- poetry/console/commands/init.py | 38 ++- poetry/console/commands/install.py | 2 +- poetry/console/commands/installer_command.py | 10 +- poetry/console/commands/lock.py | 2 +- poetry/console/commands/new.py | 2 +- poetry/console/commands/publish.py | 2 +- poetry/console/commands/remove.py | 2 +- poetry/console/commands/run.py | 11 +- poetry/console/commands/search.py | 2 +- poetry/console/commands/self/update.py | 28 +- poetry/console/commands/shell.py | 2 +- poetry/console/commands/show.py | 38 +-- poetry/console/commands/update.py | 2 +- poetry/console/commands/version.py | 10 +- .../logging/formatters/builder_formatter.py | 2 +- .../console/logging/formatters/formatter.py | 2 +- poetry/console/logging/io_formatter.py | 4 +- poetry/console/logging/io_handler.py | 6 +- poetry/factory.py | 15 +- poetry/inspection/info.py | 71 +++-- poetry/installation/authenticator.py | 25 +- poetry/installation/base_installer.py | 8 +- poetry/installation/chef.py | 22 +- poetry/installation/chooser.py | 18 +- poetry/installation/executor.py | 85 +++--- poetry/installation/installer.py | 100 ++++--- poetry/installation/noop_installer.py | 16 +- poetry/installation/operations/install.py | 14 +- poetry/installation/operations/operation.py | 22 +- poetry/installation/operations/uninstall.py | 17 +- poetry/installation/operations/update.py | 22 +- poetry/installation/pip_installer.py | 22 +- poetry/json/__init__.py | 2 +- poetry/layouts/__init__.py | 2 +- poetry/layouts/layout.py | 39 +-- poetry/layouts/src.py | 4 +- poetry/layouts/standard.py | 4 +- poetry/masonry/builders/editable.py | 26 +- poetry/mixology/__init__.py | 15 +- poetry/mixology/assignment.py | 36 ++- poetry/mixology/failure.py | 37 +-- poetry/mixology/incompatibility.py | 64 +++-- poetry/mixology/incompatibility_cause.py | 26 +- poetry/mixology/partial_solution.py | 41 ++- poetry/mixology/result.py | 15 +- .../python_requirement_solution_provider.py | 4 +- .../solutions/python_requirement_solution.py | 10 +- poetry/mixology/term.py | 30 +-- poetry/mixology/version_solver.py | 37 ++- poetry/packages/dependency_package.py | 24 +- poetry/packages/locker.py | 61 +++-- poetry/packages/package_collection.py | 12 +- poetry/poetry.py | 22 +- poetry/publishing/publisher.py | 26 +- poetry/publishing/uploader.py | 54 ++-- poetry/puzzle/exceptions.py | 8 +- poetry/puzzle/provider.py | 67 +++-- poetry/puzzle/solver.py | 99 ++++--- poetry/repositories/base_repository.py | 18 +- poetry/repositories/installed_repository.py | 12 +- poetry/repositories/legacy_repository.py | 46 ++-- poetry/repositories/pool.py | 38 +-- poetry/repositories/pypi_repository.py | 43 +-- poetry/repositories/remote_repository.py | 6 +- poetry/repositories/repository.py | 30 +-- poetry/utils/appdirs.py | 18 +- poetry/utils/env.py | 247 +++++++++--------- poetry/utils/exporter.py | 36 ++- poetry/utils/extras.py | 10 +- poetry/utils/helpers.py | 35 +-- poetry/utils/password_manager.py | 34 ++- poetry/utils/setup_reader.py | 44 ++-- poetry/utils/shell.py | 18 +- poetry/version/version_selector.py | 18 +- tests/conftest.py | 6 +- tests/console/commands/env/helpers.py | 4 +- tests/console/commands/test_init.py | 2 +- tests/console/commands/test_lock.py | 2 +- tests/inspection/test_info.py | 16 +- .../repositories/test_installed_repository.py | 8 +- tests/utils/test_env.py | 4 +- 102 files changed, 1176 insertions(+), 1071 deletions(-) diff --git a/poetry/config/config.py b/poetry/config/config.py index 79c7a75e95a..37844aa1fc9 100644 --- a/poetry/config/config.py +++ b/poetry/config/config.py @@ -19,11 +19,11 @@ _NOT_SET = object() -def boolean_validator(val): # type: (str) -> bool +def boolean_validator(val: str) -> bool: return val in {"true", "false", "1", "0"} -def boolean_normalizer(val): # type: (str) -> bool +def boolean_normalizer(val: str) -> bool: return val in ["true", "1"] @@ -42,8 +42,8 @@ class Config(object): } def __init__( - self, use_environment=True, base_dir=None - ): # type: (bool, Optional[Path]) -> None + self, use_environment: bool = True, base_dir: Optional[Path] = None + ) -> None: self._config = deepcopy(self.default_config) self._use_environment = use_environment self._base_dir = base_dir @@ -51,38 +51,38 @@ def __init__( self._auth_config_source = DictConfigSource() @property - def name(self): # type: () -> str + def name(self) -> str: return str(self._file.path) @property - def config(self): # type: () -> Dict + def config(self) -> Dict: return self._config @property - def config_source(self): # type: () -> ConfigSource + def config_source(self) -> ConfigSource: return self._config_source @property - def auth_config_source(self): # type: () -> ConfigSource + def auth_config_source(self) -> ConfigSource: return self._auth_config_source - def set_config_source(self, config_source): # type: (ConfigSource) -> Config + def set_config_source(self, config_source: ConfigSource) -> "Config": self._config_source = config_source return self - def set_auth_config_source(self, config_source): # type: (ConfigSource) -> Config + def set_auth_config_source(self, config_source: ConfigSource) -> "Config": self._auth_config_source = config_source return self - def merge(self, config): # type: (Dict[str, Any]) -> None + def merge(self, config: Dict[str, Any]) -> None: from poetry.utils.helpers import merge_dicts merge_dicts(self._config, config) - def all(self): # type: () -> Dict[str, Any] - def _all(config, parent_key=""): # type: (Dict, str) -> Dict + def all(self) -> Dict[str, Any]: + def _all(config: Dict, parent_key: str = "") -> Dict: all_ = {} for key in config: @@ -101,10 +101,10 @@ def _all(config, parent_key=""): # type: (Dict, str) -> Dict return _all(self.config) - def raw(self): # type: () -> Dict[str, Any] + def raw(self) -> Dict[str, Any]: return self._config - def get(self, setting_name, default=None): # type: (str, Any) -> Any + def get(self, setting_name: str, default: Any = None) -> Any: """ Retrieve a setting value. """ @@ -129,13 +129,13 @@ def get(self, setting_name, default=None): # type: (str, Any) -> Any return self.process(value) - def process(self, value): # type: (Any) -> Any + def process(self, value: Any) -> Any: if not isinstance(value, str): return value return re.sub(r"{(.+?)}", lambda m: self.get(m.group(1)), value) - def _get_normalizer(self, name): # type: (str) -> Callable + def _get_normalizer(self, name: str) -> Callable: if name in { "virtualenvs.create", "virtualenvs.in-project", diff --git a/poetry/config/config_source.py b/poetry/config/config_source.py index 63a4ad6b628..0a6707f28c9 100644 --- a/poetry/config/config_source.py +++ b/poetry/config/config_source.py @@ -2,8 +2,8 @@ class ConfigSource(object): - def add_property(self, key, value): # type: (str, Any) -> None + def add_property(self, key: str, value: Any) -> None: raise NotImplementedError() - def remove_property(self, key): # type: (str) -> None + def remove_property(self, key: str) -> None: raise NotImplementedError() diff --git a/poetry/config/dict_config_source.py b/poetry/config/dict_config_source.py index aaa6ee3b9d1..941e39e6be6 100644 --- a/poetry/config/dict_config_source.py +++ b/poetry/config/dict_config_source.py @@ -5,14 +5,14 @@ class DictConfigSource(ConfigSource): - def __init__(self): # type: () -> None + def __init__(self) -> None: self._config = {} @property - def config(self): # type: () -> Dict[str, Any] + def config(self) -> Dict[str, Any]: return self._config - def add_property(self, key, value): # type: (str, Any) -> None + def add_property(self, key: str, value: Any) -> None: keys = key.split(".") config = self._config @@ -26,7 +26,7 @@ def add_property(self, key, value): # type: (str, Any) -> None config = config[key] - def remove_property(self, key): # type: (str) -> None + def remove_property(self, key: str) -> None: keys = key.split(".") config = self._config diff --git a/poetry/config/file_config_source.py b/poetry/config/file_config_source.py index 3e7cf71a5d9..cfcdfced45b 100644 --- a/poetry/config/file_config_source.py +++ b/poetry/config/file_config_source.py @@ -1,7 +1,7 @@ from contextlib import contextmanager from typing import TYPE_CHECKING from typing import Any -from typing import Generator +from typing import Iterator from tomlkit import document from tomlkit import table @@ -10,25 +10,25 @@ if TYPE_CHECKING: - from tomlkit.toml_document import TOMLDocument # noqa + from tomlkit.toml_document import TOMLDocument - from poetry.core.toml.file import TOMLFile # noqa + from poetry.core.toml.file import TOMLFile class FileConfigSource(ConfigSource): - def __init__(self, file, auth_config=False): # type: ("TOMLFile", bool) -> None + def __init__(self, file: "TOMLFile", auth_config: bool = False) -> None: self._file = file self._auth_config = auth_config @property - def name(self): # type: () -> str + def name(self) -> str: return str(self._file.path) @property - def file(self): # type: () -> "TOMLFile" + def file(self) -> "TOMLFile": return self._file - def add_property(self, key, value): # type: (str, Any) -> None + def add_property(self, key: str, value: Any) -> None: with self.secure() as config: keys = key.split(".") @@ -42,7 +42,7 @@ def add_property(self, key, value): # type: (str, Any) -> None config = config[key] - def remove_property(self, key): # type: (str) -> None + def remove_property(self, key: str) -> None: with self.secure() as config: keys = key.split(".") @@ -59,7 +59,7 @@ def remove_property(self, key): # type: (str) -> None current_config = current_config[key] @contextmanager - def secure(self): # type: () -> Generator["TOMLDocument"] + def secure(self) -> Iterator["TOMLDocument"]: if self.file.exists(): initial_config = self.file.read() config = self.file.read() diff --git a/poetry/console/application.py b/poetry/console/application.py index 75d759efdc9..e54cbb5d846 100644 --- a/poetry/console/application.py +++ b/poetry/console/application.py @@ -6,6 +6,7 @@ from typing import Any from typing import Callable from typing import Optional +from typing import Type from typing import cast from cleo.application import Application as BaseApplication @@ -26,7 +27,7 @@ def load_command(name: str) -> Callable: - def _load(): + def _load() -> Type[Command]: module = import_module( "poetry.console.commands.{}".format(".".join(name.split(" "))) ) @@ -75,7 +76,7 @@ def _load(): if TYPE_CHECKING: - from poetry.poetry import Poetry # noqa + from poetry.poetry import Poetry class Application(BaseApplication): @@ -220,7 +221,7 @@ def register_command_loggers( logger.setLevel(level) - def set_env(self, event: ConsoleCommandEvent, event_name: str, _: Any): + def set_env(self, event: ConsoleCommandEvent, event_name: str, _: Any) -> None: from .commands.env_command import EnvCommand command: EnvCommand = cast(EnvCommand, event.command) @@ -272,7 +273,7 @@ def set_installer( command.set_installer(installer) -def main(): +def main() -> int: return Application().run() diff --git a/poetry/console/commands/about.py b/poetry/console/commands/about.py index c4415591814..76461852fd7 100644 --- a/poetry/console/commands/about.py +++ b/poetry/console/commands/about.py @@ -7,7 +7,7 @@ class AboutCommand(Command): description = "Shows information about Poetry." - def handle(self): # type: () -> None + def handle(self) -> None: self.line( """Poetry - Package Management for Python diff --git a/poetry/console/commands/add.py b/poetry/console/commands/add.py index abfcc5b15f6..af5e0837827 100644 --- a/poetry/console/commands/add.py +++ b/poetry/console/commands/add.py @@ -68,7 +68,7 @@ class AddCommand(InstallerCommand, InitCommand): loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"] - def handle(self): # type: () -> int + def handle(self) -> int: from tomlkit import inline_table from poetry.core.semver import parse_constraint @@ -192,8 +192,8 @@ def handle(self): # type: () -> int return status def get_existing_packages_from_input( - self, packages, poetry_content, target_section - ): # type: (List[str], Dict, str) -> List[str] + self, packages: List[str], poetry_content: Dict, target_section: str + ) -> List[str]: existing_packages = [] for name in packages: @@ -203,9 +203,7 @@ def get_existing_packages_from_input( return existing_packages - def notify_about_existing_packages( - self, existing_packages - ): # type: (List[str]) -> None + def notify_about_existing_packages(self, existing_packages: List[str]) -> None: self.line( "The following packages are already present in the pyproject.toml and will be skipped:\n" ) diff --git a/poetry/console/commands/build.py b/poetry/console/commands/build.py index 82b5b0f86c7..7d0eda85e9b 100644 --- a/poetry/console/commands/build.py +++ b/poetry/console/commands/build.py @@ -18,7 +18,7 @@ class BuildCommand(EnvCommand): "poetry.core.masonry.builders.wheel", ] - def handle(self): # type: () -> None + def handle(self) -> None: from poetry.core.masonry import Builder fmt = "all" diff --git a/poetry/console/commands/cache/clear.py b/poetry/console/commands/cache/clear.py index 581f83c041d..7bacdb891cb 100644 --- a/poetry/console/commands/cache/clear.py +++ b/poetry/console/commands/cache/clear.py @@ -14,7 +14,7 @@ class CacheClearCommand(Command): arguments = [argument("cache", description="The name of the cache to clear.")] options = [option("all", description="Clear all entries in the cache.")] - def handle(self): # type: () -> int + def handle(self) -> int: from cachy import CacheManager from poetry.locations import REPOSITORY_CACHE_DIR diff --git a/poetry/console/commands/cache/list.py b/poetry/console/commands/cache/list.py index e6346f98ebb..b22dc9c312c 100644 --- a/poetry/console/commands/cache/list.py +++ b/poetry/console/commands/cache/list.py @@ -10,7 +10,7 @@ class CacheListCommand(Command): name = "cache list" description = "List Poetry's caches." - def handle(self): # type: () -> Optional[int] + def handle(self) -> Optional[int]: from poetry.locations import REPOSITORY_CACHE_DIR if os.path.exists(str(REPOSITORY_CACHE_DIR)): diff --git a/poetry/console/commands/check.py b/poetry/console/commands/check.py index 62af8379163..f6531dab992 100644 --- a/poetry/console/commands/check.py +++ b/poetry/console/commands/check.py @@ -11,7 +11,7 @@ class CheckCommand(Command): name = "check" description = "Checks the validity of the pyproject.toml file." - def handle(self): # type: () -> int + def handle(self) -> int: # Load poetry config and display errors, if any poetry_file = Factory.locate(Path.cwd()) config = PyProjectTOML(poetry_file).poetry_config diff --git a/poetry/console/commands/command.py b/poetry/console/commands/command.py index cb13c867909..be87fe99b7a 100644 --- a/poetry/console/commands/command.py +++ b/poetry/console/commands/command.py @@ -4,16 +4,19 @@ if TYPE_CHECKING: - from poetry.poetry import Poetry # noqa + from poetry.console.application import Application + from poetry.poetry import Poetry class Command(BaseCommand): - loggers = [] @property - def poetry(self): # type: () -> "Poetry" - return self.application.poetry + def poetry(self) -> "Poetry": + return self.get_application().poetry + + def get_application(self) -> "Application": + return self.application - def reset_poetry(self): # type: () -> None - self.application.reset_poetry() + def reset_poetry(self) -> None: + self.get_application().reset_poetry() diff --git a/poetry/console/commands/config.py b/poetry/console/commands/config.py index 30dc11ecf50..798b700fd45 100644 --- a/poetry/console/commands/config.py +++ b/poetry/console/commands/config.py @@ -19,7 +19,7 @@ if TYPE_CHECKING: - from poetry.config.config_source import ConfigSource # noqa + from poetry.config.config_source import ConfigSource class ConfigCommand(Command): @@ -51,7 +51,7 @@ class ConfigCommand(Command): LIST_PROHIBITED_SETTINGS = {"http-basic", "pypi-token"} @property - def unique_config_values(self): # type: () -> Dict[str, Tuple[Any, Any, Any]] + def unique_config_values(self) -> Dict[str, Tuple[Any, Any, Any]]: from pathlib import Path from poetry.config.config import boolean_normalizer @@ -90,7 +90,7 @@ def unique_config_values(self): # type: () -> Dict[str, Tuple[Any, Any, Any]] return unique_config_values - def handle(self): # type: () -> Optional[int] + def handle(self) -> Optional[int]: from pathlib import Path from poetry.config.file_config_source import FileConfigSource @@ -269,8 +269,12 @@ def handle(self): # type: () -> Optional[int] raise ValueError("Setting {} does not exist".format(self.argument("key"))) def _handle_single_value( - self, source, key, callbacks, values - ): # type: ("ConfigSource", str, Tuple[Any, Any, Any], List[Any]) -> int + self, + source: "ConfigSource", + key: str, + callbacks: Tuple[Any, Any, Any], + values: List[Any], + ) -> int: validator, normalizer, _ = callbacks if len(values) > 1: @@ -284,7 +288,7 @@ def _handle_single_value( return 0 - def _list_configuration(self, config, raw, k=""): # type: (Dict, Dict, str) -> None + def _list_configuration(self, config: Dict, raw: Dict, k: str = "") -> None: orig_k = k for key, value in sorted(config.items()): if k + key in self.LIST_PROHIBITED_SETTINGS: @@ -319,8 +323,12 @@ def _list_configuration(self, config, raw, k=""): # type: (Dict, Dict, str) -> self.line(message) def _get_setting( - self, contents, setting=None, k=None, default=None - ): # type: (Dict, Optional[str], Optional[str], Optional[Any]) -> List[Tuple[str, str]] + self, + contents: Dict, + setting: Optional[str] = None, + k: Optional[str] = None, + default: Optional[Any] = None, + ) -> List[Tuple[str, str]]: orig_k = k if setting and setting.split(".")[0] not in contents: diff --git a/poetry/console/commands/debug/info.py b/poetry/console/commands/debug/info.py index 140b26725f6..b007d93d4c8 100644 --- a/poetry/console/commands/debug/info.py +++ b/poetry/console/commands/debug/info.py @@ -8,7 +8,7 @@ class DebugInfoCommand(Command): name = "debug info" description = "Shows debug information." - def handle(self): # type: () -> int + def handle(self) -> int: poetry_python_version = ".".join(str(s) for s in sys.version_info[:3]) self.line("") diff --git a/poetry/console/commands/debug/resolve.py b/poetry/console/commands/debug/resolve.py index ef60cbf134a..06b0fa9d21f 100644 --- a/poetry/console/commands/debug/resolve.py +++ b/poetry/console/commands/debug/resolve.py @@ -1,3 +1,4 @@ +from typing import TYPE_CHECKING from typing import Optional from cleo.helpers import argument @@ -7,6 +8,10 @@ from ..init import InitCommand +if TYPE_CHECKING: + from poetry.console.commands.show import ShowCommand + + class DebugResolveCommand(InitCommand): name = "debug resolve" @@ -30,7 +35,7 @@ class DebugResolveCommand(InitCommand): loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"] - def handle(self): # type: () -> Optional[int] + def handle(self) -> Optional[int]: from cleo.io.null_io import NullIO from poetry.core.packages.project_package import ProjectPackage @@ -88,7 +93,7 @@ def handle(self): # type: () -> Optional[int] self.line("") if self.option("tree"): - show_command = self.application.find("show") + show_command: ShowCommand = self.application.find("show") show_command.init_styles(self.io) packages = [op.package for op in ops] diff --git a/poetry/console/commands/env/info.py b/poetry/console/commands/env/info.py index dea2ade994a..aecce3628ac 100644 --- a/poetry/console/commands/env/info.py +++ b/poetry/console/commands/env/info.py @@ -7,7 +7,7 @@ if TYPE_CHECKING: - from poetry.utils.env import Env # noqa + from poetry.utils.env import Env class EnvInfoCommand(Command): @@ -17,7 +17,7 @@ class EnvInfoCommand(Command): options = [option("path", "p", "Only display the environment's path.")] - def handle(self): # type: () -> Optional[int] + def handle(self) -> Optional[int]: from poetry.utils.env import EnvManager env = EnvManager(self.poetry).get() @@ -32,7 +32,7 @@ def handle(self): # type: () -> Optional[int] self._display_complete_info(env) - def _display_complete_info(self, env): # type: ("Env") -> None + def _display_complete_info(self, env: "Env") -> None: env_python_version = ".".join(str(s) for s in env.version_info[:3]) self.line("") self.line("Virtualenv") diff --git a/poetry/console/commands/env/list.py b/poetry/console/commands/env/list.py index f422cc8ef2a..e7b1eac6cd5 100644 --- a/poetry/console/commands/env/list.py +++ b/poetry/console/commands/env/list.py @@ -10,7 +10,7 @@ class EnvListCommand(Command): options = [option("full-path", None, "Output the full paths of the virtualenvs.")] - def handle(self): # type: () -> None + def handle(self) -> None: from poetry.utils.env import EnvManager manager = EnvManager(self.poetry) diff --git a/poetry/console/commands/env/remove.py b/poetry/console/commands/env/remove.py index c8e5f1360e1..9d5153c420b 100644 --- a/poetry/console/commands/env/remove.py +++ b/poetry/console/commands/env/remove.py @@ -12,7 +12,7 @@ class EnvRemoveCommand(Command): argument("python", "The python executable to remove the virtualenv for.") ] - def handle(self): # type: () -> None + def handle(self) -> None: from poetry.utils.env import EnvManager manager = EnvManager(self.poetry) diff --git a/poetry/console/commands/env/use.py b/poetry/console/commands/env/use.py index 13728c1d9ee..fa8a455bcf7 100644 --- a/poetry/console/commands/env/use.py +++ b/poetry/console/commands/env/use.py @@ -10,7 +10,7 @@ class EnvUseCommand(Command): arguments = [argument("python", "The python executable to use.")] - def handle(self): # type: () -> None + def handle(self) -> None: from poetry.utils.env import EnvManager manager = EnvManager(self.poetry) diff --git a/poetry/console/commands/env_command.py b/poetry/console/commands/env_command.py index a7ae5e7bdda..beb40e1e88e 100644 --- a/poetry/console/commands/env_command.py +++ b/poetry/console/commands/env_command.py @@ -4,18 +4,18 @@ if TYPE_CHECKING: - from poetry.utils.env import VirtualEnv # noqa + from poetry.utils.env import VirtualEnv class EnvCommand(Command): - def __init__(self): # type: () -> None + def __init__(self) -> None: self._env = None super(EnvCommand, self).__init__() @property - def env(self): # type: () -> "VirtualEnv" + def env(self) -> "VirtualEnv": return self._env - def set_env(self, env): # type: ("VirtualEnv") -> None + def set_env(self, env: "VirtualEnv") -> None: self._env = env diff --git a/poetry/console/commands/export.py b/poetry/console/commands/export.py index 6d4f2a830fc..a58d96f50fe 100644 --- a/poetry/console/commands/export.py +++ b/poetry/console/commands/export.py @@ -31,7 +31,7 @@ class ExportCommand(Command): option("with-credentials", None, "Include credentials for extra indices."), ] - def handle(self): # type: () -> None + def handle(self) -> None: fmt = self.option("format") if fmt not in Exporter.ACCEPTED_FORMATS: diff --git a/poetry/console/commands/init.py b/poetry/console/commands/init.py index 110f97a9a9c..fbc94b9358c 100644 --- a/poetry/console/commands/init.py +++ b/poetry/console/commands/init.py @@ -7,6 +7,7 @@ import urllib.parse from pathlib import Path +from typing import TYPE_CHECKING from typing import Dict from typing import List from typing import Optional @@ -23,6 +24,10 @@ from .env_command import EnvCommand +if TYPE_CHECKING: + from poetry.repositories import Pool + + class InitCommand(Command): name = "init" description = ( @@ -57,12 +62,12 @@ class InitCommand(Command): The init command creates a basic pyproject.toml file in the current directory. """ - def __init__(self): # type: () -> None + def __init__(self) -> None: super(InitCommand, self).__init__() self._pool = None - def handle(self): # type: () -> int + def handle(self) -> int: from pathlib import Path from poetry.core.vcs.git import GitConfig @@ -227,8 +232,11 @@ def handle(self): # type: () -> int f.write(content) def _determine_requirements( - self, requires, allow_prereleases=False, source=None - ): # type: (List[str], bool, Optional[str]) -> List[Dict[str, Union[str, List[str]]]] + self, + requires: List[str], + allow_prereleases: bool = False, + source: Optional[str] = None, + ) -> List[Dict[str, Union[str, List[str]]]]: if not requires: requires = [] @@ -354,8 +362,12 @@ def _determine_requirements( return result def _find_best_version_for_package( - self, name, required_version=None, allow_prereleases=False, source=None - ): # type: (str, Optional[str], bool, Optional[str]) -> Tuple[str, str] + self, + name: str, + required_version: Optional[str] = None, + allow_prereleases: bool = False, + source: Optional[str] = None, + ) -> Tuple[str, str]: from poetry.version.version_selector import VersionSelector selector = VersionSelector(self._get_pool()) @@ -371,9 +383,7 @@ def _find_best_version_for_package( return package.pretty_name, selector.find_recommended_require_version(package) - def _parse_requirements( - self, requirements - ): # type: (List[str]) -> List[Dict[str, str]] + def _parse_requirements(self, requirements: List[str]) -> List[Dict[str, str]]: from poetry.puzzle.provider import Provider result = [] @@ -490,8 +500,8 @@ def _parse_requirements( return result def _format_requirements( - self, requirements - ): # type: (List[Dict[str, str]]) -> Dict[str, Union[str, Dict[str, str]]] + self, requirements: List[Dict[str, str]] + ) -> Dict[str, Union[str, Dict[str, str]]]: requires = {} for requirement in requirements: name = requirement.pop("name") @@ -506,7 +516,7 @@ def _format_requirements( return requires - def _validate_author(self, author, default): # type: (str, str) -> Optional[str] + def _validate_author(self, author: str, default: str) -> Optional[str]: from poetry.core.packages.package import AUTHOR_REGEX author = author or default @@ -523,7 +533,7 @@ def _validate_author(self, author, default): # type: (str, str) -> Optional[str return author - def _validate_license(self, license): # type: (str) -> str + def _validate_license(self, license: str) -> str: from poetry.core.spdx import license_by_id if license: @@ -531,7 +541,7 @@ def _validate_license(self, license): # type: (str) -> str return license - def _get_pool(self): # type: () -> "Pool" + def _get_pool(self) -> "Pool": from poetry.repositories import Pool from poetry.repositories.pypi_repository import PyPiRepository diff --git a/poetry/console/commands/install.py b/poetry/console/commands/install.py index d07c5cef93f..0ffb30a3756 100644 --- a/poetry/console/commands/install.py +++ b/poetry/console/commands/install.py @@ -50,7 +50,7 @@ class InstallCommand(InstallerCommand): _loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"] - def handle(self): # type: () -> int + def handle(self) -> int: from poetry.core.masonry.utils.module import ModuleOrPackageNotFound from poetry.masonry.builders import EditableBuilder diff --git a/poetry/console/commands/installer_command.py b/poetry/console/commands/installer_command.py index dd8ca1a8842..409e2fc456c 100644 --- a/poetry/console/commands/installer_command.py +++ b/poetry/console/commands/installer_command.py @@ -9,20 +9,20 @@ class InstallerCommand(EnvCommand): - def __init__(self): # type: () -> None - self._installer = None # type: Optional[Installer] + def __init__(self) -> None: + self._installer: Optional["Installer"] = None super(InstallerCommand, self).__init__() - def reset_poetry(self): # type: () -> None + def reset_poetry(self) -> None: super(InstallerCommand, self).reset_poetry() self._installer.set_package(self.poetry.package) self._installer.set_locker(self.poetry.locker) @property - def installer(self): # type: () -> Installer + def installer(self) -> "Installer": return self._installer - def set_installer(self, installer): # type: (Installer) -> None + def set_installer(self, installer: "Installer") -> None: self._installer = installer diff --git a/poetry/console/commands/lock.py b/poetry/console/commands/lock.py index d2895e8bee6..c62515f8a67 100644 --- a/poetry/console/commands/lock.py +++ b/poetry/console/commands/lock.py @@ -24,7 +24,7 @@ class LockCommand(InstallerCommand): loggers = ["poetry.repositories.pypi_repository"] - def handle(self): # type: () -> int + def handle(self) -> int: self._installer.use_executor( self.poetry.config.get("experimental.new-installer", False) ) diff --git a/poetry/console/commands/new.py b/poetry/console/commands/new.py index 3276ee7d9fd..e80e589d39c 100644 --- a/poetry/console/commands/new.py +++ b/poetry/console/commands/new.py @@ -19,7 +19,7 @@ class NewCommand(Command): option("src", None, "Use the src layout for the project."), ] - def handle(self): # type: () -> None + def handle(self) -> None: from pathlib import Path from poetry.core.semver import parse_constraint diff --git a/poetry/console/commands/publish.py b/poetry/console/commands/publish.py index 724e868a58a..bd777bb65d2 100644 --- a/poetry/console/commands/publish.py +++ b/poetry/console/commands/publish.py @@ -41,7 +41,7 @@ class PublishCommand(Command): loggers = ["poetry.masonry.publishing.publisher"] - def handle(self): # type: () -> Optional[int] + def handle(self) -> Optional[int]: from poetry.publishing.publisher import Publisher publisher = Publisher(self.poetry, self.io) diff --git a/poetry/console/commands/remove.py b/poetry/console/commands/remove.py index 15e05de2860..a7b2e00a26f 100644 --- a/poetry/console/commands/remove.py +++ b/poetry/console/commands/remove.py @@ -27,7 +27,7 @@ class RemoveCommand(InstallerCommand): loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"] - def handle(self): # type: () -> int + def handle(self) -> int: packages = self.argument("packages") is_dev = self.option("dev") diff --git a/poetry/console/commands/run.py b/poetry/console/commands/run.py index 9879b3ed09c..fd23f93ddd7 100644 --- a/poetry/console/commands/run.py +++ b/poetry/console/commands/run.py @@ -1,3 +1,4 @@ +from typing import TYPE_CHECKING from typing import Any from typing import Union @@ -6,6 +7,10 @@ from .env_command import EnvCommand +if TYPE_CHECKING: + from poetry.core.masonry.utils.module import Module + + class RunCommand(EnvCommand): name = "run" @@ -15,7 +20,7 @@ class RunCommand(EnvCommand): argument("args", "The command and arguments/options to run.", multiple=True) ] - def handle(self): # type: () -> Any + def handle(self) -> Any: args = self.argument("args") script = args[0] scripts = self.poetry.local_config.get("scripts") @@ -26,7 +31,7 @@ def handle(self): # type: () -> Any return self.env.execute(*args) @property - def _module(self): + def _module(self) -> "Module": from poetry.core.masonry.utils.module import Module poetry = self.poetry @@ -36,7 +41,7 @@ def _module(self): return module - def run_script(self, script, args): # type: (Union[str, dict], str) -> Any + def run_script(self, script: Union[str, dict], args: str) -> Any: if isinstance(script, dict): script = script["callable"] diff --git a/poetry/console/commands/search.py b/poetry/console/commands/search.py index 27c4671a45b..85384125bca 100644 --- a/poetry/console/commands/search.py +++ b/poetry/console/commands/search.py @@ -10,7 +10,7 @@ class SearchCommand(Command): arguments = [argument("tokens", "The tokens to search for.", multiple=True)] - def handle(self): # type: () -> None + def handle(self) -> None: from poetry.repositories.pypi_repository import PyPiRepository results = PyPiRepository().search(self.argument("tokens")) diff --git a/poetry/console/commands/self/update.py b/poetry/console/commands/self/update.py index d0d00ddbefd..06ef628b786 100644 --- a/poetry/console/commands/self/update.py +++ b/poetry/console/commands/self/update.py @@ -11,6 +11,7 @@ from functools import cmp_to_key from gzip import GzipFile +from pathlib import Path from typing import TYPE_CHECKING from typing import Any @@ -24,9 +25,8 @@ if TYPE_CHECKING: - from poetry.core.packages import Package # noqa + from poetry.core.packages import Package from poetry.core.semver import Version - from poetry.utils._compat import Path try: @@ -70,24 +70,24 @@ class SelfUpdateCommand(Command): BASE_URL = REPOSITORY_URL + "/releases/download" @property - def home(self): # type: () -> Path + def home(self) -> Path: from pathlib import Path return Path(os.environ.get("POETRY_HOME", "~/.poetry")).expanduser() @property - def bin(self): # type: () -> Path + def bin(self) -> Path: return self.home / "bin" @property - def lib(self): # type: () -> Path + def lib(self) -> Path: return self.home / "lib" @property - def lib_backup(self): # type: () -> Path + def lib_backup(self) -> Path: return self.home / "lib-backup" - def handle(self): # type: () -> None + def handle(self) -> None: from poetry.__version__ import __version__ from poetry.core.semver import Version from poetry.repositories.pypi_repository import PyPiRepository @@ -138,7 +138,7 @@ def handle(self): # type: () -> None self.update(release) - def update(self, release): # type: ("Package") -> None + def update(self, release: "Package") -> None: version = release.version self.line("Updating to {}".format(version)) @@ -174,7 +174,7 @@ def update(self, release): # type: ("Package") -> None ) ) - def _update(self, version): # type: ("Version") -> None + def _update(self, version: "Version") -> None: from poetry.utils.helpers import temporary_directory release_name = self._get_release_name(version) @@ -244,10 +244,10 @@ def _update(self, version): # type: ("Version") -> None finally: gz.close() - def process(self, *args): # type: (*Any) -> str + def process(self, *args: Any) -> str: return subprocess.check_output(list(args), stderr=subprocess.STDOUT) - def _check_recommended_installation(self): # type: () -> None + def _check_recommended_installation(self) -> None: from pathlib import Path current = Path(__file__) @@ -259,14 +259,14 @@ def _check_recommended_installation(self): # type: () -> None "so it cannot be updated automatically." ) - def _get_release_name(self, version): # type: ("Version") -> str + def _get_release_name(self, version: "Version") -> str: platform = sys.platform if platform == "linux2": platform = "linux" return "poetry-{}-{}".format(version, platform) - def make_bin(self): # type: () -> None + def make_bin(self) -> None: from poetry.utils._compat import WINDOWS self.bin.mkdir(0o755, parents=True, exist_ok=True) @@ -295,7 +295,7 @@ def make_bin(self): # type: () -> None st = os.stat(str(self.bin.joinpath("poetry"))) os.chmod(str(self.bin.joinpath("poetry")), st.st_mode | stat.S_IEXEC) - def _which_python(self): # type: () -> str + def _which_python(self) -> str: """ Decides which python executable we'll embed in the launcher script. """ diff --git a/poetry/console/commands/shell.py b/poetry/console/commands/shell.py index 35269d38d3d..f0f7f851fba 100644 --- a/poetry/console/commands/shell.py +++ b/poetry/console/commands/shell.py @@ -16,7 +16,7 @@ class ShellCommand(EnvCommand): If one doesn't exist yet, it will be created. """ - def handle(self): # type: () -> None + def handle(self) -> None: from poetry.utils.shell import Shell # Check if it's already activated or doesn't exist and won't be created diff --git a/poetry/console/commands/show.py b/poetry/console/commands/show.py index 07a58cd0754..e07b97e13a6 100644 --- a/poetry/console/commands/show.py +++ b/poetry/console/commands/show.py @@ -13,8 +13,8 @@ if TYPE_CHECKING: from cleo.io.io import IO # noqa - from poetry.core.packages import Dependency # noqa - from poetry.core.packages import Package # noqa + from poetry.core.packages import Dependency + from poetry.core.packages import Package from poetry.repositories import Repository from poetry.repositories.installed_repository import InstalledRepository @@ -46,7 +46,7 @@ class ShowCommand(EnvCommand): colors = ["cyan", "yellow", "green", "magenta", "blue"] - def handle(self): # type: () -> Optional[int] + def handle(self) -> Optional[int]: from cleo.io.null_io import NullIO from cleo.terminal import Terminal @@ -271,8 +271,8 @@ def handle(self): # type: () -> Optional[int] self.line(line) def display_package_tree( - self, io, package, installed_repo - ): # type: ("IO", "Package", "Repository") -> None + self, io: "IO", package: "Package", installed_repo: "Repository" + ) -> None: io.write("{}".format(package.pretty_name)) description = "" if package.description: @@ -309,13 +309,13 @@ def display_package_tree( def _display_tree( self, - io, # type: "IO" - dependency, # type: "Dependency" - installed_repo, # type: "Repository" - packages_in_tree, # type: List[str] - previous_tree_bar="├", # type: str - level=1, # type: int - ): # type: (...) -> None + io: "IO", + dependency: "Dependency", + installed_repo: "Repository", + packages_in_tree: List[str], + previous_tree_bar: str = "├", + level: int = 1, + ) -> None: previous_tree_bar = previous_tree_bar.replace("├", "│") dependencies = [] @@ -360,7 +360,7 @@ def _display_tree( io, dependency, installed_repo, current_tree, tree_bar, level + 1 ) - def _write_tree_line(self, io, line): # type: ("IO", str) -> None + def _write_tree_line(self, io: "IO", line: str) -> None: if not io.output.supports_utf8(): line = line.replace("└", "`-") line = line.replace("├", "|-") @@ -369,7 +369,7 @@ def _write_tree_line(self, io, line): # type: ("IO", str) -> None io.write_line(line) - def init_styles(self, io): # type: ("IO") -> None + def init_styles(self, io: "IO") -> None: from cleo.formatters.style import Style for color in self.colors: @@ -378,8 +378,8 @@ def init_styles(self, io): # type: ("IO") -> None io.error_output.formatter.set_style(color, style) def find_latest_package( - self, package, include_dev - ): # type: ("Package", bool) -> Union["Package", bool] + self, package: "Package", include_dev: bool + ) -> Union["Package", bool]: from cleo.io.null_io import NullIO from poetry.puzzle.provider import Provider @@ -407,7 +407,7 @@ def find_latest_package( return selector.find_best_candidate(name, ">={}".format(package.pretty_version)) - def get_update_status(self, latest, package): # type: ("Package", "Package") -> str + def get_update_status(self, latest: "Package", package: "Package") -> str: from poetry.core.semver import parse_constraint if latest.full_pretty_version == package.full_pretty_version: @@ -423,8 +423,8 @@ def get_update_status(self, latest, package): # type: ("Package", "Package") -> return "update-possible" def get_installed_status( - self, locked, installed_repo - ): # type: ("Package", "InstalledRepository") -> str + self, locked: "Package", installed_repo: "InstalledRepository" + ) -> str: for package in installed_repo.packages: if locked.name == package.name: return "installed" diff --git a/poetry/console/commands/update.py b/poetry/console/commands/update.py index 42bb64aba37..fd2b421bce2 100644 --- a/poetry/console/commands/update.py +++ b/poetry/console/commands/update.py @@ -27,7 +27,7 @@ class UpdateCommand(InstallerCommand): loggers = ["poetry.repositories.pypi_repository"] - def handle(self): # type: () -> int + def handle(self) -> int: packages = self.argument("packages") self._installer.use_executor( diff --git a/poetry/console/commands/version.py b/poetry/console/commands/version.py index 2fadaac4b39..cffe1b0f723 100644 --- a/poetry/console/commands/version.py +++ b/poetry/console/commands/version.py @@ -1,9 +1,15 @@ +from typing import TYPE_CHECKING + from cleo.helpers import argument from cleo.helpers import option from .command import Command +if TYPE_CHECKING: + from poetry.core.semver import Version + + class VersionCommand(Command): name = "version" @@ -40,7 +46,7 @@ class VersionCommand(Command): "prerelease", } - def handle(self): # type: () -> None + def handle(self) -> None: version = self.argument("version") if version: @@ -72,7 +78,7 @@ def handle(self): # type: () -> None ) ) - def increment_version(self, version, rule): # type: (str, str) -> "Version" + def increment_version(self, version: str, rule: str) -> "Version": from poetry.core.semver import Version try: diff --git a/poetry/console/logging/formatters/builder_formatter.py b/poetry/console/logging/formatters/builder_formatter.py index 56bed9b67a3..69641e7662a 100644 --- a/poetry/console/logging/formatters/builder_formatter.py +++ b/poetry/console/logging/formatters/builder_formatter.py @@ -4,7 +4,7 @@ class BuilderLogFormatter(Formatter): - def format(self, msg): # type: (str) -> str + def format(self, msg: str) -> str: if msg.startswith("Building "): msg = re.sub("Building (.+)", " - Building \\1", msg) elif msg.startswith("Built "): diff --git a/poetry/console/logging/formatters/formatter.py b/poetry/console/logging/formatters/formatter.py index 35b59374be4..f2ab7b18624 100644 --- a/poetry/console/logging/formatters/formatter.py +++ b/poetry/console/logging/formatters/formatter.py @@ -2,5 +2,5 @@ class Formatter(object): - def format(self, record): # type: (logging.LogRecord) -> str + def format(self, record: logging.LogRecord) -> str: raise NotImplementedError() diff --git a/poetry/console/logging/io_formatter.py b/poetry/console/logging/io_formatter.py index 68d15691edd..c7ca46fef97 100644 --- a/poetry/console/logging/io_formatter.py +++ b/poetry/console/logging/io_formatter.py @@ -6,7 +6,7 @@ if TYPE_CHECKING: - from logging import LogRecord # noqa + from logging import LogRecord class IOFormatter(logging.Formatter): @@ -18,7 +18,7 @@ class IOFormatter(logging.Formatter): "info": "fg=blue", } - def format(self, record): # type: ("LogRecord") -> str + def format(self, record: "LogRecord") -> str: if not record.exc_info: level = record.levelname.lower() msg = record.msg diff --git a/poetry/console/logging/io_handler.py b/poetry/console/logging/io_handler.py index 03804c3a669..9a13e68b0e9 100644 --- a/poetry/console/logging/io_handler.py +++ b/poetry/console/logging/io_handler.py @@ -4,18 +4,18 @@ if TYPE_CHECKING: - from logging import LogRecord # noqa + from logging import LogRecord from cleo.io.io import IO # noqa class IOHandler(logging.Handler): - def __init__(self, io): # type: ("IO") -> None + def __init__(self, io: "IO") -> None: self._io = io super(IOHandler, self).__init__() - def emit(self, record): # type: ("LogRecord") -> None + def emit(self, record: "LogRecord") -> None: try: msg = self.format(record) level = record.levelname.lower() diff --git a/poetry/factory.py b/poetry/factory.py index d8a9da7ed77..43555eef6c3 100644 --- a/poetry/factory.py +++ b/poetry/factory.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from pathlib import Path +from typing import TYPE_CHECKING from typing import Dict from typing import Optional @@ -19,14 +20,18 @@ from .repositories.pypi_repository import PyPiRepository +if TYPE_CHECKING: + from .repositories.legacy_repository import LegacyRepository + + class Factory(BaseFactory): """ Factory class to create various elements needed by Poetry. """ def create_poetry( - self, cwd=None, io=None - ): # type: (Optional[Path], Optional[IO]) -> Poetry + self, cwd: Optional[Path] = None, io: Optional[IO] = None + ) -> Poetry: if io is None: io = NullIO() @@ -100,7 +105,7 @@ def create_poetry( return poetry @classmethod - def create_config(cls, io=None): # type: (Optional[IO]) -> Config + def create_config(cls, io: Optional[IO] = None) -> Config: if io is None: io = NullIO() @@ -136,8 +141,8 @@ def create_config(cls, io=None): # type: (Optional[IO]) -> Config return config def create_legacy_repository( - self, source, auth_config - ): # type: (Dict[str, str], Config) -> "LegacyRepository" + self, source: Dict[str, str], auth_config: Config + ) -> "LegacyRepository": from .repositories.legacy_repository import LegacyRepository from .utils.helpers import get_cert from .utils.helpers import get_client_cert diff --git a/poetry/inspection/info.py b/poetry/inspection/info.py index 495a62ca3c0..1814519d240 100644 --- a/poetry/inspection/info.py +++ b/poetry/inspection/info.py @@ -43,8 +43,8 @@ class PackageInfoError(ValueError): def __init__( - self, path, *reasons - ): # type: (Union[Path, str], *Union[BaseException, str]) -> None + self, path: Union[Path, str], *reasons: Union[BaseException, str] + ) -> None: reasons = ( "Unable to determine package info for path: {}".format(str(path)), ) + reasons @@ -56,14 +56,14 @@ def __init__( class PackageInfo: def __init__( self, - name=None, # type: Optional[str] - version=None, # type: Optional[str] - summary=None, # type: Optional[str] - platform=None, # type: Optional[str] - requires_dist=None, # type: Optional[List[str]] - requires_python=None, # type: Optional[str] - files=None, # type: Optional[List[str]] - cache_version=None, # type: Optional[str] + name: Optional[str] = None, + version: Optional[str] = None, + summary: Optional[str] = None, + platform: Optional[str] = None, + requires_dist: Optional[List[str]] = None, + requires_python: Optional[str] = None, + files: Optional[List[str]] = None, + cache_version: Optional[str] = None, ): self.name = name self.version = version @@ -78,10 +78,10 @@ def __init__( self._source_reference = None @property - def cache_version(self): # type: () -> Optional[str] + def cache_version(self) -> Optional[str]: return self._cache_version - def update(self, other): # type: (PackageInfo) -> PackageInfo + 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 @@ -92,7 +92,7 @@ def update(self, other): # type: (PackageInfo) -> PackageInfo self._cache_version = other.cache_version or self._cache_version return self - def asdict(self): # type: () -> Dict[str, Optional[Union[str, List[str]]]] + def asdict(self) -> Dict[str, Optional[Union[str, List[str]]]]: """ Helper method to convert package info into a dictionary used for caching. """ @@ -108,9 +108,7 @@ def asdict(self): # type: () -> Dict[str, Optional[Union[str, List[str]]]] } @classmethod - def load( - cls, data - ): # type: (Dict[str, Optional[Union[str, List[str]]]]) -> PackageInfo + def load(cls, data: Dict[str, Optional[Union[str, List[str]]]]) -> "PackageInfo": """ Helper method to load data from a dictionary produced by `PackageInfo.asdict()`. @@ -120,13 +118,16 @@ def load( return cls(cache_version=cache_version, **data) @classmethod - def _log(cls, msg, level="info"): # type: (str, str) -> None + def _log(cls, msg: str, level: str = "info") -> None: """Internal helper method to log information.""" getattr(logger, level)("{}: {}".format(cls.__name__, msg)) def to_package( - self, name=None, extras=None, root_dir=None - ): # type: (Optional[str], Optional[List[str]], Optional[Path]) -> Package + self, + name: Optional[str] = None, + extras: Optional[List[str]] = None, + root_dir: Optional[Path] = None, + ) -> Package: """ Create a new `poetry.core.packages.package.Package` instance using metadata from this instance. @@ -202,8 +203,8 @@ def to_package( @classmethod def _from_distribution( - cls, dist - ): # type: (Union[pkginfo.BDist, pkginfo.SDist, pkginfo.Wheel]) -> PackageInfo + cls, dist: Union[pkginfo.BDist, pkginfo.SDist, pkginfo.Wheel] + ) -> "PackageInfo": """ Helper method to parse package information from a `pkginfo.Distribution` instance. @@ -234,7 +235,7 @@ def _from_distribution( return info @classmethod - def _from_sdist_file(cls, path): # type: (Path) -> PackageInfo + def _from_sdist_file(cls, path: Path) -> "PackageInfo": """ Helper method to parse package information from an sdist file. We attempt to first inspect the file using `pkginfo.SDist`. If this does not provide us with package requirements, we extract the @@ -295,11 +296,11 @@ def _from_sdist_file(cls, path): # type: (Path) -> PackageInfo return info.update(new_info) @staticmethod - def has_setup_files(path): # type: (Path) -> bool + def has_setup_files(path: Path) -> bool: return any((path / f).exists() for f in SetupReader.FILES) @classmethod - def from_setup_files(cls, path): # type: (Path) -> PackageInfo + def from_setup_files(cls, path: Path) -> "PackageInfo": """ Mechanism to parse package information from a `setup.[py|cfg]` file. This uses the implementation at `poetry.utils.setup_reader.SetupReader` in order to parse the file. This is not reliable for @@ -356,7 +357,7 @@ def from_setup_files(cls, path): # type: (Path) -> PackageInfo return info @staticmethod - def _find_dist_info(path): # type: (Path) -> Iterator[Path] + def _find_dist_info(path: Path) -> Iterator[Path]: """ Discover all `*.*-info` directories in a given path. @@ -372,7 +373,7 @@ def _find_dist_info(path): # type: (Path) -> Iterator[Path] yield Path(d) @classmethod - def from_metadata(cls, path): # type: (Path) -> Optional[PackageInfo] + def from_metadata(cls, path: Path) -> Optional["PackageInfo"]: """ Helper method to parse package information from an unpacked metadata directory. @@ -406,7 +407,7 @@ def from_metadata(cls, path): # type: (Path) -> Optional[PackageInfo] return info @classmethod - def from_package(cls, package): # type: (Package) -> PackageInfo + def from_package(cls, package: Package) -> "PackageInfo": """ Helper method to inspect a `Package` object, in order to generate package info. @@ -429,14 +430,14 @@ def from_package(cls, package): # type: (Package) -> PackageInfo ) @staticmethod - def _get_poetry_package(path): # type: (Path) -> Optional[ProjectPackage] + def _get_poetry_package(path: Path) -> Optional[ProjectPackage]: # Note: we ignore any setup.py file at this step # TODO: add support for handling non-poetry PEP-517 builds if PyProjectTOML(path.joinpath("pyproject.toml")).is_poetry_project(): return Factory().create_poetry(path).package @classmethod - def _pep517_metadata(cls, path): # type: (Path) -> PackageInfo + def _pep517_metadata(cls, path: Path) -> "PackageInfo": """ Helper method to use PEP-517 library to build and read package metadata. @@ -511,9 +512,7 @@ def _pep517_metadata(cls, path): # type: (Path) -> PackageInfo raise PackageInfoError(path, "Exhausted all core metadata sources.") @classmethod - def from_directory( - cls, path, disable_build=False - ): # type: (Path, bool) -> PackageInfo + def from_directory(cls, path: Path, disable_build: bool = False) -> "PackageInfo": """ Generate package information from a package source directory. If `disable_build` is not `True` and introspection of all available metadata fails, the package is attempted to be build in an isolated @@ -547,7 +546,7 @@ def from_directory( return info @classmethod - def from_sdist(cls, path): # type: (Path) -> PackageInfo + def from_sdist(cls, path: Path) -> "PackageInfo": """ Gather package information from an sdist file, packed or unpacked. @@ -561,7 +560,7 @@ def from_sdist(cls, path): # type: (Path) -> PackageInfo return cls.from_directory(path=path) @classmethod - def from_wheel(cls, path): # type: (Path) -> PackageInfo + def from_wheel(cls, path: Path) -> "PackageInfo": """ Gather package information from a wheel. @@ -573,7 +572,7 @@ def from_wheel(cls, path): # type: (Path) -> PackageInfo return PackageInfo() @classmethod - def from_bdist(cls, path): # type: (Path) -> PackageInfo + def from_bdist(cls, path: Path) -> "PackageInfo": """ Gather package information from a bdist (wheel etc.). @@ -591,7 +590,7 @@ def from_bdist(cls, path): # type: (Path) -> PackageInfo raise PackageInfoError(path, e) @classmethod - def from_path(cls, path): # type: (Path) -> PackageInfo + def from_path(cls, path: Path) -> "PackageInfo": """ Gather package information from a given path (bdist, sdist, directory). diff --git a/poetry/installation/authenticator.py b/poetry/installation/authenticator.py index f40c03d2b7e..84ba26c027e 100644 --- a/poetry/installation/authenticator.py +++ b/poetry/installation/authenticator.py @@ -3,6 +3,9 @@ import urllib.parse from typing import TYPE_CHECKING +from typing import Any +from typing import Optional +from typing import Tuple import requests import requests.auth @@ -13,10 +16,6 @@ if TYPE_CHECKING: - from typing import Any - from typing import Optional - from typing import Tuple - from cleo.io.io import IO from poetry.config.config import Config @@ -26,14 +25,14 @@ class Authenticator(object): - def __init__(self, config, io=None): # type: (Config, Optional[IO]) -> None + def __init__(self, config: "Config", io: Optional["IO"] = None) -> None: self._config = config self._io = io self._session = None self._credentials = {} self._password_manager = PasswordManager(self._config) - def _log(self, message, level="debug"): # type: (str, str) -> None + def _log(self, message: str, level: str = "debug") -> None: if self._io is not None: self._io.write_line( "<{level:s}>{message:s}".format( @@ -44,15 +43,13 @@ def _log(self, message, level="debug"): # type: (str, str) -> None getattr(logger, level, logger.debug)(message) @property - def session(self): # type: () -> requests.Session + def session(self) -> requests.Session: if self._session is None: self._session = requests.Session() return self._session - def request( - self, method, url, **kwargs - ): # type: (str, str, Any) -> requests.Response + def request(self, method: str, url: str, **kwargs: Any) -> requests.Response: request = requests.Request(method, url) username, password = self.get_credentials_for_url(url) @@ -104,9 +101,7 @@ def request( # this should never really be hit under any sane circumstance raise PoetryException("Failed HTTP {} request", method.upper()) - def get_credentials_for_url( - self, url - ): # type: (str) -> Tuple[Optional[str], Optional[str]] + def get_credentials_for_url(self, url: str) -> Tuple[Optional[str], Optional[str]]: parsed_url = urllib.parse.urlsplit(url) netloc = parsed_url.netloc @@ -141,8 +136,8 @@ def get_credentials_for_url( return credentials[0], credentials[1] def _get_credentials_for_netloc_from_config( - self, netloc - ): # type: (str) -> Tuple[Optional[str], Optional[str]] + self, netloc: str + ) -> Tuple[Optional[str], Optional[str]]: credentials = (None, None) for repository_name in self._config.get("repositories", []): diff --git a/poetry/installation/base_installer.py b/poetry/installation/base_installer.py index 4f600e880d4..c377dea7058 100644 --- a/poetry/installation/base_installer.py +++ b/poetry/installation/base_installer.py @@ -2,15 +2,15 @@ if TYPE_CHECKING: - from poetry.core.packages import Package # noqa + from poetry.core.packages import Package class BaseInstaller: - def install(self, package): # type: ("Package") -> None + def install(self, package: "Package") -> None: raise NotImplementedError - def update(self, source, target): # type: ("Package", "Package") -> None + def update(self, source: "Package", target: "Package") -> None: raise NotImplementedError - def remove(self, package): # type: ("Package") -> None + def remove(self, package: "Package") -> None: raise NotImplementedError diff --git a/poetry/installation/chef.py b/poetry/installation/chef.py index 6009ac0a98d..4f373b8752a 100644 --- a/poetry/installation/chef.py +++ b/poetry/installation/chef.py @@ -3,6 +3,8 @@ from pathlib import Path from typing import TYPE_CHECKING +from typing import List +from typing import Optional from poetry.core.packages.utils.link import Link @@ -11,37 +13,35 @@ if TYPE_CHECKING: - from typing import List - from typing import Optional from poetry.config.config import Config from poetry.utils.env import Env class Chef: - def __init__(self, config, env): # type: (Config, Env) -> None + 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): # type: (Path) -> Path + def prepare(self, archive: Path) -> Path: return archive - def prepare_sdist(self, archive): # type: (Path) -> Path + def prepare_sdist(self, archive: Path) -> Path: return archive - def prepare_wheel(self, archive): # type: (Path) -> Path + def prepare_wheel(self, archive: Path) -> Path: return archive - def should_prepare(self, archive): # type: (Path) -> bool + def should_prepare(self, archive: Path) -> bool: return not self.is_wheel(archive) - def is_wheel(self, archive): # type: (Path) -> bool + def is_wheel(self, archive: Path) -> bool: return archive.suffix == ".whl" - def get_cached_archive_for_link(self, link): # type: (Link) -> Optional[Link] + def get_cached_archive_for_link(self, link: Link) -> Optional[Link]: # If the archive is already a wheel, there is no need to cache it. if link.is_wheel: pass @@ -74,7 +74,7 @@ def get_cached_archive_for_link(self, link): # type: (Link) -> Optional[Link] return min(candidates)[1] - def get_cached_archives_for_link(self, link): # type: (Link) -> List[Link] + def get_cached_archives_for_link(self, link: Link) -> List[Link]: cache_dir = self.get_cache_directory_for_link(link) archive_types = ["whl", "tar.gz", "tar.bz2", "bz2", "zip"] @@ -85,7 +85,7 @@ def get_cached_archives_for_link(self, link): # type: (Link) -> List[Link] return links - def get_cache_directory_for_link(self, link): # type: (Link) -> Path + 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: diff --git a/poetry/installation/chooser.py b/poetry/installation/chooser.py index 1762f2ce546..10c89c1ce6d 100644 --- a/poetry/installation/chooser.py +++ b/poetry/installation/chooser.py @@ -18,7 +18,7 @@ class InvalidWheelName(Exception): class Wheel(object): - def __init__(self, filename): # type: (str) -> None + def __init__(self, filename: str) -> None: wheel_info = wheel_file_re.match(filename) if not wheel_info: raise InvalidWheelName("{} is not a valid wheel filename.".format(filename)) @@ -35,12 +35,12 @@ def __init__(self, filename): # type: (str) -> None 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): # type: (List[Tag]) -> Optional[int] + def get_minimum_supported_index(self, tags: List[Tag]) -> Optional[int]: 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): # type: (Env) -> bool + def is_supported_by_environment(self, env: Env) -> bool: return bool(set(env.supported_tags).intersection(self.tags)) @@ -49,11 +49,11 @@ class Chooser: A Chooser chooses an appropriate release archive for packages. """ - def __init__(self, pool, env): # type: (Pool, Env) -> None + def __init__(self, pool: Pool, env: Env) -> None: self._pool = pool self._env = env - def choose_for(self, package): # type: (Package) -> Link + def choose_for(self, package: Package) -> Link: """ Return the url of the selected archive for a given package. """ @@ -83,7 +83,7 @@ def choose_for(self, package): # type: (Package) -> Link return chosen - def _get_links(self, package): # type: (Package) -> List[Link] + def _get_links(self, package: Package) -> List[Link]: if not package.source_type: if not self._pool.has_repository("pypi"): repository = self._pool.repositories[0] @@ -112,7 +112,7 @@ def _get_links(self, package): # type: (Package) -> List[Link] return selected_links - def _sort_key(self, package, link): # type: (Package, Link) -> Tuple + def _sort_key(self, package: Package, link: Link) -> Tuple: """ Function to pass as the `key` argument to a call to sorted() to sort InstallationCandidates by preference. @@ -170,9 +170,7 @@ def _sort_key(self, package, link): # type: (Package, Link) -> Tuple pri, ) - def _is_link_hash_allowed_for_package( - self, link, package - ): # type: (Link, Package) -> bool + def _is_link_hash_allowed_for_package(self, link: Link, package: Package) -> bool: if not link.hash: return True diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index 2ffca66a1e7..520487d2536 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -35,17 +35,22 @@ if TYPE_CHECKING: from cleo.io.io import IO # noqa - from poetry.config.config import Config # noqa - from poetry.repositories import Pool # noqa - from poetry.utils.env import Env # noqa + from poetry.config.config import Config + from poetry.repositories import Pool + from poetry.utils.env import Env - from .operations import OperationTypes # noqa + from .operations import OperationTypes class Executor(object): def __init__( - self, env, pool, config, io, parallel=None - ): # type: ("Env", "Pool", "Config", "IO", bool) -> None + self, + env: "Env", + pool: "Pool", + config: "Config", + io: "IO", + parallel: bool = None, + ) -> None: self._env = env self._io = io self._dry_run = False @@ -80,36 +85,36 @@ def __init__( self._shutdown = False @property - def installations_count(self): # type: () -> int + def installations_count(self) -> int: return self._executed["install"] @property - def updates_count(self): # type: () -> int + def updates_count(self) -> int: return self._executed["update"] @property - def removals_count(self): # type: () -> int + def removals_count(self) -> int: return self._executed["uninstall"] - def supports_fancy_output(self): # type: () -> bool + def supports_fancy_output(self) -> bool: return self._io.output.is_decorated() and not self._dry_run - def disable(self): # type: () -> "Executor" + def disable(self) -> "Executor": self._enabled = False return self - def dry_run(self, dry_run=True): # type: (bool) -> Executor + def dry_run(self, dry_run: bool = True) -> "Executor": self._dry_run = dry_run return self - def verbose(self, verbose=True): # type: (bool) -> Executor + def verbose(self, verbose: bool = True) -> "Executor": self._verbose = verbose return self - def execute(self, operations): # type: (List["OperationTypes"]) -> int + def execute(self, operations: List["OperationTypes"]) -> int: self._total_operations = len(operations) for job_type in self._executed: self._executed[job_type] = 0 @@ -162,7 +167,7 @@ def execute(self, operations): # type: (List["OperationTypes"]) -> int return 1 if self._shutdown else 0 - def _write(self, operation, line): # type: ("OperationTypes", str) -> None + def _write(self, operation: "OperationTypes", line: str) -> None: if not self.supports_fancy_output() or not self._should_write_operation( operation ): @@ -180,7 +185,7 @@ def _write(self, operation, line): # type: ("OperationTypes", str) -> None section.clear() section.write(line) - def _execute_operation(self, operation): # type: ("OperationTypes") -> None + def _execute_operation(self, operation: "OperationTypes") -> None: try: if self.supports_fancy_output(): if id(operation) not in self._sections: @@ -259,7 +264,7 @@ def _execute_operation(self, operation): # type: ("OperationTypes") -> None with self._lock: self._shutdown = True - def _do_execute_operation(self, operation): # type: ("OperationTypes") -> int + def _do_execute_operation(self, operation: "OperationTypes") -> int: method = operation.job_type operation_message = self.get_operation_message(operation) @@ -304,8 +309,8 @@ def _do_execute_operation(self, operation): # type: ("OperationTypes") -> int return result def _increment_operations_count( - self, operation, executed - ): # type: ("OperationTypes", bool) -> None + self, operation: "OperationTypes", executed: bool + ) -> None: with self._lock: if executed: self._executed_operations += 1 @@ -313,7 +318,7 @@ def _increment_operations_count( else: self._skipped[operation.job_type] += 1 - def run_pip(self, *args, **kwargs): # type: (*Any, **Any) -> int + def run_pip(self, *args: Any, **kwargs: Any) -> int: try: self._env.run_pip(*args, **kwargs) except EnvCommandError as e: @@ -329,8 +334,12 @@ def run_pip(self, *args, **kwargs): # type: (*Any, **Any) -> int return 0 def get_operation_message( - self, operation, done=False, error=False, warning=False - ): # type: ("OperationTypes", bool, bool, bool) -> str + self, + operation: "OperationTypes", + done: bool = False, + error: bool = False, + warning: bool = False, + ) -> str: base_tag = "fg=default" operation_color = "c2" source_operation_color = "c2" @@ -384,7 +393,7 @@ def get_operation_message( return "" - def _display_summary(self, operations): # type: (List["OperationTypes"]) -> None + def _display_summary(self, operations: List["OperationTypes"]) -> None: installs = 0 updates = 0 uninstalls = 0 @@ -427,13 +436,13 @@ def _display_summary(self, operations): # type: (List["OperationTypes"]) -> Non ) self._io.write_line("") - def _execute_install(self, operation): # type: (Union[Install, Update]) -> int + def _execute_install(self, operation: Union[Install, Update]) -> int: return self._install(operation) - def _execute_update(self, operation): # type: (Union[Install, Update]) -> int + def _execute_update(self, operation: Union[Install, Update]) -> int: return self._update(operation) - def _execute_uninstall(self, operation): # type: (Uninstall) -> int + def _execute_uninstall(self, operation: Uninstall) -> int: message = ( " • {message}: Removing...".format( message=self.get_operation_message(operation), @@ -443,7 +452,7 @@ def _execute_uninstall(self, operation): # type: (Uninstall) -> int return self._remove(operation) - def _install(self, operation): # type: (Union[Install, Update]) -> int + def _install(self, operation: Union[Install, Update]) -> int: package = operation.package if package.source_type == "directory": return self._install_directory(operation) @@ -472,10 +481,10 @@ def _install(self, operation): # type: (Union[Install, Update]) -> int return self.run_pip(*args) - def _update(self, operation): # type: (Union[Install, Update]) -> int + def _update(self, operation: Union[Install, Update]) -> int: return self._install(operation) - def _remove(self, operation): # type: (Uninstall) -> int + def _remove(self, operation: Uninstall) -> int: package = operation.package # If we have a VCS package, remove its source directory @@ -492,7 +501,7 @@ def _remove(self, operation): # type: (Uninstall) -> int raise - def _prepare_file(self, operation): # type: (Union[Install, Update]) -> Path + def _prepare_file(self, operation: Union[Install, Update]) -> Path: package = operation.package message = ( @@ -510,7 +519,7 @@ def _prepare_file(self, operation): # type: (Union[Install, Update]) -> Path return archive - def _install_directory(self, operation): # type: (Union[Install, Update]) -> int + def _install_directory(self, operation: Union[Install, Update]) -> int: from poetry.factory import Factory package = operation.package @@ -576,7 +585,7 @@ def _install_directory(self, operation): # type: (Union[Install, Update]) -> in return self.run_pip(*args) - def _install_git(self, operation): # type: (Union[Install, Update]) -> int + def _install_git(self, operation: Union[Install, Update]) -> int: from poetry.core.vcs import Git package = operation.package @@ -604,14 +613,12 @@ def _install_git(self, operation): # type: (Union[Install, Update]) -> int return self._install_directory(operation) - def _download(self, operation): # type: (Union[Install, Update]) -> Link + def _download(self, operation: Union[Install, Update]) -> Link: link = self._chooser.choose_for(operation.package) return self._download_link(operation, link) - def _download_link( - self, operation, link - ): # type: (Union[Install, Update], Link) -> Link + def _download_link(self, operation: Union[Install, Update], link: Link) -> Link: package = operation.package archive = self._chef.get_cached_archive_for_link(link) @@ -643,9 +650,7 @@ def _download_link( return archive - def _download_archive( - self, operation, link - ): # type: (Union[Install, Update], Link) -> Path + def _download_archive(self, operation: Union[Install, Update], link: Link) -> Path: response = self._authenticator.request( "get", link.url, stream=True, io=self._sections.get(id(operation), self._io) ) @@ -694,7 +699,7 @@ def _download_archive( return archive - def _should_write_operation(self, operation): # type: (Operation) -> bool + def _should_write_operation(self, operation: Operation) -> bool: if not operation.skipped: return True diff --git a/poetry/installation/installer.py b/poetry/installation/installer.py index ecd0ba1ad78..b778711479b 100644 --- a/poetry/installation/installer.py +++ b/poetry/installation/installer.py @@ -26,22 +26,22 @@ if TYPE_CHECKING: - from poetry.utils.env import Env # noqa + from poetry.utils.env import Env - from .operations import OperationTypes # noqa + from .operations import OperationTypes class Installer: def __init__( self, - io, # type: IO - env, # type: "Env" - package, # type: ProjectPackage - locker, # type: Locker - pool, # type: Pool - config, # type: Config - installed=None, # type: Union[InstalledRepository, None] - executor=None, # type: Optional[Executor] + io: IO, + env: "Env", + package: ProjectPackage, + locker: Locker, + pool: Pool, + config: Config, + installed: Union[InstalledRepository, None] = None, + executor: Optional[Executor] = None, ): self._io = io self._env = env @@ -76,24 +76,24 @@ def __init__( self._installed_repository = installed @property - def executor(self): # type: () -> Executor + def executor(self) -> Executor: return self._executor @property - def installer(self): # type: () -> BaseInstaller + def installer(self) -> BaseInstaller: return self._installer - def set_package(self, package): # type: (ProjectPackage) -> Installer + def set_package(self, package: ProjectPackage) -> "Installer": self._package = package return self - def set_locker(self, locker): # type: (Locker) -> Installer + def set_locker(self, locker: Locker) -> "Installer": self._locker = locker return self - def run(self): # type: () -> int + def run(self) -> int: # Check if refresh if not self._update and self._lock and self._locker.is_locked(): return self._do_refresh() @@ -111,54 +111,54 @@ def run(self): # type: () -> int return self._do_install(local_repo) - def dry_run(self, dry_run=True): # type: (bool) -> Installer + def dry_run(self, dry_run: bool = True) -> "Installer": self._dry_run = dry_run self._executor.dry_run(dry_run) return self - def is_dry_run(self): # type: () -> bool + def is_dry_run(self) -> bool: return self._dry_run - def remove_untracked(self, remove_untracked=True): # type: (bool) -> Installer + def remove_untracked(self, remove_untracked: bool = True) -> "Installer": self._remove_untracked = remove_untracked return self - def is_remove_untracked(self): # type: () -> bool + def is_remove_untracked(self) -> bool: return self._remove_untracked - def verbose(self, verbose=True): # type: (bool) -> Installer + def verbose(self, verbose: bool = True) -> "Installer": self._verbose = verbose self._executor.verbose(verbose) return self - def is_verbose(self): # type: () -> bool + def is_verbose(self) -> bool: return self._verbose - def dev_mode(self, dev_mode=True): # type: (bool) -> Installer + def dev_mode(self, dev_mode: bool = True) -> "Installer": self._dev_mode = dev_mode return self - def is_dev_mode(self): # type: () -> bool + def is_dev_mode(self) -> bool: return self._dev_mode - def dev_only(self, dev_only=False): # type: (bool) -> Installer + def dev_only(self, dev_only: bool = False) -> "Installer": self._dev_only = dev_only return self - def is_dev_only(self): # type: () -> bool + def is_dev_only(self) -> bool: return self._dev_only - def update(self, update=True): # type: (bool) -> Installer + def update(self, update: bool = True) -> "Installer": self._update = update return self - def lock(self, update=True): # type: (bool) -> Installer + def lock(self, update: bool = True) -> "Installer": """ Prepare the installer for locking only. """ @@ -168,10 +168,10 @@ def lock(self, update=True): # type: (bool) -> Installer return self - def is_updating(self): # type: () -> bool + def is_updating(self) -> bool: return self._update - def execute_operations(self, execute=True): # type: (bool) -> Installer + def execute_operations(self, execute: bool = True) -> "Installer": self._execute_operations = execute if not execute: @@ -179,22 +179,22 @@ def execute_operations(self, execute=True): # type: (bool) -> Installer return self - def whitelist(self, packages): # type: (Iterable[str]) -> Installer + def whitelist(self, packages: Iterable[str]) -> "Installer": self._whitelist = [canonicalize_name(p) for p in packages] return self - def extras(self, extras): # type: (list) -> Installer + def extras(self, extras: list) -> "Installer": self._extras = extras return self - def use_executor(self, use_executor=True): # type: (bool) -> Installer + def use_executor(self, use_executor: bool = True) -> "Installer": self._use_executor = use_executor return self - def _do_refresh(self): # type: () -> int + def _do_refresh(self) -> int: from poetry.puzzle import Solver # Checking extras @@ -208,7 +208,7 @@ def _do_refresh(self): # type: () -> int self._pool, locked_repository, locked_repository, - self._io, # noqa + self._io, ) ops = solver.solve(use_latest=[]) @@ -220,7 +220,7 @@ def _do_refresh(self): # type: () -> int return 0 - def _do_install(self, local_repo): # type: (Repository) -> int + def _do_install(self, local_repo: Repository) -> int: from poetry.puzzle import Solver locked_repository = Repository() @@ -335,7 +335,7 @@ def _do_install(self, local_repo): # type: (Repository) -> int # Execute operations return self._execute(ops) - def _write_lock_file(self, repo, force=True): # type: (Repository, bool) -> None + def _write_lock_file(self, repo: Repository, force: bool = True) -> None: if force or (self._update and self._write_lock): updated_lock = self._locker.set_lock_data(self._package, repo.packages) @@ -343,7 +343,7 @@ def _write_lock_file(self, repo, force=True): # type: (Repository, bool) -> Non self._io.write_line("") self._io.write_line("Writing lock file") - def _execute(self, operations): # type: (List["OperationTypes"]) -> int + def _execute(self, operations: List["OperationTypes"]) -> int: if self._use_executor: return self._executor.execute(operations) @@ -391,7 +391,7 @@ def _execute(self, operations): # type: (List["OperationTypes"]) -> int return 0 - def _execute_operation(self, operation): # type: (Operation) -> None + def _execute_operation(self, operation: Operation) -> None: """ Execute a given operation. """ @@ -399,7 +399,7 @@ def _execute_operation(self, operation): # type: (Operation) -> None getattr(self, "_execute_{}".format(method))(operation) - def _execute_install(self, operation): # type: (Install) -> None + def _execute_install(self, operation: Install) -> None: if operation.skipped: if self.is_verbose() and (self._execute_operations or self.is_dry_run()): self._io.write_line( @@ -424,7 +424,7 @@ def _execute_install(self, operation): # type: (Install) -> None self._installer.install(operation.package) - def _execute_update(self, operation): # type: (Update) -> None + def _execute_update(self, operation: Update) -> None: source = operation.initial_package target = operation.target_package @@ -454,7 +454,7 @@ def _execute_update(self, operation): # type: (Update) -> None self._installer.update(source, target) - def _execute_uninstall(self, operation): # type: (Uninstall) -> None + def _execute_uninstall(self, operation: Uninstall) -> None: if operation.skipped: if self.is_verbose() and (self._execute_operations or self.is_dry_run()): self._io.write_line( @@ -480,8 +480,8 @@ def _execute_uninstall(self, operation): # type: (Uninstall) -> None self._installer.remove(operation.package) def _populate_local_repo( - self, local_repo, ops - ): # type: (Repository, List[Operation]) -> None + self, local_repo: Repository, ops: List[Operation] + ) -> None: for op in ops: if isinstance(op, Uninstall): continue @@ -494,8 +494,8 @@ def _populate_local_repo( local_repo.add_package(package) def _get_operations_from_lock( - self, locked_repository - ): # type: (Repository) -> List[Operation] + self, locked_repository: Repository + ) -> List[Operation]: installed_repo = self._installed_repository ops = [] @@ -526,9 +526,7 @@ def _get_operations_from_lock( return ops - def _filter_operations( - self, ops, repo - ): # type: (List[Operation], Repository) -> None + def _filter_operations(self, ops: List[Operation], repo: Repository) -> None: extra_packages = self._get_extra_packages(repo) for op in ops: if isinstance(op, Update): @@ -563,7 +561,7 @@ def _filter_operations( if package.category == "dev" and not self.is_dev_mode(): op.skip("Dev dependencies not requested") - def _get_extra_packages(self, repo): # type: (Repository) -> List[str] + def _get_extra_packages(self, repo: Repository) -> List[str]: """ Returns all package names required by extras. @@ -576,8 +574,8 @@ def _get_extra_packages(self, repo): # type: (Repository) -> List[str] return list(get_extra_package_names(repo.packages, extras, self._extras)) - def _get_installer(self): # type: () -> BaseInstaller + def _get_installer(self) -> BaseInstaller: return PipInstaller(self._env, self._io, self._pool) - def _get_installed(self): # type: () -> InstalledRepository + def _get_installed(self) -> InstalledRepository: return InstalledRepository.load(self._env) diff --git a/poetry/installation/noop_installer.py b/poetry/installation/noop_installer.py index 8b39543671f..3ef7dab543c 100644 --- a/poetry/installation/noop_installer.py +++ b/poetry/installation/noop_installer.py @@ -5,32 +5,32 @@ if TYPE_CHECKING: - from poetry.core.packages import Package # noqa + from poetry.core.packages import Package class NoopInstaller(BaseInstaller): - def __init__(self): # type: () -> None + def __init__(self) -> None: self._installs = [] self._updates = [] self._removals = [] @property - def installs(self): # type: () -> List["Package"] + def installs(self) -> List["Package"]: return self._installs @property - def updates(self): # type: () -> List["Package"] + def updates(self) -> List["Package"]: return self._updates @property - def removals(self): # type: () -> List["Package"] + def removals(self) -> List["Package"]: return self._removals - def install(self, package): # type: ("Package") -> None + def install(self, package: "Package") -> None: self._installs.append(package) - def update(self, source, target): # type: ("Package", "Package") -> None + def update(self, source: "Package", target: "Package") -> None: self._updates.append((source, target)) - def remove(self, package): # type: ("Package") -> None + def remove(self, package: "Package") -> None: self._removals.append(package) diff --git a/poetry/installation/operations/install.py b/poetry/installation/operations/install.py index 381f8f48f30..b83f449bd45 100644 --- a/poetry/installation/operations/install.py +++ b/poetry/installation/operations/install.py @@ -5,31 +5,31 @@ if TYPE_CHECKING: - from poetry.core.packages import Package # noqa + from poetry.core.packages import Package class Install(Operation): def __init__( - self, package, reason=None, priority=0 - ): # type: ("Package", Optional[str], int) -> None + self, package: "Package", reason: Optional[str] = None, priority: int = 0 + ) -> None: super(Install, self).__init__(reason, priority=priority) self._package = package @property - def package(self): # type: () -> "Package" + def package(self) -> "Package": return self._package @property - def job_type(self): # type: () -> str + def job_type(self) -> str: return "install" - def __str__(self): # type: () -> str + def __str__(self) -> str: return "Installing {} ({})".format( self.package.pretty_name, self.format_version(self.package) ) - def __repr__(self): # type: () -> str + def __repr__(self) -> str: return "".format( self.package.pretty_name, self.format_version(self.package) ) diff --git a/poetry/installation/operations/operation.py b/poetry/installation/operations/operation.py index 847510c80f8..e001cf97608 100644 --- a/poetry/installation/operations/operation.py +++ b/poetry/installation/operations/operation.py @@ -5,11 +5,11 @@ if TYPE_CHECKING: - from poetry.core.packages import Package # noqa + from poetry.core.packages import Package class Operation(object): - def __init__(self, reason=None, priority=0): # type: (Optional[str], int) -> None + def __init__(self, reason: Optional[str] = None, priority: int = 0) -> None: self._reason = reason self._skipped = False @@ -17,39 +17,39 @@ def __init__(self, reason=None, priority=0): # type: (Optional[str], int) -> No self._priority = priority @property - def job_type(self): # type: () -> str + def job_type(self) -> str: raise NotImplementedError @property - def reason(self): # type: () -> str + def reason(self) -> str: return self._reason @property - def skipped(self): # type: () -> bool + def skipped(self) -> bool: return self._skipped @property - def skip_reason(self): # type: () -> Optional[str] + def skip_reason(self) -> Optional[str]: return self._skip_reason @property - def priority(self): # type: () -> int + def priority(self) -> int: return self._priority @property - def package(self): # type: () -> "Package" + def package(self) -> "Package": raise NotImplementedError() - def format_version(self, package): # type: ("Package") -> str + def format_version(self, package: "Package") -> str: return package.full_pretty_version - def skip(self, reason): # type: (str) -> Operation + def skip(self, reason: str) -> "Operation": self._skipped = True self._skip_reason = reason return self - def unskip(self): # type: () -> Operation + def unskip(self) -> "Operation": self._skipped = False self._skip_reason = None diff --git a/poetry/installation/operations/uninstall.py b/poetry/installation/operations/uninstall.py index 32c6e4e3583..12a163a3367 100644 --- a/poetry/installation/operations/uninstall.py +++ b/poetry/installation/operations/uninstall.py @@ -5,31 +5,34 @@ if TYPE_CHECKING: - from poetry.core.packages import Package # noqa + from poetry.core.packages import Package class Uninstall(Operation): def __init__( - self, package, reason=None, priority=float("inf") - ): # type: ("Package", Optional[str], int) -> None + self, + package: "Package", + reason: Optional[str] = None, + priority: int = float("inf"), + ) -> None: super(Uninstall, self).__init__(reason, priority=priority) self._package = package @property - def package(self): # type: () -> "Package" + def package(self) -> "Package": return self._package @property - def job_type(self): # type: () -> str + def job_type(self) -> str: return "uninstall" - def __str__(self): # type: () -> str + def __str__(self) -> str: return "Uninstalling {} ({})".format( self.package.pretty_name, self.format_version(self._package) ) - def __repr__(self): # type: () -> str + def __repr__(self) -> str: return "".format( self.package.pretty_name, self.format_version(self.package) ) diff --git a/poetry/installation/operations/update.py b/poetry/installation/operations/update.py index 02cd86ccfb8..c1f33fad30f 100644 --- a/poetry/installation/operations/update.py +++ b/poetry/installation/operations/update.py @@ -5,35 +5,39 @@ if TYPE_CHECKING: - from poetry.core.packages import Package # noqa + from poetry.core.packages import Package class Update(Operation): def __init__( - self, initial, target, reason=None, priority=0 - ): # type: ("Package", "Package", Optional[str], int) -> None + self, + initial: "Package", + target: "Package", + reason: Optional[str] = None, + priority: int = 0, + ) -> None: self._initial_package = initial self._target_package = target super(Update, self).__init__(reason, priority=priority) @property - def initial_package(self): # type: () -> "Package" + def initial_package(self) -> "Package": return self._initial_package @property - def target_package(self): # type: () -> "Package" + def target_package(self) -> "Package": return self._target_package @property - def package(self): # type: () -> "Package" + def package(self) -> "Package": return self._target_package @property - def job_type(self): # type: () -> str + def job_type(self) -> str: return "update" - def __str__(self): # type: () -> str + def __str__(self) -> str: return "Updating {} ({}) to {} ({})".format( self.initial_package.pretty_name, self.format_version(self.initial_package), @@ -41,7 +45,7 @@ def __str__(self): # type: () -> str self.format_version(self.target_package), ) - def __repr__(self): # type: () -> str + def __repr__(self) -> str: return "".format( self.initial_package.pretty_name, self.format_version(self.initial_package), diff --git a/poetry/installation/pip_installer.py b/poetry/installation/pip_installer.py index 6e5f7bdcc48..e84774d39ca 100644 --- a/poetry/installation/pip_installer.py +++ b/poetry/installation/pip_installer.py @@ -19,16 +19,16 @@ if TYPE_CHECKING: - from poetry.core.packages import Package # noqa + from poetry.core.packages import Package class PipInstaller(BaseInstaller): - def __init__(self, env, io, pool): # type: (Env, IO, Pool) -> None + def __init__(self, env: Env, io: IO, pool: Pool) -> None: self._env = env self._io = io self._pool = pool - def install(self, package, update=False): # type: ("Package", bool) -> None + def install(self, package: "Package", update: bool = False) -> None: if package.source_type == "directory": self.install_directory(package) @@ -48,7 +48,7 @@ def install(self, package, update=False): # type: ("Package", bool) -> None repository = self._pool.repository(package.source_reference) parsed = urllib.parse.urlparse(package.source_url) if parsed.scheme == "http": - self._io.error( + self._io.write_error( " Installing from unsecure host: {}".format( parsed.hostname ) @@ -97,7 +97,7 @@ def install(self, package, update=False): # type: ("Package", bool) -> None self.run(*args) - def update(self, package, target): # type: ("Package", "Package") -> None + def update(self, package: "Package", target: "Package") -> None: if package.source_type != target.source_type: # If the source type has changed, we remove the current # package to avoid perpetual updates in some cases @@ -105,7 +105,7 @@ def update(self, package, target): # type: ("Package", "Package") -> None self.install(target, update=True) - def remove(self, package): # type: ("Package") -> None + def remove(self, package: "Package") -> None: try: self.run("uninstall", package.name, "-y") except CalledProcessError as e: @@ -127,10 +127,10 @@ def remove(self, package): # type: ("Package") -> None if src_dir.exists(): safe_rmtree(str(src_dir)) - def run(self, *args, **kwargs): # type: (*Any,**Any) -> str + def run(self, *args: Any, **kwargs: Any) -> str: return self._env.run_pip(*args, **kwargs) - def requirement(self, package, formatted=False): # type: ("Package", bool) -> str + def requirement(self, package: "Package", formatted: bool = False) -> str: if formatted and not package.source_type: req = "{}=={}".format(package.name, package.version) for f in package.files: @@ -171,7 +171,7 @@ def requirement(self, package, formatted=False): # type: ("Package", bool) -> s return "{}=={}".format(package.name, package.version) - def create_temporary_requirement(self, package): # type: ("Package") -> str + def create_temporary_requirement(self, package: "Package") -> str: fd, name = tempfile.mkstemp( "reqs.txt", "{}-{}".format(package.name, package.version) ) @@ -183,7 +183,7 @@ def create_temporary_requirement(self, package): # type: ("Package") -> str return name - def install_directory(self, package): # type: ("Package") -> Union[str, int] + def install_directory(self, package: "Package") -> Union[str, int]: from cleo.io.null_io import NullIO from poetry.factory import Factory @@ -241,7 +241,7 @@ def install_directory(self, package): # type: ("Package") -> Union[str, int] return self.run(*args) - def install_git(self, package): # type: ("Package") -> None + def install_git(self, package: "Package") -> None: from poetry.core.packages import Package from poetry.core.vcs import Git diff --git a/poetry/json/__init__.py b/poetry/json/__init__.py index d50eb7a7500..d65edd6106a 100644 --- a/poetry/json/__init__.py +++ b/poetry/json/__init__.py @@ -15,7 +15,7 @@ class ValidationError(ValueError): pass -def validate_object(obj, schema_name): # type: (dict, str) -> List[str] +def validate_object(obj: dict, schema_name: str) -> List[str]: schema = os.path.join(SCHEMA_DIR, "{}.json".format(schema_name)) if not os.path.exists(schema): diff --git a/poetry/layouts/__init__.py b/poetry/layouts/__init__.py index 9969ce5e3a7..291319e82c1 100644 --- a/poetry/layouts/__init__.py +++ b/poetry/layouts/__init__.py @@ -8,7 +8,7 @@ _LAYOUTS = {"src": SrcLayout, "standard": StandardLayout} -def layout(name): # type: (str) -> Type[Layout] +def layout(name: str) -> Type[Layout]: if name not in _LAYOUTS: raise ValueError("Invalid layout") diff --git a/poetry/layouts/layout.py b/poetry/layouts/layout.py index af5b48ad746..ed3b7b45777 100644 --- a/poetry/layouts/layout.py +++ b/poetry/layouts/layout.py @@ -10,8 +10,9 @@ if TYPE_CHECKING: - from poetry.core.pyproject.toml import PyProjectTOML # noqa - from poetry.utils._compat import Path # noqa + from pathlib import Path + + from poetry.core.pyproject.toml import PyProjectTOML TESTS_DEFAULT = u"""from {package_name} import __version__ @@ -47,21 +48,21 @@ def test_version(): """ BUILD_SYSTEM_MIN_VERSION = "1.0.0" -BUILD_SYSTEM_MAX_VERSION = None # type: Optional[str] +BUILD_SYSTEM_MAX_VERSION: Optional[str] = None class Layout(object): def __init__( self, - project, # type: str - version="0.1.0", # type: str - description="", # type: str - readme_format="md", # type: str - author=None, # type: Optional[str] - license=None, # type: Optional[str] - python="*", # type: str - dependencies=None, # type: Optional[Dict[str, str]] - dev_dependencies=None, # type: Optional[Dict[str, str]] + project: str, + version: str = "0.1.0", + description: str = "", + readme_format: str = "md", + author: Optional[str] = None, + license: Optional[str] = None, + python: str = "*", + dependencies: Optional[Dict[str, str]] = None, + dev_dependencies: Optional[Dict[str, str]] = None, ): self._project = project self._package_name = module_name(project) @@ -78,7 +79,7 @@ def __init__( self._author = author - def create(self, path, with_tests=True): # type: (Path, bool) -> None + def create(self, path: "Path", with_tests: bool = True) -> None: path.mkdir(parents=True, exist_ok=True) self._create_default(path) @@ -90,8 +91,8 @@ def create(self, path, with_tests=True): # type: (Path, bool) -> None self._write_poetry(path) def generate_poetry_content( - self, original=None - ): # type: (Optional["PyProjectTOML"]) -> str + self, original: Optional["PyProjectTOML"] = None + ) -> str: template = POETRY_DEFAULT if self._license: template = POETRY_WITH_LICENSE @@ -131,10 +132,10 @@ def generate_poetry_content( return content - def _create_default(self, path, src=True): # type: (Path, bool) -> None + def _create_default(self, path: "Path", src: bool = True) -> None: raise NotImplementedError() - def _create_readme(self, path): # type: (Path) -> None + def _create_readme(self, path: "Path") -> None: if self._readme_format == "rst": readme_file = path / "README.rst" else: @@ -142,7 +143,7 @@ def _create_readme(self, path): # type: (Path) -> None readme_file.touch() - def _create_tests(self, path): # type: (Path) -> None + def _create_tests(self, path: "Path") -> None: tests = path / "tests" tests_init = tests / "__init__.py" tests_default = tests / "test_{}.py".format(self._package_name) @@ -157,7 +158,7 @@ def _create_tests(self, path): # type: (Path) -> None ) ) - def _write_poetry(self, path): # type: ("Path") -> None + def _write_poetry(self, path: "Path") -> None: content = self.generate_poetry_content() poetry = path / "pyproject.toml" diff --git a/poetry/layouts/src.py b/poetry/layouts/src.py index a703ed2f2c2..3d844b4a4fc 100644 --- a/poetry/layouts/src.py +++ b/poetry/layouts/src.py @@ -6,14 +6,14 @@ if TYPE_CHECKING: - from poetry.utils._compat import Path # noqa + from pathlib import Path DEFAULT = u"""__version__ = '{version}' """ class SrcLayout(Layout): - def _create_default(self, path): # type: ("Path") -> None + def _create_default(self, path: "Path") -> None: package_path = path / "src" / self._package_name package_init = package_path / "__init__.py" diff --git a/poetry/layouts/standard.py b/poetry/layouts/standard.py index 4372d71d6b1..e0a1db99204 100644 --- a/poetry/layouts/standard.py +++ b/poetry/layouts/standard.py @@ -6,13 +6,13 @@ if TYPE_CHECKING: - from poetry.utils._compat import Path # noqa + from pathlib import Path DEFAULT = u"""__version__ = '{version}' """ class StandardLayout(Layout): - def _create_default(self, path): # type: ("Path") -> None + def _create_default(self, path: "Path") -> None: package_path = path / self._package_name package_init = package_path / "__init__.py" diff --git a/poetry/masonry/builders/editable.py b/poetry/masonry/builders/editable.py index 1756a0f0f15..07f59d0b311 100644 --- a/poetry/masonry/builders/editable.py +++ b/poetry/masonry/builders/editable.py @@ -21,8 +21,8 @@ if TYPE_CHECKING: from cleo.io.io import IO # noqa - from poetry.core.poetry import Poetry # noqa - from poetry.utils.env import Env # noqa + from poetry.core.poetry import Poetry + from poetry.utils.env import Env SCRIPT_TEMPLATE = """\ #!{python} @@ -38,13 +38,13 @@ class EditableBuilder(Builder): - def __init__(self, poetry, env, io): # type: ("Poetry", "Env", "IO") -> None + def __init__(self, poetry: "Poetry", env: "Env", io: "IO") -> None: super(EditableBuilder, self).__init__(poetry) self._env = env self._io = io - def build(self): # type: () -> None + def build(self) -> None: self._debug( " - Building package {} in editable mode".format( self._package.name @@ -66,11 +66,11 @@ def build(self): # type: () -> None added_files += self._add_scripts() self._add_dist_info(added_files) - def _run_build_script(self, build_script): # type: (Path) -> None + def _run_build_script(self, build_script: Path) -> None: self._debug(" - Executing build script: {}".format(build_script)) self._env.run("python", str(self._path.joinpath(build_script)), call=True) - def _setup_build(self): # type: () -> None + def _setup_build(self) -> None: builder = SdistBuilder(self._poetry) setup = self._path / "setup.py" has_setup = setup.exists() @@ -102,7 +102,7 @@ def _setup_build(self): # type: () -> None if not has_setup: os.remove(str(setup)) - def _add_pth(self): # type: () -> List[Path] + def _add_pth(self) -> List[Path]: paths = set() for include in self._module.includes: if isinstance(include, PackageInclude) and ( @@ -127,14 +127,14 @@ def _add_pth(self): # type: () -> List[Path] return [pth_file] except OSError: # TODO: Replace with PermissionError - self._io.error_line( + self._io.write_error_line( " - Failed to create {} for {}".format( pth_file.name, self._poetry.file.parent ) ) return [] - def _add_scripts(self): # type: () -> List[Path] + def _add_scripts(self) -> List[Path]: added = [] entry_points = self.convert_entry_points() @@ -142,7 +142,7 @@ def _add_scripts(self): # type: () -> List[Path] if is_dir_writable(path=scripts_path, create=True): break else: - self._io.error_line( + self._io.write_error_line( " - Failed to find a suitable script installation directory for {}".format( self._poetry.file.parent ) @@ -193,7 +193,7 @@ def _add_scripts(self): # type: () -> List[Path] return added - def _add_dist_info(self, added_files): # type: (List[Path]) -> None + def _add_dist_info(self, added_files: List[Path]) -> None: from poetry.core.masonry.builders.wheel import WheelBuilder added_files = added_files[:] @@ -247,7 +247,7 @@ def _add_dist_info(self, added_files): # type: (List[Path]) -> None # RECORD itself is recorded with no hash or size f.write("{},,\n".format(dist_info.joinpath("RECORD"))) - def _get_file_hash(self, filepath): # type: (Path) -> str + def _get_file_hash(self, filepath: Path) -> str: hashsum = hashlib.sha256() with filepath.open("rb") as src: while True: @@ -260,6 +260,6 @@ def _get_file_hash(self, filepath): # type: (Path) -> str return urlsafe_b64encode(hashsum.digest()).decode("ascii").rstrip("=") - def _debug(self, msg): # type: (str) -> None + def _debug(self, msg: str) -> None: if self._io.is_debug(): self._io.write_line(msg) diff --git a/poetry/mixology/__init__.py b/poetry/mixology/__init__.py index 8dd76488438..09b3708b6c5 100644 --- a/poetry/mixology/__init__.py +++ b/poetry/mixology/__init__.py @@ -6,16 +6,19 @@ if TYPE_CHECKING: - from poetry.core.packages import DependencyPackage # noqa - from poetry.core.packages import ProjectPackage # noqa - from poetry.puzzle.provider import Provider # noqa + from poetry.core.packages import ProjectPackage + from poetry.packages import DependencyPackage + from poetry.puzzle.provider import Provider - from .result import SolverResult # noqa + from .result import SolverResult def resolve_version( - root, provider, locked=None, use_latest=None -): # type: ("ProjectPackage", "Provider", Dict[str, "DependencyPackage"],List[str]) -> "SolverResult" + root: "ProjectPackage", + provider: "Provider", + locked: Dict[str, "DependencyPackage"] = None, + use_latest: List[str] = None, +) -> "SolverResult": solver = VersionSolver(root, provider, locked=locked, use_latest=use_latest) return solver.solve() diff --git a/poetry/mixology/assignment.py b/poetry/mixology/assignment.py index 21765a22fe5..c90cd837271 100644 --- a/poetry/mixology/assignment.py +++ b/poetry/mixology/assignment.py @@ -6,10 +6,10 @@ if TYPE_CHECKING: - from poetry.core.packages import Dependency # noqa - from poetry.core.packages import Package # noqa + from poetry.core.packages import Dependency + from poetry.core.packages import Package - from .incompatibility import Incompatibility # noqa + from .incompatibility import Incompatibility class Assignment(Term): @@ -18,8 +18,13 @@ class Assignment(Term): """ def __init__( - self, dependency, is_positive, decision_level, index, cause=None - ): # type: ("Dependency", bool, int, int, Optional["Incompatibility"]) -> None + self, + dependency: "Dependency", + is_positive: bool, + decision_level: int, + index: int, + cause: Optional["Incompatibility"] = None, + ) -> None: super(Assignment, self).__init__(dependency, is_positive) self._decision_level = decision_level @@ -27,28 +32,33 @@ def __init__( self._cause = cause @property - def decision_level(self): # type: () -> int + def decision_level(self) -> int: return self._decision_level @property - def index(self): # type: () -> int + def index(self) -> int: return self._index @property - def cause(self): # type: () -> "Incompatibility" + def cause(self) -> "Incompatibility": return self._cause @classmethod def decision( - cls, package, decision_level, index - ): # type: ("Package", int, int) -> Assignment + cls, package: "Package", decision_level: int, index: int + ) -> "Assignment": return cls(package.to_dependency(), True, decision_level, index) @classmethod def derivation( - cls, dependency, is_positive, cause, decision_level, index - ): # type: (Any, bool, "Incompatibility", int, int) -> "Assignment" + cls, + dependency: Any, + is_positive: bool, + cause: "Incompatibility", + decision_level: int, + index: int, + ) -> "Assignment": return cls(dependency, is_positive, decision_level, index, cause) - def is_decision(self): # type: () -> bool + def is_decision(self) -> bool: return self._cause is None diff --git a/poetry/mixology/failure.py b/poetry/mixology/failure.py index daffd12b94b..a4707c93342 100644 --- a/poetry/mixology/failure.py +++ b/poetry/mixology/failure.py @@ -11,27 +11,27 @@ class SolveFailure(Exception): - def __init__(self, incompatibility): # type: (Incompatibility) -> None + def __init__(self, incompatibility: Incompatibility) -> None: self._incompatibility = incompatibility @property - def message(self): # type: () -> str + def message(self) -> str: return str(self) - def __str__(self): # type: () -> str + def __str__(self) -> str: return _Writer(self._incompatibility).write() class _Writer: - def __init__(self, root): # type: (Incompatibility) -> None + def __init__(self, root: Incompatibility) -> None: self._root = root - self._derivations = {} # type: Dict[Incompatibility, int] - self._lines = [] # type: List[Tuple[str, Optional[int]]] - self._line_numbers = {} # type: Dict[Incompatibility, int] + self._derivations: Dict[Incompatibility, int] = {} + self._lines: List[Tuple[str, Optional[int]]] = [] + self._line_numbers: Dict[Incompatibility, int] = {} self._count_derivations(self._root) - def write(self): # type: () -> str + def write(self) -> str: buffer = [] required_python_version_notification = False @@ -98,8 +98,8 @@ def write(self): # type: () -> str return "\n".join(buffer) def _write( - self, incompatibility, message, numbered=False - ): # type: (Incompatibility, str, bool) -> None + self, incompatibility: Incompatibility, message: str, numbered: bool = False + ) -> None: if numbered: number = len(self._line_numbers) + 1 self._line_numbers[incompatibility] = number @@ -108,8 +108,11 @@ def _write( self._lines.append((message, None)) def _visit( - self, incompatibility, details_for_incompatibility, conclusion=False - ): # type: (Incompatibility, Dict, bool) -> None + self, + incompatibility: Incompatibility, + details_for_incompatibility: Dict, + conclusion: bool = False, + ) -> None: numbered = conclusion or self._derivations[incompatibility] > 1 conjunction = "So," if conclusion or incompatibility == self._root else "And" incompatibility_string = str(incompatibility) @@ -208,7 +211,7 @@ def _visit( numbered=numbered, ) elif self._is_collapsible(derived): - derived_cause = derived.cause # type: ConflictCause + derived_cause: ConflictCause = derived.cause if isinstance(derived_cause.conflict.cause, ConflictCause): collapsed_derived = derived_cause.conflict else: @@ -252,11 +255,11 @@ def _visit( numbered=numbered, ) - def _is_collapsible(self, incompatibility): # type: (Incompatibility) -> bool + def _is_collapsible(self, incompatibility: Incompatibility) -> bool: if self._derivations[incompatibility] > 1: return False - cause = incompatibility.cause # type: ConflictCause + cause: ConflictCause = incompatibility.cause if isinstance(cause.conflict.cause, ConflictCause) and isinstance( cause.other.cause, ConflictCause ): @@ -275,12 +278,12 @@ def _is_collapsible(self, incompatibility): # type: (Incompatibility) -> bool return complex not in self._line_numbers - def _is_single_line(self, cause): # type: (ConflictCause) -> bool + def _is_single_line(self, cause: ConflictCause) -> bool: return not isinstance(cause.conflict.cause, ConflictCause) and not isinstance( cause.other.cause, ConflictCause ) - def _count_derivations(self, incompatibility): # type: (Incompatibility) -> None + def _count_derivations(self, incompatibility: Incompatibility) -> None: if incompatibility in self._derivations: self._derivations[incompatibility] += 1 else: diff --git a/poetry/mixology/incompatibility.py b/poetry/mixology/incompatibility.py index 1e9a20440f3..615c0cec741 100644 --- a/poetry/mixology/incompatibility.py +++ b/poetry/mixology/incompatibility.py @@ -1,5 +1,5 @@ from typing import Dict -from typing import Generator +from typing import Iterator from typing import List from typing import Optional from typing import Union @@ -16,9 +16,7 @@ class Incompatibility: - def __init__( - self, terms, cause - ): # type: (List[Term], IncompatibilityCause) -> None + def __init__(self, terms: List[Term], cause: IncompatibilityCause) -> None: # Remove the root package from generated incompatibilities, since it will # always be satisfied. This makes error reporting clearer, and may also # make solving more efficient. @@ -43,7 +41,7 @@ def __init__( pass else: # Coalesce multiple terms about the same package if possible. - by_name = {} # type: Dict[str, Dict[str, Term]] + by_name: Dict[str, Dict[str, Term]] = {} for term in terms: if term.dependency.complete_name not in by_name: by_name[term.dependency.complete_name] = {} @@ -80,25 +78,33 @@ def __init__( self._cause = cause @property - def terms(self): # type: () -> List[Term] + def terms(self) -> List[Term]: return self._terms @property def cause( self, - ): # type: () -> Union[RootCause, NoVersionsCause, DependencyCause, ConflictCause, PythonCause, PlatformCause, PackageNotFoundCause] + ) -> Union[ + RootCause, + NoVersionsCause, + DependencyCause, + ConflictCause, + PythonCause, + PlatformCause, + PackageNotFoundCause, + ]: return self._cause @property def external_incompatibilities( self, - ): # type: () -> Generator[Union[ConflictCause, Incompatibility]] + ) -> Iterator[Union[ConflictCause, "Incompatibility"]]: """ Returns all external incompatibilities in this incompatibility's derivation graph. """ if isinstance(self._cause, ConflictCause): - cause = self._cause # type: ConflictCause + cause: ConflictCause = self._cause for incompatibility in cause.conflict.external_incompatibilities: yield incompatibility @@ -107,12 +113,12 @@ def external_incompatibilities( else: yield self - def is_failure(self): # type: () -> bool + def is_failure(self) -> bool: return len(self._terms) == 0 or ( len(self._terms) == 1 and self._terms[0].dependency.is_root ) - def __str__(self): # type: () -> str + def __str__(self) -> str: if isinstance(self._cause, DependencyCause): assert len(self._terms) == 2 @@ -128,7 +134,7 @@ def __str__(self): # type: () -> str assert len(self._terms) == 1 assert self._terms[0].is_positive() - cause = self._cause # type: PythonCause + cause: PythonCause = self._cause text = "{} requires ".format(self._terse(self._terms[0], allow_every=True)) text += "Python {}".format(cause.python_version) @@ -137,7 +143,7 @@ def __str__(self): # type: () -> str assert len(self._terms) == 1 assert self._terms[0].is_positive() - cause = self._cause # type: PlatformCause + cause: PlatformCause = self._cause text = "{} requires ".format(self._terse(self._terms[0], allow_every=True)) text += "platform {}".format(cause.platform) @@ -227,8 +233,12 @@ def __str__(self): # type: () -> str return "one of {} must be true".format(" or ".join(negative)) def and_to_string( - self, other, details, this_line, other_line - ): # type: (Incompatibility, dict, Optional[int], Optional[int]) -> str + self, + other: "Incompatibility", + details: dict, + this_line: Optional[int], + other_line: Optional[int], + ) -> str: requires_both = self._try_requires_both(other, details, this_line, other_line) if requires_both is not None: return requires_both @@ -257,8 +267,12 @@ def and_to_string( return "\n".join(buffer) def _try_requires_both( - self, other, details, this_line, other_line - ): # type: (Incompatibility, dict, Optional[int], Optional[int]) -> Optional[str] + self, + other: "Incompatibility", + details: dict, + this_line: Optional[int], + other_line: Optional[int], + ) -> Optional[str]: if len(self._terms) == 1 or len(other.terms) == 1: return @@ -303,8 +317,8 @@ def _try_requires_both( return "".join(buffer) def _try_requires_through( - self, other, details, this_line, other_line - ): # type: (Incompatibility, dict, int, int) -> Optional[str] + self, other: "Incompatibility", details: dict, this_line: int, other_line: int + ) -> Optional[str]: if len(self._terms) == 1 or len(other.terms) == 1: return @@ -381,8 +395,8 @@ def _try_requires_through( return "".join(buffer) def _try_requires_forbidden( - self, other, details, this_line, other_line - ): # type: (Incompatibility, dict, int, int) -> Optional[str] + self, other: "Incompatibility", details: dict, this_line: int, other_line: int + ) -> Optional[str]: if len(self._terms) != 1 and len(other.terms) != 1: return None @@ -422,7 +436,7 @@ def _try_requires_forbidden( buffer.append("({}) ".format(prior_line)) if isinstance(latter.cause, PythonCause): - cause = latter.cause # type: PythonCause + cause: PythonCause = latter.cause buffer.append("which requires Python {}".format(cause.python_version)) elif isinstance(latter.cause, NoVersionsCause): buffer.append("which doesn't match any versions") @@ -436,13 +450,13 @@ def _try_requires_forbidden( return "".join(buffer) - def _terse(self, term, allow_every=False): # type: (Term, bool) -> str + def _terse(self, term: Term, allow_every: bool = False) -> str: if allow_every and term.constraint.is_any(): return "every version of {}".format(term.dependency.complete_name) return str(term.dependency) - def _single_term_where(self, callable): # type: (callable) -> Optional[Term] + def _single_term_where(self, callable: callable) -> Optional[Term]: found = None for term in self._terms: if not callable(term): @@ -455,5 +469,5 @@ def _single_term_where(self, callable): # type: (callable) -> Optional[Term] return found - def __repr__(self): # type: () -> str + def __repr__(self) -> str: return "".format(str(self)) diff --git a/poetry/mixology/incompatibility_cause.py b/poetry/mixology/incompatibility_cause.py index 227b9dd0e81..3267be56d7a 100644 --- a/poetry/mixology/incompatibility_cause.py +++ b/poetry/mixology/incompatibility_cause.py @@ -2,7 +2,7 @@ if TYPE_CHECKING: - from poetry.mixology.incompatibility import Incompatibility # noqa + from poetry.mixology.incompatibility import Incompatibility class IncompatibilityCause(Exception): @@ -32,21 +32,19 @@ class ConflictCause(IncompatibilityCause): during conflict resolution. """ - def __init__( - self, conflict, other - ): # type: ("Incompatibility", "Incompatibility") -> None + def __init__(self, conflict: "Incompatibility", other: "Incompatibility") -> None: self._conflict = conflict self._other = other @property - def conflict(self): # type: () -> "Incompatibility" + def conflict(self) -> "Incompatibility": return self._conflict @property - def other(self): # type: () -> "Incompatibility" + def other(self) -> "Incompatibility": return self._other - def __str__(self): # type: () -> str + def __str__(self) -> str: return str(self._conflict) @@ -57,16 +55,16 @@ class PythonCause(IncompatibilityCause): with the current python version. """ - def __init__(self, python_version, root_python_version): # type: (str, str) -> None + def __init__(self, python_version: str, root_python_version: str) -> None: self._python_version = python_version self._root_python_version = root_python_version @property - def python_version(self): # type: () -> str + def python_version(self) -> str: return self._python_version @property - def root_python_version(self): # type: () -> str + def root_python_version(self) -> str: return self._root_python_version @@ -76,11 +74,11 @@ class PlatformCause(IncompatibilityCause): (OS most likely) being incompatible with the current platform. """ - def __init__(self, platform): # type: (str) -> None + def __init__(self, platform: str) -> None: self._platform = platform @property - def platform(self): # type: () -> str + def platform(self) -> str: return self._platform @@ -90,9 +88,9 @@ class PackageNotFoundCause(IncompatibilityCause): source. """ - def __init__(self, error): # type: (Exception) -> None + def __init__(self, error: Exception) -> None: self._error = error @property - def error(self): # type: () -> Exception + def error(self) -> Exception: return self._error diff --git a/poetry/mixology/partial_solution.py b/poetry/mixology/partial_solution.py index 93a6f73a2fb..b9f36ec13fd 100644 --- a/poetry/mixology/partial_solution.py +++ b/poetry/mixology/partial_solution.py @@ -9,9 +9,8 @@ if TYPE_CHECKING: - from poetry.core.packages import Dependency # noqa - from poetry.core.packages import Package # noqa - from poetry.packages import DependencyPackage # noqa + from poetry.core.packages import Dependency + from poetry.core.packages import Package class PartialSolution: @@ -23,19 +22,19 @@ class PartialSolution: # See https://github.com/dart-lang/mixology/tree/master/doc/solver.md#partial-solution. """ - def __init__(self): # type: () -> None + def __init__(self) -> None: # The assignments that have been made so far, in the order they were # assigned. - self._assignments = [] # type: List[Assignment] + self._assignments: List[Assignment] = [] # The decisions made for each package. - self._decisions = dict() # type: Dict[str, "Package"] + self._decisions: Dict[str, "Package"] = dict() # The intersection of all positive Assignments for each package, minus any # negative Assignments that refer to that package. # # This is derived from self._assignments. - self._positive = dict() # type: Dict[str, Term] + self._positive: Dict[str, Term] = dict() # The union of all negative Assignments for each package. # @@ -43,7 +42,7 @@ def __init__(self): # type: () -> None # map. # # This is derived from self._assignments. - self._negative = dict() # type: Dict[str, Dict[str, Term]] + self._negative: Dict[str, Dict[str, Term]] = dict() # The number of distinct solutions that have been attempted so far. self._attempted_solutions = 1 @@ -52,26 +51,26 @@ def __init__(self): # type: () -> None self._backtracking = False @property - def decisions(self): # type: () -> List["Package"] + def decisions(self) -> List["Package"]: return list(self._decisions.values()) @property - def decision_level(self): # type: () -> int + def decision_level(self) -> int: return len(self._decisions) @property - def attempted_solutions(self): # type: () -> int + def attempted_solutions(self) -> int: return self._attempted_solutions @property - def unsatisfied(self): # type: () -> List["Dependency"] + def unsatisfied(self) -> List["Dependency"]: return [ term.dependency for term in self._positive.values() if term.dependency.complete_name not in self._decisions ] - def decide(self, package): # type: ("Package") -> None + def decide(self, package: "Package") -> None: """ Adds an assignment of package as a decision and increments the decision level. @@ -91,8 +90,8 @@ def decide(self, package): # type: ("Package") -> None ) def derive( - self, dependency, is_positive, cause - ): # type: ("Dependency", bool, Incompatibility) -> None + self, dependency: "Dependency", is_positive: bool, cause: Incompatibility + ) -> None: """ Adds an assignment of package as a derivation. """ @@ -106,14 +105,14 @@ def derive( ) ) - def _assign(self, assignment): # type: (Assignment) -> None + def _assign(self, assignment: Assignment) -> None: """ Adds an Assignment to _assignments and _positive or _negative. """ self._assignments.append(assignment) self._register(assignment) - def backtrack(self, decision_level): # type: (int) -> None + def backtrack(self, decision_level: int) -> None: """ Resets the current decision level to decision_level, and removes all assignments made after that level. @@ -139,7 +138,7 @@ def backtrack(self, decision_level): # type: (int) -> None if assignment.dependency.complete_name in packages: self._register(assignment) - def _register(self, assignment): # type: (Assignment) -> None + def _register(self, assignment: Assignment) -> None: """ Registers an Assignment in _positive or _negative. """ @@ -169,7 +168,7 @@ def _register(self, assignment): # type: (Assignment) -> None self._negative[name][ref] = term - def satisfier(self, term): # type: (Term) -> Assignment + def satisfier(self, term: Term) -> Assignment: """ Returns the first Assignment in this solution such that the sublist of assignments up to and including that entry collectively satisfies term. @@ -202,10 +201,10 @@ def satisfier(self, term): # type: (Term) -> Assignment raise RuntimeError("[BUG] {} is not satisfied.".format(term)) - def satisfies(self, term): # type: (Term) -> bool + def satisfies(self, term: Term) -> bool: return self.relation(term) == SetRelation.SUBSET - def relation(self, term): # type: (Term) -> int + def relation(self, term: Term) -> int: positive = self._positive.get(term.dependency.complete_name) if positive is not None: return positive.relation(term) diff --git a/poetry/mixology/result.py b/poetry/mixology/result.py index 62a5029e5e3..6f83b01825f 100644 --- a/poetry/mixology/result.py +++ b/poetry/mixology/result.py @@ -3,22 +3,25 @@ if TYPE_CHECKING: - from poetry.core.packages import Package # noqa - from poetry.core.packages import ProjectPackage # noqa + from poetry.core.packages import Package + from poetry.core.packages import ProjectPackage class SolverResult: def __init__( - self, root, packages, attempted_solutions - ): # type: ("ProjectPackage", List["Package"], int) -> None + self, + root: "ProjectPackage", + packages: List["Package"], + attempted_solutions: int, + ) -> None: self._root = root self._packages = packages self._attempted_solutions = attempted_solutions @property - def packages(self): # type: () -> List["Package"] + def packages(self) -> List["Package"]: return self._packages @property - def attempted_solutions(self): # type: () -> int + def attempted_solutions(self) -> int: return self._attempted_solutions diff --git a/poetry/mixology/solutions/providers/python_requirement_solution_provider.py b/poetry/mixology/solutions/providers/python_requirement_solution_provider.py index 4c903677fd0..ac061e20f0f 100644 --- a/poetry/mixology/solutions/providers/python_requirement_solution_provider.py +++ b/poetry/mixology/solutions/providers/python_requirement_solution_provider.py @@ -7,7 +7,7 @@ class PythonRequirementSolutionProvider(HasSolutionsForException): - def can_solve(self, exception): # type: (Exception) -> bool + def can_solve(self, exception: Exception) -> bool: from poetry.puzzle.exceptions import SolverProblemError if not isinstance(exception, SolverProblemError): @@ -24,7 +24,7 @@ def can_solve(self, exception): # type: (Exception) -> bool return True - def get_solutions(self, exception): # type: (Exception) -> List[Solution] + def get_solutions(self, exception: Exception) -> List[Solution]: from ..solutions.python_requirement_solution import PythonRequirementSolution return [PythonRequirementSolution(exception)] diff --git a/poetry/mixology/solutions/solutions/python_requirement_solution.py b/poetry/mixology/solutions/solutions/python_requirement_solution.py index b24f7f09915..764c8f7fa68 100644 --- a/poetry/mixology/solutions/solutions/python_requirement_solution.py +++ b/poetry/mixology/solutions/solutions/python_requirement_solution.py @@ -5,11 +5,11 @@ if TYPE_CHECKING: - from poetry.mixology.incompatibility_cause import PackageNotFoundCause # noqa + from poetry.mixology.incompatibility_cause import PackageNotFoundCause class PythonRequirementSolution(Solution): - def __init__(self, exception): # type: ("PackageNotFoundCause") -> None + def __init__(self, exception: "PackageNotFoundCause") -> None: from poetry.core.semver import parse_constraint from poetry.mixology.incompatibility_cause import PythonCause @@ -44,15 +44,15 @@ def __init__(self, exception): # type: ("PackageNotFoundCause") -> None self._description = description @property - def solution_title(self): # type: () -> str + def solution_title(self) -> str: return self._title @property - def solution_description(self): # type: () -> str + def solution_description(self) -> str: return self._description @property - def documentation_links(self): # type: () -> List[str] + def documentation_links(self) -> List[str]: return [ "https://python-poetry.org/docs/dependency-specification/#python-restricted-dependencies", "https://python-poetry.org/docs/dependency-specification/#using-environment-markers", diff --git a/poetry/mixology/term.py b/poetry/mixology/term.py index b232e268af0..751895e37c8 100644 --- a/poetry/mixology/term.py +++ b/poetry/mixology/term.py @@ -8,7 +8,7 @@ if TYPE_CHECKING: - from poetry.core.semver import VersionTypes # noqa + from poetry.core.semver import VersionTypes class Term(object): @@ -19,26 +19,26 @@ class Term(object): See https://github.com/dart-lang/pub/tree/master/doc/solver.md#term. """ - def __init__(self, dependency, is_positive): # type: (Dependency, bool) -> None + def __init__(self, dependency: Dependency, is_positive: bool) -> None: self._dependency = dependency self._positive = is_positive @property - def inverse(self): # type: () -> Term + def inverse(self) -> "Term": return Term(self._dependency, not self.is_positive()) @property - def dependency(self): # type: () -> Dependency + def dependency(self) -> Dependency: return self._dependency @property - def constraint(self): # type: () -> "VersionTypes" + def constraint(self) -> "VersionTypes": return self._dependency.constraint - def is_positive(self): # type: () -> bool + def is_positive(self) -> bool: return self._positive - def satisfies(self, other): # type: (Term) -> bool + def satisfies(self, other: "Term") -> bool: """ Returns whether this term satisfies another. """ @@ -47,7 +47,7 @@ def satisfies(self, other): # type: (Term) -> bool and self.relation(other) == SetRelation.SUBSET ) - def relation(self, other): # type: (Term) -> int + def relation(self, other: "Term") -> int: """ Returns the relationship between the package versions allowed by this term and another. @@ -111,7 +111,7 @@ def relation(self, other): # type: (Term) -> int # not foo ^1.5.0 is a superset of not foo ^1.0.0 return SetRelation.OVERLAPPING - def intersect(self, other): # type: (Term) -> Optional[Term] + def intersect(self, other: "Term") -> Optional["Term"]: """ Returns a Term that represents the packages allowed by both this term and another @@ -145,14 +145,14 @@ def intersect(self, other): # type: (Term) -> Optional[Term] else: return - def difference(self, other): # type: (Term) -> Term + def difference(self, other: "Term") -> "Term": """ Returns a Term that represents packages allowed by this term and not by the other """ return self.intersect(other.inverse) - def _compatible_dependency(self, other): # type: (Term) -> bool + def _compatible_dependency(self, other: "Dependency") -> bool: return ( self.dependency.is_root or other.is_root @@ -160,15 +160,15 @@ def _compatible_dependency(self, other): # type: (Term) -> bool ) def _non_empty_term( - self, constraint, is_positive - ): # type: ("VersionTypes", bool) -> Optional[Term] + self, constraint: "VersionTypes", is_positive: bool + ) -> Optional["Term"]: if constraint.is_empty(): return return Term(self.dependency.with_constraint(constraint), is_positive) - def __str__(self): # type: () -> str + def __str__(self) -> str: return "{}{}".format("not " if not self.is_positive() else "", self._dependency) - def __repr__(self): # type: () -> str + def __repr__(self) -> str: return "".format(str(self)) diff --git a/poetry/mixology/version_solver.py b/poetry/mixology/version_solver.py index 1d1d19d2d55..f2aab10f88a 100644 --- a/poetry/mixology/version_solver.py +++ b/poetry/mixology/version_solver.py @@ -5,6 +5,7 @@ from typing import Dict from typing import List from typing import Optional +from typing import Union from poetry.core.packages import Dependency from poetry.core.packages import Package @@ -40,10 +41,10 @@ class VersionSolver: def __init__( self, - root, # type: ProjectPackage - provider, # type: Provider - locked=None, # type: Dict[str, Package] - use_latest=None, # type: List[str] + root: ProjectPackage, + provider: "Provider", + locked: Dict[str, Package] = None, + use_latest: List[str] = None, ): self._root = root self._provider = provider @@ -54,14 +55,14 @@ def __init__( self._use_latest = use_latest - self._incompatibilities = {} # type: Dict[str, List[Incompatibility]] + self._incompatibilities: Dict[str, List[Incompatibility]] = {} self._solution = PartialSolution() @property - def solution(self): # type: () -> PartialSolution + def solution(self) -> PartialSolution: return self._solution - def solve(self): # type: () -> SolverResult + def solve(self) -> SolverResult: """ Finds a set of dependencies that match the root package's constraints, or raises an error if no such set is available. @@ -91,7 +92,7 @@ def solve(self): # type: () -> SolverResult ) ) - def _propagate(self, package): # type: (str) -> None + def _propagate(self, package: str) -> None: """ Performs unit propagation on incompatibilities transitively related to package to derive new assignments for _solution. @@ -129,8 +130,8 @@ def _propagate(self, package): # type: (str) -> None changed.add(result) def _propagate_incompatibility( - self, incompatibility - ): # type: (Incompatibility) -> Optional[str, _conflict] + self, incompatibility: Incompatibility + ) -> Optional[Union[str, object]]: """ If incompatibility is almost satisfied by _solution, adds the negation of the unsatisfied term to _solution. @@ -182,9 +183,7 @@ def _propagate_incompatibility( return unsatisfied.dependency.complete_name - def _resolve_conflict( - self, incompatibility - ): # type: (Incompatibility) -> Incompatibility + def _resolve_conflict(self, incompatibility: Incompatibility) -> Incompatibility: """ Given an incompatibility that's satisfied by _solution, The `conflict resolution`_ constructs a new incompatibility that encapsulates the root @@ -317,7 +316,7 @@ def _resolve_conflict( raise SolveFailure(incompatibility) - def _choose_package_version(self): # type: () -> Optional[str] + def _choose_package_version(self) -> Optional[str]: """ Tries to select a version of a required package. @@ -331,7 +330,7 @@ def _choose_package_version(self): # type: () -> Optional[str] # Prefer packages with as few remaining versions as possible, # so that if a conflict is necessary it's forced quickly. - def _get_min(dependency): # type: (Dependency) -> int + def _get_min(dependency: Dependency) -> int: if dependency.name in self._use_latest: # If we're forced to use the latest version of a package, it effectively # only has one version to choose from. @@ -418,7 +417,7 @@ def _get_min(dependency): # type: (Dependency) -> int return dependency.complete_name - def _result(self): # type: () -> SolverResult + def _result(self) -> SolverResult: """ Creates a #SolverResult from the decisions in _solution """ @@ -430,7 +429,7 @@ def _result(self): # type: () -> SolverResult self._solution.attempted_solutions, ) - def _add_incompatibility(self, incompatibility): # type: (Incompatibility) -> None + def _add_incompatibility(self, incompatibility: Incompatibility) -> None: self._log("fact: {}".format(incompatibility)) for term in incompatibility.terms: @@ -447,7 +446,7 @@ def _add_incompatibility(self, incompatibility): # type: (Incompatibility) -> N incompatibility ) - def _get_locked(self, dependency): # type: (Dependency) -> Optional[Package] + def _get_locked(self, dependency: Dependency) -> Optional[Package]: if dependency.name in self._use_latest: return @@ -460,5 +459,5 @@ def _get_locked(self, dependency): # type: (Dependency) -> Optional[Package] return locked - def _log(self, text): # type: (str) -> None + def _log(self, text: str) -> None: self._provider.debug(text, self._solution.attempted_solutions) diff --git a/poetry/packages/dependency_package.py b/poetry/packages/dependency_package.py index e375118a5b8..cc50e8d94dc 100644 --- a/poetry/packages/dependency_package.py +++ b/poetry/packages/dependency_package.py @@ -7,46 +7,46 @@ class DependencyPackage(object): - def __init__(self, dependency, package): # type: (Dependency, Package) -> None + def __init__(self, dependency: Dependency, package: Package) -> None: self._dependency = dependency self._package = package @property - def dependency(self): # type: () -> Dependency + def dependency(self) -> Dependency: return self._dependency @property - def package(self): # type: () -> Package + def package(self) -> Package: return self._package - def clone(self): # type: () -> "DependencyPackage" + def clone(self) -> "DependencyPackage": return self.__class__(self._dependency, self._package.clone()) - def with_features(self, features): # type: (List[str]) -> "DependencyPackage" + def with_features(self, features: List[str]) -> "DependencyPackage": return self.__class__(self._dependency, self._package.with_features(features)) - def without_features(self): # type: () -> "DependencyPackage" + def without_features(self) -> "DependencyPackage": return self.with_features([]) - def __getattr__(self, name): # type: (str) -> Any + def __getattr__(self, name: str) -> Any: return getattr(self._package, name) - def __setattr__(self, key, value): # type: (str, Any) -> None + def __setattr__(self, key: str, value: Any) -> None: if key in {"_dependency", "_package"}: return super(DependencyPackage, self).__setattr__(key, value) setattr(self._package, key, value) - def __str__(self): # type: () -> str + def __str__(self) -> str: return str(self._package) - def __repr__(self): # type: () -> str + def __repr__(self) -> str: return repr(self._package) - def __hash__(self): # type: () -> int + def __hash__(self) -> int: return hash(self._package) - def __eq__(self, other): # type: (Union[Package, "DependencyPackage"]) -> bool + def __eq__(self, other: Union[Package, "DependencyPackage"]) -> bool: if isinstance(other, DependencyPackage): other = other.package diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index 4b3adb19542..48d68e950ac 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -39,7 +39,7 @@ if TYPE_CHECKING: - from ptomlkit.toml_document import TOMLDocument # noqa + from tomlkit.toml_document import TOMLDocument logger = logging.getLogger(__name__) @@ -50,24 +50,24 @@ class Locker(object): _relevant_keys = ["dependencies", "dev-dependencies", "source", "extras"] - def __init__(self, lock, local_config): # type: (Union[str, Path], dict) -> None + def __init__(self, lock: Union[str, Path], local_config: dict) -> None: self._lock = TOMLFile(lock) self._local_config = local_config self._lock_data = None self._content_hash = self._get_content_hash() @property - def lock(self): # type: () -> TOMLFile + def lock(self) -> TOMLFile: return self._lock @property - def lock_data(self): # type: () -> TOMLDocument + def lock_data(self) -> "TOMLDocument": if self._lock_data is None: self._lock_data = self._get_lock_data() return self._lock_data - def is_locked(self): # type: () -> bool + def is_locked(self) -> bool: """ Checks whether the locker has been locked (lockfile found). """ @@ -76,7 +76,7 @@ def is_locked(self): # type: () -> bool return "package" in self.lock_data - def is_fresh(self): # type: () -> bool + def is_fresh(self) -> bool: """ Checks whether the lock file is still up to date with the current hash. """ @@ -89,8 +89,8 @@ def is_fresh(self): # type: () -> bool return False def locked_repository( - self, with_dev_reqs=False - ): # type: (bool) -> poetry.repositories.Repository + self, with_dev_reqs: bool = False + ) -> poetry.repositories.Repository: """ Searches and returns a repository of locked packages. """ @@ -202,8 +202,8 @@ def locked_repository( @staticmethod def __get_locked_package( - _dependency, packages_by_name - ): # type: (Dependency, Dict[str, List[Package]]) -> Optional[Package] + _dependency: Dependency, packages_by_name: Dict[str, List[Package]] + ) -> Optional[Package]: """ Internal helper to identify corresponding locked package using dependency version constraints. @@ -216,13 +216,13 @@ def __get_locked_package( @classmethod def __walk_dependency_level( cls, - dependencies, - level, - pinned_versions, - packages_by_name, - project_level_dependencies, - nested_dependencies, - ): # type: (List[Dependency], int, bool, Dict[str, List[Package]], Set[str], Dict[Tuple[str, str], Dependency]) -> Dict[Tuple[str, str], Dependency] + dependencies: List[Dependency], + level: int, + pinned_versions: bool, + packages_by_name: Dict[str, List[Package]], + project_level_dependencies: Set[str], + nested_dependencies: Dict[Tuple[str, str], Dependency], + ) -> Dict[Tuple[str, str], Dependency]: if not dependencies: return nested_dependencies @@ -284,8 +284,12 @@ def __walk_dependency_level( @classmethod def get_project_dependencies( - cls, project_requires, locked_packages, pinned_versions=False, with_nested=False - ): # type: (List[Dependency], List[Package], bool, bool) -> Iterable[Dependency] + cls, + project_requires: List[Dependency], + locked_packages: List[Package], + pinned_versions: bool = False, + with_nested: bool = False, + ) -> Iterable[Dependency]: # group packages entries by name, this is required because requirement might use different constraints packages_by_name = {} for pkg in locked_packages: @@ -339,8 +343,11 @@ def get_project_dependencies( return sorted(nested_dependencies.values(), key=lambda x: x.name.lower()) def get_project_dependency_packages( - self, project_requires, dev=False, extras=None - ): # type: (List[Dependency], bool, Optional[Union[bool, Sequence[str]]]) -> Iterator[DependencyPackage] + self, + project_requires: List[Dependency], + dev: bool = False, + extras: Optional[Union[bool, Sequence[str]]] = None, + ) -> Iterator[DependencyPackage]: repository = self.locked_repository(with_dev_reqs=dev) # Build a set of all packages required by our selected extras @@ -388,7 +395,7 @@ def get_project_dependency_packages( yield DependencyPackage(dependency=dependency, package=package) - def set_lock_data(self, root, packages): # type: (Package, List[Package]) -> bool + def set_lock_data(self, root: Package, packages: List[Package]) -> bool: files = table() packages = self._lock_packages(packages) # Retrieving hashes @@ -433,7 +440,7 @@ def set_lock_data(self, root, packages): # type: (Package, List[Package]) -> bo return False - def _write_lock_data(self, data): # type: ("TOMLDocument") -> None + def _write_lock_data(self, data: "TOMLDocument") -> None: self.lock.write(data) # Checking lock file data consistency @@ -442,7 +449,7 @@ def _write_lock_data(self, data): # type: ("TOMLDocument") -> None self._lock_data = None - def _get_content_hash(self): # type: () -> str + def _get_content_hash(self) -> str: """ Returns the sha256 hash of the sorted content of the pyproject file. """ @@ -458,7 +465,7 @@ def _get_content_hash(self): # type: () -> str return content_hash - def _get_lock_data(self): # type: () -> "TOMLDocument" + def _get_lock_data(self) -> "TOMLDocument": if not self._lock.exists(): raise RuntimeError("No lockfile found. Unable to read locked packages") @@ -490,7 +497,7 @@ def _get_lock_data(self): # type: () -> "TOMLDocument" return lock_data - def _lock_packages(self, packages): # type: (List[Package]) -> list + def _lock_packages(self, packages: List[Package]) -> list: locked = [] for package in sorted(packages, key=lambda x: x.name): @@ -500,7 +507,7 @@ def _lock_packages(self, packages): # type: (List[Package]) -> list return locked - def _dump_package(self, package): # type: (Package) -> dict + def _dump_package(self, package: Package) -> dict: dependencies = {} for dependency in sorted(package.requires, key=lambda d: d.name): if dependency.pretty_name not in dependencies: diff --git a/poetry/packages/package_collection.py b/poetry/packages/package_collection.py index e572dcbfdeb..7aa7b29c233 100644 --- a/poetry/packages/package_collection.py +++ b/poetry/packages/package_collection.py @@ -6,14 +6,16 @@ if TYPE_CHECKING: - from poetry.core.packages import Dependency # noqa - from poetry.core.packages import Package # noqa + from poetry.core.packages import Dependency + from poetry.core.packages import Package class PackageCollection(list): def __init__( - self, dependency, packages=None - ): # type: (Dependency, List[Union["Package", DependencyPackage]]) -> None + self, + dependency: "Dependency", + packages: List[Union["Package", DependencyPackage]] = None, + ) -> None: self._dependency = dependency if packages is None: @@ -24,7 +26,7 @@ def __init__( for package in packages: self.append(package) - def append(self, package): # type: (Union["Package", DependencyPackage]) -> None + def append(self, package: Union["Package", DependencyPackage]) -> None: if isinstance(package, DependencyPackage): package = package.package diff --git a/poetry/poetry.py b/poetry/poetry.py index 325c6074df3..f5f46e1b968 100644 --- a/poetry/poetry.py +++ b/poetry/poetry.py @@ -18,11 +18,11 @@ class Poetry(BasePoetry): def __init__( self, - file, # type: Path - local_config, # type: dict - package, # type: ProjectPackage - locker, # type: Locker - config, # type: Config + file: Path, + local_config: dict, + package: ProjectPackage, + locker: Locker, + config: Config, ): super(Poetry, self).__init__(file, local_config, package) @@ -31,28 +31,28 @@ def __init__( self._pool = Pool() @property - def locker(self): # type: () -> Locker + def locker(self) -> Locker: return self._locker @property - def pool(self): # type: () -> Pool + def pool(self) -> Pool: return self._pool @property - def config(self): # type: () -> Config + def config(self) -> Config: return self._config - def set_locker(self, locker): # type: (Locker) -> Poetry + def set_locker(self, locker: Locker) -> "Poetry": self._locker = locker return self - def set_pool(self, pool): # type: (Pool) -> Poetry + def set_pool(self, pool: Pool) -> "Poetry": self._pool = pool return self - def set_config(self, config): # type: (Config) -> Poetry + def set_config(self, config: Config) -> "Poetry": self._config = config return self diff --git a/poetry/publishing/publisher.py b/poetry/publishing/publisher.py index f8a87a20388..2c9d1e14d8a 100644 --- a/poetry/publishing/publisher.py +++ b/poetry/publishing/publisher.py @@ -14,10 +14,10 @@ if TYPE_CHECKING: - from cleo.io.buffered_io import BufferedIO # noqa - from cleo.io.null_io import NullIO # noqa + from cleo.io import BufferedIO + from cleo.io import ConsoleIO - from ..poetry import Poetry # noqa + from ..poetry import Poetry logger = logging.getLogger(__name__) @@ -27,9 +27,7 @@ class Publisher: Registers and publishes packages to remote repositories. """ - def __init__( - self, poetry, io - ): # type: ("Poetry", Union["BufferedIO", "NullIO"]) -> None + def __init__(self, poetry: "Poetry", io: Union["BufferedIO", "ConsoleIO"]) -> None: self._poetry = poetry self._package = poetry.package self._io = io @@ -37,18 +35,18 @@ def __init__( self._password_manager = PasswordManager(poetry.config) @property - def files(self): # type: () -> List[Path] + def files(self) -> List[Path]: return self._uploader.files def publish( self, - repository_name, - username, - password, - cert=None, - client_cert=None, - dry_run=False, - ): # type: (Optional[str], Optional[str], Optional[str], Optional[Path], Optional[Path], Optional[bool]) -> None + repository_name: Optional[str], + username: Optional[str], + password: Optional[str], + cert: Optional[Path] = None, + client_cert: Optional[Path] = None, + dry_run: Optional[bool] = False, + ) -> None: if not repository_name: url = "https://upload.pypi.org/legacy/" repository_name = "pypi" diff --git a/poetry/publishing/uploader.py b/poetry/publishing/uploader.py index 8dccb4ef390..72889c023e2 100644 --- a/poetry/publishing/uploader.py +++ b/poetry/publishing/uploader.py @@ -29,15 +29,15 @@ if TYPE_CHECKING: - from cleo.io.null_io import NullIO # noqa + from cleo.io.null_io import NullIO - from poetry.poetry import Poetry # noqa + from poetry.poetry import Poetry _has_blake2 = hasattr(hashlib, "blake2b") class UploadError(Exception): - def __init__(self, error): # type: (Union[ConnectionError, HTTPError, str]) -> None + def __init__(self, error: Union[ConnectionError, HTTPError, str]) -> None: if isinstance(error, HTTPError): message = "HTTP Error {}: {}".format( error.response.status_code, error.response.reason @@ -53,7 +53,7 @@ def __init__(self, error): # type: (Union[ConnectionError, HTTPError, str]) -> class Uploader: - def __init__(self, poetry, io): # type: ("Poetry", "NullIO") -> None + def __init__(self, poetry: "Poetry", io: "NullIO") -> None: self._poetry = poetry self._package = poetry.package self._io = io @@ -61,11 +61,11 @@ def __init__(self, poetry, io): # type: ("Poetry", "NullIO") -> None self._password = None @property - def user_agent(self): # type: () -> str + def user_agent(self) -> str: return user_agent("poetry", __version__) @property - def adapter(self): # type: () -> adapters.HTTPAdapter + def adapter(self) -> adapters.HTTPAdapter: retry = util.Retry( connect=5, total=10, @@ -76,7 +76,7 @@ def adapter(self): # type: () -> adapters.HTTPAdapter return adapters.HTTPAdapter(max_retries=retry) @property - def files(self): # type: () -> List[Path] + def files(self) -> List[Path]: dist = self._poetry.file.parent / "dist" version = normalize_version(self._package.version.text) @@ -93,11 +93,11 @@ def files(self): # type: () -> List[Path] return sorted(wheels + tars) - def auth(self, username, password): # type: (str, str) -> None + def auth(self, username: str, password: str) -> None: self._username = username self._password = password - def make_session(self): # type: () -> requests.Session + def make_session(self) -> requests.Session: session = requests.session() if self.is_authenticated(): session.auth = (self._username, self._password) @@ -108,12 +108,16 @@ def make_session(self): # type: () -> requests.Session return session - def is_authenticated(self): # type: () -> bool + def is_authenticated(self) -> bool: return self._username is not None and self._password is not None def upload( - self, url, cert=None, client_cert=None, dry_run=False - ): # type: (str, Optional[Path], Optional[Path], bool) -> None + self, + url: str, + cert: Optional[Path] = None, + client_cert: Optional[Path] = None, + dry_run: bool = False, + ) -> None: session = self.make_session() if cert: @@ -127,7 +131,7 @@ def upload( finally: session.close() - def post_data(self, file): # type: (Path) -> Dict[str, Any] + def post_data(self, file: Path) -> Dict[str, Any]: meta = Metadata.from_package(self._package) file_type = self._get_type(file) @@ -206,8 +210,8 @@ def post_data(self, file): # type: (Path) -> Dict[str, Any] return data def _upload( - self, session, url, dry_run=False - ): # type: (requests.Session, str, Optional[bool]) -> None + self, session: requests.Session, url: str, dry_run: Optional[bool] = False + ) -> None: try: self._do_upload(session, url, dry_run) except HTTPError as e: @@ -223,8 +227,8 @@ def _upload( raise UploadError(e) def _do_upload( - self, session, url, dry_run=False - ): # type: (requests.Session, str, Optional[bool]) -> None + self, session: requests.Session, url: str, dry_run: Optional[bool] = False + ) -> None: for file in self.files: # TODO: Check existence @@ -234,8 +238,12 @@ def _do_upload( resp.raise_for_status() def _upload_file( - self, session, url, file, dry_run=False - ): # type: (requests.Session, str, Path, Optional[bool]) -> requests.Response + self, + session: requests.Session, + url: str, + file: Path, + dry_run: Optional[bool] = False, + ) -> requests.Response: from cleo.ui.progress_bar import ProgressBar data = self.post_data(file) @@ -305,9 +313,7 @@ def _upload_file( return resp - def _register( - self, session, url - ): # type: (requests.Session, str) -> requests.Response + def _register(self, session: requests.Session, url: str) -> requests.Response: """ Register a package to a repository. """ @@ -335,7 +341,7 @@ def _register( return resp - def _prepare_data(self, data): # type: (Dict) -> List[Tuple[str, str]] + def _prepare_data(self, data: Dict) -> List[Tuple[str, str]]: data_to_send = [] for key, value in data.items(): if not isinstance(value, (list, tuple)): @@ -346,7 +352,7 @@ def _prepare_data(self, data): # type: (Dict) -> List[Tuple[str, str]] return data_to_send - def _get_type(self, file): # type: (Path) -> str + def _get_type(self, file: Path) -> str: exts = file.suffixes if exts[-1] == ".whl": return "bdist_wheel" diff --git a/poetry/puzzle/exceptions.py b/poetry/puzzle/exceptions.py index 8ae381a88ba..5c032da5970 100644 --- a/poetry/puzzle/exceptions.py +++ b/poetry/puzzle/exceptions.py @@ -3,20 +3,20 @@ class SolverProblemError(Exception): - def __init__(self, error): # type: (Exception) -> None + def __init__(self, error: Exception) -> None: self._error = error super(SolverProblemError, self).__init__(str(error)) @property - def error(self): # type: () -> Exception + def error(self) -> Exception: return self._error class OverrideNeeded(Exception): - def __init__(self, *overrides): # type: (*Dict) -> None + def __init__(self, *overrides: Dict) -> None: self._overrides = overrides @property - def overrides(self): # type: () -> Tuple[Dict] + def overrides(self) -> Tuple[Dict]: return self._overrides diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index 63ce89a4c8d..6c4823a8d69 100644 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -46,7 +46,7 @@ class Indicator(ProgressIndicator): - def _formatter_elapsed(self): + def _formatter_elapsed(self) -> str: elapsed = time.time() - self._start_time return "{:.1f}s".format(elapsed) @@ -57,8 +57,8 @@ class Provider: UNSAFE_PACKAGES = {"setuptools", "distribute", "pip", "wheel"} def __init__( - self, package, pool, io, env=None - ): # type: (Package, Pool, Any, Optional[Env]) -> None + self, package: Package, pool: Pool, io: Any, env: Optional[Env] = None + ) -> None: self._package = package self._pool = pool self._io = io @@ -72,20 +72,20 @@ def __init__( self._load_deferred = True @property - def pool(self): # type: () -> Pool + def pool(self) -> Pool: return self._pool - def is_debugging(self): # type: () -> bool + def is_debugging(self) -> bool: return self._is_debugging - def set_overrides(self, overrides): # type: (Dict) -> None + def set_overrides(self, overrides: Dict) -> None: self._overrides = overrides - def load_deferred(self, load_deferred): # type: (bool) -> None + def load_deferred(self, load_deferred: bool) -> None: self._load_deferred = load_deferred @contextmanager - def use_environment(self, env): # type: (Env) -> Provider + def use_environment(self, env: Env) -> "Provider": original_env = self._env original_python_constraint = self._python_constraint @@ -98,8 +98,15 @@ def use_environment(self, env): # type: (Env) -> Provider self._python_constraint = original_python_constraint def search_for( - self, dependency - ): # type: (Union[Dependency, VCSDependency, FileDependency, DirectoryDependency, URLDependency]) -> List[DependencyPackage] + self, + dependency: Union[ + Dependency, + VCSDependency, + FileDependency, + DirectoryDependency, + URLDependency, + ], + ) -> List[DependencyPackage]: """ Search for the specifications that match the given dependency. @@ -154,7 +161,7 @@ def search_for( return PackageCollection(dependency, packages) - def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package] + def search_for_vcs(self, dependency: VCSDependency) -> List[Package]: """ Search for the specifications that match the given VCS dependency. @@ -183,8 +190,14 @@ def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package] @classmethod def get_package_from_vcs( - cls, vcs, url, branch=None, tag=None, rev=None, name=None - ): # type: (str, str, Optional[str], Optional[str], Optional[str], Optional[str]) -> Package + cls, + vcs: str, + url: str, + branch: Optional[str] = None, + tag: Optional[str] = None, + rev: Optional[str] = None, + name: Optional[str] = None, + ) -> Package: if vcs != "git": raise ValueError("Unsupported VCS dependency {}".format(vcs)) @@ -215,7 +228,7 @@ def get_package_from_vcs( return package - def search_for_file(self, dependency): # type: (FileDependency) -> List[Package] + def search_for_file(self, dependency: FileDependency) -> List[Package]: if dependency in self._deferred_cache: dependency, _package = self._deferred_cache[dependency] @@ -246,7 +259,7 @@ def search_for_file(self, dependency): # type: (FileDependency) -> List[Package return [package] @classmethod - def get_package_from_file(cls, file_path): # type: (Path) -> Package + def get_package_from_file(cls, file_path: Path) -> Package: try: package = PackageInfo.from_path(path=file_path).to_package( root_dir=file_path @@ -258,9 +271,7 @@ def get_package_from_file(cls, file_path): # type: (Path) -> Package return package - def search_for_directory( - self, dependency - ): # type: (DirectoryDependency) -> List[Package] + def search_for_directory(self, dependency: DirectoryDependency) -> List[Package]: if dependency in self._deferred_cache: dependency, _package = self._deferred_cache[dependency] @@ -284,8 +295,8 @@ def search_for_directory( @classmethod def get_package_from_directory( - cls, directory, name=None - ): # type: (Path, Optional[str]) -> Package + cls, directory: Path, name: Optional[str] = None + ) -> Package: package = PackageInfo.from_directory(path=directory).to_package( root_dir=directory ) @@ -300,7 +311,7 @@ def get_package_from_directory( return package - def search_for_url(self, dependency): # type: (URLDependency) -> List[Package] + def search_for_url(self, dependency: URLDependency) -> List[Package]: if dependency in self._deferred_cache: return [self._deferred_cache[dependency]] @@ -329,7 +340,7 @@ def search_for_url(self, dependency): # type: (URLDependency) -> List[Package] return [package] @classmethod - def get_package_from_url(cls, url): # type: (str) -> Package + def get_package_from_url(cls, url: str) -> Package: with temporary_directory() as temp_dir: temp_dir = Path(temp_dir) file_name = os.path.basename(urllib.parse.urlparse(url).path) @@ -343,8 +354,8 @@ def get_package_from_url(cls, url): # type: (str) -> Package return package def incompatibilities_for( - self, package - ): # type: (DependencyPackage) -> List[Incompatibility] + self, package: DependencyPackage + ) -> List[Incompatibility]: """ Returns incompatibilities that encapsulate a given package's dependencies, or that it can't be safely selected. @@ -419,9 +430,7 @@ def incompatibilities_for( for dep in dependencies ] - def complete_package( - self, package - ): # type: (DependencyPackage) -> DependencyPackage + def complete_package(self, package: DependencyPackage) -> DependencyPackage: if package.is_root(): package = package.clone() @@ -695,7 +704,7 @@ def complete_package( return package - def debug(self, message, depth=0): # type: (str, int) -> None + def debug(self, message: str, depth: int = 0) -> None: if not (self._io.is_very_verbose() or self._io.is_debug()): return @@ -782,7 +791,7 @@ def debug(self, message, depth=0): # type: (str, int) -> None self._io.write(debug_info) @contextmanager - def progress(self): # type: () -> Iterator[None] + def progress(self) -> Iterator[None]: if not self._io.output.is_decorated() or self.is_debugging(): self._io.write_line("Resolving dependencies...") yield diff --git a/poetry/puzzle/solver.py b/poetry/puzzle/solver.py index f78c07d71d5..7465df1bfcf 100644 --- a/poetry/puzzle/solver.py +++ b/poetry/puzzle/solver.py @@ -31,24 +31,24 @@ if TYPE_CHECKING: - from poetry.core.packages import Dependency # noqa - from poetry.core.packages import DirectoryDependency # noqa - from poetry.core.packages import FileDependency # noqa - from poetry.core.packages import URLDependency # noqa - from poetry.core.packages import VCSDependency # noqa - from poetry.installation.operations import OperationTypes # noqa + from poetry.core.packages import Dependency + from poetry.core.packages import DirectoryDependency + from poetry.core.packages import FileDependency + from poetry.core.packages import URLDependency + from poetry.core.packages import VCSDependency + from poetry.installation.operations import OperationTypes class Solver: def __init__( self, - package, # type: ProjectPackage - pool, # type: Pool - installed, # type: Repository - locked, # type: Repository - io, # type: IO - remove_untracked=False, # type: bool - provider=None, # type: Optional[Provider] + package: ProjectPackage, + pool: Pool, + installed: Repository, + locked: Repository, + io: IO, + remove_untracked: bool = False, + provider: Optional[Provider] = None, ): self._package = package self._pool = pool @@ -64,15 +64,15 @@ def __init__( self._remove_untracked = remove_untracked @property - def provider(self): # type: () -> Provider + def provider(self) -> Provider: return self._provider @contextmanager - def use_environment(self, env): # type: (Env) -> None + def use_environment(self, env: Env) -> None: with self.provider.use_environment(env): yield - def solve(self, use_latest=None): # type: (List[str]) -> List["OperationTypes"] + def solve(self, use_latest: List[str] = None) -> List["OperationTypes"]: with self._provider.progress(): start = time.time() packages, depths = self._solve(use_latest=use_latest) @@ -210,8 +210,8 @@ def solve(self, use_latest=None): # type: (List[str]) -> List["OperationTypes"] ) def solve_in_compatibility_mode( - self, overrides, use_latest=None - ): # type: (Tuple[Dict], List[str]) -> Tuple[List["Package"], List[int]] + self, overrides: Tuple[Dict], use_latest: List[str] = None + ) -> Tuple[List["Package"], List[int]]: locked = {} for package in self._locked.packages: locked[package.name] = DependencyPackage(package.to_dependency(), package) @@ -241,9 +241,7 @@ def solve_in_compatibility_mode( return packages, depths - def _solve( - self, use_latest=None - ): # type: (List[str]) -> Tuple[List[Package], List[int]] + def _solve(self, use_latest: List[str] = None) -> Tuple[List[Package], List[int]]: if self._provider._overrides: self._overrides.append(self._provider._overrides) @@ -296,20 +294,18 @@ def _solve( class DFSNode(object): - def __init__( - self, id, name, base_name - ): # type: (Tuple[str, str, bool], str, str) -> None + def __init__(self, id: Tuple[str, str, bool], name: str, base_name: str) -> None: self.id = id self.name = name self.base_name = base_name - def reachable(self): # type: () -> List + def reachable(self) -> List: return [] - def visit(self, parents): # type: (List[PackageNode]) -> None + def visit(self, parents: List["PackageNode"]) -> None: pass - def __str__(self): # type: () -> str + def __str__(self) -> str: return str(self.id) @@ -320,8 +316,8 @@ class VisitedState(enum.Enum): def depth_first_search( - source, aggregator -): # type: (PackageNode, Callable) -> List[Tuple[Package, int]] + source: "PackageNode", aggregator: Callable +) -> List[Tuple[Package, int]]: back_edges = defaultdict(list) visited = {} topo_sorted_nodes = [] @@ -349,8 +345,11 @@ def depth_first_search( def dfs_visit( - node, back_edges, visited, sorted_nodes -): # type: (PackageNode, Dict[str, List[PackageNode]], Dict[str, VisitedState], List[PackageNode]) -> bool + node: "PackageNode", + back_edges: Dict[str, List["PackageNode"]], + visited: Dict[str, VisitedState], + sorted_nodes: List["PackageNode"], +) -> bool: if visited.get(node.id, VisitedState.Unvisited) == VisitedState.Visited: return True if visited.get(node.id, VisitedState.Unvisited) == VisitedState.PartiallyVisited: @@ -372,12 +371,28 @@ def dfs_visit( class PackageNode(DFSNode): def __init__( self, - package, # type: Package - packages, # type: List[Package] - previous=None, # type: Optional[PackageNode] - previous_dep=None, # type: Optional[Union["DirectoryDependency", "FileDependency", "URLDependency", "VCSDependency", "Dependency"]] - dep=None, # type: Optional[Union["DirectoryDependency", "FileDependency", "URLDependency", "VCSDependency", "Dependency"]] - ): # type: (...) -> None + package: Package, + packages: List[Package], + previous: Optional["PackageNode"] = None, + previous_dep: Optional[ + Union[ + "DirectoryDependency", + "FileDependency", + "URLDependency", + "VCSDependency", + "Dependency", + ] + ] = None, + dep: Optional[ + Union[ + "DirectoryDependency", + "FileDependency", + "URLDependency", + "VCSDependency", + "Dependency", + ] + ] = None, + ) -> None: self.package = package self.packages = packages @@ -399,8 +414,8 @@ def __init__( package.name, ) - def reachable(self): # type: () -> List[PackageNode] - children = [] # type: List[PackageNode] + def reachable(self) -> List["PackageNode"]: + children: List[PackageNode] = [] if ( self.previous_dep @@ -446,7 +461,7 @@ def reachable(self): # type: () -> List[PackageNode] return children - def visit(self, parents): # type: (PackageNode) -> None + def visit(self, parents: "PackageNode") -> None: # The root package, which has no parents, is defined as having depth -1 # So that the root package's top-level dependencies have depth 0. self.depth = 1 + max( @@ -459,8 +474,8 @@ def visit(self, parents): # type: (PackageNode) -> None def aggregate_package_nodes( - nodes, children -): # type: (List[PackageNode], List[PackageNode]) -> Tuple[Package, int] + nodes: List[PackageNode], children: List[PackageNode] +) -> Tuple[Package, int]: package = nodes[0].package depth = max(node.depth for node in nodes) category = ( diff --git a/poetry/repositories/base_repository.py b/poetry/repositories/base_repository.py index 801056661a1..4954c569ca9 100644 --- a/poetry/repositories/base_repository.py +++ b/poetry/repositories/base_repository.py @@ -4,28 +4,28 @@ if TYPE_CHECKING: - from poetry.core.packages import Dependency # noqa - from poetry.core.packages import Package # noqa + from poetry.core.packages import Dependency + from poetry.core.packages import Package class BaseRepository(object): - def __init__(self): # type: () -> None + def __init__(self) -> None: self._packages = [] @property - def packages(self): # type: () -> List["Package"] + def packages(self) -> List["Package"]: return self._packages - def has_package(self, package): # type: ("Package") -> None + def has_package(self, package: "Package") -> None: raise NotImplementedError() def package( - self, name, version, extras=None - ): # type: (str, str, Optional[List[str]]) -> None + self, name: str, version: str, extras: Optional[List[str]] = None + ) -> None: raise NotImplementedError() - def find_packages(self, dependency): # type: ("Dependency") -> None + def find_packages(self, dependency: "Dependency") -> None: raise NotImplementedError() - def search(self, query): # type: (str) -> None + def search(self, query: str) -> None: raise NotImplementedError() diff --git a/poetry/repositories/installed_repository.py b/poetry/repositories/installed_repository.py index 1202d7f6612..6fba0dd372e 100644 --- a/poetry/repositories/installed_repository.py +++ b/poetry/repositories/installed_repository.py @@ -23,7 +23,7 @@ class InstalledRepository(Repository): @classmethod - def get_package_paths(cls, env, name): # type: (Env, str) -> Set[Path] + def get_package_paths(cls, env: Env, name: str) -> Set[Path]: """ Process a .pth file within the site-packages directories, and return any valid paths. We skip executable .pth files as there is no reliable means to do this @@ -68,9 +68,7 @@ def get_package_paths(cls, env, name): # type: (Env, str) -> Set[Path] return paths @classmethod - def set_package_vcs_properties_from_path( - cls, src, package - ): # type: (Path, Package) -> None + def set_package_vcs_properties_from_path(cls, src: Path, package: Package) -> None: from poetry.core.vcs.git import Git git = Git() @@ -82,12 +80,12 @@ def set_package_vcs_properties_from_path( package._source_reference = revision @classmethod - def set_package_vcs_properties(cls, package, env): # type: (Package, Env) -> None + def set_package_vcs_properties(cls, package: Package, env: Env) -> None: src = env.path / "src" / package.name cls.set_package_vcs_properties_from_path(src, package) @classmethod - def is_vcs_package(cls, package, env): # type: (Union[Path, Package], Env) -> bool + def is_vcs_package(cls, package: Union[Path, Package], env: Env) -> bool: # A VCS dependency should have been installed # in the src directory. src = env.path / "src" @@ -102,7 +100,7 @@ def is_vcs_package(cls, package, env): # type: (Union[Path, Package], Env) -> b return True @classmethod - def load(cls, env): # type: (Env) -> InstalledRepository + def load(cls, env: Env) -> "InstalledRepository": """ Load installed packages. """ diff --git a/poetry/repositories/legacy_repository.py b/poetry/repositories/legacy_repository.py index aa870562e8b..d14d48fe2fe 100644 --- a/poetry/repositories/legacy_repository.py +++ b/poetry/repositories/legacy_repository.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING from typing import Any from typing import Dict -from typing import Generator +from typing import Iterator from typing import List from typing import Optional @@ -38,7 +38,7 @@ if TYPE_CHECKING: - from poetry.core.packages import Dependency # noqa + from poetry.core.packages import Dependency try: from html import unescape @@ -75,9 +75,7 @@ class Page: ".tar", ] - def __init__( - self, url, content, headers - ): # type: (str, str, Dict[str, Any]) -> None + def __init__(self, url: str, content: str, headers: Dict[str, Any]) -> None: if not url.endswith("/"): url += "/" @@ -99,7 +97,7 @@ def __init__( ) @property - def versions(self): # type: () -> Generator[Version] + def versions(self) -> Iterator[Version]: seen = set() for link in self.links: version = self.link_version(link) @@ -115,7 +113,7 @@ def versions(self): # type: () -> Generator[Version] yield version @property - def links(self): # type: () -> Generator[Link] + def links(self) -> Iterator[Link]: for anchor in self._parsed.findall(".//a"): if anchor.get("href"): href = anchor.get("href") @@ -130,12 +128,12 @@ def links(self): # type: () -> Generator[Link] yield link - def links_for_version(self, version): # type: (Version) -> Generator[Link] + def links_for_version(self, version: Version) -> Iterator[Link]: for link in self.links: if self.link_version(link) == version: yield link - def link_version(self, link): # type: (Link) -> Optional[Version] + def link_version(self, link: Link) -> Optional[Version]: m = wheel_file_re.match(link.filename) if m: version = m.group("ver") @@ -156,7 +154,7 @@ def link_version(self, link): # type: (Link) -> Optional[Version] _clean_re = re.compile(r"[^a-z0-9$&+,/:;=?@.#%_\\|-]", re.I) - def clean_link(self, url): # type: (str) -> str + def clean_link(self, url: str) -> str: """Makes sure a link is fully encoded. That is, if a ' ' shows up in the link, it will be rewritten to %20 (while not over-quoting % or other characters).""" @@ -165,8 +163,14 @@ def clean_link(self, url): # type: (str) -> str class LegacyRepository(PyPiRepository): def __init__( - self, name, url, config=None, disable_cache=False, cert=None, client_cert=None - ): # type: (str, str, Optional[Config], bool, Optional[Path], Optional[Path]) -> None + self, + name: str, + url: str, + config: Optional[Config] = None, + disable_cache: bool = False, + cert: Optional[Path] = None, + client_cert: Optional[Path] = None, + ) -> None: if name == "pypi": raise ValueError("The name [pypi] is reserved for repositories") @@ -211,15 +215,15 @@ def __init__( self._disable_cache = disable_cache @property - def cert(self): # type: () -> Optional[Path] + def cert(self) -> Optional[Path]: return self._cert @property - def client_cert(self): # type: () -> Optional[Path] + def client_cert(self) -> Optional[Path]: return self._client_cert @property - def authenticated_url(self): # type: () -> str + def authenticated_url(self) -> str: if not self._session.auth: return self.url @@ -233,7 +237,7 @@ def authenticated_url(self): # type: () -> str path=parsed.path, ) - def find_packages(self, dependency): # type: ("Dependency") -> List[Package] + def find_packages(self, dependency: "Dependency") -> List[Package]: packages = [] constraint = dependency.constraint @@ -305,8 +309,8 @@ def find_packages(self, dependency): # type: ("Dependency") -> List[Package] return packages def package( - self, name, version, extras=None - ): # type: (str, str, Optional[List[str]]) -> Package + self, name: str, version: str, extras: Optional[List[str]] = None + ) -> Package: """ Retrieve the release information. @@ -330,14 +334,14 @@ def package( return package - def find_links_for_package(self, package): # type: (Package) -> List[Link] + def find_links_for_package(self, package: Package) -> List[Link]: page = self._get("/{}/".format(package.name.replace(".", "-"))) if page is None: return [] return list(page.links_for_version(package.version)) - def _get_release_info(self, name, version): # type: (str, str) -> dict + def _get_release_info(self, name: str, version: str) -> dict: page = self._get("/{}/".format(canonicalize_name(name).replace(".", "-"))) if page is None: raise PackageNotFound('No package named "{}"'.format(name)) @@ -385,7 +389,7 @@ def _get_release_info(self, name, version): # type: (str, str) -> dict return data.asdict() - def _get(self, endpoint): # type: (str) -> Optional[Page] + def _get(self, endpoint: str) -> Optional[Page]: url = self._url + endpoint try: response = self.session.get(url) diff --git a/poetry/repositories/pool.py b/poetry/repositories/pool.py index d8181ba06c0..20579b5fdab 100644 --- a/poetry/repositories/pool.py +++ b/poetry/repositories/pool.py @@ -9,19 +9,21 @@ if TYPE_CHECKING: - from poetry.core.packages import Dependency # noqa - from poetry.core.packages import Package # noqa + from poetry.core.packages import Dependency + from poetry.core.packages import Package class Pool(BaseRepository): def __init__( - self, repositories=None, ignore_repository_names=False - ): # type: (Optional[List[Repository]], bool) -> None + self, + repositories: Optional[List[Repository]] = None, + ignore_repository_names: bool = False, + ) -> None: if repositories is None: repositories = [] - self._lookup = {} # type: Dict[str, int] - self._repositories = [] # type: List[Repository] + self._lookup: Dict[str, int] = {} + self._repositories: List[Repository] = [] self._default = False self._secondary_start_idx = None @@ -33,18 +35,18 @@ def __init__( super(Pool, self).__init__() @property - def repositories(self): # type: () -> List[Repository] + def repositories(self) -> List[Repository]: return self._repositories - def has_default(self): # type: () -> bool + def has_default(self) -> bool: return self._default - def has_repository(self, name): # type: (str) -> bool + def has_repository(self, name: str) -> bool: name = name.lower() if name is not None else None return name in self._lookup - def repository(self, name): # type: (str) -> Repository + def repository(self, name: str) -> Repository: if name is not None: name = name.lower() @@ -54,8 +56,8 @@ def repository(self, name): # type: (str) -> Repository raise ValueError('Repository "{}" does not exist.'.format(name)) def add_repository( - self, repository, default=False, secondary=False - ): # type: (Repository, bool, bool) -> Pool + self, repository: Repository, default: bool = False, secondary: bool = False + ) -> "Pool": """ Adds a repository to the pool. """ @@ -99,7 +101,7 @@ def add_repository( return self - def remove_repository(self, repository_name): # type: (str) -> Pool + def remove_repository(self, repository_name: str) -> "Pool": if repository_name is not None: repository_name = repository_name.lower() @@ -109,12 +111,12 @@ def remove_repository(self, repository_name): # type: (str) -> Pool return self - def has_package(self, package): # type: ("Package") -> bool + def has_package(self, package: "Package") -> bool: raise NotImplementedError() def package( - self, name, version, extras=None, repository=None - ): # type: (str, str, List[str], str) -> "Package" + self, name: str, version: str, extras: List[str] = None, repository: str = None + ) -> "Package": if repository is not None: repository = repository.lower() @@ -144,7 +146,7 @@ def package( raise PackageNotFound("Package {} ({}) not found.".format(name, version)) - def find_packages(self, dependency): # type: ("Dependency") -> List["Package"] + def find_packages(self, dependency: "Dependency") -> List["Package"]: repository = dependency.source_name if repository is not None: repository = repository.lower() @@ -165,7 +167,7 @@ def find_packages(self, dependency): # type: ("Dependency") -> List["Package"] return packages - def search(self, query): # type: (str) -> List["Package"] + def search(self, query: str) -> List["Package"]: from .legacy_repository import LegacyRepository results = [] diff --git a/poetry/repositories/pypi_repository.py b/poetry/repositories/pypi_repository.py index 951f305dbfc..02d9543d92b 100644 --- a/poetry/repositories/pypi_repository.py +++ b/poetry/repositories/pypi_repository.py @@ -46,8 +46,11 @@ class PyPiRepository(RemoteRepository): CACHE_VERSION = parse_constraint("1.0.0") def __init__( - self, url="https://pypi.org/", disable_cache=False, fallback=True - ): # type: (str, bool, bool) -> None + self, + url: str = "https://pypi.org/", + disable_cache: bool = False, + fallback: bool = True, + ) -> None: super(PyPiRepository, self).__init__(url.rstrip("/") + "/simple/") self._base_url = url @@ -74,10 +77,10 @@ def __init__( self._name = "PyPI" @property - def session(self): # type: () -> CacheControl + def session(self) -> CacheControl: return self._session - def find_packages(self, dependency): # type: (Dependency) -> List[Package] + def find_packages(self, dependency: Dependency) -> List[Package]: """ Find packages on the remote server. """ @@ -152,13 +155,13 @@ def find_packages(self, dependency): # type: (Dependency) -> List[Package] def package( self, - name, # type: str - version, # type: str - extras=None, # type: (Union[list, None]) - ): # type: (...) -> Package + name: str, + version: str, + extras: (Union[list, None]) = None, + ) -> Package: return self.get_release_info(name, version).to_package(name=name, extras=extras) - def search(self, query): # type: (str) -> List[Package] + def search(self, query: str) -> List[Package]: results = [] search = {"q": query} @@ -190,7 +193,7 @@ def search(self, query): # type: (str) -> List[Package] return results - def get_package_info(self, name): # type: (str) -> dict + def get_package_info(self, name: str) -> dict: """ Return the package information given its name. @@ -204,14 +207,14 @@ def get_package_info(self, name): # type: (str) -> dict name, lambda: self._get_package_info(name) ) - def _get_package_info(self, name): # type: (str) -> dict + def _get_package_info(self, name: str) -> dict: data = self._get("pypi/{}/json".format(name)) if data is None: raise PackageNotFound("Package [{}] not found.".format(name)) return data - def get_release_info(self, name, version): # type: (str, str) -> PackageInfo + def get_release_info(self, name: str, version: str) -> PackageInfo: """ Return the release information given a package name and a version. @@ -238,7 +241,7 @@ def get_release_info(self, name, version): # type: (str, str) -> PackageInfo return PackageInfo.load(cached) - def find_links_for_package(self, package): # type: (Package) -> List[Link] + def find_links_for_package(self, package: Package) -> List[Link]: json_data = self._get("pypi/{}/{}/json".format(package.name, package.version)) if json_data is None: return [] @@ -250,7 +253,7 @@ def find_links_for_package(self, package): # type: (Package) -> List[Link] return links - def _get_release_info(self, name, version): # type: (str, str) -> dict + def _get_release_info(self, name: str, version: str) -> dict: self._log("Getting info for {} ({}) from PyPI".format(name, version), "debug") json_data = self._get("pypi/{}/{}/json".format(name, version)) @@ -311,7 +314,7 @@ def _get_release_info(self, name, version): # type: (str, str) -> dict return data.asdict() - def _get(self, endpoint): # type: (str) -> Union[dict, None] + def _get(self, endpoint: str) -> Union[dict, None]: try: json_response = self.session.get(self._base_url + endpoint) except requests.exceptions.TooManyRedirects: @@ -327,7 +330,7 @@ def _get(self, endpoint): # type: (str) -> Union[dict, None] return json_data - def _get_info_from_urls(self, urls): # type: (Dict[str, List[str]]) -> PackageInfo + def _get_info_from_urls(self, urls: Dict[str, List[str]]) -> PackageInfo: # Checking wheels first as they are more likely to hold # the necessary information if "bdist_wheel" in urls: @@ -419,7 +422,7 @@ def _get_info_from_urls(self, urls): # type: (Dict[str, List[str]]) -> PackageI return self._get_info_from_sdist(urls["sdist"][0]) - def _get_info_from_wheel(self, url): # type: (str) -> PackageInfo + def _get_info_from_wheel(self, url: str) -> PackageInfo: self._log( "Downloading wheel: {}".format( urllib.parse.urlparse(url).path.rsplit("/")[-1] @@ -435,7 +438,7 @@ def _get_info_from_wheel(self, url): # type: (str) -> PackageInfo return PackageInfo.from_wheel(filepath) - def _get_info_from_sdist(self, url): # type: (str) -> PackageInfo + def _get_info_from_sdist(self, url: str) -> PackageInfo: self._log( "Downloading sdist: {}".format( urllib.parse.urlparse(url).path.rsplit("/")[-1] @@ -451,8 +454,8 @@ def _get_info_from_sdist(self, url): # type: (str) -> PackageInfo return PackageInfo.from_sdist(filepath) - def _download(self, url, dest): # type: (str, str) -> None + def _download(self, url: str, dest: str) -> None: return download_file(url, dest, session=self.session) - def _log(self, msg, level="info"): # type: (str, str) -> None + def _log(self, msg: str, level: str = "info") -> None: getattr(logger, level)("{}: {}".format(self._name, msg)) diff --git a/poetry/repositories/remote_repository.py b/poetry/repositories/remote_repository.py index 7717740d87c..a893b31ec4f 100644 --- a/poetry/repositories/remote_repository.py +++ b/poetry/repositories/remote_repository.py @@ -2,15 +2,15 @@ class RemoteRepository(Repository): - def __init__(self, url): # type: (str) -> None + def __init__(self, url: str) -> None: self._url = url super(RemoteRepository, self).__init__() @property - def url(self): # type: () -> str + def url(self) -> str: return self._url @property - def authenticated_url(self): # type: () -> str + def authenticated_url(self) -> str: return self._url diff --git a/poetry/repositories/repository.py b/poetry/repositories/repository.py index f65e44bafc8..96932ec8edf 100644 --- a/poetry/repositories/repository.py +++ b/poetry/repositories/repository.py @@ -10,15 +10,13 @@ if TYPE_CHECKING: - from poetry.core.packages import Dependency # noqa - from poetry.core.packages import Link # noqa - from poetry.core.packages import Package # noqa + from poetry.core.packages import Dependency + from poetry.core.packages import Link + from poetry.core.packages import Package class Repository(BaseRepository): - def __init__( - self, packages=None, name=None - ): # type: (List["Package"], str) -> None + def __init__(self, packages: List["Package"] = None, name: str = None) -> None: super(Repository, self).__init__() self._name = name @@ -30,19 +28,19 @@ def __init__( self.add_package(package) @property - def name(self): # type: () -> str + def name(self) -> str: return self._name def package( - self, name, version, extras=None - ): # type: (str, str, Optional[List[str]]) -> "Package" + self, name: str, version: str, extras: Optional[List[str]] = None + ) -> "Package": name = name.lower() for package in self.packages: if name == package.name and package.version.text == version: return package.clone() - def find_packages(self, dependency): # type: ("Dependency") -> List["Package"] + def find_packages(self, dependency: "Dependency") -> List["Package"]: constraint = dependency.constraint packages = [] ignored_pre_release_packages = [] @@ -85,7 +83,7 @@ def find_packages(self, dependency): # type: ("Dependency") -> List["Package"] return packages or ignored_pre_release_packages - def has_package(self, package): # type: ("Package") -> bool + def has_package(self, package: "Package") -> bool: package_id = package.unique_name for repo_package in self.packages: @@ -94,10 +92,10 @@ def has_package(self, package): # type: ("Package") -> bool return False - def add_package(self, package): # type: ("Package") -> None + def add_package(self, package: "Package") -> None: self._packages.append(package) - def remove_package(self, package): # type: ("Package") -> None + def remove_package(self, package: "Package") -> None: package_id = package.unique_name index = None @@ -109,10 +107,10 @@ def remove_package(self, package): # type: ("Package") -> None if index is not None: del self._packages[index] - def find_links_for_package(self, package): # type: ("Package") -> List["Link"] + def find_links_for_package(self, package: "Package") -> List["Link"]: return [] - def search(self, query): # type: (str) -> List["Package"] + def search(self, query: str) -> List["Package"]: results = [] for package in self.packages: @@ -121,5 +119,5 @@ def search(self, query): # type: (str) -> List["Package"] return results - def __len__(self): # type: () -> int + def __len__(self) -> int: return len(self._packages) diff --git a/poetry/utils/appdirs.py b/poetry/utils/appdirs.py index aac3ad36076..f5592f6b0da 100644 --- a/poetry/utils/appdirs.py +++ b/poetry/utils/appdirs.py @@ -11,13 +11,13 @@ if TYPE_CHECKING: - from poetry.utils._compat import Path # noqa + from pathlib import Path WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") -def expanduser(path): # type: (Union[str, "Path"]) -> str +def expanduser(path: Union[str, "Path"]) -> str: """ Expand ~ and ~user constructions. @@ -29,7 +29,7 @@ def expanduser(path): # type: (Union[str, "Path"]) -> str return expanded -def user_cache_dir(appname): # type: (str) -> str +def user_cache_dir(appname: str) -> str: r""" Return full path to the user-specific cache dir for this application. @@ -72,7 +72,7 @@ def user_cache_dir(appname): # type: (str) -> str return path -def user_data_dir(appname, roaming=False): # type: (str, bool) -> str +def user_data_dir(appname: str, roaming: bool = False) -> str: r""" Return full path to the user-specific data dir for this application. @@ -112,7 +112,7 @@ def user_data_dir(appname, roaming=False): # type: (str, bool) -> str return path -def user_config_dir(appname, roaming=True): # type: (str, bool) -> str +def user_config_dir(appname: str, roaming: bool = True) -> str: """Return full path to the user-specific config dir for this application. "appname" is the name of application. @@ -145,7 +145,7 @@ def user_config_dir(appname, roaming=True): # type: (str, bool) -> str # for the discussion regarding site_config_dirs locations # see -def site_config_dirs(appname): # type: (str) -> List[str] +def site_config_dirs(appname: str) -> List[str]: r"""Return a list of potential user-shared config dirs for this application. "appname" is the name of application. @@ -186,7 +186,7 @@ def site_config_dirs(appname): # type: (str) -> List[str] # -- Windows support functions -- -def _get_win_folder_from_registry(csidl_name): # type: (str) -> str +def _get_win_folder_from_registry(csidl_name: str) -> str: """ This is a fallback technique at best. I'm not sure if using the registry for this guarantees us the correct answer for all CSIDL_* @@ -208,7 +208,7 @@ def _get_win_folder_from_registry(csidl_name): # type: (str) -> str return directory -def _get_win_folder_with_ctypes(csidl_name): # type: (str) -> str +def _get_win_folder_with_ctypes(csidl_name: str) -> str: csidl_const = { "CSIDL_APPDATA": 26, "CSIDL_COMMON_APPDATA": 35, @@ -242,7 +242,7 @@ def _get_win_folder_with_ctypes(csidl_name): # type: (str) -> str _get_win_folder = _get_win_folder_from_registry -def _win_path_to_bytes(path): # type: (str) -> Union[str, bytes] +def _win_path_to_bytes(path: str) -> Union[str, bytes]: """Encode Windows paths to bytes. Only used on Python 2. Motivation is to be consistent with other operating systems where paths diff --git a/poetry/utils/env.py b/poetry/utils/env.py index 36851104831..ff78bf0161b 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -148,8 +148,8 @@ def _version_nodot(version): class SitePackages: def __init__( - self, path, fallbacks=None, skip_write_checks=False - ): # type: (Path, List[Path], bool) -> None + self, path: Path, fallbacks: List[Path] = None, skip_write_checks: bool = False + ) -> None: self._path = path self._fallbacks = fallbacks or [] self._skip_write_checks = skip_write_checks @@ -157,15 +157,15 @@ def __init__( self._writable_candidates = None if not skip_write_checks else self._candidates @property - def path(self): # type: () -> Path + def path(self) -> Path: return self._path @property - def candidates(self): # type: () -> List[Path] + def candidates(self) -> List[Path]: return self._candidates @property - def writable_candidates(self): # type: () -> List[Path] + def writable_candidates(self) -> List[Path]: if self._writable_candidates is not None: return self._writable_candidates @@ -177,9 +177,7 @@ def writable_candidates(self): # type: () -> List[Path] return self._writable_candidates - def make_candidates( - self, path, writable_only=False - ): # type: (Path, bool) -> List[Path] + def make_candidates(self, path: Path, writable_only: bool = False) -> List[Path]: candidates = self._candidates if not writable_only else self.writable_candidates if path.is_absolute(): for candidate in candidates: @@ -198,8 +196,8 @@ def make_candidates( return [candidate / path for candidate in candidates if candidate] def _path_method_wrapper( - self, path, method, *args, **kwargs - ): # type: (Path, str, *Any, **Any) -> Union[Tuple[Path, Any], List[Tuple[Path, Any]]] + self, path: Path, method: str, *args: Any, **kwargs: Any + ) -> Union[Tuple[Path, Any], List[Tuple[Path, Any]]]: # TODO: Move to parameters after dropping Python 2.7 return_first = kwargs.pop("return_first", True) @@ -232,19 +230,19 @@ def _path_method_wrapper( raise OSError("Unable to access any of {}".format(paths_csv(candidates))) - def write_text(self, path, *args, **kwargs): # type: (Path, *Any, **Any) -> Path + def write_text(self, path: Path, *args: Any, **kwargs: Any) -> Path: return self._path_method_wrapper(path, "write_text", *args, **kwargs)[0] - def mkdir(self, path, *args, **kwargs): # type: (Path, *Any, **Any) -> Path + def mkdir(self, path: Path, *args: Any, **kwargs: Any) -> Path: return self._path_method_wrapper(path, "mkdir", *args, **kwargs)[0] - def exists(self, path): # type: (Path) -> bool + def exists(self, path: Path) -> bool: return any( value[-1] for value in self._path_method_wrapper(path, "exists", return_first=False) ) - def find(self, path, writable_only=False): # type: (Path, bool) -> List[Path] + def find(self, path: Path, writable_only: bool = False) -> List[Path]: return [ value[0] for value in self._path_method_wrapper( @@ -253,7 +251,7 @@ def find(self, path, writable_only=False): # type: (Path, bool) -> List[Path] if value[-1] is True ] - def __getattr__(self, item): # type: (str) -> Any + def __getattr__(self, item: str) -> Any: try: return super(SitePackages, self).__getattribute__(item) except AttributeError: @@ -266,9 +264,7 @@ class EnvError(Exception): class EnvCommandError(EnvError): - def __init__( - self, e, input=None - ): # type: (CalledProcessError, Optional[str]) -> None + def __init__(self, e: CalledProcessError, input: Optional[str] = None) -> None: self.e = e message = "Command {} errored with the following return code {}, and output: \n{}".format( @@ -280,7 +276,7 @@ def __init__( class NoCompatiblePythonVersionFound(EnvError): - def __init__(self, expected, given=None): # type: (str, Optional[str]) -> None + def __init__(self, expected: str, given: Optional[str] = None) -> None: if given: message = ( "The specified Python version ({}) " @@ -308,10 +304,10 @@ class EnvManager(object): ENVS_FILE = "envs.toml" - def __init__(self, poetry): # type: (Poetry) -> None + def __init__(self, poetry: Poetry) -> None: self._poetry = poetry - def activate(self, python, io): # type: (str, IO) -> Env + def activate(self, python: str, io: IO) -> "Env": venv_path = self._poetry.config.get("virtualenvs.path") if venv_path is None: venv_path = Path(CACHE_DIR) / "virtualenvs" @@ -408,7 +404,7 @@ def activate(self, python, io): # type: (str, IO) -> Env return self.get(reload=True) - def deactivate(self, io): # type: (IO) -> None + def deactivate(self, io: IO) -> None: venv_path = self._poetry.config.get("virtualenvs.path") if venv_path is None: venv_path = Path(CACHE_DIR) / "virtualenvs" @@ -432,7 +428,7 @@ def deactivate(self, io): # type: (IO) -> None envs_file.write(envs) - def get(self, reload=False): # type: (bool) -> Env + def get(self, reload: bool = False) -> Union["VirtualEnv", "SystemEnv"]: if self._env is not None and not reload: return self._env @@ -500,7 +496,7 @@ def get(self, reload=False): # type: (bool) -> Env return VirtualEnv(prefix, base_prefix) - def list(self, name=None): # type: (Optional[str]) -> List[VirtualEnv] + def list(self, name: Optional[str] = None) -> List["VirtualEnv"]: if name is None: name = self._poetry.package.name @@ -526,7 +522,7 @@ def list(self, name=None): # type: (Optional[str]) -> List[VirtualEnv] env_list.insert(0, VirtualEnv(venv)) return env_list - def remove(self, python): # type: (str) -> Env + def remove(self, python: str) -> "Env": venv_path = self._poetry.config.get("virtualenvs.path") if venv_path is None: venv_path = Path(CACHE_DIR) / "virtualenvs" @@ -620,8 +616,12 @@ def remove(self, python): # type: (str) -> Env return VirtualEnv(venv) def create_venv( - self, io, name=None, executable=None, force=False - ): # type: (IO, Optional[str], Optional[str], bool) -> VirtualEnv + self, + io: IO, + name: Optional[str] = None, + executable: Optional[str] = None, + force: bool = False, + ) -> Union["SystemEnv", "VirtualEnv"]: if self._env is not None and not force: return self._env @@ -806,8 +806,11 @@ def create_venv( @classmethod def build_venv( - cls, path, executable=None, flags=None - ): # type: (Union[Path,str], Optional[Union[str, Path]], Dict[str, bool]) -> virtualenv.run.session.Session + cls, + path: Union[Path, str], + executable: Optional[Union[str, Path]] = None, + flags: Dict[str, bool] = None, + ) -> virtualenv.run.session.Session: flags = flags or {} if isinstance(executable, Path): @@ -828,7 +831,7 @@ def build_venv( return virtualenv.cli_run(args) @classmethod - def remove_venv(cls, path): # type: (Union[Path,str]) -> None + def remove_venv(cls, path: Union[Path, str]) -> None: if isinstance(path, str): path = Path(path) assert path.is_dir() @@ -850,7 +853,7 @@ def remove_venv(cls, path): # type: (Union[Path,str]) -> None elif file_path.is_dir(): shutil.rmtree(str(file_path)) - def get_base_prefix(self): # type: () -> Path + def get_base_prefix(self) -> Path: if hasattr(sys, "real_prefix"): return Path(sys.real_prefix) @@ -860,7 +863,7 @@ def get_base_prefix(self): # type: () -> Path return Path(sys.prefix) @classmethod - def generate_env_name(cls, name, cwd): # type: (str, str) -> str + def generate_env_name(cls, name: str, cwd: str) -> str: name = name.lower() sanitized_name = re.sub(r'[ $`!*@"\\\r\n\t]', "_", name)[:42] h = hashlib.sha256(encode(cwd)).digest() @@ -874,7 +877,7 @@ class Env(object): An abstract Python environment. """ - def __init__(self, path, base=None): # type: (Path, Optional[Path]) -> None + def __init__(self, path: Path, base: Optional[Path] = None) -> None: self._is_windows = sys.platform == "win32" self._path = path @@ -893,59 +896,59 @@ def __init__(self, path, base=None): # type: (Path, Optional[Path]) -> None self._script_dirs = None @property - def path(self): # type: () -> Path + def path(self) -> Path: return self._path @property - def base(self): # type: () -> Path + def base(self) -> Path: return self._base @property - def version_info(self): # type: () -> Tuple[int] + def version_info(self) -> Tuple[int]: return tuple(self.marker_env["version_info"]) @property - def python_implementation(self): # type: () -> str + def python_implementation(self) -> str: return self.marker_env["platform_python_implementation"] @property - def python(self): # type: () -> str + def python(self) -> str: """ Path to current python executable """ return self._bin("python") @property - def marker_env(self): # type: () -> Dict[str, Any] + def marker_env(self) -> Dict[str, Any]: if self._marker_env is None: self._marker_env = self.get_marker_env() return self._marker_env @property - def pip(self): # type: () -> str + def pip(self) -> str: """ Path to current pip executable """ return self._bin("pip") @property - def platform(self): # type: () -> str + def platform(self) -> str: return sys.platform @property - def os(self): # type: () -> str + def os(self) -> str: return os.name @property - def pip_version(self): # type: () -> Version + def pip_version(self) -> Version: if self._pip_version is None: self._pip_version = self.get_pip_version() return self._pip_version @property - def site_packages(self): # type: () -> SitePackages + def site_packages(self) -> SitePackages: if self._site_packages is None: # we disable write checks if no user site exist fallbacks = [self.usersite] if self.usersite else [] @@ -955,24 +958,24 @@ def site_packages(self): # type: () -> SitePackages return self._site_packages @property - def usersite(self): # type: () -> Optional[Path] + def usersite(self) -> Optional[Path]: if "usersite" in self.paths: return Path(self.paths["usersite"]) @property - def userbase(self): # type: () -> Optional[Path] + def userbase(self) -> Optional[Path]: if "userbase" in self.paths: return Path(self.paths["userbase"]) @property - def purelib(self): # type: () -> Path + def purelib(self) -> Path: if self._purelib is None: self._purelib = Path(self.paths["purelib"]) return self._purelib @property - def platlib(self): # type: () -> Path + def platlib(self) -> Path: if self._platlib is None: if "platlib" in self.paths: self._platlib = Path(self.paths["platlib"]) @@ -981,7 +984,7 @@ def platlib(self): # type: () -> Path return self._platlib - def is_path_relative_to_lib(self, path): # type: (Path) -> bool + def is_path_relative_to_lib(self, path: Path) -> bool: for lib_path in [self.purelib, self.platlib]: try: path.relative_to(lib_path) @@ -992,25 +995,25 @@ def is_path_relative_to_lib(self, path): # type: (Path) -> bool return False @property - def sys_path(self): # type: () -> List[str] + def sys_path(self) -> List[str]: raise NotImplementedError() @property - def paths(self): # type: () -> Dict[str, str] + def paths(self) -> Dict[str, str]: if self._paths is None: self._paths = self.get_paths() return self._paths @property - def supported_tags(self): # type: () -> List[Tag] + def supported_tags(self) -> List[Tag]: if self._supported_tags is None: self._supported_tags = self.get_supported_tags() return self._supported_tags @classmethod - def get_base_prefix(cls): # type: () -> Path + def get_base_prefix(cls) -> Path: if hasattr(sys, "real_prefix"): return Path(sys.real_prefix) @@ -1019,47 +1022,47 @@ def get_base_prefix(cls): # type: () -> Path return Path(sys.prefix) - def get_version_info(self): # type: () -> Tuple[int] + def get_version_info(self) -> Tuple[int]: raise NotImplementedError() - def get_python_implementation(self): # type: () -> str + def get_python_implementation(self) -> str: raise NotImplementedError() - def get_marker_env(self): # type: () -> Dict[str, Any] + def get_marker_env(self) -> Dict[str, Any]: raise NotImplementedError() - def get_pip_command(self): # type: () -> List[str] + def get_pip_command(self) -> List[str]: raise NotImplementedError() - def get_supported_tags(self): # type: () -> List[Tag] + def get_supported_tags(self) -> List[Tag]: raise NotImplementedError() - def get_pip_version(self): # type: () -> Version + def get_pip_version(self) -> Version: raise NotImplementedError() - def get_paths(self): # type: () -> Dict[str, str] + def get_paths(self) -> Dict[str, str]: raise NotImplementedError() - def is_valid_for_marker(self, marker): # type: (BaseMarker) -> bool + def is_valid_for_marker(self, marker: BaseMarker) -> bool: return marker.validate(self.marker_env) - def is_sane(self): # type: () -> bool + def is_sane(self) -> bool: """ Checks whether the current environment is sane or not. """ return True - def run(self, bin, *args, **kwargs): # type: (str, *str, **Any) -> Union[str, int] + def run(self, bin: str, *args: str, **kwargs: Any) -> Union[str, int]: bin = self._bin(bin) cmd = [bin] + list(args) return self._run(cmd, **kwargs) - def run_pip(self, *args, **kwargs): # type: (*str, **Any) -> Union[int, str] + def run_pip(self, *args: str, **kwargs: Any) -> Union[int, str]: pip = self.get_pip_command() cmd = pip + list(args) return self._run(cmd, **kwargs) - def _run(self, cmd, **kwargs): # type: (List[str], **Any) -> Union[int, str] + def _run(self, cmd: List[str], **kwargs: Any) -> Union[int, str]: """ Run a command inside the Python environment. """ @@ -1093,9 +1096,7 @@ def _run(self, cmd, **kwargs): # type: (List[str], **Any) -> Union[int, str] return decode(output) - def execute( - self, bin, *args, **kwargs - ): # type: (str, *str, **Any) -> Optional[int] + def execute(self, bin: str, *args: str, **kwargs: Any) -> Optional[int]: bin = self._bin(bin) if not self._is_windows: @@ -1109,11 +1110,11 @@ def execute( exe.communicate() return exe.returncode - def is_venv(self): # type: () -> bool + def is_venv(self) -> bool: raise NotImplementedError() @property - def script_dirs(self): # type: () -> List[Path] + def script_dirs(self) -> List[Path]: if self._script_dirs is None: self._script_dirs = ( [Path(self.paths["scripts"])] @@ -1124,7 +1125,7 @@ def script_dirs(self): # type: () -> List[Path] self._script_dirs.append(self.userbase / self._script_dirs[0].name) return self._script_dirs - def _bin(self, bin): # type: (str) -> str + def _bin(self, bin: str) -> str: """ Return path to the given executable. """ @@ -1147,10 +1148,10 @@ def _bin(self, bin): # type: (str) -> str return str(bin_path) - def __eq__(self, other): # type: (Env) -> bool + def __eq__(self, other: "Env") -> bool: return other.__class__ == self.__class__ and other.path == self.path - def __repr__(self): # type: () -> str + def __repr__(self) -> str: return '{}("{}")'.format(self.__class__.__name__, self._path) @@ -1160,25 +1161,25 @@ class SystemEnv(Env): """ @property - def python(self): # type: () -> str + def python(self) -> str: return sys.executable @property - def sys_path(self): # type: () -> List[str] + def sys_path(self) -> List[str]: return sys.path - def get_version_info(self): # type: () -> Tuple[int] + def get_version_info(self) -> Tuple[int]: return sys.version_info - def get_python_implementation(self): # type: () -> str + def get_python_implementation(self) -> str: return platform.python_implementation() - def get_pip_command(self): # type: () -> List[str] + def get_pip_command(self) -> List[str]: # If we're not in a venv, assume the interpreter we're running on # has a pip and use that return [sys.executable, "-m", "pip"] - def get_paths(self): # type: () -> Dict[str, str] + def get_paths(self) -> Dict[str, str]: # We can't use sysconfig.get_paths() because # on some distributions it does not return the proper paths # (those used by pip for instance). We go through distutils @@ -1207,10 +1208,10 @@ def get_paths(self): # type: () -> Dict[str, str] return paths - def get_supported_tags(self): # type: () -> List[Tag] + def get_supported_tags(self) -> List[Tag]: return list(sys_tags()) - def get_marker_env(self): # type: () -> Dict[str, Any] + def get_marker_env(self) -> Dict[str, Any]: if hasattr(sys, "implementation"): info = sys.implementation.version iver = "{0.major}.{0.minor}.{0.micro}".format(info) @@ -1243,12 +1244,12 @@ def get_marker_env(self): # type: () -> Dict[str, Any] "interpreter_version": interpreter_version(), } - def get_pip_version(self): # type: () -> Version + def get_pip_version(self) -> Version: from pip import __version__ return Version.parse(__version__) - def is_venv(self): # type: () -> bool + def is_venv(self) -> bool: return self._path != self._base @@ -1257,7 +1258,7 @@ class VirtualEnv(Env): A virtual Python environment. """ - def __init__(self, path, base=None): # type: (Path, Optional[Path]) -> None + def __init__(self, path: Path, base: Optional[Path] = None) -> None: super(VirtualEnv, self).__init__(path, base) # If base is None, it probably means this is @@ -1268,25 +1269,25 @@ def __init__(self, path, base=None): # type: (Path, Optional[Path]) -> None self._base = Path(self.run("python", "-", input_=GET_BASE_PREFIX).strip()) @property - def sys_path(self): # type: () -> List[str] + def sys_path(self) -> List[str]: output = self.run("python", "-", input_=GET_SYS_PATH) return json.loads(output) - def get_version_info(self): # type: () -> Tuple[int] + def get_version_info(self) -> Tuple[int]: output = self.run("python", "-", input_=GET_PYTHON_VERSION) return tuple([int(s) for s in output.strip().split(".")]) - def get_python_implementation(self): # type: () -> str + def get_python_implementation(self) -> str: return self.marker_env["platform_python_implementation"] - def get_pip_command(self): # type: () -> List[str] + def get_pip_command(self) -> List[str]: # We're in a virtualenv that is known to be sane, # so assume that we have a functional pip return [self._bin("pip")] - def get_supported_tags(self): # type: () -> List[Tag] + def get_supported_tags(self) -> List[Tag]: file_path = Path(packaging.tags.__file__) if file_path.suffix == ".pyc": # Python 2 @@ -1316,12 +1317,12 @@ def get_supported_tags(self): # type: () -> List[Tag] return [Tag(*t) for t in json.loads(output)] - def get_marker_env(self): # type: () -> Dict[str, Any] + def get_marker_env(self) -> Dict[str, Any]: output = self.run("python", "-", input_=GET_ENVIRONMENT_INFO) return json.loads(output) - def get_pip_version(self): # type: () -> Version + def get_pip_version(self) -> Version: output = self.run_pip("--version").strip() m = re.match("pip (.+?)(?: from .+)?$", output) if not m: @@ -1329,19 +1330,19 @@ def get_pip_version(self): # type: () -> Version return Version.parse(m.group(1)) - def get_paths(self): # type: () -> Dict[str, str] + def get_paths(self) -> Dict[str, str]: output = self.run("python", "-", input_=GET_PATHS) return json.loads(output) - def is_venv(self): # type: () -> bool + def is_venv(self) -> bool: return True - def is_sane(self): # type: () -> bool + def is_sane(self) -> bool: # A virtualenv is considered sane if both "python" and "pip" exist. return os.path.exists(self.python) and os.path.exists(self._bin("pip")) - def _run(self, cmd, **kwargs): # type: (List[str], **Any) -> Optional[int] + def _run(self, cmd: List[str], **kwargs: Any) -> Optional[int]: with self.temp_environ(): os.environ["PATH"] = self._updated_path() os.environ["VIRTUAL_ENV"] = str(self._path) @@ -1351,9 +1352,7 @@ def _run(self, cmd, **kwargs): # type: (List[str], **Any) -> Optional[int] return super(VirtualEnv, self)._run(cmd, **kwargs) - def execute( - self, bin, *args, **kwargs - ): # type: (str, *str, **Any) -> Optional[int] + def execute(self, bin: str, *args: str, **kwargs: Any) -> Optional[int]: with self.temp_environ(): os.environ["PATH"] = self._updated_path() os.environ["VIRTUAL_ENV"] = str(self._path) @@ -1364,7 +1363,7 @@ def execute( return super(VirtualEnv, self).execute(bin, *args, **kwargs) @contextmanager - def temp_environ(self): # type: () -> Iterator[None] + def temp_environ(self) -> Iterator[None]: environ = dict(os.environ) try: yield @@ -1372,18 +1371,18 @@ def temp_environ(self): # type: () -> Iterator[None] os.environ.clear() os.environ.update(environ) - def unset_env(self, key): # type: (str) -> None + def unset_env(self, key: str) -> None: if key in os.environ: del os.environ[key] - def _updated_path(self): # type: () -> str + def _updated_path(self) -> str: return os.pathsep.join([str(self._bin_dir), os.environ.get("PATH", "")]) class NullEnv(SystemEnv): def __init__( - self, path=None, base=None, execute=False - ): # type: (Path, Optional[Path], bool) -> None + self, path: Path = None, base: Optional[Path] = None, execute: bool = False + ) -> None: if path is None: path = Path(sys.prefix) @@ -1392,40 +1391,38 @@ def __init__( self._execute = execute self.executed = [] - def get_pip_command(self): # type: () -> List[str] + def get_pip_command(self) -> List[str]: return [self._bin("python"), "-m", "pip"] - def _run(self, cmd, **kwargs): # type: (List[str], **Any) -> int + def _run(self, cmd: List[str], **kwargs: Any) -> int: self.executed.append(cmd) if self._execute: return super(NullEnv, self)._run(cmd, **kwargs) - def execute( - self, bin, *args, **kwargs - ): # type: (str, *str, **Any) -> Optional[int] + def execute(self, bin: str, *args: str, **kwargs: Any) -> Optional[int]: self.executed.append([bin] + list(args)) if self._execute: return super(NullEnv, self).execute(bin, *args, **kwargs) - def _bin(self, bin): # type: (str) -> str + def _bin(self, bin: str) -> str: return bin class MockEnv(NullEnv): def __init__( self, - version_info=(3, 7, 0), # type: Tuple[int, int, int] - python_implementation="CPython", # type: str - platform="darwin", # type: str - os_name="posix", # type: str - is_venv=False, # type: bool - pip_version="19.1", # type: str - sys_path=None, # type: Optional[List[str]] - marker_env=None, # type: Dict[str, Any] - supported_tags=None, # type: List[Tag] - **kwargs, # type: Any + version_info: Tuple[int, int, int] = (3, 7, 0), + python_implementation: str = "CPython", + platform: str = "darwin", + os_name: str = "posix", + is_venv: bool = False, + pip_version: str = "19.1", + sys_path: Optional[List[str]] = None, + marker_env: Dict[str, Any] = None, + supported_tags: List[Tag] = None, + **kwargs: Any, ): super(MockEnv, self).__init__(**kwargs) @@ -1440,25 +1437,25 @@ def __init__( self._supported_tags = supported_tags @property - def platform(self): # type: () -> str + def platform(self) -> str: return self._platform @property - def os(self): # type: () -> str + def os(self) -> str: return self._os_name @property - def pip_version(self): # type: () -> Version + def pip_version(self) -> Version: return self._pip_version @property - def sys_path(self): # type: () -> List[str] + def sys_path(self) -> List[str]: if self._sys_path is None: return super(MockEnv, self).sys_path return self._sys_path - def get_marker_env(self): # type: () -> Dict[str, Any] + def get_marker_env(self) -> Dict[str, Any]: if self._mock_marker_env is not None: return self._mock_marker_env @@ -1475,5 +1472,5 @@ def get_marker_env(self): # type: () -> Dict[str, Any] return marker_env - def is_venv(self): # type: () -> bool + def is_venv(self) -> bool: return self._is_venv diff --git a/poetry/utils/exporter.py b/poetry/utils/exporter.py index 23f7f0f1503..c15d4508150 100644 --- a/poetry/utils/exporter.py +++ b/poetry/utils/exporter.py @@ -21,19 +21,19 @@ class Exporter(object): ACCEPTED_FORMATS = (FORMAT_REQUIREMENTS_TXT,) ALLOWED_HASH_ALGORITHMS = ("sha256", "sha384", "sha512") - def __init__(self, poetry): # type: (Poetry) -> None + def __init__(self, poetry: Poetry) -> None: self._poetry = poetry def export( self, - fmt, - cwd, - output, - with_hashes=True, - dev=False, - extras=None, - with_credentials=False, - ): # type: (str, Path, Union[IO, str], bool, bool, Optional[Union[bool, Sequence[str]]], bool) -> None + fmt: str, + cwd: Path, + output: Union[IO, str], + with_hashes: bool = True, + dev: bool = False, + extras: Optional[Union[bool, Sequence[str]]] = None, + with_credentials: bool = False, + ) -> None: if fmt not in self.ACCEPTED_FORMATS: raise ValueError("Invalid export format: {}".format(fmt)) @@ -48,13 +48,13 @@ def export( def _export_requirements_txt( self, - cwd, - output, - with_hashes=True, - dev=False, - extras=None, - with_credentials=False, - ): # type: (Path, Union[IO, str], bool, bool, Optional[Union[bool, Sequence[str]]], bool) -> None + cwd: Path, + output: Union[IO, str], + with_hashes: bool = True, + dev: bool = False, + extras: Optional[Union[bool, Sequence[str]]] = None, + with_credentials: bool = False, + ) -> None: indexes = set() content = "" dependency_lines = set() @@ -150,9 +150,7 @@ def _export_requirements_txt( self._output(content, cwd, output) - def _output( - self, content, cwd, output - ): # type: (str, Path, Union[IO, str]) -> None + def _output(self, content: str, cwd: Path, output: Union[IO, str]) -> None: decoded = decode(content) try: output.write(decoded) diff --git a/poetry/utils/extras.py b/poetry/utils/extras.py index cff3f5a1ff6..7c4361092a4 100644 --- a/poetry/utils/extras.py +++ b/poetry/utils/extras.py @@ -9,10 +9,10 @@ def get_extra_package_names( - packages, # type: Sequence[Package] - extras, # type: Mapping[str, List[str]] - extra_names, # type: Sequence[str] -): # type: (...) -> Iterator[str] + packages: Sequence[Package], + extras: Mapping[str, List[str]], + extra_names: Sequence[str], +) -> Iterator[str]: """ Returns all package names required by the given extras. @@ -37,7 +37,7 @@ def get_extra_package_names( # keep record of packages seen during recursion in order to avoid recursion error seen_package_names = set() - def _extra_packages(package_names): # type: (Iterable[str]) -> Iterator[str] + def _extra_packages(package_names: Iterable[str]) -> Iterator[str]: """Recursively find dependencies for packages names""" # for each extra pacakge name for package_name in package_names: diff --git a/poetry/utils/helpers.py b/poetry/utils/helpers.py index b7bc0429045..925ff2d8543 100644 --- a/poetry/utils/helpers.py +++ b/poetry/utils/helpers.py @@ -29,25 +29,25 @@ _canonicalize_regex = re.compile("[-_]+") -def canonicalize_name(name): # type: (str) -> str +def canonicalize_name(name: str) -> str: return _canonicalize_regex.sub("-", name).lower() -def module_name(name): # type: (str) -> str +def module_name(name: str) -> str: return canonicalize_name(name).replace(".", "_").replace("-", "_") -def normalize_version(version): # type: (str) -> str +def normalize_version(version: str) -> str: return str(Version(version)) -def _del_ro(action, name, exc): # type: (Callable, str, Exception) -> None +def _del_ro(action: Callable, name: str, exc: Exception) -> None: os.chmod(name, stat.S_IWRITE) os.remove(name) @contextmanager -def temporary_directory(*args, **kwargs): # type: (*Any, **Any) -> Iterator[str] +def temporary_directory(*args: Any, **kwargs: Any) -> Iterator[str]: name = tempfile.mkdtemp(*args, **kwargs) yield name @@ -55,7 +55,7 @@ def temporary_directory(*args, **kwargs): # type: (*Any, **Any) -> Iterator[str shutil.rmtree(name, onerror=_del_ro) -def get_cert(config, repository_name): # type: (Config, str) -> Optional[Path] +def get_cert(config: Config, repository_name: str) -> Optional[Path]: cert = config.get("certificates.{}.cert".format(repository_name)) if cert: return Path(cert) @@ -63,7 +63,7 @@ def get_cert(config, repository_name): # type: (Config, str) -> Optional[Path] return None -def get_client_cert(config, repository_name): # type: (Config, str) -> Optional[Path] +def get_client_cert(config: Config, repository_name: str) -> Optional[Path]: client_cert = config.get("certificates.{}.client-cert".format(repository_name)) if client_cert: return Path(client_cert) @@ -71,7 +71,7 @@ def get_client_cert(config, repository_name): # type: (Config, str) -> Optional return None -def _on_rm_error(func, path, exc_info): # type: (Callable, str, Exception) -> None +def _on_rm_error(func: Callable, path: str, exc_info: Exception) -> None: if not os.path.exists(path): return @@ -79,14 +79,14 @@ def _on_rm_error(func, path, exc_info): # type: (Callable, str, Exception) -> N func(path) -def safe_rmtree(path): # type: (str) -> None +def safe_rmtree(path: str) -> None: if Path(path).is_symlink(): return os.unlink(str(path)) shutil.rmtree(path, onerror=_on_rm_error) -def merge_dicts(d1, d2): # type: (Dict, Dict) -> None +def merge_dicts(d1: Dict, d2: Dict) -> None: for k, v in d2.items(): if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], Mapping): merge_dicts(d1[k], d2[k]) @@ -95,8 +95,11 @@ def merge_dicts(d1, d2): # type: (Dict, Dict) -> None def download_file( - url, dest, session=None, chunk_size=1024 -): # type: (str, str, Optional[requests.Session], int) -> None + url: str, + dest: str, + session: Optional[requests.Session] = None, + chunk_size: int = 1024, +) -> None: get = requests.get if not session else session.get with get(url, stream=True) as response: @@ -109,8 +112,8 @@ def download_file( def get_package_version_display_string( - package, root=None -): # type: (Package, Optional[Path]) -> str + package: Package, root: Optional[Path] = None +) -> str: if package.source_type in ["file", "directory"] and root: return "{} {}".format( package.version, @@ -120,11 +123,11 @@ def get_package_version_display_string( return package.full_pretty_version -def paths_csv(paths): # type: (List[Path]) -> str +def paths_csv(paths: List[Path]) -> str: return ", ".join('"{}"'.format(str(c)) for c in paths) -def is_dir_writable(path, create=False): # type: (Path, bool) -> bool +def is_dir_writable(path: Path, create: bool = False) -> bool: try: if not path.exists(): if not create: diff --git a/poetry/utils/password_manager.py b/poetry/utils/password_manager.py index e6cc11d65e2..bbf2a1a210a 100644 --- a/poetry/utils/password_manager.py +++ b/poetry/utils/password_manager.py @@ -6,7 +6,7 @@ if TYPE_CHECKING: - from poetry.config.config import Config # noqa + from poetry.config.config import Config logger = logging.getLogger(__name__) @@ -22,16 +22,16 @@ class KeyRingError(Exception): class KeyRing: - def __init__(self, namespace): # type: (str) -> None + def __init__(self, namespace: str) -> None: self._namespace = namespace self._is_available = True self._check() - def is_available(self): # type: () -> bool + def is_available(self) -> bool: return self._is_available - def get_password(self, name, username): # type: (str, str) -> Optional[str] + def get_password(self, name: str, username: str) -> Optional[str]: if not self.is_available(): return @@ -47,7 +47,7 @@ def get_password(self, name, username): # type: (str, str) -> Optional[str] "Unable to retrieve the password for {} from the key ring".format(name) ) - def set_password(self, name, username, password): # type: (str, str, str) -> None + def set_password(self, name: str, username: str, password: str) -> None: if not self.is_available(): return @@ -65,7 +65,7 @@ def set_password(self, name, username, password): # type: (str, str, str) -> No ) ) - def delete_password(self, name, username): # type: (str, str) -> None + def delete_password(self, name: str, username: str) -> None: if not self.is_available(): return @@ -81,10 +81,10 @@ def delete_password(self, name, username): # type: (str, str) -> None "Unable to delete the password for {} from the key ring".format(name) ) - def get_entry_name(self, name): # type: (str) -> str + def get_entry_name(self, name: str) -> str: return "{}-{}".format(self._namespace, name) - def _check(self): # type: () -> None + def _check(self) -> None: try: import keyring except Exception as e: @@ -120,12 +120,12 @@ def _check(self): # type: () -> None class PasswordManager: - def __init__(self, config): # type: ("Config") -> None + def __init__(self, config: "Config") -> None: self._config = config self._keyring = None @property - def keyring(self): # type: () -> KeyRing + def keyring(self) -> KeyRing: if self._keyring is None: self._keyring = KeyRing("poetry-repository") if not self._keyring.is_available(): @@ -135,7 +135,7 @@ def keyring(self): # type: () -> KeyRing return self._keyring - def set_pypi_token(self, name, token): # type: (str, str) -> None + def set_pypi_token(self, name: str, token: str) -> None: if not self.keyring.is_available(): self._config.auth_config_source.add_property( "pypi-token.{}".format(name), token @@ -143,13 +143,13 @@ def set_pypi_token(self, name, token): # type: (str, str) -> None else: self.keyring.set_password(name, "__token__", token) - def get_pypi_token(self, name): # type: (str) -> str + def get_pypi_token(self, name: str) -> str: if not self.keyring.is_available(): return self._config.get("pypi-token.{}".format(name)) return self.keyring.get_password(name, "__token__") - def delete_pypi_token(self, name): # type: (str) -> None + def delete_pypi_token(self, name: str) -> None: if not self.keyring.is_available(): return self._config.auth_config_source.remove_property( "pypi-token.{}".format(name) @@ -157,7 +157,7 @@ def delete_pypi_token(self, name): # type: (str) -> None self.keyring.delete_password(name, "__token__") - def get_http_auth(self, name): # type: (str) -> Optional[Dict[str, str]] + def get_http_auth(self, name: str) -> Optional[Dict[str, str]]: auth = self._config.get("http-basic.{}".format(name)) if not auth: username = self._config.get("http-basic.{}.username".format(name)) @@ -174,9 +174,7 @@ def get_http_auth(self, name): # type: (str) -> Optional[Dict[str, str]] "password": password, } - def set_http_password( - self, name, username, password - ): # type: (str, str, str) -> None + def set_http_password(self, name: str, username: str, password: str) -> None: auth = {"username": username} if not self.keyring.is_available(): @@ -186,7 +184,7 @@ def set_http_password( self._config.auth_config_source.add_property("http-basic.{}".format(name), auth) - def delete_http_password(self, name): # type: (str) -> None + def delete_http_password(self, name: str) -> None: auth = self.get_http_auth(name) if not auth or "username" not in auth: return diff --git a/poetry/utils/setup_reader.py b/poetry/utils/setup_reader.py index 48d511f9252..78f7a96af46 100644 --- a/poetry/utils/setup_reader.py +++ b/poetry/utils/setup_reader.py @@ -30,8 +30,8 @@ class SetupReader(object): @classmethod def read_from_directory( - cls, directory - ): # type: (Union[str, Path]) -> Dict[str, Union[List, Dict]] + cls, directory: Union[str, Path] + ) -> Dict[str, Union[List, Dict]]: if isinstance(directory, str): directory = Path(directory) @@ -51,9 +51,7 @@ def read_from_directory( return result - def read_setup_py( - self, filepath - ): # type: (Union[str, Path]) -> Dict[str, Union[List, Dict]] + def read_setup_py(self, filepath: Union[str, Path]) -> Dict[str, Union[List, Dict]]: if isinstance(filepath, str): filepath = Path(filepath) @@ -80,8 +78,8 @@ def read_setup_py( return result def read_setup_cfg( - self, filepath - ): # type: (Union[str, Path]) -> Dict[str, Union[List, Dict]] + self, filepath: Union[str, Path] + ) -> Dict[str, Union[List, Dict]]: parser = ConfigParser() parser.read(str(filepath)) @@ -129,8 +127,8 @@ def read_setup_cfg( } def _find_setup_call( - self, elements - ): # type: (List[Any]) -> Tuple[Optional[ast.Call], Optional[List[Any]]] + self, elements: List[Any] + ) -> Tuple[Optional[ast.Call], Optional[List[Any]]]: funcdefs = [] for i, element in enumerate(elements): if isinstance(element, ast.If) and i == len(elements) - 1: @@ -178,8 +176,8 @@ def _find_setup_call( return self._find_sub_setup_call(funcdefs) def _find_sub_setup_call( - self, elements - ): # type: (List[Any]) -> Tuple[Optional[ast.Call], Optional[List[Any]]] + self, elements: List[Any] + ) -> Tuple[Optional[ast.Call], Optional[List[Any]]]: for element in elements: if not isinstance(element, (ast.FunctionDef, ast.If)): continue @@ -194,9 +192,7 @@ def _find_sub_setup_call( return None, None - def _find_install_requires( - self, call, body - ): # type: (ast.Call, Iterable[Any]) -> List[str] + def _find_install_requires(self, call: ast.Call, body: Iterable[Any]) -> List[str]: install_requires = [] value = self._find_in_call(call, "install_requires") if value is None: @@ -237,8 +233,8 @@ def _find_install_requires( return install_requires def _find_extras_require( - self, call, body - ): # type: (ast.Call, Iterable[Any]) -> Dict[str, List] + self, call: ast.Call, body: Iterable[Any] + ) -> Dict[str, List]: extras_require = {} value = self._find_in_call(call, "extras_require") if value is None: @@ -289,8 +285,8 @@ def _find_extras_require( return extras_require def _find_single_string( - self, call, body, name - ): # type: (ast.Call, List[Any], str) -> Optional[str] + self, call: ast.Call, body: List[Any], name: str + ) -> Optional[str]: value = self._find_in_call(call, name) if value is None: # Trying to find in kwargs @@ -325,12 +321,12 @@ def _find_single_string( if variable is not None and isinstance(variable, ast.Str): return variable.s - def _find_in_call(self, call, name): # type: (ast.Call, str) -> Optional[Any] + def _find_in_call(self, call: ast.Call, name: str) -> Optional[Any]: for keyword in call.keywords: if keyword.arg == name: return keyword.value - def _find_call_kwargs(self, call): # type: (ast.Call) -> Optional[Any] + def _find_call_kwargs(self, call: ast.Call) -> Optional[Any]: kwargs = None for keyword in call.keywords: if keyword.arg is None: @@ -338,9 +334,7 @@ def _find_call_kwargs(self, call): # type: (ast.Call) -> Optional[Any] return kwargs - def _find_variable_in_body( - self, body, name - ): # type: (Iterable[Any], str) -> Optional[Any] + def _find_variable_in_body(self, body: Iterable[Any], name: str) -> Optional[Any]: found = None for elem in body: if found: @@ -357,8 +351,8 @@ def _find_variable_in_body( return elem.value def _find_in_dict( - self, dict_, name - ): # type: (Union[ast.Dict, ast.Call], str) -> Optional[Any] + self, dict_: Union[ast.Dict, ast.Call], name: str + ) -> Optional[Any]: for key, val in zip(dict_.keys, dict_.values): if isinstance(key, ast.Str) and key.s == name: return val diff --git a/poetry/utils/shell.py b/poetry/utils/shell.py index a0f539fdece..dbf1db3fb8c 100644 --- a/poetry/utils/shell.py +++ b/poetry/utils/shell.py @@ -22,20 +22,20 @@ class Shell: _shell = None - def __init__(self, name, path): # type: (str, str) -> None + def __init__(self, name: str, path: str) -> None: self._name = name self._path = path @property - def name(self): # type: () -> str + def name(self) -> str: return self._name @property - def path(self): # type: () -> str + def path(self) -> str: return self._path @classmethod - def get(cls): # type: () -> Shell + def get(cls) -> "Shell": """ Retrieve the current shell. """ @@ -61,7 +61,7 @@ def get(cls): # type: () -> Shell return cls._shell - def activate(self, env): # type: (VirtualEnv) -> None + def activate(self, env: VirtualEnv) -> None: if WINDOWS: return env.execute(self.path) @@ -79,7 +79,7 @@ def activate(self, env): # type: (VirtualEnv) -> None activate_path = env.path / bin_dir / activate_script c.sendline("{} {}".format(self._get_source_command(), activate_path)) - def resize(sig, data): # type: (Any, Any) -> None + def resize(sig: Any, data: Any) -> None: terminal = Terminal() c.setwinsize(terminal.height, terminal.width) @@ -91,7 +91,7 @@ def resize(sig, data): # type: (Any, Any) -> None sys.exit(c.exitstatus) - def _get_activate_script(self): # type: () -> str + def _get_activate_script(self) -> str: if "fish" == self._name: suffix = ".fish" elif "csh" == self._name: @@ -103,7 +103,7 @@ def _get_activate_script(self): # type: () -> str return "activate" + suffix - def _get_source_command(self): # type: () -> str + def _get_source_command(self) -> str: if "fish" == self._name: return "source" elif "csh" == self._name: @@ -113,5 +113,5 @@ def _get_source_command(self): # type: () -> str return "." - def __repr__(self): # type: () -> str + def __repr__(self) -> str: return '{}("{}", "{}")'.format(self.__class__.__name__, self._name, self._path) diff --git a/poetry/version/version_selector.py b/poetry/version/version_selector.py index 350e3ec7ca5..1c7cc254e04 100644 --- a/poetry/version/version_selector.py +++ b/poetry/version/version_selector.py @@ -7,20 +7,20 @@ if TYPE_CHECKING: - from poetry.repositories import Pool # noqa + from poetry.repositories import Pool class VersionSelector(object): - def __init__(self, pool): # type: ("Pool") -> None + def __init__(self, pool: "Pool") -> None: self._pool = pool def find_best_candidate( self, - package_name, # type: str - target_package_version=None, # type: Optional[str] - allow_prereleases=False, # type: bool - source=None, # type: Optional[str] - ): # type: (...) -> Union[Package, bool] + package_name: str, + target_package_version: Optional[str] = None, + allow_prereleases: bool = False, + source: Optional[str] = None, + ) -> Union[Package, bool]: """ Given a package name and optional version, returns the latest Package that matches @@ -58,12 +58,12 @@ def find_best_candidate( return False return package - def find_recommended_require_version(self, package): # type: (Package) -> str + 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, pretty_version): # type: (str, str) -> str + def _transform_version(self, version: str, pretty_version: str) -> str: try: parsed = Version.parse(version) parts = [parsed.major, parsed.minor, parsed.patch] diff --git a/tests/conftest.py b/tests/conftest.py index d2773c95d56..9481c31457b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,19 +34,19 @@ class Config(BaseConfig): - def get(self, setting_name, default=None): # type: (str, Any) -> Any + def get(self, setting_name: str, default: Any = None) -> Any: self.merge(self._config_source.config) self.merge(self._auth_config_source.config) return super(Config, self).get(setting_name, default=default) - def raw(self): # type: () -> Dict[str, Any] + def raw(self) -> Dict[str, Any]: self.merge(self._config_source.config) self.merge(self._auth_config_source.config) return super(Config, self).raw() - def all(self): # type: () -> Dict[str, Any] + def all(self) -> Dict[str, Any]: self.merge(self._config_source.config) self.merge(self._auth_config_source.config) diff --git a/tests/console/commands/env/helpers.py b/tests/console/commands/env/helpers.py index 0c65e4f14fb..4f8b7a4d818 100644 --- a/tests/console/commands/env/helpers.py +++ b/tests/console/commands/env/helpers.py @@ -6,8 +6,8 @@ def build_venv( - path, executable=None, flags=None -): # type: (Union[Path,str], Optional[str], bool) -> () + path: Union[Path, str], executable: Optional[str] = None, flags: bool = None +) -> (): Path(path).mkdir(parents=True, exist_ok=True) diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py index 442a0123284..087c3f2088b 100644 --- a/tests/console/commands/test_init.py +++ b/tests/console/commands/test_init.py @@ -15,7 +15,7 @@ @pytest.fixture -def source_dir(tmp_path): # type: (...) -> Path +def source_dir(tmp_path) -> Path: cwd = os.getcwd() try: diff --git a/tests/console/commands/test_lock.py b/tests/console/commands/test_lock.py index 51e56d155ca..f3ed49bb76f 100644 --- a/tests/console/commands/test_lock.py +++ b/tests/console/commands/test_lock.py @@ -7,7 +7,7 @@ @pytest.fixture -def source_dir(tmp_path): # type: (Path) -> Path +def source_dir(tmp_path: Path) -> Path: yield Path(tmp_path.as_posix()) diff --git a/tests/inspection/test_info.py b/tests/inspection/test_info.py index a1edea4a39b..3a15e605a6f 100644 --- a/tests/inspection/test_info.py +++ b/tests/inspection/test_info.py @@ -21,22 +21,22 @@ def pep517_metadata_mock(): @pytest.fixture -def demo_sdist(): # type: () -> Path +def demo_sdist() -> Path: return FIXTURE_DIR_BASE / "distributions" / "demo-0.1.0.tar.gz" @pytest.fixture -def demo_wheel(): # type: () -> Path +def demo_wheel() -> Path: return FIXTURE_DIR_BASE / "distributions" / "demo-0.1.0-py2.py3-none-any.whl" @pytest.fixture -def source_dir(tmp_path): # type: (Path) -> Path +def source_dir(tmp_path: Path) -> Path: yield Path(tmp_path.as_posix()) @pytest.fixture -def demo_setup(source_dir): # type: (Path) -> Path +def demo_setup(source_dir: Path) -> Path: setup_py = source_dir / "setup.py" setup_py.write_text( decode( @@ -50,7 +50,7 @@ def demo_setup(source_dir): # type: (Path) -> Path @pytest.fixture -def demo_setup_cfg(source_dir): # type: (Path) -> Path +def demo_setup_cfg(source_dir: Path) -> Path: setup_cfg = source_dir / "setup.cfg" setup_cfg.write_text( decode( @@ -69,7 +69,7 @@ def demo_setup_cfg(source_dir): # type: (Path) -> Path @pytest.fixture -def demo_setup_complex(source_dir): # type: (Path) -> Path +def demo_setup_complex(source_dir: Path) -> Path: setup_py = source_dir / "setup.py" setup_py.write_text( decode( @@ -83,7 +83,7 @@ def demo_setup_complex(source_dir): # type: (Path) -> Path @pytest.fixture -def demo_setup_complex_pep517_legacy(demo_setup_complex): # type: (Path) -> Path +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]\n" 'requires = ["setuptools", "wheel"]') @@ -91,7 +91,7 @@ def demo_setup_complex_pep517_legacy(demo_setup_complex): # type: (Path) -> Pat yield demo_setup_complex -def demo_check_info(info, requires_dist=None): # type: (PackageInfo, Set[str]) -> None +def demo_check_info(info: PackageInfo, requires_dist: Set[str] = None) -> None: assert info.name == "demo" assert info.version == "0.1.0" assert info.requires_dist diff --git a/tests/repositories/test_installed_repository.py b/tests/repositories/test_installed_repository.py index 5097d3bc4a0..3fcb7449cf7 100644 --- a/tests/repositories/test_installed_repository.py +++ b/tests/repositories/test_installed_repository.py @@ -47,12 +47,12 @@ def sys_path(self): @pytest.fixture -def env(): # type: () -> MockEnv +def env() -> MockEnv: return MockEnv(path=ENV_DIR) @pytest.fixture -def repository(mocker, env): # type: (MockFixture, MockEnv) -> InstalledRepository +def repository(mocker: MockFixture, env: MockEnv) -> InstalledRepository: mocker.patch( "poetry.utils._compat.metadata.Distribution.discover", return_value=INSTALLED_RESULTS, @@ -73,8 +73,8 @@ def repository(mocker, env): # type: (MockFixture, MockEnv) -> InstalledReposit def get_package_from_repository( - name, repository -): # type: (str, InstalledRepository) -> Optional[Package] + name: str, repository: InstalledRepository +) -> Optional[Package]: for pkg in repository.packages: if pkg.name == name: return pkg diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index da810307a9f..dc65d6dce23 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -118,8 +118,8 @@ def test_env_get_venv_with_venv_folder_present( def build_venv( - path, executable=None, flags=None -): # type: (Union[Path,str], Optional[str], bool) -> () + path: Union[Path, str], executable: Optional[str] = None, flags: bool = None +) -> (): os.mkdir(str(path)) From 73437a847e5dd7e23e1dff2774637c2437d40c7f Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Mon, 8 Feb 2021 15:24:10 +0100 Subject: [PATCH 085/222] Fixed propagation of markers to dependencies. Fixes #3254 --- poetry/packages/locker.py | 6 +- tests/utils/test_exporter.py | 196 ++++++++++++++++++++++++++++++++++- 2 files changed, 198 insertions(+), 4 deletions(-) diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index 48d68e950ac..f06d36d6caf 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -269,9 +269,9 @@ def __walk_dependency_level( if key not in nested_dependencies: nested_dependencies[key] = requirement else: - nested_dependencies[key].marker = nested_dependencies[ - key - ].marker.intersect(requirement.marker) + nested_dependencies[key].marker = nested_dependencies[key].marker.union( + requirement.marker + ) return cls.__walk_dependency_level( dependencies=next_level_dependencies, diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index 5fed10d1c99..5491ed7e3fa 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -175,6 +175,200 @@ def test_exporter_can_export_requirements_txt_with_standard_packages_and_markers assert expected == content +def test_exporter_can_export_requirements_txt_poetry(tmp_dir, poetry): + """Regression test for #3254""" + + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "poetry", + "version": "1.1.4", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"keyring": "*"}, + }, + { + "name": "junit-xml", + "version": "1.9", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"six": "*"}, + }, + { + "name": "keyring", + "version": "21.8.0", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": { + "SecretStorage": { + "version": "*", + "markers": "sys_platform == 'linux'", + } + }, + }, + { + "name": "secretstorage", + "version": "3.3.0", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"cryptography": "*"}, + }, + { + "name": "cryptography", + "version": "3.2", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"six": "*"}, + }, + { + "name": "six", + "version": "1.15.0", + "category": "main", + "optional": False, + "python-versions": "*", + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": { + "poetry": [], + "keyring": [], + "secretstorage": [], + "cryptography": [], + "six": [], + "junit-xml": [], + }, + }, + } + ) + set_package_requires( + poetry, skip={"keyring", "secretstorage", "cryptography", "six"} + ) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + # The dependency graph: + # junit-xml 1.9 Creates JUnit XML test result documents that can be read by tools such as Jenkins + # └── six * + # poetry 1.1.4 Python dependency management and packaging made easy. + # ├── keyring >=21.2.0,<22.0.0 + # │ ├── importlib-metadata >=1 + # │ │ └── zipp >=0.5 + # │ ├── jeepney >=0.4.2 + # │ ├── pywin32-ctypes <0.1.0 || >0.1.0,<0.1.1 || >0.1.1 + # │ └── secretstorage >=3.2 -- On linux only + # │ ├── cryptography >=2.0 + # │ │ └── six >=1.4.1 + # │ └── jeepney >=0.6 (circular dependency aborted here) + expected = { + "poetry": dependency_from_pep_508("poetry==1.1.4"), + "junit-xml": dependency_from_pep_508("junit-xml==1.9"), + "keyring": dependency_from_pep_508("keyring==21.8.0"), + "secretstorage": dependency_from_pep_508( + "secretstorage==3.3.0; sys_platform=='linux'" + ), + "cryptography": dependency_from_pep_508( + "cryptography==3.2; sys_platform=='linux'" + ), + "six": dependency_from_pep_508("six==1.15.0"), + } + + for line in content.strip().split("\n"): + dependency = dependency_from_pep_508(line) + assert dependency.name in expected + expected_dependency = expected.pop(dependency.name) + assert dependency == expected_dependency + print(dependency.marker) + print(expected_dependency.marker) + assert dependency.marker == expected_dependency.marker + + +def test_exporter_can_export_requirements_txt_pyinstaller(tmp_dir, poetry): + """Regression test for #3254""" + + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "pyinstaller", + "version": "4.0", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": { + "altgraph": "*", + "macholib": { + "version": "*", + "markers": "sys_platform == 'darwin'", + }, + }, + }, + { + "name": "altgraph", + "version": "0.17", + "category": "main", + "optional": False, + "python-versions": "*", + }, + { + "name": "macholib", + "version": "1.8", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"altgraph": ">=0.15"}, + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"pyinstaller": [], "altgraph": [], "macholib": []}, + }, + } + ) + set_package_requires(poetry, skip={"altgraph", "macholib"}) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + # Rationale for the results: + # * PyInstaller has an explicit dependency on altgraph, so it must always be installed. + # * PyInstaller requires macholib on Darwin, which in turn requires altgraph. + # The dependency graph: + # pyinstaller 4.0 PyInstaller bundles a Python application and all its dependencies into a single package. + # ├── altgraph * + # ├── macholib >=1.8 -- only on Darwin + # │ └── altgraph >=0.15 + expected = { + "pyinstaller": dependency_from_pep_508("pyinstaller==4.0"), + "altgraph": dependency_from_pep_508("altgraph==0.17"), + "macholib": dependency_from_pep_508("macholib==1.8; sys_platform == 'darwin'"), + } + + for line in content.strip().split("\n"): + dependency = dependency_from_pep_508(line) + assert dependency.name in expected + expected_dependency = expected.pop(dependency.name) + assert dependency == expected_dependency + assert dependency.marker == expected_dependency.marker + + def test_exporter_can_export_requirements_txt_with_nested_packages_and_markers( tmp_dir, poetry ): @@ -241,7 +435,7 @@ def test_exporter_can_export_requirements_txt_with_nested_packages_and_markers( "c==7.8.9; sys_platform == 'win32' and python_version < '3.7'" ), "d": dependency_from_pep_508( - "d==0.0.1; python_version < '3.7' and platform_system == 'Windows' and sys_platform == 'win32'" + "d==0.0.1; platform_system == 'Windows' and python_version < '3.7' or sys_platform == 'win32' and python_version < '3.7'" ), } From ae60ac58d4b52a2ccf6595a0c9d8e6d8d6eee090 Mon Sep 17 00:00:00 2001 From: 0xflotus <0xflotus@gmail.com> Date: Wed, 10 Feb 2021 00:53:55 +0100 Subject: [PATCH 086/222] docs: fix typo in basic usage --- docs/docs/basic-usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/basic-usage.md b/docs/docs/basic-usage.md index eed4817379f..c9805bb3996 100644 --- a/docs/docs/basic-usage.md +++ b/docs/docs/basic-usage.md @@ -106,7 +106,7 @@ To deactivate the virtual environment without leaving the shell use `deactivate` that an activated virtual environment remains active after the Poetry command has completed execution. - Therefore, Poetry has to create a sub-shell with the virtual envrionment activated + Therefore, Poetry has to create a sub-shell with the virtual environment activated in order for the subsequent commands to run from within the virtual environment. From bc3ad7db771bfeabf1fcea6c9b402c279ff54866 Mon Sep 17 00:00:00 2001 From: pwoolvett Date: Tue, 9 Feb 2021 20:58:10 -0300 Subject: [PATCH 087/222] fix(shell): quote path before activating env Resolves: #3630 --- poetry/utils/shell.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/poetry/utils/shell.py b/poetry/utils/shell.py index dbf1db3fb8c..544be053c4c 100644 --- a/poetry/utils/shell.py +++ b/poetry/utils/shell.py @@ -65,6 +65,8 @@ def activate(self, env: VirtualEnv) -> None: if WINDOWS: return env.execute(self.path) + import shlex + terminal = Terminal() with env.temp_environ(): c = pexpect.spawn( @@ -77,7 +79,9 @@ def activate(self, env: VirtualEnv) -> None: activate_script = self._get_activate_script() bin_dir = "Scripts" if WINDOWS else "bin" activate_path = env.path / bin_dir / activate_script - c.sendline("{} {}".format(self._get_source_command(), activate_path)) + c.sendline( + "{} {}".format(self._get_source_command(), shlex.quote(str(activate_path))) + ) def resize(sig: Any, data: Any) -> None: terminal = Terminal() From 25771b4d27e6ba4926f52aea876a7d8dd35f7574 Mon Sep 17 00:00:00 2001 From: Aykut Yilmaz Date: Wed, 10 Feb 2021 01:01:41 +0100 Subject: [PATCH 088/222] doc: fix changelog typo for release 1.1.4 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 168944d8f4b..e7fe3d6e588 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1084,7 +1084,7 @@ Initial release [Unreleased]: https://github.com/python-poetry/poetry/compare/1.1.4...master -[1.1.3]: https://github.com/python-poetry/poetry/compare/1.1.4 +[1.1.4]: https://github.com/python-poetry/poetry/compare/1.1.4 [1.1.3]: https://github.com/python-poetry/poetry/compare/1.1.3 [1.1.2]: https://github.com/python-poetry/poetry/releases/tag/1.1.2 [1.1.1]: https://github.com/python-poetry/poetry/releases/tag/1.1.1 From 6c92d42af70e1ca60f8e55599866ca8e609659ad Mon Sep 17 00:00:00 2001 From: Christoph Reiter Date: Wed, 10 Feb 2021 02:04:02 +0100 Subject: [PATCH 089/222] Bump pkginfo to ^1.5 Fixes #3362 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 562bfe68901..1f6ec062e7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ requests = "^2.18" cachy = "^0.3.0" requests-toolbelt = "^0.9.1" cachecontrol = { version = "^0.12.4", extras = ["filecache"] } -pkginfo = "^1.4" +pkginfo = "^1.5" html5lib = "^1.0" shellingham = "^1.1" tomlkit = ">=0.7.0,<1.0.0" From 6f70c3e257cdd97d41b82ba26267ee753230a39e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Wed, 10 Feb 2021 22:16:37 +0100 Subject: [PATCH 090/222] Upgrade poetry-core to 1.0.2 --- poetry.lock | 8 ++++---- poetry/mixology/incompatibility.py | 7 ++++++- pyproject.toml | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6a04096460f..c576b9e1eff 100644 --- a/poetry.lock +++ b/poetry.lock @@ -352,7 +352,7 @@ dev = ["pre-commit", "tox"] [[package]] name = "poetry-core" -version = "1.0.0" +version = "1.0.2" description = "Poetry PEP 517 Build Backend" category = "main" optional = false @@ -668,7 +668,7 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "c495920c853f794d4046d2d4cc47410ea076d4647e54fd3364e46051d5f18da3" +content-hash = "3061627bdc17958f2b0d1dfc86e448e167ff3e97639e6f3c8f67bf0b06d9995c" [metadata.files] appdirs = [ @@ -901,8 +901,8 @@ pluggy = [ {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] poetry-core = [ - {file = "poetry-core-1.0.0.tar.gz", hash = "sha256:6a664ff389b9f45382536f8fa1611a0cb4d2de7c5a5c885db1f0c600cd11fbd5"}, - {file = "poetry_core-1.0.0-py2.py3-none-any.whl", hash = "sha256:769288e0e1b88dfcceb3185728f0b7388b26d5f93d6c22d2dcae372da51d200d"}, + {file = "poetry-core-1.0.2.tar.gz", hash = "sha256:ff505d656a6cf40ffbf84393d8b5bf37b78523a15def3ac473b6fad74261ee71"}, + {file = "poetry_core-1.0.2-py2.py3-none-any.whl", hash = "sha256:ee0ed4164440eeab27d1b01bc7b9b3afdc3124f68d4ea28d0821a402a9c7c044"}, ] pre-commit = [ {file = "pre_commit-2.9.0-py2.py3-none-any.whl", hash = "sha256:4aee0db4808fa48d2458cedd5b9a084ef24dda1a0fa504432a11977a4d1cfd0a"}, diff --git a/poetry/mixology/incompatibility.py b/poetry/mixology/incompatibility.py index 615c0cec741..0a848d1374c 100644 --- a/poetry/mixology/incompatibility.py +++ b/poetry/mixology/incompatibility.py @@ -454,7 +454,12 @@ def _terse(self, term: Term, allow_every: bool = False) -> str: if allow_every and term.constraint.is_any(): return "every version of {}".format(term.dependency.complete_name) - return str(term.dependency) + if term.dependency.is_root: + return term.dependency.pretty_name + + return "{} ({})".format( + term.dependency.pretty_name, term.dependency.pretty_constraint + ) def _single_term_where(self, callable: callable) -> Optional[Term]: found = None diff --git a/pyproject.toml b/pyproject.toml index 1f6ec062e7b..716fd2f27aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ [tool.poetry.dependencies] python = "^3.6" -poetry-core = "^1.0.0" +poetry-core = "^1.0.2" cleo = "^1.0.0a1" crashtest = "^0.3.0" requests = "^2.18" From 74ec16905d74235f028a5e03686df5459a9bdcee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 12 Feb 2021 11:22:29 +0100 Subject: [PATCH 091/222] Update Cirrus config --- .cirrus.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cirrus.yml b/.cirrus.yml index daf4efc9203..e771b621abf 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,5 +1,5 @@ freebsd_instance: - image_family: freebsd-12-1-snap + image_family: freebsd-12-2 test_task: name: "Tests / FreeBSD / " From 6eaa2984d9fb51a837c4ab11643f4dd9d7828d4d Mon Sep 17 00:00:00 2001 From: Paul Sanders Date: Sat, 13 Feb 2021 14:58:00 -0500 Subject: [PATCH 092/222] Refactoring uploader into a fixture (#3679) * Relates-to: #3155 converting duplicted code to fixture --- tests/console/commands/debug/test_resolve.py | 2 +- tests/publishing/test_uploader.py | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/console/commands/debug/test_resolve.py b/tests/console/commands/debug/test_resolve.py index f3f0db24c6e..9af3e0010d8 100644 --- a/tests/console/commands/debug/test_resolve.py +++ b/tests/console/commands/debug/test_resolve.py @@ -10,7 +10,7 @@ def tester(command_tester_factory): @pytest.fixture(autouse=True) -def _add_packages(repo): +def __add_packages(repo): cachy020 = get_package("cachy", "0.2.0") cachy020.add_dependency(Factory.create_dependency("msgpack-python", ">=0.5 <0.6")) diff --git a/tests/publishing/test_uploader.py b/tests/publishing/test_uploader.py index 8f5ec1b9e7a..e4deb372884 100644 --- a/tests/publishing/test_uploader.py +++ b/tests/publishing/test_uploader.py @@ -16,9 +16,13 @@ def project(name): return fixtures_dir / name -def test_uploader_properly_handles_400_errors(http): +@pytest.fixture +def uploader(): + return Uploader(Factory().create_poetry(project("simple_project")), NullIO()) + + +def test_uploader_properly_handles_400_errors(http, uploader): http.register_uri(http.POST, "https://foo.com", status=400, body="Bad request") - uploader = Uploader(Factory().create_poetry(project("simple_project")), NullIO()) with pytest.raises(UploadError) as e: uploader.upload("https://foo.com") @@ -26,9 +30,8 @@ def test_uploader_properly_handles_400_errors(http): assert "HTTP Error 400: Bad Request" == str(e.value) -def test_uploader_properly_handles_403_errors(http): +def test_uploader_properly_handles_403_errors(http, uploader): http.register_uri(http.POST, "https://foo.com", status=403, body="Unauthorized") - uploader = Uploader(Factory().create_poetry(project("simple_project")), NullIO()) with pytest.raises(UploadError) as e: uploader.upload("https://foo.com") @@ -36,9 +39,8 @@ def test_uploader_properly_handles_403_errors(http): assert "HTTP Error 403: Forbidden" == str(e.value) -def test_uploader_properly_handles_301_redirects(http): +def test_uploader_properly_handles_301_redirects(http, uploader): http.register_uri(http.POST, "https://foo.com", status=301, body="Redirect") - uploader = Uploader(Factory().create_poetry(project("simple_project")), NullIO()) with pytest.raises(UploadError) as e: uploader.upload("https://foo.com") @@ -48,12 +50,11 @@ def test_uploader_properly_handles_301_redirects(http): ) -def test_uploader_registers_for_appropriate_400_errors(mocker, http): +def test_uploader_registers_for_appropriate_400_errors(mocker, http, uploader): register = mocker.patch("poetry.publishing.uploader.Uploader._register") http.register_uri( http.POST, "https://foo.com", status=400, body="No package was ever registered" ) - uploader = Uploader(Factory().create_poetry(project("simple_project")), NullIO()) with pytest.raises(UploadError): uploader.upload("https://foo.com") From adc3cde3473e85eb744f1f73d937439a681ae365 Mon Sep 17 00:00:00 2001 From: Akhilesh Raju Date: Sat, 6 Mar 2021 00:23:51 -0800 Subject: [PATCH 093/222] Doc: Update "extras" section in pyproject.toml file Resolves: #1076 --- docs/docs/pyproject.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/docs/pyproject.md b/docs/docs/pyproject.md index 14221b8b03a..bdcc6e0bc49 100644 --- a/docs/docs/pyproject.md +++ b/docs/docs/pyproject.md @@ -243,15 +243,32 @@ mysqlclient = { version = "^1.3", optional = true } [tool.poetry.extras] mysql = ["mysqlclient"] pgsql = ["psycopg2"] +databases = ["mysqlclient", "psycopg2"] ``` -When installing packages, you can specify extras by using the `-E|--extras` option: +When installing packages with Poetry, you can specify extras by using the `-E|--extras` option: ```bash poetry install --extras "mysql pgsql" poetry install -E mysql -E pgsql ``` +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). + +For example, when installing the package using `pip`, the dependencies required by +the `databases` extra can be installed as shown below. + +```bash +pip install awesome[databases] +``` + +!!!note + + The dependencies specified for each `extra` must already be defined as project dependencies. + Dependencies listed in the `dev-dependencies` section cannot be specified as extras. + + ## `plugins` Poetry supports arbitrary plugins which work similarly to From 68f446769b704db5cfebb997bad3c31a569229b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 5 Feb 2021 15:38:54 +0100 Subject: [PATCH 094/222] Update poetry-core --- poetry.lock | 20 +++++---- poetry/console/commands/add.py | 2 +- poetry/console/commands/build.py | 2 +- poetry/console/commands/config.py | 7 ++-- poetry/console/commands/init.py | 10 ++--- poetry/console/commands/new.py | 2 +- poetry/console/commands/self/update.py | 22 ++++------ poetry/console/commands/show.py | 7 ++-- poetry/console/commands/version.py | 4 +- poetry/inspection/info.py | 10 ++--- poetry/installation/base_installer.py | 2 +- poetry/installation/noop_installer.py | 2 +- poetry/installation/operations/install.py | 2 +- poetry/installation/operations/operation.py | 4 +- poetry/installation/operations/uninstall.py | 2 +- poetry/installation/operations/update.py | 2 +- poetry/installation/pip_installer.py | 6 +-- poetry/mixology/__init__.py | 2 +- poetry/mixology/assignment.py | 4 +- poetry/mixology/failure.py | 2 +- poetry/mixology/partial_solution.py | 4 +- poetry/mixology/result.py | 4 +- .../solutions/python_requirement_solution.py | 2 +- poetry/mixology/term.py | 5 +-- poetry/mixology/version_solver.py | 7 ++-- poetry/packages/locker.py | 5 +-- poetry/packages/package_collection.py | 4 +- poetry/poetry.py | 36 ++++++++-------- poetry/puzzle/provider.py | 12 +++--- poetry/puzzle/solver.py | 12 +++--- poetry/repositories/base_repository.py | 4 +- poetry/repositories/installed_repository.py | 2 +- poetry/repositories/legacy_repository.py | 12 +++--- poetry/repositories/pool.py | 4 +- poetry/repositories/pypi_repository.py | 41 ++++++++++++------- poetry/repositories/repository.py | 14 +++---- poetry/utils/env.py | 2 +- poetry/utils/extras.py | 11 +++-- poetry/utils/setup_reader.py | 2 +- poetry/version/version_selector.py | 4 +- pyproject.toml | 2 +- tests/console/commands/env/helpers.py | 2 +- tests/console/commands/env/test_use.py | 2 +- tests/console/commands/test_add.py | 5 +-- tests/console/commands/test_config.py | 2 +- tests/helpers.py | 6 +-- tests/installation/test_installer.py | 2 +- tests/installation/test_installer_old.py | 2 +- tests/mixology/helpers.py | 2 +- tests/puzzle/test_provider.py | 2 +- tests/puzzle/test_solver.py | 12 +++--- .../repositories/test_installed_repository.py | 2 +- tests/repositories/test_legacy_repository.py | 2 +- tests/repositories/test_pypi_repository.py | 2 +- tests/utils/test_env.py | 2 +- tests/utils/test_exporter.py | 38 ++++++++--------- tests/utils/test_extras.py | 2 +- 57 files changed, 196 insertions(+), 192 deletions(-) diff --git a/poetry.lock b/poetry.lock index c576b9e1eff..f33411bc2d8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -352,14 +352,21 @@ dev = ["pre-commit", "tox"] [[package]] name = "poetry-core" -version = "1.0.2" +version = "1.1.0a0" description = "Poetry PEP 517 Build Backend" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "^3.6" +develop = false [package.dependencies] -importlib-metadata = {version = ">=1.7.0,<2.0.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.5\" and python_version < \"3.8\""} +importlib-metadata = {version = "^1.7.0", markers = "python_version >= \"3.5\" and python_version < \"3.8\""} + +[package.source] +type = "git" +url = "https://github.com/python-poetry/poetry-core" +reference = "master" +resolved_reference = "d0b8f3ff1d2ec94e317d8ec20920f32a5d4992ef" [[package]] name = "pre-commit" @@ -668,7 +675,7 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "3061627bdc17958f2b0d1dfc86e448e167ff3e97639e6f3c8f67bf0b06d9995c" +content-hash = "0039f039cc6768e38203b79e8aee64fa11a96b781da515b68587b90c97ed048a" [metadata.files] appdirs = [ @@ -900,10 +907,7 @@ pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] -poetry-core = [ - {file = "poetry-core-1.0.2.tar.gz", hash = "sha256:ff505d656a6cf40ffbf84393d8b5bf37b78523a15def3ac473b6fad74261ee71"}, - {file = "poetry_core-1.0.2-py2.py3-none-any.whl", hash = "sha256:ee0ed4164440eeab27d1b01bc7b9b3afdc3124f68d4ea28d0821a402a9c7c044"}, -] +poetry-core = [] pre-commit = [ {file = "pre_commit-2.9.0-py2.py3-none-any.whl", hash = "sha256:4aee0db4808fa48d2458cedd5b9a084ef24dda1a0fa504432a11977a4d1cfd0a"}, {file = "pre_commit-2.9.0.tar.gz", hash = "sha256:b2d106d51c6ba6217e859d81774aae33fd825fe7de0dcf0c46e2586333d7a92e"}, diff --git a/poetry/console/commands/add.py b/poetry/console/commands/add.py index af5e0837827..adfe4d9f062 100644 --- a/poetry/console/commands/add.py +++ b/poetry/console/commands/add.py @@ -71,7 +71,7 @@ class AddCommand(InstallerCommand, InitCommand): def handle(self) -> int: from tomlkit import inline_table - from poetry.core.semver import parse_constraint + from poetry.core.semver.helpers import parse_constraint packages = self.argument("name") is_dev = self.option("dev") diff --git a/poetry/console/commands/build.py b/poetry/console/commands/build.py index 7d0eda85e9b..c12fe9ab13d 100644 --- a/poetry/console/commands/build.py +++ b/poetry/console/commands/build.py @@ -19,7 +19,7 @@ class BuildCommand(EnvCommand): ] def handle(self) -> None: - from poetry.core.masonry import Builder + from poetry.core.masonry.builder import Builder fmt = "all" if self.option("format"): diff --git a/poetry/console/commands/config.py b/poetry/console/commands/config.py index 798b700fd45..1f008195444 100644 --- a/poetry/console/commands/config.py +++ b/poetry/console/commands/config.py @@ -11,10 +11,6 @@ from cleo.helpers import argument from cleo.helpers import option -from poetry.core.pyproject import PyProjectException -from poetry.core.toml.file import TOMLFile -from poetry.factory import Factory - from .command import Command @@ -94,6 +90,9 @@ def handle(self) -> Optional[int]: from pathlib import Path from poetry.config.file_config_source import FileConfigSource + from poetry.core.pyproject.exceptions import PyProjectException + from poetry.core.toml.file import TOMLFile + from poetry.factory import Factory from poetry.locations import CONFIG_DIR config = Factory.create_config(self.io) diff --git a/poetry/console/commands/init.py b/poetry/console/commands/init.py index fbc94b9358c..2e10f2ead01 100644 --- a/poetry/console/commands/init.py +++ b/poetry/console/commands/init.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os import re import sys @@ -17,9 +14,6 @@ from cleo.helpers import option from tomlkit import inline_table -from poetry.core.pyproject import PyProjectException -from poetry.core.pyproject.toml import PyProjectTOML - from .command import Command from .env_command import EnvCommand @@ -70,6 +64,7 @@ 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.layouts import layout from poetry.utils.env import SystemEnv @@ -384,6 +379,7 @@ def _find_best_version_for_package( return package.pretty_name, selector.find_recommended_require_version(package) def _parse_requirements(self, requirements: List[str]) -> List[Dict[str, str]]: + from poetry.core.pyproject.exceptions import PyProjectException from poetry.puzzle.provider import Provider result = [] @@ -534,7 +530,7 @@ def _validate_author(self, author: str, default: str) -> Optional[str]: return author def _validate_license(self, license: str) -> str: - from poetry.core.spdx import license_by_id + from poetry.core.spdx.helpers import license_by_id if license: license_by_id(license) diff --git a/poetry/console/commands/new.py b/poetry/console/commands/new.py index e80e589d39c..04c3a8ba653 100644 --- a/poetry/console/commands/new.py +++ b/poetry/console/commands/new.py @@ -22,7 +22,7 @@ class NewCommand(Command): def handle(self) -> None: from pathlib import Path - from poetry.core.semver import parse_constraint + from poetry.core.semver.helpers import parse_constraint from poetry.core.vcs.git import GitConfig from poetry.layouts import layout from poetry.utils.env import SystemEnv diff --git a/poetry/console/commands/self/update.py b/poetry/console/commands/self/update.py index 06ef628b786..95e8f5dce6a 100644 --- a/poetry/console/commands/self/update.py +++ b/poetry/console/commands/self/update.py @@ -14,27 +14,18 @@ from pathlib import Path from typing import TYPE_CHECKING from typing import Any +from urllib.error import HTTPError +from urllib.request import urlopen from cleo.helpers import argument from cleo.helpers import option -from poetry.console.exceptions import PoetrySimpleConsoleException -from poetry.core.packages import Dependency - from ..command import Command if TYPE_CHECKING: - from poetry.core.packages import Package - from poetry.core.semver import Version - - -try: - from urllib.error import HTTPError - from urllib.request import urlopen -except ImportError: - from urllib2 import HTTPError - from urllib2 import urlopen + from poetry.core.packages.package import Package + from poetry.core.semver.version import Version BIN = """# -*- coding: utf-8 -*- @@ -89,7 +80,8 @@ def lib_backup(self) -> Path: def handle(self) -> None: from poetry.__version__ import __version__ - from poetry.core.semver import Version + from poetry.core.packages.dependency import Dependency + from poetry.core.semver.version import Version from poetry.repositories.pypi_repository import PyPiRepository self._check_recommended_installation() @@ -250,6 +242,8 @@ def process(self, *args: Any) -> str: def _check_recommended_installation(self) -> None: from pathlib import Path + from poetry.console.exceptions import PoetrySimpleConsoleException + current = Path(__file__) try: current.relative_to(self.home) diff --git a/poetry/console/commands/show.py b/poetry/console/commands/show.py index e07b97e13a6..042f4163c8a 100644 --- a/poetry/console/commands/show.py +++ b/poetry/console/commands/show.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from typing import TYPE_CHECKING from typing import List from typing import Optional @@ -13,8 +12,8 @@ if TYPE_CHECKING: from cleo.io.io import IO # noqa - from poetry.core.packages import Dependency - from poetry.core.packages import Package + from poetry.core.packages.dependency import Dependency + from poetry.core.packages.package import Package from poetry.repositories import Repository from poetry.repositories.installed_repository import InstalledRepository @@ -408,7 +407,7 @@ def find_latest_package( return selector.find_best_candidate(name, ">={}".format(package.pretty_version)) def get_update_status(self, latest: "Package", package: "Package") -> str: - from poetry.core.semver import parse_constraint + from poetry.core.semver.helpers import parse_constraint if latest.full_pretty_version == package.full_pretty_version: return "up-to-date" diff --git a/poetry/console/commands/version.py b/poetry/console/commands/version.py index cffe1b0f723..4a6d5870924 100644 --- a/poetry/console/commands/version.py +++ b/poetry/console/commands/version.py @@ -7,7 +7,7 @@ if TYPE_CHECKING: - from poetry.core.semver import Version + from poetry.core.semver.version import Version class VersionCommand(Command): @@ -79,7 +79,7 @@ def handle(self) -> None: ) def increment_version(self, version: str, rule: str) -> "Version": - from poetry.core.semver import Version + from poetry.core.semver.version import Version try: version = Version.parse(version) diff --git a/poetry/inspection/info.py b/poetry/inspection/info.py index 1814519d240..4ccaf429d6d 100644 --- a/poetry/inspection/info.py +++ b/poetry/inspection/info.py @@ -14,9 +14,9 @@ import pkginfo from poetry.core.factory import Factory -from poetry.core.packages import Package -from poetry.core.packages import ProjectPackage -from poetry.core.packages import dependency_from_pep_508 +from poetry.core.packages.dependency import Dependency +from poetry.core.packages.package import Package +from poetry.core.packages.project_package import ProjectPackage from poetry.core.pyproject.toml import PyProjectTOML from poetry.core.utils.helpers import parse_requires from poetry.core.utils.helpers import temporary_directory @@ -170,11 +170,11 @@ def to_package( for req in self.requires_dist or []: try: # Attempt to parse the PEP-508 requirement string - dependency = dependency_from_pep_508(req, relative_to=root_dir) + dependency = Dependency.create_from_pep_508(req, relative_to=root_dir) except InvalidMarker: # Invalid marker, We strip the markers hoping for the best req = req.split(";")[0] - dependency = dependency_from_pep_508(req, relative_to=root_dir) + dependency = Dependency.create_from_pep_508(req, relative_to=root_dir) except ValueError: # Likely unable to parse constraint so we skip it self._log( diff --git a/poetry/installation/base_installer.py b/poetry/installation/base_installer.py index c377dea7058..ecb6dadf3df 100644 --- a/poetry/installation/base_installer.py +++ b/poetry/installation/base_installer.py @@ -2,7 +2,7 @@ if TYPE_CHECKING: - from poetry.core.packages import Package + from poetry.core.packages.package import Package class BaseInstaller: diff --git a/poetry/installation/noop_installer.py b/poetry/installation/noop_installer.py index 3ef7dab543c..fe0a01e29d4 100644 --- a/poetry/installation/noop_installer.py +++ b/poetry/installation/noop_installer.py @@ -5,7 +5,7 @@ if TYPE_CHECKING: - from poetry.core.packages import Package + from poetry.core.packages.package import Package class NoopInstaller(BaseInstaller): diff --git a/poetry/installation/operations/install.py b/poetry/installation/operations/install.py index b83f449bd45..621ff4a37dc 100644 --- a/poetry/installation/operations/install.py +++ b/poetry/installation/operations/install.py @@ -5,7 +5,7 @@ if TYPE_CHECKING: - from poetry.core.packages import Package + from poetry.core.packages.package import Package class Install(Operation): diff --git a/poetry/installation/operations/operation.py b/poetry/installation/operations/operation.py index e001cf97608..581b62959cd 100644 --- a/poetry/installation/operations/operation.py +++ b/poetry/installation/operations/operation.py @@ -1,11 +1,9 @@ -# -*- coding: utf-8 -*- - from typing import TYPE_CHECKING from typing import Optional if TYPE_CHECKING: - from poetry.core.packages import Package + from poetry.core.packages.package import Package class Operation(object): diff --git a/poetry/installation/operations/uninstall.py b/poetry/installation/operations/uninstall.py index 12a163a3367..d9f41055d1a 100644 --- a/poetry/installation/operations/uninstall.py +++ b/poetry/installation/operations/uninstall.py @@ -5,7 +5,7 @@ if TYPE_CHECKING: - from poetry.core.packages import Package + from poetry.core.packages.package import Package class Uninstall(Operation): diff --git a/poetry/installation/operations/update.py b/poetry/installation/operations/update.py index c1f33fad30f..26ca853df05 100644 --- a/poetry/installation/operations/update.py +++ b/poetry/installation/operations/update.py @@ -5,7 +5,7 @@ if TYPE_CHECKING: - from poetry.core.packages import Package + from poetry.core.packages.package import Package class Update(Operation): diff --git a/poetry/installation/pip_installer.py b/poetry/installation/pip_installer.py index e84774d39ca..094fe23825b 100644 --- a/poetry/installation/pip_installer.py +++ b/poetry/installation/pip_installer.py @@ -19,7 +19,7 @@ if TYPE_CHECKING: - from poetry.core.packages import Package + from poetry.core.packages.package import Package class PipInstaller(BaseInstaller): @@ -242,8 +242,8 @@ def install_directory(self, package: "Package") -> Union[str, int]: return self.run(*args) def install_git(self, package: "Package") -> None: - from poetry.core.packages import Package - from poetry.core.vcs import Git + from poetry.core.packages.package import Package + from poetry.core.vcs.git import Git src_dir = self._env.path / "src" / package.name if src_dir.exists(): diff --git a/poetry/mixology/__init__.py b/poetry/mixology/__init__.py index 09b3708b6c5..8cf422203a9 100644 --- a/poetry/mixology/__init__.py +++ b/poetry/mixology/__init__.py @@ -6,7 +6,7 @@ if TYPE_CHECKING: - from poetry.core.packages import ProjectPackage + from poetry.core.packages.project_package import ProjectPackage from poetry.packages import DependencyPackage from poetry.puzzle.provider import Provider diff --git a/poetry/mixology/assignment.py b/poetry/mixology/assignment.py index c90cd837271..836c1895b66 100644 --- a/poetry/mixology/assignment.py +++ b/poetry/mixology/assignment.py @@ -6,8 +6,8 @@ if TYPE_CHECKING: - from poetry.core.packages import Dependency - from poetry.core.packages import Package + from poetry.core.packages.dependency import Dependency + from poetry.core.packages.package import Package from .incompatibility import Incompatibility diff --git a/poetry/mixology/failure.py b/poetry/mixology/failure.py index a4707c93342..55a163c0e5f 100644 --- a/poetry/mixology/failure.py +++ b/poetry/mixology/failure.py @@ -3,7 +3,7 @@ from typing import Optional from typing import Tuple -from poetry.core.semver import parse_constraint +from poetry.core.semver.helpers import parse_constraint from .incompatibility import Incompatibility from .incompatibility_cause import ConflictCause diff --git a/poetry/mixology/partial_solution.py b/poetry/mixology/partial_solution.py index b9f36ec13fd..f06c65de792 100644 --- a/poetry/mixology/partial_solution.py +++ b/poetry/mixology/partial_solution.py @@ -9,8 +9,8 @@ if TYPE_CHECKING: - from poetry.core.packages import Dependency - from poetry.core.packages import Package + from poetry.core.packages.dependency import Dependency + from poetry.core.packages.package import Package class PartialSolution: diff --git a/poetry/mixology/result.py b/poetry/mixology/result.py index 6f83b01825f..d2ef1a2dade 100644 --- a/poetry/mixology/result.py +++ b/poetry/mixology/result.py @@ -3,8 +3,8 @@ if TYPE_CHECKING: - from poetry.core.packages import Package - from poetry.core.packages import ProjectPackage + from poetry.core.packages.package import Package + from poetry.core.packages.project_package import ProjectPackage class SolverResult: diff --git a/poetry/mixology/solutions/solutions/python_requirement_solution.py b/poetry/mixology/solutions/solutions/python_requirement_solution.py index 764c8f7fa68..ed6e2cbd974 100644 --- a/poetry/mixology/solutions/solutions/python_requirement_solution.py +++ b/poetry/mixology/solutions/solutions/python_requirement_solution.py @@ -10,7 +10,7 @@ class PythonRequirementSolution(Solution): def __init__(self, exception: "PackageNotFoundCause") -> None: - from poetry.core.semver import parse_constraint + from poetry.core.semver.helpers import parse_constraint from poetry.mixology.incompatibility_cause import PythonCause self._title = "Check your dependencies Python requirement." diff --git a/poetry/mixology/term.py b/poetry/mixology/term.py index 751895e37c8..3b76578ba14 100644 --- a/poetry/mixology/term.py +++ b/poetry/mixology/term.py @@ -1,14 +1,13 @@ -# -*- coding: utf-8 -*- from typing import TYPE_CHECKING from typing import Optional -from poetry.core.packages import Dependency +from poetry.core.packages.dependency import Dependency from .set_relation import SetRelation if TYPE_CHECKING: - from poetry.core.semver import VersionTypes + from poetry.core.semver.helpers import VersionTypes class Term(object): diff --git a/poetry/mixology/version_solver.py b/poetry/mixology/version_solver.py index f2aab10f88a..f55231660f6 100644 --- a/poetry/mixology/version_solver.py +++ b/poetry/mixology/version_solver.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import time from typing import TYPE_CHECKING @@ -7,9 +6,9 @@ from typing import Optional from typing import Union -from poetry.core.packages import Dependency -from poetry.core.packages import Package -from poetry.core.packages import ProjectPackage +from poetry.core.packages.dependency import Dependency +from poetry.core.packages.package import Package +from poetry.core.packages.project_package import ProjectPackage from .failure import SolveFailure from .incompatibility import Incompatibility diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index f06d36d6caf..4f3ba725751 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -26,10 +26,9 @@ import poetry.repositories -from poetry.core.packages import dependency_from_pep_508 from poetry.core.packages.dependency import Dependency from poetry.core.packages.package import Package -from poetry.core.semver import parse_constraint +from poetry.core.semver.helpers import parse_constraint from poetry.core.semver.version import Version from poetry.core.toml.file import TOMLFile from poetry.core.version.markers import parse_marker @@ -148,7 +147,7 @@ def locked_repository( for dep in deps: try: - dependency = dependency_from_pep_508(dep) + dependency = Dependency.create_from_pep_508(dep) except InvalidRequirement: # handle lock files with invalid PEP 508 m = re.match(r"^(.+?)(?:\[(.+?)])?(?:\s+\((.+)\))?$", dep) diff --git a/poetry/packages/package_collection.py b/poetry/packages/package_collection.py index 7aa7b29c233..00e271dd6c6 100644 --- a/poetry/packages/package_collection.py +++ b/poetry/packages/package_collection.py @@ -6,8 +6,8 @@ if TYPE_CHECKING: - from poetry.core.packages import Dependency - from poetry.core.packages import Package + from poetry.core.packages.dependency import Dependency + from poetry.core.packages.package import Package class PackageCollection(list): diff --git a/poetry/poetry.py b/poetry/poetry.py index f5f46e1b968..5c248d46224 100644 --- a/poetry/poetry.py +++ b/poetry/poetry.py @@ -1,15 +1,17 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - from pathlib import Path +from typing import TYPE_CHECKING -from poetry.core.packages import ProjectPackage from poetry.core.poetry import Poetry as BasePoetry from .__version__ import __version__ -from .config.config import Config -from .packages import Locker -from .repositories.pool import Pool + + +if TYPE_CHECKING: + from poetry.core.packages.project_package import ProjectPackage + + from .config.config import Config + from .packages.locker import Locker + from .repositories.pool import Pool class Poetry(BasePoetry): @@ -20,10 +22,12 @@ def __init__( self, file: Path, local_config: dict, - package: ProjectPackage, - locker: Locker, - config: Config, + package: "ProjectPackage", + locker: "Locker", + config: "Config", ): + from .repositories.pool import Pool # noqa + super(Poetry, self).__init__(file, local_config, package) self._locker = locker @@ -31,28 +35,28 @@ def __init__( self._pool = Pool() @property - def locker(self) -> Locker: + def locker(self) -> "Locker": return self._locker @property - def pool(self) -> Pool: + def pool(self) -> "Pool": return self._pool @property - def config(self) -> Config: + def config(self) -> "Config": return self._config - def set_locker(self, locker: Locker) -> "Poetry": + def set_locker(self, locker: "Locker") -> "Poetry": self._locker = locker return self - def set_pool(self, pool: Pool) -> "Poetry": + def set_pool(self, pool: "Pool") -> "Poetry": self._pool = pool return self - def set_config(self, config: Config) -> "Poetry": + def set_config(self, config: "Config") -> "Poetry": self._config = config return self diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index 6c4823a8d69..d7576bb7a23 100644 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -16,13 +16,13 @@ from cleo.ui.progress_indicator import ProgressIndicator -from poetry.core.packages import Dependency -from poetry.core.packages import DirectoryDependency -from poetry.core.packages import FileDependency -from poetry.core.packages import Package -from poetry.core.packages import URLDependency -from poetry.core.packages import VCSDependency +from poetry.core.packages.dependency import Dependency +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.url_dependency import URLDependency from poetry.core.packages.utils.utils import get_python_constraint_from_marker +from poetry.core.packages.vcs_dependency import VCSDependency from poetry.core.semver.version import Version from poetry.core.vcs.git import Git from poetry.core.version.markers import MarkerUnion diff --git a/poetry/puzzle/solver.py b/poetry/puzzle/solver.py index 7465df1bfcf..bc787508636 100644 --- a/poetry/puzzle/solver.py +++ b/poetry/puzzle/solver.py @@ -13,7 +13,7 @@ from cleo.io.io import IO -from poetry.core.packages import Package +from poetry.core.packages.package import Package from poetry.core.packages.project_package import ProjectPackage from poetry.installation.operations import Install from poetry.installation.operations import Uninstall @@ -31,11 +31,11 @@ if TYPE_CHECKING: - from poetry.core.packages import Dependency - from poetry.core.packages import DirectoryDependency - from poetry.core.packages import FileDependency - from poetry.core.packages import URLDependency - from poetry.core.packages import VCSDependency + from poetry.core.packages.dependency import Dependency + from poetry.core.packages.directory_dependency import DirectoryDependency + from poetry.core.packages.file_dependency import FileDependency + from poetry.core.packages.url_dependency import URLDependency + from poetry.core.packages.vcs_dependency import VCSDependency from poetry.installation.operations import OperationTypes diff --git a/poetry/repositories/base_repository.py b/poetry/repositories/base_repository.py index 4954c569ca9..0e03b8446ce 100644 --- a/poetry/repositories/base_repository.py +++ b/poetry/repositories/base_repository.py @@ -4,8 +4,8 @@ if TYPE_CHECKING: - from poetry.core.packages import Dependency - from poetry.core.packages import Package + from poetry.core.packages.dependency import Dependency + from poetry.core.packages.package import Package class BaseRepository(object): diff --git a/poetry/repositories/installed_repository.py b/poetry/repositories/installed_repository.py index 6fba0dd372e..c3f9e13c764 100644 --- a/poetry/repositories/installed_repository.py +++ b/poetry/repositories/installed_repository.py @@ -4,7 +4,7 @@ from typing import Set from typing import Union -from poetry.core.packages import Package +from poetry.core.packages.package import Package from poetry.core.utils.helpers import module_name from poetry.utils._compat import metadata from poetry.utils.env import Env diff --git a/poetry/repositories/legacy_repository.py b/poetry/repositories/legacy_repository.py index d14d48fe2fe..1a4de68769a 100644 --- a/poetry/repositories/legacy_repository.py +++ b/poetry/repositories/legacy_repository.py @@ -19,12 +19,12 @@ from cachecontrol.caches.file_cache import FileCache from cachy import CacheManager -from poetry.core.packages import Package +from poetry.core.packages.package import Package from poetry.core.packages.utils.link import Link -from poetry.core.semver import Version -from poetry.core.semver import VersionConstraint -from poetry.core.semver import VersionRange -from poetry.core.semver import parse_constraint +from poetry.core.semver.helpers import parse_constraint +from poetry.core.semver.version import Version +from poetry.core.semver.version_constraint import VersionConstraint +from poetry.core.semver.version_range import VersionRange from poetry.locations import REPOSITORY_CACHE_DIR from poetry.utils.helpers import canonicalize_name from poetry.utils.patterns import wheel_file_re @@ -38,7 +38,7 @@ if TYPE_CHECKING: - from poetry.core.packages import Dependency + from poetry.core.packages.dependency import Dependency try: from html import unescape diff --git a/poetry/repositories/pool.py b/poetry/repositories/pool.py index 20579b5fdab..69d933296ac 100644 --- a/poetry/repositories/pool.py +++ b/poetry/repositories/pool.py @@ -9,8 +9,8 @@ if TYPE_CHECKING: - from poetry.core.packages import Dependency - from poetry.core.packages import Package + from poetry.core.packages.dependency import Dependency + from poetry.core.packages.package import Package class Pool(BaseRepository): diff --git a/poetry/repositories/pypi_repository.py b/poetry/repositories/pypi_repository.py index 02d9543d92b..f6f9400cfc5 100644 --- a/poetry/repositories/pypi_repository.py +++ b/poetry/repositories/pypi_repository.py @@ -4,6 +4,7 @@ from collections import defaultdict from pathlib import Path +from typing import TYPE_CHECKING from typing import Dict from typing import List from typing import Union @@ -16,14 +17,13 @@ from cachy import CacheManager from html5lib.html5parser import parse -from poetry.core.packages import Dependency -from poetry.core.packages import Package -from poetry.core.packages import dependency_from_pep_508 +from poetry.core.packages.dependency import Dependency +from poetry.core.packages.package import Package from poetry.core.packages.utils.link import Link -from poetry.core.semver import VersionConstraint -from poetry.core.semver import VersionRange -from poetry.core.semver import parse_constraint from poetry.core.semver.exceptions import ParseVersionError +from poetry.core.semver.helpers import parse_constraint +from poetry.core.semver.version_constraint import VersionConstraint +from poetry.core.semver.version_range import VersionRange from poetry.core.version.markers import parse_marker from poetry.locations import REPOSITORY_CACHE_DIR from poetry.utils._compat import to_str @@ -31,7 +31,6 @@ from poetry.utils.helpers import temporary_directory from poetry.utils.patterns import wheel_file_re -from ..inspection.info import PackageInfo from .exceptions import PackageNotFound from .remote_repository import RemoteRepository @@ -41,6 +40,10 @@ logger = logging.getLogger(__name__) +if TYPE_CHECKING: + from poetry.inspection.info import PackageInfo + + class PyPiRepository(RemoteRepository): CACHE_VERSION = parse_constraint("1.0.0") @@ -214,13 +217,15 @@ def _get_package_info(self, name: str) -> dict: return data - def get_release_info(self, name: str, version: str) -> PackageInfo: + def get_release_info(self, name: str, version: str) -> "PackageInfo": """ Return the release information given a package name and a version. The information is returned from the cache if it exists or retrieved from the remote server. """ + from poetry.inspection.info import PackageInfo + if self._disable_cache: return PackageInfo.load(self._get_release_info(name, version)) @@ -254,6 +259,8 @@ def find_links_for_package(self, package: Package) -> List[Link]: return links def _get_release_info(self, name: str, version: str) -> dict: + from poetry.inspection.info import PackageInfo + self._log("Getting info for {} ({}) from PyPI".format(name, version), "debug") json_data = self._get("pypi/{}/{}/json".format(name, version)) @@ -330,7 +337,7 @@ def _get(self, endpoint: str) -> Union[dict, None]: return json_data - def _get_info_from_urls(self, urls: Dict[str, List[str]]) -> PackageInfo: + def _get_info_from_urls(self, urls: Dict[str, List[str]]) -> "PackageInfo": # Checking wheels first as they are more likely to hold # the necessary information if "bdist_wheel" in urls: @@ -377,11 +384,11 @@ def _get_info_from_urls(self, urls: Dict[str, List[str]]) -> PackageInfo: return info py2_requires_dist = set( - dependency_from_pep_508(r).to_pep_508() + Dependency.create_from_pep_508(r).to_pep_508() for r in info.requires_dist ) py3_requires_dist = set( - dependency_from_pep_508(r).to_pep_508() + Dependency.create_from_pep_508(r).to_pep_508() for r in py3_info.requires_dist ) base_requires_dist = py2_requires_dist & py3_requires_dist @@ -391,14 +398,14 @@ def _get_info_from_urls(self, urls: Dict[str, List[str]]) -> PackageInfo: # Normalizing requires_dist requires_dist = list(base_requires_dist) for requirement in py2_only_requires_dist: - dep = dependency_from_pep_508(requirement) + dep = Dependency.create_from_pep_508(requirement) dep.marker = dep.marker.intersect( parse_marker("python_version == '2.7'") ) requires_dist.append(dep.to_pep_508()) for requirement in py3_only_requires_dist: - dep = dependency_from_pep_508(requirement) + dep = Dependency.create_from_pep_508(requirement) dep.marker = dep.marker.intersect( parse_marker("python_version >= '3'") ) @@ -422,7 +429,9 @@ def _get_info_from_urls(self, urls: Dict[str, List[str]]) -> PackageInfo: return self._get_info_from_sdist(urls["sdist"][0]) - def _get_info_from_wheel(self, url: str) -> PackageInfo: + def _get_info_from_wheel(self, url: str) -> "PackageInfo": + from poetry.inspection.info import PackageInfo + self._log( "Downloading wheel: {}".format( urllib.parse.urlparse(url).path.rsplit("/")[-1] @@ -438,7 +447,9 @@ def _get_info_from_wheel(self, url: str) -> PackageInfo: return PackageInfo.from_wheel(filepath) - def _get_info_from_sdist(self, url: str) -> PackageInfo: + def _get_info_from_sdist(self, url: str) -> "PackageInfo": + from poetry.inspection.info import PackageInfo + self._log( "Downloading sdist: {}".format( urllib.parse.urlparse(url).path.rsplit("/")[-1] diff --git a/poetry/repositories/repository.py b/poetry/repositories/repository.py index 96932ec8edf..8e25330206f 100644 --- a/poetry/repositories/repository.py +++ b/poetry/repositories/repository.py @@ -2,17 +2,13 @@ from typing import List from typing import Optional -from poetry.core.semver import VersionConstraint -from poetry.core.semver import VersionRange -from poetry.core.semver import parse_constraint - from .base_repository import BaseRepository if TYPE_CHECKING: - from poetry.core.packages import Dependency - from poetry.core.packages import Link - from poetry.core.packages import Package + from poetry.core.packages.dependency import Dependency + from poetry.core.packages.package import Package + from poetry.core.packages.utils.link import Link class Repository(BaseRepository): @@ -41,6 +37,10 @@ def package( return package.clone() def find_packages(self, dependency: "Dependency") -> List["Package"]: + from poetry.core.semver.helpers import parse_constraint + from poetry.core.semver.version_constraint import VersionConstraint + from poetry.core.semver.version_range import VersionRange + constraint = dependency.constraint packages = [] ignored_pre_release_packages = [] diff --git a/poetry/utils/env.py b/poetry/utils/env.py index ff78bf0161b..ae94d40e1aa 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -31,7 +31,7 @@ from packaging.tags import interpreter_version from packaging.tags import sys_tags -from poetry.core.semver import parse_constraint +from poetry.core.semver.helpers import parse_constraint from poetry.core.semver.version import Version from poetry.core.toml.file import TOMLFile from poetry.core.version.markers import BaseMarker diff --git a/poetry/utils/extras.py b/poetry/utils/extras.py index 7c4361092a4..37f04681eed 100644 --- a/poetry/utils/extras.py +++ b/poetry/utils/extras.py @@ -1,15 +1,17 @@ +from typing import TYPE_CHECKING from typing import Iterable from typing import Iterator from typing import List from typing import Mapping from typing import Sequence -from poetry.core.packages import Package -from poetry.utils.helpers import canonicalize_name + +if TYPE_CHECKING: + from poetry.core.packages.package import Package # noqa def get_extra_package_names( - packages: Sequence[Package], + packages: Sequence["Package"], extras: Mapping[str, List[str]], extra_names: Sequence[str], ) -> Iterator[str]: @@ -21,6 +23,9 @@ def get_extra_package_names( in the `extras` section of `poetry.lock`. :param extra_names: A list of strings specifying names of extra groups to resolve. """ + from poetry.core.packages.package import Package # noqa + from poetry.utils.helpers import canonicalize_name + if not extra_names: return [] diff --git a/poetry/utils/setup_reader.py b/poetry/utils/setup_reader.py index 78f7a96af46..da6d844e816 100644 --- a/poetry/utils/setup_reader.py +++ b/poetry/utils/setup_reader.py @@ -10,7 +10,7 @@ from typing import Tuple from typing import Union -from poetry.core.semver import Version +from poetry.core.semver.version import Version class SetupReader(object): diff --git a/poetry/version/version_selector.py b/poetry/version/version_selector.py index 1c7cc254e04..fe84f50764a 100644 --- a/poetry/version/version_selector.py +++ b/poetry/version/version_selector.py @@ -2,8 +2,8 @@ from typing import Optional from typing import Union -from poetry.core.packages import Package -from poetry.core.semver import Version +from poetry.core.packages.package import Package +from poetry.core.semver.version import Version if TYPE_CHECKING: diff --git a/pyproject.toml b/pyproject.toml index 716fd2f27aa..2a59102ff77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ [tool.poetry.dependencies] python = "^3.6" -poetry-core = "^1.0.2" +poetry-core = { git = "https://github.com/python-poetry/poetry-core", branch = "master"} cleo = "^1.0.0a1" crashtest = "^0.3.0" requests = "^2.18" diff --git a/tests/console/commands/env/helpers.py b/tests/console/commands/env/helpers.py index 4f8b7a4d818..63bf00fd68b 100644 --- a/tests/console/commands/env/helpers.py +++ b/tests/console/commands/env/helpers.py @@ -2,7 +2,7 @@ from typing import Optional from typing import Union -from poetry.core.semver import Version +from poetry.core.semver.version import Version def build_venv( diff --git a/tests/console/commands/env/test_use.py b/tests/console/commands/env/test_use.py index e288bd66003..9ae8b41f4bd 100644 --- a/tests/console/commands/env/test_use.py +++ b/tests/console/commands/env/test_use.py @@ -5,7 +5,7 @@ import pytest import tomlkit -from poetry.core.semver import Version +from poetry.core.semver.version import Version from poetry.core.toml.file import TOMLFile from poetry.utils.env import MockEnv from tests.console.commands.env.helpers import build_venv diff --git a/tests/console/commands/test_add.py b/tests/console/commands/test_add.py index 6405bc7431c..4808c53f29f 100644 --- a/tests/console/commands/test_add.py +++ b/tests/console/commands/test_add.py @@ -1,13 +1,10 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import sys from pathlib import Path import pytest -from poetry.core.semver import Version +from poetry.core.semver.version import Version from poetry.repositories.legacy_repository import LegacyRepository from tests.helpers import get_dependency from tests.helpers import get_package diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index ad20da54d39..66b0dae9b69 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -4,7 +4,7 @@ import pytest from poetry.config.config_source import ConfigSource -from poetry.core.pyproject import PyProjectException +from poetry.core.pyproject.exceptions import PyProjectException from poetry.factory import Factory diff --git a/tests/helpers.py b/tests/helpers.py index 078e15b6143..f1bafd42700 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -7,9 +7,9 @@ from poetry.console.application import Application from poetry.core.masonry.utils.helpers import escape_name from poetry.core.masonry.utils.helpers import escape_version -from poetry.core.packages import Dependency -from poetry.core.packages import Link -from poetry.core.packages import Package +from poetry.core.packages.dependency import Dependency +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.factory import Factory diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 32eb63149ea..4aa6daef30f 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -9,7 +9,7 @@ from cleo.io.null_io import NullIO -from poetry.core.packages import ProjectPackage +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 diff --git a/tests/installation/test_installer_old.py b/tests/installation/test_installer_old.py index 21c66e2a176..a9cc4879d9d 100644 --- a/tests/installation/test_installer_old.py +++ b/tests/installation/test_installer_old.py @@ -8,7 +8,7 @@ from cleo.io.null_io import NullIO -from poetry.core.packages import ProjectPackage +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 diff --git a/tests/mixology/helpers.py b/tests/mixology/helpers.py index fcde6701516..dc0a48e526b 100644 --- a/tests/mixology/helpers.py +++ b/tests/mixology/helpers.py @@ -1,4 +1,4 @@ -from poetry.core.packages import Package +from poetry.core.packages.package import Package from poetry.factory import Factory from poetry.mixology.failure import SolveFailure from poetry.mixology.version_solver import VersionSolver diff --git a/tests/puzzle/test_provider.py b/tests/puzzle/test_provider.py index d7ab8f8788a..b5add046370 100644 --- a/tests/puzzle/test_provider.py +++ b/tests/puzzle/test_provider.py @@ -5,9 +5,9 @@ from cleo.io.null_io import NullIO -from poetry.core.packages import ProjectPackage from poetry.core.packages.directory_dependency import DirectoryDependency from poetry.core.packages.file_dependency import FileDependency +from poetry.core.packages.project_package import ProjectPackage from poetry.core.packages.vcs_dependency import VCSDependency from poetry.inspection.info import PackageInfo from poetry.puzzle.provider import Provider diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 75b5c565ed5..9b4b9a7a0a8 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -4,9 +4,9 @@ from cleo.io.null_io import NullIO -from poetry.core.packages import Package -from poetry.core.packages import ProjectPackage -from poetry.core.packages import dependency_from_pep_508 +from poetry.core.packages.dependency import Dependency +from poetry.core.packages.package import Package +from poetry.core.packages.project_package import ProjectPackage from poetry.core.version.markers import parse_marker from poetry.factory import Factory from poetry.puzzle import Solver @@ -1362,9 +1362,9 @@ 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( solver, repo, package ): - dep1 = dependency_from_pep_508('B (>=1.0); extra == "foo"') + dep1 = Dependency.create_from_pep_508('B (>=1.0); extra == "foo"') dep1.activate() - dep2 = dependency_from_pep_508('B (>=2.0); extra == "bar"') + dep2 = Dependency.create_from_pep_508('B (>=2.0); extra == "bar"') dep2.activate() package.add_dependency( @@ -1496,7 +1496,7 @@ def test_solver_ignores_dependencies_with_incompatible_python_full_version_marke package_a = get_package("A", "1.0.0") package_a.requires.append( - dependency_from_pep_508( + Dependency.create_from_pep_508( 'B (<2.0); platform_python_implementation == "PyPy" and python_full_version < "2.7.9"' ) ) diff --git a/tests/repositories/test_installed_repository.py b/tests/repositories/test_installed_repository.py index 3fcb7449cf7..d10ab37af1d 100644 --- a/tests/repositories/test_installed_repository.py +++ b/tests/repositories/test_installed_repository.py @@ -5,7 +5,7 @@ from pytest_mock.plugin import MockFixture -from poetry.core.packages import Package +from poetry.core.packages.package import Package from poetry.repositories.installed_repository import InstalledRepository from poetry.utils._compat import metadata from poetry.utils.env import MockEnv as BaseMockEnv diff --git a/tests/repositories/test_legacy_repository.py b/tests/repositories/test_legacy_repository.py index 9de70520728..8ced98e4e44 100644 --- a/tests/repositories/test_legacy_repository.py +++ b/tests/repositories/test_legacy_repository.py @@ -5,7 +5,7 @@ import pytest import requests -from poetry.core.packages import Dependency +from poetry.core.packages.dependency import Dependency from poetry.factory import Factory from poetry.repositories.exceptions import PackageNotFound from poetry.repositories.exceptions import RepositoryError diff --git a/tests/repositories/test_pypi_repository.py b/tests/repositories/test_pypi_repository.py index cdd8484cc34..f567314a34c 100644 --- a/tests/repositories/test_pypi_repository.py +++ b/tests/repositories/test_pypi_repository.py @@ -9,7 +9,7 @@ from requests.exceptions import TooManyRedirects from requests.models import Response -from poetry.core.packages import Dependency +from poetry.core.packages.dependency import Dependency from poetry.factory import Factory from poetry.repositories.pypi_repository import PyPiRepository from poetry.utils._compat import encode diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index dc65d6dce23..c929f443caa 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -11,7 +11,7 @@ from cleo.io.null_io import NullIO -from poetry.core.semver import Version +from poetry.core.semver.version import Version from poetry.core.toml.file import TOMLFile from poetry.factory import Factory from poetry.utils.env import GET_BASE_PREFIX diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index 5491ed7e3fa..f95c24627fa 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -4,7 +4,7 @@ import pytest -from poetry.core.packages import dependency_from_pep_508 +from poetry.core.packages.dependency import Dependency from poetry.core.toml.file import TOMLFile from poetry.factory import Factory from poetry.packages import Locker as BaseLocker @@ -273,25 +273,23 @@ def test_exporter_can_export_requirements_txt_poetry(tmp_dir, poetry): # │ │ └── six >=1.4.1 # │ └── jeepney >=0.6 (circular dependency aborted here) expected = { - "poetry": dependency_from_pep_508("poetry==1.1.4"), - "junit-xml": dependency_from_pep_508("junit-xml==1.9"), - "keyring": dependency_from_pep_508("keyring==21.8.0"), - "secretstorage": dependency_from_pep_508( + "poetry": Dependency.create_from_pep_508("poetry==1.1.4"), + "junit-xml": Dependency.create_from_pep_508("junit-xml==1.9"), + "keyring": Dependency.create_from_pep_508("keyring==21.8.0"), + "secretstorage": Dependency.create_from_pep_508( "secretstorage==3.3.0; sys_platform=='linux'" ), - "cryptography": dependency_from_pep_508( + "cryptography": Dependency.create_from_pep_508( "cryptography==3.2; sys_platform=='linux'" ), - "six": dependency_from_pep_508("six==1.15.0"), + "six": Dependency.create_from_pep_508("six==1.15.0"), } for line in content.strip().split("\n"): - dependency = dependency_from_pep_508(line) + dependency = Dependency.create_from_pep_508(line) assert dependency.name in expected expected_dependency = expected.pop(dependency.name) assert dependency == expected_dependency - print(dependency.marker) - print(expected_dependency.marker) assert dependency.marker == expected_dependency.marker @@ -356,13 +354,15 @@ def test_exporter_can_export_requirements_txt_pyinstaller(tmp_dir, poetry): # ├── macholib >=1.8 -- only on Darwin # │ └── altgraph >=0.15 expected = { - "pyinstaller": dependency_from_pep_508("pyinstaller==4.0"), - "altgraph": dependency_from_pep_508("altgraph==0.17"), - "macholib": dependency_from_pep_508("macholib==1.8; sys_platform == 'darwin'"), + "pyinstaller": Dependency.create_from_pep_508("pyinstaller==4.0"), + "altgraph": Dependency.create_from_pep_508("altgraph==0.17"), + "macholib": Dependency.create_from_pep_508( + "macholib==1.8; sys_platform == 'darwin'" + ), } for line in content.strip().split("\n"): - dependency = dependency_from_pep_508(line) + dependency = Dependency.create_from_pep_508(line) assert dependency.name in expected expected_dependency = expected.pop(dependency.name) assert dependency == expected_dependency @@ -427,20 +427,20 @@ def test_exporter_can_export_requirements_txt_with_nested_packages_and_markers( content = f.read() expected = { - "a": dependency_from_pep_508("a==1.2.3; python_version < '3.7'"), - "b": dependency_from_pep_508( + "a": Dependency.create_from_pep_508("a==1.2.3; python_version < '3.7'"), + "b": Dependency.create_from_pep_508( "b==4.5.6; platform_system == 'Windows' and python_version < '3.7'" ), - "c": dependency_from_pep_508( + "c": Dependency.create_from_pep_508( "c==7.8.9; sys_platform == 'win32' and python_version < '3.7'" ), - "d": dependency_from_pep_508( + "d": Dependency.create_from_pep_508( "d==0.0.1; platform_system == 'Windows' and python_version < '3.7' or sys_platform == 'win32' and python_version < '3.7'" ), } for line in content.strip().split("\n"): - dependency = dependency_from_pep_508(line) + dependency = Dependency.create_from_pep_508(line) assert dependency.name in expected expected_dependency = expected.pop(dependency.name) assert dependency == expected_dependency diff --git a/tests/utils/test_extras.py b/tests/utils/test_extras.py index e06e48096df..ab138c888e7 100644 --- a/tests/utils/test_extras.py +++ b/tests/utils/test_extras.py @@ -1,6 +1,6 @@ import pytest -from poetry.core.packages import Package +from poetry.core.packages.package import Package from poetry.factory import Factory from poetry.utils.extras import get_extra_package_names From 47aa857f2e26ec4ce91337085f3af5cfac963c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Tue, 9 Mar 2021 20:21:16 +0100 Subject: [PATCH 095/222] Fix documentation linting --- docs/docs/pyproject.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/pyproject.md b/docs/docs/pyproject.md index bdcc6e0bc49..58ff75a7238 100644 --- a/docs/docs/pyproject.md +++ b/docs/docs/pyproject.md @@ -253,10 +253,10 @@ poetry install --extras "mysql pgsql" poetry install -E mysql -E pgsql ``` -When installing or specifying Poetry-built packages, the extras defined in this section can be activated +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). -For example, when installing the package using `pip`, the dependencies required by +For example, when installing the package using `pip`, the dependencies required by the `databases` extra can be installed as shown below. ```bash From c31864c25b3fe3e961e70093cf5d921cd4ebbf31 Mon Sep 17 00:00:00 2001 From: Chris Lieb Date: Fri, 12 Mar 2021 16:36:22 -0500 Subject: [PATCH 096/222] Add new --check flag to lock command --- docs/docs/cli.md | 4 + docs/docs/pyproject.md | 4 +- poetry/console/commands/lock.py | 13 ++ tests/console/commands/test_lock.py | 56 ++++++- tests/fixtures/outdated_lock/poetry.lock | 152 ++++++++++++++++++ tests/fixtures/outdated_lock/pyproject.toml | 15 ++ tests/fixtures/up_to_date_lock/poetry.lock | 152 ++++++++++++++++++ tests/fixtures/up_to_date_lock/pyproject.toml | 15 ++ 8 files changed, 406 insertions(+), 5 deletions(-) create mode 100644 tests/fixtures/outdated_lock/poetry.lock create mode 100644 tests/fixtures/outdated_lock/pyproject.toml create mode 100644 tests/fixtures/up_to_date_lock/poetry.lock create mode 100644 tests/fixtures/up_to_date_lock/pyproject.toml diff --git a/docs/docs/cli.md b/docs/docs/cli.md index 4675ce1023b..70208501c87 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -450,6 +450,10 @@ This command locks (without installing) the dependencies specified in `pyproject poetry lock ``` +### Options + +* `--check`: Verify that `poetry.lock` is consistent with `pyproject.toml` + ## version This command shows the current version of the project or bumps the version of diff --git a/docs/docs/pyproject.md b/docs/docs/pyproject.md index bdcc6e0bc49..58ff75a7238 100644 --- a/docs/docs/pyproject.md +++ b/docs/docs/pyproject.md @@ -253,10 +253,10 @@ poetry install --extras "mysql pgsql" poetry install -E mysql -E pgsql ``` -When installing or specifying Poetry-built packages, the extras defined in this section can be activated +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). -For example, when installing the package using `pip`, the dependencies required by +For example, when installing the package using `pip`, the dependencies required by the `databases` extra can be installed as shown below. ```bash diff --git a/poetry/console/commands/lock.py b/poetry/console/commands/lock.py index c62515f8a67..bfcdcaf665a 100644 --- a/poetry/console/commands/lock.py +++ b/poetry/console/commands/lock.py @@ -12,6 +12,12 @@ class LockCommand(InstallerCommand): option( "no-update", None, "Do not update locked versions, only refresh lock file." ), + option( + "check", + None, + "Check that the poetry.lock file corresponds to the current version " + "of pyproject.toml.", + ), ] help = """ @@ -29,6 +35,13 @@ def handle(self) -> int: self.poetry.config.get("experimental.new-installer", False) ) + if self.option("check"): + return ( + 0 + if self.poetry.locker.is_locked() and self.poetry.locker.is_fresh() + else 1 + ) + self._installer.lock(update=not self.option("no-update")) return self._installer.run() diff --git a/tests/console/commands/test_lock.py b/tests/console/commands/test_lock.py index f3ed49bb76f..e49e4326555 100644 --- a/tests/console/commands/test_lock.py +++ b/tests/console/commands/test_lock.py @@ -16,9 +16,8 @@ def tester(command_tester_factory): return command_tester_factory("lock") -@pytest.fixture -def poetry_with_old_lockfile(project_factory, fixture_dir, source_dir): - source = fixture_dir("old_lock") +def _project_factory(fixture_name, project_factory, fixture_dir): + 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( @@ -28,6 +27,57 @@ def poetry_with_old_lockfile(project_factory, fixture_dir, source_dir): ) +@pytest.fixture +def poetry_with_outdated_lockfile(project_factory, fixture_dir): + return _project_factory("outdated_lock", project_factory, fixture_dir) + + +@pytest.fixture +def poetry_with_up_to_date_lockfile(project_factory, fixture_dir): + return _project_factory("up_to_date_lock", project_factory, fixture_dir) + + +@pytest.fixture +def poetry_with_old_lockfile(project_factory, fixture_dir): + return _project_factory("old_lock", project_factory, fixture_dir) + + +def test_lock_check_outdated( + command_tester_factory, poetry_with_outdated_lockfile, http +): + http.disable() + + locker = Locker( + lock=poetry_with_outdated_lockfile.pyproject.file.path.parent / "poetry.lock", + local_config=poetry_with_outdated_lockfile.locker._local_config, + ) + poetry_with_outdated_lockfile.set_locker(locker) + + tester = command_tester_factory("lock", poetry=poetry_with_outdated_lockfile) + status_code = tester.execute("--check") + + # exit with an error + assert status_code == 1 + + +def test_lock_check_up_to_date( + command_tester_factory, poetry_with_up_to_date_lockfile, http +): + http.disable() + + locker = Locker( + lock=poetry_with_up_to_date_lockfile.pyproject.file.path.parent / "poetry.lock", + local_config=poetry_with_up_to_date_lockfile.locker._local_config, + ) + poetry_with_up_to_date_lockfile.set_locker(locker) + + tester = command_tester_factory("lock", poetry=poetry_with_up_to_date_lockfile) + status_code = tester.execute("--check") + + # exit with an error + assert status_code == 0 + + def test_lock_no_update(command_tester_factory, poetry_with_old_lockfile, repo): repo.add_package(get_package("sampleproject", "1.3.1")) repo.add_package(get_package("sampleproject", "2.0.0")) diff --git a/tests/fixtures/outdated_lock/poetry.lock b/tests/fixtures/outdated_lock/poetry.lock new file mode 100644 index 00000000000..1d950ca7c10 --- /dev/null +++ b/tests/fixtures/outdated_lock/poetry.lock @@ -0,0 +1,152 @@ +[[package]] +name = "certifi" +version = "2020.6.20" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "chardet" +version = "3.0.4" +description = "Universal encoding detector for Python 2 and 3" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "docker" +version = "4.3.0" +description = "A Python library for the Docker Engine API." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +pywin32 = {version = "227", markers = "sys_platform == \"win32\""} +requests = ">=2.14.2,<2.18.0 || >2.18.0" +six = ">=1.4.0" +websocket-client = ">=0.32.0" + +[package.extras] +ssh = ["paramiko (>=2.4.2)"] +tls = ["pyOpenSSL (>=17.5.0)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] + +[[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.*" + +[[package]] +name = "pywin32" +version = "227" +description = "Python for Window Extensions" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "requests" +version = "2.24.0" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<4" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] + +[[package]] +name = "six" +version = "1.15.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "urllib3" +version = "1.25.10" +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.*, <4" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] + +[[package]] +name = "websocket-client" +version = "0.57.0" +description = "WebSocket client for Python. hybi13 is supported." +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +six = "*" + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "2f47de5e052dabeff3c1362d3a37b5cfcaf9bbe9d9ce1681207e72ca1f4dab55" + +[metadata.files] +certifi = [ + {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, + {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, +] +chardet = [ + {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, + {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, +] +docker = [ + {file = "docker-4.3.0-py2.py3-none-any.whl", hash = "sha256:ba118607b0ba6bfc1b236ec32019a355c47b5d012d01d976467d4692ef443929"}, + {file = "docker-4.3.0.tar.gz", hash = "sha256:431a268f2caf85aa30613f9642da274c62f6ee8bae7d70d968e01529f7d6af93"}, +] +idna = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] +pywin32 = [ + {file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"}, + {file = "pywin32-227-cp27-cp27m-win_amd64.whl", hash = "sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116"}, + {file = "pywin32-227-cp35-cp35m-win32.whl", hash = "sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa"}, + {file = "pywin32-227-cp35-cp35m-win_amd64.whl", hash = "sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4"}, + {file = "pywin32-227-cp36-cp36m-win32.whl", hash = "sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be"}, + {file = "pywin32-227-cp36-cp36m-win_amd64.whl", hash = "sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2"}, + {file = "pywin32-227-cp37-cp37m-win32.whl", hash = "sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507"}, + {file = "pywin32-227-cp37-cp37m-win_amd64.whl", hash = "sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511"}, + {file = "pywin32-227-cp38-cp38-win32.whl", hash = "sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc"}, + {file = "pywin32-227-cp38-cp38-win_amd64.whl", hash = "sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e"}, + {file = "pywin32-227-cp39-cp39-win32.whl", hash = "sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295"}, + {file = "pywin32-227-cp39-cp39-win_amd64.whl", hash = "sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c"}, +] +requests = [ + {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, + {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, +] +six = [ + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, +] +urllib3 = [ + {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"}, + {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, +] +websocket-client = [ + {file = "websocket_client-0.57.0-py2.py3-none-any.whl", hash = "sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549"}, + {file = "websocket_client-0.57.0.tar.gz", hash = "sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010"}, +] diff --git a/tests/fixtures/outdated_lock/pyproject.toml b/tests/fixtures/outdated_lock/pyproject.toml new file mode 100644 index 00000000000..555147605ea --- /dev/null +++ b/tests/fixtures/outdated_lock/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "foobar" +version = "0.1.0" +description = "" +authors = ["Poetry Developer "] + +[tool.poetry.dependencies] +python = "^3.8" +docker = "4.3.1" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/fixtures/up_to_date_lock/poetry.lock b/tests/fixtures/up_to_date_lock/poetry.lock new file mode 100644 index 00000000000..a896b5d0621 --- /dev/null +++ b/tests/fixtures/up_to_date_lock/poetry.lock @@ -0,0 +1,152 @@ +[[package]] +name = "certifi" +version = "2020.12.5" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[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.*" + +[[package]] +name = "docker" +version = "4.3.1" +description = "A Python library for the Docker Engine API." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +pywin32 = {version = "227", markers = "sys_platform == \"win32\""} +requests = ">=2.14.2,<2.18.0 || >2.18.0" +six = ">=1.4.0" +websocket-client = ">=0.32.0" + +[package.extras] +ssh = ["paramiko (>=2.4.2)"] +tls = ["pyOpenSSL (>=17.5.0)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] + +[[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.*" + +[[package]] +name = "pywin32" +version = "227" +description = "Python for Window Extensions" +category = "main" +optional = false +python-versions = "*" + +[[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.*" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] + +[[package]] +name = "six" +version = "1.15.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "urllib3" +version = "1.26.3" +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.*, <4" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "websocket-client" +version = "0.58.0" +description = "WebSocket client for Python with low level API options" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +six = "*" + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "0cd068218f235c162f7b74bc8faf4ce3387b82daee1c1bb7a97af034f27ee116" + +[metadata.files] +certifi = [ + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, +] +chardet = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] +docker = [ + {file = "docker-4.3.1-py2.py3-none-any.whl", hash = "sha256:13966471e8bc23b36bfb3a6fb4ab75043a5ef1dac86516274777576bed3b9828"}, + {file = "docker-4.3.1.tar.gz", hash = "sha256:bad94b8dd001a8a4af19ce4becc17f41b09f228173ffe6a4e0355389eef142f2"}, +] +idna = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] +pywin32 = [ + {file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"}, + {file = "pywin32-227-cp27-cp27m-win_amd64.whl", hash = "sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116"}, + {file = "pywin32-227-cp35-cp35m-win32.whl", hash = "sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa"}, + {file = "pywin32-227-cp35-cp35m-win_amd64.whl", hash = "sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4"}, + {file = "pywin32-227-cp36-cp36m-win32.whl", hash = "sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be"}, + {file = "pywin32-227-cp36-cp36m-win_amd64.whl", hash = "sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2"}, + {file = "pywin32-227-cp37-cp37m-win32.whl", hash = "sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507"}, + {file = "pywin32-227-cp37-cp37m-win_amd64.whl", hash = "sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511"}, + {file = "pywin32-227-cp38-cp38-win32.whl", hash = "sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc"}, + {file = "pywin32-227-cp38-cp38-win_amd64.whl", hash = "sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e"}, + {file = "pywin32-227-cp39-cp39-win32.whl", hash = "sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295"}, + {file = "pywin32-227-cp39-cp39-win_amd64.whl", hash = "sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c"}, +] +requests = [ + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, +] +six = [ + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, +] +urllib3 = [ + {file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"}, + {file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"}, +] +websocket-client = [ + {file = "websocket_client-0.58.0-py2.py3-none-any.whl", hash = "sha256:44b5df8f08c74c3d82d28100fdc81f4536809ce98a17f0757557813275fbb663"}, + {file = "websocket_client-0.58.0.tar.gz", hash = "sha256:63509b41d158ae5b7f67eb4ad20fecbb4eee99434e73e140354dc3ff8e09716f"}, +] diff --git a/tests/fixtures/up_to_date_lock/pyproject.toml b/tests/fixtures/up_to_date_lock/pyproject.toml new file mode 100644 index 00000000000..555147605ea --- /dev/null +++ b/tests/fixtures/up_to_date_lock/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "foobar" +version = "0.1.0" +description = "" +authors = ["Poetry Developer "] + +[tool.poetry.dependencies] +python = "^3.8" +docker = "4.3.1" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" From b1d380ec63ef43a2b59c52d10f9733b50eca9081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 19 Mar 2021 21:51:43 +0100 Subject: [PATCH 097/222] Fix tests due to new poetry-core (#3809) --- poetry.lock | 2 +- tests/installation/test_executor.py | 2 +- tests/packages/test_locker.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index f33411bc2d8..9e2466ffb3c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -366,7 +366,7 @@ importlib-metadata = {version = "^1.7.0", markers = "python_version >= \"3.5\" a type = "git" url = "https://github.com/python-poetry/poetry-core" reference = "master" -resolved_reference = "d0b8f3ff1d2ec94e317d8ec20920f32a5d4992ef" +resolved_reference = "5d5251c427aacedcf54f9743635a8124e5a26151" [[package]] name = "pre-commit" diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 4b1596aeb5e..ab911aba2a9 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -131,7 +131,7 @@ def test_execute_executes_a_batch_of_operations( expected = set(expected.splitlines()) output = set(io.fetch_output().splitlines()) assert expected == output - assert 5 == len(env.executed) + assert 6 == len(env.executed) assert 0 == return_code diff --git a/tests/packages/test_locker.py b/tests/packages/test_locker.py index 7fa89a44962..7b0d81bda9e 100644 --- a/tests/packages/test_locker.py +++ b/tests/packages/test_locker.py @@ -73,7 +73,7 @@ def test_lock_file_data_is_ordered(locker, root): category = "main" optional = false python-versions = "*" -develop = true +develop = false [package.source] type = "git" From 8bd9a3e6b36f427cdcb28fa2fa3c2cbcc6e2bedb Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 6 Mar 2021 17:32:37 +0100 Subject: [PATCH 098/222] Fix reference to `poetry-core` package The distribution is named `poetry-core`, not `poetry_core`. --- docs/docs/pyproject.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/pyproject.md b/docs/docs/pyproject.md index 58ff75a7238..a398329c675 100644 --- a/docs/docs/pyproject.md +++ b/docs/docs/pyproject.md @@ -305,7 +305,7 @@ it in the `build-system` section of the `pyproject.toml` file like so: ```toml [build-system] -requires = ["poetry_core>=1.0.0"] +requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" ``` @@ -317,4 +317,4 @@ build-backend = "poetry.core.masonry.api" !!!note If your `pyproject.toml` file still references `poetry` directly as a build backend, - you should update it to reference `poetry_core` instead. + you should update it to reference `poetry-core` instead. From aa16fff6e1d7ffd92dece7d44b036f1e9f6fe8ae Mon Sep 17 00:00:00 2001 From: Reza Gharibi Date: Sun, 21 Mar 2021 03:16:23 +0330 Subject: [PATCH 099/222] doc: fix markdown styling --- docs/docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/index.md b/docs/docs/index.md index 64bb512f6cb..56b3040b02a 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -120,7 +120,7 @@ pip install --user poetry #### Installing with `pipx` -Using [`pipx`](https://github.com/cs01/pipx) to install Poetry is also possible. [pipx] is used to install Python CLI applications globally while still isolating them in virtual environments. This allows for clean upgrades and uninstalls. pipx supports Python 3.6 and later. If using an earlier version of Python, consider [pipsi](https://github.com/mitsuhiko/pipsi). +Using [`pipx`](https://github.com/cs01/pipx) to install Poetry is also possible. `pipx` is used to install Python CLI applications globally while still isolating them in virtual environments. This allows for clean upgrades and uninstalls. `pipx` supports Python 3.6 and later. If using an earlier version of Python, consider [`pipsi`](https://github.com/mitsuhiko/pipsi). ```bash pipx install poetry From 40f30d09a6485c277162930d8c8600364947559e Mon Sep 17 00:00:00 2001 From: Colin Brochtrup Date: Sat, 20 Mar 2021 19:20:12 -0500 Subject: [PATCH 100/222] executor: fix should write check logic Write check in executor, ensure operation skipping override does not skip over verbose or dry_run. Resolves: #3127 --- poetry/installation/executor.py | 5 +---- tests/installation/test_installer.py | 29 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index 520487d2536..ea2940e1fbd 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -700,7 +700,4 @@ def _download_archive(self, operation: Union[Install, Update], link: Link) -> Pa return archive def _should_write_operation(self, operation: Operation) -> bool: - if not operation.skipped: - return True - - return self._dry_run or self._verbose + return not operation.skipped or self._dry_run or self._verbose diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 4aa6daef30f..7cba8667a67 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -7,7 +7,11 @@ import pytest +from cleo.io.inputs.input import Input +from cleo.io.io import IO from cleo.io.null_io import NullIO +from cleo.io.outputs.buffered_output import BufferedOutput +from cleo.io.outputs.output import Verbosity from poetry.core.packages.project_package import ProjectPackage from poetry.core.toml.file import TOMLFile @@ -1889,3 +1893,28 @@ def test_installer_can_handle_old_lock_files( # colorama will be added assert 8 == installer.executor.installations_count + + +@pytest.mark.parametrize("quiet", [True, False]) +def test_run_with_dependencies_quiet(installer, locker, repo, package, quiet): + package_a = get_package("A", "1.0") + package_b = get_package("B", "1.1") + repo.add_package(package_a) + repo.add_package(package_b) + + installer._io = IO(Input(), BufferedOutput(), BufferedOutput()) + installer._io.set_verbosity(Verbosity.QUIET if quiet else Verbosity.NORMAL) + + package.add_dependency(Factory.create_dependency("A", "~1.0")) + package.add_dependency(Factory.create_dependency("B", "^1.0")) + + installer.run() + expected = fixture("with-dependencies") + + assert locker.written_data == expected + + installer._io.output._buffer.seek(0) + if quiet: + assert installer._io.output._buffer.read() == "" + else: + assert installer._io.output._buffer.read() != "" From 37b36f48e823599c5d448ce1a2ce309e14e6febc Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 6 Oct 2020 12:23:48 +0200 Subject: [PATCH 101/222] tests/executor: set directory dep as editable --- tests/installation/test_executor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index ab911aba2a9..444a8af2211 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -94,6 +94,7 @@ def test_execute_executes_a_batch_of_operations( .resolve() .as_posix(), ) + directory_package.develop = True git_package = Package( "demo", From 38558a1942b95c16038c35c8cfb7b5c0a80fb823 Mon Sep 17 00:00:00 2001 From: vlad0337187 Date: Sat, 20 Feb 2021 23:57:36 +0200 Subject: [PATCH 102/222] feature(virtualenv): add 'system-site-packages' option --- docs/docs/configuration.md | 6 ++++++ poetry/config/config.py | 3 ++- poetry/console/commands/config.py | 15 ++++++++----- poetry/utils/env.py | 2 +- tests/console/commands/env/test_use.py | 4 +++- tests/console/commands/test_config.py | 3 +++ tests/test_factory.py | 2 ++ tests/utils/test_env.py | 30 +++++++++++++++++++------- 8 files changed, 49 insertions(+), 16 deletions(-) diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index 72310886ca7..fb085b94c57 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -35,6 +35,7 @@ cache-dir = "/path/to/cache/directory" virtualenvs.create = true virtualenvs.in-project = null virtualenvs.options.always-copy = true +virtualenvs.options.system-site-packages = false virtualenvs.path = "{cache-dir}/virtualenvs" # /path/to/cache/directory/virtualenvs ``` @@ -143,6 +144,11 @@ Defaults to `{cache-dir}/virtualenvs` (`{cache-dir}\virtualenvs` on Windows). If set to `true` the `--always-copy` parameter is passed to `virtualenv` on creation of the venv. Thus all needed files are copied into the venv instead of symlinked. Defaults to `false`. +### `virtualenvs.options.system-site-packages`: boolean + +Give the virtual environment access to the system site-packages directory. +Applies on virtualenv creation. +Defaults to `false`. ### `repositories.`: string diff --git a/poetry/config/config.py b/poetry/config/config.py index 37844aa1fc9..c0f794bd004 100644 --- a/poetry/config/config.py +++ b/poetry/config/config.py @@ -35,7 +35,7 @@ class Config(object): "create": True, "in-project": None, "path": os.path.join("{cache-dir}", "virtualenvs"), - "options": {"always-copy": False}, + "options": {"always-copy": False, "system-site-packages": False}, }, "experimental": {"new-installer": True}, "installer": {"parallel": True}, @@ -140,6 +140,7 @@ def _get_normalizer(self, name: str) -> Callable: "virtualenvs.create", "virtualenvs.in-project", "virtualenvs.options.always-copy", + "virtualenvs.options.system-site-packages", "installer.parallel", }: return boolean_normalizer diff --git a/poetry/console/commands/config.py b/poetry/console/commands/config.py index 1f008195444..300245955d0 100644 --- a/poetry/console/commands/config.py +++ b/poetry/console/commands/config.py @@ -62,6 +62,16 @@ def unique_config_values(self) -> Dict[str, Tuple[Any, Any, Any]]: ), "virtualenvs.create": (boolean_validator, boolean_normalizer, True), "virtualenvs.in-project": (boolean_validator, boolean_normalizer, False), + "virtualenvs.options.always-copy": ( + boolean_validator, + boolean_normalizer, + False, + ), + "virtualenvs.options.system-site-packages": ( + boolean_validator, + boolean_normalizer, + False, + ), "virtualenvs.path": ( str, lambda val: str(Path(val)), @@ -72,11 +82,6 @@ def unique_config_values(self) -> Dict[str, Tuple[Any, Any, Any]]: boolean_normalizer, True, ), - "virtualenvs.options.always-copy": ( - boolean_validator, - boolean_normalizer, - False, - ), "installer.parallel": ( boolean_validator, boolean_normalizer, diff --git a/poetry/utils/env.py b/poetry/utils/env.py index ae94d40e1aa..d930b0d0316 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -637,8 +637,8 @@ def create_venv( create_venv = self._poetry.config.get("virtualenvs.create") root_venv = self._poetry.config.get("virtualenvs.in-project") - venv_path = self._poetry.config.get("virtualenvs.path") + if root_venv: venv_path = cwd / ".venv" elif venv_path is None: diff --git a/tests/console/commands/env/test_use.py b/tests/console/commands/env/test_use.py index 9ae8b41f4bd..f80ff8ee20b 100644 --- a/tests/console/commands/env/test_use.py +++ b/tests/console/commands/env/test_use.py @@ -52,7 +52,9 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( venv_py37 = venv_cache / "{}-py3.7".format(venv_name) mock_build_env.assert_called_with( - venv_py37, executable="python3.7", flags={"always-copy": False} + venv_py37, + executable="python3.7", + flags={"always-copy": False, "system-site-packages": False}, ) envs_file = TOMLFile(venv_cache / "envs.toml") diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index 66b0dae9b69..9cb692b40aa 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -32,6 +32,7 @@ def test_list_displays_default_value_if_not_set(tester, config, config_cache_dir virtualenvs.create = true virtualenvs.in-project = null virtualenvs.options.always-copy = false +virtualenvs.options.system-site-packages = false virtualenvs.path = {path} # {virtualenvs} """.format( cache=json.dumps(str(config_cache_dir)), @@ -53,6 +54,7 @@ def test_list_displays_set_get_setting(tester, config, config_cache_dir): virtualenvs.create = false virtualenvs.in-project = null virtualenvs.options.always-copy = false +virtualenvs.options.system-site-packages = false virtualenvs.path = {path} # {virtualenvs} """.format( cache=json.dumps(str(config_cache_dir)), @@ -96,6 +98,7 @@ def test_list_displays_set_get_local_setting(tester, config, config_cache_dir): virtualenvs.create = false virtualenvs.in-project = null virtualenvs.options.always-copy = false +virtualenvs.options.system-site-packages = false virtualenvs.path = {path} # {virtualenvs} """.format( cache=json.dumps(str(config_cache_dir)), diff --git a/tests/test_factory.py b/tests/test_factory.py index d213d808405..7a4e9d5ac4a 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -222,3 +222,5 @@ def test_create_poetry_with_local_config(fixture_dir): assert not poetry.config.get("virtualenvs.in-project") assert not poetry.config.get("virtualenvs.create") + assert not poetry.config.get("virtualenvs.options.always-copy") + assert not poetry.config.get("virtualenvs.options.system-site-packages") diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index c929f443caa..945a4d47844 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -159,7 +159,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( m.assert_called_with( Path(tmp_dir) / "{}-py3.7".format(venv_name), executable="python3.7", - flags={"always-copy": False}, + flags={"always-copy": False, "system-site-packages": False}, ) envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") @@ -279,7 +279,7 @@ def test_activate_activates_different_virtualenv_with_envs_file( m.assert_called_with( Path(tmp_dir) / "{}-py3.6".format(venv_name), executable="python3.6", - flags={"always-copy": False}, + flags={"always-copy": False, "system-site-packages": False}, ) assert envs_file.exists() @@ -333,7 +333,7 @@ def test_activate_activates_recreates_for_different_patch( build_venv_m.assert_called_with( Path(tmp_dir) / "{}-py3.7".format(venv_name), executable="python3.7", - flags={"always-copy": False}, + flags={"always-copy": False, "system-site-packages": False}, ) remove_venv_m.assert_called_with(Path(tmp_dir) / "{}-py3.7".format(venv_name)) @@ -661,7 +661,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ m.assert_called_with( config_virtualenvs_path / "{}-py3.7".format(venv_name), executable="python3", - flags={"always-copy": False}, + flags={"always-copy": False, "system-site-packages": False}, ) @@ -685,7 +685,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific m.assert_called_with( config_virtualenvs_path / "{}-py3.9".format(venv_name), executable="python3.9", - flags={"always-copy": False}, + flags={"always-copy": False, "system-site-packages": False}, ) @@ -769,7 +769,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility( config_virtualenvs_path / "{}-py{}.{}".format(venv_name, version.major, version.minor), executable=None, - flags={"always-copy": False}, + flags={"always-copy": False, "system-site-packages": False}, ) @@ -804,7 +804,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable( config_virtualenvs_path / "{}-py{}.{}".format(venv_name, version.major, version.minor - 1), executable="python{}.{}".format(version.major, version.minor - 1), - flags={"always-copy": False}, + flags={"always-copy": False, "system-site-packages": False}, ) @@ -838,7 +838,7 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( m.assert_called_with( poetry.file.parent / ".venv", executable="python3.7", - flags={"always-copy": False}, + flags={"always-copy": False, "system-site-packages": False}, ) envs_file = TOMLFile(Path(tmp_dir) / "virtualenvs" / "envs.toml") @@ -875,3 +875,17 @@ def test_venv_has_correct_paths(tmp_venv): assert paths.get("platlib") is not None assert paths.get("scripts") is not None assert tmp_venv.site_packages.path == Path(paths["purelib"]) + + +def test_env_system_packages(tmp_path, config): + venv_path = tmp_path / "venv" + pyvenv_cfg = venv_path / "pyvenv.cfg" + + EnvManager(config).build_venv(path=venv_path, flags={"system-site-packages": True}) + + if sys.version_info >= (3, 3): + assert "include-system-site-packages = true" in pyvenv_cfg.read_text() + elif (2, 6) < sys.version_info < (3, 0): + assert not venv_path.joinpath( + "lib", "python2.7", "no-global-site-packages.txt" + ).exists() From 4b5d4131dbeec34e06c95e8c06f382f588c45a8c Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Sun, 21 Mar 2021 02:03:54 +0100 Subject: [PATCH 103/222] Revert "feature(virtualenv): add 'system-site-packages' option" This reverts commit 38558a1942b95c16038c35c8cfb7b5c0a80fb823. --- docs/docs/configuration.md | 6 ------ poetry/config/config.py | 3 +-- poetry/console/commands/config.py | 15 +++++-------- poetry/utils/env.py | 2 +- tests/console/commands/env/test_use.py | 4 +--- tests/console/commands/test_config.py | 3 --- tests/test_factory.py | 2 -- tests/utils/test_env.py | 30 +++++++------------------- 8 files changed, 16 insertions(+), 49 deletions(-) diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index fb085b94c57..72310886ca7 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -35,7 +35,6 @@ cache-dir = "/path/to/cache/directory" virtualenvs.create = true virtualenvs.in-project = null virtualenvs.options.always-copy = true -virtualenvs.options.system-site-packages = false virtualenvs.path = "{cache-dir}/virtualenvs" # /path/to/cache/directory/virtualenvs ``` @@ -144,11 +143,6 @@ Defaults to `{cache-dir}/virtualenvs` (`{cache-dir}\virtualenvs` on Windows). If set to `true` the `--always-copy` parameter is passed to `virtualenv` on creation of the venv. Thus all needed files are copied into the venv instead of symlinked. Defaults to `false`. -### `virtualenvs.options.system-site-packages`: boolean - -Give the virtual environment access to the system site-packages directory. -Applies on virtualenv creation. -Defaults to `false`. ### `repositories.`: string diff --git a/poetry/config/config.py b/poetry/config/config.py index c0f794bd004..37844aa1fc9 100644 --- a/poetry/config/config.py +++ b/poetry/config/config.py @@ -35,7 +35,7 @@ class Config(object): "create": True, "in-project": None, "path": os.path.join("{cache-dir}", "virtualenvs"), - "options": {"always-copy": False, "system-site-packages": False}, + "options": {"always-copy": False}, }, "experimental": {"new-installer": True}, "installer": {"parallel": True}, @@ -140,7 +140,6 @@ def _get_normalizer(self, name: str) -> Callable: "virtualenvs.create", "virtualenvs.in-project", "virtualenvs.options.always-copy", - "virtualenvs.options.system-site-packages", "installer.parallel", }: return boolean_normalizer diff --git a/poetry/console/commands/config.py b/poetry/console/commands/config.py index 300245955d0..1f008195444 100644 --- a/poetry/console/commands/config.py +++ b/poetry/console/commands/config.py @@ -62,16 +62,6 @@ def unique_config_values(self) -> Dict[str, Tuple[Any, Any, Any]]: ), "virtualenvs.create": (boolean_validator, boolean_normalizer, True), "virtualenvs.in-project": (boolean_validator, boolean_normalizer, False), - "virtualenvs.options.always-copy": ( - boolean_validator, - boolean_normalizer, - False, - ), - "virtualenvs.options.system-site-packages": ( - boolean_validator, - boolean_normalizer, - False, - ), "virtualenvs.path": ( str, lambda val: str(Path(val)), @@ -82,6 +72,11 @@ def unique_config_values(self) -> Dict[str, Tuple[Any, Any, Any]]: boolean_normalizer, True, ), + "virtualenvs.options.always-copy": ( + boolean_validator, + boolean_normalizer, + False, + ), "installer.parallel": ( boolean_validator, boolean_normalizer, diff --git a/poetry/utils/env.py b/poetry/utils/env.py index d930b0d0316..ae94d40e1aa 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -637,8 +637,8 @@ def create_venv( create_venv = self._poetry.config.get("virtualenvs.create") root_venv = self._poetry.config.get("virtualenvs.in-project") - venv_path = self._poetry.config.get("virtualenvs.path") + venv_path = self._poetry.config.get("virtualenvs.path") if root_venv: venv_path = cwd / ".venv" elif venv_path is None: diff --git a/tests/console/commands/env/test_use.py b/tests/console/commands/env/test_use.py index f80ff8ee20b..9ae8b41f4bd 100644 --- a/tests/console/commands/env/test_use.py +++ b/tests/console/commands/env/test_use.py @@ -52,9 +52,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( venv_py37 = venv_cache / "{}-py3.7".format(venv_name) mock_build_env.assert_called_with( - venv_py37, - executable="python3.7", - flags={"always-copy": False, "system-site-packages": False}, + venv_py37, executable="python3.7", flags={"always-copy": False} ) envs_file = TOMLFile(venv_cache / "envs.toml") diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index 9cb692b40aa..66b0dae9b69 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -32,7 +32,6 @@ def test_list_displays_default_value_if_not_set(tester, config, config_cache_dir virtualenvs.create = true virtualenvs.in-project = null virtualenvs.options.always-copy = false -virtualenvs.options.system-site-packages = false virtualenvs.path = {path} # {virtualenvs} """.format( cache=json.dumps(str(config_cache_dir)), @@ -54,7 +53,6 @@ def test_list_displays_set_get_setting(tester, config, config_cache_dir): virtualenvs.create = false virtualenvs.in-project = null virtualenvs.options.always-copy = false -virtualenvs.options.system-site-packages = false virtualenvs.path = {path} # {virtualenvs} """.format( cache=json.dumps(str(config_cache_dir)), @@ -98,7 +96,6 @@ def test_list_displays_set_get_local_setting(tester, config, config_cache_dir): virtualenvs.create = false virtualenvs.in-project = null virtualenvs.options.always-copy = false -virtualenvs.options.system-site-packages = false virtualenvs.path = {path} # {virtualenvs} """.format( cache=json.dumps(str(config_cache_dir)), diff --git a/tests/test_factory.py b/tests/test_factory.py index 7a4e9d5ac4a..d213d808405 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -222,5 +222,3 @@ def test_create_poetry_with_local_config(fixture_dir): assert not poetry.config.get("virtualenvs.in-project") assert not poetry.config.get("virtualenvs.create") - assert not poetry.config.get("virtualenvs.options.always-copy") - assert not poetry.config.get("virtualenvs.options.system-site-packages") diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 945a4d47844..c929f443caa 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -159,7 +159,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( m.assert_called_with( Path(tmp_dir) / "{}-py3.7".format(venv_name), executable="python3.7", - flags={"always-copy": False, "system-site-packages": False}, + flags={"always-copy": False}, ) envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") @@ -279,7 +279,7 @@ def test_activate_activates_different_virtualenv_with_envs_file( m.assert_called_with( Path(tmp_dir) / "{}-py3.6".format(venv_name), executable="python3.6", - flags={"always-copy": False, "system-site-packages": False}, + flags={"always-copy": False}, ) assert envs_file.exists() @@ -333,7 +333,7 @@ def test_activate_activates_recreates_for_different_patch( build_venv_m.assert_called_with( Path(tmp_dir) / "{}-py3.7".format(venv_name), executable="python3.7", - flags={"always-copy": False, "system-site-packages": False}, + flags={"always-copy": False}, ) remove_venv_m.assert_called_with(Path(tmp_dir) / "{}-py3.7".format(venv_name)) @@ -661,7 +661,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ m.assert_called_with( config_virtualenvs_path / "{}-py3.7".format(venv_name), executable="python3", - flags={"always-copy": False, "system-site-packages": False}, + flags={"always-copy": False}, ) @@ -685,7 +685,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific m.assert_called_with( config_virtualenvs_path / "{}-py3.9".format(venv_name), executable="python3.9", - flags={"always-copy": False, "system-site-packages": False}, + flags={"always-copy": False}, ) @@ -769,7 +769,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility( config_virtualenvs_path / "{}-py{}.{}".format(venv_name, version.major, version.minor), executable=None, - flags={"always-copy": False, "system-site-packages": False}, + flags={"always-copy": False}, ) @@ -804,7 +804,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable( config_virtualenvs_path / "{}-py{}.{}".format(venv_name, version.major, version.minor - 1), executable="python{}.{}".format(version.major, version.minor - 1), - flags={"always-copy": False, "system-site-packages": False}, + flags={"always-copy": False}, ) @@ -838,7 +838,7 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( m.assert_called_with( poetry.file.parent / ".venv", executable="python3.7", - flags={"always-copy": False, "system-site-packages": False}, + flags={"always-copy": False}, ) envs_file = TOMLFile(Path(tmp_dir) / "virtualenvs" / "envs.toml") @@ -875,17 +875,3 @@ def test_venv_has_correct_paths(tmp_venv): assert paths.get("platlib") is not None assert paths.get("scripts") is not None assert tmp_venv.site_packages.path == Path(paths["purelib"]) - - -def test_env_system_packages(tmp_path, config): - venv_path = tmp_path / "venv" - pyvenv_cfg = venv_path / "pyvenv.cfg" - - EnvManager(config).build_venv(path=venv_path, flags={"system-site-packages": True}) - - if sys.version_info >= (3, 3): - assert "include-system-site-packages = true" in pyvenv_cfg.read_text() - elif (2, 6) < sys.version_info < (3, 0): - assert not venv_path.joinpath( - "lib", "python2.7", "no-global-site-packages.txt" - ).exists() From 480eaa1fbe4132500670abc7412a7bc40563971e Mon Sep 17 00:00:00 2001 From: Igor Ovsyannikov Date: Sun, 21 Mar 2021 02:04:44 +0100 Subject: [PATCH 104/222] Add virtualenvs.options.system-site-packages setting Resolves: #1393 Co-authored-by: wyl8899 Co-authored-by: vlad0337187 --- docs/docs/configuration.md | 6 ++++++ poetry/config/config.py | 3 ++- poetry/console/commands/config.py | 15 ++++++++----- poetry/utils/env.py | 2 +- tests/console/commands/env/test_use.py | 4 +++- tests/console/commands/test_config.py | 3 +++ tests/test_factory.py | 2 ++ tests/utils/test_env.py | 30 +++++++++++++++++++------- 8 files changed, 49 insertions(+), 16 deletions(-) diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index 72310886ca7..fb085b94c57 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -35,6 +35,7 @@ cache-dir = "/path/to/cache/directory" virtualenvs.create = true virtualenvs.in-project = null virtualenvs.options.always-copy = true +virtualenvs.options.system-site-packages = false virtualenvs.path = "{cache-dir}/virtualenvs" # /path/to/cache/directory/virtualenvs ``` @@ -143,6 +144,11 @@ Defaults to `{cache-dir}/virtualenvs` (`{cache-dir}\virtualenvs` on Windows). If set to `true` the `--always-copy` parameter is passed to `virtualenv` on creation of the venv. Thus all needed files are copied into the venv instead of symlinked. Defaults to `false`. +### `virtualenvs.options.system-site-packages`: boolean + +Give the virtual environment access to the system site-packages directory. +Applies on virtualenv creation. +Defaults to `false`. ### `repositories.`: string diff --git a/poetry/config/config.py b/poetry/config/config.py index 37844aa1fc9..c0f794bd004 100644 --- a/poetry/config/config.py +++ b/poetry/config/config.py @@ -35,7 +35,7 @@ class Config(object): "create": True, "in-project": None, "path": os.path.join("{cache-dir}", "virtualenvs"), - "options": {"always-copy": False}, + "options": {"always-copy": False, "system-site-packages": False}, }, "experimental": {"new-installer": True}, "installer": {"parallel": True}, @@ -140,6 +140,7 @@ def _get_normalizer(self, name: str) -> Callable: "virtualenvs.create", "virtualenvs.in-project", "virtualenvs.options.always-copy", + "virtualenvs.options.system-site-packages", "installer.parallel", }: return boolean_normalizer diff --git a/poetry/console/commands/config.py b/poetry/console/commands/config.py index 1f008195444..300245955d0 100644 --- a/poetry/console/commands/config.py +++ b/poetry/console/commands/config.py @@ -62,6 +62,16 @@ def unique_config_values(self) -> Dict[str, Tuple[Any, Any, Any]]: ), "virtualenvs.create": (boolean_validator, boolean_normalizer, True), "virtualenvs.in-project": (boolean_validator, boolean_normalizer, False), + "virtualenvs.options.always-copy": ( + boolean_validator, + boolean_normalizer, + False, + ), + "virtualenvs.options.system-site-packages": ( + boolean_validator, + boolean_normalizer, + False, + ), "virtualenvs.path": ( str, lambda val: str(Path(val)), @@ -72,11 +82,6 @@ def unique_config_values(self) -> Dict[str, Tuple[Any, Any, Any]]: boolean_normalizer, True, ), - "virtualenvs.options.always-copy": ( - boolean_validator, - boolean_normalizer, - False, - ), "installer.parallel": ( boolean_validator, boolean_normalizer, diff --git a/poetry/utils/env.py b/poetry/utils/env.py index ae94d40e1aa..d930b0d0316 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -637,8 +637,8 @@ def create_venv( create_venv = self._poetry.config.get("virtualenvs.create") root_venv = self._poetry.config.get("virtualenvs.in-project") - venv_path = self._poetry.config.get("virtualenvs.path") + if root_venv: venv_path = cwd / ".venv" elif venv_path is None: diff --git a/tests/console/commands/env/test_use.py b/tests/console/commands/env/test_use.py index 9ae8b41f4bd..f80ff8ee20b 100644 --- a/tests/console/commands/env/test_use.py +++ b/tests/console/commands/env/test_use.py @@ -52,7 +52,9 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( venv_py37 = venv_cache / "{}-py3.7".format(venv_name) mock_build_env.assert_called_with( - venv_py37, executable="python3.7", flags={"always-copy": False} + venv_py37, + executable="python3.7", + flags={"always-copy": False, "system-site-packages": False}, ) envs_file = TOMLFile(venv_cache / "envs.toml") diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index 66b0dae9b69..9cb692b40aa 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -32,6 +32,7 @@ def test_list_displays_default_value_if_not_set(tester, config, config_cache_dir virtualenvs.create = true virtualenvs.in-project = null virtualenvs.options.always-copy = false +virtualenvs.options.system-site-packages = false virtualenvs.path = {path} # {virtualenvs} """.format( cache=json.dumps(str(config_cache_dir)), @@ -53,6 +54,7 @@ def test_list_displays_set_get_setting(tester, config, config_cache_dir): virtualenvs.create = false virtualenvs.in-project = null virtualenvs.options.always-copy = false +virtualenvs.options.system-site-packages = false virtualenvs.path = {path} # {virtualenvs} """.format( cache=json.dumps(str(config_cache_dir)), @@ -96,6 +98,7 @@ def test_list_displays_set_get_local_setting(tester, config, config_cache_dir): virtualenvs.create = false virtualenvs.in-project = null virtualenvs.options.always-copy = false +virtualenvs.options.system-site-packages = false virtualenvs.path = {path} # {virtualenvs} """.format( cache=json.dumps(str(config_cache_dir)), diff --git a/tests/test_factory.py b/tests/test_factory.py index d213d808405..7a4e9d5ac4a 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -222,3 +222,5 @@ def test_create_poetry_with_local_config(fixture_dir): assert not poetry.config.get("virtualenvs.in-project") assert not poetry.config.get("virtualenvs.create") + assert not poetry.config.get("virtualenvs.options.always-copy") + assert not poetry.config.get("virtualenvs.options.system-site-packages") diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index c929f443caa..945a4d47844 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -159,7 +159,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( m.assert_called_with( Path(tmp_dir) / "{}-py3.7".format(venv_name), executable="python3.7", - flags={"always-copy": False}, + flags={"always-copy": False, "system-site-packages": False}, ) envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") @@ -279,7 +279,7 @@ def test_activate_activates_different_virtualenv_with_envs_file( m.assert_called_with( Path(tmp_dir) / "{}-py3.6".format(venv_name), executable="python3.6", - flags={"always-copy": False}, + flags={"always-copy": False, "system-site-packages": False}, ) assert envs_file.exists() @@ -333,7 +333,7 @@ def test_activate_activates_recreates_for_different_patch( build_venv_m.assert_called_with( Path(tmp_dir) / "{}-py3.7".format(venv_name), executable="python3.7", - flags={"always-copy": False}, + flags={"always-copy": False, "system-site-packages": False}, ) remove_venv_m.assert_called_with(Path(tmp_dir) / "{}-py3.7".format(venv_name)) @@ -661,7 +661,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ m.assert_called_with( config_virtualenvs_path / "{}-py3.7".format(venv_name), executable="python3", - flags={"always-copy": False}, + flags={"always-copy": False, "system-site-packages": False}, ) @@ -685,7 +685,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific m.assert_called_with( config_virtualenvs_path / "{}-py3.9".format(venv_name), executable="python3.9", - flags={"always-copy": False}, + flags={"always-copy": False, "system-site-packages": False}, ) @@ -769,7 +769,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility( config_virtualenvs_path / "{}-py{}.{}".format(venv_name, version.major, version.minor), executable=None, - flags={"always-copy": False}, + flags={"always-copy": False, "system-site-packages": False}, ) @@ -804,7 +804,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable( config_virtualenvs_path / "{}-py{}.{}".format(venv_name, version.major, version.minor - 1), executable="python{}.{}".format(version.major, version.minor - 1), - flags={"always-copy": False}, + flags={"always-copy": False, "system-site-packages": False}, ) @@ -838,7 +838,7 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( m.assert_called_with( poetry.file.parent / ".venv", executable="python3.7", - flags={"always-copy": False}, + flags={"always-copy": False, "system-site-packages": False}, ) envs_file = TOMLFile(Path(tmp_dir) / "virtualenvs" / "envs.toml") @@ -875,3 +875,17 @@ def test_venv_has_correct_paths(tmp_venv): assert paths.get("platlib") is not None assert paths.get("scripts") is not None assert tmp_venv.site_packages.path == Path(paths["purelib"]) + + +def test_env_system_packages(tmp_path, config): + venv_path = tmp_path / "venv" + pyvenv_cfg = venv_path / "pyvenv.cfg" + + EnvManager(config).build_venv(path=venv_path, flags={"system-site-packages": True}) + + if sys.version_info >= (3, 3): + assert "include-system-site-packages = true" in pyvenv_cfg.read_text() + elif (2, 6) < sys.version_info < (3, 0): + assert not venv_path.joinpath( + "lib", "python2.7", "no-global-site-packages.txt" + ).exists() From 0359a42dbf484e7a597b04db050f09c3bb43c4be Mon Sep 17 00:00:00 2001 From: Reid Price Date: Tue, 13 Oct 2020 00:33:36 -0700 Subject: [PATCH 105/222] docs/configuration: fix note markdown This repairs a broken triple-bang note. Also cleans up a few stray double-newlines and very long lines to match rest of file. --- docs/docs/configuration.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index fb085b94c57..4743515dd43 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -91,7 +91,6 @@ This also works for secret settings, like credentials: export POETRY_HTTP_BASIC_MY_REPOSITORY_PASSWORD=secret ``` - ## Available settings ### `cache-dir`: string @@ -120,7 +119,8 @@ Defaults to `true`. If set to `false`, poetry will install dependencies into the current python environment. -!!!note: +!!!note + When setting this configuration to `false`, the Python environment used must have `pip` installed and available. @@ -129,10 +129,12 @@ If set to `false`, poetry will install dependencies into the current python envi Create the virtualenv inside the project's root directory. Defaults to `None`. -If set to `true`, the virtualenv will be created and expected in a folder named `.venv` within the root directory of the project. - -If not set explicitly (default), `poetry` will use the virtualenv from the `.venv` directory when one is available. If set to `false`, `poetry` will ignore any existing `.venv` directory. +If set to `true`, the virtualenv will be created and expected in a folder named +`.venv` within the root directory of the project. +If not set explicitly (default), `poetry` will use the virtualenv from the `.venv` +directory when one is available. If set to `false`, `poetry` will ignore any +existing `.venv` directory. ### `virtualenvs.path`: string From 7360b09e4ba3c01e1d5dc6eaaf34cb3ff57bc16e Mon Sep 17 00:00:00 2001 From: Dan Yeaw Date: Sat, 20 Feb 2021 22:10:15 -0500 Subject: [PATCH 106/222] utils/envs: fix envs in MSYS2 always broken Fixes #2867, the virtual environment found seems to be broken messages when running in MSYS2. This change detects when in MSYS2 in order to use the bin directory for the virtualenv instead of Scripts which is normally used in Windows. --- poetry/utils/env.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/poetry/utils/env.py b/poetry/utils/env.py index d930b0d0316..cd1b2c3c8ea 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -879,9 +879,13 @@ class Env(object): def __init__(self, path: Path, base: Optional[Path] = None) -> None: self._is_windows = sys.platform == "win32" + self._is_mingw = sysconfig.get_platform() == "mingw" + if not self._is_windows or self._is_mingw: + bin_dir = "bin" + else: + bin_dir = "Scripts" self._path = path - bin_dir = "bin" if not self._is_windows else "Scripts" self._bin_dir = self._path / bin_dir self._base = base or path From 129ed0e1c4580683c5699ab780fed233c308fd18 Mon Sep 17 00:00:00 2001 From: Thomas Gaudin Date: Tue, 16 Mar 2021 23:36:43 +0100 Subject: [PATCH 107/222] Move multiple constraints note in dependency-specification.md I think the "Expanded dependency specification syntax" paragraph added in commit c94c34c813060abfaebc2632c58abe063e40efd3 was inserted in the wrong place and cut the note from its corresponding block. --- docs/docs/dependency-specification.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/docs/dependency-specification.md b/docs/docs/dependency-specification.md index d90067d31e3..97888a02cd7 100644 --- a/docs/docs/dependency-specification.md +++ b/docs/docs/dependency-specification.md @@ -185,6 +185,11 @@ foo = [ ] ``` +!!!note + + The constraints **must** have different requirements (like `python`) + otherwise it will cause an error when resolving dependencies. + ## Expanded dependency specification syntax In the case of more complex dependency specifications, you may find that you @@ -212,8 +217,3 @@ markers = "platform_python_implementation == 'CPython'" All of the same information is still present, and ends up providing the exact same specification. It's simply split into multiple, slightly more readable, lines. - -!!!note - - The constraints **must** have different requirements (like `python`) - otherwise it will cause an error when resolving dependencies. From 2de58ebe74dd93129f3a41b00e89085bea9dcb5a Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Mon, 22 Mar 2021 00:47:33 +0100 Subject: [PATCH 108/222] commands/show: fix rendering of single package --- poetry/console/commands/show.py | 2 +- tests/console/commands/test_show.py | 40 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/poetry/console/commands/show.py b/poetry/console/commands/show.py index 042f4163c8a..799fa4d78db 100644 --- a/poetry/console/commands/show.py +++ b/poetry/console/commands/show.py @@ -124,7 +124,7 @@ def handle(self) -> Optional[int]: ] table.add_rows(rows) - table.render(self.io) + table.render() if pkg.requires: self.line("") diff --git a/tests/console/commands/test_show.py b/tests/console/commands/test_show.py index f31c82882ea..2c69d3a3667 100644 --- a/tests/console/commands/test_show.py +++ b/tests/console/commands/test_show.py @@ -84,6 +84,46 @@ def test_show_basic_with_installed_packages(tester, poetry, installed): assert expected == tester.io.fetch_output() +def test_show_basic_with_installed_packages_single(tester, poetry, installed): + poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0")) + + cachy_010 = get_package("cachy", "0.1.0") + cachy_010.description = "Cachy package" + + installed.add_package(cachy_010) + + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "hashes": {"cachy": []}, + }, + } + ) + + tester.execute("cachy") + + assert [ + "name : cachy", + "version : 0.1.0", + "description : Cachy package", + ] == [line.strip() for line in tester.io.fetch_output().splitlines()] + + def test_show_basic_with_not_installed_packages_non_decorated( tester, poetry, installed ): From 23a1c98dead1b9184bbe51a08d57327154cf441b Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Mon, 22 Mar 2021 01:01:44 +0100 Subject: [PATCH 109/222] Revert "tests/executor: set directory dep as editable" This reverts commit 37b36f48 as this is no longer required. --- tests/installation/test_executor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 444a8af2211..ab911aba2a9 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -94,7 +94,6 @@ def test_execute_executes_a_batch_of_operations( .resolve() .as_posix(), ) - directory_package.develop = True git_package = Package( "demo", From 65c105ab3947c7d096929dfc3e46ba5e8435af8f Mon Sep 17 00:00:00 2001 From: Maximilian <39695405+maxispeicher@users.noreply.github.com> Date: Sun, 21 Mar 2021 00:04:40 +0100 Subject: [PATCH 110/222] exporter: ensure local references use uri This change ensures that when exporting `requirements.txt`, local direct reference dependencies are exported as uri and not paths. Resolves: #3189 --- poetry/utils/exporter.py | 22 +++++++---- tests/utils/test_exporter.py | 76 +++++++++++++++++++++++++++++++++++- 2 files changed, 89 insertions(+), 9 deletions(-) diff --git a/poetry/utils/exporter.py b/poetry/utils/exporter.py index c15d4508150..b3630aa6184 100644 --- a/poetry/utils/exporter.py +++ b/poetry/utils/exporter.py @@ -7,6 +7,7 @@ from cleo.io.io import IO +from poetry.core.packages.utils.utils import path_to_url from poetry.poetry import Poetry from poetry.utils._compat import decode @@ -71,23 +72,30 @@ def _export_requirements_txt( line += "-e " requirement = dependency.to_pep_508(with_extras=False) - is_direct_reference = ( - dependency.is_vcs() - or dependency.is_url() - or dependency.is_file() - or dependency.is_directory() + is_direct_local_reference = ( + dependency.is_file() or dependency.is_directory() ) + is_direct_remote_reference = dependency.is_vcs() or dependency.is_url() - if is_direct_reference: + if is_direct_remote_reference: line = requirement + elif is_direct_local_reference: + dependency_uri = path_to_url(dependency.source_url) + line = "{} @ {}".format(dependency.name, dependency_uri) else: line = "{}=={}".format(package.name, package.version) + + if not is_direct_remote_reference: if ";" in requirement: markers = requirement.split(";", 1)[1].strip() if markers: line += "; {}".format(markers) - if not is_direct_reference and package.source_url: + if ( + not is_direct_remote_reference + and not is_direct_local_reference + and package.source_url + ): indexes.add(package.source_url) if package.files and with_hashes: diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index f95c24627fa..eb3e000feed 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -1026,7 +1026,79 @@ def test_exporter_can_export_requirements_txt_with_directory_packages( expected = """\ foo @ {}/tests/fixtures/sample_project """.format( - working_directory.as_posix() + working_directory.as_uri() + ) + + assert expected == content + + +def test_exporter_can_export_requirements_txt_with_nested_directory_packages( + tmp_dir, poetry, working_directory +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + "source": { + "type": "directory", + "url": "tests/fixtures/sample_project", + "reference": "", + }, + }, + { + "name": "bar", + "version": "4.5.6", + "category": "main", + "optional": False, + "python-versions": "*", + "source": { + "type": "directory", + "url": "tests/fixtures/sample_project/../project_with_nested_local/bar", + "reference": "", + }, + }, + { + "name": "baz", + "version": "7.8.9", + "category": "main", + "optional": False, + "python-versions": "*", + "source": { + "type": "directory", + "url": "tests/fixtures/sample_project/../project_with_nested_local/bar/..", + "reference": "", + }, + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": [], "bar": [], "baz": []}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +bar @ {}/tests/fixtures/project_with_nested_local/bar +baz @ {}/tests/fixtures/project_with_nested_local +foo @ {}/tests/fixtures/sample_project +""".format( + working_directory.as_uri(), + working_directory.as_uri(), + working_directory.as_uri(), ) assert expected == content @@ -1071,7 +1143,7 @@ def test_exporter_can_export_requirements_txt_with_directory_packages_and_marker expected = """\ foo @ {}/tests/fixtures/sample_project; python_version < "3.7" """.format( - working_directory.as_posix() + working_directory.as_uri() ) assert expected == content From 3b340b23256a875afd0bbd574c0b44d62480ac3b Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 25 Sep 2020 02:21:10 +0200 Subject: [PATCH 111/222] repository/legacy: calculate sha256 if unavailable In some cases, legacy repositories might not provide a checksum as a url fragment or use a deprecated algorithm. In these scenarios, this change ensures that poetry downloads and calculates the sha256 checksum for the file. Resolves: #1631 #1553 --- poetry/repositories/legacy_repository.py | 41 +++++++++++++++++-- .../repositories/fixtures/legacy/ipython.html | 4 +- tests/repositories/test_legacy_repository.py | 32 ++++++++++++++- 3 files changed, 69 insertions(+), 8 deletions(-) diff --git a/poetry/repositories/legacy_repository.py b/poetry/repositories/legacy_repository.py index 1a4de68769a..63085a760c6 100644 --- a/poetry/repositories/legacy_repository.py +++ b/poetry/repositories/legacy_repository.py @@ -1,4 +1,5 @@ import cgi +import hashlib import re import urllib.parse import warnings @@ -27,6 +28,8 @@ from poetry.core.semver.version_range import VersionRange from poetry.locations import REPOSITORY_CACHE_DIR from poetry.utils.helpers import canonicalize_name +from poetry.utils.helpers import download_file +from poetry.utils.helpers import temporary_directory from poetry.utils.patterns import wheel_file_re from ..config.config import Config @@ -374,10 +377,37 @@ def _get_release_info(self, name: str, version: str) -> dict: ): urls["sdist"].append(link.url) - h = link.hash - if h: - h = link.hash_name + ":" + link.hash - files.append({"file": link.filename, "hash": h}) + file_hash = "{}:{}".format(link.hash_name, link.hash) if link.hash else None + + if not link.hash or ( + link.hash_name not in ("sha256", "sha384", "sha512") + and hasattr(hashlib, link.hash_name) + ): + with temporary_directory() as temp_dir: + filepath = Path(temp_dir) / link.filename + self._download(link.url, str(filepath)) + + known_hash = ( + getattr(hashlib, link.hash_name)() if link.hash_name else None + ) + required_hash = hashlib.sha256() + + chunksize = 4096 + with filepath.open("rb") as f: + while True: + chunk = f.read(chunksize) + if not chunk: + break + if known_hash: + known_hash.update(chunk) + required_hash.update(chunk) + + if not known_hash or known_hash.hexdigest() == link.hash: + file_hash = "{}:{}".format( + required_hash.name, required_hash.hexdigest() + ) + + files.append({"file": link.filename, "hash": file_hash}) data.files = files @@ -415,3 +445,6 @@ def _get(self, endpoint: str) -> Optional[Page]: ) return Page(response.url, response.content, response.headers) + + def _download(self, url, dest): # type: (str, str) -> None + return download_file(url, dest, session=self.session) diff --git a/tests/repositories/fixtures/legacy/ipython.html b/tests/repositories/fixtures/legacy/ipython.html index cbdc19e5850..5b61c92d784 100644 --- a/tests/repositories/fixtures/legacy/ipython.html +++ b/tests/repositories/fixtures/legacy/ipython.html @@ -5,10 +5,10 @@

Links for ipython

- ipython-5.7.0-py2-none-any.whl
+ ipython-5.7.0-py2-none-any.whl
ipython-5.7.0-py3-none-any.whl
ipython-5.7.0.tar.gz
- ipython-7.5.0-py3-none-any.whl
+ ipython-7.5.0-py3-none-any.whl
ipython-7.5.0.tar.gz
diff --git a/tests/repositories/test_legacy_repository.py b/tests/repositories/test_legacy_repository.py index 8ced98e4e44..5f49110d975 100644 --- a/tests/repositories/test_legacy_repository.py +++ b/tests/repositories/test_legacy_repository.py @@ -273,7 +273,7 @@ def test_get_package_retrieves_non_sha256_hashes(): expected = [ { "file": "ipython-7.5.0-py3-none-any.whl", - "hash": "md5:dbdc53e3918f28fa335a173432402a00", + "hash": "sha256:78aea20b7991823f6a32d55f4e963a61590820e43f666ad95ad07c7f0c704efa", }, { "file": "ipython-7.5.0.tar.gz", @@ -284,12 +284,40 @@ def test_get_package_retrieves_non_sha256_hashes(): assert expected == package.files +def test_get_package_retrieves_non_sha256_hashes_mismatching_known_hash(): + repo = MockRepository() + + package = repo.package("ipython", "5.7.0") + + expected = [ + { + "file": "ipython-5.7.0-py2-none-any.whl", + "hash": "md5:a10a802ef98da741cd6f4f6289d47ba7", + }, + { + "file": "ipython-5.7.0-py3-none-any.whl", + "hash": "sha256:fc0464e68f9c65cd8c453474b4175432cc29ecb6c83775baedf6dbfcee9275ab", + }, + { + "file": "ipython-5.7.0.tar.gz", + "hash": "sha256:8db43a7fb7619037c98626613ff08d03dda9d5d12c84814a4504c78c0da8323c", + }, + ] + + assert expected == package.files + + def test_get_package_retrieves_packages_with_no_hashes(): repo = MockRepository() package = repo.package("jupyter", "1.0.0") - assert [] == package.files + assert [ + { + "file": "jupyter-1.0.0.tar.gz", + "hash": "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f", + } + ] == package.files class MockHttpRepository(LegacyRepository): From 2f5098d6c9cff963a674430eb02b30b4eef39d33 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Mon, 22 Mar 2021 01:21:34 +0100 Subject: [PATCH 112/222] Disable setup file generation for builds --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 2a59102ff77..d0089b0883b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,9 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules" ] +[tool.poetry.build] +generate-setup-file = false + # Requirements [tool.poetry.dependencies] python = "^3.6" From eea86dfe1d3dcb846409140ac20ded62fa62c454 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Wed, 30 Sep 2020 18:16:01 +0200 Subject: [PATCH 113/222] ci: decrease pytest verbosity to speedup runs --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4c9bdd876c7..c1eb33fe187 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -65,4 +65,4 @@ jobs: - name: Run pytest shell: bash - run: python -m poetry run python -m pytest -v tests + run: python -m poetry run python -m pytest -p no:sugar -q tests/ From b7bb4ddf5fab46becd1e13d963577759be64d049 Mon Sep 17 00:00:00 2001 From: stephsamson Date: Sun, 5 Apr 2020 14:26:50 +0200 Subject: [PATCH 114/222] Add documentation on inline tables in include. Resolves: Core #8. --- docs/docs/pyproject.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/docs/pyproject.md b/docs/docs/pyproject.md index a398329c675..5a2271c1fc6 100644 --- a/docs/docs/pyproject.md +++ b/docs/docs/pyproject.md @@ -173,6 +173,17 @@ If a VCS is being used for a package, the exclude field will be seeded with the include = ["CHANGELOG.md"] ``` +You can also use inline tables for `include`: + +```toml +[tool.poetry] +# ... +include = [ + { path = "tests", format = "sdist" }, + { path = "for_wheel.txt", format = "wheel" } +] +``` + ```toml exclude = ["my_package/excluded.py"] ``` From 28f434b886562bf34f1e958e817075025ea5c0c4 Mon Sep 17 00:00:00 2001 From: stephsamson Date: Tue, 24 Nov 2020 13:51:43 +0100 Subject: [PATCH 115/222] Per comments. --- docs/docs/pyproject.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/pyproject.md b/docs/docs/pyproject.md index 5a2271c1fc6..3f1cd4ffdb0 100644 --- a/docs/docs/pyproject.md +++ b/docs/docs/pyproject.md @@ -173,14 +173,14 @@ If a VCS is being used for a package, the exclude field will be seeded with the include = ["CHANGELOG.md"] ``` -You can also use inline tables for `include`: +You can also specify the formats for which these patterns have to be included, as shown here: ```toml [tool.poetry] # ... include = [ { path = "tests", format = "sdist" }, - { path = "for_wheel.txt", format = "wheel" } + { path = "for_wheel.txt", format = ["sdist", "wheel"] } ] ``` From e3b48c5bb4644d16196cfc3ee979de0aeb3408f3 Mon Sep 17 00:00:00 2001 From: stephsamson Date: Fri, 27 Nov 2020 13:10:51 +0100 Subject: [PATCH 116/222] Specify default for inline table format. --- docs/docs/pyproject.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/docs/pyproject.md b/docs/docs/pyproject.md index 3f1cd4ffdb0..5511086cccc 100644 --- a/docs/docs/pyproject.md +++ b/docs/docs/pyproject.md @@ -184,6 +184,8 @@ include = [ ] ``` +If no format is specified, it will default to include to a `wheel` only. + ```toml exclude = ["my_package/excluded.py"] ``` From 0eaf9430da1ffa02dfdf88a07c7a1f9a1f24bd85 Mon Sep 17 00:00:00 2001 From: stephsamson Date: Fri, 27 Nov 2020 14:33:49 +0100 Subject: [PATCH 117/222] Default to both sdist and wheel --- docs/docs/pyproject.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/pyproject.md b/docs/docs/pyproject.md index 5511086cccc..8430ad308c6 100644 --- a/docs/docs/pyproject.md +++ b/docs/docs/pyproject.md @@ -184,7 +184,7 @@ include = [ ] ``` -If no format is specified, it will default to include to a `wheel` only. +If no format is specified, it will default to include both `sdist` and `wheel`. ```toml exclude = ["my_package/excluded.py"] From f739381e6c765f7e5eda3df38ed3098a4f0f1dbe Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 18 Aug 2020 01:04:02 +0200 Subject: [PATCH 118/222] Drop pip from critical packages in project venv Use embedded pip wheel from virtualenv package. This avoids the need for pip to be a critical package in the project's virtual environment. --- poetry/inspection/info.py | 7 ++-- poetry/puzzle/provider.py | 2 +- poetry/utils/env.py | 32 ++++++++++++++----- tests/inspection/test_info.py | 2 +- tests/installation/test_installer.py | 10 ++++-- tests/installation/test_installer_old.py | 8 +++-- .../masonry/builders/test_editable_builder.py | 12 ++----- tests/puzzle/test_solver.py | 2 +- 8 files changed, 44 insertions(+), 31 deletions(-) diff --git a/poetry/inspection/info.py b/poetry/inspection/info.py index 4ccaf429d6d..f0347818d9e 100644 --- a/poetry/inspection/info.py +++ b/poetry/inspection/info.py @@ -454,17 +454,14 @@ def _pep517_metadata(cls, path: Path) -> "PackageInfo": 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(venv_dir.as_posix()) + EnvManager.build_venv(venv_dir.as_posix(), with_pip=True) venv = VirtualEnv(venv_dir, venv_dir) dest_dir = Path(tmp_dir) / "dist" dest_dir.mkdir() try: - venv.run( - "python", - "-m", - "pip", + venv.run_pip( "install", "--disable-pip-version-check", "--ignore-installed", diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index d7576bb7a23..25b43180d3b 100644 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -54,7 +54,7 @@ def _formatter_elapsed(self) -> str: class Provider: - UNSAFE_PACKAGES = {"setuptools", "distribute", "pip", "wheel"} + UNSAFE_PACKAGES = {"setuptools"} def __init__( self, package: Package, pool: Pool, io: Any, env: Optional[Env] = None diff --git a/poetry/utils/env.py b/poetry/utils/env.py index cd1b2c3c8ea..e2d5a831529 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -30,6 +30,7 @@ from packaging.tags import interpreter_name from packaging.tags import interpreter_version from packaging.tags import sys_tags +from virtualenv.seed.wheels.embed import get_embed_wheel from poetry.core.semver.helpers import parse_constraint from poetry.core.semver.version import Version @@ -810,6 +811,7 @@ def build_venv( path: Union[Path, str], executable: Optional[Union[str, Path]] = None, flags: Dict[str, bool] = None, + with_pip: bool = False, ) -> virtualenv.run.session.Session: flags = flags or {} @@ -821,12 +823,17 @@ def build_venv( "--no-periodic-update", "--python", executable or sys.executable, - str(path), ] + if not with_pip: + # we cannot drop setuptools yet because we do editable installs (git, path) in project envs + args.extend(["--no-pip", "--no-wheel"]) + for flag, value in flags.items(): if value is True: - args.insert(0, "--{}".format(flag)) + args.append("--{}".format(flag)) + + args.append(str(path)) return virtualenv.cli_run(args) @@ -929,12 +936,21 @@ def marker_env(self) -> Dict[str, Any]: return self._marker_env + def get_embedded_wheel(self, distribution): + return get_embed_wheel( + distribution, "{}.{}".format(self.version_info[0], self.version_info[1]) + ).path + @property def pip(self) -> str: """ Path to current pip executable """ - return self._bin("pip") + # we do not use as_posix() here due to issues with windows pathlib2 implementation + path = self._bin("pip") + if not Path(path).exists(): + return str(self.get_embedded_wheel("pip") / "pip") + return path @property def platform(self) -> str: @@ -1181,7 +1197,7 @@ def get_python_implementation(self) -> str: def get_pip_command(self) -> List[str]: # If we're not in a venv, assume the interpreter we're running on # has a pip and use that - return [sys.executable, "-m", "pip"] + return [sys.executable, self.pip] def get_paths(self) -> Dict[str, str]: # We can't use sysconfig.get_paths() because @@ -1289,7 +1305,7 @@ def get_python_implementation(self) -> str: def get_pip_command(self) -> List[str]: # We're in a virtualenv that is known to be sane, # so assume that we have a functional pip - return [self._bin("pip")] + return [self._bin("python"), self.pip] def get_supported_tags(self) -> List[Tag]: file_path = Path(packaging.tags.__file__) @@ -1343,8 +1359,8 @@ def is_venv(self) -> bool: return True def is_sane(self) -> bool: - # A virtualenv is considered sane if both "python" and "pip" exist. - return os.path.exists(self.python) and os.path.exists(self._bin("pip")) + # A virtualenv is considered sane if "python" exists. + return os.path.exists(self.python) def _run(self, cmd: List[str], **kwargs: Any) -> Optional[int]: with self.temp_environ(): @@ -1396,7 +1412,7 @@ def __init__( self.executed = [] def get_pip_command(self) -> List[str]: - return [self._bin("python"), "-m", "pip"] + return [self._bin("python"), self.pip] def _run(self, cmd: List[str], **kwargs: Any) -> int: self.executed.append(cmd) diff --git a/tests/inspection/test_info.py b/tests/inspection/test_info.py index 3a15e605a6f..215109d551f 100644 --- a/tests/inspection/test_info.py +++ b/tests/inspection/test_info.py @@ -217,7 +217,7 @@ def test_info_setup_missing_mandatory_should_trigger_pep517( except PackageInfoError: assert spy.call_count == 3 else: - assert spy.call_count == 2 + assert spy.call_count == 1 def test_info_prefer_poetry_config_over_egg_info(): diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 7cba8667a67..980bbd85dd6 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -392,15 +392,19 @@ def test_run_install_remove_untracked(installer, locker, repo, package, installe package_b = get_package("b", "1.1") package_c = get_package("c", "1.2") package_pip = get_package("pip", "20.0.0") + package_setuptools = get_package("setuptools", "20.0.0") + repo.add_package(package_a) repo.add_package(package_b) repo.add_package(package_c) repo.add_package(package_pip) + repo.add_package(package_setuptools) installed.add_package(package_a) installed.add_package(package_b) installed.add_package(package_c) - installed.add_package(package_pip) # Always required and never removed. + installed.add_package(package_pip) + installed.add_package(package_setuptools) # Always required and never removed. installed.add_package(package) # Root package never removed. package.add_dependency(Factory.create_dependency("A", "~1.0")) @@ -410,8 +414,8 @@ def test_run_install_remove_untracked(installer, locker, repo, package, installe assert 0 == installer.executor.installations_count assert 0 == installer.executor.updates_count - assert 2 == installer.executor.removals_count - assert {"b", "c"} == set(r.name for r in installer.executor.removals) + assert 3 == installer.executor.removals_count + assert {"b", "c", "pip"} == set(r.name for r in installer.executor.removals) def test_run_whitelist_add(installer, locker, repo, package): diff --git a/tests/installation/test_installer_old.py b/tests/installation/test_installer_old.py index a9cc4879d9d..046d2dd1968 100644 --- a/tests/installation/test_installer_old.py +++ b/tests/installation/test_installer_old.py @@ -318,15 +318,19 @@ def test_run_install_remove_untracked(installer, locker, repo, package, installe package_b = get_package("b", "1.1") package_c = get_package("c", "1.2") package_pip = get_package("pip", "20.0.0") + package_setuptools = get_package("setuptools", "20.0.0") + repo.add_package(package_a) repo.add_package(package_b) repo.add_package(package_c) repo.add_package(package_pip) + repo.add_package(package_setuptools) installed.add_package(package_a) installed.add_package(package_b) installed.add_package(package_c) - installed.add_package(package_pip) # Always required and never removed. + installed.add_package(package_pip) + installed.add_package(package_setuptools) # Always required and never removed. installed.add_package(package) # Root package never removed. package.add_dependency(Factory.create_dependency("A", "~1.0")) @@ -341,7 +345,7 @@ def test_run_install_remove_untracked(installer, locker, repo, package, installe assert len(updates) == 0 removals = installer.installer.removals - assert set(r.name for r in removals) == {"b", "c"} + assert set(r.name for r in removals) == {"b", "c", "pip"} def test_run_whitelist_add(installer, locker, repo, package): diff --git a/tests/masonry/builders/test_editable_builder.py b/tests/masonry/builders/test_editable_builder.py index 8e236b9b188..98fc6c2eb89 100644 --- a/tests/masonry/builders/test_editable_builder.py +++ b/tests/masonry/builders/test_editable_builder.py @@ -185,17 +185,9 @@ def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts( builder = EditableBuilder(extended_poetry, env, NullIO()) builder.build() - assert [ - [ - "python", - "-m", - "pip", - "install", - "-e", - str(extended_poetry.file.parent), - "--no-deps", - ] + env.get_pip_command() + + ["install", "-e", str(extended_poetry.file.parent), "--no-deps"] ] == env.executed diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 9b4b9a7a0a8..0c2bc196ba1 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -2407,7 +2407,7 @@ def test_solver_remove_untracked_keeps_critical_package( package, pool, installed, locked, io ): solver = Solver(package, pool, installed, locked, io, remove_untracked=True) - package_pip = get_package("pip", "1.0") + package_pip = get_package("setuptools", "1.0") installed.add_package(package_pip) ops = solver.solve() From 9a68da31b088b71f3f84868892b4940ba7e0481e Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 18 Aug 2020 13:29:20 +0200 Subject: [PATCH 119/222] Drop setuptools from critical package list --- poetry.lock | 472 ++++++++++-------- poetry/installation/pip_installer.py | 16 +- poetry/puzzle/provider.py | 2 +- poetry/utils/env.py | 3 +- pyproject.toml | 1 + .../fixtures/with-pypi-repository.test | 17 + tests/installation/test_installer.py | 13 +- tests/installation/test_installer_old.py | 9 +- tests/puzzle/test_solver.py | 1 + 9 files changed, 310 insertions(+), 224 deletions(-) diff --git a/poetry.lock b/poetry.lock index 9e2466ffb3c..dd00e3385b6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -60,7 +60,7 @@ msgpack = ["msgpack-python (>=0.5,<0.6)"] [[package]] name = "certifi" -version = "2020.11.8" +version = "2020.12.5" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -68,7 +68,7 @@ python-versions = "*" [[package]] name = "cffi" -version = "1.14.3" +version = "1.14.5" description = "Foreign Function Interface for Python calling C code." category = "main" optional = false @@ -87,15 +87,15 @@ python-versions = ">=3.6.1" [[package]] name = "chardet" -version = "3.0.4" +version = "4.0.0" description = "Universal encoding detector for Python 2 and 3" category = "main" optional = false -python-versions = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "cleo" -version = "1.0.0a1" +version = "1.0.0a3" description = "Cleo allows you to create beautiful and testable command-line interfaces." category = "main" optional = false @@ -115,7 +115,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "5.3" +version = "5.5" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -134,22 +134,36 @@ python-versions = ">=3.6,<4.0" [[package]] name = "cryptography" -version = "3.2.1" +version = "3.4.6" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +python-versions = ">=3.6" [package.dependencies] -cffi = ">=1.8,<1.11.3 || >1.11.3" -six = ">=1.4.1" +cffi = ">=1.12" [package.extras] docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +sdist = ["setuptools-rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=3.6.0,!=3.9.0,!=3.9.1,!=3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] + +[[package]] +name = "deepdiff" +version = "5.2.3" +description = "Deep Difference and Search of any Python object/data." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +ordered-set = "4.0.2" + +[package.extras] +cli = ["click (==7.1.2)", "pyyaml (==5.3.1)", "toml (==0.10.2)", "clevercsv (==0.6.6)"] [[package]] name = "distlib" @@ -187,7 +201,7 @@ lxml = ["lxml"] [[package]] name = "httpretty" -version = "1.0.2" +version = "1.0.5" description = "HTTP client mock for Python" category = "dev" optional = false @@ -195,14 +209,14 @@ python-versions = ">=3" [[package]] name = "identify" -version = "1.5.10" +version = "2.2.0" description = "File identification library for Python" category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = ">=3.6.1" [package.extras] -license = ["editdistance"] +license = ["editdistance-s"] [[package]] name = "idna" @@ -229,17 +243,18 @@ testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] [[package]] name = "importlib-resources" -version = "3.3.0" +version = "5.1.2" description = "Read resources from Python packages" category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.6" [package.dependencies] zipp = {version = ">=0.4", markers = "python_version < \"3.8\""} [package.extras] -docs = ["sphinx", "rst.linker", "jaraco.packaging"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"] [[package]] name = "jeepney" @@ -254,7 +269,7 @@ test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio"] [[package]] name = "keyring" -version = "21.5.0" +version = "21.8.0" description = "Store and access your passwords safely." category = "main" optional = false @@ -267,7 +282,7 @@ pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "pytest-black (>=0.3.7)", "pytest-mypy"] [[package]] @@ -280,7 +295,7 @@ python-versions = "*" [[package]] name = "more-itertools" -version = "8.6.0" +version = "8.7.0" description = "More routines for operating on iterables, beyond itertools" category = "dev" optional = false @@ -288,7 +303,7 @@ python-versions = ">=3.5" [[package]] name = "msgpack" -version = "1.0.0" +version = "1.0.2" description = "MessagePack (de)serializer." category = "main" optional = false @@ -302,9 +317,17 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "ordered-set" +version = "4.0.2" +description = "A set that remembers its order, and allows looking up its items by their index in that order." +category = "dev" +optional = false +python-versions = ">=3.5" + [[package]] name = "packaging" -version = "20.4" +version = "20.9" description = "Core utilities for Python packages" category = "main" optional = false @@ -312,7 +335,6 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pyparsing = ">=2.0.2" -six = "*" [[package]] name = "pexpect" @@ -327,7 +349,7 @@ ptyprocess = ">=0.5" [[package]] name = "pkginfo" -version = "1.6.1" +version = "1.7.0" description = "Query metadatdata from sdists / bdists / installed packages." category = "main" optional = false @@ -370,7 +392,7 @@ resolved_reference = "5d5251c427aacedcf54f9743635a8124e5a26151" [[package]] name = "pre-commit" -version = "2.9.0" +version = "2.11.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false @@ -388,7 +410,7 @@ virtualenv = ">=20.0.8" [[package]] name = "ptyprocess" -version = "0.6.0" +version = "0.7.0" description = "Run a subprocess in a pseudo terminal" category = "main" optional = false @@ -396,7 +418,7 @@ python-versions = "*" [[package]] name = "py" -version = "1.9.0" +version = "1.10.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "dev" optional = false @@ -451,14 +473,14 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "pytest-cov" -version = "2.10.1" +version = "2.11.1" description = "Pytest plugin for measuring coverage." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] -coverage = ">=4.4" +coverage = ">=5.2.1" pytest = ">=4.6" [package.extras] @@ -501,15 +523,15 @@ python-versions = "*" [[package]] name = "pyyaml" -version = "5.3.1" +version = "5.4.1" description = "YAML parser and emitter for Python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] name = "requests" -version = "2.25.0" +version = "2.25.1" description = "Python HTTP for Humans." category = "main" optional = false @@ -517,7 +539,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<4" +chardet = ">=3.0.2,<5" idna = ">=2.5,<3" urllib3 = ">=1.21.1,<1.27" @@ -538,19 +560,19 @@ requests = ">=2.0.1,<3.0.0" [[package]] name = "secretstorage" -version = "3.2.0" +version = "3.3.1" description = "Python bindings to FreeDesktop.org Secret Service API" category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] cryptography = ">=2.0" -jeepney = ">=0.4.2" +jeepney = ">=0.6" [[package]] name = "shellingham" -version = "1.3.2" +version = "1.4.0" description = "Tool to Detect Surrounding Shell" category = "main" optional = false @@ -590,7 +612,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "tox" -version = "3.20.1" +version = "3.23.0" description = "tox is a generic virtualenv management and test command line tool" category = "dev" optional = false @@ -599,7 +621,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} filelock = ">=3.0.0" -importlib-metadata = {version = ">=0.12,<3", markers = "python_version < \"3.8\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} packaging = ">=14" pluggy = ">=0.12.0" py = ">=1.4.17" @@ -609,7 +631,7 @@ virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2, [package.extras] docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] -testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "pytest-xdist (>=1.22.2)"] +testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "pytest-xdist (>=1.22.2)", "pathlib2 (>=2.3.3)"] [[package]] name = "urllib3" @@ -626,7 +648,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.2.1" +version = "20.4.3" description = "Virtual Python Environment builder" category = "main" optional = false @@ -642,7 +664,7 @@ six = ">=1.9.0,<2" [package.extras] docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] -testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "pytest-xdist (>=1.31.0)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] [[package]] name = "wcwidth" @@ -662,20 +684,20 @@ python-versions = "*" [[package]] name = "zipp" -version = "3.4.0" +version = "3.4.1" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.6" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "0039f039cc6768e38203b79e8aee64fa11a96b781da515b68587b90c97ed048a" +content-hash = "fa762bec451ef26021b1882a6f657ca2c0b3eeff514e28f805dfa7dfc1a02495" [metadata.files] appdirs = [ @@ -699,126 +721,139 @@ cachy = [ {file = "cachy-0.3.0.tar.gz", hash = "sha256:186581f4ceb42a0bbe040c407da73c14092379b1e4c0e327fdb72ae4a9b269b1"}, ] certifi = [ - {file = "certifi-2020.11.8-py2.py3-none-any.whl", hash = "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd"}, - {file = "certifi-2020.11.8.tar.gz", hash = "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"}, + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, ] cffi = [ - {file = "cffi-1.14.3-2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc"}, - {file = "cffi-1.14.3-2-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768"}, - {file = "cffi-1.14.3-2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d"}, - {file = "cffi-1.14.3-2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1"}, - {file = "cffi-1.14.3-2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca"}, - {file = "cffi-1.14.3-2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a"}, - {file = "cffi-1.14.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c"}, - {file = "cffi-1.14.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730"}, - {file = "cffi-1.14.3-cp27-cp27m-win32.whl", hash = "sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d"}, - {file = "cffi-1.14.3-cp27-cp27m-win_amd64.whl", hash = "sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05"}, - {file = "cffi-1.14.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b"}, - {file = "cffi-1.14.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171"}, - {file = "cffi-1.14.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f"}, - {file = "cffi-1.14.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4"}, - {file = "cffi-1.14.3-cp35-cp35m-win32.whl", hash = "sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d"}, - {file = "cffi-1.14.3-cp35-cp35m-win_amd64.whl", hash = "sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d"}, - {file = "cffi-1.14.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3"}, - {file = "cffi-1.14.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808"}, - {file = "cffi-1.14.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537"}, - {file = "cffi-1.14.3-cp36-cp36m-win32.whl", hash = "sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0"}, - {file = "cffi-1.14.3-cp36-cp36m-win_amd64.whl", hash = "sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e"}, - {file = "cffi-1.14.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1"}, - {file = "cffi-1.14.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579"}, - {file = "cffi-1.14.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394"}, - {file = "cffi-1.14.3-cp37-cp37m-win32.whl", hash = "sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc"}, - {file = "cffi-1.14.3-cp37-cp37m-win_amd64.whl", hash = "sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869"}, - {file = "cffi-1.14.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e"}, - {file = "cffi-1.14.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828"}, - {file = "cffi-1.14.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9"}, - {file = "cffi-1.14.3-cp38-cp38-win32.whl", hash = "sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522"}, - {file = "cffi-1.14.3-cp38-cp38-win_amd64.whl", hash = "sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15"}, - {file = "cffi-1.14.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d"}, - {file = "cffi-1.14.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c"}, - {file = "cffi-1.14.3-cp39-cp39-win32.whl", hash = "sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b"}, - {file = "cffi-1.14.3-cp39-cp39-win_amd64.whl", hash = "sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3"}, - {file = "cffi-1.14.3.tar.gz", hash = "sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591"}, + {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, + {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, + {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, + {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, + {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, + {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, + {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, + {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, + {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, + {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, + {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, + {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, + {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, + {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, + {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, + {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, + {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, + {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, + {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, ] cfgv = [ {file = "cfgv-3.2.0-py2.py3-none-any.whl", hash = "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d"}, {file = "cfgv-3.2.0.tar.gz", hash = "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1"}, ] chardet = [ - {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, - {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, ] cleo = [ - {file = "cleo-1.0.0a1-py3-none-any.whl", hash = "sha256:e4a45adc6b56a04d350e7b4893352fdcc07d89d35991e5df16753e05a7c78c2b"}, - {file = "cleo-1.0.0a1.tar.gz", hash = "sha256:45bc5f04278c2f183c7ab77b3ec20f5204711fecb37ae688424c39ea8badf3fe"}, + {file = "cleo-1.0.0a3-py3-none-any.whl", hash = "sha256:46b2f970d06caa311d1e12a1013b0ce2a8149502669ac82cbedafb9e0bfdbccd"}, + {file = "cleo-1.0.0a3.tar.gz", hash = "sha256:9c1c8dd06635c936f45e4649aa2f7581517b4d52c7a9414d1b42586e63c2fe5d"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ - {file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"}, - {file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"}, - {file = "coverage-5.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9"}, - {file = "coverage-5.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729"}, - {file = "coverage-5.3-cp27-cp27m-win32.whl", hash = "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d"}, - {file = "coverage-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418"}, - {file = "coverage-5.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9"}, - {file = "coverage-5.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5"}, - {file = "coverage-5.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822"}, - {file = "coverage-5.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097"}, - {file = "coverage-5.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9"}, - {file = "coverage-5.3-cp35-cp35m-win32.whl", hash = "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636"}, - {file = "coverage-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f"}, - {file = "coverage-5.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237"}, - {file = "coverage-5.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54"}, - {file = "coverage-5.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7"}, - {file = "coverage-5.3-cp36-cp36m-win32.whl", hash = "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a"}, - {file = "coverage-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d"}, - {file = "coverage-5.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"}, - {file = "coverage-5.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f"}, - {file = "coverage-5.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c"}, - {file = "coverage-5.3-cp37-cp37m-win32.whl", hash = "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751"}, - {file = "coverage-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709"}, - {file = "coverage-5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516"}, - {file = "coverage-5.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f"}, - {file = "coverage-5.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259"}, - {file = "coverage-5.3-cp38-cp38-win32.whl", hash = "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82"}, - {file = "coverage-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221"}, - {file = "coverage-5.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978"}, - {file = "coverage-5.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21"}, - {file = "coverage-5.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24"}, - {file = "coverage-5.3-cp39-cp39-win32.whl", hash = "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7"}, - {file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"}, - {file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"}, + {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, + {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, + {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, + {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, + {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, + {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, + {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, + {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, + {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, + {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, + {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, + {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, + {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, + {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, + {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, + {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, + {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, + {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, + {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, + {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, + {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, + {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, + {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, + {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, + {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, + {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, + {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, + {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, + {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, + {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, + {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, + {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, + {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, + {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, + {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, + {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, + {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, + {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, + {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, + {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, + {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, + {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, + {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, + {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, + {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, + {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, + {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, + {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] crashtest = [ {file = "crashtest-0.3.1-py3-none-any.whl", hash = "sha256:300f4b0825f57688b47b6d70c6a31de33512eb2fa1ac614f780939aa0cf91680"}, {file = "crashtest-0.3.1.tar.gz", hash = "sha256:42ca7b6ce88b6c7433e2ce47ea884e91ec93104a4b754998be498a8e6c3d37dd"}, ] cryptography = [ - {file = "cryptography-3.2.1-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7"}, - {file = "cryptography-3.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d"}, - {file = "cryptography-3.2.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33"}, - {file = "cryptography-3.2.1-cp27-cp27m-win32.whl", hash = "sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297"}, - {file = "cryptography-3.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4"}, - {file = "cryptography-3.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8"}, - {file = "cryptography-3.2.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e"}, - {file = "cryptography-3.2.1-cp35-abi3-macosx_10_10_x86_64.whl", hash = "sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b"}, - {file = "cryptography-3.2.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df"}, - {file = "cryptography-3.2.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77"}, - {file = "cryptography-3.2.1-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7"}, - {file = "cryptography-3.2.1-cp35-cp35m-win32.whl", hash = "sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b"}, - {file = "cryptography-3.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7"}, - {file = "cryptography-3.2.1-cp36-abi3-win32.whl", hash = "sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f"}, - {file = "cryptography-3.2.1-cp36-abi3-win_amd64.whl", hash = "sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538"}, - {file = "cryptography-3.2.1-cp36-cp36m-win32.whl", hash = "sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b"}, - {file = "cryptography-3.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13"}, - {file = "cryptography-3.2.1-cp37-cp37m-win32.whl", hash = "sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e"}, - {file = "cryptography-3.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb"}, - {file = "cryptography-3.2.1-cp38-cp38-win32.whl", hash = "sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b"}, - {file = "cryptography-3.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851"}, - {file = "cryptography-3.2.1.tar.gz", hash = "sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3"}, + {file = "cryptography-3.4.6-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799"}, + {file = "cryptography-3.4.6-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:4169a27b818de4a1860720108b55a2801f32b6ae79e7f99c00d79f2a2822eeb7"}, + {file = "cryptography-3.4.6-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:93cfe5b7ff006de13e1e89830810ecbd014791b042cbe5eec253be11ac2b28f3"}, + {file = "cryptography-3.4.6-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:5ecf2bcb34d17415e89b546dbb44e73080f747e504273e4d4987630493cded1b"}, + {file = "cryptography-3.4.6-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:fec7fb46b10da10d9e1d078d1ff8ed9e05ae14f431fdbd11145edd0550b9a964"}, + {file = "cryptography-3.4.6-cp36-abi3-win32.whl", hash = "sha256:df186fcbf86dc1ce56305becb8434e4b6b7504bc724b71ad7a3239e0c9d14ef2"}, + {file = "cryptography-3.4.6-cp36-abi3-win_amd64.whl", hash = "sha256:66b57a9ca4b3221d51b237094b0303843b914b7d5afd4349970bb26518e350b0"}, + {file = "cryptography-3.4.6-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:066bc53f052dfeda2f2d7c195cf16fb3e5ff13e1b6b7415b468514b40b381a5b"}, + {file = "cryptography-3.4.6-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:600cf9bfe75e96d965509a4c0b2b183f74a4fa6f5331dcb40fb7b77b7c2484df"}, + {file = "cryptography-3.4.6-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:0923ba600d00718d63a3976f23cab19aef10c1765038945628cd9be047ad0336"}, + {file = "cryptography-3.4.6-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724"}, + {file = "cryptography-3.4.6.tar.gz", hash = "sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87"}, +] +deepdiff = [ + {file = "deepdiff-5.2.3-py3-none-any.whl", hash = "sha256:3d3da4bd7e01fb5202088658ed26427104c748dda56a0ecfac9ce9a1d2d00844"}, + {file = "deepdiff-5.2.3.tar.gz", hash = "sha256:ae2cb98353309f93fbfdda4d77adb08fb303314d836bb6eac3d02ed71a10b40e"}, ] distlib = [ {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"}, @@ -833,11 +868,11 @@ html5lib = [ {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, ] httpretty = [ - {file = "httpretty-1.0.2.tar.gz", hash = "sha256:24a6fd2fe1c76e94801b74db8f52c0fb42718dc4a199a861b305b1a492b9d868"}, + {file = "httpretty-1.0.5.tar.gz", hash = "sha256:e53c927c4d3d781a0761727f1edfad64abef94e828718e12b672a678a8b3e0b5"}, ] identify = [ - {file = "identify-1.5.10-py2.py3-none-any.whl", hash = "sha256:cc86e6a9a390879dcc2976cef169dd9cc48843ed70b7380f321d1b118163c60e"}, - {file = "identify-1.5.10.tar.gz", hash = "sha256:943cd299ac7f5715fcb3f684e2fc1594c1e0f22a90d15398e5888143bd4144b5"}, + {file = "identify-2.2.0-py2.py3-none-any.whl", hash = "sha256:39c0b110c9d0cd2391b6c38cd0ff679ee4b4e98f8db8b06c5d9d9e502711a1e1"}, + {file = "identify-2.2.0.tar.gz", hash = "sha256:efbf090a619255bc31c4fbba709e2805f7d30913fd4854ad84ace52bd276e2f6"}, ] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, @@ -848,60 +883,73 @@ importlib-metadata = [ {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"}, ] importlib-resources = [ - {file = "importlib_resources-3.3.0-py2.py3-none-any.whl", hash = "sha256:a3d34a8464ce1d5d7c92b0ea4e921e696d86f2aa212e684451cb1482c8d84ed5"}, - {file = "importlib_resources-3.3.0.tar.gz", hash = "sha256:7b51f0106c8ec564b1bef3d9c588bc694ce2b92125bbb6278f4f2f5b54ec3592"}, + {file = "importlib_resources-5.1.2-py3-none-any.whl", hash = "sha256:ebab3efe74d83b04d6bf5cd9a17f0c5c93e60fb60f30c90f56265fce4682a469"}, + {file = "importlib_resources-5.1.2.tar.gz", hash = "sha256:642586fc4740bd1cad7690f836b3321309402b20b332529f25617ff18e8e1370"}, ] jeepney = [ {file = "jeepney-0.6.0-py3-none-any.whl", hash = "sha256:aec56c0eb1691a841795111e184e13cad504f7703b9a64f63020816afa79a8ae"}, {file = "jeepney-0.6.0.tar.gz", hash = "sha256:7d59b6622675ca9e993a6bd38de845051d315f8b0c72cca3aef733a20b648657"}, ] keyring = [ - {file = "keyring-21.5.0-py3-none-any.whl", hash = "sha256:12de23258a95f3b13e5b167f7a641a878e91eab8ef16fafc077720a95e6115bb"}, - {file = "keyring-21.5.0.tar.gz", hash = "sha256:207bd66f2a9881c835dad653da04e196c678bf104f8252141d2d3c4f31051579"}, + {file = "keyring-21.8.0-py3-none-any.whl", hash = "sha256:4be9cbaaaf83e61d6399f733d113ede7d1c73bc75cb6aeb64eee0f6ac39b30ea"}, + {file = "keyring-21.8.0.tar.gz", hash = "sha256:1746d3ac913d449a090caf11e9e4af00e26c3f7f7e81027872192b2398b98675"}, ] 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.6.0.tar.gz", hash = "sha256:b3a9005928e5bed54076e6e549c792b306fddfe72b2d1d22dd63d42d5d3899cf"}, - {file = "more_itertools-8.6.0-py3-none-any.whl", hash = "sha256:8e1a2a43b2f2727425f2b5839587ae37093f19153dc26c0927d1048ff6557330"}, + {file = "more-itertools-8.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"}, + {file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"}, ] msgpack = [ - {file = "msgpack-1.0.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:cec8bf10981ed70998d98431cd814db0ecf3384e6b113366e7f36af71a0fca08"}, - {file = "msgpack-1.0.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aa5c057eab4f40ec47ea6f5a9825846be2ff6bf34102c560bad5cad5a677c5be"}, - {file = "msgpack-1.0.0-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:4233b7f86c1208190c78a525cd3828ca1623359ef48f78a6fea4b91bb995775a"}, - {file = "msgpack-1.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b3758dfd3423e358bbb18a7cccd1c74228dffa7a697e5be6cb9535de625c0dbf"}, - {file = "msgpack-1.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:25b3bc3190f3d9d965b818123b7752c5dfb953f0d774b454fd206c18fe384fb8"}, - {file = "msgpack-1.0.0-cp36-cp36m-win32.whl", hash = "sha256:e7bbdd8e2b277b77782f3ce34734b0dfde6cbe94ddb74de8d733d603c7f9e2b1"}, - {file = "msgpack-1.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5dba6d074fac9b24f29aaf1d2d032306c27f04187651511257e7831733293ec2"}, - {file = "msgpack-1.0.0-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:908944e3f038bca67fcfedb7845c4a257c7749bf9818632586b53bcf06ba4b97"}, - {file = "msgpack-1.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:db685187a415f51d6b937257474ca72199f393dad89534ebbdd7d7a3b000080e"}, - {file = "msgpack-1.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ea41c9219c597f1d2bf6b374d951d310d58684b5de9dc4bd2976db9e1e22c140"}, - {file = "msgpack-1.0.0-cp37-cp37m-win32.whl", hash = "sha256:e35b051077fc2f3ce12e7c6a34cf309680c63a842db3a0616ea6ed25ad20d272"}, - {file = "msgpack-1.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5bea44181fc8e18eed1d0cd76e355073f00ce232ff9653a0ae88cb7d9e643322"}, - {file = "msgpack-1.0.0-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c901e8058dd6653307906c5f157f26ed09eb94a850dddd989621098d347926ab"}, - {file = "msgpack-1.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:271b489499a43af001a2e42f42d876bb98ccaa7e20512ff37ca78c8e12e68f84"}, - {file = "msgpack-1.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7a22c965588baeb07242cb561b63f309db27a07382825fc98aecaf0827c1538e"}, - {file = "msgpack-1.0.0-cp38-cp38-win32.whl", hash = "sha256:002a0d813e1f7b60da599bdf969e632074f9eec1b96cbed8fb0973a63160a408"}, - {file = "msgpack-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:39c54fdebf5fa4dda733369012c59e7d085ebdfe35b6cf648f09d16708f1be5d"}, - {file = "msgpack-1.0.0.tar.gz", hash = "sha256:9534d5cc480d4aff720233411a1f765be90885750b07df772380b34c10ecb5c0"}, + {file = "msgpack-1.0.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:b6d9e2dae081aa35c44af9c4298de4ee72991305503442a5c74656d82b581fe9"}, + {file = "msgpack-1.0.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a99b144475230982aee16b3d249170f1cccebf27fb0a08e9f603b69637a62192"}, + {file = "msgpack-1.0.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1026dcc10537d27dd2d26c327e552f05ce148977e9d7b9f1718748281b38c841"}, + {file = "msgpack-1.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:fe07bc6735d08e492a327f496b7850e98cb4d112c56df69b0c844dbebcbb47f6"}, + {file = "msgpack-1.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9ea52fff0473f9f3000987f313310208c879493491ef3ccf66268eff8d5a0326"}, + {file = "msgpack-1.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:26a1759f1a88df5f1d0b393eb582ec022326994e311ba9c5818adc5374736439"}, + {file = "msgpack-1.0.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:497d2c12426adcd27ab83144057a705efb6acc7e85957a51d43cdcf7f258900f"}, + {file = "msgpack-1.0.2-cp36-cp36m-win32.whl", hash = "sha256:e89ec55871ed5473a041c0495b7b4e6099f6263438e0bd04ccd8418f92d5d7f2"}, + {file = "msgpack-1.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a4355d2193106c7aa77c98fc955252a737d8550320ecdb2e9ac701e15e2943bc"}, + {file = "msgpack-1.0.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:d6c64601af8f3893d17ec233237030e3110f11b8a962cb66720bf70c0141aa54"}, + {file = "msgpack-1.0.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f484cd2dca68502de3704f056fa9b318c94b1539ed17a4c784266df5d6978c87"}, + {file = "msgpack-1.0.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f3e6aaf217ac1c7ce1563cf52a2f4f5d5b1f64e8729d794165db71da57257f0c"}, + {file = "msgpack-1.0.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:8521e5be9e3b93d4d5e07cb80b7e32353264d143c1f072309e1863174c6aadb1"}, + {file = "msgpack-1.0.2-cp37-cp37m-win32.whl", hash = "sha256:31c17bbf2ae5e29e48d794c693b7ca7a0c73bd4280976d408c53df421e838d2a"}, + {file = "msgpack-1.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8ffb24a3b7518e843cd83538cf859e026d24ec41ac5721c18ed0c55101f9775b"}, + {file = "msgpack-1.0.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:b28c0876cce1466d7c2195d7658cf50e4730667196e2f1355c4209444717ee06"}, + {file = "msgpack-1.0.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:87869ba567fe371c4555d2e11e4948778ab6b59d6cc9d8460d543e4cfbbddd1c"}, + {file = "msgpack-1.0.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b55f7db883530b74c857e50e149126b91bb75d35c08b28db12dcb0346f15e46e"}, + {file = "msgpack-1.0.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:ac25f3e0513f6673e8b405c3a80500eb7be1cf8f57584be524c4fa78fe8e0c83"}, + {file = "msgpack-1.0.2-cp38-cp38-win32.whl", hash = "sha256:0cb94ee48675a45d3b86e61d13c1e6f1696f0183f0715544976356ff86f741d9"}, + {file = "msgpack-1.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:e36a812ef4705a291cdb4a2fd352f013134f26c6ff63477f20235138d1d21009"}, + {file = "msgpack-1.0.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2a5866bdc88d77f6e1370f82f2371c9bc6fc92fe898fa2dec0c5d4f5435a2694"}, + {file = "msgpack-1.0.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:92be4b12de4806d3c36810b0fe2aeedd8d493db39e2eb90742b9c09299eb5759"}, + {file = "msgpack-1.0.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:de6bd7990a2c2dabe926b7e62a92886ccbf809425c347ae7de277067f97c2887"}, + {file = "msgpack-1.0.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5a9ee2540c78659a1dd0b110f73773533ee3108d4e1219b5a15a8d635b7aca0e"}, + {file = "msgpack-1.0.2-cp39-cp39-win32.whl", hash = "sha256:c747c0cc08bd6d72a586310bda6ea72eeb28e7505990f342552315b229a19b33"}, + {file = "msgpack-1.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:d8167b84af26654c1124857d71650404336f4eb5cc06900667a493fc619ddd9f"}, + {file = "msgpack-1.0.2.tar.gz", hash = "sha256:fae04496f5bc150eefad4e9571d1a76c55d021325dcd484ce45065ebbdd00984"}, ] nodeenv = [ {file = "nodeenv-1.5.0-py2.py3-none-any.whl", hash = "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9"}, {file = "nodeenv-1.5.0.tar.gz", hash = "sha256:ab45090ae383b716c4ef89e690c41ff8c2b257b85b309f01f3654df3d084bd7c"}, ] +ordered-set = [ + {file = "ordered-set-4.0.2.tar.gz", hash = "sha256:ba93b2df055bca202116ec44b9bead3df33ea63a7d5827ff8e16738b97f33a95"}, +] packaging = [ - {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, - {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, + {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, + {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, ] 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.6.1-py2.py3-none-any.whl", hash = "sha256:ce14d7296c673dc4c61c759a0b6c14bae34e34eb819c0017bb6ca5b7292c56e9"}, - {file = "pkginfo-1.6.1.tar.gz", hash = "sha256:a6a4ac943b496745cec21f14f021bbd869d5e9b4f6ec06918cffea5a2f4b9193"}, + {file = "pkginfo-1.7.0-py2.py3-none-any.whl", hash = "sha256:9fdbea6495622e022cc72c2e5e1b735218e4ffb2a2a69cde2694a6c1f16afb75"}, + {file = "pkginfo-1.7.0.tar.gz", hash = "sha256:029a70cb45c6171c329dfc890cde0879f8c52d6f3922794796e06f577bb03db4"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, @@ -909,16 +957,16 @@ pluggy = [ ] poetry-core = [] pre-commit = [ - {file = "pre_commit-2.9.0-py2.py3-none-any.whl", hash = "sha256:4aee0db4808fa48d2458cedd5b9a084ef24dda1a0fa504432a11977a4d1cfd0a"}, - {file = "pre_commit-2.9.0.tar.gz", hash = "sha256:b2d106d51c6ba6217e859d81774aae33fd825fe7de0dcf0c46e2586333d7a92e"}, + {file = "pre_commit-2.11.1-py2.py3-none-any.whl", hash = "sha256:94c82f1bf5899d56edb1d926732f4e75a7df29a0c8c092559c77420c9d62428b"}, + {file = "pre_commit-2.11.1.tar.gz", hash = "sha256:de55c5c72ce80d79106e48beb1b54104d16495ce7f95b0c7b13d4784193a00af"}, ] ptyprocess = [ - {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, - {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"}, + {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.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, - {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, ] pycparser = [ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, @@ -937,8 +985,8 @@ pytest = [ {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, ] pytest-cov = [ - {file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"}, - {file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"}, + {file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"}, + {file = "pytest_cov-2.11.1-py2.py3-none-any.whl", hash = "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da"}, ] pytest-mock = [ {file = "pytest-mock-1.13.0.tar.gz", hash = "sha256:e24a911ec96773022ebcc7030059b57cd3480b56d4f5d19b7c370ec635e6aed5"}, @@ -952,33 +1000,43 @@ pywin32-ctypes = [ {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, ] pyyaml = [ - {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, - {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, - {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, - {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, - {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, - {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, - {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, - {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, - {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, - {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, - {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, + {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, + {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, + {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, + {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, + {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, + {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, + {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, + {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] requests = [ - {file = "requests-2.25.0-py2.py3-none-any.whl", hash = "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"}, - {file = "requests-2.25.0.tar.gz", hash = "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8"}, + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, ] 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.2.0-py3-none-any.whl", hash = "sha256:ed5279d788af258e4676fa26b6efb6d335a31f1f9f529b6f1e200f388fac33e1"}, - {file = "SecretStorage-3.2.0.tar.gz", hash = "sha256:46305c3847ee3f7252b284e0eee5590fa6341c891104a2fd2313f8798c615a82"}, + {file = "SecretStorage-3.3.1-py3-none-any.whl", hash = "sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f"}, + {file = "SecretStorage-3.3.1.tar.gz", hash = "sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195"}, ] shellingham = [ - {file = "shellingham-1.3.2-py2.py3-none-any.whl", hash = "sha256:7f6206ae169dc1a03af8a138681b3f962ae61cc93ade84d0585cca3aaf770044"}, - {file = "shellingham-1.3.2.tar.gz", hash = "sha256:576c1982bea0ba82fb46c36feb951319d7f42214a82634233f58b40d858a751e"}, + {file = "shellingham-1.4.0-py2.py3-none-any.whl", hash = "sha256:536b67a0697f2e4af32ab176c00a50ac2899c5a05e0d8e2dadac8e58888283f9"}, + {file = "shellingham-1.4.0.tar.gz", hash = "sha256:4855c2458d6904829bd34c299f11fdeed7cfefbf8a2c522e4caea6cd76b3171e"}, ] six = [ {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, @@ -996,16 +1054,16 @@ tomlkit = [ {file = "tomlkit-0.7.0.tar.gz", hash = "sha256:ac57f29693fab3e309ea789252fcce3061e19110085aa31af5446ca749325618"}, ] tox = [ - {file = "tox-3.20.1-py2.py3-none-any.whl", hash = "sha256:42ce19ce5dc2f6d6b1fdc5666c476e1f1e2897359b47e0aa3a5b774f335d57c2"}, - {file = "tox-3.20.1.tar.gz", hash = "sha256:4321052bfe28f9d85082341ca8e233e3ea901fdd14dab8a5d3fbd810269fbaf6"}, + {file = "tox-3.23.0-py2.py3-none-any.whl", hash = "sha256:e007673f3595cede9b17a7c4962389e4305d4a3682a6c5a4159a1453b4f326aa"}, + {file = "tox-3.23.0.tar.gz", hash = "sha256:05a4dbd5e4d3d8269b72b55600f0b0303e2eb47ad5c6fe76d3576f4c58d93661"}, ] urllib3 = [ {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"}, {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, ] virtualenv = [ - {file = "virtualenv-20.2.1-py2.py3-none-any.whl", hash = "sha256:07cff122e9d343140366055f31be4dcd61fd598c69d11cd33a9d9c8df4546dd7"}, - {file = "virtualenv-20.2.1.tar.gz", hash = "sha256:e0aac7525e880a429764cefd3aaaff54afb5d9f25c82627563603f5d7de5a6e5"}, + {file = "virtualenv-20.4.3-py2.py3-none-any.whl", hash = "sha256:83f95875d382c7abafe06bd2a4cdd1b363e1bb77e02f155ebe8ac082a916b37c"}, + {file = "virtualenv-20.4.3.tar.gz", hash = "sha256:49ec4eb4c224c6f7dd81bb6d0a28a09ecae5894f4e593c89b0db0885f565a107"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -1016,6 +1074,6 @@ webencodings = [ {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, ] zipp = [ - {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, - {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, + {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, + {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, ] diff --git a/poetry/installation/pip_installer.py b/poetry/installation/pip_installer.py index 094fe23825b..e9f4c874f2f 100644 --- a/poetry/installation/pip_installer.py +++ b/poetry/installation/pip_installer.py @@ -2,6 +2,7 @@ import tempfile import urllib.parse +from pathlib import Path from subprocess import CalledProcessError from typing import TYPE_CHECKING from typing import Any @@ -9,14 +10,15 @@ from cleo.io.io import IO +from poetry.installation.base_installer import BaseInstaller from poetry.core.pyproject.toml import PyProjectTOML from poetry.repositories.pool import Pool from poetry.utils._compat import encode from poetry.utils.env import Env +from poetry.utils.env import EnvManager +from poetry.utils.env import VirtualEnv from poetry.utils.helpers import safe_rmtree - -from .base_installer import BaseInstaller - +from poetry.utils.helpers import temporary_directory if TYPE_CHECKING: from poetry.core.packages.package import Package @@ -193,7 +195,7 @@ def install_directory(self, package: "Package") -> Union[str, int]: else: req = os.path.realpath(package.source_url) - args = ["install", "--no-deps", "-U"] + args = ["install", "--no-deps", "-U", "--root", str(self._env.path)] pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml")) @@ -239,7 +241,11 @@ def install_directory(self, package: "Package") -> Union[str, int]: args.append(req) - return self.run(*args) + with temporary_directory() as tmp_dir: + venv_dir = Path(tmp_dir) / ".venv" + EnvManager.build_venv(venv_dir.as_posix(), with_pip=True) + venv = VirtualEnv(venv_dir, venv_dir) + return venv.run("pip", *args) def install_git(self, package: "Package") -> None: from poetry.core.packages.package import Package diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index 25b43180d3b..f18a56e0579 100644 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -54,7 +54,7 @@ def _formatter_elapsed(self) -> str: class Provider: - UNSAFE_PACKAGES = {"setuptools"} + UNSAFE_PACKAGES = set() def __init__( self, package: Package, pool: Pool, io: Any, env: Optional[Env] = None diff --git a/poetry/utils/env.py b/poetry/utils/env.py index e2d5a831529..11129d23d2b 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -826,8 +826,7 @@ def build_venv( ] if not with_pip: - # we cannot drop setuptools yet because we do editable installs (git, path) in project envs - args.extend(["--no-pip", "--no-wheel"]) + args.extend(["--no-pip", "--no-wheel", "--no-setuptools"]) for flag, value in flags.items(): if value is True: diff --git a/pyproject.toml b/pyproject.toml index d0089b0883b..bd2dd612dec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,7 @@ tox = "^3.0" pytest-sugar = "^0.9.2" httpretty = "^1.0" zipp = { version = "^3.4", python = "<3.8"} +deepdiff = "^5.0.2" # temporary workaround for https://github.com/python-poetry/poetry/issues/3404 urllib3 = "1.25.10" diff --git a/tests/installation/fixtures/with-pypi-repository.test b/tests/installation/fixtures/with-pypi-repository.test index 3f3719452d9..d1ed1ae55ed 100644 --- a/tests/installation/fixtures/with-pypi-repository.test +++ b/tests/installation/fixtures/with-pypi-repository.test @@ -66,6 +66,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" py = ">=1.5.0" six = ">=1.10.0" attrs = ">=17.4.0" +setuptools = "*" more-itertools = ">=4.0.0" pluggy = ">=0.5,<0.7" funcsigs = {"version" = "*", "markers" = "python_version < \"3.0\""} @@ -79,6 +80,18 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "setuptools" +version = "39.2.0" +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.extras] +certs = ["certifi (==2016.9.26)"] +ssl = ["wincertstore (==0.2)"] + [metadata] python-versions = "*" lock-version = "1.1" @@ -113,6 +126,10 @@ pytest = [ {file = "pytest-3.5.0-py2.py3-none-any.whl", hash = "sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c"}, {file = "pytest-3.5.0.tar.gz", hash = "sha256:fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1"}, ] +setuptools = [ + {file = "setuptools-39.2.0-py2.py3-none-any.whl", hash = "sha256:8fca9275c89964f13da985c3656cb00ba029d7f3916b37990927ffdf264e7926"}, + {file = "setuptools-39.2.0.zip", hash = "sha256:f7cddbb5f5c640311eb00eab6e849f7701fa70bf6a183fc8a2c33dd1d1672fb2"}, +] six = [ {file = "six-1.11.0-py2.py3-none-any.whl", hash = "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"}, {file = "six-1.11.0.tar.gz", hash = "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"}, diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 980bbd85dd6..df2dd72b2f8 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -13,6 +13,7 @@ from cleo.io.outputs.buffered_output import BufferedOutput from cleo.io.outputs.output import Verbosity +from deepdiff import DeepDiff from poetry.core.packages.project_package import ProjectPackage from poetry.core.toml.file import TOMLFile from poetry.factory import Factory @@ -404,7 +405,7 @@ def test_run_install_remove_untracked(installer, locker, repo, package, installe installed.add_package(package_b) installed.add_package(package_c) installed.add_package(package_pip) - installed.add_package(package_setuptools) # Always required and never removed. + installed.add_package(package_setuptools) installed.add_package(package) # Root package never removed. package.add_dependency(Factory.create_dependency("A", "~1.0")) @@ -414,8 +415,10 @@ def test_run_install_remove_untracked(installer, locker, repo, package, installe assert 0 == installer.executor.installations_count assert 0 == installer.executor.updates_count - assert 3 == installer.executor.removals_count - assert {"b", "c", "pip"} == set(r.name for r in installer.executor.removals) + assert 4 == installer.executor.removals_count + assert {"b", "c", "pip", "setuptools"} == set( + r.name for r in installer.executor.removals + ) def test_run_whitelist_add(installer, locker, repo, package): @@ -831,7 +834,7 @@ def test_installer_with_pypi_repository(package, locker, installed, config): expected = fixture("with-pypi-repository") - assert locker.written_data == expected + assert not DeepDiff(locker.written_data, expected, ignore_order=True) def test_run_installs_with_local_file(installer, locker, repo, package, fixture_dir): @@ -1612,7 +1615,7 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de installer.whitelist(["pytest"]) installer.run() - assert 6 == installer.executor.installations_count + assert 7 == installer.executor.installations_count assert 0 == installer.executor.updates_count assert 0 == installer.executor.removals_count diff --git a/tests/installation/test_installer_old.py b/tests/installation/test_installer_old.py index 046d2dd1968..5207e6854de 100644 --- a/tests/installation/test_installer_old.py +++ b/tests/installation/test_installer_old.py @@ -8,6 +8,7 @@ from cleo.io.null_io import NullIO +from deepdiff import DeepDiff from poetry.core.packages.project_package import ProjectPackage from poetry.core.toml.file import TOMLFile from poetry.factory import Factory @@ -330,7 +331,7 @@ def test_run_install_remove_untracked(installer, locker, repo, package, installe installed.add_package(package_b) installed.add_package(package_c) installed.add_package(package_pip) - installed.add_package(package_setuptools) # Always required and never removed. + installed.add_package(package_setuptools) installed.add_package(package) # Root package never removed. package.add_dependency(Factory.create_dependency("A", "~1.0")) @@ -345,7 +346,7 @@ def test_run_install_remove_untracked(installer, locker, repo, package, installe assert len(updates) == 0 removals = installer.installer.removals - assert set(r.name for r in removals) == {"b", "c", "pip"} + assert set(r.name for r in removals) == {"b", "c", "pip", "setuptools"} def test_run_whitelist_add(installer, locker, repo, package): @@ -738,7 +739,7 @@ def test_installer_with_pypi_repository(package, locker, installed, config): expected = fixture("with-pypi-repository") - assert locker.written_data == expected + assert not DeepDiff(locker.written_data, expected, ignore_order=True) def test_run_installs_with_local_file(installer, locker, repo, package, fixture_dir): @@ -1509,7 +1510,7 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de installer.whitelist(["pytest"]) installer.run() - assert len(installer.installer.installs) == 6 + assert len(installer.installer.installs) == 7 assert len(installer.installer.updates) == 0 assert len(installer.installer.removals) == 0 diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 0c2bc196ba1..37693b6333e 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -2403,6 +2403,7 @@ def test_solver_remove_untracked_single(package, pool, installed, locked, io): check_solver_result(ops, [{"job": "remove", "package": package_a}]) +@pytest.mark.skip(reason="Poetry no longer has critical package requirements") def test_solver_remove_untracked_keeps_critical_package( package, pool, installed, locked, io ): From 6400f423faa9888ee6d52c22c14103c214454c47 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 29 Sep 2020 13:10:36 +0200 Subject: [PATCH 120/222] Use isolated ephemeral envs for editable installs --- poetry/inspection/info.py | 11 ++---- poetry/installation/executor.py | 5 ++- poetry/installation/pip_installer.py | 36 ++++++++---------- poetry/masonry/builders/editable.py | 6 +-- poetry/utils/env.py | 29 +++++++++++++- poetry/utils/pip.py | 38 +++++++++++++++++++ tests/installation/test_executor.py | 10 ++++- tests/installation/test_installer.py | 5 +-- tests/installation/test_installer_old.py | 5 +-- .../masonry/builders/test_editable_builder.py | 13 ++++--- 10 files changed, 110 insertions(+), 48 deletions(-) create mode 100644 poetry/utils/pip.py diff --git a/poetry/inspection/info.py b/poetry/inspection/info.py index f0347818d9e..12e143c6370 100644 --- a/poetry/inspection/info.py +++ b/poetry/inspection/info.py @@ -22,8 +22,7 @@ from poetry.core.utils.helpers import temporary_directory from poetry.core.version.markers import InvalidMarker from poetry.utils.env import EnvCommandError -from poetry.utils.env import EnvManager -from poetry.utils.env import VirtualEnv +from poetry.utils.env import ephemeral_environment from poetry.utils.setup_reader import SetupReader @@ -451,13 +450,9 @@ def _pep517_metadata(cls, path: Path) -> "PackageInfo": except PackageInfoError: pass - with temporary_directory() as tmp_dir: + with ephemeral_environment(pip=True, wheel=True, setuptools=True) as venv: # TODO: cache PEP 517 build environment corresponding to each project venv - venv_dir = Path(tmp_dir) / ".venv" - EnvManager.build_venv(venv_dir.as_posix(), with_pip=True) - venv = VirtualEnv(venv_dir, venv_dir) - - dest_dir = Path(tmp_dir) / "dist" + dest_dir = venv.path.parent / "dist" dest_dir.mkdir() try: diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index ea2940e1fbd..449ef9f1697 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -22,6 +22,7 @@ from poetry.utils._compat import decode from poetry.utils.env import EnvCommandError from poetry.utils.helpers import safe_rmtree +from poetry.utils.pip import pip_editable_install from .authenticator import Authenticator from .chef import Chef @@ -572,14 +573,14 @@ def _install_directory(self, operation: Union[Install, Update]) -> int: with builder.setup_py(): if package.develop: - args.append("-e") + return pip_editable_install(req, self._env) args.append(req) return self.run_pip(*args) if package.develop: - args.append("-e") + return pip_editable_install(req, self._env) args.append(req) diff --git a/poetry/installation/pip_installer.py b/poetry/installation/pip_installer.py index e9f4c874f2f..bf253367fc3 100644 --- a/poetry/installation/pip_installer.py +++ b/poetry/installation/pip_installer.py @@ -10,15 +10,15 @@ from cleo.io.io import IO -from poetry.installation.base_installer import BaseInstaller from poetry.core.pyproject.toml import PyProjectTOML +from poetry.installation.base_installer import BaseInstaller from poetry.repositories.pool import Pool from poetry.utils._compat import encode from poetry.utils.env import Env -from poetry.utils.env import EnvManager -from poetry.utils.env import VirtualEnv from poetry.utils.helpers import safe_rmtree -from poetry.utils.helpers import temporary_directory +from poetry.utils.pip import pip_editable_install +from poetry.utils.pip import pip_install + if TYPE_CHECKING: from poetry.core.packages.package import Package @@ -190,12 +190,12 @@ def install_directory(self, package: "Package") -> Union[str, int]: from poetry.factory import Factory + req: Path + if package.root_dir: req = (package.root_dir / package.source_url).as_posix() else: - req = os.path.realpath(package.source_url) - - args = ["install", "--no-deps", "-U", "--root", str(self._env.path)] + req = Path(package.source_url).resolve(strict=False) pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml")) @@ -230,22 +230,16 @@ def install_directory(self, package: "Package") -> Union[str, int]: with builder.setup_py(): if package.develop: - args.append("-e") - - args.append(req) - - return self.run(*args) + return pip_editable_install( + directory=req, environment=self._env + ) + return pip_install( + path=req, environment=self._env, deps=False, upgrade=True + ) if package.develop: - args.append("-e") - - args.append(req) - - with temporary_directory() as tmp_dir: - venv_dir = Path(tmp_dir) / ".venv" - EnvManager.build_venv(venv_dir.as_posix(), with_pip=True) - venv = VirtualEnv(venv_dir, venv_dir) - return venv.run("pip", *args) + return pip_editable_install(directory=req, environment=self._env) + return pip_install(path=req, environment=self._env, deps=False, upgrade=True) def install_git(self, package: "Package") -> None: from poetry.core.packages.package import Package diff --git a/poetry/masonry/builders/editable.py b/poetry/masonry/builders/editable.py index 07f59d0b311..c8b83959a43 100644 --- a/poetry/masonry/builders/editable.py +++ b/poetry/masonry/builders/editable.py @@ -16,6 +16,7 @@ from poetry.utils._compat import WINDOWS from poetry.utils._compat import decode from poetry.utils.helpers import is_dir_writable +from poetry.utils.pip import pip_editable_install if TYPE_CHECKING: @@ -56,7 +57,6 @@ def build(self) -> None: self._debug( " - Falling back on using a setup.py" ) - return self._setup_build() self._run_build_script(self._package.build_script) @@ -85,14 +85,14 @@ def _setup_build(self) -> None: try: if self._env.pip_version < Version(19, 0): - self._env.run_pip("install", "-e", str(self._path), "--no-deps") + pip_editable_install(self._path, self._env) else: # Temporarily rename pyproject.toml shutil.move( str(self._poetry.file), str(self._poetry.file.with_suffix(".tmp")) ) try: - self._env.run_pip("install", "-e", str(self._path), "--no-deps") + pip_editable_install(self._path, self._env) finally: shutil.move( str(self._poetry.file.with_suffix(".tmp")), diff --git a/poetry/utils/env.py b/poetry/utils/env.py index 11129d23d2b..0a9e2cbb8ab 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -43,6 +43,7 @@ from poetry.utils._compat import list_to_shell_command from poetry.utils.helpers import is_dir_writable from poetry.utils.helpers import paths_csv +from poetry.utils.helpers import temporary_directory GET_ENVIRONMENT_INFO = """\ @@ -812,6 +813,8 @@ def build_venv( executable: Optional[Union[str, Path]] = None, flags: Dict[str, bool] = None, with_pip: bool = False, + with_wheel: bool = False, + with_setuptools: bool = False, ) -> virtualenv.run.session.Session: flags = flags or {} @@ -826,7 +829,16 @@ def build_venv( ] if not with_pip: - args.extend(["--no-pip", "--no-wheel", "--no-setuptools"]) + args.append("--no-pip") + else: + if with_wheel is None: + with_wheel = True + + if with_wheel is None or not with_wheel: + args.append("--no-wheel") + + if with_setuptools is None or not with_setuptools: + args.append("--no-setuptools") for flag, value in flags.items(): if value is True: @@ -1429,6 +1441,21 @@ def _bin(self, bin: str) -> str: return bin +@contextmanager +def ephemeral_environment(executable=None, pip=False, wheel=None, setuptools=None): + 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(), + executable=executable, + with_pip=pip, + with_wheel=wheel, + with_setuptools=setuptools, + ) + yield VirtualEnv(venv_dir, venv_dir) + + class MockEnv(NullEnv): def __init__( self, diff --git a/poetry/utils/pip.py b/poetry/utils/pip.py new file mode 100644 index 00000000000..97e7dc6d0d7 --- /dev/null +++ b/poetry/utils/pip.py @@ -0,0 +1,38 @@ +from poetry.exceptions import PoetryException +from poetry.utils._compat import Path +from poetry.utils.env import Env +from poetry.utils.env import ephemeral_environment + + +def pip_install( + path, environment, editable=False, deps=False, upgrade=False +): # type: (Path, Env, bool, bool, bool) -> None + path = Path(path) if isinstance(path, str) else path + + args = ["pip", "install", "--prefix", str(environment.path)] + + if upgrade: + args.append("--upgrade") + + if not deps: + args.append("--no-deps") + + if editable: + if not path.is_dir(): + raise PoetryException( + "Cannot install non directory dependencies in editable mode" + ) + args.append("-e") + + args.append(str(path)) + + with ephemeral_environment( + executable=environment.python, pip=True, setuptools=True + ) as env: + return env.run(*args) + + +def pip_editable_install(directory, environment): # type: (Path, Env) -> None + return pip_install( + path=directory, environment=environment, editable=True, deps=False, upgrade=True + ) diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index ab911aba2a9..5e99e1a5666 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -66,8 +66,12 @@ def callback(request, uri, headers): def test_execute_executes_a_batch_of_operations( - config, pool, io, tmp_dir, mock_file_downloads, env + mocker, config, pool, io, tmp_dir, mock_file_downloads, env ): + pip_editable_install = mocker.patch( + "poetry.installation.executor.pip_editable_install" + ) + config = Config() config.merge({"cache-dir": tmp_dir}) @@ -101,6 +105,7 @@ def test_execute_executes_a_batch_of_operations( source_type="git", source_reference="master", source_url="https://github.com/demo/demo.git", + develop=True, ) return_code = executor.execute( @@ -131,8 +136,9 @@ def test_execute_executes_a_batch_of_operations( expected = set(expected.splitlines()) output = set(io.fetch_output().splitlines()) assert expected == output - assert 6 == len(env.executed) + assert 4 == len(env.executed) assert 0 == return_code + pip_editable_install.assert_called_once() def test_execute_shows_skipped_operations_if_verbose( diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index df2dd72b2f8..a671e6b4d0a 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -12,8 +12,8 @@ from cleo.io.null_io import NullIO from cleo.io.outputs.buffered_output import BufferedOutput from cleo.io.outputs.output import Verbosity - from deepdiff import DeepDiff + from poetry.core.packages.project_package import ProjectPackage from poetry.core.toml.file import TOMLFile from poetry.factory import Factory @@ -833,8 +833,7 @@ def test_installer_with_pypi_repository(package, locker, installed, config): installer.run() expected = fixture("with-pypi-repository") - - assert not DeepDiff(locker.written_data, expected, ignore_order=True) + assert not DeepDiff(expected, locker.written_data, ignore_order=True) def test_run_installs_with_local_file(installer, locker, repo, package, fixture_dir): diff --git a/tests/installation/test_installer_old.py b/tests/installation/test_installer_old.py index 5207e6854de..a8e7a9c1877 100644 --- a/tests/installation/test_installer_old.py +++ b/tests/installation/test_installer_old.py @@ -7,8 +7,8 @@ import pytest from cleo.io.null_io import NullIO - from deepdiff import DeepDiff + from poetry.core.packages.project_package import ProjectPackage from poetry.core.toml.file import TOMLFile from poetry.factory import Factory @@ -738,8 +738,7 @@ def test_installer_with_pypi_repository(package, locker, installed, config): installer.run() expected = fixture("with-pypi-repository") - - assert not DeepDiff(locker.written_data, expected, ignore_order=True) + assert not DeepDiff(expected, locker.written_data, ignore_order=True) def test_run_installs_with_local_file(installer, locker, repo, package, fixture_dir): diff --git a/tests/masonry/builders/test_editable_builder.py b/tests/masonry/builders/test_editable_builder.py index 98fc6c2eb89..efe79bed772 100644 --- a/tests/masonry/builders/test_editable_builder.py +++ b/tests/masonry/builders/test_editable_builder.py @@ -179,16 +179,19 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts( - extended_poetry, tmp_dir + mocker, extended_poetry, tmp_dir ): + pip_editable_install = mocker.patch( + "poetry.masonry.builders.editable.pip_editable_install" + ) env = MockEnv(path=Path(tmp_dir) / "foo") builder = EditableBuilder(extended_poetry, env, NullIO()) builder.build() - assert [ - env.get_pip_command() - + ["install", "-e", str(extended_poetry.file.parent), "--no-deps"] - ] == env.executed + pip_editable_install.assert_called_once_with( + extended_poetry.pyproject.file.path.parent, env + ) + assert [] == env.executed def test_builder_installs_proper_files_when_packages_configured( From a3348f2824f75a0c7a3396c18f188cbefbe6e153 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 29 Sep 2020 13:25:37 +0200 Subject: [PATCH 121/222] Use utils.pip for executor installs --- poetry/installation/executor.py | 21 ++++++--------------- poetry/utils/pip.py | 24 ++++++++++++++++++------ tests/installation/test_executor.py | 3 ++- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index 449ef9f1697..9d9eafec289 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -24,6 +24,7 @@ from poetry.utils.helpers import safe_rmtree from poetry.utils.pip import pip_editable_install +from ..utils.pip import pip_install from .authenticator import Authenticator from .chef import Chef from .chooser import Chooser @@ -475,12 +476,9 @@ def _install(self, operation: Union[Install, Update]) -> int: ) ) self._write(operation, message) - - args = ["install", "--no-deps", str(archive)] - if operation.job_type == "update": - args.insert(2, "-U") - - return self.run_pip(*args) + return pip_install( + str(archive), self._env, upgrade=operation.job_type == "update" + ) def _update(self, operation: Union[Install, Update]) -> int: return self._install(operation) @@ -538,8 +536,6 @@ def _install_directory(self, operation: Union[Install, Update]) -> int: else: req = os.path.realpath(package.source_url) - args = ["install", "--no-deps", "-U"] - pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml")) if pyproject.is_poetry_project(): @@ -574,17 +570,12 @@ def _install_directory(self, operation: Union[Install, Update]) -> int: with builder.setup_py(): if package.develop: return pip_editable_install(req, self._env) - - args.append(req) - - return self.run_pip(*args) + return pip_install(req, self._env, upgrade=True) if package.develop: return pip_editable_install(req, self._env) - args.append(req) - - return self.run_pip(*args) + return pip_install(req, self._env, upgrade=True) def _install_git(self, operation: Union[Install, Update]) -> int: from poetry.core.vcs import Git diff --git a/poetry/utils/pip.py b/poetry/utils/pip.py index 97e7dc6d0d7..1c182e46708 100644 --- a/poetry/utils/pip.py +++ b/poetry/utils/pip.py @@ -1,15 +1,21 @@ +from pathlib import Path +from typing import Union + from poetry.exceptions import PoetryException -from poetry.utils._compat import Path from poetry.utils.env import Env from poetry.utils.env import ephemeral_environment def pip_install( - path, environment, editable=False, deps=False, upgrade=False -): # type: (Path, Env, bool, bool, bool) -> None + path: Union[Path, str], + environment: Env, + editable: bool = False, + deps: bool = False, + upgrade: bool = False, +) -> Union[int, str]: path = Path(path) if isinstance(path, str) else path - args = ["pip", "install", "--prefix", str(environment.path)] + args = ["install", "--prefix", str(environment.path)] if upgrade: args.append("--upgrade") @@ -26,13 +32,19 @@ def pip_install( args.append(str(path)) + if path.is_file() and path.suffix == ".whl": + return environment.run_pip(*args) + with ephemeral_environment( executable=environment.python, pip=True, setuptools=True ) as env: - return env.run(*args) + return env.run( + "pip", + *args, + ) -def pip_editable_install(directory, environment): # type: (Path, Env) -> None +def pip_editable_install(directory: Path, environment: Env) -> Union[int, str]: return pip_install( path=directory, environment=environment, editable=True, deps=False, upgrade=True ) diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 5e99e1a5666..368b72c90f5 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -13,6 +13,7 @@ from poetry.config.config import Config from poetry.core.packages.package import Package +from poetry.core.utils._compat import PY36 from poetry.installation.executor import Executor from poetry.installation.operations import Install from poetry.installation.operations import Uninstall @@ -69,7 +70,7 @@ def test_execute_executes_a_batch_of_operations( mocker, config, pool, io, tmp_dir, mock_file_downloads, env ): pip_editable_install = mocker.patch( - "poetry.installation.executor.pip_editable_install" + "poetry.installation.executor.pip_editable_install", unsafe=not PY36 ) config = Config() From ba60ed88293011f1af0b8c3317527cd4cc1f260a Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Sun, 21 Mar 2021 14:35:37 +0100 Subject: [PATCH 122/222] executor: use pathlib instead of os.path --- poetry/installation/executor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index 9d9eafec289..b2d85e566a6 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -532,9 +532,9 @@ def _install_directory(self, operation: Union[Install, Update]) -> int: self._write(operation, message) if package.root_dir: - req = os.path.join(str(package.root_dir), package.source_url) + req = package.root_dir / package.source_url else: - req = os.path.realpath(package.source_url) + req = Path(package.source_url).resolve(strict=False) pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml")) From 92dc54a8080872fc90503c738ac580cc63f5e7b4 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Sun, 21 Mar 2021 14:47:15 +0100 Subject: [PATCH 123/222] env: add missing type hinting --- poetry/utils/env.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/poetry/utils/env.py b/poetry/utils/env.py index 0a9e2cbb8ab..a35e5f7b3f3 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -14,6 +14,7 @@ from pathlib import Path from subprocess import CalledProcessError from typing import Any +from typing import ContextManager from typing import Dict from typing import Iterator from typing import List @@ -813,8 +814,8 @@ def build_venv( executable: Optional[Union[str, Path]] = None, flags: Dict[str, bool] = None, with_pip: bool = False, - with_wheel: bool = False, - with_setuptools: bool = False, + with_wheel: Optional[bool] = None, + with_setuptools: Optional[bool] = None, ) -> virtualenv.run.session.Session: flags = flags or {} @@ -832,6 +833,8 @@ def build_venv( args.append("--no-pip") else: if with_wheel is None: + # we want wheels to be enabled when pip is required and it has + # not been explicitly disabled with_wheel = True if with_wheel is None or not with_wheel: @@ -1442,7 +1445,12 @@ def _bin(self, bin: str) -> str: @contextmanager -def ephemeral_environment(executable=None, pip=False, wheel=None, setuptools=None): +def ephemeral_environment( + executable=None, + pip: bool = False, + wheel: Optional[bool] = None, + setuptools: Optional[bool] = None, +) -> ContextManager[VirtualEnv]: with temporary_directory() as tmp_dir: # TODO: cache PEP 517 build environment corresponding to each project venv venv_dir = Path(tmp_dir) / ".venv" From 6e6d039296cc127eaa89e8f2c0e6b9a44577acb3 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Sun, 21 Mar 2021 14:57:51 +0100 Subject: [PATCH 124/222] tests: fix env test helpers --- tests/console/commands/env/helpers.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/console/commands/env/helpers.py b/tests/console/commands/env/helpers.py index 63bf00fd68b..b15a68d07a2 100644 --- a/tests/console/commands/env/helpers.py +++ b/tests/console/commands/env/helpers.py @@ -1,18 +1,16 @@ from pathlib import Path -from typing import Optional +from typing import Any from typing import Union from poetry.core.semver.version import Version -def build_venv( - path: Union[Path, str], executable: Optional[str] = None, flags: bool = None -) -> (): +def build_venv(path: Union[Path, str], **_: Any) -> (): Path(path).mkdir(parents=True, exist_ok=True) def check_output_wrapper(version=Version.parse("3.7.1")): - def check_output(cmd, *args, **kwargs): + def check_output(cmd, *_, **__): if "sys.version_info[:3]" in cmd: return version.text elif "sys.version_info[:2]" in cmd: From 931cf120c80d1400cd5b9933db7cf078ab3718f4 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Sun, 21 Mar 2021 15:45:05 +0100 Subject: [PATCH 125/222] env: allow using embedded pip via run This change allows users to run embedded pip via `poetry run pip`. --- poetry/utils/env.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/poetry/utils/env.py b/poetry/utils/env.py index a35e5f7b3f3..a4588617d1e 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -1087,6 +1087,9 @@ def is_sane(self) -> bool: return True def run(self, bin: str, *args: str, **kwargs: Any) -> Union[str, int]: + if bin == "pip": + return self.run_pip(*args, **kwargs) + bin = self._bin(bin) cmd = [bin] + list(args) return self._run(cmd, **kwargs) @@ -1131,6 +1134,9 @@ def _run(self, cmd: List[str], **kwargs: Any) -> Union[int, str]: return decode(output) def execute(self, bin: str, *args: str, **kwargs: Any) -> Optional[int]: + if bin == "pip": + return self.run_pip(*args, **kwargs) + bin = self._bin(bin) if not self._is_windows: From 3c9ced2e12618f9a9946a76c0430b8c80c0d0374 Mon Sep 17 00:00:00 2001 From: Cere Blanco <743526+cereblanco@users.noreply.github.com> Date: Fri, 26 Mar 2021 03:03:45 +0800 Subject: [PATCH 126/222] repositories: fix 401, 403 handling Resolves: #3303 --- poetry/repositories/legacy_repository.py | 13 ++++++------- tests/repositories/test_legacy_repository.py | 15 +++++++-------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/poetry/repositories/legacy_repository.py b/poetry/repositories/legacy_repository.py index 63085a760c6..f0551e5adf8 100644 --- a/poetry/repositories/legacy_repository.py +++ b/poetry/repositories/legacy_repository.py @@ -423,19 +423,18 @@ def _get(self, endpoint: str) -> Optional[Page]: url = self._url + endpoint try: response = self.session.get(url) + if response.status_code in (401, 403): + self._log( + "Authorization error accessing {url}".format(url=url), + level="warning", + ) + return if response.status_code == 404: return response.raise_for_status() except requests.HTTPError as e: raise RepositoryError(e) - if response.status_code in (401, 403): - self._log( - "Authorization error accessing {url}".format(url=response.url), - level="warn", - ) - return - if response.url != url: self._log( "Response URL {response_url} differs from request URL {url}".format( diff --git a/tests/repositories/test_legacy_repository.py b/tests/repositories/test_legacy_repository.py index 5f49110d975..a1db15952e1 100644 --- a/tests/repositories/test_legacy_repository.py +++ b/tests/repositories/test_legacy_repository.py @@ -338,19 +338,18 @@ def test_get_200_returns_page(http): assert repo._get("/foo") -def test_get_404_returns_none(http): - repo = MockHttpRepository({"/foo": 404}, http) +@pytest.mark.parametrize("status_code", [401, 403, 404]) +def test_get_40x_and_returns_none(http, status_code): + repo = MockHttpRepository({"/foo": status_code}, http) assert repo._get("/foo") is None -def test_get_4xx_and_5xx_raises(http): - endpoints = {"/{}".format(code): code for code in {401, 403, 500}} - repo = MockHttpRepository(endpoints, http) +def test_get_5xx_raises(http): + repo = MockHttpRepository({"/foo": 500}, http) - for endpoint in endpoints: - with pytest.raises(RepositoryError): - repo._get(endpoint) + with pytest.raises(RepositoryError): + repo._get("/foo") def test_get_redirected_response_url(http, monkeypatch): From cb14c70e998eb29cc43199695284c82d1906d1e8 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Thu, 25 Mar 2021 22:06:49 +0100 Subject: [PATCH 127/222] pip installer: fix installed script executable --- poetry/utils/pip.py | 31 ++++++++++++++++++++--------- tests/installation/test_executor.py | 2 +- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/poetry/utils/pip.py b/poetry/utils/pip.py index 1c182e46708..a4929709105 100644 --- a/poetry/utils/pip.py +++ b/poetry/utils/pip.py @@ -1,8 +1,12 @@ +import os +import sys + from pathlib import Path from typing import Union from poetry.exceptions import PoetryException from poetry.utils.env import Env +from poetry.utils.env import EnvCommandError from poetry.utils.env import ephemeral_environment @@ -14,9 +18,13 @@ def pip_install( upgrade: bool = False, ) -> Union[int, str]: path = Path(path) if isinstance(path, str) else path + is_wheel = path.suffix == ".whl" args = ["install", "--prefix", str(environment.path)] + if not is_wheel: + args.insert(1, "--use-pep517") + if upgrade: args.append("--upgrade") @@ -32,16 +40,21 @@ def pip_install( args.append(str(path)) - if path.is_file() and path.suffix == ".whl": + try: return environment.run_pip(*args) - - with ephemeral_environment( - executable=environment.python, pip=True, setuptools=True - ) as env: - return env.run( - "pip", - *args, - ) + except EnvCommandError as e: + if sys.version_info < (3, 7) and not is_wheel: + # Under certain Python3.6 installs vendored pip wheel does not contain zip-safe + # pep517 lib. In this cases we create an isolated ephemeral virtual environment. + with ephemeral_environment( + executable=environment.python, pip=True, setuptools=True + ) as env: + return environment.run( + env._bin("pip"), + *args, + env={**os.environ, "PYTHONPATH": str(env.purelib)}, + ) + raise PoetryException(f"Failed to install {path.as_posix()}") from e def pip_editable_install(directory: Path, environment: Env) -> Union[int, str]: diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 368b72c90f5..4511e770a9d 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -137,7 +137,7 @@ def test_execute_executes_a_batch_of_operations( expected = set(expected.splitlines()) output = set(io.fetch_output().splitlines()) assert expected == output - assert 4 == len(env.executed) + assert 5 == len(env.executed) assert 0 == return_code pip_editable_install.assert_called_once() From 91f85f3d80832542d39684b845669cd404e56a3a Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Thu, 25 Mar 2021 23:10:33 +0100 Subject: [PATCH 128/222] ci: turn off fail fast --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c1eb33fe187..07475596c09 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,6 +23,7 @@ jobs: matrix: os: [Ubuntu, MacOS, Windows] python-version: [3.6, 3.7, 3.8, 3.9] + fail-fast: false steps: - uses: actions/checkout@v2 From cdfcd146ebe54b4277346a6cb9bfb57cb835e2b5 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Thu, 25 Mar 2021 23:15:19 +0100 Subject: [PATCH 129/222] deps: update virtualenv to 20.4.3 --- poetry.lock | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index dd00e3385b6..fb8897009e2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -697,7 +697,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "fa762bec451ef26021b1882a6f657ca2c0b3eeff514e28f805dfa7dfc1a02495" +content-hash = "6cbc07e5853bcf1280421b77b6fca85f2f7eb5a6ff12049f65ea116b256d94ea" [metadata.files] appdirs = [ diff --git a/pyproject.toml b/pyproject.toml index bd2dd612dec..a487a789dbf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ shellingham = "^1.1" tomlkit = ">=0.7.0,<1.0.0" pexpect = "^4.7.0" packaging = "^20.4" -virtualenv = { version = "^20.0.26" } +virtualenv = "^20.4.3" keyring = "^21.2.0" importlib-metadata = {version = "^1.6.0", python = "<3.8"} From d151f7437709fc546588e161c13b43b0c1d45498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 12 Jun 2020 09:43:12 +0200 Subject: [PATCH 130/222] Implement a plugin system --- docs/docs/plugins.md | 172 ++++++++++++++++++++++++ docs/mkdocs.yml | 1 + poetry.lock | 14 +- poetry/console/application.py | 67 ++++++++- poetry/console/command_loader.py | 12 ++ poetry/console/events/__init__.py | 0 poetry/console/events/console_events.py | 0 poetry/factory.py | 16 ++- poetry/packages/project_package.py | 23 ++++ poetry/plugins/__init__.py | 5 + poetry/plugins/application_plugin.py | 12 ++ poetry/plugins/base_plugin.py | 6 + poetry/plugins/plugin.py | 14 ++ poetry/plugins/plugin_manager.py | 55 ++++++++ poetry/poetry.py | 7 + pyproject.toml | 1 + tests/console/test_application.py | 101 ++++++++++++++ tests/plugins/test_plugin_manager.py | 112 +++++++++++++++ tests/test_factory.py | 20 +++ 19 files changed, 631 insertions(+), 7 deletions(-) create mode 100644 docs/docs/plugins.md create mode 100644 poetry/console/command_loader.py create mode 100644 poetry/console/events/__init__.py create mode 100644 poetry/console/events/console_events.py create mode 100644 poetry/packages/project_package.py create mode 100644 poetry/plugins/__init__.py create mode 100644 poetry/plugins/application_plugin.py create mode 100644 poetry/plugins/base_plugin.py create mode 100644 poetry/plugins/plugin.py create mode 100644 poetry/plugins/plugin_manager.py create mode 100644 tests/console/test_application.py create mode 100644 tests/plugins/test_plugin_manager.py diff --git a/docs/docs/plugins.md b/docs/docs/plugins.md new file mode 100644 index 00000000000..22e76ba573f --- /dev/null +++ b/docs/docs/plugins.md @@ -0,0 +1,172 @@ +# Plugins + +Poetry supports using and building plugins if you wish to +alter or expand Poetry's functionality with your own. + +For example if your environment poses special requirements +on the behaviour of Poetry which do not apply to the majority of its users +or if you wish to accomplish something with Poetry in a way that is not desired by most users. + +In these cases you could consider creating a plugin to handle your specific logic. + + +## Creating a plugin + +A plugin is a regular Python package which ships its code as part of the package +and may also depend on further packages. + +### Plugin package + +The plugin package must depend on Poetry +and declare a proper [plugin](/docs/pyproject/#plugins) in the `pyproject.toml` file. + +```toml +[tool.poetry] +name = "my-poetry-plugin" +version = "1.0.0" + +# ... +[tool.poetry.dependency] +python = "~2.7 || ^3.7" +poetry = "^1.0" + +[tool.poetry.plugins."poetry.plugin"] +demo = "poetry_demo_plugin.plugin:MyPlugin" +``` + +### Generic plugins + +Every plugin has to supply a class which implements the `poetry.plugins.Plugin` interface. + +The `activate()` method of the plugin is called after the plugin is loaded +and receives an instance of `Poetry` as well as an instance of `cleo.io.IO`. + +Using these two objects all configuration can be read +and all public internal objects and state can be manipulated as desired. + +Example: + +```python +from cleo.io.io import IO + +from poetry.plugins.plugin import Plugin +from poetry.poetry import Poetry + + +class MyPlugin(Plugin): + + def activate(self, poetry: Poetry, io: IO): + version = self.get_custom_version() + io.write_line(f"Setting package version to {version}") + poetry.package.set_version(version) + + def get_custom_version(self) -> str: + ... +``` + +### Application plugins + +If you want to add commands or options to the `poetry` script you need +to create an application plugin which implements the `poetry.plugins.ApplicationPlugin` interface. + +The `activate()` method of the application plugin is called after the plugin is loaded +and receives an instance of `console.Application`. + +```python +from cleo.commands.command import Command +from poetry.plugins.application_plugin import ApplicationPlugin + + +class CustomCommand(Command): + + name = "my-command" + + def handle(self) -> int: + self.line("My command") + + return 0 + + +def factory(): + return CustomCommand() + + +class MyApplicationPlugin(ApplicationPlugin): + def activate(self, application): + application.command_loader.register_factory("my-command", factory) +``` + +!!!note + + It's possible to do the following to register the command: + + ```python + application.add(MyCommand()) + ``` + + However, it is **strongly** recommended to register a new factory + in the command loader to defer the loading of the command when it's actually + called. + + This will help keep the performances of Poetry good. + +The plugin also must be declared in the `pyproject.toml` file of the plugin package +as an `application.plugin` plugin: + +```toml +[tool.poetry.plugins."poetry.application.plugin"] +foo-command = "poetry_demo_plugin.plugin:MyApplicationPlugin" +``` + +!!!warning + + A plugin **must not** remove or modify in any way the core commands of Poetry. + + +### Event handler + +Plugins can also listen to specific events and act on them if necessary. + +There are two types of events: application events and generic events. + +These events are fired by [Cleo](https://github.com/sdispater/cleo) +and are accessible from the `cleo.events.console_events` module. + +- `COMMAND`: this event allows attaching listeners before any command is executed. +- `SIGNAL`: this event allows some actions to be performed after the command execution is interrupted. +- `TERMINATE`: this event allows listeners to be attached after the command. +- `ERROR`: this event occurs when an uncaught exception is raised. + +Let's see how to implement an application event handler. For this example +we will see how to load environment variables from a `.env` file before executing +a command. + + +```python +from cleo.events.console_events import COMMAND +from cleo.events.console_command_event import ConsoleCommandEvent +from cleo.events.event_dispatcher import EventDispatcher +from dotenv import load_dotenv +from poetry.console.application import Application +from poetry.console.commands.env_command import EnvCommand +from poetry.plugins.application_plugin import ApplicationPlugin + + +class MyApplicationPlugin(ApplicationPlugin): + def activate(self, application: Application): + application.event_dispatcher.add_listener(COMMAND, self.load_dotenv) + + def load_dotenv( + self, event: ConsoleCommandEvent, event_name: str, dispatcher: EventDispatcher + ) -> None: + command = event.io + if not isinstance(command, EnvCommand): + return + + io = event.io + + if io.is_debug(): + io.write_line("Loading environment variables.") + + load_dotenv() +``` diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index c35ef9579b4..e055a6b6370 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -16,6 +16,7 @@ nav: - Repositories: repositories.md - Managing environments: managing-environments.md - Dependency specification: dependency-specification.md + - Plugins: plugins.md - The pyproject.toml file: pyproject.md - Contributing: contributing.md - FAQ: faq.md diff --git a/poetry.lock b/poetry.lock index fb8897009e2..9b75e396995 100644 --- a/poetry.lock +++ b/poetry.lock @@ -173,6 +173,14 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "entrypoints" +version = "0.3" +description = "Discover and load entry points from installed packages." +category = "main" +optional = false +python-versions = ">=2.7" + [[package]] name = "filelock" version = "3.0.12" @@ -697,7 +705,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "6cbc07e5853bcf1280421b77b6fca85f2f7eb5a6ff12049f65ea116b256d94ea" +content-hash = "c72b0807603d4902cff83901d0e65165e243937b5be90b05c17d3c92a06b4fc8" [metadata.files] appdirs = [ @@ -859,6 +867,10 @@ distlib = [ {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"}, {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"}, ] +entrypoints = [ + {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"}, + {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"}, +] filelock = [ {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, diff --git a/poetry/console/application.py b/poetry/console/application.py index e54cbb5d846..ca0bb73f785 100644 --- a/poetry/console/application.py +++ b/poetry/console/application.py @@ -19,10 +19,10 @@ from cleo.io.inputs.input import Input from cleo.io.io import IO from cleo.io.outputs.output import Output -from cleo.loaders.factory_command_loader import FactoryCommandLoader from poetry.__version__ import __version__ +from .command_loader import CommandLoader from .commands.command import Command @@ -76,6 +76,8 @@ def _load() -> Type[Command]: if TYPE_CHECKING: + from cleo.io.inputs.definition import Definition + from poetry.poetry import Poetry @@ -84,6 +86,9 @@ def __init__(self) -> None: super(Application, self).__init__("poetry", __version__) self._poetry = None + self._io: Optional[IO] = None + self._disable_plugins = False + self._plugins_loaded = False dispatcher = EventDispatcher() dispatcher.add_listener(COMMAND, self.register_command_loggers) @@ -91,9 +96,7 @@ def __init__(self) -> None: dispatcher.add_listener(COMMAND, self.set_installer) self.set_event_dispatcher(dispatcher) - command_loader = FactoryCommandLoader( - {name: load_command(name) for name in COMMANDS} - ) + command_loader = CommandLoader({name: load_command(name) for name in COMMANDS}) self.set_command_loader(command_loader) @property @@ -105,10 +108,16 @@ def poetry(self) -> "Poetry": if self._poetry is not None: return self._poetry - self._poetry = Factory().create_poetry(Path.cwd()) + self._poetry = Factory().create_poetry( + Path.cwd(), io=self._io, disable_plugins=self._disable_plugins + ) return self._poetry + @property + def command_loader(self) -> CommandLoader: + return self._command_loader + def reset_poetry(self) -> None: self._poetry = None @@ -138,8 +147,17 @@ def create_io( io.output.set_formatter(formatter) io.error_output.set_formatter(formatter) + self._io = io + return io + def _run(self, io: IO) -> int: + self._disable_plugins = io.input.parameter_option("--no-plugins") + + self._load_plugins(io) + + return super()._run(io) + def _configure_io(self, io: IO) -> None: # We need to check if the command being run # is the "run" command. @@ -272,6 +290,45 @@ def set_installer( installer.use_executor(poetry.config.get("experimental.new-installer", False)) command.set_installer(installer) + def _load_plugins(self, io: IO) -> None: + if self._plugins_loaded: + return + + from cleo.exceptions import CommandNotFoundException + + name = self._get_command_name(io) + command_name = "" + if name: + try: + command_name = self.find(name).name + except CommandNotFoundException: + pass + + self._disable_plugins = ( + io.input.has_parameter_option("--no-plugins") or command_name == "new" + ) + + if not self._disable_plugins: + from poetry.plugins.plugin_manager import PluginManager + + manager = PluginManager("application.plugin") + manager.load_plugins() + manager.activate(self) + + self._plugins_loaded = True + + @property + def _default_definition(self) -> "Definition": + from cleo.io.inputs.option import Option + + definition = super()._default_definition + + definition.add_option( + Option("--no-plugins", flag=True, description="Disables plugins.") + ) + + return definition + def main() -> int: return Application().run() diff --git a/poetry/console/command_loader.py b/poetry/console/command_loader.py new file mode 100644 index 00000000000..852abe07dc6 --- /dev/null +++ b/poetry/console/command_loader.py @@ -0,0 +1,12 @@ +from typing import Callable + +from cleo.exceptions import LogicException +from cleo.loaders.factory_command_loader import FactoryCommandLoader + + +class CommandLoader(FactoryCommandLoader): + def register_factory(self, command_name: str, factory: Callable) -> None: + if command_name in self._factories: + raise LogicException(f'The command "{command_name}" already exists.') + + self._factories[command_name] = factory diff --git a/poetry/console/events/__init__.py b/poetry/console/events/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/poetry/console/events/console_events.py b/poetry/console/events/console_events.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/poetry/factory.py b/poetry/factory.py index 43555eef6c3..31f5edd308a 100644 --- a/poetry/factory.py +++ b/poetry/factory.py @@ -16,6 +16,8 @@ from .config.file_config_source import FileConfigSource from .locations import CONFIG_DIR from .packages.locker import Locker +from .packages.project_package import ProjectPackage +from .plugins.plugin_manager import PluginManager from .poetry import Poetry from .repositories.pypi_repository import PyPiRepository @@ -30,7 +32,10 @@ class Factory(BaseFactory): """ def create_poetry( - self, cwd: Optional[Path] = None, io: Optional[IO] = None + self, + cwd: Optional[Path] = None, + io: Optional[IO] = None, + disable_plugins: bool = False, ) -> Poetry: if io is None: io = NullIO() @@ -102,8 +107,17 @@ def create_poetry( if io.is_debug(): io.write_line("Deactivating the PyPI repository") + plugin_manager = PluginManager("plugin", disable_plugins=disable_plugins) + plugin_manager.load_plugins() + poetry.set_plugin_manager(plugin_manager) + plugin_manager.activate(poetry, io) + return poetry + @classmethod + def get_package(cls, name: str, version: str) -> ProjectPackage: + return ProjectPackage(name, version, version) + @classmethod def create_config(cls, io: Optional[IO] = None) -> Config: if io is None: diff --git a/poetry/packages/project_package.py b/poetry/packages/project_package.py new file mode 100644 index 00000000000..22379c2026f --- /dev/null +++ b/poetry/packages/project_package.py @@ -0,0 +1,23 @@ +from typing import TYPE_CHECKING +from typing import Optional +from typing import Union + +from poetry.core.packages.project_package import ProjectPackage as _ProjectPackage + + +if TYPE_CHECKING: + from poetry.core.semver.version import Version # noqa + + +class ProjectPackage(_ProjectPackage): + def set_version( + self, version: Union[str, "Version"], pretty_version: Optional[str] = None + ) -> "ProjectPackage": + from poetry.core.semver.version import Version # noqa + + if not isinstance(version, Version): + self._version = Version.parse(version) + self._pretty_version = pretty_version or version + else: + self._version = version + self._pretty_version = pretty_version or version.text diff --git a/poetry/plugins/__init__.py b/poetry/plugins/__init__.py new file mode 100644 index 00000000000..c81eb48d65f --- /dev/null +++ b/poetry/plugins/__init__.py @@ -0,0 +1,5 @@ +from .application_plugin import ApplicationPlugin +from .plugin import Plugin + + +__all__ = ["ApplicationPlugin", "Plugin"] diff --git a/poetry/plugins/application_plugin.py b/poetry/plugins/application_plugin.py new file mode 100644 index 00000000000..0f896282172 --- /dev/null +++ b/poetry/plugins/application_plugin.py @@ -0,0 +1,12 @@ +from .base_plugin import BasePlugin + + +class ApplicationPlugin(BasePlugin): + """ + Base class for plugins. + """ + + type = "application.plugin" + + def activate(self, application): + raise NotImplementedError() diff --git a/poetry/plugins/base_plugin.py b/poetry/plugins/base_plugin.py new file mode 100644 index 00000000000..9e287c8178c --- /dev/null +++ b/poetry/plugins/base_plugin.py @@ -0,0 +1,6 @@ +class BasePlugin(object): + """ + Base class for all plugin types + """ + + PLUGIN_API_VERSION = "1.0.0" diff --git a/poetry/plugins/plugin.py b/poetry/plugins/plugin.py new file mode 100644 index 00000000000..0c2f0711a29 --- /dev/null +++ b/poetry/plugins/plugin.py @@ -0,0 +1,14 @@ +from .base_plugin import BasePlugin + + +class Plugin(BasePlugin): + """ + Generic plugin not related to the console application. + The activate() method must be implemented and receives + the Poetry instance. + """ + + type = "plugin" + + def activate(self, poetry, io): + raise NotImplementedError() diff --git a/poetry/plugins/plugin_manager.py b/poetry/plugins/plugin_manager.py new file mode 100644 index 00000000000..b1c43921f80 --- /dev/null +++ b/poetry/plugins/plugin_manager.py @@ -0,0 +1,55 @@ +import logging + +import entrypoints + +from .application_plugin import ApplicationPlugin +from .plugin import Plugin + + +logger = logging.getLogger(__name__) + + +class PluginManager(object): + """ + This class registers and activates plugins. + """ + + def __init__(self, type, disable_plugins=False): # type: (str, bool) -> None + self._type = type + self._disable_plugins = disable_plugins + self._plugins = [] + + def load_plugins(self): # type: () -> None + if self._disable_plugins: + return + + plugin_entrypoints = entrypoints.get_group_all("poetry.{}".format(self._type)) + + for entrypoint in plugin_entrypoints: + self._load_plugin_entrypoint(entrypoint) + + def add_plugin(self, plugin): # type: (Plugin) -> None + if not isinstance(plugin, (Plugin, ApplicationPlugin)): + raise ValueError( + "The Poetry plugin must be an instance of Plugin or ApplicationPlugin" + ) + + self._plugins.append(plugin) + + def activate(self, *args, **kwargs): + for plugin in self._plugins: + plugin.activate(*args, **kwargs) + + def _load_plugin_entrypoint( + self, entrypoint + ): # type: (entrypoints.EntryPoint) -> None + logger.debug("Loading the {} plugin".format(entrypoint.name)) + + plugin = entrypoint.load() + + if not issubclass(plugin, (Plugin, ApplicationPlugin)): + raise ValueError( + "The Poetry plugin must be an instance of Plugin or ApplicationPlugin" + ) + + self.add_plugin(plugin()) diff --git a/poetry/poetry.py b/poetry/poetry.py index 5c248d46224..54fee74b7fc 100644 --- a/poetry/poetry.py +++ b/poetry/poetry.py @@ -11,6 +11,7 @@ from .config.config import Config from .packages.locker import Locker + from .plugins.plugin_manager import PluginManager from .repositories.pool import Pool @@ -33,6 +34,7 @@ def __init__( self._locker = locker self._config = config self._pool = Pool() + self._plugin_manager = None @property def locker(self) -> "Locker": @@ -60,3 +62,8 @@ def set_config(self, config: "Config") -> "Poetry": self._config = config return self + + def set_plugin_manager(self, plugin_manager: "PluginManager") -> "Poetry": + self._plugin_manager = plugin_manager + + return self diff --git a/pyproject.toml b/pyproject.toml index a487a789dbf..bc9c02754ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ pexpect = "^4.7.0" packaging = "^20.4" virtualenv = "^20.4.3" keyring = "^21.2.0" +entrypoints = "^0.3" importlib-metadata = {version = "^1.6.0", python = "<3.8"} [tool.poetry.dev-dependencies] diff --git a/tests/console/test_application.py b/tests/console/test_application.py new file mode 100644 index 00000000000..6e4e5bab98f --- /dev/null +++ b/tests/console/test_application.py @@ -0,0 +1,101 @@ +import re + +from cleo.testers.application_tester import ApplicationTester +from entrypoints import EntryPoint + +from poetry.console.application import Application +from poetry.console.commands.command import Command +from poetry.plugins.application_plugin import ApplicationPlugin + + +class FooCommand(Command): + name = "foo" + + description = "Foo Command" + + def handle(self): + self.line("foo called") + + return 0 + + +class AddCommandPlugin(ApplicationPlugin): + def activate(self, application: Application): + application.command_loader.register_factory("foo", lambda: FooCommand()) + + +def test_application_with_plugins(mocker): + mocker.patch( + "entrypoints.get_group_all", + return_value=[ + EntryPoint( + "my-plugin", "tests.console.test_application", "AddCommandPlugin" + ) + ], + ) + + app = Application() + + tester = ApplicationTester(app) + tester.execute("") + + assert re.search(r"\s+foo\s+Foo Command", tester.io.fetch_output()) is not None + assert 0 == tester.status_code + + +def test_application_with_plugins_disabled(mocker): + mocker.patch( + "entrypoints.get_group_all", + return_value=[ + EntryPoint( + "my-plugin", "tests.console.test_application", "AddCommandPlugin" + ) + ], + ) + + app = Application() + + tester = ApplicationTester(app) + tester.execute("--no-plugins") + + assert re.search(r"\s+foo\s+Foo Command", tester.io.fetch_output()) is None + assert 0 == tester.status_code + + +def test_application_execute_plugin_command(mocker): + mocker.patch( + "entrypoints.get_group_all", + return_value=[ + EntryPoint( + "my-plugin", "tests.console.test_application", "AddCommandPlugin" + ) + ], + ) + + app = Application() + + tester = ApplicationTester(app) + tester.execute("foo") + + assert "foo called\n" == tester.io.fetch_output() + assert 0 == tester.status_code + + +def test_application_execute_plugin_command_with_plugins_disabled(mocker): + mocker.patch( + "entrypoints.get_group_all", + return_value=[ + EntryPoint( + "my-plugin", "tests.console.test_application", "AddCommandPlugin" + ) + ], + ) + + app = Application() + + tester = ApplicationTester(app) + tester.execute("foo --no-plugins") + + assert "" == tester.io.fetch_output() + assert '\nThe command "foo" does not exist.\n' == tester.io.fetch_error() + assert 1 == tester.status_code diff --git a/tests/plugins/test_plugin_manager.py b/tests/plugins/test_plugin_manager.py new file mode 100644 index 00000000000..b8a4c5d5230 --- /dev/null +++ b/tests/plugins/test_plugin_manager.py @@ -0,0 +1,112 @@ +from pathlib import Path + +import pytest + +from cleo.io.buffered_io import BufferedIO +from entrypoints import EntryPoint + +from poetry.packages.locker import Locker +from poetry.packages.project_package import ProjectPackage +from poetry.plugins import ApplicationPlugin +from poetry.plugins import Plugin +from poetry.plugins.plugin_manager import PluginManager +from poetry.poetry import Poetry + + +CWD = Path(__file__).parent.parent / "fixtures" / "simple_project" + + +class MyPlugin(Plugin): + def activate(self, poetry, io): + io.write_line("Updating version") + poetry.package.set_version("9.9.9") + + +class MyCommandPlugin(ApplicationPlugin): + @property + def commands(self): + return [] + + +class InvalidPlugin: + def activate(self, poetry, io): + io.write_line("Updating version") + poetry.package.version = "9.9.9" + + +@pytest.fixture() +def poetry(tmp_dir, config): + poetry = Poetry( + CWD / "pyproject.toml", + {}, + ProjectPackage("simple-project", "1.2.3"), + Locker(CWD / "poetry.lock", {}), + config, + ) + + return poetry + + +@pytest.fixture() +def io(): + return BufferedIO() + + +@pytest.fixture() +def manager_factory(poetry, io): + def _manager(type="plugin"): + return PluginManager(type) + + return _manager + + +@pytest.fixture() +def no_plugin_manager(poetry, io): + return PluginManager("plugin", disable_plugins=True) + + +def test_load_plugins_and_activate(manager_factory, poetry, io, mocker): + manager = manager_factory() + + mocker.patch( + "entrypoints.get_group_all", + return_value=[ + EntryPoint("my-plugin", "tests.plugins.test_plugin_manager", "MyPlugin") + ], + ) + + manager.load_plugins() + manager.activate(poetry, io) + + assert "9.9.9" == poetry.package.version.text + assert "Updating version\n" == io.fetch_output() + + +def test_load_plugins_with_invalid_plugin(manager_factory, poetry, io, mocker): + manager = manager_factory() + + mocker.patch( + "entrypoints.get_group_all", + return_value=[ + EntryPoint( + "my-plugin", "tests.plugins.test_plugin_manager", "InvalidPlugin" + ) + ], + ) + + with pytest.raises(ValueError): + manager.load_plugins() + + +def test_load_plugins_with_plugins_disabled(no_plugin_manager, poetry, io, mocker): + mocker.patch( + "entrypoints.get_group_all", + return_value=[ + EntryPoint("my-plugin", "tests.plugins.test_plugin_manager", "MyPlugin") + ], + ) + + no_plugin_manager.load_plugins() + + assert "1.2.3" == poetry.package.version.text + assert "" == io.fetch_output() diff --git a/tests/test_factory.py b/tests/test_factory.py index 7a4e9d5ac4a..f966a8407fb 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -6,8 +6,11 @@ import pytest +from entrypoints import EntryPoint + 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 @@ -15,6 +18,12 @@ fixtures_dir = Path(__file__).parent / "fixtures" +class MyPlugin(Plugin): + def activate(self, poetry, io): + io.write_line("Updating version") + poetry.package.set_version("9.9.9") + + def test_create_poetry(): poetry = Factory().create_poetry(fixtures_dir / "sample_project") @@ -224,3 +233,14 @@ def test_create_poetry_with_local_config(fixture_dir): assert not poetry.config.get("virtualenvs.create") assert not poetry.config.get("virtualenvs.options.always-copy") assert not poetry.config.get("virtualenvs.options.system-site-packages") + + +def test_create_poetry_with_plugins(mocker): + mocker.patch( + "entrypoints.get_group_all", + return_value=[EntryPoint("my-plugin", "tests.test_factory", "MyPlugin")], + ) + + poetry = Factory().create_poetry(fixtures_dir / "sample_project") + + assert "9.9.9" == poetry.package.version.text From f5df3af9be82ecc5c96021e578f0479e394849dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 10 Jul 2020 12:31:39 +0200 Subject: [PATCH 131/222] Add a plugin add command --- docs/docs/cli.md | 43 +++ docs/docs/plugins.md | 75 ++++- poetry/console/application.py | 18 +- poetry/console/commands/command.py | 11 +- poetry/console/commands/env_command.py | 6 +- poetry/console/commands/init.py | 24 +- poetry/console/commands/plugin/__init__.py | 0 poetry/console/commands/plugin/add.py | 203 ++++++++++++ poetry/factory.py | 109 +++++-- poetry/installation/installer.py | 2 +- poetry/locations.py | 2 + poetry/puzzle/provider.py | 1 - poetry/repositories/installed_repository.py | 9 +- poetry/utils/env.py | 9 +- tests/console/commands/plugin/__init__.py | 0 tests/console/commands/plugin/test_add.py | 297 ++++++++++++++++++ tests/console/commands/plugin/test_remove.py | 0 .../poetry-plugin/poetry_plugin/__init__.py | 0 .../demo/poetry-plugin/pyproject.toml | 18 ++ 19 files changed, 772 insertions(+), 55 deletions(-) create mode 100644 poetry/console/commands/plugin/__init__.py create mode 100644 poetry/console/commands/plugin/add.py create mode 100644 tests/console/commands/plugin/__init__.py create mode 100644 tests/console/commands/plugin/test_add.py create mode 100644 tests/console/commands/plugin/test_remove.py create mode 100644 tests/fixtures/git/github.com/demo/poetry-plugin/poetry_plugin/__init__.py create mode 100644 tests/fixtures/git/github.com/demo/poetry-plugin/pyproject.toml diff --git a/docs/docs/cli.md b/docs/docs/cli.md index 70208501c87..d105c58ee57 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -538,3 +538,46 @@ To only remove a specific package from a cache, you have to specify the cache en ```bash poetry cache clear pypi:requests:2.24.0 ``` + +## plugin + +The `plugin` namespace regroups sub commands to manage Poetry plugins. + +### `plugin add` + +The `plugin add` command installs Poetry plugins and make them available at runtime. + +For example, to install the `poetry-plugin` plugin, you can run: + +```bash +poetry plugin add poetry-plugin +``` + +The package specification formats supported by the `plugin add` command are the same as the ones supported +by the [`add` command](#add). + +If you just want to check what would happen by installing a plugin, you can use the `--dry-run` option + +```bash +poetry plugin add poetry-plugin --dry-run +``` + +#### Options + +* `--dry-run`: Outputs the operations but will not execute anything (implicitly enables --verbose). + +### `plugin list` + +The `plugin list` command lists all the currently installed plugins. + +```bash +poetry plugin list +``` + +### `plugin remove` + +The `plugin remove` command removes installed plugins. + +```bash +poetry plugin remove poetry-plugin +``` diff --git a/docs/docs/plugins.md b/docs/docs/plugins.md index 22e76ba573f..6f46aed0ec6 100644 --- a/docs/docs/plugins.md +++ b/docs/docs/plugins.md @@ -78,15 +78,15 @@ from poetry.plugins.application_plugin import ApplicationPlugin class CustomCommand(Command): - + name = "my-command" - + def handle(self) -> int: self.line("My command") - + return 0 - + def factory(): return CustomCommand() @@ -127,8 +127,6 @@ foo-command = "poetry_demo_plugin.plugin:MyApplicationPlugin" Plugins can also listen to specific events and act on them if necessary. -There are two types of events: application events and generic events. - These events are fired by [Cleo](https://github.com/sdispater/cleo) and are accessible from the `cleo.events.console_events` module. @@ -159,10 +157,10 @@ class MyApplicationPlugin(ApplicationPlugin): def load_dotenv( self, event: ConsoleCommandEvent, event_name: str, dispatcher: EventDispatcher ) -> None: - command = event.io + command = event.command if not isinstance(command, EnvCommand): return - + io = event.io if io.is_debug(): @@ -170,3 +168,64 @@ class MyApplicationPlugin(ApplicationPlugin): load_dotenv() ``` + + +## Using plugins + +Installed plugin packages are automatically loaded when Poetry starts up. + +You have multiple ways to install plugins for Poetry + +### The `plugin add` command + +This is the easiest way and should account for all the ways Poetry can be installed. + +```bash +poetry plugin add poetry-plugin +``` + +The `plugin add` command will ensure that the plugin is compatible with the current version of Poetry +and install the needed packages for the plugin to work. + +The package specification formats supported by the `plugin add` command are the same as the ones supported +by the [`add` command](/docs/cli/#add). + +If you no longer need a plugin and want to uninstall it, you can use the `plugin remove` command. + +```shell +poetry plugin remove poetry-plugin +``` + +You can also list all currently installed plugins by running: + +```shell +poetry plugin list +``` + +### With `pipx inject` + +If you used `pipx` to install Poetry you can add the plugin packages via the `pipx inject` command. + +```shell +pipx inject poetry poetry-plugin +``` + +If you want to uninstall a plugin, you can run: + +```shell +pipx runpip poetry uninstall poetry-plugin +``` + +### With `pip` + +If you used `pip` to install Poetry you can add the plugin packages via the `pip install` command. + +```shell +pip install --user poetry-plugin +``` + +If you want to uninstall a plugin, you can run: + +```shell +pip uninstall poetry-plugin +``` diff --git a/poetry/console/application.py b/poetry/console/application.py index ca0bb73f785..268aa7cb9ce 100644 --- a/poetry/console/application.py +++ b/poetry/console/application.py @@ -70,6 +70,8 @@ def _load() -> Type[Command]: "env list", "env remove", "env use", + # Plugin commands + "plugin add", # Self commands "self update", ] @@ -78,6 +80,7 @@ def _load() -> Type[Command]: if TYPE_CHECKING: from cleo.io.inputs.definition import Definition + from poetry.console.commands.installer_command import InstallerCommand from poetry.poetry import Poetry @@ -92,8 +95,8 @@ def __init__(self) -> None: dispatcher = EventDispatcher() dispatcher.add_listener(COMMAND, self.register_command_loggers) - dispatcher.add_listener(COMMAND, self.set_env) - dispatcher.add_listener(COMMAND, self.set_installer) + dispatcher.add_listener(COMMAND, self.configure_env) + dispatcher.add_listener(COMMAND, self.configure_installer) self.set_event_dispatcher(dispatcher) command_loader = CommandLoader({name: load_command(name) for name in COMMANDS}) @@ -239,7 +242,9 @@ def register_command_loggers( logger.setLevel(level) - def set_env(self, event: ConsoleCommandEvent, event_name: str, _: Any) -> None: + def configure_env( + self, event: ConsoleCommandEvent, event_name: str, _: Any + ) -> None: from .commands.env_command import EnvCommand command: EnvCommand = cast(EnvCommand, event.command) @@ -262,7 +267,7 @@ def set_env(self, event: ConsoleCommandEvent, event_name: str, _: Any) -> None: command.set_env(env) - def set_installer( + def configure_installer( self, event: ConsoleCommandEvent, event_name: str, _: Any ) -> None: from .commands.installer_command import InstallerCommand @@ -276,11 +281,14 @@ def set_installer( if command.installer is not None: return + self._configure_installer(command, event.io) + + def _configure_installer(self, command: "InstallerCommand", io: "IO") -> None: from poetry.installation.installer import Installer poetry = command.poetry installer = Installer( - event.io, + io, command.env, poetry.package, poetry.locker, diff --git a/poetry/console/commands/command.py b/poetry/console/commands/command.py index be87fe99b7a..a717fa4e666 100644 --- a/poetry/console/commands/command.py +++ b/poetry/console/commands/command.py @@ -1,4 +1,5 @@ from typing import TYPE_CHECKING +from typing import Optional from cleo.commands.command import Command as BaseCommand @@ -11,9 +12,17 @@ class Command(BaseCommand): loggers = [] + _poetry: Optional["Poetry"] = None + @property def poetry(self) -> "Poetry": - return self.get_application().poetry + if self._poetry is None: + return self.get_application().poetry + + return self._poetry + + def set_poetry(self, poetry: "Poetry") -> None: + self._poetry = poetry def get_application(self) -> "Application": return self.application diff --git a/poetry/console/commands/env_command.py b/poetry/console/commands/env_command.py index beb40e1e88e..fd44b415c00 100644 --- a/poetry/console/commands/env_command.py +++ b/poetry/console/commands/env_command.py @@ -4,7 +4,7 @@ if TYPE_CHECKING: - from poetry.utils.env import VirtualEnv + from poetry.utils.env import Env class EnvCommand(Command): @@ -14,8 +14,8 @@ def __init__(self) -> None: super(EnvCommand, self).__init__() @property - def env(self) -> "VirtualEnv": + def env(self) -> "Env": return self._env - def set_env(self, env: "VirtualEnv") -> None: + def set_env(self, env: "Env") -> None: self._env = env diff --git a/poetry/console/commands/init.py b/poetry/console/commands/init.py index 2e10f2ead01..b2e4fd0a315 100644 --- a/poetry/console/commands/init.py +++ b/poetry/console/commands/init.py @@ -430,20 +430,32 @@ def _parse_requirements(self, requirements: List[str]) -> List[Dict[str, str]]: result.append(pair) continue - elif (os.path.sep in requirement or "/" in requirement) and cwd.joinpath( - requirement - ).exists(): - path = cwd.joinpath(requirement) + elif (os.path.sep in requirement or "/" in requirement) and ( + cwd.joinpath(requirement).exists() + or Path(requirement).expanduser().exists() + and Path(requirement).expanduser().is_absolute() + ): + path = Path(requirement).expanduser() + is_absolute = path.is_absolute() + + if not path.is_absolute(): + path = cwd.joinpath(requirement) + if path.is_file(): package = Provider.get_package_from_file(path.resolve()) else: - package = Provider.get_package_from_directory(path) + package = Provider.get_package_from_directory(path.resolve()) result.append( dict( [ ("name", package.name), - ("path", path.relative_to(cwd).as_posix()), + ( + "path", + path.relative_to(cwd).as_posix() + if not is_absolute + else path.as_posix(), + ), ] + ([("extras", extras)] if extras else []) ) diff --git a/poetry/console/commands/plugin/__init__.py b/poetry/console/commands/plugin/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/poetry/console/commands/plugin/add.py b/poetry/console/commands/plugin/add.py new file mode 100644 index 00000000000..8418aa25c89 --- /dev/null +++ b/poetry/console/commands/plugin/add.py @@ -0,0 +1,203 @@ +import os + +from typing import TYPE_CHECKING +from typing import Dict +from typing import List +from typing import cast + +from cleo.helpers import argument +from cleo.helpers import option + +from ..init import InitCommand + + +if TYPE_CHECKING: + from poetry.console.application import Application # noqa + from poetry.console.commands.update import UpdateCommand # noqa + + +class PluginAddCommand(InitCommand): + + name = "plugin add" + + description = "Adds new plugins." + + arguments = [ + argument("plugins", "The names of the plugins to install.", multiple=True), + ] + + options = [ + option( + "dry-run", + None, + "Output the operations but do not execute anything (implicitly enables --verbose).", + ) + ] + + help = """ +The plugin add command installs Poetry plugins globally. + +It works similarly to the add command: + +If you do not specify a version constraint, poetry will choose a suitable one based on the available package versions. + +You can specify a package in the following forms: + + - A single name (requests) + - A name and a constraint (requests@^2.23.0) + - A git url (git+https://github.com/python-poetry/poetry.git) + - A git url with a revision (git+https://github.com/python-poetry/poetry.git#develop) + - A git SSH url (git+ssh://github.com/python-poetry/poetry.git) + - A git SSH url with a revision (git+ssh://github.com/python-poetry/poetry.git#develop) + - A file path (../my-package/my-package.whl) + - A directory (../my-package/) + - A url (https://example.com/packages/my-package-0.1.0.tar.gz)\ +""" + + def handle(self) -> int: + from pathlib import Path + + import tomlkit + + from cleo.io.inputs.string_input import StringInput + from cleo.io.io import IO + + from poetry.core.pyproject.toml import PyProjectTOML + from poetry.core.semver.helpers import parse_constraint + from poetry.factory import Factory + from poetry.packages.project_package import ProjectPackage + from poetry.repositories.installed_repository import InstalledRepository + from poetry.utils.env import EnvManager + + plugins = self.argument("plugins") + + # Plugins should be installed in the system env to be globally available + system_env = EnvManager.get_system_env() + + env_dir = Path( + os.getenv("POETRY_HOME") if os.getenv("POETRY_HOME") else system_env.path + ) + + # We check for the plugins existence first. + if env_dir.joinpath("pyproject.toml").exists(): + pyproject = tomlkit.loads( + env_dir.joinpath("pyproject.toml").read_text(encoding="utf-8") + ) + poetry_content = pyproject["tool"]["poetry"] + existing_packages = self.get_existing_packages_from_input( + plugins, poetry_content, "dependencies" + ) + + if existing_packages: + self.notify_about_existing_packages(existing_packages) + + plugins = [plugin for plugin in plugins if plugin not in existing_packages] + + if not plugins: + return 0 + + plugins = self._determine_requirements(plugins) + + # We retrieve the packages installed in the system environment. + # We assume that this environment will be a self contained virtual environment + # built by the official installer or by pipx. + # If not, it might lead to side effects since other installed packages + # might not be required by Poetry but still taken into account when resolving dependencies. + installed_repository = InstalledRepository.load( + system_env, with_dependencies=True + ) + + root_package = None + for package in installed_repository.packages: + if package.name == "poetry": + root_package = ProjectPackage(package.name, package.version) + for dependency in package.requires: + root_package.add_dependency(dependency) + + break + + root_package.python_versions = ".".join( + str(v) for v in system_env.version_info[:3] + ) + # We create a `pyproject.toml` file based on all the information + # we have about the current environment. + if not env_dir.joinpath("pyproject.toml").exists(): + Factory.create_pyproject_from_package(root_package, env_dir) + + # We add the plugins to the dependencies section of the previously + # created `pyproject.toml` file + pyproject = PyProjectTOML(env_dir.joinpath("pyproject.toml")) + poetry_content = pyproject.poetry_config + poetry_dependency_section = poetry_content["dependencies"] + plugin_names = [] + for plugin in plugins: + if "version" in plugin: + # Validate version constraint + parse_constraint(plugin["version"]) + + constraint = tomlkit.inline_table() + for name, value in plugin.items(): + if name == "name": + continue + + constraint[name] = value + + if len(constraint) == 1 and "version" in constraint: + constraint = constraint["version"] + + poetry_dependency_section[plugin["name"]] = constraint + plugin_names.append(plugin["name"]) + + pyproject.save() + + # From this point forward, all the logic will be deferred to + # the update command, by using the previously created `pyproject.toml` + # file. + application = cast("Application", self.application) + update_command: "UpdateCommand" = cast( + "UpdateCommand", application.find("update") + ) + # We won't go through the event dispatching done by the application + # so we need to configure the command manually + update_command.set_poetry(Factory().create_poetry(env_dir)) + update_command.set_env(system_env) + application._configure_installer(update_command, self._io) + + argv = ["update"] + plugin_names + if self.option("dry-run"): + argv.append("--dry-run") + + return update_command.run( + IO( + StringInput(" ".join(argv)), + self._io.output, + self._io.error_output, + ) + ) + + def get_existing_packages_from_input( + self, packages: List[str], poetry_content: Dict, target_section: str + ) -> List[str]: + existing_packages = [] + + for name in packages: + for key in poetry_content[target_section]: + if key.lower() == name.lower(): + existing_packages.append(name) + + return existing_packages + + def notify_about_existing_packages(self, existing_packages: List[str]) -> None: + self.line( + "The following plugins are already present in the " + "pyproject.toml file and will be skipped:\n" + ) + for name in existing_packages: + self.line(" • {name}".format(name=name)) + + self.line( + "\nIf you want to update it to the latest compatible version, " + "you can use `poetry plugin update package`.\n" + "If you prefer to upgrade it to the latest available version, " + "you can use `poetry plugin add package@latest`.\n" + ) diff --git a/poetry/factory.py b/poetry/factory.py index 31f5edd308a..36078bed1e5 100644 --- a/poetry/factory.py +++ b/poetry/factory.py @@ -4,6 +4,7 @@ from pathlib import Path from typing import TYPE_CHECKING from typing import Dict +from typing import List from typing import Optional from cleo.io.io import IO @@ -80,32 +81,9 @@ def create_poetry( ) # Configuring sources - sources = poetry.local_config.get("source", []) - for source in sources: - repository = self.create_legacy_repository(source, config) - is_default = source.get("default", False) - is_secondary = source.get("secondary", False) - if io.is_debug(): - message = "Adding repository {} ({})".format( - repository.name, repository.url - ) - if is_default: - message += " and setting it as the default one" - elif is_secondary: - message += " and setting it as secondary" - - io.write_line(message) - - poetry.pool.add_repository(repository, is_default, secondary=is_secondary) - - # Always put PyPI last to prefer private repositories - # but only if we have no other default source - if not poetry.pool.has_default(): - has_sources = bool(sources) - poetry.pool.add_repository(PyPiRepository(), not has_sources, has_sources) - else: - if io.is_debug(): - io.write_line("Deactivating the PyPI repository") + self.configure_sources( + poetry, poetry.local_config.get("source", []), config, io + ) plugin_manager = PluginManager("plugin", disable_plugins=disable_plugins) plugin_manager.load_plugins() @@ -154,8 +132,39 @@ def create_config(cls, io: Optional[IO] = None) -> Config: return config + @classmethod + def configure_sources( + cls, poetry: "Poetry", sources: List[Dict[str, str]], config: "Config", io: "IO" + ) -> None: + for source in sources: + repository = cls.create_legacy_repository(source, config) + is_default = source.get("default", False) + is_secondary = source.get("secondary", False) + if io.is_debug(): + message = "Adding repository {} ({})".format( + repository.name, repository.url + ) + if is_default: + message += " and setting it as the default one" + elif is_secondary: + message += " and setting it as secondary" + + io.write_line(message) + + poetry.pool.add_repository(repository, is_default, secondary=is_secondary) + + # Always put PyPI last to prefer private repositories + # but only if we have no other default source + if not poetry.pool.has_default(): + has_sources = bool(sources) + poetry.pool.add_repository(PyPiRepository(), not has_sources, has_sources) + else: + if io.is_debug(): + io.write_line("Deactivating the PyPI repository") + + @classmethod def create_legacy_repository( - self, source: Dict[str, str], auth_config: Config + cls, source: Dict[str, str], auth_config: Config ) -> "LegacyRepository": from .repositories.legacy_repository import LegacyRepository from .utils.helpers import get_cert @@ -178,3 +187,49 @@ def create_legacy_repository( cert=get_cert(auth_config, name), client_cert=get_client_cert(auth_config, name), ) + + @classmethod + def create_pyproject_from_package( + cls, package: "ProjectPackage", path: "Path" + ) -> None: + import tomlkit + + from poetry.layouts.layout import POETRY_DEFAULT + + pyproject = tomlkit.loads(POETRY_DEFAULT) + content = pyproject["tool"]["poetry"] + + content["name"] = package.name + content["version"] = package.version.text + content["description"] = package.description + content["authors"] = package.authors + + dependency_section = content["dependencies"] + dependency_section["python"] = package.python_versions + + for dep in package.requires: + constraint = tomlkit.inline_table() + if dep.is_vcs(): + constraint[dep.vcs] = dep.source_url + + if dep.reference: + constraint["rev"] = dep.reference + elif dep.is_file() or dep.is_directory(): + constraint["path"] = dep.source_url + else: + constraint["version"] = dep.pretty_constraint + + if not dep.marker.is_any(): + constraint["markers"] = str(dep.marker) + + if dep.extras: + constraint["extras"] = list(sorted(dep.extras)) + + if len(constraint) == 1 and "version" in constraint: + constraint = constraint["version"] + + dependency_section[dep.name] = constraint + + path.joinpath("pyproject.toml").write_text( + pyproject.as_string(), encoding="utf-8" + ) diff --git a/poetry/installation/installer.py b/poetry/installation/installer.py index b778711479b..bc254c135e3 100644 --- a/poetry/installation/installer.py +++ b/poetry/installation/installer.py @@ -40,7 +40,7 @@ def __init__( locker: Locker, pool: Pool, config: Config, - installed: Union[InstalledRepository, None] = None, + installed: Union[Repository, None] = None, executor: Optional[Executor] = None, ): self._io = io diff --git a/poetry/locations.py b/poetry/locations.py index 5bd4b7feb17..ff38c9c9e82 100644 --- a/poetry/locations.py +++ b/poetry/locations.py @@ -2,9 +2,11 @@ from .utils.appdirs import user_cache_dir from .utils.appdirs import user_config_dir +from .utils.appdirs import user_data_dir CACHE_DIR = user_cache_dir("pypoetry") +DATA_DIR = user_data_dir("pypoetry") CONFIG_DIR = user_config_dir("pypoetry") REPOSITORY_CACHE_DIR = Path(CACHE_DIR) / "cache" / "repositories" diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index f18a56e0579..3d384a6697d 100644 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -431,7 +431,6 @@ def incompatibilities_for( ] def complete_package(self, package: DependencyPackage) -> DependencyPackage: - if package.is_root(): package = package.clone() requires = package.all_requires diff --git a/poetry/repositories/installed_repository.py b/poetry/repositories/installed_repository.py index c3f9e13c764..f33bbd9b99f 100644 --- a/poetry/repositories/installed_repository.py +++ b/poetry/repositories/installed_repository.py @@ -100,10 +100,12 @@ def is_vcs_package(cls, package: Union[Path, Package], env: Env) -> bool: return True @classmethod - def load(cls, env: Env) -> "InstalledRepository": + def load(cls, env: Env, with_dependencies: bool = False) -> "InstalledRepository": """ Load installed packages. """ + from poetry.core.packages.dependency import Dependency + repo = cls() seen = set() @@ -118,6 +120,11 @@ def load(cls, env: Env) -> "InstalledRepository": package = Package(name, version, version) package.description = distribution.metadata.get("summary", "") + if with_dependencies: + for require in distribution.metadata.get_all("requires-dist", []): + dep = Dependency.create_from_pep_508(require) + package.add_dependency(dep) + if package.name in seen: continue diff --git a/poetry/utils/env.py b/poetry/utils/env.py index a4588617d1e..4678cfb5558 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -803,7 +803,7 @@ def create_venv( p_venv = os.path.normcase(str(venv)) if any(p.startswith(p_venv) for p in paths): # Running properly in the virtualenv, don't need to do anything - return SystemEnv(Path(sys.prefix), self.get_base_prefix()) + return self.get_system_env() return VirtualEnv(venv) @@ -874,7 +874,12 @@ def remove_venv(cls, path: Union[Path, str]) -> None: elif file_path.is_dir(): shutil.rmtree(str(file_path)) - def get_base_prefix(self) -> Path: + @classmethod + def get_system_env(cls) -> "SystemEnv": + return SystemEnv(Path(sys.prefix), cls.get_base_prefix()) + + @classmethod + def get_base_prefix(cls) -> Path: if hasattr(sys, "real_prefix"): return Path(sys.real_prefix) diff --git a/tests/console/commands/plugin/__init__.py b/tests/console/commands/plugin/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/console/commands/plugin/test_add.py b/tests/console/commands/plugin/test_add.py new file mode 100644 index 00000000000..fdcc46ed423 --- /dev/null +++ b/tests/console/commands/plugin/test_add.py @@ -0,0 +1,297 @@ +import pytest + +from poetry.__version__ import __version__ +from poetry.core.packages.package import Package +from poetry.factory import Factory +from poetry.repositories.installed_repository import InstalledRepository +from poetry.repositories.pool import Pool +from poetry.utils.env import EnvManager + + +@pytest.fixture() +def tester(command_tester_factory): + return command_tester_factory("plugin add") + + +@pytest.fixture() +def installed(): + repository = InstalledRepository() + + repository.add_package(Package("poetry", __version__)) + + return repository + + +def configure_sources_factory(repo): + def _configure_sources(poetry, sources, config, io): + pool = Pool() + pool.add_repository(repo) + poetry.set_pool(pool) + + return _configure_sources + + +@pytest.fixture(autouse=True) +def setup_mocks(mocker, env, repo, installed): + mocker.patch.object(EnvManager, "get_system_env", return_value=env) + mocker.patch.object(InstalledRepository, "load", return_value=installed) + mocker.patch.object( + Factory, "configure_sources", side_effect=configure_sources_factory(repo) + ) + + +def test_add_no_constraint(app, repo, tester, env, installed): + repo.add_package(Package("poetry-plugin", "0.1.0")) + + tester.execute("poetry-plugin") + + expected = """\ +Using version ^0.1.0 for poetry-plugin +Updating dependencies +Resolving dependencies... + +Writing lock file + +Package operations: 1 install, 0 updates, 0 removals + + • Installing poetry-plugin (0.1.0) +""" + + assert tester.io.fetch_output() == expected + + update_command = app.find("update") + assert update_command.poetry.file.parent == env.path + assert update_command.poetry.locker.lock.parent == env.path + assert update_command.poetry.locker.lock.exists() + + content = update_command.poetry.file.read()["tool"]["poetry"] + assert "poetry-plugin" in content["dependencies"] + assert content["dependencies"]["poetry-plugin"] == "^0.1.0" + + +def test_add_with_constraint(app, repo, tester, env, installed): + repo.add_package(Package("poetry-plugin", "0.1.0")) + repo.add_package(Package("poetry-plugin", "0.2.0")) + + tester.execute("poetry-plugin@^0.2.0") + + expected = """\ +Updating dependencies +Resolving dependencies... + +Writing lock file + +Package operations: 1 install, 0 updates, 0 removals + + • Installing poetry-plugin (0.2.0) +""" + + assert tester.io.fetch_output() == expected + + update_command = app.find("update") + assert update_command.poetry.file.parent == env.path + assert update_command.poetry.locker.lock.parent == env.path + + content = update_command.poetry.file.read()["tool"]["poetry"] + assert "poetry-plugin" in content["dependencies"] + assert content["dependencies"]["poetry-plugin"] == "^0.2.0" + + +def test_add_with_git_constraint(app, repo, tester, env, installed): + repo.add_package(Package("pendulum", "2.0.5")) + + tester.execute("git+https://github.com/demo/poetry-plugin.git") + + expected = """\ +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) +""" + + assert tester.io.fetch_output() == expected + + update_command = app.find("update") + assert update_command.poetry.file.parent == env.path + assert update_command.poetry.locker.lock.parent == env.path + + content = update_command.poetry.file.read()["tool"]["poetry"] + assert "poetry-plugin" in content["dependencies"] + assert content["dependencies"]["poetry-plugin"] == { + "git": "https://github.com/demo/poetry-plugin.git" + } + + +def test_add_with_git_constraint_with_extras(app, repo, tester, env, installed): + repo.add_package(Package("pendulum", "2.0.5")) + repo.add_package(Package("tomlkit", "0.7.0")) + + tester.execute("git+https://github.com/demo/poetry-plugin.git[foo]") + + expected = """\ +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) +""" + + assert tester.io.fetch_output() == expected + + update_command = app.find("update") + assert update_command.poetry.file.parent == env.path + assert update_command.poetry.locker.lock.parent == env.path + + content = update_command.poetry.file.read()["tool"]["poetry"] + assert "poetry-plugin" in content["dependencies"] + assert content["dependencies"]["poetry-plugin"] == { + "git": "https://github.com/demo/poetry-plugin.git", + "extras": ["foo"], + } + + +def test_add_existing_plugin_warns_about_no_operation( + app, repo, tester, env, installed +): + env.path.joinpath("pyproject.toml").write_text( + """\ +[tool.poetry] +name = "poetry" +version = "1.2.0" +description = "Python dependency management and packaging made easy." +authors = [ + "Sébastien Eustace " +] + +[tool.poetry.dependencies] +python = "^3.6" +poetry-plugin = "^1.2.3" +""", + encoding="utf-8", + ) + + installed.add_package(Package("poetry-plugin", "1.2.3")) + + repo.add_package(Package("poetry-plugin", "1.2.3")) + + tester.execute("poetry-plugin") + + expected = """\ +The following plugins are already present in the pyproject.toml file and will be skipped: + + • poetry-plugin + +If you want to update it to the latest compatible version, you can use `poetry plugin update package`. +If you prefer to upgrade it to the latest available version, you can use `poetry plugin add package@latest`. + +""" + + assert tester.io.fetch_output() == expected + + update_command = app.find("update") + # The update command should not have been called + assert update_command.poetry.file.parent != env.path + + +def test_add_existing_plugin_updates_if_requested( + app, repo, tester, env, installed, mocker +): + env.path.joinpath("pyproject.toml").write_text( + """\ +[tool.poetry] +name = "poetry" +version = "1.2.0" +description = "Python dependency management and packaging made easy." +authors = [ + "Sébastien Eustace " +] + +[tool.poetry.dependencies] +python = "^3.6" +poetry-plugin = "^1.2.3" +""", + encoding="utf-8", + ) + + installed.add_package(Package("poetry-plugin", "1.2.3")) + + repo.add_package(Package("poetry-plugin", "1.2.3")) + repo.add_package(Package("poetry-plugin", "2.3.4")) + + tester.execute("poetry-plugin@latest") + + expected = """\ +Using version ^2.3.4 for poetry-plugin +Updating dependencies +Resolving dependencies... + +Writing lock file + +Package operations: 0 installs, 1 update, 0 removals + + • Updating poetry-plugin (1.2.3 -> 2.3.4) +""" + + assert tester.io.fetch_output() == expected + + update_command = app.find("update") + assert update_command.poetry.file.parent == env.path + assert update_command.poetry.locker.lock.parent == env.path + assert update_command.poetry.locker.lock.exists() + + content = update_command.poetry.file.read()["tool"]["poetry"] + assert "poetry-plugin" in content["dependencies"] + assert content["dependencies"]["poetry-plugin"] == "^2.3.4" + + +def test_adding_a_plugin_can_update_poetry_dependencies_if_needed( + app, repo, tester, env, installed +): + poetry_package = Package("poetry", "1.2.0") + poetry_package.add_dependency(Factory.create_dependency("tomlkit", "^0.7.0")) + + plugin_package = Package("poetry-plugin", "1.2.3") + plugin_package.add_dependency(Factory.create_dependency("tomlkit", "^0.7.2")) + + installed.add_package(poetry_package) + installed.add_package(Package("tomlkit", "0.7.1")) + + repo.add_package(plugin_package) + repo.add_package(Package("tomlkit", "0.7.1")) + repo.add_package(Package("tomlkit", "0.7.2")) + + tester.execute("poetry-plugin") + + expected = """\ +Using version ^1.2.3 for poetry-plugin +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) +""" + + assert tester.io.fetch_output() == expected + + update_command = app.find("update") + assert update_command.poetry.file.parent == env.path + assert update_command.poetry.locker.lock.parent == env.path + assert update_command.poetry.locker.lock.exists() + + content = update_command.poetry.file.read()["tool"]["poetry"] + assert "poetry-plugin" in content["dependencies"] + assert content["dependencies"]["poetry-plugin"] == "^1.2.3" diff --git a/tests/console/commands/plugin/test_remove.py b/tests/console/commands/plugin/test_remove.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/fixtures/git/github.com/demo/poetry-plugin/poetry_plugin/__init__.py b/tests/fixtures/git/github.com/demo/poetry-plugin/poetry_plugin/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/fixtures/git/github.com/demo/poetry-plugin/pyproject.toml b/tests/fixtures/git/github.com/demo/poetry-plugin/pyproject.toml new file mode 100644 index 00000000000..b45d9d976eb --- /dev/null +++ b/tests/fixtures/git/github.com/demo/poetry-plugin/pyproject.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "poetry-plugin" +version = "0.1.2" +description = "Some description." +authors = [ + "Sébastien Eustace " +] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.6" +pendulum = "^2.0" +tomlkit = {version = "^0.7.0", optional = true} + +[tool.poetry.extras] +foo = ["tomlkit"] + +[tool.poetry.dev-dependencies] From 594697d5aad9a8ff70d01c9938f8dad4e118d8b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Mon, 8 Mar 2021 15:49:03 +0100 Subject: [PATCH 132/222] Add a `plugin show` command --- docs/docs/cli.md | 6 +- docs/docs/plugins.md | 2 +- poetry/console/application.py | 1 + poetry/console/commands/plugin/show.py | 95 ++++++++++++ poetry/plugins/plugin_manager.py | 7 +- tests/console/commands/plugin/test_show.py | 172 +++++++++++++++++++++ 6 files changed, 278 insertions(+), 5 deletions(-) create mode 100644 poetry/console/commands/plugin/show.py create mode 100644 tests/console/commands/plugin/test_show.py diff --git a/docs/docs/cli.md b/docs/docs/cli.md index d105c58ee57..1bf9ce1b134 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -566,12 +566,12 @@ poetry plugin add poetry-plugin --dry-run * `--dry-run`: Outputs the operations but will not execute anything (implicitly enables --verbose). -### `plugin list` +### `plugin show` -The `plugin list` command lists all the currently installed plugins. +The `plugin show` command lists all the currently installed plugins. ```bash -poetry plugin list +poetry plugin show ``` ### `plugin remove` diff --git a/docs/docs/plugins.md b/docs/docs/plugins.md index 6f46aed0ec6..637306323a9 100644 --- a/docs/docs/plugins.md +++ b/docs/docs/plugins.md @@ -199,7 +199,7 @@ poetry plugin remove poetry-plugin You can also list all currently installed plugins by running: ```shell -poetry plugin list +poetry plugin show ``` ### With `pipx inject` diff --git a/poetry/console/application.py b/poetry/console/application.py index 268aa7cb9ce..3285e681c87 100644 --- a/poetry/console/application.py +++ b/poetry/console/application.py @@ -72,6 +72,7 @@ def _load() -> Type[Command]: "env use", # Plugin commands "plugin add", + "plugin show", # Self commands "self update", ] diff --git a/poetry/console/commands/plugin/show.py b/poetry/console/commands/plugin/show.py new file mode 100644 index 00000000000..ced1a0fd282 --- /dev/null +++ b/poetry/console/commands/plugin/show.py @@ -0,0 +1,95 @@ +from collections import defaultdict +from typing import TYPE_CHECKING +from typing import DefaultDict +from typing import Dict +from typing import List +from typing import Union + +from poetry.console.commands.command import Command + + +if TYPE_CHECKING: + from poetry.core.packages.package import Package + + +class PluginShowCommand(Command): + + name = "plugin show" + + description = "Shows information about the currently installed plugins." + + def handle(self) -> int: + from poetry.plugins.application_plugin import ApplicationPlugin + from poetry.plugins.plugin_manager import PluginManager + from poetry.repositories.installed_repository import InstalledRepository + from poetry.utils.env import EnvManager + from poetry.utils.helpers import canonicalize_name + + plugins: DefaultDict[str, Dict[str, Union["Package", List[str]]]] = defaultdict( + lambda: { + "package": None, + "plugins": [], + "application_plugins": [], + } + ) + + entry_points = ( + PluginManager("application.plugin").get_plugin_entry_points() + + PluginManager("plugin").get_plugin_entry_points() + ) + + system_env = EnvManager.get_system_env() + installed_repository = InstalledRepository.load( + system_env, with_dependencies=True + ) + + packages_by_name = {pkg.name: pkg for pkg in installed_repository.packages} + + for entry_point in entry_points: + plugin = entry_point.load() + category = "plugins" + if issubclass(plugin, ApplicationPlugin): + category = "application_plugins" + + package = packages_by_name[canonicalize_name(entry_point.name)] + plugins[package.pretty_name]["package"] = package + plugins[package.pretty_name][category].append(entry_point) + + for name, info in plugins.items(): + package = info["package"] + self.line("") + self.line( + " • {} ({}){}".format( + name, + package.version, + " " + package.description if package.description else "", + ) + ) + provide_line = " " + if info["plugins"]: + provide_line += " {} plugin{}".format( + len(info["plugins"]), "s" if len(info["plugins"]) > 1 else "" + ) + + if info["application_plugins"]: + if info["plugins"]: + provide_line += " and" + + provide_line += " {} application plugin{}".format( + len(info["application_plugins"]), + "s" if len(info["application_plugins"]) > 1 else "", + ) + + self.line(provide_line) + + if package.requires: + self.line("") + self.line(" Dependencies") + for dependency in package.requires: + self.line( + " - {} ({})".format( + dependency.pretty_name, dependency.pretty_constraint + ) + ) + + return 0 diff --git a/poetry/plugins/plugin_manager.py b/poetry/plugins/plugin_manager.py index b1c43921f80..6f9e8f49ba2 100644 --- a/poetry/plugins/plugin_manager.py +++ b/poetry/plugins/plugin_manager.py @@ -1,5 +1,7 @@ import logging +from typing import List + import entrypoints from .application_plugin import ApplicationPlugin @@ -23,11 +25,14 @@ def load_plugins(self): # type: () -> None if self._disable_plugins: return - plugin_entrypoints = entrypoints.get_group_all("poetry.{}".format(self._type)) + plugin_entrypoints = self.get_plugin_entry_points() for entrypoint in plugin_entrypoints: self._load_plugin_entrypoint(entrypoint) + def get_plugin_entry_points(self) -> List[entrypoints.EntryPoint]: + return entrypoints.get_group_all("poetry.{}".format(self._type)) + def add_plugin(self, plugin): # type: (Plugin) -> None if not isinstance(plugin, (Plugin, ApplicationPlugin)): raise ValueError( diff --git a/tests/console/commands/plugin/test_show.py b/tests/console/commands/plugin/test_show.py new file mode 100644 index 00000000000..80c990376da --- /dev/null +++ b/tests/console/commands/plugin/test_show.py @@ -0,0 +1,172 @@ +import pytest + +from entrypoints import EntryPoint as _EntryPoint + +from poetry.__version__ import __version__ +from poetry.core.packages.package import Package +from poetry.factory import Factory +from poetry.plugins.application_plugin import ApplicationPlugin +from poetry.plugins.plugin import Plugin +from poetry.repositories.installed_repository import InstalledRepository +from poetry.repositories.pool import Pool +from poetry.utils.env import EnvManager + + +class EntryPoint(_EntryPoint): + def load(self): + if "ApplicationPlugin" in self.object_name: + return ApplicationPlugin + + return Plugin + + +@pytest.fixture() +def tester(command_tester_factory): + return command_tester_factory("plugin show") + + +@pytest.fixture() +def installed(): + repository = InstalledRepository() + + repository.add_package(Package("poetry", __version__)) + + return repository + + +def configure_sources_factory(repo): + def _configure_sources(poetry, sources, config, io): + pool = Pool() + pool.add_repository(repo) + poetry.set_pool(pool) + + return _configure_sources + + +@pytest.fixture(autouse=True) +def setup_mocks(mocker, env, repo, installed): + mocker.patch.object(EnvManager, "get_system_env", return_value=env) + mocker.patch.object(InstalledRepository, "load", return_value=installed) + mocker.patch.object( + Factory, "configure_sources", side_effect=configure_sources_factory(repo) + ) + + +def test_show_displays_installed_plugins(app, tester, installed, mocker): + mocker.patch( + "entrypoints.get_group_all", + side_effect=[ + [ + EntryPoint( + "poetry-plugin", + "poetry_plugin.plugins:ApplicationPlugin", + "FirstApplicationPlugin", + ) + ], + [ + EntryPoint( + "poetry-plugin", + "poetry_plugin.plugins:Plugin", + "FirstPlugin", + ) + ], + ], + ) + + installed.add_package(Package("poetry-plugin", "1.2.3")) + + tester.execute("") + + expected = """ + • poetry-plugin (1.2.3) + 1 plugin and 1 application plugin +""" + + assert tester.io.fetch_output() == expected + + +def test_show_displays_installed_plugins_with_multiple_plugins( + app, tester, installed, mocker +): + mocker.patch( + "entrypoints.get_group_all", + side_effect=[ + [ + EntryPoint( + "poetry-plugin", + "poetry_plugin.plugins:ApplicationPlugin", + "FirstApplicationPlugin", + ), + EntryPoint( + "poetry-plugin", + "poetry_plugin.plugins:ApplicationPlugin", + "SecondApplicationPlugin", + ), + ], + [ + EntryPoint( + "poetry-plugin", + "poetry_plugin.plugins:Plugin", + "FirstPlugin", + ), + EntryPoint( + "poetry-plugin", + "poetry_plugin.plugins:Plugin", + "SecondPlugin", + ), + ], + ], + ) + + installed.add_package(Package("poetry-plugin", "1.2.3")) + + tester.execute("") + + expected = """ + • poetry-plugin (1.2.3) + 2 plugins and 2 application plugins +""" + + assert tester.io.fetch_output() == expected + + +def test_show_displays_installed_plugins_with_dependencies( + app, tester, installed, mocker +): + mocker.patch( + "entrypoints.get_group_all", + side_effect=[ + [ + EntryPoint( + "poetry-plugin", + "poetry_plugin.plugins:ApplicationPlugin", + "FirstApplicationPlugin", + ) + ], + [ + EntryPoint( + "poetry-plugin", + "poetry_plugin.plugins:Plugin", + "FirstPlugin", + ) + ], + ], + ) + + plugin = Package("poetry-plugin", "1.2.3") + plugin.add_dependency(Factory.create_dependency("foo", ">=1.2.3")) + plugin.add_dependency(Factory.create_dependency("bar", "<4.5.6")) + installed.add_package(plugin) + + tester.execute("") + + expected = """ + • poetry-plugin (1.2.3) + 1 plugin and 1 application plugin + + Dependencies + - foo (>=1.2.3) + - bar (<4.5.6) +""" + + assert tester.io.fetch_output() == expected From 4fe39a33b0bc462b1ebfd2e060dc943a3afdadea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Tue, 9 Mar 2021 11:43:28 +0100 Subject: [PATCH 133/222] Add a `plugin remove` command --- poetry/console/application.py | 1 + poetry/console/commands/plugin/remove.py | 73 +++++++ poetry/console/commands/remove.py | 26 ++- tests/console/commands/plugin/test_remove.py | 189 +++++++++++++++++++ 4 files changed, 275 insertions(+), 14 deletions(-) create mode 100644 poetry/console/commands/plugin/remove.py diff --git a/poetry/console/application.py b/poetry/console/application.py index 3285e681c87..e2f9f3b6a3e 100644 --- a/poetry/console/application.py +++ b/poetry/console/application.py @@ -72,6 +72,7 @@ def _load() -> Type[Command]: "env use", # Plugin commands "plugin add", + "plugin remove", "plugin show", # Self commands "self update", diff --git a/poetry/console/commands/plugin/remove.py b/poetry/console/commands/plugin/remove.py new file mode 100644 index 00000000000..cb8143175f7 --- /dev/null +++ b/poetry/console/commands/plugin/remove.py @@ -0,0 +1,73 @@ +import os + +from typing import TYPE_CHECKING +from typing import cast + +from cleo.helpers import argument +from cleo.helpers import option + +from poetry.console.commands.command import Command + + +if TYPE_CHECKING: + from poetry.console.application import Application # noqa + from poetry.console.commands.remove import RemoveCommand + + +class PluginRemoveCommand(Command): + + name = "plugin remove" + + description = "Removes installed plugins" + + arguments = [ + argument("plugins", "The names of the plugins to install.", multiple=True), + ] + + options = [ + option( + "dry-run", + None, + "Output the operations but do not execute anything (implicitly enables --verbose).", + ) + ] + + def handle(self) -> int: + from pathlib import Path + + from cleo.io.inputs.string_input import StringInput + from cleo.io.io import IO + + from poetry.factory import Factory + from poetry.utils.env import EnvManager + + plugins = self.argument("plugins") + + system_env = EnvManager.get_system_env() + env_dir = Path( + os.getenv("POETRY_HOME") if os.getenv("POETRY_HOME") else system_env.path + ) + + # From this point forward, all the logic will be deferred to + # the remove command, by using the global `pyproject.toml` file. + application = cast("Application", self.application) + remove_command: "RemoveCommand" = cast( + "RemoveCommand", application.find("remove") + ) + # We won't go through the event dispatching done by the application + # so we need to configure the command manually + remove_command.set_poetry(Factory().create_poetry(env_dir)) + remove_command.set_env(system_env) + application._configure_installer(remove_command, self._io) + + argv = ["remove"] + plugins + if self.option("dry-run"): + argv.append("--dry-run") + + return remove_command.run( + IO( + StringInput(" ".join(argv)), + self._io.output, + self._io.error_output, + ) + ) diff --git a/poetry/console/commands/remove.py b/poetry/console/commands/remove.py index a7b2e00a26f..e2b65c46a72 100644 --- a/poetry/console/commands/remove.py +++ b/poetry/console/commands/remove.py @@ -1,6 +1,7 @@ from cleo.helpers import argument from cleo.helpers import option +from ...utils.helpers import canonicalize_name from .installer_command import InstallerCommand @@ -54,12 +55,17 @@ def handle(self) -> int: for key in requirements: del poetry_content[section][key] - # Write the new content back - self.poetry.file.write(content) + dependencies = ( + self.poetry.package.requires + if section == "dependencies" + else self.poetry.package.dev_requires + ) - # Update packages - self.reset_poetry() + for i, dependency in enumerate(reversed(dependencies)): + if dependency.name == canonicalize_name(key): + del dependencies[-i] + # Update packages self._installer.use_executor( self.poetry.config.get("experimental.new-installer", False) ) @@ -76,15 +82,7 @@ def handle(self) -> int: raise - if status != 0 or self.option("dry-run"): - # Revert changes - if not self.option("dry-run"): - self.line_error( - "\n" - "Removal failed, reverting pyproject.toml " - "to its original content." - ) - - self.poetry.file.write(original_content) + if not self.option("dry-run"): + self.poetry.file.write(content) return status diff --git a/tests/console/commands/plugin/test_remove.py b/tests/console/commands/plugin/test_remove.py index e69de29bb2d..0a66d8717cc 100644 --- a/tests/console/commands/plugin/test_remove.py +++ b/tests/console/commands/plugin/test_remove.py @@ -0,0 +1,189 @@ +import pytest +import tomlkit + +from poetry.__version__ import __version__ +from poetry.core.packages.package import Package +from poetry.factory import Factory +from poetry.layouts.layout import POETRY_DEFAULT +from poetry.repositories.installed_repository import InstalledRepository +from poetry.repositories.pool import Pool +from poetry.utils.env import EnvManager + + +@pytest.fixture() +def tester(command_tester_factory): + return command_tester_factory("plugin remove") + + +@pytest.fixture() +def installed(): + repository = InstalledRepository() + + repository.add_package(Package("poetry", __version__)) + + return repository + + +def configure_sources_factory(repo): + def _configure_sources(poetry, sources, config, io): + pool = Pool() + pool.add_repository(repo) + poetry.set_pool(pool) + + return _configure_sources + + +@pytest.fixture(autouse=True) +def setup_mocks(mocker, env, repo, installed): + mocker.patch.object(EnvManager, "get_system_env", return_value=env) + mocker.patch.object(InstalledRepository, "load", return_value=installed) + mocker.patch.object( + Factory, "configure_sources", side_effect=configure_sources_factory(repo) + ) + + +@pytest.fixture() +def pyproject(env): + pyproject = tomlkit.loads(POETRY_DEFAULT) + content = pyproject["tool"]["poetry"] + + content["name"] = "poetry" + content["version"] = __version__ + content["description"] = "" + content["authors"] = ["Sébastien Eustace "] + + dependency_section = content["dependencies"] + dependency_section["python"] = "^3.6" + + env.path.joinpath("pyproject.toml").write_text( + tomlkit.dumps(pyproject), encoding="utf-8" + ) + + +def test_remove_installed_package(app, repo, tester, env, installed, pyproject): + lock_content = { + "package": [ + { + "name": "poetry-plugin", + "version": "1.2.3", + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "^3.6", + "platform": "*", + "content-hash": "123456789", + "hashes": {"poetry-plugin": []}, + }, + } + + env.path.joinpath("poetry.lock").write_text( + tomlkit.dumps(lock_content), encoding="utf-8" + ) + + pyproject = tomlkit.loads( + env.path.joinpath("pyproject.toml").read_text(encoding="utf-8") + ) + content = pyproject["tool"]["poetry"] + + dependency_section = content["dependencies"] + dependency_section["poetry-plugin"] = "^1.2.3" + + env.path.joinpath("pyproject.toml").write_text( + tomlkit.dumps(pyproject), encoding="utf-8" + ) + + installed.add_package(Package("poetry-plugin", "1.2.3")) + + tester.execute("poetry-plugin") + + expected = """\ +Updating dependencies +Resolving dependencies... + +Writing lock file + +Package operations: 0 installs, 0 updates, 1 removal + + • Removing poetry-plugin (1.2.3) +""" + + assert tester.io.fetch_output() == expected + + remove_command = app.find("remove") + assert remove_command.poetry.file.parent == env.path + assert remove_command.poetry.locker.lock.parent == env.path + assert remove_command.poetry.locker.lock.exists() + assert not remove_command.installer.executor._dry_run + + content = remove_command.poetry.file.read()["tool"]["poetry"] + assert "poetry-plugin" not in content["dependencies"] + + +def test_remove_installed_package_dry_run(app, repo, tester, env, installed, pyproject): + lock_content = { + "package": [ + { + "name": "poetry-plugin", + "version": "1.2.3", + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "^3.6", + "platform": "*", + "content-hash": "123456789", + "hashes": {"poetry-plugin": []}, + }, + } + + env.path.joinpath("poetry.lock").write_text( + tomlkit.dumps(lock_content), encoding="utf-8" + ) + + pyproject = tomlkit.loads( + env.path.joinpath("pyproject.toml").read_text(encoding="utf-8") + ) + content = pyproject["tool"]["poetry"] + + dependency_section = content["dependencies"] + dependency_section["poetry-plugin"] = "^1.2.3" + + env.path.joinpath("pyproject.toml").write_text( + tomlkit.dumps(pyproject), encoding="utf-8" + ) + + installed.add_package(Package("poetry-plugin", "1.2.3")) + + tester.execute("poetry-plugin --dry-run") + + expected = """\ +Updating dependencies +Resolving dependencies... + +Writing lock file + +Package operations: 0 installs, 0 updates, 1 removal + + • Removing poetry-plugin (1.2.3) + • Removing poetry-plugin (1.2.3) +""" + + assert tester.io.fetch_output() == expected + + remove_command = app.find("remove") + assert remove_command.poetry.file.parent == env.path + assert remove_command.poetry.locker.lock.parent == env.path + assert remove_command.poetry.locker.lock.exists() + assert remove_command.installer.executor._dry_run + + content = remove_command.poetry.file.read()["tool"]["poetry"] + assert "poetry-plugin" in content["dependencies"] From 1977945745aeced3376901d4a86b63cbf73e5ed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 19 Mar 2021 17:16:51 +0100 Subject: [PATCH 134/222] Update coverage config --- .coveragerc | 0 pyproject.toml | 6 ++++++ 2 files changed, 6 insertions(+) delete mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/pyproject.toml b/pyproject.toml index bc9c02754ce..b68c65adea7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,3 +99,9 @@ exclude = ''' | tests/.*/setup.py )/ ''' + +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "if TYPE_CHECKING:" +] From 3738ae79b55bdb3856fd15c6a922a38e89c2b556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Thu, 25 Mar 2021 16:44:59 +0100 Subject: [PATCH 135/222] Remove plugin deactivation for the new command --- poetry/console/application.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/poetry/console/application.py b/poetry/console/application.py index e2f9f3b6a3e..d9d9277d274 100644 --- a/poetry/console/application.py +++ b/poetry/console/application.py @@ -304,19 +304,7 @@ def _load_plugins(self, io: IO) -> None: if self._plugins_loaded: return - from cleo.exceptions import CommandNotFoundException - - name = self._get_command_name(io) - command_name = "" - if name: - try: - command_name = self.find(name).name - except CommandNotFoundException: - pass - - self._disable_plugins = ( - io.input.has_parameter_option("--no-plugins") or command_name == "new" - ) + self._disable_plugins = io.input.has_parameter_option("--no-plugins") if not self._disable_plugins: from poetry.plugins.plugin_manager import PluginManager From 9175a538068417fe9c4691c02217bda6049169f3 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Thu, 25 Mar 2021 02:15:13 +0100 Subject: [PATCH 136/222] core: use new version class implementation --- poetry.lock | 19 ++++++++++--- poetry/console/commands/version.py | 27 +++++++------------ poetry/installation/executor.py | 5 ++-- poetry/masonry/builders/editable.py | 2 +- poetry/mixology/version_solver.py | 2 +- poetry/packages/locker.py | 2 +- poetry/publishing/uploader.py | 2 +- poetry/puzzle/solver.py | 2 +- poetry/repositories/legacy_repository.py | 6 ++--- poetry/repositories/pypi_repository.py | 10 +++---- poetry/repositories/repository.py | 6 ++--- poetry/utils/helpers.py | 5 ---- poetry/version/version_selector.py | 6 ++--- pyproject.toml | 2 +- tests/console/commands/env/test_use.py | 2 +- tests/console/commands/self/test_update.py | 2 +- .../version_solver/test_backtracking.py | 2 +- tests/packages/test_locker.py | 4 ++- tests/utils/test_env.py | 2 +- tests/utils/test_setup_reader.py | 4 +-- 20 files changed, 57 insertions(+), 55 deletions(-) diff --git a/poetry.lock b/poetry.lock index 9b75e396995..709946e9f5c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -151,6 +151,14 @@ sdist = ["setuptools-rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +[[package]] +name = "dataclasses" +version = "0.8" +description = "A backport of the dataclasses module for Python 3.6" +category = "main" +optional = false +python-versions = ">=3.6, <3.7" + [[package]] name = "deepdiff" version = "5.2.3" @@ -390,13 +398,14 @@ python-versions = "^3.6" develop = false [package.dependencies] +dataclasses = {version = "^0.8", markers = "python_version >= \"3.6\" and python_version < \"3.7\""} importlib-metadata = {version = "^1.7.0", markers = "python_version >= \"3.5\" and python_version < \"3.8\""} [package.source] type = "git" -url = "https://github.com/python-poetry/poetry-core" +url = "https://github.com/python-poetry/poetry-core.git" reference = "master" -resolved_reference = "5d5251c427aacedcf54f9743635a8124e5a26151" +resolved_reference = "c11cb9a6ebdda53d45dae78b45f6f73f5368e793" [[package]] name = "pre-commit" @@ -705,7 +714,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "c72b0807603d4902cff83901d0e65165e243937b5be90b05c17d3c92a06b4fc8" +content-hash = "8442060c68d80744b05aac3a07818a1e04e457c05b0e481d717cb44721009566" [metadata.files] appdirs = [ @@ -859,6 +868,10 @@ cryptography = [ {file = "cryptography-3.4.6-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724"}, {file = "cryptography-3.4.6.tar.gz", hash = "sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87"}, ] +dataclasses = [ + {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, + {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"}, +] deepdiff = [ {file = "deepdiff-5.2.3-py3-none-any.whl", hash = "sha256:3d3da4bd7e01fb5202088658ed26427104c748dda56a0ecfac9ce9a1d2d00844"}, {file = "deepdiff-5.2.3.tar.gz", hash = "sha256:ae2cb98353309f93fbfdda4d77adb08fb303314d836bb6eac3d02ed71a10b40e"}, diff --git a/poetry/console/commands/version.py b/poetry/console/commands/version.py index 4a6d5870924..71c18a0260c 100644 --- a/poetry/console/commands/version.py +++ b/poetry/console/commands/version.py @@ -87,31 +87,22 @@ def increment_version(self, version: str, rule: str) -> "Version": raise ValueError("The project's version doesn't seem to follow semver") if rule in {"major", "premajor"}: - new = version.next_major + new = version.next_major() if rule == "premajor": - new = new.first_prerelease + new = new.first_prerelease() elif rule in {"minor", "preminor"}: - new = version.next_minor + new = version.next_minor() if rule == "preminor": - new = new.first_prerelease + new = new.first_prerelease() elif rule in {"patch", "prepatch"}: - new = version.next_patch + new = version.next_patch() if rule == "prepatch": - new = new.first_prerelease + new = new.first_prerelease() elif rule == "prerelease": - if version.is_prerelease(): - pre = version.prerelease - new_prerelease = int(pre[1]) + 1 - new = Version.parse( - "{}.{}.{}-{}".format( - version.major, - version.minor, - version.patch, - ".".join([pre[0], str(new_prerelease)]), - ) - ) + if version.is_unstable(): + new = Version(version.epoch, version.release, version.pre.next()) else: - new = version.next_patch.first_prerelease + new = version.next_patch().first_prerelease() else: new = Version.parse(rule) diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index b2d85e566a6..89ab57a7cf5 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -543,8 +543,9 @@ def _install_directory(self, operation: Union[Install, Update]) -> int: # some versions of pip (< 19.0.0) don't understand it # so we need to check the version of pip to know # if we can rely on the build system - legacy_pip = self._env.pip_version < self._env.pip_version.__class__( - 19, 0, 0 + legacy_pip = ( + self._env.pip_version + < self._env.pip_version.__class__.from_parts(19, 0, 0) ) package_poetry = Factory().create_poetry(pyproject.file.path.parent) diff --git a/poetry/masonry/builders/editable.py b/poetry/masonry/builders/editable.py index c8b83959a43..c78af6c65a6 100644 --- a/poetry/masonry/builders/editable.py +++ b/poetry/masonry/builders/editable.py @@ -84,7 +84,7 @@ def _setup_build(self) -> None: f.write(decode(builder.build_setup())) try: - if self._env.pip_version < Version(19, 0): + if self._env.pip_version < Version.from_parts(19, 0): pip_editable_install(self._path, self._env) else: # Temporarily rename pyproject.toml diff --git a/poetry/mixology/version_solver.py b/poetry/mixology/version_solver.py index f55231660f6..beb60c59dd7 100644 --- a/poetry/mixology/version_solver.py +++ b/poetry/mixology/version_solver.py @@ -339,7 +339,7 @@ def _get_min(dependency: Dependency) -> int: if locked and ( dependency.constraint.allows(locked.version) or locked.is_prerelease() - and dependency.constraint.allows(locked.version.next_patch) + and dependency.constraint.allows(locked.version.next_patch()) ): return 1 diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index 4f3ba725751..3b1436bcf52 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -478,7 +478,7 @@ def _get_lock_data(self) -> "TOMLDocument": # We expect the locker to be able to read lock files # from the same semantic versioning range accepted_versions = parse_constraint( - "^{}".format(Version(current_version.major, 0)) + "^{}".format(Version.from_parts(current_version.major, 0)) ) lock_version_allowed = accepted_versions.allows(lock_version) if lock_version_allowed and current_version < lock_version: diff --git a/poetry/publishing/uploader.py b/poetry/publishing/uploader.py index 72889c023e2..787f112f49c 100644 --- a/poetry/publishing/uploader.py +++ b/poetry/publishing/uploader.py @@ -24,7 +24,7 @@ from poetry.core.masonry.metadata import Metadata from poetry.core.masonry.utils.helpers import escape_name from poetry.core.masonry.utils.helpers import escape_version -from poetry.utils.helpers import normalize_version +from poetry.core.utils.helpers import normalize_version from poetry.utils.patterns import wheel_file_re diff --git a/poetry/puzzle/solver.py b/poetry/puzzle/solver.py index bc787508636..605eea303b8 100644 --- a/poetry/puzzle/solver.py +++ b/poetry/puzzle/solver.py @@ -437,7 +437,7 @@ def reachable(self) -> List["PackageNode"]: if pkg.complete_name == dependency.complete_name and ( dependency.constraint.allows(pkg.version) or dependency.allows_prereleases() - and pkg.version.is_prerelease() + and pkg.version.is_unstable() and dependency.constraint.allows(pkg.version.stable) ): # If there is already a child with this name diff --git a/poetry/repositories/legacy_repository.py b/poetry/repositories/legacy_repository.py index f0551e5adf8..8446b31ee99 100644 --- a/poetry/repositories/legacy_repository.py +++ b/poetry/repositories/legacy_repository.py @@ -254,9 +254,9 @@ def find_packages(self, dependency: "Dependency") -> List[Package]: if isinstance(constraint, VersionRange): if ( constraint.max is not None - and constraint.max.is_prerelease() + and constraint.max.is_unstable() or constraint.min is not None - and constraint.min.is_prerelease() + and constraint.min.is_unstable() ): allow_prereleases = True @@ -275,7 +275,7 @@ def find_packages(self, dependency: "Dependency") -> List[Package]: versions = [] for version in page.versions: - if version.is_prerelease() and not allow_prereleases: + if version.is_unstable() and not allow_prereleases: if constraint.is_any(): # we need this when all versions of the package are pre-releases ignored_pre_release_versions.append(version) diff --git a/poetry/repositories/pypi_repository.py b/poetry/repositories/pypi_repository.py index f6f9400cfc5..0a5d02e1f10 100644 --- a/poetry/repositories/pypi_repository.py +++ b/poetry/repositories/pypi_repository.py @@ -20,10 +20,10 @@ from poetry.core.packages.dependency import Dependency from poetry.core.packages.package import Package from poetry.core.packages.utils.link import Link -from poetry.core.semver.exceptions import ParseVersionError from poetry.core.semver.helpers import parse_constraint from poetry.core.semver.version_constraint import VersionConstraint from poetry.core.semver.version_range import VersionRange +from poetry.core.version.exceptions import InvalidVersion from poetry.core.version.markers import parse_marker from poetry.locations import REPOSITORY_CACHE_DIR from poetry.utils._compat import to_str @@ -98,9 +98,9 @@ def find_packages(self, dependency: Dependency) -> List[Package]: if isinstance(constraint, VersionRange): if ( constraint.max is not None - and constraint.max.is_prerelease() + and constraint.max.is_unstable() or constraint.min is not None - and constraint.min.is_prerelease() + and constraint.min.is_unstable() ): allow_prereleases = True @@ -129,7 +129,7 @@ def find_packages(self, dependency: Dependency) -> List[Package]: try: package = Package(info["info"]["name"], version) - except ParseVersionError: + except InvalidVersion: self._log( 'Unable to parse version "{}" for the {} package, skipping'.format( version, dependency.name @@ -186,7 +186,7 @@ def search(self, query: str) -> List[Package]: result = Package(name, version, description) result.description = to_str(description.strip()) results.append(result) - except ParseVersionError: + except InvalidVersion: self._log( 'Unable to parse version "{}" for the {} package, skipping'.format( version, name diff --git a/poetry/repositories/repository.py b/poetry/repositories/repository.py index 8e25330206f..43894e4e4d4 100644 --- a/poetry/repositories/repository.py +++ b/poetry/repositories/repository.py @@ -55,9 +55,9 @@ def find_packages(self, dependency: "Dependency") -> List["Package"]: if isinstance(constraint, VersionRange): if ( constraint.max is not None - and constraint.max.is_prerelease() + and constraint.max.is_unstable() or constraint.min is not None - and constraint.min.is_prerelease() + and constraint.min.is_unstable() ): allow_prereleases = True @@ -77,7 +77,7 @@ def find_packages(self, dependency: "Dependency") -> List["Package"]: if constraint.allows(package.version) or ( package.is_prerelease() - and constraint.allows(package.version.next_patch) + and constraint.allows(package.version.next_patch()) ): packages.append(package) diff --git a/poetry/utils/helpers.py b/poetry/utils/helpers.py index 925ff2d8543..1156b6415db 100644 --- a/poetry/utils/helpers.py +++ b/poetry/utils/helpers.py @@ -17,7 +17,6 @@ from poetry.config.config import Config from poetry.core.packages.package import Package -from poetry.core.version import Version try: @@ -37,10 +36,6 @@ def module_name(name: str) -> str: return canonicalize_name(name).replace(".", "_").replace("-", "_") -def normalize_version(version: str) -> str: - return str(Version(version)) - - def _del_ro(action: Callable, name: str, exc: Exception) -> None: os.chmod(name, stat.S_IWRITE) os.remove(name) diff --git a/poetry/version/version_selector.py b/poetry/version/version_selector.py index fe84f50764a..27dd582aaff 100644 --- a/poetry/version/version_selector.py +++ b/poetry/version/version_selector.py @@ -36,7 +36,7 @@ def find_best_candidate( }, ) candidates = self._pool.find_packages(dependency) - only_prereleases = all([c.version.is_prerelease() for c in candidates]) + only_prereleases = all([c.version.is_unstable() for c in candidates]) if not candidates: return False @@ -77,7 +77,7 @@ def _transform_version(self, version: str, pretty_version: str) -> str: version = pretty_version else: version = ".".join(str(p) for p in parts) - if parsed.is_prerelease(): - version += "-{}".format(".".join(str(p) for p in parsed.prerelease)) + if parsed.is_unstable(): + version += "-{}".format(parsed.pre.to_string()) return "^{}".format(version) diff --git a/pyproject.toml b/pyproject.toml index b68c65adea7..10d03a07d72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ generate-setup-file = false [tool.poetry.dependencies] python = "^3.6" -poetry-core = { git = "https://github.com/python-poetry/poetry-core", branch = "master"} +poetry-core = { git = "https://github.com/python-poetry/poetry-core.git", branch = "master"} cleo = "^1.0.0a1" crashtest = "^0.3.0" requests = "^2.18" diff --git a/tests/console/commands/env/test_use.py b/tests/console/commands/env/test_use.py index f80ff8ee20b..93e96c02523 100644 --- a/tests/console/commands/env/test_use.py +++ b/tests/console/commands/env/test_use.py @@ -23,7 +23,7 @@ def setup(mocker): def mock_subprocess_calls(setup, current_python, mocker): mocker.patch( "subprocess.check_output", - side_effect=check_output_wrapper(Version(*current_python)), + side_effect=check_output_wrapper(Version.from_parts(*current_python)), ) mocker.patch( "subprocess.Popen.communicate", diff --git a/tests/console/commands/self/test_update.py b/tests/console/commands/self/test_update.py index 2512654dea9..5b70b4daefe 100644 --- a/tests/console/commands/self/test_update.py +++ b/tests/console/commands/self/test_update.py @@ -25,7 +25,7 @@ def test_self_update_should_install_all_necessary_elements( command = tester.command - version = Version.parse(__version__).next_minor.text + version = Version.parse(__version__).next_minor().text mocker.patch( "poetry.repositories.pypi_repository.PyPiRepository.find_packages", return_value=[Package("poetry", version)], diff --git a/tests/mixology/version_solver/test_backtracking.py b/tests/mixology/version_solver/test_backtracking.py index 1716ca35f70..8fbd8874c7d 100644 --- a/tests/mixology/version_solver/test_backtracking.py +++ b/tests/mixology/version_solver/test_backtracking.py @@ -99,7 +99,7 @@ def test_backjump_to_nearer_unsatisfied_package(root, provider, repo): root.add_dependency(Factory.create_dependency("b", "*")) add_to_repo(repo, "a", "1.0.0", deps={"c": "1.0.0"}) - add_to_repo(repo, "a", "2.0.0", deps={"c": "2.0.0-nonexistent"}) + add_to_repo(repo, "a", "2.0.0", deps={"c": "2.0.0-1"}) add_to_repo(repo, "b", "1.0.0") add_to_repo(repo, "b", "2.0.0") add_to_repo(repo, "b", "3.0.0") diff --git a/tests/packages/test_locker.py b/tests/packages/test_locker.py index 7b0d81bda9e..285b91a8788 100644 --- a/tests/packages/test_locker.py +++ b/tests/packages/test_locker.py @@ -428,7 +428,9 @@ def test_locker_should_emit_warnings_if_lock_version_is_newer_but_allowed( [metadata.files] """.format( - version=".".join(Version.parse(Locker._VERSION).next_minor.text.split(".")[:2]) + version=".".join( + Version.parse(Locker._VERSION).next_minor().text.split(".")[:2] + ) ) caplog.set_level(logging.WARNING, logger="poetry.packages.locker") diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 945a4d47844..0cc9c6c50bd 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -429,7 +429,7 @@ def test_deactivate_activated(tmp_dir, manager, poetry, config, mocker): venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) version = Version.parse(".".join(str(c) for c in sys.version_info[:3])) - other_version = Version.parse("3.4") if version.major == 2 else version.next_minor + other_version = Version.parse("3.4") if version.major == 2 else version.next_minor() ( Path(tmp_dir) / "{}-py{}.{}".format(venv_name, version.major, version.minor) ).mkdir() diff --git a/tests/utils/test_setup_reader.py b/tests/utils/test_setup_reader.py index ccde90ac253..0112e865793 100644 --- a/tests/utils/test_setup_reader.py +++ b/tests/utils/test_setup_reader.py @@ -2,7 +2,7 @@ import pytest -from poetry.core.semver.exceptions import ParseVersionError +from poetry.core.version.exceptions import InvalidVersion from poetry.utils.setup_reader import SetupReader @@ -115,7 +115,7 @@ def test_setup_reader_read_setup_cfg(setup): def test_setup_reader_read_setup_cfg_with_attr(setup): - with pytest.raises(ParseVersionError): + with pytest.raises(InvalidVersion): SetupReader.read_from_directory(setup("with-setup-cfg-attr")) From 8c5611c633cfd5a007c79fec1c1c87632240fec4 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Sat, 27 Mar 2021 20:32:51 +0100 Subject: [PATCH 137/222] deps: update cryptography and identity --- poetry.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/poetry.lock b/poetry.lock index 709946e9f5c..601f22d4211 100644 --- a/poetry.lock +++ b/poetry.lock @@ -134,7 +134,7 @@ python-versions = ">=3.6,<4.0" [[package]] name = "cryptography" -version = "3.4.6" +version = "3.4.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false @@ -225,7 +225,7 @@ python-versions = ">=3" [[package]] name = "identify" -version = "2.2.0" +version = "2.2.1" description = "File identification library for Python" category = "dev" optional = false @@ -855,18 +855,18 @@ crashtest = [ {file = "crashtest-0.3.1.tar.gz", hash = "sha256:42ca7b6ce88b6c7433e2ce47ea884e91ec93104a4b754998be498a8e6c3d37dd"}, ] cryptography = [ - {file = "cryptography-3.4.6-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799"}, - {file = "cryptography-3.4.6-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:4169a27b818de4a1860720108b55a2801f32b6ae79e7f99c00d79f2a2822eeb7"}, - {file = "cryptography-3.4.6-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:93cfe5b7ff006de13e1e89830810ecbd014791b042cbe5eec253be11ac2b28f3"}, - {file = "cryptography-3.4.6-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:5ecf2bcb34d17415e89b546dbb44e73080f747e504273e4d4987630493cded1b"}, - {file = "cryptography-3.4.6-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:fec7fb46b10da10d9e1d078d1ff8ed9e05ae14f431fdbd11145edd0550b9a964"}, - {file = "cryptography-3.4.6-cp36-abi3-win32.whl", hash = "sha256:df186fcbf86dc1ce56305becb8434e4b6b7504bc724b71ad7a3239e0c9d14ef2"}, - {file = "cryptography-3.4.6-cp36-abi3-win_amd64.whl", hash = "sha256:66b57a9ca4b3221d51b237094b0303843b914b7d5afd4349970bb26518e350b0"}, - {file = "cryptography-3.4.6-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:066bc53f052dfeda2f2d7c195cf16fb3e5ff13e1b6b7415b468514b40b381a5b"}, - {file = "cryptography-3.4.6-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:600cf9bfe75e96d965509a4c0b2b183f74a4fa6f5331dcb40fb7b77b7c2484df"}, - {file = "cryptography-3.4.6-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:0923ba600d00718d63a3976f23cab19aef10c1765038945628cd9be047ad0336"}, - {file = "cryptography-3.4.6-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724"}, - {file = "cryptography-3.4.6.tar.gz", hash = "sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87"}, + {file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"}, + {file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"}, + {file = "cryptography-3.4.7-cp36-abi3-win32.whl", hash = "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d"}, + {file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"}, + {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"}, ] dataclasses = [ {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, @@ -896,8 +896,8 @@ httpretty = [ {file = "httpretty-1.0.5.tar.gz", hash = "sha256:e53c927c4d3d781a0761727f1edfad64abef94e828718e12b672a678a8b3e0b5"}, ] identify = [ - {file = "identify-2.2.0-py2.py3-none-any.whl", hash = "sha256:39c0b110c9d0cd2391b6c38cd0ff679ee4b4e98f8db8b06c5d9d9e502711a1e1"}, - {file = "identify-2.2.0.tar.gz", hash = "sha256:efbf090a619255bc31c4fbba709e2805f7d30913fd4854ad84ace52bd276e2f6"}, + {file = "identify-2.2.1-py2.py3-none-any.whl", hash = "sha256:9cc5f58996cd359b7b72f0a5917d8639de5323917e6952a3bfbf36301b576f40"}, + {file = "identify-2.2.1.tar.gz", hash = "sha256:1cfb05b578de996677836d5a2dde14b3dffde313cf7d2b3e793a0787a36e26dd"}, ] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, From 15d7a263b2cde791823db168cb17d700b490f534 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 20 Oct 2020 13:07:57 +0200 Subject: [PATCH 138/222] env: do not modify os.environ Replace updates of os.environ with explcit passing of `env` to subprocess calls. Relates-to: #3199 --- poetry/utils/env.py | 50 +++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/poetry/utils/env.py b/poetry/utils/env.py index 4678cfb5558..a3a3514bd24 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -11,6 +11,7 @@ import textwrap from contextlib import contextmanager +from copy import deepcopy from pathlib import Path from subprocess import CalledProcessError from typing import Any @@ -1143,15 +1144,13 @@ def execute(self, bin: str, *args: str, **kwargs: Any) -> Optional[int]: return self.run_pip(*args, **kwargs) bin = self._bin(bin) + env = kwargs.pop("env", {k: v for k, v in os.environ.items()}) if not self._is_windows: args = [bin] + list(args) - if "env" in kwargs: - return os.execvpe(bin, args, kwargs["env"]) - else: - return os.execvp(bin, args) + return os.execvpe(bin, args, env=env) else: - exe = subprocess.Popen([bin] + list(args), **kwargs) + exe = subprocess.Popen([bin] + list(args), env=env, **kwargs) exe.communicate() return exe.returncode @@ -1388,24 +1387,35 @@ def is_sane(self) -> bool: return os.path.exists(self.python) def _run(self, cmd: List[str], **kwargs: Any) -> Optional[int]: - with self.temp_environ(): - os.environ["PATH"] = self._updated_path() - os.environ["VIRTUAL_ENV"] = str(self._path) + kwargs["env"] = self.get_temp_environ(environ=kwargs.get("env")) + return super(VirtualEnv, self)._run(cmd, **kwargs) - self.unset_env("PYTHONHOME") - self.unset_env("__PYVENV_LAUNCHER__") + def get_temp_environ( + self, + environ: Optional[Dict[str, str]] = None, + exclude: Optional[List[str]] = None, + **kwargs: str, + ) -> Dict[str, str]: + exclude = exclude or [] + exclude.extend(["PYTHONHOME", "__PYVENV_LAUNCHER__"]) + + if environ: + environ = deepcopy(environ) + for key in exclude: + environ.pop(key, None) + else: + environ = {k: v for k, v in os.environ.items() if k not in exclude} - return super(VirtualEnv, self)._run(cmd, **kwargs) + environ.update(kwargs) - def execute(self, bin: str, *args: str, **kwargs: Any) -> Optional[int]: - with self.temp_environ(): - os.environ["PATH"] = self._updated_path() - os.environ["VIRTUAL_ENV"] = str(self._path) + environ["PATH"] = self._updated_path() + environ["VIRTUAL_ENV"] = str(self._path) - self.unset_env("PYTHONHOME") - self.unset_env("__PYVENV_LAUNCHER__") + return environ - return super(VirtualEnv, self).execute(bin, *args, **kwargs) + def execute(self, bin: str, *args: str, **kwargs: Any) -> Optional[int]: + kwargs["env"] = self.get_temp_environ(environ=kwargs.get("env")) + return super(VirtualEnv, self).execute(bin, *args, **kwargs) @contextmanager def temp_environ(self) -> Iterator[None]: @@ -1416,10 +1426,6 @@ def temp_environ(self) -> Iterator[None]: os.environ.clear() os.environ.update(environ) - def unset_env(self, key: str) -> None: - if key in os.environ: - del os.environ[key] - def _updated_path(self) -> str: return os.pathsep.join([str(self._bin_dir), os.environ.get("PATH", "")]) From b21dd144e7a4c6dbe1fb613a7d70e43737ac4a84 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Sat, 27 Mar 2021 21:36:36 +0100 Subject: [PATCH 139/222] tests: fix classifiers to include python 3.10 --- tests/masonry/builders/test_editable_builder.py | 1 + tests/test_factory.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/masonry/builders/test_editable_builder.py b/tests/masonry/builders/test_editable_builder.py index efe79bed772..05e798012d5 100644 --- a/tests/masonry/builders/test_editable_builder.py +++ b/tests/masonry/builders/test_editable_builder.py @@ -120,6 +120,7 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 Classifier: Topic :: Software Development :: Build Tools Classifier: Topic :: Software Development :: Libraries :: Python Modules Project-URL: Documentation, https://python-poetry.org/docs diff --git a/tests/test_factory.py b/tests/test_factory.py index f966a8407fb..13a0437174c 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -119,6 +119,7 @@ def test_create_poetry(): "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Topic :: Software Development :: Build Tools", "Topic :: Software Development :: Libraries :: Python Modules", ] From 5073c205e3a5c189a8bf0eae52a4c48ee1b88eb1 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Sat, 27 Mar 2021 00:37:40 +0100 Subject: [PATCH 140/222] env: align ephemeral environment interface to build --- poetry/inspection/info.py | 4 +++- poetry/utils/env.py | 14 ++++++++------ poetry/utils/pip.py | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/poetry/inspection/info.py b/poetry/inspection/info.py index 12e143c6370..fbc96b40015 100644 --- a/poetry/inspection/info.py +++ b/poetry/inspection/info.py @@ -450,7 +450,9 @@ def _pep517_metadata(cls, path: Path) -> "PackageInfo": except PackageInfoError: pass - with ephemeral_environment(pip=True, wheel=True, setuptools=True) as venv: + with ephemeral_environment( + with_pip=True, with_wheel=True, with_setuptools=True + ) as venv: # TODO: cache PEP 517 build environment corresponding to each project venv dest_dir = venv.path.parent / "dist" dest_dir.mkdir() diff --git a/poetry/utils/env.py b/poetry/utils/env.py index a3a3514bd24..933f9165210 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -1464,9 +1464,10 @@ def _bin(self, bin: str) -> str: @contextmanager def ephemeral_environment( executable=None, - pip: bool = False, - wheel: Optional[bool] = None, - setuptools: Optional[bool] = None, + flags: Dict[str, bool] = None, + with_pip: bool = False, + with_wheel: Optional[bool] = None, + with_setuptools: Optional[bool] = None, ) -> ContextManager[VirtualEnv]: with temporary_directory() as tmp_dir: # TODO: cache PEP 517 build environment corresponding to each project venv @@ -1474,9 +1475,10 @@ def ephemeral_environment( EnvManager.build_venv( path=venv_dir.as_posix(), executable=executable, - with_pip=pip, - with_wheel=wheel, - with_setuptools=setuptools, + flags=flags, + with_pip=with_pip, + with_wheel=with_wheel, + with_setuptools=with_setuptools, ) yield VirtualEnv(venv_dir, venv_dir) diff --git a/poetry/utils/pip.py b/poetry/utils/pip.py index a4929709105..7055e3db21e 100644 --- a/poetry/utils/pip.py +++ b/poetry/utils/pip.py @@ -47,7 +47,7 @@ def pip_install( # Under certain Python3.6 installs vendored pip wheel does not contain zip-safe # pep517 lib. In this cases we create an isolated ephemeral virtual environment. with ephemeral_environment( - executable=environment.python, pip=True, setuptools=True + executable=environment.python, with_pip=True, with_setuptools=True ) as env: return environment.run( env._bin("pip"), From 30190c74a2a15aa5bf7ce50b5e36d0aee29e840f Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Sat, 27 Mar 2021 00:38:37 +0100 Subject: [PATCH 141/222] pip installer: disable version check explicitly --- poetry/utils/pip.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/poetry/utils/pip.py b/poetry/utils/pip.py index 7055e3db21e..5267cf435af 100644 --- a/poetry/utils/pip.py +++ b/poetry/utils/pip.py @@ -20,7 +20,10 @@ def pip_install( path = Path(path) if isinstance(path, str) else path is_wheel = path.suffix == ".whl" - args = ["install", "--prefix", str(environment.path)] + # We disable version check here as we are already pinning to version available in either the + # virtual environment or the virtualenv package embedded wheel. Version checks are a wasteful + # network call that adds a lot of wait time when installing a lot of packages. + args = ["install", "--disable-pip-version-check", "--prefix", str(environment.path)] if not is_wheel: args.insert(1, "--use-pep517") From 34d66baa24875bbf799276a8509743cf595ab662 Mon Sep 17 00:00:00 2001 From: Tom Forbes Date: Thu, 18 Feb 2021 19:45:38 +0000 Subject: [PATCH 142/222] Prefer Python 3 executables over python 2 during install --- get-poetry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/get-poetry.py b/get-poetry.py index 73b818c7a5c..16e4bdcddfd 100644 --- a/get-poetry.py +++ b/get-poetry.py @@ -638,7 +638,7 @@ def extract_lib(self, filename): def _which_python(self): """Decides which python executable we'll embed in the launcher script.""" - allowed_executables = ["python", "python3"] + allowed_executables = ["python3", "python"] if WINDOWS: allowed_executables += ["py.exe -3", "py.exe -2"] From 3a7dad95786655c8e4f96fb870849f9ba2ef595e Mon Sep 17 00:00:00 2001 From: Erik van der Goetz Date: Sun, 28 Mar 2021 17:25:39 -0400 Subject: [PATCH 143/222] docs: Minor fix to plugin documentation --- docs/docs/plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/plugins.md b/docs/docs/plugins.md index 637306323a9..f31bc85b3de 100644 --- a/docs/docs/plugins.md +++ b/docs/docs/plugins.md @@ -26,7 +26,7 @@ name = "my-poetry-plugin" version = "1.0.0" # ... -[tool.poetry.dependency] +[tool.poetry.dependencies] python = "~2.7 || ^3.7" poetry = "^1.0" From 71708c6e07398cacfc0eb394229b0e890c056a16 Mon Sep 17 00:00:00 2001 From: Aadi Bajpai Date: Thu, 19 Nov 2020 21:53:27 +0530 Subject: [PATCH 144/222] update discord invite discordapp.com domain will go defunct very soon, everything has transitioned to discord.com now https://support.discord.com/hc/en-us/articles/360042987951-Discordapp-com-is-now-Discord-com --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bc392a15eaa..7759fca1232 100644 --- a/README.md +++ b/README.md @@ -268,4 +268,4 @@ At this point the rest of the resolution is straightforward since there is no mo * [Official Website](https://python-poetry.org) * [Issue Tracker](https://github.com/python-poetry/poetry/issues) -* [Discord](https://discordapp.com/invite/awxPgve) +* [Discord](https://discord.com/invite/awxPgve) From 737947925fb6cb1893d7d4f0d39b7df3a8b50aa9 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 30 Mar 2021 00:42:14 +0200 Subject: [PATCH 145/222] tests: remove duplicated code for plugin commands --- tests/console/commands/plugin/conftest.py | 35 ++++++ tests/console/commands/plugin/test_add.py | 116 +++++-------------- tests/console/commands/plugin/test_remove.py | 76 +----------- tests/console/commands/plugin/test_show.py | 31 ----- 4 files changed, 66 insertions(+), 192 deletions(-) create mode 100644 tests/console/commands/plugin/conftest.py diff --git a/tests/console/commands/plugin/conftest.py b/tests/console/commands/plugin/conftest.py new file mode 100644 index 00000000000..84509c81172 --- /dev/null +++ b/tests/console/commands/plugin/conftest.py @@ -0,0 +1,35 @@ +import pytest + +from poetry.__version__ import __version__ +from poetry.core.packages.package import Package +from poetry.factory import Factory +from poetry.repositories.installed_repository import InstalledRepository +from poetry.repositories.pool import Pool +from poetry.utils.env import EnvManager + + +@pytest.fixture() +def installed(): + repository = InstalledRepository() + + repository.add_package(Package("poetry", __version__)) + + return repository + + +def configure_sources_factory(repo): + def _configure_sources(poetry, sources, config, io): # noqa + pool = Pool() + pool.add_repository(repo) + poetry.set_pool(pool) + + return _configure_sources + + +@pytest.fixture(autouse=True) +def setup_mocks(mocker, env, repo, installed): + mocker.patch.object(EnvManager, "get_system_env", return_value=env) + mocker.patch.object(InstalledRepository, "load", return_value=installed) + mocker.patch.object( + Factory, "configure_sources", side_effect=configure_sources_factory(repo) + ) diff --git a/tests/console/commands/plugin/test_add.py b/tests/console/commands/plugin/test_add.py index fdcc46ed423..88c02812afd 100644 --- a/tests/console/commands/plugin/test_add.py +++ b/tests/console/commands/plugin/test_add.py @@ -1,11 +1,7 @@ import pytest -from poetry.__version__ import __version__ from poetry.core.packages.package import Package from poetry.factory import Factory -from poetry.repositories.installed_repository import InstalledRepository -from poetry.repositories.pool import Pool -from poetry.utils.env import EnvManager @pytest.fixture() @@ -13,31 +9,17 @@ def tester(command_tester_factory): return command_tester_factory("plugin add") -@pytest.fixture() -def installed(): - repository = InstalledRepository() - - repository.add_package(Package("poetry", __version__)) - - return repository - - -def configure_sources_factory(repo): - def _configure_sources(poetry, sources, config, io): - pool = Pool() - pool.add_repository(repo) - poetry.set_pool(pool) - - return _configure_sources +def assert_plugin_add_result(tester, app, env, expected, constraint): + assert tester.io.fetch_output() == expected + update_command = app.find("update") + assert update_command.poetry.file.parent == env.path + assert update_command.poetry.locker.lock.parent == env.path + assert update_command.poetry.locker.lock.exists() -@pytest.fixture(autouse=True) -def setup_mocks(mocker, env, repo, installed): - mocker.patch.object(EnvManager, "get_system_env", return_value=env) - mocker.patch.object(InstalledRepository, "load", return_value=installed) - mocker.patch.object( - Factory, "configure_sources", side_effect=configure_sources_factory(repo) - ) + content = update_command.poetry.file.read()["tool"]["poetry"] + assert "poetry-plugin" in content["dependencies"] + assert content["dependencies"]["poetry-plugin"] == constraint def test_add_no_constraint(app, repo, tester, env, installed): @@ -56,17 +38,7 @@ def test_add_no_constraint(app, repo, tester, env, installed): • Installing poetry-plugin (0.1.0) """ - - assert tester.io.fetch_output() == expected - - update_command = app.find("update") - assert update_command.poetry.file.parent == env.path - assert update_command.poetry.locker.lock.parent == env.path - assert update_command.poetry.locker.lock.exists() - - content = update_command.poetry.file.read()["tool"]["poetry"] - assert "poetry-plugin" in content["dependencies"] - assert content["dependencies"]["poetry-plugin"] == "^0.1.0" + assert_plugin_add_result(tester, app, env, expected, "^0.1.0") def test_add_with_constraint(app, repo, tester, env, installed): @@ -86,15 +58,7 @@ def test_add_with_constraint(app, repo, tester, env, installed): • Installing poetry-plugin (0.2.0) """ - assert tester.io.fetch_output() == expected - - update_command = app.find("update") - assert update_command.poetry.file.parent == env.path - assert update_command.poetry.locker.lock.parent == env.path - - content = update_command.poetry.file.read()["tool"]["poetry"] - assert "poetry-plugin" in content["dependencies"] - assert content["dependencies"]["poetry-plugin"] == "^0.2.0" + assert_plugin_add_result(tester, app, env, expected, "^0.2.0") def test_add_with_git_constraint(app, repo, tester, env, installed): @@ -114,17 +78,9 @@ def test_add_with_git_constraint(app, repo, tester, env, installed): • Installing poetry-plugin (0.1.2 9cf87a2) """ - assert tester.io.fetch_output() == expected - - update_command = app.find("update") - assert update_command.poetry.file.parent == env.path - assert update_command.poetry.locker.lock.parent == env.path - - content = update_command.poetry.file.read()["tool"]["poetry"] - assert "poetry-plugin" in content["dependencies"] - assert content["dependencies"]["poetry-plugin"] == { - "git": "https://github.com/demo/poetry-plugin.git" - } + assert_plugin_add_result( + tester, app, env, expected, {"git": "https://github.com/demo/poetry-plugin.git"} + ) def test_add_with_git_constraint_with_extras(app, repo, tester, env, installed): @@ -146,18 +102,16 @@ def test_add_with_git_constraint_with_extras(app, repo, tester, env, installed): • Installing poetry-plugin (0.1.2 9cf87a2) """ - assert tester.io.fetch_output() == expected - - update_command = app.find("update") - assert update_command.poetry.file.parent == env.path - assert update_command.poetry.locker.lock.parent == env.path - - content = update_command.poetry.file.read()["tool"]["poetry"] - assert "poetry-plugin" in content["dependencies"] - assert content["dependencies"]["poetry-plugin"] == { - "git": "https://github.com/demo/poetry-plugin.git", - "extras": ["foo"], - } + assert_plugin_add_result( + tester, + app, + env, + expected, + { + "git": "https://github.com/demo/poetry-plugin.git", + "extras": ["foo"], + }, + ) def test_add_existing_plugin_warns_about_no_operation( @@ -242,16 +196,7 @@ def test_add_existing_plugin_updates_if_requested( • Updating poetry-plugin (1.2.3 -> 2.3.4) """ - assert tester.io.fetch_output() == expected - - update_command = app.find("update") - assert update_command.poetry.file.parent == env.path - assert update_command.poetry.locker.lock.parent == env.path - assert update_command.poetry.locker.lock.exists() - - content = update_command.poetry.file.read()["tool"]["poetry"] - assert "poetry-plugin" in content["dependencies"] - assert content["dependencies"]["poetry-plugin"] == "^2.3.4" + assert_plugin_add_result(tester, app, env, expected, "^2.3.4") def test_adding_a_plugin_can_update_poetry_dependencies_if_needed( @@ -285,13 +230,4 @@ def test_adding_a_plugin_can_update_poetry_dependencies_if_needed( • Installing poetry-plugin (1.2.3) """ - assert tester.io.fetch_output() == expected - - update_command = app.find("update") - assert update_command.poetry.file.parent == env.path - assert update_command.poetry.locker.lock.parent == env.path - assert update_command.poetry.locker.lock.exists() - - content = update_command.poetry.file.read()["tool"]["poetry"] - assert "poetry-plugin" in content["dependencies"] - assert content["dependencies"]["poetry-plugin"] == "^1.2.3" + assert_plugin_add_result(tester, app, env, expected, "^1.2.3") diff --git a/tests/console/commands/plugin/test_remove.py b/tests/console/commands/plugin/test_remove.py index 0a66d8717cc..75c60422e3a 100644 --- a/tests/console/commands/plugin/test_remove.py +++ b/tests/console/commands/plugin/test_remove.py @@ -3,11 +3,7 @@ from poetry.__version__ import __version__ from poetry.core.packages.package import Package -from poetry.factory import Factory from poetry.layouts.layout import POETRY_DEFAULT -from poetry.repositories.installed_repository import InstalledRepository -from poetry.repositories.pool import Pool -from poetry.utils.env import EnvManager @pytest.fixture() @@ -15,33 +11,6 @@ def tester(command_tester_factory): return command_tester_factory("plugin remove") -@pytest.fixture() -def installed(): - repository = InstalledRepository() - - repository.add_package(Package("poetry", __version__)) - - return repository - - -def configure_sources_factory(repo): - def _configure_sources(poetry, sources, config, io): - pool = Pool() - pool.add_repository(repo) - poetry.set_pool(pool) - - return _configure_sources - - -@pytest.fixture(autouse=True) -def setup_mocks(mocker, env, repo, installed): - mocker.patch.object(EnvManager, "get_system_env", return_value=env) - mocker.patch.object(InstalledRepository, "load", return_value=installed) - mocker.patch.object( - Factory, "configure_sources", side_effect=configure_sources_factory(repo) - ) - - @pytest.fixture() def pyproject(env): pyproject = tomlkit.loads(POETRY_DEFAULT) @@ -60,7 +29,8 @@ def pyproject(env): ) -def test_remove_installed_package(app, repo, tester, env, installed, pyproject): +@pytest.fixture(autouse=True) +def install_plugin(env, installed, pyproject): lock_content = { "package": [ { @@ -99,6 +69,8 @@ def test_remove_installed_package(app, repo, tester, env, installed, pyproject): installed.add_package(Package("poetry-plugin", "1.2.3")) + +def test_remove_installed_package(app, tester, env): tester.execute("poetry-plugin") expected = """\ @@ -124,45 +96,7 @@ def test_remove_installed_package(app, repo, tester, env, installed, pyproject): assert "poetry-plugin" not in content["dependencies"] -def test_remove_installed_package_dry_run(app, repo, tester, env, installed, pyproject): - lock_content = { - "package": [ - { - "name": "poetry-plugin", - "version": "1.2.3", - "category": "main", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { - "python-versions": "^3.6", - "platform": "*", - "content-hash": "123456789", - "hashes": {"poetry-plugin": []}, - }, - } - - env.path.joinpath("poetry.lock").write_text( - tomlkit.dumps(lock_content), encoding="utf-8" - ) - - pyproject = tomlkit.loads( - env.path.joinpath("pyproject.toml").read_text(encoding="utf-8") - ) - content = pyproject["tool"]["poetry"] - - dependency_section = content["dependencies"] - dependency_section["poetry-plugin"] = "^1.2.3" - - env.path.joinpath("pyproject.toml").write_text( - tomlkit.dumps(pyproject), encoding="utf-8" - ) - - installed.add_package(Package("poetry-plugin", "1.2.3")) - +def test_remove_installed_package_dry_run(app, tester, env): tester.execute("poetry-plugin --dry-run") expected = """\ diff --git a/tests/console/commands/plugin/test_show.py b/tests/console/commands/plugin/test_show.py index 80c990376da..8e075762dbf 100644 --- a/tests/console/commands/plugin/test_show.py +++ b/tests/console/commands/plugin/test_show.py @@ -2,14 +2,10 @@ from entrypoints import EntryPoint as _EntryPoint -from poetry.__version__ import __version__ from poetry.core.packages.package import Package from poetry.factory import Factory from poetry.plugins.application_plugin import ApplicationPlugin from poetry.plugins.plugin import Plugin -from poetry.repositories.installed_repository import InstalledRepository -from poetry.repositories.pool import Pool -from poetry.utils.env import EnvManager class EntryPoint(_EntryPoint): @@ -25,33 +21,6 @@ def tester(command_tester_factory): return command_tester_factory("plugin show") -@pytest.fixture() -def installed(): - repository = InstalledRepository() - - repository.add_package(Package("poetry", __version__)) - - return repository - - -def configure_sources_factory(repo): - def _configure_sources(poetry, sources, config, io): - pool = Pool() - pool.add_repository(repo) - poetry.set_pool(pool) - - return _configure_sources - - -@pytest.fixture(autouse=True) -def setup_mocks(mocker, env, repo, installed): - mocker.patch.object(EnvManager, "get_system_env", return_value=env) - mocker.patch.object(InstalledRepository, "load", return_value=installed) - mocker.patch.object( - Factory, "configure_sources", side_effect=configure_sources_factory(repo) - ) - - def test_show_displays_installed_plugins(app, tester, installed, mocker): mocker.patch( "entrypoints.get_group_all", From 4ebcd828db75585720d5392a0024e9a3aedf3b11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 19 Feb 2021 10:16:28 +0100 Subject: [PATCH 146/222] Implement a new bootstrapping method --- .github/workflows/main.yml | 23 +- install-poetry.py | 876 +++++++++++++++++++++++++++++++++++++ 2 files changed, 891 insertions(+), 8 deletions(-) create mode 100644 install-poetry.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 07475596c09..51d23eddc2d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,14 +39,21 @@ jobs: - name: Bootstrap poetry shell: bash - run: | - python -m ensurepip - python -m pip install --upgrade pip - python -m pip install . + run: python install-poetry.py -y + + - name: Update PATH + if: ${{ matrix.os != 'Windows' }} + shell: bash + run: echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Update Path for Windows + if: ${{ matrix.os == 'Windows' }} + shell: bash + run: echo "$APPDATA\Python\Scripts" >> $GITHUB_PATH - name: Configure poetry shell: bash - run: python -m poetry config virtualenvs.in-project true + run: poetry config virtualenvs.in-project true - name: Set up cache uses: actions/cache@v2 @@ -58,12 +65,12 @@ jobs: - name: Ensure cache is healthy if: steps.cache.outputs.cache-hit == 'true' shell: bash - run: timeout 10s python -m poetry run pip --version || rm -rf .venv + run: timeout 10s poetry run pip --version || rm -rf .venv - name: Install dependencies shell: bash - run: python -m poetry install + run: poetry install - name: Run pytest shell: bash - run: python -m poetry run python -m pytest -p no:sugar -q tests/ + run: poetry run python -m pytest -p no:sugar -q tests/ diff --git a/install-poetry.py b/install-poetry.py new file mode 100644 index 00000000000..1d2561f8342 --- /dev/null +++ b/install-poetry.py @@ -0,0 +1,876 @@ +""" +This script will install Poetry and its dependencies. + +It does, in order: + + - Downloads the virtualenv package to a temporary directory and add it to sys.path. + - Creates a virtual environment in the correct OS data dir which will be + - `%APPDATA%\\pypoetry` on Windows + - ~/Library/Application Support/pypoetry on MacOS + - `${XDG_DATA_HOME}/pypoetry` (or `~/.local/share/pypoetry` if it's not set) on UNIX systems + - In `${POETRY_HOME}` if it's set. + - Installs the latest or given version of Poetry inside this virtual environment. + - Installs a `poetry` script in the Python user directory (or `${POETRY_HOME/bin}` if `POETRY_HOME` is set). +""" + +import argparse +import json +import os +import re +import shutil +import site +import stat +import subprocess +import sys +import tempfile + +from contextlib import closing +from contextlib import contextmanager +from functools import cmp_to_key +from io import UnsupportedOperation +from pathlib import Path +from typing import Optional +from typing import Tuple +from urllib.request import Request +from urllib.request import urlopen + + +SHELL = os.getenv("SHELL", "") +WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") +MACOS = sys.platform == "darwin" + +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("cyan", None, None), + "comment": style("yellow", None, None), + "success": style("green", None, None), + "error": style("red", None, None), + "warning": style("yellow", None, None), + "b": style(None, None, ("bold",)), +} + + +def is_decorated(): + if WINDOWS: + return ( + os.getenv("ANSICON") is not None + or "ON" == os.getenv("ConEmuANSI") + or "xterm" == os.getenv("Term") + ) + + 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) + + +def string_to_bool(value): + value = value.lower() + + return value in {"true", "1", "y", "yes"} + + +def data_dir(version: Optional[str] = None) -> Path: + if os.getenv("POETRY_HOME"): + return Path(os.getenv("POETRY_HOME")).expanduser() + + if WINDOWS: + const = "CSIDL_APPDATA" + path = os.path.normpath(_get_win_folder(const)) + path = os.path.join(path, "pypoetry") + elif MACOS: + path = os.path.expanduser("~/Library/Application Support/pypoetry") + if not os.path.isdir(path): + path = os.path.expanduser("~/.config/pypoetry") + else: + path = os.getenv("XDG_DATA_HOME", os.path.expanduser("~/.local/share")) + path = os.path.join(path, "pypoetry") + + if version: + path = os.path.join(path, version) + + return Path(path) + + +def bin_dir(version: Optional[str] = None) -> Path: + if os.getenv("POETRY_HOME"): + return Path(os.getenv("POETRY_HOME"), "bin").expanduser() + + user_base = site.getuserbase() + + if WINDOWS: + bin_dir = os.path.join(user_base, "Scripts") + else: + bin_dir = os.path.join(user_base, "bin") + + return Path(bin_dir) + + +def _get_win_folder_from_registry(csidl_name): + import winreg as _winreg + + shell_folder_name = { + "CSIDL_APPDATA": "AppData", + "CSIDL_COMMON_APPDATA": "Common AppData", + "CSIDL_LOCAL_APPDATA": "Local AppData", + }[csidl_name] + + key = _winreg.OpenKey( + _winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders", + ) + dir, type = _winreg.QueryValueEx(key, shell_folder_name) + + return dir + + +def _get_win_folder_with_ctypes(csidl_name): + import ctypes + + csidl_const = { + "CSIDL_APPDATA": 26, + "CSIDL_COMMON_APPDATA": 35, + "CSIDL_LOCAL_APPDATA": 28, + }[csidl_name] + + buf = ctypes.create_unicode_buffer(1024) + ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in buf: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf2 = ctypes.create_unicode_buffer(1024) + if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): + buf = buf2 + + return buf.value + + +if WINDOWS: + try: + from ctypes import windll # noqa + + _get_win_folder = _get_win_folder_with_ctypes + except ImportError: + _get_win_folder = _get_win_folder_from_registry + + +@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 + + +BIN = """# -*- coding: utf-8 -*- +import glob +import sys +import os + +site_packages = "{site_packages}" + +sys.path.insert(0, site_packages) + +if __name__ == "__main__": + from {main_module} import main + + main() +""" + +BAT = '@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} + +You can uninstall at any time by executing this script with the --uninstall option, +and these changes will be reverted. +""" + +POST_MESSAGE = """{poetry} ({version}) is installed now. Great! + +You can test that everything is set up by executing: + +`{test_command}` +""" + +POST_MESSAGE_NOT_IN_PATH = """{poetry} ({version}) is installed now. Great! + +To get started you need {poetry}'s bin directory ({poetry_home_bin}) in your `PATH` +environment variable. +{configure_message} +Alternatively, you can call {poetry} explicitly with `{poetry_executable}`. + +You can test that everything is set up by executing: + +`{test_command}` +""" + +POST_MESSAGE_CONFIGURE_UNIX = """ +Add `export PATH="{poetry_home_bin}:$PATH` to your shell configuration file. +""" + +POST_MESSAGE_CONFIGURE_FISH = """ +You can execute `set -U fish_user_paths {poetry_home_bin} $fish_user_paths` +""" + +POST_MESSAGE_CONFIGURE_WINDOWS = """""" + + +class Cursor: + def __init__(self) -> None: + self._output = sys.stdout + + def move_up(self, lines: int = 1) -> "Cursor": + self._output.write("\x1b[{}A".format(lines)) + + return self + + def move_down(self, lines: int = 1) -> "Cursor": + self._output.write("\x1b[{}B".format(lines)) + + return self + + def move_right(self, columns: int = 1) -> "Cursor": + self._output.write("\x1b[{}C".format(columns)) + + return self + + def move_left(self, columns: int = 1) -> "Cursor": + self._output.write("\x1b[{}D".format(columns)) + + return self + + def move_to_column(self, column: int) -> "Cursor": + self._output.write("\x1b[{}G".format(column)) + + return self + + def move_to_position(self, column: int, row: int) -> "Cursor": + self._output.write("\x1b[{};{}H".format(row + 1, column)) + + return self + + def save_position(self) -> "Cursor": + self._output.write("\x1b7") + + return self + + def restore_position(self) -> "Cursor": + self._output.write("\x1b8") + + return self + + def hide(self) -> "Cursor": + self._output.write("\x1b[?25l") + + return self + + def show(self) -> "Cursor": + self._output.write("\x1b[?25h\x1b[?0c") + + return self + + def clear_line(self) -> "Cursor": + """ + Clears all the output from the current line. + """ + self._output.write("\x1b[2K") + + return self + + def clear_line_after(self) -> "Cursor": + """ + Clears all the output from the current line after the current position. + """ + self._output.write("\x1b[K") + + return self + + def clear_output(self) -> "Cursor": + """ + Clears all the output from the cursors' current position + to the end of the screen. + """ + self._output.write("\x1b[0J") + + return self + + def clear_screen(self) -> "Cursor": + """ + Clears the entire screen. + """ + self._output.write("\x1b[2J") + + return self + + +class Installer: + 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]+)?" + ) + + def __init__( + self, + version: Optional[str] = None, + preview: bool = False, + force: bool = False, + accept_all: bool = False, + git: Optional[str] = None, + path: Optional[str] = None, + ) -> None: + self._version = version + self._preview = preview + self._force = force + self._accept_all = accept_all + self._git = git + self._path = path + self._data_dir = data_dir() + self._bin_dir = bin_dir() + self._cursor = Cursor() + + def allows_prereleases(self) -> bool: + return self._preview + + def run(self) -> int: + if self._git: + version = self._git + elif self._path: + version = self._path + else: + version, current_version = self.get_version() + + if version is None: + return 0 + + self.display_pre_message() + self.ensure_directories() + + try: + self.install(version) + except subprocess.CalledProcessError as e: + print(colorize("error", "An error has occured: {}".format(str(e)))) + print(e.output.decode()) + + return e.returncode + + self._write("") + self.display_post_message(version) + + return 0 + + def install(self, version, upgrade=False): + """ + Installs Poetry in $POETRY_HOME. + """ + self._write( + "Installing {} ({})".format( + colorize("info", "Poetry"), colorize("info", version) + ) + ) + + env_path, site_packages = self.make_env(version) + self.install_poetry(version, env_path) + self.make_bin(site_packages, version) + + self._overwrite( + "Installing {} ({}): {}".format( + colorize("info", "Poetry"), + colorize("b", version), + colorize("success", "Done"), + ) + ) + + self._data_dir.joinpath("VERSION").write_text(version) + + return 0 + + def uninstall(self) -> int: + if not self._data_dir.exists(): + self._write( + "{} is not currently installed.".format(colorize("info", "Poetry")) + ) + + return 1 + + version = None + if self._data_dir.joinpath("VERSION").exists(): + version = self._data_dir.joinpath("VERSION").read_text().strip() + + if version: + self._write( + "Removing {} ({})".format( + colorize("info", "Poetry"), colorize("b", version) + ) + ) + else: + self._write("Removing {}".format(colorize("info", "Poetry"))) + + shutil.rmtree(str(self._data_dir)) + for script in ["poetry", "poetry.bat"]: + self._bin_dir.joinpath(script).unlink(missing_ok=True) + + return 0 + + def make_env(self, version: str) -> Tuple[Path, Path]: + self._overwrite( + "Installing {} ({}): {}".format( + colorize("info", "Poetry"), + colorize("b", version), + colorize("comment", "Creating environment"), + ) + ) + + env_path = self._data_dir.joinpath("venv") + + with temporary_directory() as tmp_dir: + subprocess.call( + [sys.executable, "-m", "pip", "install", "virtualenv", "-t", tmp_dir], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + + sys.path.insert(0, tmp_dir) + + import virtualenv + + virtualenv.cli_run([str(env_path), "--clear"]) + + if WINDOWS: + return env_path, list(env_path.glob("Lib/site-packages"))[0] + + return env_path, list(env_path.glob("lib/python*/site-packages"))[0] + + def make_bin(self, site_packages: Path, version: str) -> None: + self._overwrite( + "Installing {} ({}): {}".format( + colorize("info", "Poetry"), + colorize("b", version), + colorize("comment", "Creating script"), + ) + ) + + self._bin_dir.mkdir(parents=True, exist_ok=True) + + python_executable = sys.executable + + if WINDOWS: + with self._bin_dir.joinpath("poetry.bat").open("w") as f: + f.write( + BAT.format( + python_executable=python_executable, + poetry_bin=self._bin_dir.joinpath("poetry"), + ) + ) + + # Versions of Poetry prior to 1.2.0 did not have the main() + # function at the poetry.console.application level but et he poetry.console one. + main_module = "poetry.console.application" + version_content = site_packages.joinpath("poetry/__version__.py").read_text( + encoding="utf-8" + ) + + current_version_re = re.match('(?ms).*__version__ = "(.+)".*', version_content) + if not current_version_re: + self._write( + colorize( + "warning", + "Unable to get the current Poetry version. Assuming None", + ) + ) + if is_decorated(): + self._write("") + + current_version = "1.2.0" + else: + current_version = current_version_re.group(1) + + m = self.VERSION_REGEX.match(current_version) + if tuple(int(p) for p in m.groups()[:2]) < (1, 2): + main_module = "poetry.console" + + with self._bin_dir.joinpath("poetry").open("w", encoding="utf-8") as f: + f.write("#!/usr/bin/env {}\n".format(python_executable)) + + if WINDOWS: + f.write( + BIN.format( + site_packages=str(site_packages.resolve()).replace( + "\\", "\\\\" + ), + main_module=main_module, + ) + ) + else: + f.write( + BIN.format( + site_packages=str(site_packages.resolve()), + main_module=main_module, + ) + ) + + if not WINDOWS: + # Making the file executable + st = os.stat(self._bin_dir.joinpath("poetry").as_posix()) + os.chmod( + self._bin_dir.joinpath("poetry").as_posix(), st.st_mode | stat.S_IEXEC + ) + + def install_poetry(self, version: str, env_path: Path) -> None: + self._overwrite( + "Installing {} ({}): {}".format( + colorize("info", "Poetry"), + colorize("b", version), + colorize("comment", "Installing Poetry"), + ) + ) + + if WINDOWS: + python = env_path.joinpath("Scripts/python.exe") + else: + python = env_path.joinpath("bin/python") + + if self._git: + specification = "git+" + version + elif self._path: + specification = version + else: + specification = f"poetry=={version}" + + subprocess.call( + [str(python), "-m", "pip", "install", specification], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + + def display_pre_message(self) -> None: + kwargs = { + "poetry": colorize("info", "Poetry"), + "poetry_home_bin": colorize("comment", self._bin_dir), + } + self._write(PRE_MESSAGE.format(**kwargs)) + + def display_post_message(self, version: str) -> None: + if WINDOWS: + return self.display_post_message_windows(version) + + if SHELL == "fish": + return self.display_post_message_fish(version) + + return self.display_post_message_unix(version) + + def display_post_message_windows(self, version: str) -> None: + path = self.get_windows_path_var() + + message = POST_MESSAGE_NOT_IN_PATH + if path and str(self._bin_dir) in path: + message = POST_MESSAGE + + self._write( + message.format( + poetry=colorize("info", "Poetry"), + version=colorize("b", version), + poetry_home_bin=colorize("comment", self._bin_dir), + poetry_executable=colorize("b", self._bin_dir.joinpath("poetry")), + configure_message=POST_MESSAGE_CONFIGURE_WINDOWS.format( + poetry_home_bin=colorize("comment", self._bin_dir) + ), + test_command=colorize("b", "poetry --version"), + ) + ) + + def get_windows_path_var(self) -> Optional[str]: + import winreg + + 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 display_post_message_fish(self, version: str) -> None: + fish_user_paths = subprocess.check_output( + ["fish", "-c", "echo $fish_user_paths"] + ).decode("utf-8") + + message = POST_MESSAGE_NOT_IN_PATH + if fish_user_paths and str(self._bin_dir) in fish_user_paths: + message = POST_MESSAGE + + self._write( + message.format( + poetry=colorize("info", "Poetry"), + version=colorize("b", version), + poetry_home_bin=colorize("comment", self._bin_dir), + poetry_executable=colorize("b", self._bin_dir.joinpath("poetry")), + configure_message=POST_MESSAGE_CONFIGURE_FISH.format( + poetry_home_bin=colorize("comment", self._bin_dir) + ), + test_command=colorize("b", "poetry --version"), + ) + ) + + def display_post_message_unix(self, version: str) -> None: + paths = os.getenv("PATH", "").split(":") + + message = POST_MESSAGE_NOT_IN_PATH + if paths and str(self._bin_dir) in paths: + message = POST_MESSAGE + + self._write( + message.format( + poetry=colorize("info", "Poetry"), + version=colorize("b", version), + poetry_home_bin=colorize("comment", self._bin_dir), + poetry_executable=colorize("b", self._bin_dir.joinpath("poetry")), + configure_message=POST_MESSAGE_CONFIGURE_UNIX.format( + poetry_home_bin=colorize("comment", self._bin_dir) + ), + test_command=colorize("b", "poetry --version"), + ) + ) + + def ensure_directories(self) -> None: + self._data_dir.mkdir(parents=True, exist_ok=True) + self._bin_dir.mkdir(parents=True, exist_ok=True) + + def get_version(self): + current_version = None + if self._data_dir.joinpath("VERSION").exists(): + current_version = self._data_dir.joinpath("VERSION").read_text().strip() + + self._write(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 + + self._write("") + releases = sorted( + metadata["releases"].keys(), key=cmp_to_key(_compare_versions) + ) + + if self._version and self._version not in releases: + self._write( + colorize("error", "Version {} does not exist.".format(self._version)) + ) + + return None, None + + 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 + + version = release + + break + + if current_version == version and not self._force: + self._write( + "The latest version ({}) is already installed.".format( + colorize("b", version) + ) + ) + + return None, current_version + + return version, current_version + + def _write(self, line) -> None: + sys.stdout.write(line + "\n") + + def _overwrite(self, line) -> None: + if not is_decorated(): + return self._write(line) + + self._cursor.move_up() + self._cursor.clear_line() + self._write(line) + + 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( + "-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( + "--path", + dest="path", + action="store", + help=( + "Install from a given path (file or directory) instead of " + "fetching the latest version of Poetry available online." + ), + ) + parser.add_argument( + "--git", + dest="git", + action="store", + help=( + "Install from a git repository instead of fetching the latest version " + "of Poetry available online." + ), + ) + + args = parser.parse_args() + + 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, + accept_all=args.accept_all + or string_to_bool(os.getenv("POETRY_ACCEPT", "0")) + or not is_interactive(), + path=args.path, + git=args.git, + ) + + 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 fb168214b8a657fe918d83214de94b2daa8c2745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 19 Feb 2021 16:22:58 +0100 Subject: [PATCH 147/222] Add documentation for the new bootstrapping method --- docs/docs/index.md | 112 ++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 67 deletions(-) diff --git a/docs/docs/index.md b/docs/docs/index.md index 56b3040b02a..16c62076e24 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -18,109 +18,84 @@ on Windows, Linux and OSX. ## Installation Poetry provides a custom installer that will install `poetry` isolated -from the rest of your system by vendorizing its dependencies. This is the -recommended way of installing `poetry`. +from the rest of your system. ### osx / linux / bashonwindows install instructions ```bash -curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python - +curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python - ``` ### windows powershell install instructions ```powershell -(Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python - +(Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | python - ``` -!!! note +The installer installs the `poetry` tool to Poetry's `bin` directory. This location depends on you system: - You only need to install Poetry once. It will automatically pick up the current - Python version and use it to [create virtualenvs](/docs/managing-environments) accordingly. +- `$HOME/.local/bin` for Unix +- `%APPDATA%\Python\Scripts` on Windows -The installer installs the `poetry` tool to Poetry's `bin` directory. -On Unix it is located at `$HOME/.poetry/bin` and on Windows at `%USERPROFILE%\.poetry\bin`. +If this directory is not on you `PATH`, you will need to add it manually +if you want to invoke Poetry with simply `poetry`. -This directory will be automatically added to your `$PATH` environment variable, -by appending a statement to your `$HOME/.profile` configuration (or equivalent files). -If you do not feel comfortable with this, please pass the `--no-modify-path` flag to -the installer and manually add the Poetry's `bin` directory to your path. +Alternatively, you can use the full path to `poetry` to use it. -Finally, open a new shell and type the following: +Once Poetry is installed you can execute the following: ```bash poetry --version ``` -If you see something like `Poetry 0.12.0` then you are ready to use Poetry. +If you see something like `Poetry (version 1.2.0)` then you are ready to use Poetry. If you decide Poetry isn't your thing, you can completely remove it from your system by running the installer again with the `--uninstall` option or by setting the `POETRY_UNINSTALL` environment variable before executing the installer. ```bash -python get-poetry.py --uninstall -POETRY_UNINSTALL=1 python get-poetry.py +python install-poetry.py --uninstall +POETRY_UNINSTALL=1 python install-poetry.py ``` -By default, Poetry is installed into the user's platform-specific home directory. If you wish to change this, you may define the `POETRY_HOME` environment variable: +By default, Poetry is installed into the user's platform-specific home directory. +If you wish to change this, you may define the `POETRY_HOME` environment variable: ```bash -POETRY_HOME=/etc/poetry python get-poetry.py +POETRY_HOME=/etc/poetry python install-poetry.py ``` -If you want to install prerelease versions, you can do so by passing `--preview` to `get-poetry.py` +If you want to install prerelease versions, you can do so by passing `--preview` option to `install-poetry.py` or by using the `POETRY_PREVIEW` environment variable: ```bash -python get-poetry.py --preview -POETRY_PREVIEW=1 python get-poetry.py +python install-poetry.py --preview +POETRY_PREVIEW=1 python install-poetry.py ``` -Similarly, if you want to install a specific version, you can use `--version` or the `POETRY_VERSION` +Similarly, if you want to install a specific version, you can use `--version` option or the `POETRY_VERSION` environment variable: ```bash -python get-poetry.py --version 0.12.0 -POETRY_VERSION=0.12.0 python get-poetry.py +python install-poetry.py --version 1.2.0 +POETRY_VERSION=1.2.0 python install-poetry.py ``` -!!!note - - Note that the installer does not support Poetry releases < 0.12.0. - -!!!note - - The setup script must be able to find one of following executables in your shell's path environment: - - - `python` (which can be a py3 or py2 interpreter) - - `python3` - - `py.exe -3` (Windows) - - `py.exe -2` (Windows) +You can also install Poetry for a `git` repository by using the `--git` option: -### Alternative installation methods (not recommended) +```bash +python install-poetry.py --git https://github.com/python-poetry/poetry.git@master +```` !!!note - Using alternative installation methods will make Poetry always - use the Python version for which it has been installed to create - virtualenvs. - - So, you will need to install Poetry for each Python version you - want to use and switch between them. - -#### Installing with `pip` - -Using `pip` to install Poetry is possible. - -```bash -pip install --user poetry -``` + Note that the installer does not support Python < 3.6. -!!!warning - Be aware that it will also install Poetry's dependencies - which might cause conflicts with other packages. +### Alternative installation methods #### Installing with `pipx` -Using [`pipx`](https://github.com/cs01/pipx) to install Poetry is also possible. `pipx` is used to install Python CLI applications globally while still isolating them in virtual environments. This allows for clean upgrades and uninstalls. `pipx` supports Python 3.6 and later. If using an earlier version of Python, consider [`pipsi`](https://github.com/mitsuhiko/pipsi). +Using [`pipx`](https://github.com/pipxproject/pipx) to install Poetry is also possible. +`pipx` is used to install Python CLI applications globally while still isolating them in virtual environments. +This allows for clean upgrades and uninstalls. ```bash pipx install poetry @@ -134,7 +109,19 @@ pipx upgrade poetry pipx uninstall poetry ``` -[Github repository](https://github.com/cs01/pipx). + +#### Installing with `pip` + +Using `pip` to install Poetry is possible. + +```bash +pip install --user poetry +``` + +!!!warning + + Be aware that it will also install Poetry's dependencies + which might cause conflicts with other packages. ## Updating `poetry` @@ -155,18 +142,9 @@ And finally, if you want to install a specific version, you can pass it as an ar to `self update`. ```bash -poetry self update 0.8.0 +poetry self update 1.2.0 ``` -!!!note - - The `self update` command will only work if you used the recommended - installer to install Poetry. - -!!!note - - If you are still on poetry version < 1.0 use `poetry self:update` instead. - ## Enable tab completion for Bash, Fish, or Zsh From 8f69bca2211f81e44fc9004c079c364ea6ac9cc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 19 Feb 2021 17:29:43 +0100 Subject: [PATCH 148/222] Use a symlink to the venv-installed poetry script --- install-poetry.py | 105 +++++++--------------------------------------- 1 file changed, 16 insertions(+), 89 deletions(-) diff --git a/install-poetry.py b/install-poetry.py index 1d2561f8342..5d3e39263a7 100644 --- a/install-poetry.py +++ b/install-poetry.py @@ -19,7 +19,6 @@ import re import shutil import site -import stat import subprocess import sys import tempfile @@ -30,7 +29,6 @@ from io import UnsupportedOperation from pathlib import Path from typing import Optional -from typing import Tuple from urllib.request import Request from urllib.request import urlopen @@ -238,24 +236,6 @@ def temporary_directory(*args, **kwargs): yield name -BIN = """# -*- coding: utf-8 -*- -import glob -import sys -import os - -site_packages = "{site_packages}" - -sys.path.insert(0, site_packages) - -if __name__ == "__main__": - from {main_module} import main - - main() -""" - -BAT = '@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}, @@ -458,9 +438,9 @@ def install(self, version, upgrade=False): ) ) - env_path, site_packages = self.make_env(version) + env_path = self.make_env(version) self.install_poetry(version, env_path) - self.make_bin(site_packages, version) + self.make_bin(version) self._overwrite( "Installing {} ({}): {}".format( @@ -497,11 +477,12 @@ def uninstall(self) -> int: shutil.rmtree(str(self._data_dir)) for script in ["poetry", "poetry.bat"]: - self._bin_dir.joinpath(script).unlink(missing_ok=True) + if self._bin_dir.joinpath(script).exists(): + self._bin_dir.joinpath(script).unlink() return 0 - def make_env(self, version: str) -> Tuple[Path, Path]: + def make_env(self, version: str) -> Path: self._overwrite( "Installing {} ({}): {}".format( colorize("info", "Poetry"), @@ -525,12 +506,9 @@ def make_env(self, version: str) -> Tuple[Path, Path]: virtualenv.cli_run([str(env_path), "--clear"]) - if WINDOWS: - return env_path, list(env_path.glob("Lib/site-packages"))[0] - - return env_path, list(env_path.glob("lib/python*/site-packages"))[0] + return env_path - def make_bin(self, site_packages: Path, version: str) -> None: + def make_bin(self, version: str) -> None: self._overwrite( "Installing {} ({}): {}".format( colorize("info", "Poetry"), @@ -541,69 +519,18 @@ def make_bin(self, site_packages: Path, version: str) -> None: self._bin_dir.mkdir(parents=True, exist_ok=True) - python_executable = sys.executable - + script = "poetry" + target_script = "venv/bin/poetry" if WINDOWS: - with self._bin_dir.joinpath("poetry.bat").open("w") as f: - f.write( - BAT.format( - python_executable=python_executable, - poetry_bin=self._bin_dir.joinpath("poetry"), - ) - ) - - # Versions of Poetry prior to 1.2.0 did not have the main() - # function at the poetry.console.application level but et he poetry.console one. - main_module = "poetry.console.application" - version_content = site_packages.joinpath("poetry/__version__.py").read_text( - encoding="utf-8" - ) + script = "poetry.exe" + target_script = "venv/Scripts/poetry.exe" - current_version_re = re.match('(?ms).*__version__ = "(.+)".*', version_content) - if not current_version_re: - self._write( - colorize( - "warning", - "Unable to get the current Poetry version. Assuming None", - ) - ) - if is_decorated(): - self._write("") + if self._bin_dir.joinpath(script).exists(): + self._bin_dir.joinpath(script).unlink() - current_version = "1.2.0" - else: - current_version = current_version_re.group(1) - - m = self.VERSION_REGEX.match(current_version) - if tuple(int(p) for p in m.groups()[:2]) < (1, 2): - main_module = "poetry.console" - - with self._bin_dir.joinpath("poetry").open("w", encoding="utf-8") as f: - f.write("#!/usr/bin/env {}\n".format(python_executable)) - - if WINDOWS: - f.write( - BIN.format( - site_packages=str(site_packages.resolve()).replace( - "\\", "\\\\" - ), - main_module=main_module, - ) - ) - else: - f.write( - BIN.format( - site_packages=str(site_packages.resolve()), - main_module=main_module, - ) - ) - - if not WINDOWS: - # Making the file executable - st = os.stat(self._bin_dir.joinpath("poetry").as_posix()) - os.chmod( - self._bin_dir.joinpath("poetry").as_posix(), st.st_mode | stat.S_IEXEC - ) + self._bin_dir.joinpath(script).symlink_to( + self._data_dir.joinpath(target_script) + ) def install_poetry(self, version: str, env_path: Path) -> None: self._overwrite( From 370bccf49562d61654218b538fcc1efe4b2724f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 19 Mar 2021 10:18:47 +0100 Subject: [PATCH 149/222] Add a note to warn that the old installer is deprecated --- docs/docs/index.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/docs/index.md b/docs/docs/index.md index 16c62076e24..b12c510d358 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -29,6 +29,11 @@ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install- (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | python - ``` +!!!warning + + The previous `get-poetry.py` installer is now deprecated, if you are currently using it + you should migrate to the new, supported, `install-poetry.py` installer. + The installer installs the `poetry` tool to Poetry's `bin` directory. This location depends on you system: - `$HOME/.local/bin` for Unix From 1d1cd7815e7481e5c5f2d857a1ec27d4e8f70b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 19 Mar 2021 10:23:04 +0100 Subject: [PATCH 150/222] Update installation instructions in the README --- README.md | 74 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 7759fca1232..b1a30d7a012 100644 --- a/README.md +++ b/README.md @@ -17,42 +17,76 @@ The [complete documentation](https://python-poetry.org/docs/) is available on th ## Installation Poetry provides a custom installer that will install `poetry` isolated -from the rest of your system by vendorizing its dependencies. This is the -recommended way of installing `poetry`. +from the rest of your system. +### osx / linux / bashonwindows install instructions ```bash -curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python +curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python - ``` +### windows powershell install instructions +```powershell +(Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | python - +``` + +**Warning**: The previous `get-poetry.py` installer is now deprecated, if you are currently using it +you should migrate to the new, supported, `install-poetry.py` installer. + +The installer installs the `poetry` tool to Poetry's `bin` directory. This location depends on you system: + +- `$HOME/.local/bin` for Unix +- `%APPDATA%\Python\Scripts` on Windows -Alternatively, you can download the `get-poetry.py` file and execute it separately. +If this directory is not on you `PATH`, you will need to add it manually +if you want to invoke Poetry with simply `poetry`. + +Alternatively, you can use the full path to `poetry` to use it. + +Once Poetry is installed you can execute the following: + +```bash +poetry --version +``` -The setup script must be able to find one of following executables in your shell's path environment: +If you see something like `Poetry (version 1.2.0)` then you are ready to use Poetry. +If you decide Poetry isn't your thing, you can completely remove it from your system +by running the installer again with the `--uninstall` option or by setting +the `POETRY_UNINSTALL` environment variable before executing the installer. -- `python` (which can be a py3 or py2 interpreter) -- `python3` -- `py.exe -3` (Windows) -- `py.exe -2` (Windows) +```bash +python install-poetry.py --uninstall +POETRY_UNINSTALL=1 python install-poetry.py +``` -If you want to install prerelease versions, you can do so by passing `--preview` to `get-poetry.py`: +By default, Poetry is installed into the user's platform-specific home directory. +If you wish to change this, you may define the `POETRY_HOME` environment variable: ```bash -python get-poetry.py --preview +POETRY_HOME=/etc/poetry python install-poetry.py ``` -Similarly, if you want to install a specific version, you can use `--version`: +If you want to install prerelease versions, you can do so by passing `--preview` option to `install-poetry.py` +or by using the `POETRY_PREVIEW` environment variable: ```bash -python get-poetry.py --version 0.7.0 +python install-poetry.py --preview +POETRY_PREVIEW=1 python install-poetry.py ``` -Using `pip` to install `poetry` is also possible. +Similarly, if you want to install a specific version, you can use `--version` option or the `POETRY_VERSION` +environment variable: ```bash -pip install --user poetry +python install-poetry.py --version 1.2.0 +POETRY_VERSION=1.2.0 python install-poetry.py ``` -Be aware, however, that it will also install poetry's dependencies -which might cause conflicts. +You can also install Poetry for a `git` repository by using the `--git` option: + +```bash +python install-poetry.py --git https://github.com/python-poetry/poetry.git@master +```` + +**Note**: Note that the installer does not support Python < 3.6. ## Updating `poetry` @@ -72,13 +106,9 @@ And finally, if you want to install a specific version you can pass it as an arg to `self update`. ```bash -poetry self update 1.0.0 +poetry self update 1.2.0 ``` -*Note:* - - If you are still on poetry version < 1.0 use `poetry self:update` instead. - ## Enable tab completion for Bash, Fish, or Zsh From 8cf7bf7a42abcb217a7526c051fc0d8267d556c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 19 Mar 2021 11:32:23 +0100 Subject: [PATCH 151/222] Fix issues on Windows --- install-poetry.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/install-poetry.py b/install-poetry.py index 5d3e39263a7..fffaed6f3ad 100644 --- a/install-poetry.py +++ b/install-poetry.py @@ -528,9 +528,16 @@ def make_bin(self, version: str) -> None: if self._bin_dir.joinpath(script).exists(): self._bin_dir.joinpath(script).unlink() - self._bin_dir.joinpath(script).symlink_to( - self._data_dir.joinpath(target_script) - ) + try: + self._bin_dir.joinpath(script).symlink_to( + self._data_dir.joinpath(target_script) + ) + except OSError: + # This can happen if the user + # does not have the correct permission on Windows + shutil.copy( + self._data_dir.joinpath(target_script), self._bin_dir.joinpath(script) + ) def install_poetry(self, version: str, env_path: Path) -> None: self._overwrite( @@ -553,7 +560,7 @@ def install_poetry(self, version: str, env_path: Path) -> None: else: specification = f"poetry=={version}" - subprocess.call( + subprocess.run( [str(python), "-m", "pip", "install", specification], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, From e359ba50c666aaca71cb02b261268c0ae4935db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 19 Mar 2021 15:22:49 +0100 Subject: [PATCH 152/222] Fix data dir location on MacOS --- install-poetry.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/install-poetry.py b/install-poetry.py index fffaed6f3ad..7cb19c85296 100644 --- a/install-poetry.py +++ b/install-poetry.py @@ -141,8 +141,6 @@ def data_dir(version: Optional[str] = None) -> Path: path = os.path.join(path, "pypoetry") elif MACOS: path = os.path.expanduser("~/Library/Application Support/pypoetry") - if not os.path.isdir(path): - path = os.path.expanduser("~/.config/pypoetry") else: path = os.getenv("XDG_DATA_HOME", os.path.expanduser("~/.local/share")) path = os.path.join(path, "pypoetry") From 45a9b8f20384591d0a33ae876bcf23656f928ec0 Mon Sep 17 00:00:00 2001 From: Cere Blanco <743526+cereblanco@users.noreply.github.com> Date: Wed, 31 Mar 2021 01:20:45 +0800 Subject: [PATCH 153/222] ci: add support for python 3.10 experimental builds Resolves: #3842 --- .github/workflows/main.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 51d23eddc2d..1db4a32cc47 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,10 +19,16 @@ jobs: tests: name: ${{ matrix.os }} / ${{ matrix.python-version }} runs-on: ${{ matrix.os }}-latest + continue-on-error: ${{ matrix.experimental }} strategy: matrix: os: [Ubuntu, MacOS, Windows] python-version: [3.6, 3.7, 3.8, 3.9] + experimental: [false] + include: + - os: Ubuntu + python-version: "3.10.0-alpha - 3.10.0" + experimental: true fail-fast: false steps: - uses: actions/checkout@v2 From b753aaf4c3c08ef0e54941a6616fe318fdf4f6e4 Mon Sep 17 00:00:00 2001 From: ThatXliner Date: Fri, 2 Apr 2021 09:13:57 -0700 Subject: [PATCH 154/222] Used pyupgrade to remove compatibility code (#3861) * Used pyupgrade to remove compatibility code May fix #3860 --- poetry/config/config.py | 4 +- poetry/config/config_source.py | 2 +- poetry/console/application.py | 6 +- poetry/inspection/info.py | 20 +++--- poetry/installation/authenticator.py | 10 +-- poetry/installation/chef.py | 2 +- poetry/installation/chooser.py | 12 ++-- poetry/installation/executor.py | 13 ++-- poetry/installation/installer.py | 10 +-- poetry/installation/pip_installer.py | 12 ++-- poetry/json/__init__.py | 5 +- poetry/layouts/layout.py | 6 +- poetry/layouts/src.py | 4 +- poetry/layouts/standard.py | 4 +- poetry/mixology/assignment.py | 2 +- poetry/mixology/failure.py | 8 +-- poetry/mixology/incompatibility.py | 36 ++++++----- poetry/mixology/partial_solution.py | 2 +- poetry/mixology/term.py | 10 +-- poetry/mixology/version_solver.py | 10 ++- poetry/packages/dependency_package.py | 4 +- poetry/packages/locker.py | 4 +- poetry/packages/package_collection.py | 4 +- poetry/plugins/base_plugin.py | 2 +- poetry/plugins/plugin_manager.py | 6 +- poetry/publishing/publisher.py | 8 +-- poetry/publishing/uploader.py | 37 ++++-------- poetry/puzzle/exceptions.py | 2 +- poetry/puzzle/provider.py | 8 +-- poetry/puzzle/solver.py | 10 ++- poetry/repositories/base_repository.py | 2 +- poetry/repositories/legacy_repository.py | 8 +-- poetry/repositories/pool.py | 10 +-- poetry/repositories/pypi_repository.py | 30 ++++----- poetry/repositories/remote_repository.py | 2 +- poetry/repositories/repository.py | 2 +- poetry/utils/_compat.py | 2 +- poetry/utils/env.py | 77 ++++++++++++------------ poetry/utils/exporter.py | 18 +++--- poetry/utils/helpers.py | 6 +- poetry/utils/password_manager.py | 26 ++++---- poetry/utils/setup_reader.py | 2 +- poetry/utils/shell.py | 2 +- poetry/version/version_selector.py | 6 +- 44 files changed, 198 insertions(+), 258 deletions(-) diff --git a/poetry/config/config.py b/poetry/config/config.py index c0f794bd004..b1b3b3e61bb 100644 --- a/poetry/config/config.py +++ b/poetry/config/config.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - import os import re @@ -27,7 +25,7 @@ def boolean_normalizer(val: str) -> bool: return val in ["true", "1"] -class Config(object): +class Config: default_config = { "cache-dir": str(CACHE_DIR), diff --git a/poetry/config/config_source.py b/poetry/config/config_source.py index 0a6707f28c9..2fc9b585ac6 100644 --- a/poetry/config/config_source.py +++ b/poetry/config/config_source.py @@ -1,7 +1,7 @@ from typing import Any -class ConfigSource(object): +class ConfigSource: def add_property(self, key: str, value: Any) -> None: raise NotImplementedError() diff --git a/poetry/console/application.py b/poetry/console/application.py index d9d9277d274..8dcbced0b73 100644 --- a/poetry/console/application.py +++ b/poetry/console/application.py @@ -88,7 +88,7 @@ def _load() -> Type[Command]: class Application(BaseApplication): def __init__(self) -> None: - super(Application, self).__init__("poetry", __version__) + super().__init__("poetry", __version__) self._poetry = None self._io: Optional[IO] = None @@ -132,7 +132,7 @@ def create_io( output: Optional[Output] = None, error_output: Optional[Output] = None, ) -> IO: - io = super(Application, self).create_io(input, output, error_output) + io = super().create_io(input, output, error_output) # Set our own CLI styles formatter = io.output.formatter @@ -265,7 +265,7 @@ def configure_env( env = env_manager.create_venv(io) if env.is_venv() and io.is_verbose(): - io.write_line("Using virtualenv: {}".format(env.path)) + io.write_line(f"Using virtualenv: {env.path}") command.set_env(env) diff --git a/poetry/inspection/info.py b/poetry/inspection/info.py index fbc96b40015..ae5f59bd63f 100644 --- a/poetry/inspection/info.py +++ b/poetry/inspection/info.py @@ -47,9 +47,7 @@ def __init__( reasons = ( "Unable to determine package info for path: {}".format(str(path)), ) + reasons - super(PackageInfoError, self).__init__( - "\n\n".join(str(msg).strip() for msg in reasons if msg) - ) + super().__init__("\n\n".join(str(msg).strip() for msg in reasons if msg)) class PackageInfo: @@ -119,7 +117,7 @@ def load(cls, data: Dict[str, Optional[Union[str, List[str]]]]) -> "PackageInfo" @classmethod def _log(cls, msg: str, level: str = "info") -> None: """Internal helper method to log information.""" - getattr(logger, level)("{}: {}".format(cls.__name__, msg)) + getattr(logger, level)(f"{cls.__name__}: {msg}") def to_package( self, @@ -139,9 +137,7 @@ def to_package( if not self.version: # The version could not be determined, so we raise an error since it is mandatory. - raise RuntimeError( - "Unable to retrieve the package version for {}".format(name) - ) + raise RuntimeError(f"Unable to retrieve the package version for {name}") package = Package( name=name, @@ -329,7 +325,7 @@ def from_setup_files(cls, path: Path) -> "PackageInfo": requires += "\n" for extra_name, deps in result["extras_require"].items(): - requires += "[{}]\n".format(extra_name) + requires += f"[{extra_name}]\n" for dep in deps: requires += dep + "\n" @@ -462,7 +458,7 @@ def _pep517_metadata(cls, path: Path) -> "PackageInfo": "install", "--disable-pip-version-check", "--ignore-installed", - *PEP517_META_BUILD_DEPS + *PEP517_META_BUILD_DEPS, ) venv.run( "python", @@ -475,7 +471,7 @@ def _pep517_metadata(cls, path: Path) -> "PackageInfo": except EnvCommandError as e: # something went wrong while attempting pep517 metadata build # fallback to egg_info if setup.py available - cls._log("PEP517 build failed: {}".format(e), level="debug") + cls._log(f"PEP517 build failed: {e}", level="debug") setup_py = path / "setup.py" if not setup_py.exists(): raise PackageInfoError( @@ -497,9 +493,7 @@ def _pep517_metadata(cls, path: Path) -> "PackageInfo": os.chdir(cwd.as_posix()) if info: - cls._log( - "Falling back to parsed setup.py file for {}".format(path), "debug" - ) + cls._log(f"Falling back to parsed setup.py file for {path}", "debug") return info # if we reach here, everything has failed and all hope is lost diff --git a/poetry/installation/authenticator.py b/poetry/installation/authenticator.py index 84ba26c027e..8c5e1d01222 100644 --- a/poetry/installation/authenticator.py +++ b/poetry/installation/authenticator.py @@ -24,7 +24,7 @@ logger = logging.getLogger() -class Authenticator(object): +class Authenticator: def __init__(self, config: "Config", io: Optional["IO"] = None) -> None: self._config = config self._io = io @@ -92,9 +92,7 @@ def request(self, method: str, url: str, **kwargs: Any) -> requests.Response: if not is_last_attempt: attempt += 1 delay = 0.5 * attempt - self._log( - "Retrying HTTP request in {} seconds.".format(delay), level="debug" - ) + self._log(f"Retrying HTTP request in {delay} seconds.", level="debug") time.sleep(delay) continue @@ -141,9 +139,7 @@ def _get_credentials_for_netloc_from_config( credentials = (None, None) for repository_name in self._config.get("repositories", []): - repository_config = self._config.get( - "repositories.{}".format(repository_name) - ) + repository_config = self._config.get(f"repositories.{repository_name}") if not repository_config: continue diff --git a/poetry/installation/chef.py b/poetry/installation/chef.py index 4f373b8752a..ce095a03d5c 100644 --- a/poetry/installation/chef.py +++ b/poetry/installation/chef.py @@ -80,7 +80,7 @@ def get_cached_archives_for_link(self, link: Link) -> List[Link]: archive_types = ["whl", "tar.gz", "tar.bz2", "bz2", "zip"] links = [] for archive_type in archive_types: - for archive in cache_dir.glob("*.{}".format(archive_type)): + for archive in cache_dir.glob(f"*.{archive_type}"): links.append(Link(archive.as_uri())) return links diff --git a/poetry/installation/chooser.py b/poetry/installation/chooser.py index 10c89c1ce6d..0069a50a47f 100644 --- a/poetry/installation/chooser.py +++ b/poetry/installation/chooser.py @@ -17,11 +17,11 @@ class InvalidWheelName(Exception): pass -class Wheel(object): +class Wheel: def __init__(self, filename: str) -> None: wheel_info = wheel_file_re.match(filename) if not wheel_info: - raise InvalidWheelName("{} is not a valid wheel filename.".format(filename)) + raise InvalidWheelName(f"{filename} is not a valid wheel filename.") self.filename = filename self.name = wheel_info.group("name").replace("_", "-") @@ -70,16 +70,12 @@ def choose_for(self, package: Package) -> Link: links.append(link) if not links: - raise RuntimeError( - "Unable to find installation candidates for {}".format(package) - ) + raise RuntimeError(f"Unable to find installation candidates for {package}") # Get the best link chosen = max(links, key=lambda link: self._sort_key(package, link)) if not chosen: - raise RuntimeError( - "Unable to find installation candidates for {}".format(package) - ) + raise RuntimeError(f"Unable to find installation candidates for {package}") return chosen diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index 89ab57a7cf5..5e2daa012eb 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import division - import itertools import os import threading @@ -44,7 +41,7 @@ from .operations import OperationTypes -class Executor(object): +class Executor: def __init__( self, env: "Env", @@ -296,7 +293,7 @@ def _do_execute_operation(self, operation: "OperationTypes") -> int: return 0 - result = getattr(self, "_execute_{}".format(method))(operation) + result = getattr(self, f"_execute_{method}")(operation) if result != 0: return result @@ -431,9 +428,7 @@ def _display_summary(self, operations: List["OperationTypes"]) -> None: "" if updates == 1 else "s", uninstalls, "" if uninstalls == 1 else "s", - ", {} skipped".format(skipped) - if skipped and self._verbose - else "", + f", {skipped} skipped" if skipped and self._verbose else "", ) ) self._io.write_line("") @@ -638,7 +633,7 @@ def _download_link(self, operation: Union[Install, Update], link: Link) -> Link: archive_hash = "sha256:" + FileDependency(package.name, archive).hash() if archive_hash not in {f["hash"] for f in package.files}: raise RuntimeError( - "Invalid hash for {} using archive {}".format(package, archive.name) + f"Invalid hash for {package} using archive {archive.name}" ) return archive diff --git a/poetry/installation/installer.py b/poetry/installation/installer.py index bc254c135e3..0b91efa35f6 100644 --- a/poetry/installation/installer.py +++ b/poetry/installation/installer.py @@ -200,7 +200,7 @@ def _do_refresh(self) -> int: # Checking extras for extra in self._extras: if extra not in self._package.extras: - raise ValueError("Extra [{}] is not specified.".format(extra)) + raise ValueError(f"Extra [{extra}] is not specified.") locked_repository = self._locker.locked_repository(True) solver = Solver( @@ -237,7 +237,7 @@ def _do_install(self, local_repo: Repository) -> int: # Checking extras for extra in self._extras: if extra not in self._package.extras: - raise ValueError("Extra [{}] is not specified.".format(extra)) + raise ValueError(f"Extra [{extra}] is not specified.") self._io.write_line("Updating dependencies") solver = Solver( @@ -267,7 +267,7 @@ def _do_install(self, local_repo: Repository) -> int: for extra in self._extras: if extra not in self._locker.lock_data.get("extras", {}): - raise ValueError("Extra [{}] is not specified.".format(extra)) + raise ValueError(f"Extra [{extra}] is not specified.") # If we are installing from lock # Filter the operations by comparing it with what is @@ -378,7 +378,7 @@ def _execute(self, operations: List["OperationTypes"]) -> int: "" if updates == 1 else "s", uninstalls, "" if uninstalls == 1 else "s", - ", {} skipped".format(skipped) + f", {skipped} skipped" if skipped and self.is_verbose() else "", ) @@ -397,7 +397,7 @@ def _execute_operation(self, operation: Operation) -> None: """ method = operation.job_type - getattr(self, "_execute_{}".format(method))(operation) + getattr(self, f"_execute_{method}")(operation) def _execute_install(self, operation: Install) -> None: if operation.skipped: diff --git a/poetry/installation/pip_installer.py b/poetry/installation/pip_installer.py index bf253367fc3..9c70f338afe 100644 --- a/poetry/installation/pip_installer.py +++ b/poetry/installation/pip_installer.py @@ -134,14 +134,14 @@ def run(self, *args: Any, **kwargs: Any) -> str: def requirement(self, package: "Package", formatted: bool = False) -> str: if formatted and not package.source_type: - req = "{}=={}".format(package.name, package.version) + req = f"{package.name}=={package.version}" for f in package.files: hash_type = "sha256" h = f["hash"] if ":" in h: hash_type, h = h.split(":") - req += " --hash {}:{}".format(hash_type, h) + req += f" --hash {hash_type}:{h}" req += "\n" @@ -169,14 +169,12 @@ def requirement(self, package: "Package", formatted: bool = False) -> str: return req if package.source_type == "url": - return "{}#egg={}".format(package.source_url, package.name) + return f"{package.source_url}#egg={package.name}" - return "{}=={}".format(package.name, package.version) + return f"{package.name}=={package.version}" def create_temporary_requirement(self, package: "Package") -> str: - fd, name = tempfile.mkstemp( - "reqs.txt", "{}-{}".format(package.name, package.version) - ) + fd, name = tempfile.mkstemp("reqs.txt", f"{package.name}-{package.version}") try: os.write(fd, encode(self.requirement(package, formatted=True))) diff --git a/poetry/json/__init__.py b/poetry/json/__init__.py index d65edd6106a..ad4eafed7a9 100644 --- a/poetry/json/__init__.py +++ b/poetry/json/__init__.py @@ -1,7 +1,6 @@ import json import os -from io import open from typing import List import jsonschema @@ -16,10 +15,10 @@ class ValidationError(ValueError): def validate_object(obj: dict, schema_name: str) -> List[str]: - schema = os.path.join(SCHEMA_DIR, "{}.json".format(schema_name)) + schema = os.path.join(SCHEMA_DIR, f"{schema_name}.json") if not os.path.exists(schema): - raise ValueError("Schema {} does not exist.".format(schema_name)) + raise ValueError(f"Schema {schema_name} does not exist.") with open(schema, encoding="utf-8") as f: schema = json.loads(f.read()) diff --git a/poetry/layouts/layout.py b/poetry/layouts/layout.py index ed3b7b45777..dfc2e609c95 100644 --- a/poetry/layouts/layout.py +++ b/poetry/layouts/layout.py @@ -14,7 +14,7 @@ from poetry.core.pyproject.toml import PyProjectTOML -TESTS_DEFAULT = u"""from {package_name} import __version__ +TESTS_DEFAULT = """from {package_name} import __version__ def test_version(): @@ -51,7 +51,7 @@ def test_version(): BUILD_SYSTEM_MAX_VERSION: Optional[str] = None -class Layout(object): +class Layout: def __init__( self, project: str, @@ -146,7 +146,7 @@ def _create_readme(self, path: "Path") -> None: def _create_tests(self, path: "Path") -> None: tests = path / "tests" tests_init = tests / "__init__.py" - tests_default = tests / "test_{}.py".format(self._package_name) + tests_default = tests / f"test_{self._package_name}.py" tests.mkdir() tests_init.touch(exist_ok=False) diff --git a/poetry/layouts/src.py b/poetry/layouts/src.py index 3d844b4a4fc..4f43263ef93 100644 --- a/poetry/layouts/src.py +++ b/poetry/layouts/src.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from typing import TYPE_CHECKING from .layout import Layout @@ -8,7 +6,7 @@ if TYPE_CHECKING: from pathlib import Path -DEFAULT = u"""__version__ = '{version}' +DEFAULT = """__version__ = '{version}' """ diff --git a/poetry/layouts/standard.py b/poetry/layouts/standard.py index e0a1db99204..9971d4aed8d 100644 --- a/poetry/layouts/standard.py +++ b/poetry/layouts/standard.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from typing import TYPE_CHECKING from .layout import Layout @@ -7,7 +5,7 @@ if TYPE_CHECKING: from pathlib import Path -DEFAULT = u"""__version__ = '{version}' +DEFAULT = """__version__ = '{version}' """ diff --git a/poetry/mixology/assignment.py b/poetry/mixology/assignment.py index 836c1895b66..e0079d46b1a 100644 --- a/poetry/mixology/assignment.py +++ b/poetry/mixology/assignment.py @@ -25,7 +25,7 @@ def __init__( index: int, cause: Optional["Incompatibility"] = None, ) -> None: - super(Assignment, self).__init__(dependency, is_positive) + super().__init__(dependency, is_positive) self._decision_level = decision_level self._index = index diff --git a/poetry/mixology/failure.py b/poetry/mixology/failure.py index 55a163c0e5f..bf84348ced1 100644 --- a/poetry/mixology/failure.py +++ b/poetry/mixology/failure.py @@ -65,9 +65,7 @@ def write(self) -> str: if isinstance(self._root.cause, ConflictCause): self._visit(self._root, {}) else: - self._write( - self._root, "Because {}, version solving failed.".format(self._root) - ) + self._write(self._root, f"Because {self._root}, version solving failed.") padding = ( 0 @@ -89,7 +87,7 @@ def write(self) -> str: number = line[-1] if number is not None: - message = "({})".format(number).ljust(padding) + message + message = f"({number})".ljust(padding) + message else: message = " " * padding + message @@ -165,7 +163,7 @@ def _visit( self._visit(second, details_for_cause) self._write( incompatibility, - "Thus, {}.".format(incompatibility_string), + f"Thus, {incompatibility_string}.", numbered=numbered, ) else: diff --git a/poetry/mixology/incompatibility.py b/poetry/mixology/incompatibility.py index 0a848d1374c..e195e08b7ab 100644 --- a/poetry/mixology/incompatibility.py +++ b/poetry/mixology/incompatibility.py @@ -105,11 +105,9 @@ def external_incompatibilities( """ if isinstance(self._cause, ConflictCause): cause: ConflictCause = self._cause - for incompatibility in cause.conflict.external_incompatibilities: - yield incompatibility + yield from cause.conflict.external_incompatibilities - for incompatibility in cause.other.external_incompatibilities: - yield incompatibility + yield from cause.other.external_incompatibilities else: yield self @@ -136,7 +134,7 @@ def __str__(self) -> str: cause: PythonCause = self._cause text = "{} requires ".format(self._terse(self._terms[0], allow_every=True)) - text += "Python {}".format(cause.python_version) + text += f"Python {cause.python_version}" return text elif isinstance(self._cause, PlatformCause): @@ -145,7 +143,7 @@ def __str__(self) -> str: cause: PlatformCause = self._cause text = "{} requires ".format(self._terse(self._terms[0], allow_every=True)) - text += "platform {}".format(cause.platform) + text += f"platform {cause.platform}" return text elif isinstance(self._cause, NoVersionsCause): @@ -201,7 +199,7 @@ def __str__(self) -> str: else self._terse(term2) ) - return "{} is incompatible with {}".format(package1, package2) + return f"{package1} is incompatible with {package2}" else: return "either {} or {}".format( self._terse(term1), self._terse(term2) @@ -305,14 +303,14 @@ def _try_requires_both( else: buffer.append("requires") - buffer.append(" both {}".format(this_negatives)) + buffer.append(f" both {this_negatives}") if this_line is not None: - buffer.append(" ({})".format(this_line)) + buffer.append(f" ({this_line})") - buffer.append(" and {}".format(other_negatives)) + buffer.append(f" and {other_negatives}") if other_line is not None: - buffer.append(" ({})".format(other_line)) + buffer.append(f" ({other_line})") return "".join(buffer) @@ -361,7 +359,7 @@ def _try_requires_through( buffer = [] if len(prior_positives) > 1: prior_string = " or ".join([self._terse(term) for term in prior_positives]) - buffer.append("if {} then ".format(prior_string)) + buffer.append(f"if {prior_string} then ") else: if isinstance(prior.cause, DependencyCause): verb = "depends on" @@ -374,7 +372,7 @@ def _try_requires_through( buffer.append(self._terse(prior_negative)) if prior_line is not None: - buffer.append(" ({})".format(prior_line)) + buffer.append(f" ({prior_line})") buffer.append(" which ") @@ -390,7 +388,7 @@ def _try_requires_through( ) if latter_line is not None: - buffer.append(" ({})".format(latter_line)) + buffer.append(f" ({latter_line})") return "".join(buffer) @@ -423,7 +421,7 @@ def _try_requires_forbidden( buffer = [] if len(positives) > 1: prior_string = " or ".join([self._terse(term) for term in positives]) - buffer.append("if {} then ".format(prior_string)) + buffer.append(f"if {prior_string} then ") else: buffer.append(self._terse(positives[0], allow_every=True)) if isinstance(prior.cause, DependencyCause): @@ -433,11 +431,11 @@ def _try_requires_forbidden( buffer.append(self._terse(latter.terms[0]) + " ") if prior_line is not None: - buffer.append("({}) ".format(prior_line)) + buffer.append(f"({prior_line}) ") if isinstance(latter.cause, PythonCause): cause: PythonCause = latter.cause - buffer.append("which requires Python {}".format(cause.python_version)) + 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): @@ -446,13 +444,13 @@ def _try_requires_forbidden( buffer.append("which is forbidden") if latter_line is not None: - buffer.append(" ({})".format(latter_line)) + buffer.append(f" ({latter_line})") return "".join(buffer) def _terse(self, term: Term, allow_every: bool = False) -> str: if allow_every and term.constraint.is_any(): - return "every version of {}".format(term.dependency.complete_name) + return f"every version of {term.dependency.complete_name}" if term.dependency.is_root: return term.dependency.pretty_name diff --git a/poetry/mixology/partial_solution.py b/poetry/mixology/partial_solution.py index f06c65de792..96a761d4438 100644 --- a/poetry/mixology/partial_solution.py +++ b/poetry/mixology/partial_solution.py @@ -199,7 +199,7 @@ def satisfier(self, term: Term) -> Assignment: if assigned_term.satisfies(term): return assignment - raise RuntimeError("[BUG] {} is not satisfied.".format(term)) + raise RuntimeError(f"[BUG] {term} is not satisfied.") def satisfies(self, term: Term) -> bool: return self.relation(term) == SetRelation.SUBSET diff --git a/poetry/mixology/term.py b/poetry/mixology/term.py index 3b76578ba14..9542689e033 100644 --- a/poetry/mixology/term.py +++ b/poetry/mixology/term.py @@ -10,7 +10,7 @@ from poetry.core.semver.helpers import VersionTypes -class Term(object): +class Term: """ A statement about a package which is true or false for a given selection of package versions. @@ -52,9 +52,7 @@ def relation(self, other: "Term") -> int: allowed by this term and another. """ if self.dependency.complete_name != other.dependency.complete_name: - raise ValueError( - "{} should refer to {}".format(other, self.dependency.complete_name) - ) + raise ValueError(f"{other} should refer to {self.dependency.complete_name}") other_constraint = other.constraint @@ -116,9 +114,7 @@ def intersect(self, other: "Term") -> Optional["Term"]: allowed by both this term and another """ if self.dependency.complete_name != other.dependency.complete_name: - raise ValueError( - "{} should refer to {}".format(other, self.dependency.complete_name) - ) + raise ValueError(f"{other} should refer to {self.dependency.complete_name}") if self._compatible_dependency(other.dependency): if self.is_positive() != other.is_positive(): diff --git a/poetry/mixology/version_solver.py b/poetry/mixology/version_solver.py index beb60c59dd7..c9025964440 100644 --- a/poetry/mixology/version_solver.py +++ b/poetry/mixology/version_solver.py @@ -193,7 +193,7 @@ def _resolve_conflict(self, incompatibility: Incompatibility) -> Incompatibility .. _conflict resolution: https://github.com/dart-lang/pub/tree/master/doc/solver.md#conflict-resolution """ - self._log("conflict: {}".format(incompatibility)) + self._log(f"conflict: {incompatibility}") new_incompatibility = False while not incompatibility.is_failure(): @@ -308,10 +308,8 @@ def _resolve_conflict(self, incompatibility: Incompatibility) -> Incompatibility bang, most_recent_term, partially, most_recent_satisfier ) ) - self._log( - '{} which is caused by "{}"'.format(bang, most_recent_satisfier.cause) - ) - self._log("{} thus: {}".format(bang, incompatibility)) + self._log(f'{bang} which is caused by "{most_recent_satisfier.cause}"') + self._log(f"{bang} thus: {incompatibility}") raise SolveFailure(incompatibility) @@ -429,7 +427,7 @@ def _result(self) -> SolverResult: ) def _add_incompatibility(self, incompatibility: Incompatibility) -> None: - self._log("fact: {}".format(incompatibility)) + self._log(f"fact: {incompatibility}") for term in incompatibility.terms: if term.dependency.complete_name not in self._incompatibilities: diff --git a/poetry/packages/dependency_package.py b/poetry/packages/dependency_package.py index cc50e8d94dc..e215db5b3b2 100644 --- a/poetry/packages/dependency_package.py +++ b/poetry/packages/dependency_package.py @@ -6,7 +6,7 @@ from poetry.core.packages.package import Package -class DependencyPackage(object): +class DependencyPackage: def __init__(self, dependency: Dependency, package: Package) -> None: self._dependency = dependency self._package = package @@ -33,7 +33,7 @@ def __getattr__(self, name: str) -> Any: def __setattr__(self, key: str, value: Any) -> None: if key in {"_dependency", "_package"}: - return super(DependencyPackage, self).__setattr__(key, value) + return super().__setattr__(key, value) setattr(self._package, key, value) diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index 3b1436bcf52..03f6e677eb9 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -43,7 +43,7 @@ logger = logging.getLogger(__name__) -class Locker(object): +class Locker: _VERSION = "1.1" @@ -471,7 +471,7 @@ def _get_lock_data(self) -> "TOMLDocument": try: lock_data = self._lock.read() except TOMLKitError as e: - raise RuntimeError("Unable to read the lock file ({}).".format(e)) + raise RuntimeError(f"Unable to read the lock file ({e}).") lock_version = Version.parse(lock_data["metadata"].get("lock-version", "1.0")) current_version = Version.parse(self._VERSION) diff --git a/poetry/packages/package_collection.py b/poetry/packages/package_collection.py index 00e271dd6c6..3386aee855b 100644 --- a/poetry/packages/package_collection.py +++ b/poetry/packages/package_collection.py @@ -21,7 +21,7 @@ def __init__( if packages is None: packages = [] - super(PackageCollection, self).__init__() + super().__init__() for package in packages: self.append(package) @@ -32,4 +32,4 @@ def append(self, package: Union["Package", DependencyPackage]) -> None: package = DependencyPackage(self._dependency, package) - return super(PackageCollection, self).append(package) + return super().append(package) diff --git a/poetry/plugins/base_plugin.py b/poetry/plugins/base_plugin.py index 9e287c8178c..de42071b4dc 100644 --- a/poetry/plugins/base_plugin.py +++ b/poetry/plugins/base_plugin.py @@ -1,4 +1,4 @@ -class BasePlugin(object): +class BasePlugin: """ Base class for all plugin types """ diff --git a/poetry/plugins/plugin_manager.py b/poetry/plugins/plugin_manager.py index 6f9e8f49ba2..c24ad2b20c1 100644 --- a/poetry/plugins/plugin_manager.py +++ b/poetry/plugins/plugin_manager.py @@ -11,7 +11,7 @@ logger = logging.getLogger(__name__) -class PluginManager(object): +class PluginManager: """ This class registers and activates plugins. """ @@ -31,7 +31,7 @@ def load_plugins(self): # type: () -> None self._load_plugin_entrypoint(entrypoint) def get_plugin_entry_points(self) -> List[entrypoints.EntryPoint]: - return entrypoints.get_group_all("poetry.{}".format(self._type)) + return entrypoints.get_group_all(f"poetry.{self._type}") def add_plugin(self, plugin): # type: (Plugin) -> None if not isinstance(plugin, (Plugin, ApplicationPlugin)): @@ -48,7 +48,7 @@ def activate(self, *args, **kwargs): def _load_plugin_entrypoint( self, entrypoint ): # type: (entrypoints.EntryPoint) -> None - logger.debug("Loading the {} plugin".format(entrypoint.name)) + logger.debug(f"Loading the {entrypoint.name} plugin") plugin = entrypoint.load() diff --git a/poetry/publishing/publisher.py b/poetry/publishing/publisher.py index 2c9d1e14d8a..cfa5c7d085d 100644 --- a/poetry/publishing/publisher.py +++ b/poetry/publishing/publisher.py @@ -52,17 +52,15 @@ def publish( repository_name = "pypi" else: # Retrieving config information - url = self._poetry.config.get("repositories.{}.url".format(repository_name)) + url = self._poetry.config.get(f"repositories.{repository_name}.url") if url is None: - raise RuntimeError( - "Repository {} is not defined".format(repository_name) - ) + raise RuntimeError(f"Repository {repository_name} is not defined") if not (username and password): # Check if we have a token first token = self._password_manager.get_pypi_token(repository_name) if token: - logger.debug("Found an API token for {}.".format(repository_name)) + logger.debug(f"Found an API token for {repository_name}.") username = "__token__" password = token else: diff --git a/poetry/publishing/uploader.py b/poetry/publishing/uploader.py index 787f112f49c..798b2533af8 100644 --- a/poetry/publishing/uploader.py +++ b/poetry/publishing/uploader.py @@ -39,8 +39,8 @@ class UploadError(Exception): def __init__(self, error: Union[ConnectionError, HTTPError, str]) -> None: if isinstance(error, HTTPError): - message = "HTTP Error {}: {}".format( - error.response.status_code, error.response.reason + message = ( + f"HTTP Error {error.response.status_code}: {error.response.reason}" ) elif isinstance(error, ConnectionError): message = ( @@ -49,7 +49,7 @@ def __init__(self, error: Union[ConnectionError, HTTPError, str]) -> None: ) else: message = str(error) - super(UploadError, self).__init__(message) + super().__init__(message) class Uploader: @@ -82,14 +82,10 @@ def files(self) -> List[Path]: wheels = list( dist.glob( - "{}-{}-*.whl".format( - escape_name(self._package.pretty_name), escape_version(version) - ) + f"{escape_name(self._package.pretty_name)}-{escape_version(version)}-*.whl" ) ) - tars = list( - dist.glob("{}-{}.tar.gz".format(self._package.pretty_name, version)) - ) + tars = list(dist.glob(f"{self._package.pretty_name}-{version}.tar.gz")) return sorted(wheels + tars) @@ -263,9 +259,7 @@ def _upload_file( ) encoder = MultipartEncoder(data_to_send) bar = ProgressBar(self._io, max=encoder.len) - bar.set_format( - " - Uploading {0} %percent%%".format(file.name) - ) + bar.set_format(f" - Uploading {file.name} %percent%%") monitor = MultipartEncoderMonitor( encoder, lambda monitor: bar.set_progress(monitor.bytes_read) ) @@ -284,17 +278,13 @@ def _upload_file( ) if dry_run or 200 <= resp.status_code < 300: bar.set_format( - " - Uploading {0} %percent%%".format( - file.name - ) + f" - Uploading {file.name} %percent%%" ) bar.finish() elif resp.status_code == 301: if self._io.output.is_decorated(): self._io.overwrite( - " - Uploading {0} {1}".format( - file.name, "FAILED" - ) + f" - Uploading {file.name} FAILED" ) raise UploadError( "Redirects are not supported. " @@ -303,9 +293,7 @@ def _upload_file( except (requests.ConnectionError, requests.HTTPError) as e: if self._io.output.is_decorated(): self._io.overwrite( - " - Uploading {0} {1}".format( - file.name, "FAILED" - ) + f" - Uploading {file.name} FAILED" ) raise UploadError(e) finally: @@ -318,12 +306,13 @@ def _register(self, session: requests.Session, url: str) -> requests.Response: Register a package to a repository. """ dist = self._poetry.file.parent / "dist" - file = dist / "{}-{}.tar.gz".format( - self._package.name, normalize_version(self._package.version.text) + file = ( + dist + / f"{self._package.name}-{normalize_version(self._package.version.text)}.tar.gz" ) if not file.exists(): - raise RuntimeError('"{0}" does not exist.'.format(file.name)) + raise RuntimeError(f'"{file.name}" does not exist.') data = self.post_data(file) data.update({":action": "submit", "protocol_version": "1"}) diff --git a/poetry/puzzle/exceptions.py b/poetry/puzzle/exceptions.py index 5c032da5970..4eaf46fda40 100644 --- a/poetry/puzzle/exceptions.py +++ b/poetry/puzzle/exceptions.py @@ -6,7 +6,7 @@ class SolverProblemError(Exception): def __init__(self, error: Exception) -> None: self._error = error - super(SolverProblemError, self).__init__(str(error)) + super().__init__(str(error)) @property def error(self) -> Exception: diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index 3d384a6697d..c7427161c2c 100644 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -49,7 +49,7 @@ class Indicator(ProgressIndicator): def _formatter_elapsed(self) -> str: elapsed = time.time() - self._start_time - return "{:.1f}s".format(elapsed) + return f"{elapsed:.1f}s" class Provider: @@ -199,7 +199,7 @@ def get_package_from_vcs( name: Optional[str] = None, ) -> Package: if vcs != "git": - raise ValueError("Unsupported VCS dependency {}".format(vcs)) + raise ValueError(f"Unsupported VCS dependency {vcs}") tmp_dir = Path( mkdtemp(prefix="pypoetry-git-{}".format(url.split("/")[-1].rstrip(".git"))) @@ -266,7 +266,7 @@ def get_package_from_file(cls, file_path: Path) -> Package: ) except PackageInfoError: raise RuntimeError( - "Unable to determine package info from path: {}".format(file_path) + f"Unable to determine package info from path: {file_path}" ) return package @@ -546,7 +546,7 @@ def complete_package(self, package: DependencyPackage) -> DependencyPackage: dependencies.append(deps[0]) continue - self.debug("Duplicate dependencies for {}".format(dep_name)) + self.debug(f"Duplicate dependencies for {dep_name}") # Regrouping by constraint by_constraint = dict() diff --git a/poetry/puzzle/solver.py b/poetry/puzzle/solver.py index 605eea303b8..b5a413f15e0 100644 --- a/poetry/puzzle/solver.py +++ b/poetry/puzzle/solver.py @@ -85,9 +85,7 @@ def solve(self, use_latest: List[str] = None) -> List["OperationTypes"]: ) ) self._provider.debug( - "Resolved with overrides: {}".format( - ", ".join("({})".format(b) for b in self._overrides) - ) + f"Resolved with overrides: {', '.join(f'({b})' for b in self._overrides)}" ) operations = [] @@ -221,7 +219,7 @@ def solve_in_compatibility_mode( for override in overrides: self._provider.debug( "Retrying dependency resolution " - "with the following overrides ({}).".format(override) + f"with the following overrides ({override})." ) self._provider.set_overrides(override) _packages, _depths = self._solve(use_latest=use_latest) @@ -293,7 +291,7 @@ def _solve(self, use_latest: List[str] = None) -> Tuple[List[Package], List[int] return final_packages, depths -class DFSNode(object): +class DFSNode: def __init__(self, id: Tuple[str, str, bool], name: str, base_name: str) -> None: self.id = id self.name = name @@ -408,7 +406,7 @@ def __init__( self.category = dep.category self.optional = dep.is_optional() - super(PackageNode, self).__init__( + super().__init__( (package.complete_name, self.category, self.optional), package.complete_name, package.name, diff --git a/poetry/repositories/base_repository.py b/poetry/repositories/base_repository.py index 0e03b8446ce..3d5e5c5ebcd 100644 --- a/poetry/repositories/base_repository.py +++ b/poetry/repositories/base_repository.py @@ -8,7 +8,7 @@ from poetry.core.packages.package import Package -class BaseRepository(object): +class BaseRepository: def __init__(self) -> None: self._packages = [] diff --git a/poetry/repositories/legacy_repository.py b/poetry/repositories/legacy_repository.py index 8446b31ee99..60fd8c8b6fa 100644 --- a/poetry/repositories/legacy_repository.py +++ b/poetry/repositories/legacy_repository.py @@ -330,7 +330,7 @@ def package( return self._packages[index] except ValueError: - package = super(LegacyRepository, self).package(name, version, extras) + package = super().package(name, version, extras) package._source_type = "legacy" package._source_url = self._url package._source_reference = self.name @@ -347,7 +347,7 @@ def find_links_for_package(self, package: Package) -> List[Link]: def _get_release_info(self, name: str, version: str) -> dict: page = self._get("/{}/".format(canonicalize_name(name).replace(".", "-"))) if page is None: - raise PackageNotFound('No package named "{}"'.format(name)) + raise PackageNotFound(f'No package named "{name}"') data = PackageInfo( name=name, @@ -377,7 +377,7 @@ def _get_release_info(self, name: str, version: str) -> dict: ): urls["sdist"].append(link.url) - file_hash = "{}:{}".format(link.hash_name, link.hash) if link.hash else None + file_hash = f"{link.hash_name}:{link.hash}" if link.hash else None if not link.hash or ( link.hash_name not in ("sha256", "sha384", "sha512") @@ -425,7 +425,7 @@ def _get(self, endpoint: str) -> Optional[Page]: response = self.session.get(url) if response.status_code in (401, 403): self._log( - "Authorization error accessing {url}".format(url=url), + f"Authorization error accessing {url}", level="warning", ) return diff --git a/poetry/repositories/pool.py b/poetry/repositories/pool.py index 69d933296ac..251ff92506b 100644 --- a/poetry/repositories/pool.py +++ b/poetry/repositories/pool.py @@ -32,7 +32,7 @@ def __init__( self._ignore_repository_names = ignore_repository_names - super(Pool, self).__init__() + super().__init__() @property def repositories(self) -> List[Repository]: @@ -53,7 +53,7 @@ def repository(self, name: str) -> Repository: if name in self._lookup: return self._repositories[self._lookup[name]] - raise ValueError('Repository "{}" does not exist.'.format(name)) + raise ValueError(f'Repository "{name}" does not exist.') def add_repository( self, repository: Repository, default: bool = False, secondary: bool = False @@ -125,7 +125,7 @@ def package( and repository not in self._lookup and not self._ignore_repository_names ): - raise ValueError('Repository "{}" does not exist.'.format(repository)) + raise ValueError(f'Repository "{repository}" does not exist.') if repository is not None and not self._ignore_repository_names: try: @@ -144,7 +144,7 @@ def package( return package - raise PackageNotFound("Package {} ({}) not found.".format(name, version)) + raise PackageNotFound(f"Package {name} ({version}) not found.") def find_packages(self, dependency: "Dependency") -> List["Package"]: repository = dependency.source_name @@ -156,7 +156,7 @@ def find_packages(self, dependency: "Dependency") -> List["Package"]: and repository not in self._lookup and not self._ignore_repository_names ): - raise ValueError('Repository "{}" does not exist.'.format(repository)) + raise ValueError(f'Repository "{repository}" does not exist.') if repository is not None and not self._ignore_repository_names: return self.repository(repository).find_packages(dependency) diff --git a/poetry/repositories/pypi_repository.py b/poetry/repositories/pypi_repository.py index 0a5d02e1f10..1c7b7be316a 100644 --- a/poetry/repositories/pypi_repository.py +++ b/poetry/repositories/pypi_repository.py @@ -54,7 +54,7 @@ def __init__( disable_cache: bool = False, fallback: bool = True, ) -> None: - super(PyPiRepository, self).__init__(url.rstrip("/") + "/simple/") + super().__init__(url.rstrip("/") + "/simple/") self._base_url = url self._disable_cache = disable_cache @@ -211,9 +211,9 @@ def get_package_info(self, name: str) -> dict: ) def _get_package_info(self, name: str) -> dict: - data = self._get("pypi/{}/json".format(name)) + data = self._get(f"pypi/{name}/json") if data is None: - raise PackageNotFound("Package [{}] not found.".format(name)) + raise PackageNotFound(f"Package [{name}] not found.") return data @@ -230,24 +230,24 @@ def get_release_info(self, name: str, version: str) -> "PackageInfo": return PackageInfo.load(self._get_release_info(name, version)) cached = self._cache.remember_forever( - "{}:{}".format(name, version), lambda: self._get_release_info(name, version) + f"{name}:{version}", lambda: self._get_release_info(name, version) ) cache_version = cached.get("_cache_version", "0.0.0") if parse_constraint(cache_version) != self.CACHE_VERSION: # The cache must be updated self._log( - "The cache for {} {} is outdated. Refreshing.".format(name, version), + f"The cache for {name} {version} is outdated. Refreshing.", level="debug", ) cached = self._get_release_info(name, version) - self._cache.forever("{}:{}".format(name, version), cached) + self._cache.forever(f"{name}:{version}", cached) return PackageInfo.load(cached) def find_links_for_package(self, package: Package) -> List[Link]: - json_data = self._get("pypi/{}/{}/json".format(package.name, package.version)) + json_data = self._get(f"pypi/{package.name}/{package.version}/json") if json_data is None: return [] @@ -261,11 +261,11 @@ def find_links_for_package(self, package: Package) -> List[Link]: def _get_release_info(self, name: str, version: str) -> dict: from poetry.inspection.info import PackageInfo - self._log("Getting info for {} ({}) from PyPI".format(name, version), "debug") + self._log(f"Getting info for {name} ({version}) from PyPI", "debug") - json_data = self._get("pypi/{}/{}/json".format(name, version)) + json_data = self._get(f"pypi/{name}/{version}/json") if json_data is None: - raise PackageNotFound("Package [{}] not found.".format(name)) + raise PackageNotFound(f"Package [{name}] not found.") info = json_data["info"] @@ -383,14 +383,14 @@ def _get_info_from_urls(self, urls: Dict[str, List[str]]) -> "PackageInfo": return info - py2_requires_dist = set( + py2_requires_dist = { Dependency.create_from_pep_508(r).to_pep_508() for r in info.requires_dist - ) - py3_requires_dist = set( + } + py3_requires_dist = { Dependency.create_from_pep_508(r).to_pep_508() for r in py3_info.requires_dist - ) + } base_requires_dist = py2_requires_dist & py3_requires_dist py2_only_requires_dist = py2_requires_dist - py3_requires_dist py3_only_requires_dist = py3_requires_dist - py2_requires_dist @@ -469,4 +469,4 @@ def _download(self, url: str, dest: str) -> None: return download_file(url, dest, session=self.session) def _log(self, msg: str, level: str = "info") -> None: - getattr(logger, level)("{}: {}".format(self._name, msg)) + getattr(logger, level)(f"{self._name}: {msg}") diff --git a/poetry/repositories/remote_repository.py b/poetry/repositories/remote_repository.py index a893b31ec4f..b07321dcd66 100644 --- a/poetry/repositories/remote_repository.py +++ b/poetry/repositories/remote_repository.py @@ -5,7 +5,7 @@ class RemoteRepository(Repository): def __init__(self, url: str) -> None: self._url = url - super(RemoteRepository, self).__init__() + super().__init__() @property def url(self) -> str: diff --git a/poetry/repositories/repository.py b/poetry/repositories/repository.py index 43894e4e4d4..c2a36c2dfe1 100644 --- a/poetry/repositories/repository.py +++ b/poetry/repositories/repository.py @@ -13,7 +13,7 @@ class Repository(BaseRepository): def __init__(self, packages: List["Package"] = None, name: str = None) -> None: - super(Repository, self).__init__() + super().__init__() self._name = name diff --git a/poetry/utils/_compat.py b/poetry/utils/_compat.py index 1ede84dcd3f..977c997ede3 100644 --- a/poetry/utils/_compat.py +++ b/poetry/utils/_compat.py @@ -46,6 +46,6 @@ def to_str(string): def list_to_shell_command(cmd): return " ".join( - '"{}"'.format(token) if " " in token and token[0] not in {"'", '"'} else token + f'"{token}"' if " " in token and token[0] not in {"'", '"'} else token for token in cmd ) diff --git a/poetry/utils/env.py b/poetry/utils/env.py index 933f9165210..3df76495acc 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -225,7 +225,7 @@ def _path_method_wrapper( return result else: results.append(result) - except (IOError, OSError): + except OSError: # TODO: Replace with PermissionError pass @@ -257,7 +257,7 @@ def find(self, path: Path, writable_only: bool = False) -> List[Path]: def __getattr__(self, item: str) -> Any: try: - return super(SitePackages, self).__getattribute__(item) + return super().__getattribute__(item) except AttributeError: return getattr(self.path, item) @@ -275,8 +275,8 @@ def __init__(self, e: CalledProcessError, input: Optional[str] = None) -> None: e.cmd, e.returncode, decode(e.output) ) if input: - message += "input was : {}".format(input) - super(EnvCommandError, self).__init__(message) + message += f"input was : {input}" + super().__init__(message) class NoCompatiblePythonVersionFound(EnvError): @@ -296,10 +296,10 @@ def __init__(self, expected: str, given: Optional[str] = None) -> None: 'via the "env use" command.' ) - super(NoCompatiblePythonVersionFound, self).__init__(message) + super().__init__(message) -class EnvManager(object): +class EnvManager: """ Environments manager """ @@ -324,9 +324,9 @@ def activate(self, python: str, io: IO) -> "Env": try: python_version = Version.parse(python) - python = "python{}".format(python_version.major) + python = f"python{python_version.major}" if python_version.precision > 1: - python += ".{}".format(python_version.minor) + python += f".{python_version.minor}" except ValueError: # Executable in PATH or full executable path pass @@ -348,7 +348,7 @@ def activate(self, python: str, io: IO) -> "Env": raise EnvCommandError(e) python_version = Version.parse(python_version.strip()) - minor = "{}.{}".format(python_version.major, python_version.minor) + minor = f"{python_version.major}.{python_version.minor}" patch = python_version.text create = False @@ -383,7 +383,7 @@ def activate(self, python: str, io: IO) -> "Env": # We need to recreate create = True - name = "{}-py{}".format(base_env_name, minor) + name = f"{base_env_name}-py{minor}" venv = venv_path / name # Create if needed @@ -482,7 +482,7 @@ def get(self, reload: bool = False) -> Union["VirtualEnv", "SystemEnv"]: else: venv_path = Path(venv_path) - name = "{}-py{}".format(base_env_name, python_minor.strip()) + name = f"{base_env_name}-py{python_minor.strip()}" venv = venv_path / name @@ -513,8 +513,7 @@ def list(self, name: Optional[str] = None) -> List["VirtualEnv"]: venv_path = Path(venv_path) env_list = [ - VirtualEnv(Path(p)) - for p in sorted(venv_path.glob("{}-py*".format(venv_name))) + VirtualEnv(Path(p)) for p in sorted(venv_path.glob(f"{venv_name}-py*")) ] venv = self._poetry.file.parent / ".venv" @@ -566,14 +565,14 @@ def remove(self, python: str) -> "Env": return venv raise ValueError( - 'Environment "{}" does not exist.'.format(python) + f'Environment "{python}" does not exist.' ) try: python_version = Version.parse(python) - python = "python{}".format(python_version.major) + python = f"python{python_version.major}" if python_version.precision > 1: - python += ".{}".format(python_version.minor) + python += f".{python_version.minor}" except ValueError: # Executable in PATH or full executable path pass @@ -595,15 +594,13 @@ def remove(self, python: str) -> "Env": raise EnvCommandError(e) python_version = Version.parse(python_version.strip()) - minor = "{}.{}".format(python_version.major, python_version.minor) + minor = f"{python_version.major}.{python_version.minor}" - name = "{}-py{}".format(base_env_name, minor) + name = f"{base_env_name}-py{minor}" venv = venv_path / name if not venv.exists(): - raise ValueError( - 'Environment "{}" does not exist.'.format(name) - ) + raise ValueError(f'Environment "{name}" does not exist.') if envs_file.exists(): envs = envs_file.read() @@ -698,7 +695,7 @@ def create_venv( ) ): if len(python_to_try) == 1: - if not parse_constraint("^{}.0".format(python_to_try)).allows_any( + if not parse_constraint(f"^{python_to_try}.0").allows_any( supported_python ): continue @@ -710,7 +707,7 @@ def create_venv( python = "python" + python_to_try if io.is_debug(): - io.write_line("Trying {}".format(python)) + io.write_line(f"Trying {python}") try: python_patch = decode( @@ -733,7 +730,7 @@ def create_venv( continue if supported_python.allows(Version.parse(python_patch)): - io.write_line("Using {} ({})".format(python, python_patch)) + io.write_line(f"Using {python} ({python_patch})") executable = python python_minor = ".".join(python_patch.split(".")[:2]) break @@ -747,7 +744,7 @@ def create_venv( venv = venv_path else: name = self.generate_env_name(name, str(cwd)) - name = "{}-py{}".format(name, python_minor.strip()) + name = f"{name}-py{python_minor.strip()}" venv = venv_path / name if not venv.exists(): @@ -788,7 +785,7 @@ def create_venv( flags=self._poetry.config.get("virtualenvs.options"), ) elif io.is_very_verbose(): - io.write_line("Virtualenv {} already exists.".format(name)) + io.write_line(f"Virtualenv {name} already exists.") # venv detection: # stdlib venv may symlink sys.executable, so we can't use realpath. @@ -846,7 +843,7 @@ def build_venv( for flag, value in flags.items(): if value is True: - args.append("--{}".format(flag)) + args.append(f"--{flag}") args.append(str(path)) @@ -896,10 +893,10 @@ def generate_env_name(cls, name: str, cwd: str) -> str: h = hashlib.sha256(encode(cwd)).digest() h = base64.urlsafe_b64encode(h).decode()[:8] - return "{}-{}".format(sanitized_name, h) + return f"{sanitized_name}-{h}" -class Env(object): +class Env: """ An abstract Python environment. """ @@ -1196,7 +1193,7 @@ def __eq__(self, other: "Env") -> bool: return other.__class__ == self.__class__ and other.path == self.path def __repr__(self) -> str: - return '{}("{}")'.format(self.__class__.__name__, self._path) + return f'{self.__class__.__name__}("{self._path}")' class SystemEnv(Env): @@ -1244,7 +1241,7 @@ def get_paths(self) -> Dict[str, str]: # headers is not a path returned by sysconfig.get_paths() continue - paths[key] = getattr(obj, "install_{}".format(key)) + paths[key] = getattr(obj, f"install_{key}") if site.check_enableusersite() and hasattr(obj, "install_usersite"): paths["usersite"] = getattr(obj, "install_usersite") @@ -1303,7 +1300,7 @@ class VirtualEnv(Env): """ def __init__(self, path: Path, base: Optional[Path] = None) -> None: - super(VirtualEnv, self).__init__(path, base) + super().__init__(path, base) # If base is None, it probably means this is # a virtualenv created from VIRTUAL_ENV. @@ -1388,7 +1385,7 @@ def is_sane(self) -> bool: def _run(self, cmd: List[str], **kwargs: Any) -> Optional[int]: kwargs["env"] = self.get_temp_environ(environ=kwargs.get("env")) - return super(VirtualEnv, self)._run(cmd, **kwargs) + return super()._run(cmd, **kwargs) def get_temp_environ( self, @@ -1415,7 +1412,7 @@ def get_temp_environ( def execute(self, bin: str, *args: str, **kwargs: Any) -> Optional[int]: kwargs["env"] = self.get_temp_environ(environ=kwargs.get("env")) - return super(VirtualEnv, self).execute(bin, *args, **kwargs) + return super().execute(bin, *args, **kwargs) @contextmanager def temp_environ(self) -> Iterator[None]: @@ -1437,7 +1434,7 @@ def __init__( if path is None: path = Path(sys.prefix) - super(NullEnv, self).__init__(path, base=base) + super().__init__(path, base=base) self._execute = execute self.executed = [] @@ -1449,13 +1446,13 @@ def _run(self, cmd: List[str], **kwargs: Any) -> int: self.executed.append(cmd) if self._execute: - return super(NullEnv, self)._run(cmd, **kwargs) + return super()._run(cmd, **kwargs) def execute(self, bin: str, *args: str, **kwargs: Any) -> Optional[int]: self.executed.append([bin] + list(args)) if self._execute: - return super(NullEnv, self).execute(bin, *args, **kwargs) + return super().execute(bin, *args, **kwargs) def _bin(self, bin: str) -> str: return bin @@ -1497,7 +1494,7 @@ def __init__( supported_tags: List[Tag] = None, **kwargs: Any, ): - super(MockEnv, self).__init__(**kwargs) + super().__init__(**kwargs) self._version_info = version_info self._python_implementation = python_implementation @@ -1524,7 +1521,7 @@ def pip_version(self) -> Version: @property def sys_path(self) -> List[str]: if self._sys_path is None: - return super(MockEnv, self).sys_path + return super().sys_path return self._sys_path @@ -1532,7 +1529,7 @@ def get_marker_env(self) -> Dict[str, Any]: if self._mock_marker_env is not None: return self._mock_marker_env - marker_env = super(MockEnv, self).get_marker_env() + marker_env = super().get_marker_env() marker_env["python_implementation"] = self._python_implementation marker_env["version_info"] = self._version_info marker_env["python_version"] = ".".join(str(v) for v in self._version_info[:2]) diff --git a/poetry/utils/exporter.py b/poetry/utils/exporter.py index b3630aa6184..2ce9c6d19fd 100644 --- a/poetry/utils/exporter.py +++ b/poetry/utils/exporter.py @@ -12,7 +12,7 @@ from poetry.utils._compat import decode -class Exporter(object): +class Exporter: """ Exporter class to export a lock file to alternative formats. """ @@ -36,7 +36,7 @@ def export( with_credentials: bool = False, ) -> None: if fmt not in self.ACCEPTED_FORMATS: - raise ValueError("Invalid export format: {}".format(fmt)) + raise ValueError(f"Invalid export format: {fmt}") getattr(self, "_export_{}".format(fmt.replace(".", "_")))( cwd, @@ -81,15 +81,15 @@ def _export_requirements_txt( line = requirement elif is_direct_local_reference: dependency_uri = path_to_url(dependency.source_url) - line = "{} @ {}".format(dependency.name, dependency_uri) + line = f"{dependency.name} @ {dependency_uri}" else: - line = "{}=={}".format(package.name, package.version) + line = f"{package.name}=={package.version}" if not is_direct_remote_reference: if ";" in requirement: markers = requirement.split(";", 1)[1].strip() if markers: - line += "; {}".format(markers) + line += f"; {markers}" if ( not is_direct_remote_reference @@ -109,7 +109,7 @@ def _export_requirements_txt( if algorithm not in self.ALLOWED_HASH_ALGORITHMS: continue - hashes.append("{}:{}".format(algorithm, h)) + hashes.append(f"{algorithm}:{h}") if hashes: line += " \\\n" @@ -143,7 +143,7 @@ def _export_requirements_txt( if with_credentials else repository.url ) - indexes_header = "--index-url {}\n".format(url) + indexes_header = f"--index-url {url}\n" continue url = ( @@ -151,8 +151,8 @@ def _export_requirements_txt( ) parsed_url = urllib.parse.urlsplit(url) if parsed_url.scheme == "http": - indexes_header += "--trusted-host {}\n".format(parsed_url.netloc) - indexes_header += "--extra-index-url {}\n".format(url) + indexes_header += f"--trusted-host {parsed_url.netloc}\n" + indexes_header += f"--extra-index-url {url}\n" content = indexes_header + "\n" + content diff --git a/poetry/utils/helpers.py b/poetry/utils/helpers.py index 1156b6415db..d81d0f15b3d 100644 --- a/poetry/utils/helpers.py +++ b/poetry/utils/helpers.py @@ -51,7 +51,7 @@ def temporary_directory(*args: Any, **kwargs: Any) -> Iterator[str]: def get_cert(config: Config, repository_name: str) -> Optional[Path]: - cert = config.get("certificates.{}.cert".format(repository_name)) + cert = config.get(f"certificates.{repository_name}.cert") if cert: return Path(cert) else: @@ -59,7 +59,7 @@ def get_cert(config: Config, repository_name: str) -> Optional[Path]: def get_client_cert(config: Config, repository_name: str) -> Optional[Path]: - client_cert = config.get("certificates.{}.client-cert".format(repository_name)) + client_cert = config.get(f"certificates.{repository_name}.client-cert") if client_cert: return Path(client_cert) else: @@ -131,7 +131,7 @@ def is_dir_writable(path: Path, create: bool = False) -> bool: with tempfile.TemporaryFile(dir=str(path)): pass - except (IOError, OSError): + except OSError: return False else: return True diff --git a/poetry/utils/password_manager.py b/poetry/utils/password_manager.py index bbf2a1a210a..e5eb05eab51 100644 --- a/poetry/utils/password_manager.py +++ b/poetry/utils/password_manager.py @@ -44,7 +44,7 @@ def get_password(self, name: str, username: str) -> Optional[str]: return keyring.get_password(name, username) except (RuntimeError, keyring.errors.KeyringError): raise KeyRingError( - "Unable to retrieve the password for {} from the key ring".format(name) + f"Unable to retrieve the password for {name} from the key ring" ) def set_password(self, name: str, username: str, password: str) -> None: @@ -78,11 +78,11 @@ def delete_password(self, name: str, username: str) -> None: keyring.delete_password(name, username) except (RuntimeError, keyring.errors.KeyringError): raise KeyRingError( - "Unable to delete the password for {} from the key ring".format(name) + f"Unable to delete the password for {name} from the key ring" ) def get_entry_name(self, name: str) -> str: - return "{}-{}".format(self._namespace, name) + return f"{self._namespace}-{name}" def _check(self) -> None: try: @@ -137,31 +137,27 @@ def keyring(self) -> KeyRing: def set_pypi_token(self, name: str, token: str) -> None: if not self.keyring.is_available(): - self._config.auth_config_source.add_property( - "pypi-token.{}".format(name), token - ) + self._config.auth_config_source.add_property(f"pypi-token.{name}", token) else: self.keyring.set_password(name, "__token__", token) def get_pypi_token(self, name: str) -> str: if not self.keyring.is_available(): - return self._config.get("pypi-token.{}".format(name)) + return self._config.get(f"pypi-token.{name}") return self.keyring.get_password(name, "__token__") def delete_pypi_token(self, name: str) -> None: if not self.keyring.is_available(): - return self._config.auth_config_source.remove_property( - "pypi-token.{}".format(name) - ) + return self._config.auth_config_source.remove_property(f"pypi-token.{name}") self.keyring.delete_password(name, "__token__") def get_http_auth(self, name: str) -> Optional[Dict[str, str]]: - auth = self._config.get("http-basic.{}".format(name)) + auth = self._config.get(f"http-basic.{name}") if not auth: - username = self._config.get("http-basic.{}.username".format(name)) - password = self._config.get("http-basic.{}.password".format(name)) + username = self._config.get(f"http-basic.{name}.username") + password = self._config.get(f"http-basic.{name}.password") if not username and not password: return None else: @@ -182,7 +178,7 @@ def set_http_password(self, name: str, username: str, password: str) -> None: else: self.keyring.set_password(name, username, password) - self._config.auth_config_source.add_property("http-basic.{}".format(name), auth) + self._config.auth_config_source.add_property(f"http-basic.{name}", auth) def delete_http_password(self, name: str) -> None: auth = self.get_http_auth(name) @@ -194,4 +190,4 @@ def delete_http_password(self, name: str) -> None: except KeyRingError: pass - self._config.auth_config_source.remove_property("http-basic.{}".format(name)) + self._config.auth_config_source.remove_property(f"http-basic.{name}") diff --git a/poetry/utils/setup_reader.py b/poetry/utils/setup_reader.py index da6d844e816..c85f34cdda3 100644 --- a/poetry/utils/setup_reader.py +++ b/poetry/utils/setup_reader.py @@ -13,7 +13,7 @@ from poetry.core.semver.version import Version -class SetupReader(object): +class SetupReader: """ Class that reads a setup.py file without executing it. """ diff --git a/poetry/utils/shell.py b/poetry/utils/shell.py index 544be053c4c..ce91bfba029 100644 --- a/poetry/utils/shell.py +++ b/poetry/utils/shell.py @@ -118,4 +118,4 @@ def _get_source_command(self) -> str: return "." def __repr__(self) -> str: - return '{}("{}", "{}")'.format(self.__class__.__name__, self._name, self._path) + return f'{self.__class__.__name__}("{self._name}", "{self._path}")' diff --git a/poetry/version/version_selector.py b/poetry/version/version_selector.py index 27dd582aaff..94892eb5287 100644 --- a/poetry/version/version_selector.py +++ b/poetry/version/version_selector.py @@ -10,7 +10,7 @@ from poetry.repositories import Pool -class VersionSelector(object): +class VersionSelector: def __init__(self, pool: "Pool") -> None: self._pool = pool @@ -78,6 +78,6 @@ def _transform_version(self, version: str, pretty_version: str) -> str: else: version = ".".join(str(p) for p in parts) if parsed.is_unstable(): - version += "-{}".format(parsed.pre.to_string()) + version += f"-{parsed.pre.to_string()}" - return "^{}".format(version) + return f"^{version}" From fcc2c839952a57e0268b623b319fa512982ca405 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 30 Mar 2021 02:09:06 +0200 Subject: [PATCH 155/222] add command to add, remove and show project sources --- docs/docs/cli.md | 53 +++++++++ poetry.lock | 2 +- poetry/config/source.py | 15 +++ poetry/console/application.py | 5 +- poetry/console/commands/source/__init__.py | 0 poetry/console/commands/source/add.py | 108 +++++++++++++++++++ poetry/console/commands/source/remove.py | 55 ++++++++++ poetry/console/commands/source/show.py | 59 ++++++++++ poetry/console/commands/source/update.py | 0 poetry/poetry.py | 11 +- pyproject.toml | 1 + tests/console/commands/source/__init__.py | 0 tests/console/commands/source/conftest.py | 63 +++++++++++ tests/console/commands/source/test_add.py | 71 ++++++++++++ tests/console/commands/source/test_remove.py | 28 +++++ tests/console/commands/source/test_show.py | 75 +++++++++++++ 16 files changed, 542 insertions(+), 4 deletions(-) create mode 100644 poetry/config/source.py create mode 100644 poetry/console/commands/source/__init__.py create mode 100644 poetry/console/commands/source/add.py create mode 100644 poetry/console/commands/source/remove.py create mode 100644 poetry/console/commands/source/show.py create mode 100644 poetry/console/commands/source/update.py create mode 100644 tests/console/commands/source/__init__.py create mode 100644 tests/console/commands/source/conftest.py create mode 100644 tests/console/commands/source/test_add.py create mode 100644 tests/console/commands/source/test_remove.py create mode 100644 tests/console/commands/source/test_show.py diff --git a/docs/docs/cli.md b/docs/docs/cli.md index 1bf9ce1b134..64cf17941e5 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -581,3 +581,56 @@ The `plugin remove` command removes installed plugins. ```bash poetry plugin remove poetry-plugin ``` + +## source + +The `source` namespace regroups sub commands to manage repository sources for a Poetry project. + +### `source add` + +The `source add` command adds source configuration to the project. + +For example, to add the `pypi-test` source, you can run: + +```bash +poetry source add pypi-test https://test.pypi.org/simple/ +``` + +!!!note + + You cannot use the name `pypi` as it is reserved for use by the default PyPI source. + +#### Options + +* `--default`: Set this source as the [default](/docs/repositories/#disabling-the-pypi-repository) (disable PyPI). +* `--secondary`: Set this source as a [secondary](/docs/repositories/#install-dependencies-from-a-private-repository) source. + +!!!note + + You cannot set a source as both `default` and `secondary`. + +### `source show` + +The `source show` command displays information on all configured sources for the project. + +```bash +poetry source show +``` + +Optionally, you can show information of one or more sources by specifying their names. + +```bash +poetry source show pypi-test +``` + +!!!note + + This command will only show sources configured via the `pyproject.toml` and does not include PyPI. + +### `source remove` + +The `source remove` command removes a configured source from your `pyproject.toml`. + +```bash +poetry source remove pypi-test +``` diff --git a/poetry.lock b/poetry.lock index 601f22d4211..3c591d347ac 100644 --- a/poetry.lock +++ b/poetry.lock @@ -714,7 +714,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "8442060c68d80744b05aac3a07818a1e04e457c05b0e481d717cb44721009566" +content-hash = "cccd84fe6459fdb43ff8bb775ee093c7ee351f112a054ffc6ca5ecf59deba1d5" [metadata.files] appdirs = [ diff --git a/poetry/config/source.py b/poetry/config/source.py new file mode 100644 index 00000000000..3735b193dfa --- /dev/null +++ b/poetry/config/source.py @@ -0,0 +1,15 @@ +import dataclasses + +from typing import Dict +from typing import Union + + +@dataclasses.dataclass(order=True, eq=True) +class Source: + name: str + url: str + default: bool = dataclasses.field(default=False) + secondary: bool = dataclasses.field(default=False) + + def to_dict(self) -> Dict[str, Union[str, bool]]: + return dataclasses.asdict(self) diff --git a/poetry/console/application.py b/poetry/console/application.py index 8dcbced0b73..42cc4377043 100644 --- a/poetry/console/application.py +++ b/poetry/console/application.py @@ -76,9 +76,12 @@ def _load() -> Type[Command]: "plugin show", # Self commands "self update", + # Source commands + "source add", + "source remove", + "source show", ] - if TYPE_CHECKING: from cleo.io.inputs.definition import Definition diff --git a/poetry/console/commands/source/__init__.py b/poetry/console/commands/source/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/poetry/console/commands/source/add.py b/poetry/console/commands/source/add.py new file mode 100644 index 00000000000..1b097076b84 --- /dev/null +++ b/poetry/console/commands/source/add.py @@ -0,0 +1,108 @@ +from typing import Optional + +from cleo.helpers import argument +from cleo.helpers import option +from cleo.io.null_io import NullIO +from tomlkit import nl +from tomlkit import table +from tomlkit.items import AoT +from tomlkit.items import Table + +from poetry.config.source import Source +from poetry.console.commands.command import Command +from poetry.factory import Factory +from poetry.repositories import Pool + + +class SourceAddCommand(Command): + + name = "source add" + description = "Add source configuration for project." + + arguments = [ + argument( + "name", + "Source repository name.", + ), + argument("url", "Source repository url."), + ] + + options = [ + 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.", + ), + option("secondary", "s", "Set this source as secondary."), + ] + + @staticmethod + def source_to_table(source: Source) -> Table: + source_table: Table = table() + for key, value in source.to_dict().items(): + source_table.add(key, value) + source_table.add(nl()) + return source_table + + def handle(self) -> Optional[int]: + name = self.argument("name") + url = self.argument("url") + is_default = self.option("default") + is_secondary = self.option("secondary") + + if is_default and is_secondary: + self.line_error( + "Cannot configure a source as both default and secondary." + ) + return 1 + + new_source = Source( + name=name, url=url, default=is_default, secondary=is_secondary + ) + existing_sources = self.poetry.get_sources() + + sources = AoT([]) + + for source in existing_sources: + if source == new_source: + self.line( + f"Source with name {name} already exits. Skipping addition." + ) + return 0 + elif source.default and is_default: + self.line_error( + f"Source with name {source.name} is already set to default. " + f"Only one default source can be configured at a time." + ) + return 1 + + if source.name == name: + self.line(f"Source with name {name} already exits. Updating.") + source = new_source + new_source = None + + sources.append(self.source_to_table(source)) + + if new_source is not None: + self.line(f"Adding source with name {name}.") + sources.append(self.source_to_table(new_source)) + + # ensure new source is valid. eg: invalid name etc. + self.poetry._pool = Pool() + try: + Factory.configure_sources( + self.poetry, sources, self.poetry.config, NullIO() + ) + self.poetry.pool.repository(name) + except ValueError as e: + self.line_error( + f"Failed to validate addition of {name}: {e}" + ) + return 1 + + self.poetry.pyproject.poetry_config["source"] = sources + self.poetry.pyproject.save() + + return 0 diff --git a/poetry/console/commands/source/remove.py b/poetry/console/commands/source/remove.py new file mode 100644 index 00000000000..6bc2db11e59 --- /dev/null +++ b/poetry/console/commands/source/remove.py @@ -0,0 +1,55 @@ +from typing import Optional + +from cleo.helpers import argument +from tomlkit import nl +from tomlkit import table +from tomlkit.items import AoT +from tomlkit.items import Table + +from poetry.config.source import Source +from poetry.console.commands.command import Command + + +class SourceRemoveCommand(Command): + + name = "source remove" + description = "Remove source configured for the project." + + arguments = [ + argument( + "name", + "Source repository name.", + ), + ] + + @staticmethod + def source_to_table(source: Source) -> Table: + source_table: Table = table() + for key, value in source.to_dict().items(): + source_table.add(key, value) + source_table.add(nl()) + return source_table + + def handle(self) -> Optional[int]: + name = self.argument("name") + + sources = AoT([]) + removed = False + + for source in self.poetry.get_sources(): + if source.name == name: + self.line(f"Removing source with name {source.name}.") + removed = True + continue + sources.append(self.source_to_table(source)) + + if not removed: + self.line_error( + f"Source with name {name} was not found." + ) + return 1 + + self.poetry.pyproject.poetry_config["source"] = sources + self.poetry.pyproject.save() + + return 0 diff --git a/poetry/console/commands/source/show.py b/poetry/console/commands/source/show.py new file mode 100644 index 00000000000..67e843fcf61 --- /dev/null +++ b/poetry/console/commands/source/show.py @@ -0,0 +1,59 @@ +from typing import Optional + +from cleo.helpers import argument + +from poetry.console.commands.command import Command + + +class SourceShowCommand(Command): + name = "source show" + description = "Show information about sources configured for the project." + + arguments = [ + argument( + "source", + "Source(s) to show information for. Defaults to showing all sources.", + optional=True, + multiple=True, + ), + ] + + def handle(self) -> Optional[int]: + sources = self.poetry.get_sources() + names = self.argument("source") + + if not sources: + self.line("No sources configured for this project.") + return 0 + + if names and not any(map(lambda s: s.name in names, sources)): + self.line_error(f"No source found with name(s): {', '.join(names)}") + return 1 + + bool_string = { + True: "yes", + False: "no", + } + + for source in sources: + if names and source.name not in names: + continue + + table = self.table(style="compact") + rows = [ + ["name", " : {}".format(source.name)], + ["url", " : {}".format(source.url)], + [ + "default", + " : {}".format(bool_string.get(source.default, False)), + ], + [ + "secondary", + " : {}".format(bool_string.get(source.secondary, False)), + ], + ] + table.add_rows(rows) + table.render() + self.line("") + + return 0 diff --git a/poetry/console/commands/source/update.py b/poetry/console/commands/source/update.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/poetry/poetry.py b/poetry/poetry.py index 54fee74b7fc..95814330e4a 100644 --- a/poetry/poetry.py +++ b/poetry/poetry.py @@ -1,10 +1,11 @@ from pathlib import Path from typing import TYPE_CHECKING +from typing import List +from poetry.__version__ import __version__ +from poetry.config.source import Source from poetry.core.poetry import Poetry as BasePoetry -from .__version__ import __version__ - if TYPE_CHECKING: from poetry.core.packages.project_package import ProjectPackage @@ -67,3 +68,9 @@ def set_plugin_manager(self, plugin_manager: "PluginManager") -> "Poetry": self._plugin_manager = plugin_manager return self + + def get_sources(self) -> List[Source]: + return [ + Source(**source) + for source in self.pyproject.poetry_config.get("source", []) + ] diff --git a/pyproject.toml b/pyproject.toml index 10d03a07d72..bae6619a3cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ virtualenv = "^20.4.3" keyring = "^21.2.0" entrypoints = "^0.3" importlib-metadata = {version = "^1.6.0", python = "<3.8"} +dataclasses = {version = "^0.8", python = "~3.6"} [tool.poetry.dev-dependencies] pytest = "^5.4.3" diff --git a/tests/console/commands/source/__init__.py b/tests/console/commands/source/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/console/commands/source/conftest.py b/tests/console/commands/source/conftest.py new file mode 100644 index 00000000000..fc3c38b181f --- /dev/null +++ b/tests/console/commands/source/conftest.py @@ -0,0 +1,63 @@ +import pytest + +from poetry.config.source import Source + + +@pytest.fixture +def source_one(): + return Source(name="one", url="https://one.com") + + +@pytest.fixture +def source_two(): + return Source(name="two", url="https://two.com") + + +@pytest.fixture +def source_default(): + return Source(name="default", url="https://default.com", default=True) + + +@pytest.fixture +def source_secondary(): + return Source(name="secondary", url="https://secondary.com", secondary=True) + + +_existing_source = Source(name="existing", url="https://existing.com") + + +@pytest.fixture +def source_existing(): + return _existing_source + + +PYPROJECT_WITH_SOURCES = f""" +[tool.poetry] +name = "source-command-test" +version = "0.1.0" +description = "" +authors = ["Poetry Tester "] + +[tool.poetry.dependencies] +python = "^3.9" + +[tool.poetry.dev-dependencies] + +[[tool.poetry.source]] +name = "{_existing_source.name}" +url = "{_existing_source.url}" +""" + + +@pytest.fixture +def poetry_with_source(project_factory): + return project_factory(pyproject_content=PYPROJECT_WITH_SOURCES) + + +@pytest.fixture +def add_multiple_sources( + command_tester_factory, poetry_with_source, source_one, source_two +): + add = command_tester_factory("source add", poetry=poetry_with_source) + for source in [source_one, source_two]: + add.execute(f"{source.name} {source.url}") diff --git a/tests/console/commands/source/test_add.py b/tests/console/commands/source/test_add.py new file mode 100644 index 00000000000..97a407918dd --- /dev/null +++ b/tests/console/commands/source/test_add.py @@ -0,0 +1,71 @@ +import dataclasses + +import pytest + + +@pytest.fixture +def tester(command_tester_factory, poetry_with_source): + return command_tester_factory("source add", poetry=poetry_with_source) + + +def assert_source_added(tester, poetry, source_existing, source_added): + 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 test_source_add_simple(tester, source_existing, source_one, poetry_with_source): + tester.execute(f"{source_one.name} {source_one.url}") + assert_source_added(tester, poetry_with_source, source_existing, source_one) + + +def test_source_add_default( + tester, source_existing, source_default, poetry_with_source +): + tester.execute(f"--default {source_default.name} {source_default.url}") + assert_source_added(tester, poetry_with_source, source_existing, source_default) + + +def test_source_add_secondary( + tester, source_existing, source_secondary, poetry_with_source +): + 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): + tester.execute("--default --secondary error https://error.com") + assert ( + tester.io.fetch_error().strip() + == "Cannot configure a source as both default and secondary." + ) + assert tester.status_code == 1 + + +def test_source_add_error_pypi(tester): + tester.execute("pypi https://test.pypi.org/simple/") + assert ( + tester.io.fetch_error().strip() + == "Failed to validate addition of pypi: The name [pypi] is reserved for repositories" + ) + assert tester.status_code == 1 + + +def test_source_add_existing(tester, source_existing, poetry_with_source): + tester.execute(f"--default {source_existing.name} {source_existing.url}") + assert ( + tester.io.fetch_output().strip() + == f"Source with name {source_existing.name} already exits. Updating." + ) + + poetry_with_source.pyproject.reload() + sources = poetry_with_source.get_sources() + + assert len(sources) == 1 + assert sources[0] != source_existing + assert sources[0] == dataclasses.replace(source_existing, default=True) diff --git a/tests/console/commands/source/test_remove.py b/tests/console/commands/source/test_remove.py new file mode 100644 index 00000000000..33f35a8080b --- /dev/null +++ b/tests/console/commands/source/test_remove.py @@ -0,0 +1,28 @@ +import pytest + + +@pytest.fixture +def tester(command_tester_factory, poetry_with_source, add_multiple_sources): + return command_tester_factory("source remove", poetry=poetry_with_source) + + +def test_source_remove_simple( + tester, poetry_with_source, source_existing, source_one, source_two +): + tester.execute(f"{source_existing.name}") + assert ( + tester.io.fetch_output().strip() + == f"Removing source with name {source_existing.name}." + ) + + poetry_with_source.pyproject.reload() + sources = poetry_with_source.get_sources() + assert sources == [source_one, source_two] + + assert tester.status_code == 0 + + +def test_source_remove_error(tester): + 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 new file mode 100644 index 00000000000..8aa961cf147 --- /dev/null +++ b/tests/console/commands/source/test_show.py @@ -0,0 +1,75 @@ +import pytest + + +@pytest.fixture +def tester(command_tester_factory, poetry_with_source, add_multiple_sources): + return command_tester_factory("source show", poetry=poetry_with_source) + + +def test_source_show_simple(tester): + 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 +""".splitlines() + assert ( + list(map(lambda l: l.strip(), tester.io.fetch_output().strip().splitlines())) + == expected + ) + assert tester.status_code == 0 + + +def test_source_show_one(tester, source_one): + tester.execute(f"{source_one.name}") + + expected = """\ +name : one +url : https://one.com +default : no +secondary : no +""".splitlines() + assert ( + list(map(lambda l: l.strip(), tester.io.fetch_output().strip().splitlines())) + == expected + ) + assert tester.status_code == 0 + + +def test_source_show_two(tester, source_one, source_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 +""".splitlines() + assert ( + list(map(lambda l: l.strip(), tester.io.fetch_output().strip().splitlines())) + == expected + ) + assert tester.status_code == 0 + + +def test_source_show_error(tester): + tester.execute("error") + assert tester.io.fetch_error().strip() == "No source found with name(s): error" + assert tester.status_code == 1 From 5b7791c5533958b60a8eb0283a2854ff29fb31a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 26 Mar 2021 11:29:08 +0100 Subject: [PATCH 156/222] Write PEP-610-compliant files when installing --- poetry/installation/executor.py | 135 +++++++++++++++++- tests/fixtures/simple_project/pyproject.toml | 5 + tests/installation/test_executor.py | 141 +++++++++++++++++++ 3 files changed, 277 insertions(+), 4 deletions(-) diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index 5e2daa012eb..84176431640 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -1,4 +1,5 @@ import itertools +import json import os import threading @@ -8,6 +9,7 @@ from subprocess import CalledProcessError from typing import TYPE_CHECKING from typing import Any +from typing import Dict from typing import List from typing import Union @@ -35,6 +37,7 @@ from cleo.io.io import IO # noqa from poetry.config.config import Config + from poetry.core.packages.package import Package from poetry.repositories import Pool from poetry.utils.env import Env @@ -82,6 +85,7 @@ def __init__( self._sections = dict() self._lock = threading.Lock() self._shutdown = False + self._hashes: Dict[str, str] = {} @property def installations_count(self) -> int: @@ -434,10 +438,18 @@ def _display_summary(self, operations: List["OperationTypes"]) -> None: self._io.write_line("") def _execute_install(self, operation: Union[Install, Update]) -> int: - return self._install(operation) + status_code = self._install(operation) + + self._save_url_reference(operation) + + return status_code def _execute_update(self, operation: Union[Install, Update]) -> int: - return self._update(operation) + status_code = self._update(operation) + + self._save_url_reference(operation) + + return status_code def _execute_uninstall(self, operation: Uninstall) -> int: message = ( @@ -594,12 +606,17 @@ def _install_git(self, operation: Union[Install, Update]) -> int: git = Git() git.clone(package.source_url, src_dir) - git.checkout(package.source_reference, src_dir) + git.checkout(package.source_resolved_reference, src_dir) # Now we just need to install from the source directory + original_url = package.source_url package._source_url = str(src_dir) - return self._install_directory(operation) + status_code = self._install_directory(operation) + + package._source_url = original_url + + return status_code def _download(self, operation: Union[Install, Update]) -> Link: link = self._chooser.choose_for(operation.package) @@ -636,6 +653,8 @@ def _download_link(self, operation: Union[Install, Update], link: Link) -> Link: f"Invalid hash for {package} using archive {archive.name}" ) + self._hashes[package.name] = archive_hash + return archive def _download_archive(self, operation: Union[Install, Update], link: Link) -> Path: @@ -689,3 +708,111 @@ def _download_archive(self, operation: Union[Install, Update], link: Link) -> Pa def _should_write_operation(self, operation: Operation) -> bool: return not operation.skipped or self._dry_run or self._verbose + + def _save_url_reference(self, operation: "OperationTypes") -> None: + """ + Create and store a PEP-610 `direct_url.json` file, if needed. + """ + if operation.job_type not in {"install", "update"}: + return + + from poetry.core.masonry.utils.helpers import escape_name + from poetry.core.masonry.utils.helpers import escape_version + + package = operation.package + + if not package.source_url: + # Since we are installing from our own distribution cache + # pip will write a `direct_url.json` file pointing to the cache + # distribution. + # That's not what we want so we remove the direct_url.json file, + # if it exists. + dist_info = self._env.site_packages.path.joinpath( + "{}-{}.dist-info".format( + escape_name(package.pretty_name), + escape_version(package.version.text), + ) + ) + if dist_info.exists() and dist_info.joinpath("direct_url.json").exists(): + dist_info.joinpath("direct_url.json").unlink() + + return + + url_reference = None + + if package.source_type == "git": + url_reference = self._create_git_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) + + if url_reference: + dist_info = self._env.site_packages.path.joinpath( + "{}-{}.dist-info".format( + escape_name(package.name), escape_version(package.version.text) + ) + ) + + if dist_info.exists(): + dist_info.joinpath("direct_url.json").write_text( + json.dumps(url_reference), encoding="utf-8" + ) + + def _create_git_url_reference( + self, package: "Package" + ) -> Dict[str, Union[str, Dict[str, str]]]: + reference = { + "url": package.source_url, + "vcs_info": { + "vcs": "git", + "requested_revision": package.source_reference, + "commit_id": package.source_resolved_reference, + }, + } + + return reference + + def _create_url_url_reference( + self, package: "Package" + ) -> Dict[str, Union[str, Dict[str, str]]]: + archive_info = {} + + if package.name in self._hashes: + archive_info["hash"] = self._hashes[package.name] + + reference = {"url": package.source_url, "archive_info": archive_info} + + return reference + + def _create_file_url_reference( + self, package: "Package" + ) -> Dict[str, Union[str, Dict[str, str]]]: + archive_info = {} + + if package.name in self._hashes: + archive_info["hash"] = self._hashes[package.name] + + reference = { + "url": Path(package.source_url).as_uri(), + "archive_info": archive_info, + } + + return reference + + def _create_directory_url_reference( + self, package: "Package" + ) -> Dict[str, Union[str, Dict[str, str]]]: + dir_info = {} + + if package.develop: + dir_info["editable"] = True + + reference = { + "url": Path(package.source_url).as_uri(), + "dir_info": dir_info, + } + + return reference diff --git a/tests/fixtures/simple_project/pyproject.toml b/tests/fixtures/simple_project/pyproject.toml index 0fd938e41a0..41a062fc09a 100644 --- a/tests/fixtures/simple_project/pyproject.toml +++ b/tests/fixtures/simple_project/pyproject.toml @@ -28,3 +28,8 @@ python = "~2.7 || ^3.4" foo = "foo:bar" baz = "bar:baz.boom.bim" fox = "fuz.foo:bar.baz" + + +[build-system] +requires = ["poetry-core>=1.0.2"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 4511e770a9d..f074078638d 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import json import re import shutil @@ -27,6 +28,7 @@ def env(tmp_dir): path = Path(tmp_dir) / ".venv" path.mkdir(parents=True) + return MockEnv(path=path, is_venv=True) @@ -261,3 +263,142 @@ def test_executor_should_delete_incomplete_downloads( executor._download(Install(Package("tomlkit", "0.5.3"))) assert not destination_fixture.exists() + + +def test_executor_should_write_pep610_url_references_for_files( + tmp_venv, pool, config, io +): + url = ( + Path(__file__) + .parent.parent.joinpath( + "fixtures/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()) + + executor = Executor(tmp_venv, pool, config, io) + executor.execute([Install(package)]) + + dist_info = tmp_venv.site_packages.path.joinpath("demo-0.1.0.dist-info") + assert dist_info.exists() + + direct_url_file = dist_info.joinpath("direct_url.json") + + assert direct_url_file.exists() + + url_reference = json.loads(direct_url_file.read_text(encoding="utf-8")) + + assert url_reference == {"archive_info": {}, "url": url.as_uri()} + + +def test_executor_should_write_pep610_url_references_for_directories( + tmp_venv, pool, config, io +): + url = Path(__file__).parent.parent.joinpath("fixtures/simple_project").resolve() + package = Package( + "simple-project", "1.2.3", source_type="directory", source_url=url.as_posix() + ) + + executor = Executor(tmp_venv, pool, config, io) + executor.execute([Install(package)]) + + dist_info = tmp_venv.site_packages.path.joinpath("simple_project-1.2.3.dist-info") + assert dist_info.exists() + + direct_url_file = dist_info.joinpath("direct_url.json") + + assert direct_url_file.exists() + + url_reference = json.loads(direct_url_file.read_text(encoding="utf-8")) + + assert url_reference == {"dir_info": {}, "url": url.as_uri()} + + +def test_executor_should_write_pep610_url_references_for_editable_directories( + tmp_venv, pool, config, io +): + url = Path(__file__).parent.parent.joinpath("fixtures/simple_project").resolve() + package = Package( + "simple-project", + "1.2.3", + source_type="directory", + source_url=url.as_posix(), + develop=True, + ) + + executor = Executor(tmp_venv, pool, config, io) + executor.execute([Install(package)]) + + dist_info = tmp_venv.site_packages.path.joinpath("simple_project-1.2.3.dist-info") + assert dist_info.exists() + + direct_url_file = dist_info.joinpath("direct_url.json") + + assert direct_url_file.exists() + + url_reference = json.loads(direct_url_file.read_text(encoding="utf-8")) + + assert url_reference == {"dir_info": {"editable": True}, "url": url.as_uri()} + + +def test_executor_should_write_pep610_url_references_for_urls( + tmp_venv, pool, config, io, mock_file_downloads +): + package = Package( + "demo", + "0.1.0", + source_type="url", + source_url="https://files.pythonhosted.org/demo-0.1.0-py2.py3-none-any.whl", + ) + + executor = Executor(tmp_venv, pool, config, io) + executor.execute([Install(package)]) + + dist_info = tmp_venv.site_packages.path.joinpath("demo-0.1.0.dist-info") + assert dist_info.exists() + + direct_url_file = dist_info.joinpath("direct_url.json") + + assert direct_url_file.exists() + + url_reference = json.loads(direct_url_file.read_text(encoding="utf-8")) + + assert url_reference == { + "archive_info": {}, + "url": "https://files.pythonhosted.org/demo-0.1.0-py2.py3-none-any.whl", + } + + +def test_executor_should_write_pep610_url_references_for_git( + tmp_venv, pool, config, io, mock_file_downloads +): + package = Package( + "demo", + "0.1.2", + source_type="git", + source_reference="master", + source_resolved_reference="123456", + source_url="https://github.com/demo/demo.git", + ) + + executor = Executor(tmp_venv, pool, config, io) + executor.execute([Install(package)]) + + dist_info = tmp_venv.site_packages.path.joinpath("demo-0.1.2.dist-info") + assert dist_info.exists() + + direct_url_file = dist_info.joinpath("direct_url.json") + + assert direct_url_file.exists() + + url_reference = json.loads(direct_url_file.read_text(encoding="utf-8")) + + assert url_reference == { + "vcs_info": { + "vcs": "git", + "requested_revision": "master", + "commit_id": "123456", + }, + "url": "https://github.com/demo/demo.git", + } From 3d74ddf9b3a9bcbf655b39d26e37bee14a3559e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Thu, 1 Apr 2021 18:39:28 +0200 Subject: [PATCH 157/222] Read PEP-610-compliant files when loading installed packages --- poetry/repositories/installed_repository.py | 192 +++++++++++++----- .../METADATA | 6 + .../direct_url.json | 4 + .../METADATA | 6 + .../direct_url.json | 6 + .../file_pep_610-1.2.3.dist-info/METADATA | 6 + .../direct_url.json | 6 + .../git_pep_610-1.2.3.dist-info/METADATA | 6 + .../direct_url.json | 8 + .../url_pep_610-1.2.3.dist-info/METADATA | 6 + .../direct_url.json | 4 + .../repositories/test_installed_repository.py | 64 ++++++ 12 files changed, 262 insertions(+), 52 deletions(-) create mode 100644 tests/repositories/fixtures/installed/lib/python3.7/site-packages/directory_pep_610-1.2.3.dist-info/METADATA create mode 100644 tests/repositories/fixtures/installed/lib/python3.7/site-packages/directory_pep_610-1.2.3.dist-info/direct_url.json create mode 100644 tests/repositories/fixtures/installed/lib/python3.7/site-packages/editable_directory_pep_610-1.2.3.dist-info/METADATA create mode 100644 tests/repositories/fixtures/installed/lib/python3.7/site-packages/editable_directory_pep_610-1.2.3.dist-info/direct_url.json create mode 100644 tests/repositories/fixtures/installed/lib/python3.7/site-packages/file_pep_610-1.2.3.dist-info/METADATA create mode 100644 tests/repositories/fixtures/installed/lib/python3.7/site-packages/file_pep_610-1.2.3.dist-info/direct_url.json create mode 100644 tests/repositories/fixtures/installed/lib/python3.7/site-packages/git_pep_610-1.2.3.dist-info/METADATA create mode 100644 tests/repositories/fixtures/installed/lib/python3.7/site-packages/git_pep_610-1.2.3.dist-info/direct_url.json create mode 100644 tests/repositories/fixtures/installed/lib/python3.7/site-packages/url_pep_610-1.2.3.dist-info/METADATA create mode 100644 tests/repositories/fixtures/installed/lib/python3.7/site-packages/url_pep_610-1.2.3.dist-info/direct_url.json diff --git a/poetry/repositories/installed_repository.py b/poetry/repositories/installed_repository.py index f33bbd9b99f..d89c9837c98 100644 --- a/poetry/repositories/installed_repository.py +++ b/poetry/repositories/installed_repository.py @@ -1,10 +1,15 @@ import itertools +import json from pathlib import Path +from typing import TYPE_CHECKING from typing import Set +from typing import Tuple from typing import Union from poetry.core.packages.package import Package +from poetry.core.packages.utils.utils import url_to_path +from poetry.core.utils.helpers import canonicalize_name from poetry.core.utils.helpers import module_name from poetry.utils._compat import metadata from poetry.utils.env import Env @@ -14,6 +19,9 @@ _VENDORS = Path(__file__).parent.parent.joinpath("_vendor") +if TYPE_CHECKING: + from importlib.metadata import Distribution + try: FileNotFoundError @@ -68,21 +76,14 @@ def get_package_paths(cls, env: Env, name: str) -> Set[Path]: return paths @classmethod - def set_package_vcs_properties_from_path(cls, src: Path, package: Package) -> None: + def get_package_vcs_properties_from_path(cls, src: Path) -> Tuple[str, str, str]: from poetry.core.vcs.git import Git git = Git() revision = git.rev_parse("HEAD", src).strip() url = git.remote_url(src) - package._source_type = "git" - package._source_url = url - package._source_reference = revision - - @classmethod - def set_package_vcs_properties(cls, package: Package, env: Env) -> None: - src = env.path / "src" / package.name - cls.set_package_vcs_properties_from_path(src, package) + return "git", url, revision @classmethod def is_vcs_package(cls, package: Union[Path, Package], env: Env) -> bool: @@ -99,6 +100,125 @@ def is_vcs_package(cls, package: Union[Path, Package], env: Env) -> bool: else: return True + @classmethod + def create_package_from_distribution( + cls, distribution: "Distribution", env: "Env" + ) -> Package: + # We first check for a direct_url.json file to determine + # the type of package. + path = Path(str(distribution._path)) + + if ( + path.name.endswith(".dist-info") + and path.joinpath("direct_url.json").exists() + ): + return cls.create_package_from_pep610(distribution) + + is_standard_package = env.is_path_relative_to_lib(path) + + source_type = None + source_url = None + source_reference = None + source_resolved_reference = None + if is_standard_package: + if path.name.endswith(".dist-info"): + paths = cls.get_package_paths( + env=env, name=distribution.metadata["name"] + ) + if paths: + is_editable_package = False + for src in paths: + if cls.is_vcs_package(src, env): + ( + source_type, + source_url, + source_reference, + ) = cls.get_package_vcs_properties_from_path(src) + break + + if not ( + is_editable_package or env.is_path_relative_to_lib(src) + ): + is_editable_package = True + else: + # TODO: handle multiple source directories? + if is_editable_package: + source_type = "directory" + source_url = paths.pop().as_posix() + else: + if cls.is_vcs_package(path, env): + ( + source_type, + source_url, + source_reference, + ) = cls.get_package_vcs_properties_from_path( + env.path / "src" / canonicalize_name(distribution.metadata["name"]) + ) + else: + # If not, it's a path dependency + source_type = "directory" + source_url = str(path.parent) + + package = Package( + distribution.metadata["name"], + distribution.metadata["version"], + source_type=source_type, + source_url=source_url, + source_reference=source_reference, + source_resolved_reference=source_resolved_reference, + ) + package.description = distribution.metadata.get("summary", "") + + return package + + @classmethod + def create_package_from_pep610(cls, distribution: "Distribution") -> Package: + path = Path(str(distribution._path)) + source_type = None + source_url = None + source_reference = None + source_resolved_reference = None + develop = False + + url_reference = json.loads( + path.joinpath("direct_url.json").read_text(encoding="utf-8") + ) + if "archive_info" in url_reference: + # File or URL distribution + if url_reference["url"].startswith("file:"): + # File distribution + source_type = "file" + source_url = url_to_path(url_reference["url"]).as_posix() + else: + # URL distribution + source_type = "url" + source_url = url_reference["url"] + elif "dir_info" in url_reference: + # Directory distribution + source_type = "directory" + source_url = url_to_path(url_reference["url"]).as_posix() + develop = url_reference["dir_info"].get("editable", False) + elif "vcs_info" in url_reference: + # VCS distribution + source_type = url_reference["vcs_info"]["vcs"] + source_url = url_reference["url"] + source_reference = url_reference["vcs_info"]["requested_revision"] + source_resolved_reference = url_reference["vcs_info"]["commit_id"] + + package = Package( + distribution.metadata["name"], + distribution.metadata["version"], + source_type=source_type, + source_url=source_url, + source_reference=source_reference, + source_resolved_reference=source_resolved_reference, + develop=develop, + ) + + package.description = distribution.metadata.get("summary", "") + + return package + @classmethod def load(cls, env: Env, with_dependencies: bool = False) -> "InstalledRepository": """ @@ -114,20 +234,13 @@ def load(cls, env: Env, with_dependencies: bool = False) -> "InstalledRepository metadata.distributions(path=[entry]), key=lambda d: str(d._path), ): - name = distribution.metadata["name"] - path = Path(str(distribution._path)) - version = distribution.metadata["version"] - package = Package(name, version, version) - package.description = distribution.metadata.get("summary", "") + name = canonicalize_name(distribution.metadata["name"]) - if with_dependencies: - for require in distribution.metadata.get_all("requires-dist", []): - dep = Dependency.create_from_pep_508(require) - package.add_dependency(dep) - - if package.name in seen: + if name in seen: continue + path = Path(str(distribution._path)) + try: path.relative_to(_VENDORS) except ValueError: @@ -135,39 +248,14 @@ def load(cls, env: Env, with_dependencies: bool = False) -> "InstalledRepository else: continue - seen.add(package.name) - - repo.add_package(package) + package = cls.create_package_from_distribution(distribution, env) - is_standard_package = env.is_path_relative_to_lib(path) - - if is_standard_package: - if path.name.endswith(".dist-info"): - paths = cls.get_package_paths(env=env, name=package.pretty_name) - if paths: - is_editable_package = False - for src in paths: - if cls.is_vcs_package(src, env): - cls.set_package_vcs_properties(package, env) - break - - if not ( - is_editable_package - or env.is_path_relative_to_lib(src) - ): - is_editable_package = True - else: - # TODO: handle multiple source directories? - if is_editable_package: - package._source_type = "directory" - package._source_url = paths.pop().as_posix() - continue + if with_dependencies: + for require in distribution.metadata.get_all("requires-dist", []): + dep = Dependency.create_from_pep_508(require) + package.add_dependency(dep) - if cls.is_vcs_package(path, env): - cls.set_package_vcs_properties(package, env) - else: - # If not, it's a path dependency - package._source_type = "directory" - package._source_url = str(path.parent) + seen.add(package.name) + repo.add_package(package) return repo diff --git a/tests/repositories/fixtures/installed/lib/python3.7/site-packages/directory_pep_610-1.2.3.dist-info/METADATA b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/directory_pep_610-1.2.3.dist-info/METADATA new file mode 100644 index 00000000000..30928a39f0c --- /dev/null +++ b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/directory_pep_610-1.2.3.dist-info/METADATA @@ -0,0 +1,6 @@ +Metadata-Version: 2.1 +Name: directory-pep-610 +Version: 1.2.3 +Summary: Foo +License: MIT +Requires-Python: >=3.6 diff --git a/tests/repositories/fixtures/installed/lib/python3.7/site-packages/directory_pep_610-1.2.3.dist-info/direct_url.json b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/directory_pep_610-1.2.3.dist-info/direct_url.json new file mode 100644 index 00000000000..3385611ce74 --- /dev/null +++ b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/directory_pep_610-1.2.3.dist-info/direct_url.json @@ -0,0 +1,4 @@ +{ + "url": "file:///path/to/distributions/directory-pep-610", + "dir_info": {} +} diff --git a/tests/repositories/fixtures/installed/lib/python3.7/site-packages/editable_directory_pep_610-1.2.3.dist-info/METADATA b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/editable_directory_pep_610-1.2.3.dist-info/METADATA new file mode 100644 index 00000000000..337c6fc4301 --- /dev/null +++ b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/editable_directory_pep_610-1.2.3.dist-info/METADATA @@ -0,0 +1,6 @@ +Metadata-Version: 2.1 +Name: editable-directory-pep-610 +Version: 1.2.3 +Summary: Foo +License: MIT +Requires-Python: >=3.6 diff --git a/tests/repositories/fixtures/installed/lib/python3.7/site-packages/editable_directory_pep_610-1.2.3.dist-info/direct_url.json b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/editable_directory_pep_610-1.2.3.dist-info/direct_url.json new file mode 100644 index 00000000000..e45f7c31a35 --- /dev/null +++ b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/editable_directory_pep_610-1.2.3.dist-info/direct_url.json @@ -0,0 +1,6 @@ +{ + "url": "file:///path/to/distributions/directory-pep-610", + "dir_info": { + "editable": true + } +} diff --git a/tests/repositories/fixtures/installed/lib/python3.7/site-packages/file_pep_610-1.2.3.dist-info/METADATA b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/file_pep_610-1.2.3.dist-info/METADATA new file mode 100644 index 00000000000..9478ca1f064 --- /dev/null +++ b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/file_pep_610-1.2.3.dist-info/METADATA @@ -0,0 +1,6 @@ +Metadata-Version: 2.1 +Name: file-pep-610 +Version: 1.2.3 +Summary: Foo +License: MIT +Requires-Python: >=3.6 diff --git a/tests/repositories/fixtures/installed/lib/python3.7/site-packages/file_pep_610-1.2.3.dist-info/direct_url.json b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/file_pep_610-1.2.3.dist-info/direct_url.json new file mode 100644 index 00000000000..d481649fa32 --- /dev/null +++ b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/file_pep_610-1.2.3.dist-info/direct_url.json @@ -0,0 +1,6 @@ +{ + "url": "file:///path/to/distributions/file-pep-610-1.2.3.tar.gz", + "archive_info": { + "hash": "sha256=2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae" + } +} diff --git a/tests/repositories/fixtures/installed/lib/python3.7/site-packages/git_pep_610-1.2.3.dist-info/METADATA b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/git_pep_610-1.2.3.dist-info/METADATA new file mode 100644 index 00000000000..bfc73cf720c --- /dev/null +++ b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/git_pep_610-1.2.3.dist-info/METADATA @@ -0,0 +1,6 @@ +Metadata-Version: 2.1 +Name: git-pep-610 +Version: 1.2.3 +Summary: Foo +License: MIT +Requires-Python: >=3.6 diff --git a/tests/repositories/fixtures/installed/lib/python3.7/site-packages/git_pep_610-1.2.3.dist-info/direct_url.json b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/git_pep_610-1.2.3.dist-info/direct_url.json new file mode 100644 index 00000000000..a3115254845 --- /dev/null +++ b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/git_pep_610-1.2.3.dist-info/direct_url.json @@ -0,0 +1,8 @@ +{ + "url": "https://github.com/demo/git-pep-610.git", + "vcs_info": { + "vcs": "git", + "requested_revision": "my-branch", + "commit_id": "123456" + } +} diff --git a/tests/repositories/fixtures/installed/lib/python3.7/site-packages/url_pep_610-1.2.3.dist-info/METADATA b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/url_pep_610-1.2.3.dist-info/METADATA new file mode 100644 index 00000000000..7b2afd3189b --- /dev/null +++ b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/url_pep_610-1.2.3.dist-info/METADATA @@ -0,0 +1,6 @@ +Metadata-Version: 2.1 +Name: url-pep-610 +Version: 1.2.3 +Summary: Foo +License: MIT +Requires-Python: >=3.6 diff --git a/tests/repositories/fixtures/installed/lib/python3.7/site-packages/url_pep_610-1.2.3.dist-info/direct_url.json b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/url_pep_610-1.2.3.dist-info/direct_url.json new file mode 100644 index 00000000000..b36e4055294 --- /dev/null +++ b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/url_pep_610-1.2.3.dist-info/direct_url.json @@ -0,0 +1,4 @@ +{ + "url": "https://python-poetry.org/distributions/url-pep-610-1.2.3.tar.gz", + "archive_info": {} +} diff --git a/tests/repositories/test_installed_repository.py b/tests/repositories/test_installed_repository.py index d10ab37af1d..5342181588d 100644 --- a/tests/repositories/test_installed_repository.py +++ b/tests/repositories/test_installed_repository.py @@ -30,6 +30,13 @@ metadata.PathDistribution(SITE_PURELIB / "editable-with-import-2.3.4.dist-info"), metadata.PathDistribution(SITE_PLATLIB / "lib64-2.3.4.dist-info"), metadata.PathDistribution(SITE_PLATLIB / "bender-2.0.5.dist-info"), + metadata.PathDistribution(SITE_PURELIB / "git_pep_610-1.2.3.dist-info"), + metadata.PathDistribution(SITE_PURELIB / "url_pep_610-1.2.3.dist-info"), + metadata.PathDistribution(SITE_PURELIB / "file_pep_610-1.2.3.dist-info"), + metadata.PathDistribution(SITE_PURELIB / "directory_pep_610-1.2.3.dist-info"), + metadata.PathDistribution( + SITE_PURELIB / "editable_directory_pep_610-1.2.3.dist-info" + ), ] @@ -165,3 +172,60 @@ def test_load_standard_package_with_pth_file(repository): assert standard.version.text == "1.2.3" assert standard.source_type is None assert standard.source_url is None + + +def test_load_pep_610_compliant_git_packages(repository): + package = get_package_from_repository("git-pep-610", repository) + + assert package is not None + assert package.name == "git-pep-610" + assert package.version.text == "1.2.3" + assert package.source_type == "git" + assert package.source_url == "https://github.com/demo/git-pep-610.git" + assert package.source_reference == "my-branch" + assert package.source_resolved_reference == "123456" + + +def test_load_pep_610_compliant_url_packages(repository): + package = get_package_from_repository("url-pep-610", repository) + + assert package is not None + assert package.name == "url-pep-610" + assert package.version.text == "1.2.3" + assert package.source_type == "url" + assert ( + package.source_url + == "https://python-poetry.org/distributions/url-pep-610-1.2.3.tar.gz" + ) + + +def test_load_pep_610_compliant_file_packages(repository): + package = get_package_from_repository("file-pep-610", repository) + + assert package is not None + assert package.name == "file-pep-610" + assert package.version.text == "1.2.3" + assert package.source_type == "file" + assert package.source_url == "/path/to/distributions/file-pep-610-1.2.3.tar.gz" + + +def test_load_pep_610_compliant_directory_packages(repository): + package = get_package_from_repository("directory-pep-610", repository) + + assert package is not None + assert package.name == "directory-pep-610" + assert package.version.text == "1.2.3" + assert package.source_type == "directory" + assert package.source_url == "/path/to/distributions/directory-pep-610" + assert not package.develop + + +def test_load_pep_610_compliant_editable_directory_packages(repository): + package = get_package_from_repository("editable-directory-pep-610", repository) + + assert package is not None + assert package.name == "editable-directory-pep-610" + assert package.version.text == "1.2.3" + assert package.source_type == "directory" + assert package.source_url == "/path/to/distributions/directory-pep-610" + assert package.develop From f1b1cf9345837b916e15837d0258f1cc6a0b3f91 Mon Sep 17 00:00:00 2001 From: Dima Tisnek Date: Fri, 2 Apr 2021 15:25:29 +0900 Subject: [PATCH 158/222] Document 1.1.5 in change log text taken from https://github.com/python-poetry/poetry/releases/tag/1.1.5 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7fe3d6e588..c7ad0bc3f69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Change Log +## [1.1.5] - 2021-03-04 + +### Fixed +- Fixed an error in the export command when no lock file existed and a verbose flag was passed to the command. (#3310) +- Fixed an error where the pyproject.toml was not reverted when using the add command. (#3622) +- Fixed errors when using non-HTTPS indices. (#3622) +- Fixed errors when handling simple indices redirection. (#3622) +- Fixed errors when trying to handle newer wheels by using the latest version of poetry-core and packaging. (#3677) +- Fixed an error when using some versions of poetry-core due to an incorrect import. (#3696) + ## [1.1.4] - 2020-10-23 ### Added From 48339106eb0d403a3c66519317488c8185844b32 Mon Sep 17 00:00:00 2001 From: Eero Ruohola Date: Thu, 1 Apr 2021 16:10:57 +0300 Subject: [PATCH 159/222] Add a missing " to the post install message of `install-poetry.py` --- install-poetry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install-poetry.py b/install-poetry.py index 7cb19c85296..ba67d3df8c8 100644 --- a/install-poetry.py +++ b/install-poetry.py @@ -267,7 +267,7 @@ def temporary_directory(*args, **kwargs): """ POST_MESSAGE_CONFIGURE_UNIX = """ -Add `export PATH="{poetry_home_bin}:$PATH` to your shell configuration file. +Add `export PATH="{poetry_home_bin}:$PATH"` to your shell configuration file. """ POST_MESSAGE_CONFIGURE_FISH = """ From df8f40e6f298fd9d5f164dff8da596a4d89efda2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 2 Apr 2021 15:32:35 +0200 Subject: [PATCH 160/222] Fix locked VCS dependencies always being updated --- poetry/installation/executor.py | 7 ++- poetry/installation/installer.py | 11 ++--- poetry/installation/pip_installer.py | 7 ++- tests/installation/test_installer.py | 64 ++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 9 deletions(-) diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index 84176431640..d9f1b4d8981 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -606,7 +606,12 @@ def _install_git(self, operation: Union[Install, Update]) -> int: git = Git() git.clone(package.source_url, src_dir) - git.checkout(package.source_resolved_reference, src_dir) + + reference = package.source_resolved_reference + if not reference: + reference = package.source_reference + + git.checkout(reference, src_dir) # Now we just need to install from the source directory original_url = package.source_url diff --git a/poetry/installation/installer.py b/poetry/installation/installer.py index 0b91efa35f6..0718eef52d0 100644 --- a/poetry/installation/installer.py +++ b/poetry/installation/installer.py @@ -309,12 +309,6 @@ def _do_install(self, local_repo: Repository) -> int: pool.add_repository(repo) - # We whitelist all packages to be sure - # that the latest ones are picked up - whitelist = [] - for pkg in locked_repository.packages: - whitelist.append(pkg.name) - solver = Solver( root, pool, @@ -323,9 +317,12 @@ def _do_install(self, local_repo: Repository) -> int: NullIO(), remove_untracked=self._remove_untracked, ) + # Everything is resolved at this point, so we no longer need + # to load deferred dependencies (i.e. VCS, URL and path dependencies) + solver.provider.load_deferred(False) with solver.use_environment(self._env): - ops = solver.solve(use_latest=whitelist) + ops = solver.solve(use_latest=self._whitelist) # We need to filter operations so that packages # not compatible with the current system, diff --git a/poetry/installation/pip_installer.py b/poetry/installation/pip_installer.py index 9c70f338afe..7ed81dd2c91 100644 --- a/poetry/installation/pip_installer.py +++ b/poetry/installation/pip_installer.py @@ -251,7 +251,12 @@ def install_git(self, package: "Package") -> None: git = Git() git.clone(package.source_url, src_dir) - git.checkout(package.source_reference, src_dir) + + reference = package.source_resolved_reference + if not reference: + reference = package.source_reference + + git.checkout(reference, src_dir) # Now we just need to install from the source directory pkg = Package(package.name, package.version) diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index a671e6b4d0a..e9bc0e4e1c9 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -14,6 +14,7 @@ from cleo.io.outputs.output import Verbosity from deepdiff import DeepDiff +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 @@ -1924,3 +1925,66 @@ def test_run_with_dependencies_quiet(installer, locker, repo, package, quiet): assert installer._io.output._buffer.read() == "" else: assert installer._io.output._buffer.read() != "" + + +def test_installer_should_use_the_locked_version_of_git_dependencies( + installer, locker, package, repo +): + locker.locked(True) + locker.mock_lock_data( + { + "package": [ + { + "name": "demo", + "version": "0.1.1", + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + "dependencies": {"pendulum": ">=1.4.4"}, + "source": { + "type": "git", + "url": "https://github.com/demo/demo.git", + "reference": "master", + "resolved_reference": "123456", + }, + }, + { + "name": "pendulum", + "version": "1.4.4", + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + "dependencies": {}, + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "hashes": {"demo": [], "pendulum": []}, + }, + } + ) + + package.add_dependency( + Factory.create_dependency( + "demo", {"git": "https://github.com/demo/demo.git", "branch": "master"} + ) + ) + + repo.add_package(get_package("pendulum", "1.4.4")) + + installer.run() + + assert installer.executor.installations[-1] == Package( + "demo", + "0.1.1", + source_type="git", + source_url="https://github.com/demo/demo.git", + source_reference="master", + source_resolved_reference="123456", + ) From a12d14285456d4fe57eac406e61444ff955c53e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Tue, 6 Apr 2021 15:58:06 +0200 Subject: [PATCH 161/222] Update poetry-core --- poetry.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3c591d347ac..89acdc2aad1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -390,7 +390,7 @@ dev = ["pre-commit", "tox"] [[package]] name = "poetry-core" -version = "1.1.0a0" +version = "1.1.0a1" description = "Poetry PEP 517 Build Backend" category = "main" optional = false @@ -405,7 +405,7 @@ importlib-metadata = {version = "^1.7.0", markers = "python_version >= \"3.5\" a type = "git" url = "https://github.com/python-poetry/poetry-core.git" reference = "master" -resolved_reference = "c11cb9a6ebdda53d45dae78b45f6f73f5368e793" +resolved_reference = "8d5e5c5d070940736ab72c5dec09cd7deab07cf3" [[package]] name = "pre-commit" From 4835c5910eb7ecc92eea121041d87c372e285d43 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Wed, 7 Apr 2021 23:11:12 +0200 Subject: [PATCH 162/222] pep610: handle pure/plat lib differences cleanly --- poetry/installation/executor.py | 45 +++++++++------- poetry/installation/pip_installer.py | 5 +- poetry/utils/env.py | 52 ++++++++++++++----- tests/installation/test_executor.py | 25 +++++---- tests/installation/test_pip_installer.py | 11 ++-- .../masonry/builders/test_editable_builder.py | 24 +++++---- 6 files changed, 98 insertions(+), 64 deletions(-) diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index d9f1b4d8981..6533017abcb 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -714,6 +714,19 @@ def _download_archive(self, operation: Union[Install, Update], link: Link) -> Pa def _should_write_operation(self, operation: Operation) -> bool: return not operation.skipped or self._dry_run or self._verbose + @staticmethod + def _package_dist_info_path(package: "Package") -> Path: + from poetry.core.masonry.utils.helpers import escape_name + from poetry.core.masonry.utils.helpers import escape_version + + return Path( + f"{escape_name(package.pretty_name)}-{escape_version(package.version.text)}.dist-info" + ) + + @classmethod + def _direct_url_json_path(cls, package: "Package") -> Path: + return cls._package_dist_info_path(package) / "direct_url.json" + def _save_url_reference(self, operation: "OperationTypes") -> None: """ Create and store a PEP-610 `direct_url.json` file, if needed. @@ -721,9 +734,6 @@ def _save_url_reference(self, operation: "OperationTypes") -> None: if operation.job_type not in {"install", "update"}: return - from poetry.core.masonry.utils.helpers import escape_name - from poetry.core.masonry.utils.helpers import escape_version - package = operation.package if not package.source_url: @@ -732,14 +742,10 @@ def _save_url_reference(self, operation: "OperationTypes") -> None: # distribution. # That's not what we want so we remove the direct_url.json file, # if it exists. - dist_info = self._env.site_packages.path.joinpath( - "{}-{}.dist-info".format( - escape_name(package.pretty_name), - escape_version(package.version.text), - ) - ) - if dist_info.exists() and dist_info.joinpath("direct_url.json").exists(): - dist_info.joinpath("direct_url.json").unlink() + for direct_url in self._env.site_packages.find( + self._direct_url_json_path(package), True + ): + direct_url.unlink() return @@ -755,16 +761,15 @@ def _save_url_reference(self, operation: "OperationTypes") -> None: url_reference = self._create_file_url_reference(package) if url_reference: - dist_info = self._env.site_packages.path.joinpath( - "{}-{}.dist-info".format( - escape_name(package.name), escape_version(package.version.text) - ) - ) - - if dist_info.exists(): - dist_info.joinpath("direct_url.json").write_text( - json.dumps(url_reference), encoding="utf-8" + for path in self._env.site_packages.find( + self._package_dist_info_path(package), writable_only=True + ): + self._env.site_packages.write_text( + path / "direct_url.json", + json.dumps(url_reference), + encoding="utf-8", ) + break def _create_git_url_reference( self, package: "Package" diff --git a/poetry/installation/pip_installer.py b/poetry/installation/pip_installer.py index 7ed81dd2c91..77cf0b79259 100644 --- a/poetry/installation/pip_installer.py +++ b/poetry/installation/pip_installer.py @@ -117,10 +117,7 @@ def remove(self, package: "Package") -> None: raise # This is a workaround for https://github.com/pypa/pip/issues/4176 - nspkg_pth_file = self._env.site_packages.path / "{}-nspkg.pth".format( - package.name - ) - if nspkg_pth_file.exists(): + for nspkg_pth_file in self._env.site_packages.find(f"{package.name}-nspkg.pth"): nspkg_pth_file.unlink() # If we have a VCS package, remove its source directory diff --git a/poetry/utils/env.py b/poetry/utils/env.py index 3df76495acc..390bf428c30 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -152,17 +152,34 @@ def _version_nodot(version): class SitePackages: def __init__( - self, path: Path, fallbacks: List[Path] = None, skip_write_checks: bool = False + self, + purelib: Path, + platlib: Optional[Path] = None, + fallbacks: List[Path] = None, + skip_write_checks: bool = False, ) -> None: - self._path = path + self._purelib = purelib + self._platlib = platlib or purelib + + if platlib and platlib.resolve() == purelib.resolve(): + self._platlib = purelib + self._fallbacks = fallbacks or [] self._skip_write_checks = skip_write_checks - self._candidates = [self._path] + self._fallbacks + self._candidates = list({self._purelib, self._platlib}) + self._fallbacks self._writable_candidates = None if not skip_write_checks else self._candidates @property def path(self) -> Path: - return self._path + return self._purelib + + @property + def purelib(self) -> Path: + return self._purelib + + @property + def platlib(self) -> Path: + return self._platlib @property def candidates(self) -> List[Path]: @@ -200,12 +217,16 @@ def make_candidates(self, path: Path, writable_only: bool = False) -> List[Path] return [candidate / path for candidate in candidates if candidate] def _path_method_wrapper( - self, path: Path, method: str, *args: Any, **kwargs: Any + self, + path: Union[str, Path], + method: str, + *args: Any, + return_first: bool = True, + writable_only: bool = False, + **kwargs: Any, ) -> Union[Tuple[Path, Any], List[Tuple[Path, Any]]]: - - # TODO: Move to parameters after dropping Python 2.7 - return_first = kwargs.pop("return_first", True) - writable_only = kwargs.pop("writable_only", False) + if isinstance(path, str): + path = Path(path) candidates = self.make_candidates(path, writable_only=writable_only) @@ -234,19 +255,19 @@ def _path_method_wrapper( raise OSError("Unable to access any of {}".format(paths_csv(candidates))) - def write_text(self, path: Path, *args: Any, **kwargs: Any) -> Path: + def write_text(self, path: Union[str, Path], *args: Any, **kwargs: Any) -> Path: return self._path_method_wrapper(path, "write_text", *args, **kwargs)[0] - def mkdir(self, path: Path, *args: Any, **kwargs: Any) -> Path: + def mkdir(self, path: Union[str, Path], *args: Any, **kwargs: Any) -> Path: return self._path_method_wrapper(path, "mkdir", *args, **kwargs)[0] - def exists(self, path: Path) -> bool: + def exists(self, path: Union[str, Path]) -> bool: return any( value[-1] for value in self._path_method_wrapper(path, "exists", return_first=False) ) - def find(self, path: Path, writable_only: bool = False) -> List[Path]: + def find(self, path: Union[str, Path], writable_only: bool = False) -> List[Path]: return [ value[0] for value in self._path_method_wrapper( @@ -990,7 +1011,10 @@ def site_packages(self) -> SitePackages: # we disable write checks if no user site exist fallbacks = [self.usersite] if self.usersite else [] self._site_packages = SitePackages( - self.purelib, fallbacks, skip_write_checks=False if fallbacks else True + self.purelib, + self.platlib, + fallbacks, + skip_write_checks=False if fallbacks else True, ) return self._site_packages diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index f074078638d..07054ee1eb8 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -280,9 +280,10 @@ def test_executor_should_write_pep610_url_references_for_files( executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) - dist_info = tmp_venv.site_packages.path.joinpath("demo-0.1.0.dist-info") - assert dist_info.exists() + dist_info = "demo-0.1.0.dist-info" + assert tmp_venv.site_packages.exists(dist_info) + dist_info = tmp_venv.site_packages.find(dist_info)[0] direct_url_file = dist_info.joinpath("direct_url.json") assert direct_url_file.exists() @@ -303,9 +304,10 @@ def test_executor_should_write_pep610_url_references_for_directories( executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) - dist_info = tmp_venv.site_packages.path.joinpath("simple_project-1.2.3.dist-info") - assert dist_info.exists() + dist_info = "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] direct_url_file = dist_info.joinpath("direct_url.json") assert direct_url_file.exists() @@ -330,9 +332,10 @@ def test_executor_should_write_pep610_url_references_for_editable_directories( executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) - dist_info = tmp_venv.site_packages.path.joinpath("simple_project-1.2.3.dist-info") - assert dist_info.exists() + dist_info_dir = "simple_project-1.2.3.dist-info" + assert tmp_venv.site_packages.exists(dist_info_dir) + dist_info = tmp_venv.site_packages.find(dist_info_dir)[0] direct_url_file = dist_info.joinpath("direct_url.json") assert direct_url_file.exists() @@ -355,9 +358,10 @@ def test_executor_should_write_pep610_url_references_for_urls( executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) - dist_info = tmp_venv.site_packages.path.joinpath("demo-0.1.0.dist-info") - assert dist_info.exists() + dist_info = "demo-0.1.0.dist-info" + assert tmp_venv.site_packages.exists(dist_info) + dist_info = tmp_venv.site_packages.find(dist_info)[0] direct_url_file = dist_info.joinpath("direct_url.json") assert direct_url_file.exists() @@ -385,9 +389,10 @@ def test_executor_should_write_pep610_url_references_for_git( executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) - dist_info = tmp_venv.site_packages.path.joinpath("demo-0.1.2.dist-info") - assert dist_info.exists() + dist_info = "demo-0.1.2.dist-info" + assert tmp_venv.site_packages.exists(dist_info) + dist_info = tmp_venv.site_packages.find(dist_info)[0] direct_url_file = dist_info.joinpath("direct_url.json") assert direct_url_file.exists() diff --git a/tests/installation/test_pip_installer.py b/tests/installation/test_pip_installer.py index 34c9cc6b4bd..20fc9d0a9d3 100644 --- a/tests/installation/test_pip_installer.py +++ b/tests/installation/test_pip_installer.py @@ -1,3 +1,4 @@ +import re import shutil from pathlib import Path @@ -190,11 +191,6 @@ def test_uninstall_git_package_nspkg_pth_cleanup(mocker, tmp_venv, pool): source_reference="master", ) - # we do this here because the virtual env might not be usable if failure case is triggered - pth_file_candidate = tmp_venv.site_packages.path / "{}-nspkg.pth".format( - package.name - ) - # in order to reproduce the scenario where the git source is removed prior to proper # clean up of nspkg.pth file, we need to make sure the fixture is copied and not # symlinked into the git src directory @@ -213,8 +209,9 @@ def copy_only(source, dest): installer.install(package) installer.remove(package) - assert not Path(pth_file_candidate).exists() + pth_file = 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 output = tmp_venv.run("python", "-m", "site") - assert "Error processing line 1 of {}".format(pth_file_candidate) not in output + assert not re.match(rf"Error processing line 1 of .*{pth_file}", output) diff --git a/tests/masonry/builders/test_editable_builder.py b/tests/masonry/builders/test_editable_builder.py index 05e798012d5..86ae73ece5b 100644 --- a/tests/masonry/builders/test_editable_builder.py +++ b/tests/masonry/builders/test_editable_builder.py @@ -78,16 +78,18 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ builder.build() assert tmp_venv._bin_dir.joinpath("foo").exists() - assert tmp_venv.site_packages.path.joinpath("simple_project.pth").exists() + pth_file = "simple_project.pth" + assert tmp_venv.site_packages.exists(pth_file) assert ( simple_poetry.file.parent.resolve().as_posix() - == tmp_venv.site_packages.path.joinpath("simple_project.pth") - .read_text() - .strip(os.linesep) + == tmp_venv.site_packages.find(pth_file)[0].read_text().strip(os.linesep) ) - dist_info = tmp_venv.site_packages.path.joinpath("simple_project-1.2.3.dist-info") - assert dist_info.exists() + dist_info = "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] + assert dist_info.joinpath("INSTALLER").exists() assert dist_info.joinpath("METADATA").exists() assert dist_info.joinpath("RECORD").exists() @@ -134,7 +136,9 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ assert metadata == dist_info.joinpath("METADATA").read_text(encoding="utf-8") records = dist_info.joinpath("RECORD").read_text() - assert str(tmp_venv.site_packages.path.joinpath("simple_project.pth")) in records + pth_file = "simple_project.pth" + assert tmp_venv.site_packages.exists(pth_file) + assert str(tmp_venv.site_packages.find(pth_file)[0]) in records assert str(tmp_venv._bin_dir.joinpath("foo")) in records assert str(tmp_venv._bin_dir.joinpath("baz")) in records assert str(dist_info.joinpath("METADATA")) in records @@ -201,8 +205,10 @@ def test_builder_installs_proper_files_when_packages_configured( builder = EditableBuilder(project_with_include, tmp_venv, NullIO()) builder.build() - pth_file = tmp_venv.site_packages.path.joinpath("with_include.pth") - assert pth_file.is_file() + pth_file = "with_include.pth" + assert tmp_venv.site_packages.exists(pth_file) + + pth_file = tmp_venv.site_packages.find(pth_file)[0] paths = set() with pth_file.open() as f: From 00309beef85ca32f6bef0c62c23e4d52abb27220 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 9 Apr 2021 12:04:08 +0200 Subject: [PATCH 163/222] env: ignore warnings when executing python scripts (#3895) * env: ignore warnings when executing python scripts When executing python scripts with an intent to deserialise via `json.loads` we need to ensure we ignore warnings. This is required because, warnings can cause a `JSONDecodeError`. Resolves: #3897 * ci: use current source if experimental --- .github/workflows/main.yml | 6 ++++++ poetry/utils/env.py | 17 +++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1db4a32cc47..95d7e720675 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -43,7 +43,13 @@ jobs: shell: bash run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") + - name: Bootstrap poetry (experimental) + if: ${{ matrix.experimental == true }} + shell: bash + run: python install-poetry.py -y --path . + - name: Bootstrap poetry + if: ${{ matrix.experimental == false }} shell: bash run: python install-poetry.py -y diff --git a/poetry/utils/env.py b/poetry/utils/env.py index 390bf428c30..06442f9328f 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -1126,6 +1126,9 @@ def run_pip(self, *args: str, **kwargs: Any) -> Union[int, str]: cmd = pip + list(args) return self._run(cmd, **kwargs) + def run_python_script(self, content: str, **kwargs: Any) -> str: + return self.run("python", "-W", "ignore", "-", input_=content, **kwargs) + def _run(self, cmd: List[str], **kwargs: Any) -> Union[int, str]: """ Run a command inside the Python environment. @@ -1331,16 +1334,15 @@ def __init__(self, path: Path, base: Optional[Path] = None) -> None: # In this case we need to get sys.base_prefix # from inside the virtualenv. if base is None: - self._base = Path(self.run("python", "-", input_=GET_BASE_PREFIX).strip()) + self._base = Path(self.run_python_script(GET_BASE_PREFIX).strip()) @property def sys_path(self) -> List[str]: - output = self.run("python", "-", input_=GET_SYS_PATH) - + output = self.run_python_script(GET_SYS_PATH) return json.loads(output) def get_version_info(self) -> Tuple[int]: - output = self.run("python", "-", input_=GET_PYTHON_VERSION) + output = self.run_python_script(GET_PYTHON_VERSION) return tuple([int(s) for s in output.strip().split(".")]) @@ -1378,12 +1380,12 @@ def get_supported_tags(self) -> List[Tag]: """ ) - output = self.run("python", "-", input_=script) + output = self.run_python_script(script) return [Tag(*t) for t in json.loads(output)] def get_marker_env(self) -> Dict[str, Any]: - output = self.run("python", "-", input_=GET_ENVIRONMENT_INFO) + output = self.run_python_script(GET_ENVIRONMENT_INFO) return json.loads(output) @@ -1396,8 +1398,7 @@ def get_pip_version(self) -> Version: return Version.parse(m.group(1)) def get_paths(self) -> Dict[str, str]: - output = self.run("python", "-", input_=GET_PATHS) - + output = self.run_python_script(GET_PATHS) return json.loads(output) def is_venv(self) -> bool: From 9e0d9d5e8c524ef6e422bb950c2ae534bc936a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 9 Apr 2021 16:17:28 +0200 Subject: [PATCH 164/222] Fix reverting the pyproject.toml content in the remove command --- poetry/console/commands/remove.py | 10 ++-------- tests/console/commands/test_remove.py | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 tests/console/commands/test_remove.py diff --git a/poetry/console/commands/remove.py b/poetry/console/commands/remove.py index e2b65c46a72..58af353ccc6 100644 --- a/poetry/console/commands/remove.py +++ b/poetry/console/commands/remove.py @@ -32,7 +32,6 @@ def handle(self) -> int: packages = self.argument("packages") is_dev = self.option("dev") - original_content = self.poetry.file.read() content = self.poetry.file.read() poetry_content = content["tool"]["poetry"] section = "dependencies" @@ -75,14 +74,9 @@ def handle(self) -> int: self._installer.update(True) self._installer.whitelist(requirements) - try: - status = self._installer.run() - except Exception: - self.poetry.file.write(original_content) + status = self._installer.run() - raise - - if not self.option("dry-run"): + if not self.option("dry-run") and status == 0: self.poetry.file.write(content) return status diff --git a/tests/console/commands/test_remove.py b/tests/console/commands/test_remove.py new file mode 100644 index 00000000000..7ccddb5140b --- /dev/null +++ b/tests/console/commands/test_remove.py @@ -0,0 +1,24 @@ +import pytest + +from poetry.core.packages.package import Package + + +@pytest.fixture() +def tester(command_tester_factory): + return command_tester_factory("remove") + + +def test_remove_command_should_not_write_changes_upon_installer_errors( + tester, app, repo, command_tester_factory, mocker +): + repo.add_package(Package("foo", "2.0.0")) + + command_tester_factory("add").execute("foo") + + mocker.patch("poetry.installation.installer.Installer.run", return_value=1) + + original_content = app.poetry.file.read().as_string() + + tester.execute("foo") + + assert app.poetry.file.read().as_string() == original_content From bf18a8f700c9b717e9ab36571a2ef3010a09132a Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 9 Apr 2021 17:24:42 +0200 Subject: [PATCH 165/222] tests: add case for transitive markers propagation (#3904) * tests: add case for transitive markers propagation Relates-to: #3878 * deps: update poetry-core Relates-to: #3878 --- poetry.lock | 4 ++-- tests/puzzle/test_solver.py | 44 +++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 89acdc2aad1..ded07f4134e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -390,7 +390,7 @@ dev = ["pre-commit", "tox"] [[package]] name = "poetry-core" -version = "1.1.0a1" +version = "1.1.0a2" description = "Poetry PEP 517 Build Backend" category = "main" optional = false @@ -405,7 +405,7 @@ importlib-metadata = {version = "^1.7.0", markers = "python_version >= \"3.5\" a type = "git" url = "https://github.com/python-poetry/poetry-core.git" reference = "master" -resolved_reference = "8d5e5c5d070940736ab72c5dec09cd7deab07cf3" +resolved_reference = "d3e60732ce9bd4f30dee3e594405fe6a80163b7e" [[package]] name = "pre-commit" diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 37693b6333e..13ef289dcd5 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -780,6 +780,50 @@ def test_solver_sub_dependencies_with_not_supported_python_version( check_solver_result(ops, [{"job": "install", "package": package_a}]) +def test_solver_sub_dependencies_with_not_supported_python_version_transitive( + solver, repo, package +): + solver.provider.set_package_python_versions("^3.4") + + package.add_dependency( + Factory.create_dependency("httpx", {"version": "^0.17.1", "python": "^3.6"}) + ) + + httpx = get_package("httpx", "0.17.1") + httpx.python_versions = ">=3.6" + + httpcore = get_package("httpcore", "0.12.3") + httpcore.python_versions = ">=3.6" + + sniffio_1_1_0 = get_package("sniffio", "1.1.0") + sniffio_1_1_0.python_versions = ">=3.5" + + sniffio = get_package("sniffio", "1.2.0") + sniffio.python_versions = ">=3.5" + + httpx.add_dependency( + Factory.create_dependency("httpcore", {"version": ">=0.12.1,<0.13"}) + ) + httpx.add_dependency(Factory.create_dependency("sniffio", {"version": "*"})) + httpcore.add_dependency(Factory.create_dependency("sniffio", {"version": "==1.*"})) + + repo.add_package(httpx) + repo.add_package(httpcore) + repo.add_package(sniffio) + repo.add_package(sniffio_1_1_0) + + ops = solver.solve() + + check_solver_result( + ops, + [ + {"job": "install", "package": sniffio, "skipped": False}, + {"job": "install", "package": httpcore, "skipped": False}, + {"job": "install", "package": httpx, "skipped": False}, + ], + ) + + def test_solver_with_dependency_in_both_main_and_dev_dependencies( solver, repo, package ): From 977a72ca4614b6bb6e5ac885ec666e18c03c0a1d Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 9 Apr 2021 01:22:04 +0200 Subject: [PATCH 166/222] site: use importlib instead of adhoc file searches This change replaces various cases in which installed distribution and file look-ups used path searches with importlib.metadata backed look-ups. This fixes issues with `dist-info` lookup for PEP610 implementation as well as `.pth` file look-up and `dist-info` removal. --- poetry/installation/executor.py | 32 ++--- poetry/installation/pip_installer.py | 4 +- poetry/masonry/builders/editable.py | 36 +++--- poetry/repositories/installed_repository.py | 8 +- poetry/utils/env.py | 123 +++++++++++++++++--- tests/installation/test_executor.py | 110 +++++++---------- 6 files changed, 187 insertions(+), 126 deletions(-) diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index 6533017abcb..6e45dd1318f 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -714,19 +714,6 @@ def _download_archive(self, operation: Union[Install, Update], link: Link) -> Pa def _should_write_operation(self, operation: Operation) -> bool: return not operation.skipped or self._dry_run or self._verbose - @staticmethod - def _package_dist_info_path(package: "Package") -> Path: - from poetry.core.masonry.utils.helpers import escape_name - from poetry.core.masonry.utils.helpers import escape_version - - return Path( - f"{escape_name(package.pretty_name)}-{escape_version(package.version.text)}.dist-info" - ) - - @classmethod - def _direct_url_json_path(cls, package: "Package") -> Path: - return cls._package_dist_info_path(package) / "direct_url.json" - def _save_url_reference(self, operation: "OperationTypes") -> None: """ Create and store a PEP-610 `direct_url.json` file, if needed. @@ -742,11 +729,14 @@ def _save_url_reference(self, operation: "OperationTypes") -> None: # distribution. # That's not what we want so we remove the direct_url.json file, # if it exists. - for direct_url in self._env.site_packages.find( - self._direct_url_json_path(package), True + for ( + direct_url_json + ) in self._env.site_packages.find_distribution_direct_url_json_files( + distribution_name=package.name, writable_only=True ): - direct_url.unlink() - + # We can't use unlink(missing_ok=True) because it's not always available + if direct_url_json.exists(): + direct_url_json.unlink() return url_reference = None @@ -761,15 +751,13 @@ def _save_url_reference(self, operation: "OperationTypes") -> None: url_reference = self._create_file_url_reference(package) if url_reference: - for path in self._env.site_packages.find( - self._package_dist_info_path(package), writable_only=True + for dist in self._env.site_packages.distributions( + name=package.name, writable_only=True ): - self._env.site_packages.write_text( - path / "direct_url.json", + dist._path.joinpath("direct_url.json").write_text( json.dumps(url_reference), encoding="utf-8", ) - break def _create_git_url_reference( self, package: "Package" diff --git a/poetry/installation/pip_installer.py b/poetry/installation/pip_installer.py index 77cf0b79259..438864a3bdb 100644 --- a/poetry/installation/pip_installer.py +++ b/poetry/installation/pip_installer.py @@ -117,7 +117,9 @@ def remove(self, package: "Package") -> None: raise # This is a workaround for https://github.com/pypa/pip/issues/4176 - for nspkg_pth_file in self._env.site_packages.find(f"{package.name}-nspkg.pth"): + for nspkg_pth_file in self._env.site_packages.find_distribution_nspkg_pth_files( + distribution_name=package.name + ): nspkg_pth_file.unlink() # If we have a VCS package, remove its source directory diff --git a/poetry/masonry/builders/editable.py b/poetry/masonry/builders/editable.py index c78af6c65a6..2483a5ec704 100644 --- a/poetry/masonry/builders/editable.py +++ b/poetry/masonry/builders/editable.py @@ -61,6 +61,15 @@ def build(self) -> None: self._run_build_script(self._package.build_script) + for removed in self._env.site_packages.remove_distribution_files( + distribution_name=self._package.name + ): + self._debug( + " - Removed {} directory from {}".format( + removed.name, removed.parent + ) + ) + added_files = [] added_files += self._add_pth() added_files += self._add_scripts() @@ -115,6 +124,18 @@ def _add_pth(self) -> List[Path]: content += decode(path + os.linesep) pth_file = Path(self._module.name).with_suffix(".pth") + + # remove any pre-existing pth files for this package + for file in self._env.site_packages.find(path=pth_file, writable_only=True): + self._debug( + " - Removing existing {} from {} for {}".format( + file.name, file.parent, self._poetry.file.parent + ) + ) + # We can't use unlink(missing_ok=True) because it's not always available + if file.exists(): + file.unlink() + try: pth_file = self._env.site_packages.write_text( pth_file, content, encoding="utf-8" @@ -199,20 +220,7 @@ def _add_dist_info(self, added_files: List[Path]) -> None: added_files = added_files[:] builder = WheelBuilder(self._poetry) - - dist_info_path = Path(builder.dist_info) - for dist_info in self._env.site_packages.find( - dist_info_path, writable_only=True - ): - if dist_info.exists(): - self._debug( - " - Removing existing {} directory from {}".format( - dist_info.name, dist_info.parent - ) - ) - shutil.rmtree(str(dist_info)) - - dist_info = self._env.site_packages.mkdir(dist_info_path) + dist_info = self._env.site_packages.mkdir(Path(builder.dist_info)) self._debug( " - Adding the {} directory to {}".format( diff --git a/poetry/repositories/installed_repository.py b/poetry/repositories/installed_repository.py index d89c9837c98..841c0a5e610 100644 --- a/poetry/repositories/installed_repository.py +++ b/poetry/repositories/installed_repository.py @@ -2,7 +2,6 @@ import json from pathlib import Path -from typing import TYPE_CHECKING from typing import Set from typing import Tuple from typing import Union @@ -19,9 +18,6 @@ _VENDORS = Path(__file__).parent.parent.joinpath("_vendor") -if TYPE_CHECKING: - from importlib.metadata import Distribution - try: FileNotFoundError @@ -102,7 +98,7 @@ def is_vcs_package(cls, package: Union[Path, Package], env: Env) -> bool: @classmethod def create_package_from_distribution( - cls, distribution: "Distribution", env: "Env" + cls, distribution: metadata.Distribution, env: "Env" ) -> Package: # We first check for a direct_url.json file to determine # the type of package. @@ -172,7 +168,7 @@ def create_package_from_distribution( return package @classmethod - def create_package_from_pep610(cls, distribution: "Distribution") -> Package: + def create_package_from_pep610(cls, distribution: metadata.Distribution) -> Package: path = Path(str(distribution._path)) source_type = None source_url = None diff --git a/poetry/utils/env.py b/poetry/utils/env.py index 06442f9328f..ba9519cbc2e 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -1,5 +1,6 @@ import base64 import hashlib +import itertools import json import os import platform @@ -17,6 +18,7 @@ from typing import Any from typing import ContextManager from typing import Dict +from typing import Iterable from typing import Iterator from typing import List from typing import Optional @@ -43,6 +45,7 @@ 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 is_dir_writable from poetry.utils.helpers import paths_csv from poetry.utils.helpers import temporary_directory @@ -166,7 +169,12 @@ def __init__( self._fallbacks = fallbacks or [] self._skip_write_checks = skip_write_checks - self._candidates = list({self._purelib, self._platlib}) + self._fallbacks + + self._candidates: List[Path] = [] + for path in itertools.chain([self._purelib, self._platlib], self._fallbacks): + if path not in self._candidates: + self._candidates.append(path) + self._writable_candidates = None if not skip_write_checks else self._candidates @property @@ -198,7 +206,9 @@ def writable_candidates(self) -> List[Path]: return self._writable_candidates - def make_candidates(self, path: Path, writable_only: bool = False) -> List[Path]: + def make_candidates( + self, path: Path, writable_only: bool = False, strict: bool = False + ) -> List[Path]: candidates = self._candidates if not writable_only else self.writable_candidates if path.is_absolute(): for candidate in candidates: @@ -214,7 +224,94 @@ def make_candidates(self, path: Path, writable_only: bool = False) -> List[Path] ) ) - return [candidate / path for candidate in candidates if candidate] + results = [candidate / path for candidate in candidates if candidate] + + if not results and strict: + raise RuntimeError( + 'Unable to find a suitable destination for "{}" in {}'.format( + str(path), paths_csv(self._candidates) + ) + ) + + return results + + def distributions( + self, name: Optional[str] = None, writable_only: bool = False + ) -> Iterable[metadata.PathDistribution]: + path = list( + map( + str, self._candidates if not writable_only else self.writable_candidates + ) + ) + for distribution in metadata.PathDistribution.discover( + name=name, path=path + ): # type: metadata.PathDistribution + yield distribution + + def find_distribution( + self, name: str, writable_only: bool = False + ) -> Optional[metadata.PathDistribution]: + for distribution in self.distributions(name=name, writable_only=writable_only): + return distribution + else: + return None + + def find_distribution_files_with_suffix( + self, distribution_name: str, suffix: str, writable_only: bool = False + ) -> Iterable[Path]: + for distribution in self.distributions( + name=distribution_name, writable_only=writable_only + ): + for file in distribution.files: + if file.name.endswith(suffix): + yield Path(distribution.locate_file(file)) + + def find_distribution_files_with_name( + self, distribution_name: str, name: str, writable_only: bool = False + ) -> Iterable[Path]: + for distribution in self.distributions( + name=distribution_name, writable_only=writable_only + ): + for file in distribution.files: + if file.name == name: + yield Path(distribution.locate_file(file)) + + def find_distribution_nspkg_pth_files( + self, distribution_name: str, writable_only: bool = False + ) -> Iterable[Path]: + return self.find_distribution_files_with_suffix( + distribution_name=distribution_name, + suffix="-nspkg.pth", + writable_only=writable_only, + ) + + def find_distribution_direct_url_json_files( + self, distribution_name: str, writable_only: bool = False + ) -> Iterable[Path]: + return self.find_distribution_files_with_name( + distribution_name=distribution_name, + name="direct_url.json", + writable_only=writable_only, + ) + + def remove_distribution_files(self, distribution_name: str) -> List[Path]: + paths = [] + + for distribution in self.distributions( + name=distribution_name, writable_only=True + ): + for file in distribution.files: + file = Path(distribution.locate_file(file)) + # We can't use unlink(missing_ok=True) because it's not always available + if file.exists(): + file.unlink() + + if distribution._path.exists(): + shutil.rmtree(str(distribution._path)) + + paths.append(distribution._path) + + return paths def _path_method_wrapper( self, @@ -228,14 +325,9 @@ def _path_method_wrapper( if isinstance(path, str): path = Path(path) - candidates = self.make_candidates(path, writable_only=writable_only) - - if not candidates: - raise RuntimeError( - 'Unable to find a suitable destination for "{}" in {}'.format( - str(path), paths_csv(self._candidates) - ) - ) + candidates = self.make_candidates( + path, writable_only=writable_only, strict=True + ) results = [] @@ -244,8 +336,7 @@ def _path_method_wrapper( result = candidate, getattr(candidate, method)(*args, **kwargs) if return_first: return result - else: - results.append(result) + results.append(result) except OSError: # TODO: Replace with PermissionError pass @@ -267,7 +358,11 @@ def exists(self, path: Union[str, Path]) -> bool: for value in self._path_method_wrapper(path, "exists", return_first=False) ) - def find(self, path: Union[str, Path], writable_only: bool = False) -> List[Path]: + def find( + self, + path: Union[str, Path], + writable_only: bool = False, + ) -> List[Path]: return [ value[0] for value in self._path_method_wrapper( diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 07054ee1eb8..910a4962655 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -265,6 +265,24 @@ def test_executor_should_delete_incomplete_downloads( assert not destination_fixture.exists() +def verify_installed_distribution(venv, package, url_reference=None): + distributions = list(venv.site_packages.distributions(name=package.name)) + assert len(distributions) == 1 + + distribution = distributions[0] + metadata = distribution.metadata + assert metadata["Name"] == package.name + assert metadata["Version"] == package.version.text + + direct_url_file = distribution._path.joinpath("direct_url.json") + + if url_reference is not None: + assert direct_url_file.exists() + assert json.loads(direct_url_file.read_text(encoding="utf-8")) == url_reference + else: + assert not direct_url_file.exists() + + def test_executor_should_write_pep610_url_references_for_files( tmp_venv, pool, config, io ): @@ -279,18 +297,9 @@ def test_executor_should_write_pep610_url_references_for_files( executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) - - dist_info = "demo-0.1.0.dist-info" - assert tmp_venv.site_packages.exists(dist_info) - - dist_info = tmp_venv.site_packages.find(dist_info)[0] - direct_url_file = dist_info.joinpath("direct_url.json") - - assert direct_url_file.exists() - - url_reference = json.loads(direct_url_file.read_text(encoding="utf-8")) - - assert url_reference == {"archive_info": {}, "url": url.as_uri()} + verify_installed_distribution( + tmp_venv, package, {"archive_info": {}, "url": url.as_uri()} + ) def test_executor_should_write_pep610_url_references_for_directories( @@ -303,18 +312,9 @@ def test_executor_should_write_pep610_url_references_for_directories( executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) - - dist_info = "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] - direct_url_file = dist_info.joinpath("direct_url.json") - - assert direct_url_file.exists() - - url_reference = json.loads(direct_url_file.read_text(encoding="utf-8")) - - assert url_reference == {"dir_info": {}, "url": url.as_uri()} + verify_installed_distribution( + tmp_venv, package, {"dir_info": {}, "url": url.as_uri()} + ) def test_executor_should_write_pep610_url_references_for_editable_directories( @@ -331,18 +331,9 @@ def test_executor_should_write_pep610_url_references_for_editable_directories( executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) - - dist_info_dir = "simple_project-1.2.3.dist-info" - assert tmp_venv.site_packages.exists(dist_info_dir) - - dist_info = tmp_venv.site_packages.find(dist_info_dir)[0] - direct_url_file = dist_info.joinpath("direct_url.json") - - assert direct_url_file.exists() - - url_reference = json.loads(direct_url_file.read_text(encoding="utf-8")) - - assert url_reference == {"dir_info": {"editable": True}, "url": url.as_uri()} + verify_installed_distribution( + tmp_venv, package, {"dir_info": {"editable": True}, "url": url.as_uri()} + ) def test_executor_should_write_pep610_url_references_for_urls( @@ -357,21 +348,9 @@ def test_executor_should_write_pep610_url_references_for_urls( executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) - - dist_info = "demo-0.1.0.dist-info" - assert tmp_venv.site_packages.exists(dist_info) - - dist_info = tmp_venv.site_packages.find(dist_info)[0] - direct_url_file = dist_info.joinpath("direct_url.json") - - assert direct_url_file.exists() - - url_reference = json.loads(direct_url_file.read_text(encoding="utf-8")) - - assert url_reference == { - "archive_info": {}, - "url": "https://files.pythonhosted.org/demo-0.1.0-py2.py3-none-any.whl", - } + verify_installed_distribution( + tmp_venv, package, {"archive_info": {}, "url": package.source_url} + ) def test_executor_should_write_pep610_url_references_for_git( @@ -388,22 +367,15 @@ def test_executor_should_write_pep610_url_references_for_git( executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) - - dist_info = "demo-0.1.2.dist-info" - assert tmp_venv.site_packages.exists(dist_info) - - dist_info = tmp_venv.site_packages.find(dist_info)[0] - direct_url_file = dist_info.joinpath("direct_url.json") - - assert direct_url_file.exists() - - url_reference = json.loads(direct_url_file.read_text(encoding="utf-8")) - - assert url_reference == { - "vcs_info": { - "vcs": "git", - "requested_revision": "master", - "commit_id": "123456", + verify_installed_distribution( + tmp_venv, + package, + { + "vcs_info": { + "vcs": "git", + "requested_revision": "master", + "commit_id": "123456", + }, + "url": package.source_url, }, - "url": "https://github.com/demo/demo.git", - } + ) From e175d2ae203bbae7f347605c6624d8816bad97a8 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 9 Apr 2021 22:56:20 +0200 Subject: [PATCH 167/222] ci/cirrus: remove release task --- .cirrus.yml | 51 --------------------------------------------------- 1 file changed, 51 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index e771b621abf..c8ccc47cf40 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -24,54 +24,3 @@ test_task: path: junit.xml format: junit type: text/xml - -release_task: - name: "Release / FreeBSD" - only_if: $CIRRUS_TAG != '' - env: - GITHUB_TOKEN: ENCRYPTED[2b573a2d28a03523ac6fb5b3c2f513a41c0a98db81e40e50e1d103b171f85c57e58ae38d957499dbf7fd7635cfcfd7be] - PYTHON: python3.8 - PYTHON36: python3.6 - PYTHON37: python3.7 - PYTHON38: python3.8 - freebsd_instance: - matrix: - - image_family: freebsd-12-1-snap - - image_family: freebsd-13-0-snap - - image_family: freebsd-11-4-snap - python_script: pkg install -y curl bash jq python3 python36 python37 python38 - pip_script: - - python3.6 -m ensurepip - - python3.7 -m ensurepip - - python3.8 -m ensurepip - build_script: bash ./make-nix-release.sh - upload_script: | - #!/usr/bin/env bash - - if [[ "$CIRRUS_RELEASE" == "" ]]; then - CIRRUS_RELEASE=$(curl -sL https://api.github.com/repos/$CIRRUS_REPO_FULL_NAME/releases/tags/$CIRRUS_TAG | jq -r '.id') - if [[ "$CIRRUS_RELEASE" == "null" ]]; then - echo "Failed to find a release associated with this tag!" - exit 0 - fi - fi - - if [[ "$GITHUB_TOKEN" == "" ]]; then - echo "Please provide GitHub access token via GITHUB_TOKEN environment variable!" - exit 1 - fi - - for fpath in releases/* - do - echo "Uploading $fpath..." - name=$(basename "$fpath") - url_to_upload="https://uploads.github.com/repos/$CIRRUS_REPO_FULL_NAME/releases/$CIRRUS_RELEASE/assets?name=$name" - echo "Uploading to $url_to_upload" - curl -X POST \ - --data-binary @$fpath \ - --header "Authorization: token $GITHUB_TOKEN" \ - --header "Content-Type: application/octet-stream" \ - $url_to_upload - done - archive_artifacts: - path: "releases/*" From ecec2fc59cc2cc026bfb0367fdd272741fa35b80 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 9 Apr 2021 22:58:00 +0200 Subject: [PATCH 168/222] ci: clean up release task This change is in preparation for the removal of the old bootstrap method in favour of the new `install-poetry` script. Releases no longer require platform specific build artifacts. --- .github/workflows/release.yml | 258 ++++------------------------------ 1 file changed, 27 insertions(+), 231 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f6a15041ae0..5a37ebadbe8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,250 +6,46 @@ on: - '*.*.*' jobs: - - Linux: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Get tag - id: tag - run: | - echo ::set-output name=tag::${GITHUB_REF#refs/tags/} - - name: Building release - run: | - make linux_release - - name: Upload release file - uses: actions/upload-artifact@v1 - with: - name: poetry-${{ steps.tag.outputs.tag }}-linux.tar.gz - path: releases/poetry-${{ steps.tag.outputs.tag }}-linux.tar.gz - - name: Upload checksum file - uses: actions/upload-artifact@v1 - with: - name: poetry-${{ steps.tag.outputs.tag }}-linux.sha256sum - path: releases/poetry-${{ steps.tag.outputs.tag }}-linux.sha256sum - - MacOS: - runs-on: macos-latest - - steps: - - uses: actions/checkout@v2 - - name: Get tag - id: tag - run: | - echo ::set-output name=tag::${GITHUB_REF#refs/tags/} - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: "3.8" - - name: Install Poetry - run: | - python get-poetry.py --preview -y - source $HOME/.poetry/env - - name: Install dependencies - run: | - source $HOME/.poetry/env - poetry install --no-dev - - name: Preparing Python executables - run: | - curl -L https://github.com/sdispater/python-binaries/releases/download/3.6.8/python-3.6.8.macos.tar.xz -o python-3.6.8.tar.xz - curl -L https://github.com/sdispater/python-binaries/releases/download/3.7.6/python-3.7.6.macos.tar.xz -o python-3.7.6.tar.xz - curl -L https://github.com/sdispater/python-binaries/releases/download/3.8.3/python-3.8.3.macos.tar.xz -o python-3.8.3.tar.xz - curl -L https://github.com/sdispater/python-binaries/releases/download/3.9.0b4/python-3.9.0b4.macos.tar.xz -o python-3.9.0b4.tar.xz - tar -zxf python-3.6.8.tar.xz - tar -zxf python-3.7.6.tar.xz - tar -zxf python-3.8.3.tar.xz - tar -zxf python-3.9.0b4.tar.xz - - name: Build specific release - run: | - source $HOME/.poetry/env - poetry run python sonnet make release --ansi -P "3.6:python-3.6.8/bin/python" -P "3.7:python-3.7.6/bin/python" -P "3.8:python-3.8.3/bin/python" -P "3.9:python-3.9.0b4/bin/python" - - name: Upload release file - uses: actions/upload-artifact@v1 - with: - name: poetry-${{ steps.tag.outputs.tag }}-darwin.tar.gz - path: releases/poetry-${{ steps.tag.outputs.tag }}-darwin.tar.gz - - name: Upload checksum file - uses: actions/upload-artifact@v1 - with: - name: poetry-${{ steps.tag.outputs.tag }}-darwin.sha256sum - path: releases/poetry-${{ steps.tag.outputs.tag }}-darwin.sha256sum - - Windows: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v2 - - name: Get tag - id: tag - shell: bash - run: | - echo ::set-output name=tag::${GITHUB_REF#refs/tags/} - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: "3.8" - - name: Install Poetry - run: | - python get-poetry.py --preview -y - $env:Path += ";$env:Userprofile\.poetry\bin" - - name: Install dependencies - run: | - $env:Path += ";$env:Userprofile\.poetry\bin" - poetry install --no-dev - - name: Preparing Python executables - run: | - Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.6.8/python-3.6.8.windows.tar.xz -O python-3.6.8.tar.xz - Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.7.6/python-3.7.6.windows.tar.xz -O python-3.7.6.tar.xz - Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.8.3/python-3.8.3.windows.tar.xz -O python-3.8.3.tar.xz - Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.9.0b4/python-3.9.0b4.windows.tar.xz -O python-3.9.0b4.tar.xz - 7z x python-3.6.8.tar.xz - 7z x python-3.7.6.tar.xz - 7z x python-3.8.3.tar.xz - 7z x python-3.9.0b4.tar.xz - 7z x python-3.6.8.tar - 7z x python-3.7.6.tar - 7z x python-3.8.3.tar - 7z x python-3.9.0b4.tar - - name: Build specific release - run: | - $env:Path += ";$env:Userprofile\.poetry\bin" - poetry run python sonnet make release --ansi -P "3.6:python-3.6.8\python.exe" -P "3.7:python-3.7.6\python.exe" -P "3.8:python-3.8.3\python.exe" -P "3.9:python-3.9.0b4\python.exe" - - name: Upload release file - uses: actions/upload-artifact@v1 - with: - name: poetry-${{ steps.tag.outputs.tag }}-win32.tar.gz - path: releases/poetry-${{ steps.tag.outputs.tag }}-win32.tar.gz - - name: Upload checksum file - uses: actions/upload-artifact@v1 - with: - name: poetry-${{ steps.tag.outputs.tag }}-win32.sha256sum - path: releases/poetry-${{ steps.tag.outputs.tag }}-win32.sha256sum - Release: - needs: [Linux, MacOS, Windows] runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 + - name: Get tag id: tag - run: | - echo ::set-output name=tag::${GITHUB_REF#refs/tags/} - - name: Download Linux release file - uses: actions/download-artifact@master - with: - name: poetry-${{ steps.tag.outputs.tag }}-linux.tar.gz - path: releases/ - - name: Download Linux checksum file - uses: actions/download-artifact@master - with: - name: poetry-${{ steps.tag.outputs.tag }}-linux.sha256sum - path: releases/ - - name: Download MacOS release file - uses: actions/download-artifact@master - with: - name: poetry-${{ steps.tag.outputs.tag }}-darwin.tar.gz - path: releases/ - - name: Download MacOS checksum file - uses: actions/download-artifact@master - with: - name: poetry-${{ steps.tag.outputs.tag }}-darwin.sha256sum - path: releases/ - - name: Download Windows release file - uses: actions/download-artifact@master - with: - name: poetry-${{ steps.tag.outputs.tag }}-win32.tar.gz - path: releases/ - - name: Download Windows checksum file - uses: actions/download-artifact@master - with: - name: poetry-${{ steps.tag.outputs.tag }}-win32.sha256sum - path: releases/ - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ steps.tag.outputs.tag }} - release_name: ${{ steps.tag.outputs.tag }} - draft: false - prerelease: false - - name: Upload Linux release file asset - uses: actions/upload-release-asset@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: releases/poetry-${{ steps.tag.outputs.tag }}-linux.tar.gz - asset_name: poetry-${{ steps.tag.outputs.tag }}-linux.tar.gz - asset_content_type: application/gzip - - name: Upload Linux checksum file asset - uses: actions/upload-release-asset@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: releases/poetry-${{ steps.tag.outputs.tag }}-linux.sha256sum - asset_name: poetry-${{ steps.tag.outputs.tag }}-linux.sha256sum - asset_content_type: text/plain - - name: Upload MacOS release file asset - uses: actions/upload-release-asset@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: releases/poetry-${{ steps.tag.outputs.tag }}-darwin.tar.gz - asset_name: poetry-${{ steps.tag.outputs.tag }}-darwin.tar.gz - asset_content_type: application/gzip - - name: Upload MacOS checksum file asset - uses: actions/upload-release-asset@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: releases/poetry-${{ steps.tag.outputs.tag }}-darwin.sha256sum - asset_name: poetry-${{ steps.tag.outputs.tag }}-darwin.sha256sum - asset_content_type: text/plain - - name: Upload Windows release file asset - uses: actions/upload-release-asset@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: releases/poetry-${{ steps.tag.outputs.tag }}-win32.tar.gz - asset_name: poetry-${{ steps.tag.outputs.tag }}-win32.tar.gz - asset_content_type: application/gzip - - name: Upload Windows checksum file asset - uses: actions/upload-release-asset@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: releases/poetry-${{ steps.tag.outputs.tag }}-win32.sha256sum - asset_name: poetry-${{ steps.tag.outputs.tag }}-win32.sha256sum - asset_content_type: text/plain - - name: Set up Python 3.8 + run: echo ::set-output name=tag::${GITHUB_REF#refs/tags/} + + - name: Set up Python 3.9 uses: actions/setup-python@v2 with: - python-version: "3.8" + python-version: "3.9" + - name: Install Poetry - run: | - python get-poetry.py --preview -y - - name: Install dependencies - run: | - source $HOME/.poetry/env - poetry install --no-dev + run: python install-poetry.py -y + + - name: Update PATH + run: echo "$HOME/.local/bin" >> $GITHUB_PATH + - name: Build project for distribution + run: poetry run poetry build + + - name: Check Version + id: check-version run: | - source $HOME/.poetry/env - poetry run poetry build + [[ "$(poetry version --short)" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] \ + || echo ::set-output name=prerelease::true + + - name: Create Release + uses: ncipollo/release-action@v1 + with: + artifacts: "dist/*" + token: ${{ secrets.GITHUB_TOKEN }} + draft: false + prerelease: steps.check-version.outputs.prerelease == 'true' + - name: Publish to PyPI env: POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }} - run: | - source $HOME/.poetry/env - poetry run poetry publish + run: poetry run poetry publish From 85f922dfddb8d8e61408b5d8e53f3bffb6beb2b3 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 4 Aug 2020 16:54:49 +0200 Subject: [PATCH 169/222] layouts: improve supported layout creation - remove unnecessary template duplication - stop generating default test case (this is out of scope for poetry) - stop generating content for package `__init__.py` - simplify inheritance to only require base directory overrides - remove "StandardLayout" (replaced with `Layout`) - support handling namespace package names given in the form "a.b.c" - specify include packages by default (be explicit) --- poetry/layouts/__init__.py | 3 +- poetry/layouts/layout.py | 102 +++++++++++++++++----------- poetry/layouts/src.py | 21 ++---- poetry/layouts/standard.py | 21 ------ tests/console/commands/test_init.py | 11 ++- 5 files changed, 77 insertions(+), 81 deletions(-) diff --git a/poetry/layouts/__init__.py b/poetry/layouts/__init__.py index 291319e82c1..f7ddf8c4def 100644 --- a/poetry/layouts/__init__.py +++ b/poetry/layouts/__init__.py @@ -2,10 +2,9 @@ from .layout import Layout from .src import SrcLayout -from .standard import StandardLayout -_LAYOUTS = {"src": SrcLayout, "standard": StandardLayout} +_LAYOUTS = {"src": SrcLayout, "standard": Layout} def layout(name: str) -> Type[Layout]: diff --git a/poetry/layouts/layout.py b/poetry/layouts/layout.py index dfc2e609c95..0f0ef4bef4b 100644 --- a/poetry/layouts/layout.py +++ b/poetry/layouts/layout.py @@ -1,26 +1,22 @@ +from pathlib import Path from typing import TYPE_CHECKING from typing import Dict from typing import Optional from tomlkit import dumps +from tomlkit import inline_table from tomlkit import loads from tomlkit import table +from poetry.utils.helpers import canonicalize_name from poetry.utils.helpers import module_name if TYPE_CHECKING: - from pathlib import Path + from tomlkit.items import InlineTable from poetry.core.pyproject.toml import PyProjectTOML -TESTS_DEFAULT = """from {package_name} import __version__ - - -def test_version(): - assert __version__ == '{version}' -""" - POETRY_DEFAULT = """\ [tool.poetry] @@ -28,26 +24,15 @@ def test_version(): version = "" description = "" authors = [] - -[tool.poetry.dependencies] - -[tool.poetry.dev-dependencies] -""" - -POETRY_WITH_LICENSE = """\ -[tool.poetry] -name = "" -version = "" -description = "" -authors = [] license = "" +packages = [] [tool.poetry.dependencies] [tool.poetry.dev-dependencies] """ -BUILD_SYSTEM_MIN_VERSION = "1.0.0" +BUILD_SYSTEM_MIN_VERSION: Optional[str] = None BUILD_SYSTEM_MAX_VERSION: Optional[str] = None @@ -59,13 +44,16 @@ def __init__( description: str = "", readme_format: str = "md", author: Optional[str] = None, - license: Optional[str] = None, + license: Optional[str] = None, # noqa python: str = "*", dependencies: Optional[Dict[str, str]] = None, dev_dependencies: Optional[Dict[str, str]] = None, ): - self._project = project - self._package_name = module_name(project) + self._project = canonicalize_name(project).replace(".", "-") + self._package_path_relative = Path( + *(module_name(part) for part in canonicalize_name(project).split(".")) + ) + self._package_name = ".".join(self._package_path_relative.parts) self._version = version self._description = description self._readme_format = readme_format @@ -79,6 +67,30 @@ def __init__( self._author = author + @property + def basedir(self) -> Path: + return Path() + + @property + def package_path(self) -> Path: + return self.basedir / self._package_path_relative + + def get_package_include(self) -> Optional["InlineTable"]: + package = inline_table() + + include = self._package_path_relative.parts[0] + package.append("include", include) + + if self.basedir != Path(): + package.append("from", self.basedir.as_posix()) + else: + if include == self._project: + # package include and package name are the same, + # packages table is redundant here. + return None + + return package + def create(self, path: "Path", with_tests: bool = True) -> None: path.mkdir(parents=True, exist_ok=True) @@ -94,17 +106,25 @@ def generate_poetry_content( self, original: Optional["PyProjectTOML"] = None ) -> str: template = POETRY_DEFAULT - if self._license: - template = POETRY_WITH_LICENSE content = loads(template) + poetry_content = content["tool"]["poetry"] poetry_content["name"] = self._project poetry_content["version"] = self._version poetry_content["description"] = self._description poetry_content["authors"].append(self._author) + if self._license: poetry_content["license"] = self._license + else: + poetry_content.remove("license") + + packages = self.get_package_include() + if packages: + poetry_content["packages"].append(packages) + else: + poetry_content.remove("packages") poetry_content["dependencies"]["python"] = self._python @@ -116,9 +136,14 @@ def generate_poetry_content( # Add build system build_system = table() - build_system_version = ">=" + BUILD_SYSTEM_MIN_VERSION + build_system_version = "" + + if BUILD_SYSTEM_MIN_VERSION is not None: + build_system_version = ">=" + BUILD_SYSTEM_MIN_VERSION if BUILD_SYSTEM_MAX_VERSION is not None: - build_system_version += ",<" + BUILD_SYSTEM_MAX_VERSION + if build_system_version: + build_system_version += "," + build_system_version += "<" + BUILD_SYSTEM_MAX_VERSION build_system.add("requires", ["poetry-core" + build_system_version]) build_system.add("build-backend", "poetry.core.masonry.api") @@ -133,7 +158,11 @@ def generate_poetry_content( return content def _create_default(self, path: "Path", src: bool = True) -> None: - raise NotImplementedError() + package_path = path / self.package_path + package_path.mkdir(parents=True) + + package_init = package_path / "__init__.py" + package_init.touch() def _create_readme(self, path: "Path") -> None: if self._readme_format == "rst": @@ -143,20 +172,13 @@ def _create_readme(self, path: "Path") -> None: readme_file.touch() - def _create_tests(self, path: "Path") -> None: + @staticmethod + def _create_tests(path: "Path") -> None: tests = path / "tests" - tests_init = tests / "__init__.py" - tests_default = tests / f"test_{self._package_name}.py" - tests.mkdir() - tests_init.touch(exist_ok=False) - with tests_default.open("w", encoding="utf-8") as f: - f.write( - TESTS_DEFAULT.format( - package_name=self._package_name, version=self._version - ) - ) + tests_init = tests / "__init__.py" + tests_init.touch(exist_ok=False) def _write_poetry(self, path: "Path") -> None: content = self.generate_poetry_content() diff --git a/poetry/layouts/src.py b/poetry/layouts/src.py index 4f43263ef93..6d10e63296b 100644 --- a/poetry/layouts/src.py +++ b/poetry/layouts/src.py @@ -1,22 +1,9 @@ -from typing import TYPE_CHECKING +from pathlib import Path from .layout import Layout -if TYPE_CHECKING: - from pathlib import Path - -DEFAULT = """__version__ = '{version}' -""" - - class SrcLayout(Layout): - def _create_default(self, path: "Path") -> None: - package_path = path / "src" / self._package_name - - package_init = package_path / "__init__.py" - - package_path.mkdir(parents=True) - - with package_init.open("w", encoding="utf-8") as f: - f.write(DEFAULT.format(version=self._version)) + @property + def basedir(self) -> "Path": + return Path("src") diff --git a/poetry/layouts/standard.py b/poetry/layouts/standard.py index 9971d4aed8d..e69de29bb2d 100644 --- a/poetry/layouts/standard.py +++ b/poetry/layouts/standard.py @@ -1,21 +0,0 @@ -from typing import TYPE_CHECKING - -from .layout import Layout - - -if TYPE_CHECKING: - from pathlib import Path -DEFAULT = """__version__ = '{version}' -""" - - -class StandardLayout(Layout): - def _create_default(self, path: "Path") -> None: - package_path = path / self._package_name - - package_init = package_path / "__init__.py" - - package_path.mkdir() - - with package_init.open("w", encoding="utf-8") as f: - f.write(DEFAULT.format(version=self._version)) diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py index 087c3f2088b..e7cab36d4b7 100644 --- a/tests/console/commands/test_init.py +++ b/tests/console/commands/test_init.py @@ -66,6 +66,7 @@ def init_basic_toml(): description = "This is a description" authors = ["Your Name "] license = "MIT" +packages = [{include = "my_package"}] [tool.poetry.dependencies] python = "~2.7 || ^3.6" @@ -112,6 +113,7 @@ def test_interactive_with_dependencies(tester, repo): description = "This is a description" authors = ["Your Name "] license = "MIT" +packages = [{include = "my_package"}] [tool.poetry.dependencies] python = "~2.7 || ^3.6" @@ -144,6 +146,7 @@ def test_empty_license(tester): version = "1.2.3" description = "" authors = ["Your Name "] +packages = [{{include = "my_package"}}] [tool.poetry.dependencies] python = "^{python}" @@ -152,7 +155,6 @@ def test_empty_license(tester): """.format( python=".".join(str(c) for c in sys.version_info[:2]) ) - assert expected in tester.io.fetch_output() @@ -186,6 +188,7 @@ def test_interactive_with_git_dependencies(tester, repo): description = "This is a description" authors = ["Your Name "] license = "MIT" +packages = [{include = "my_package"}] [tool.poetry.dependencies] python = "~2.7 || ^3.6" @@ -228,6 +231,7 @@ def test_interactive_with_git_dependencies_with_reference(tester, repo): description = "This is a description" authors = ["Your Name "] license = "MIT" +packages = [{include = "my_package"}] [tool.poetry.dependencies] python = "~2.7 || ^3.6" @@ -270,6 +274,7 @@ def test_interactive_with_git_dependencies_and_other_name(tester, repo): description = "This is a description" authors = ["Your Name "] license = "MIT" +packages = [{include = "my_package"}] [tool.poetry.dependencies] python = "~2.7 || ^3.6" @@ -315,6 +320,7 @@ def test_interactive_with_directory_dependency(tester, repo, source_dir, fixture description = "This is a description" authors = ["Your Name "] license = "MIT" +packages = [{include = "my_package"}] [tool.poetry.dependencies] python = "~2.7 || ^3.6" @@ -361,6 +367,7 @@ def test_interactive_with_directory_dependency_and_other_name( description = "This is a description" authors = ["Your Name "] license = "MIT" +packages = [{include = "my_package"}] [tool.poetry.dependencies] python = "~2.7 || ^3.6" @@ -406,6 +413,7 @@ def test_interactive_with_file_dependency(tester, repo, source_dir, fixture_dir) description = "This is a description" authors = ["Your Name "] license = "MIT" +packages = [{include = "my_package"}] [tool.poetry.dependencies] python = "~2.7 || ^3.6" @@ -471,6 +479,7 @@ def test_predefined_dependency(tester, repo): description = "This is a description" authors = ["Your Name "] license = "MIT" +packages = [{include = "my_package"}] [tool.poetry.dependencies] python = "~2.7 || ^3.6" From 3987b0e09a2ee83d000428f18aea91f355037d1b Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 4 Aug 2020 17:00:18 +0200 Subject: [PATCH 170/222] command/new: support namespace package creation With this change, names containing "." are treated as namespace packages. The following behaviour is now expected. - "a.b.c" creates package name "a-b-c" with directory structure "a/b/c" - "a-b-c" creates package name "a-b-c" with directory structure "a/b/c" - "a.b_c" creates package name "a-b-c" with directory structure "a/b_c" - "a_b_c" creates package name "a-b-c" with directory structure "a_b_c" --- docs/docs/cli.md | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/docs/docs/cli.md b/docs/docs/cli.md index 64cf17941e5..fa945f413b0 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -35,8 +35,7 @@ my-package ├── my_package │ └── __init__.py └── tests - ├── __init__.py - └── test_my_package.py + └── __init__.py ``` If you want to name your project differently than the folder, you can pass @@ -62,8 +61,27 @@ my-package │ └── my_package │ └── __init__.py └── tests - ├── __init__.py - └── test_my_package.py + └── __init__.py +``` + +The `--name` option is smart enough to detect namespace packages and create +the required structure for you. + +```bash +poetry new --src --name my.package my-package +``` + +will create the following structure: + +```text +my-package +├── pyproject.toml +├── src +│ └── my +│ └── package +│ └── __init__.py +└── tests + └── __init__.py ``` ## init From f5a63be614e2c4d94d49b56d39b8bfeabc61ff66 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 4 Aug 2020 17:00:57 +0200 Subject: [PATCH 171/222] command/new: do not add pytest dependency to project --- poetry/console/commands/new.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/poetry/console/commands/new.py b/poetry/console/commands/new.py index 04c3a8ba653..786e0be8185 100644 --- a/poetry/console/commands/new.py +++ b/poetry/console/commands/new.py @@ -22,7 +22,6 @@ class NewCommand(Command): def handle(self) -> None: from pathlib import Path - from poetry.core.semver.helpers import parse_constraint from poetry.core.vcs.git import GitConfig from poetry.layouts import layout from poetry.utils.env import SystemEnv @@ -60,20 +59,12 @@ def handle(self) -> None: ".".join(str(v) for v in current_env.version_info[:2]) ) - dev_dependencies = {} - python_constraint = parse_constraint(default_python) - if parse_constraint("<3.5").allows_any(python_constraint): - dev_dependencies["pytest"] = "^4.6" - if parse_constraint(">=3.5").allows_all(python_constraint): - dev_dependencies["pytest"] = "^5.2" - layout_ = layout_( name, "0.1.0", author=author, readme_format=readme_format, python=default_python, - dev_dependencies=dev_dependencies, ) layout_.create(path) From 41b4bfdfdade7756461d528a968ac9aadf804c3f Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 4 Aug 2020 19:10:38 +0200 Subject: [PATCH 172/222] command/new: handle relative paths properly --- poetry/console/commands/new.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/poetry/console/commands/new.py b/poetry/console/commands/new.py index 786e0be8185..7e40b48414c 100644 --- a/poetry/console/commands/new.py +++ b/poetry/console/commands/new.py @@ -3,8 +3,6 @@ from cleo.helpers import argument from cleo.helpers import option -from poetry.utils.helpers import module_name - from .command import Command @@ -31,7 +29,11 @@ def handle(self) -> None: else: layout_ = layout("standard") - path = Path.cwd() / Path(self.argument("path")) + path = Path(self.argument("path")) + if not path.is_absolute(): + # we do not use resolve here due to compatibility issues for path.resolve(strict=False) + path = Path.cwd().joinpath(path) + name = self.option("name") if not name: name = path.name @@ -68,8 +70,15 @@ def handle(self) -> None: ) layout_.create(path) + path = path.resolve() + + try: + path = path.relative_to(Path.cwd()) + except ValueError: + pass + self.line( "Created package {} in {}".format( - module_name(name), path.relative_to(Path.cwd()) + layout_._package_name, path.as_posix() # noqa ) ) From 06f7c2d2613b2eecdb0952a5eb1eab19daaec87e Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 4 Aug 2020 19:11:11 +0200 Subject: [PATCH 173/222] command/new: add test coverage --- tests/console/commands/test_new.py | 142 +++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 tests/console/commands/test_new.py diff --git a/tests/console/commands/test_new.py b/tests/console/commands/test_new.py new file mode 100644 index 00000000000..1ac42facd04 --- /dev/null +++ b/tests/console/commands/test_new.py @@ -0,0 +1,142 @@ +import pytest + +from cleo.testers import CommandTester + +from poetry.console import Application +from poetry.factory import Factory +from poetry.poetry import Poetry +from poetry.utils._compat import Path # noqa + + +@pytest.fixture +def command(app, poetry): # type: (Application, Poetry) -> CommandTester + command = app.find("new") + command._pool = poetry.pool + return CommandTester(command) + + +def verify_project_directory(path, package_name, package_path, include_from=None): + package_path = Path(package_path) + assert path.is_dir() + + pyproject = path / "pyproject.toml" + assert pyproject.is_file() + + init_file = path / package_path / "__init__.py" + assert init_file.is_file() + + tests_init_file = path / "tests" / "__init__.py" + assert tests_init_file.is_file() + + poetry = Factory().create_poetry(cwd=path) + assert poetry.package.name == package_name + + if include_from: + package_include = { + "include": package_path.relative_to(include_from).parts[0], + "from": include_from, + } + else: + package_include = {"include": package_path.parts[0]} + + packages = poetry.local_config.get("packages") + + if not packages: + assert poetry.local_config.get("name") == package_include.get("include") + else: + assert len(packages) == 1 + assert packages[0] == package_include + + +@pytest.mark.parametrize( + "options,directory,package_name,package_path,include_from", + [ + ([], "package", "package", "package", None), + (["--src"], "package", "package", "src/package", "src"), + ( + ["--name namespace.package"], + "namespace-package", + "namespace-package", + "namespace/package", + None, + ), + ( + ["--src", "--name namespace.package"], + "namespace-package", + "namespace-package", + "src/namespace/package", + "src", + ), + ( + ["--name namespace.package_a"], + "namespace-package_a", + "namespace-package-a", + "namespace/package_a", + None, + ), + ( + ["--src", "--name namespace.package_a"], + "namespace-package_a", + "namespace-package-a", + "src/namespace/package_a", + "src", + ), + ( + ["--name namespace_package"], + "namespace-package", + "namespace-package", + "namespace_package", + None, + ), + ( + ["--name namespace_package", "--src"], + "namespace-package", + "namespace-package", + "src/namespace_package", + "src", + ), + ( + ["--name namespace.package"], + "package", + "namespace-package", + "namespace/package", + None, + ), + ( + ["--name namespace.package", "--src"], + "package", + "namespace-package", + "src/namespace/package", + "src", + ), + ( + ["--name namespace.package"], + "package", + "namespace-package", + "namespace/package", + None, + ), + ( + ["--name namespace.package", "--src"], + "package", + "namespace-package", + "src/namespace/package", + "src", + ), + ([], "namespace_package", "namespace-package", "namespace_package", None), + ( + ["--src", "--name namespace_package"], + "namespace_package", + "namespace-package", + "src/namespace_package", + "src", + ), + ], +) +def test_command_new( + options, directory, package_name, package_path, include_from, command, tmp_dir +): + path = Path(tmp_dir) / directory + options.append(path.as_posix()) + command.execute(" ".join(options)) + verify_project_directory(path, package_name, package_path, include_from) From e9738b1caf52515e3fa04c364dadf439ab342fb3 Mon Sep 17 00:00:00 2001 From: finswimmer Date: Thu, 6 Aug 2020 23:57:04 +0200 Subject: [PATCH 174/222] layouts: add readme to pyproject.toml on creation Relates-to: #280 Co-authored-by: Arun Babu Neelicattu --- poetry/layouts/layout.py | 23 ++++++++++++++++------- tests/console/commands/test_init.py | 22 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/poetry/layouts/layout.py b/poetry/layouts/layout.py index 0f0ef4bef4b..f4610564582 100644 --- a/poetry/layouts/layout.py +++ b/poetry/layouts/layout.py @@ -25,6 +25,7 @@ description = "" authors = [] license = "" +readme = "" packages = [] [tool.poetry.dependencies] @@ -37,6 +38,8 @@ class Layout: + ACCEPTED_README_FORMATS = {"md", "rst"} + def __init__( self, project: str, @@ -56,7 +59,15 @@ def __init__( self._package_name = ".".join(self._package_path_relative.parts) self._version = version self._description = description - self._readme_format = readme_format + + self._readme_format = readme_format.lower() + if self._readme_format not in self.ACCEPTED_README_FORMATS: + raise ValueError( + "Invalid readme format '{}', use one of {}.".format( + readme_format, ", ".join(self.ACCEPTED_README_FORMATS) + ) + ) + self._license = license self._python = python self._dependencies = dependencies or {} @@ -120,6 +131,7 @@ def generate_poetry_content( else: poetry_content.remove("license") + poetry_content["readme"] = "README.{}".format(self._readme_format) packages = self.get_package_include() if packages: poetry_content["packages"].append(packages) @@ -164,13 +176,10 @@ def _create_default(self, path: "Path", src: bool = True) -> None: package_init = package_path / "__init__.py" package_init.touch() - def _create_readme(self, path: "Path") -> None: - if self._readme_format == "rst": - readme_file = path / "README.rst" - else: - readme_file = path / "README.md" - + def _create_readme(self, path: "Path") -> "Path": + readme_file = path.joinpath("README.{}".format(self._readme_format)) readme_file.touch() + return readme_file @staticmethod def _create_tests(path: "Path") -> None: diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py index e7cab36d4b7..4ae13465399 100644 --- a/tests/console/commands/test_init.py +++ b/tests/console/commands/test_init.py @@ -66,6 +66,7 @@ def init_basic_toml(): description = "This is a description" authors = ["Your Name "] license = "MIT" +readme = "README.md" packages = [{include = "my_package"}] [tool.poetry.dependencies] @@ -113,6 +114,7 @@ def test_interactive_with_dependencies(tester, repo): description = "This is a description" authors = ["Your Name "] license = "MIT" +readme = "README.md" packages = [{include = "my_package"}] [tool.poetry.dependencies] @@ -146,6 +148,7 @@ def test_empty_license(tester): version = "1.2.3" description = "" authors = ["Your Name "] +readme = "README.md" packages = [{{include = "my_package"}}] [tool.poetry.dependencies] @@ -188,6 +191,7 @@ def test_interactive_with_git_dependencies(tester, repo): description = "This is a description" authors = ["Your Name "] license = "MIT" +readme = "README.md" packages = [{include = "my_package"}] [tool.poetry.dependencies] @@ -231,6 +235,7 @@ def test_interactive_with_git_dependencies_with_reference(tester, repo): description = "This is a description" authors = ["Your Name "] license = "MIT" +readme = "README.md" packages = [{include = "my_package"}] [tool.poetry.dependencies] @@ -274,6 +279,7 @@ def test_interactive_with_git_dependencies_and_other_name(tester, repo): description = "This is a description" authors = ["Your Name "] license = "MIT" +readme = "README.md" packages = [{include = "my_package"}] [tool.poetry.dependencies] @@ -320,6 +326,7 @@ def test_interactive_with_directory_dependency(tester, repo, source_dir, fixture description = "This is a description" authors = ["Your Name "] license = "MIT" +readme = "README.md" packages = [{include = "my_package"}] [tool.poetry.dependencies] @@ -367,6 +374,7 @@ def test_interactive_with_directory_dependency_and_other_name( description = "This is a description" authors = ["Your Name "] license = "MIT" +readme = "README.md" packages = [{include = "my_package"}] [tool.poetry.dependencies] @@ -413,6 +421,7 @@ def test_interactive_with_file_dependency(tester, repo, source_dir, fixture_dir) description = "This is a description" authors = ["Your Name "] license = "MIT" +readme = "README.md" packages = [{include = "my_package"}] [tool.poetry.dependencies] @@ -446,6 +455,8 @@ def test_python_option(tester): description = "This is a description" authors = ["Your Name "] license = "MIT" +readme = "README.md" +packages = [{include = "my_package"}] [tool.poetry.dependencies] python = "~2.7 || ^3.6" @@ -479,6 +490,7 @@ def test_predefined_dependency(tester, repo): description = "This is a description" authors = ["Your Name "] license = "MIT" +readme = "README.md" packages = [{include = "my_package"}] [tool.poetry.dependencies] @@ -520,6 +532,8 @@ def test_predefined_and_interactive_dependencies(tester, repo): description = "This is a description" authors = ["Your Name "] license = "MIT" +readme = "README.md" +packages = [{include = "my_package"}] [tool.poetry.dependencies] python = "~2.7 || ^3.6" @@ -554,6 +568,8 @@ def test_predefined_dev_dependency(tester, repo): description = "This is a description" authors = ["Your Name "] license = "MIT" +readme = "README.md" +packages = [{include = "my_package"}] [tool.poetry.dependencies] python = "~2.7 || ^3.6" @@ -594,11 +610,15 @@ def test_predefined_and_interactive_dev_dependencies(tester, repo): description = "This is a description" authors = ["Your Name "] license = "MIT" +readme = "README.md" +packages = [{include = "my_package"}] [tool.poetry.dependencies] python = "~2.7 || ^3.6" [tool.poetry.dev-dependencies] +pytest = "^3.6.0" +pytest-requests = "^0.2.0" """ output = tester.io.fetch_output() @@ -657,6 +677,8 @@ def test_init_non_interactive_existing_pyproject_add_dependency( version = "0.1.0" description = "" authors = ["Your Name "] +readme = "README.md" +packages = [{include = "my_package"}] [tool.poetry.dependencies] python = "^3.6" From affabe04c8cdfaa63d7d87b36107fd1003048688 Mon Sep 17 00:00:00 2001 From: finswimmer Date: Fri, 7 Aug 2020 00:00:41 +0200 Subject: [PATCH 175/222] command/new: make readme format configurable This change makes readme formant configurable, defaulting to markdown when using the new command. Resolves: #280 Closes: #1515 Co-authored-by: Arun Babu Neelicattu --- docs/docs/cli.md | 5 +++-- poetry/console/commands/new.py | 8 ++++++- tests/console/commands/test_new.py | 35 ++++++++++++++++++++---------- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/docs/docs/cli.md b/docs/docs/cli.md index fa945f413b0..af7c8b1a81a 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -31,7 +31,7 @@ will create a folder as follows: ```text my-package ├── pyproject.toml -├── README.rst +├── README.md ├── my_package │ └── __init__.py └── tests @@ -56,7 +56,7 @@ That will create a folder structure as follows: ```text my-package ├── pyproject.toml -├── README.rst +├── README.md ├── src │ └── my_package │ └── __init__.py @@ -76,6 +76,7 @@ will create the following structure: ```text my-package ├── pyproject.toml +├── README.md ├── src │ └── my │ └── package diff --git a/poetry/console/commands/new.py b/poetry/console/commands/new.py index 7e40b48414c..8158442f741 100644 --- a/poetry/console/commands/new.py +++ b/poetry/console/commands/new.py @@ -15,6 +15,12 @@ class NewCommand(Command): options = [ option("name", None, "Set the resulting package name.", flag=False), option("src", None, "Use the src layout for the project."), + option( + "readme", + None, + "Specify the readme file format. One of md (default) or rst", + flag=False, + ), ] def handle(self) -> None: @@ -46,7 +52,7 @@ def handle(self) -> None: "exists and is not empty".format(path) ) - readme_format = "rst" + readme_format = self.option("readme") or "md" config = GitConfig() author = None diff --git a/tests/console/commands/test_new.py b/tests/console/commands/test_new.py index 1ac42facd04..0df5149ae0c 100644 --- a/tests/console/commands/test_new.py +++ b/tests/console/commands/test_new.py @@ -1,21 +1,20 @@ -import pytest +from pathlib import Path +from typing import Optional -from cleo.testers import CommandTester +import pytest -from poetry.console import Application from poetry.factory import Factory from poetry.poetry import Poetry -from poetry.utils._compat import Path # noqa @pytest.fixture -def command(app, poetry): # type: (Application, Poetry) -> CommandTester - command = app.find("new") - command._pool = poetry.pool - return CommandTester(command) +def tester(command_tester_factory): + return command_tester_factory("new") -def verify_project_directory(path, package_name, package_path, include_from=None): +def verify_project_directory( + path: Path, package_name: str, package_path: str, include_from: Optional[str] = None +) -> Poetry: package_path = Path(package_path) assert path.is_dir() @@ -47,6 +46,8 @@ def verify_project_directory(path, package_name, package_path, include_from=None assert len(packages) == 1 assert packages[0] == package_include + return poetry + @pytest.mark.parametrize( "options,directory,package_name,package_path,include_from", @@ -134,9 +135,21 @@ def verify_project_directory(path, package_name, package_path, include_from=None ], ) def test_command_new( - options, directory, package_name, package_path, include_from, command, tmp_dir + options, directory, package_name, package_path, include_from, tester, tmp_dir ): path = Path(tmp_dir) / directory options.append(path.as_posix()) - command.execute(" ".join(options)) + tester.execute(" ".join(options)) verify_project_directory(path, package_name, package_path, include_from) + + +@pytest.mark.parametrize("fmt", [(None,), ("md",), ("rst",)]) +def test_command_new_with_readme(fmt, tester, tmp_dir): + fmt = "md" + package = "package" + path = Path(tmp_dir) / package + options = ["--readme {}".format(fmt) if fmt else "md", path.as_posix()] + tester.execute(" ".join(options)) + + poetry = verify_project_directory(path, package, package, None) + assert poetry.local_config.get("readme") == "README.{}".format(fmt or "md") From cb19dc057a7fb140f5f21dc134265e822f2b9a91 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Sun, 11 Apr 2021 01:22:55 +0200 Subject: [PATCH 176/222] ci: allow bootstrap script args in matrix --- .github/workflows/main.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 95d7e720675..608fe4292df 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,10 +25,12 @@ jobs: os: [Ubuntu, MacOS, Windows] python-version: [3.6, 3.7, 3.8, 3.9] experimental: [false] + bootstrap-args: [""] include: - os: Ubuntu python-version: "3.10.0-alpha - 3.10.0" experimental: true + bootstrap-args: "--git https://github.com/python-poetry/poetry.git" fail-fast: false steps: - uses: actions/checkout@v2 @@ -43,15 +45,11 @@ jobs: shell: bash run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") - - name: Bootstrap poetry (experimental) - if: ${{ matrix.experimental == true }} - shell: bash - run: python install-poetry.py -y --path . - - name: Bootstrap poetry - if: ${{ matrix.experimental == false }} shell: bash - run: python install-poetry.py -y + run: | + curl -sL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py \ + | python - -y ${{ matrix.bootstrap-args }} - name: Update PATH if: ${{ matrix.os != 'Windows' }} From 2d99ab4b8d41439cff22e6d032c58d5270dbcda5 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Sun, 11 Apr 2021 01:06:18 +0200 Subject: [PATCH 177/222] ci: fix typo in release workflow --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5a37ebadbe8..d71b0165e3f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: run: echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Build project for distribution - run: poetry run poetry build + run: poetry build - name: Check Version id: check-version @@ -48,4 +48,4 @@ jobs: - name: Publish to PyPI env: POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }} - run: poetry run poetry publish + run: poetry publish From cd8ac0d229e8fbd181725f1c9397ace003145ca1 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 12 Apr 2021 17:17:46 +0200 Subject: [PATCH 178/222] fix(get-poetry): use exit code 1 with non-existing version `python get-poetry.py --version 0.12.34` should exit non-zero. This apparently regressed in 23667d28a96cd [1]. 1: https://github.com/python-poetry/poetry/commit/23667d28a96cdf62a83e944ac977e22d3ced7590#diff-952871ed95a893e0348aa1269bf15e689d18756657cfec5c7316d05e55eb6dc6R302 --- get-poetry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/get-poetry.py b/get-poetry.py index 16e4bdcddfd..a8afd0d94ba 100644 --- a/get-poetry.py +++ b/get-poetry.py @@ -352,7 +352,7 @@ def run(self): version, current_version = self.get_version() if version is None: - return 0 + return 1 self.customize_install() self.display_pre_message() From 8b479d1559808adc6a03fcee4c42df2250363769 Mon Sep 17 00:00:00 2001 From: stephsamson Date: Wed, 14 Apr 2021 11:44:00 +0200 Subject: [PATCH 179/222] Revert "fix(get-poetry): use exit code 1 with non-existing version" This reverts commit cd8ac0d229e8fbd181725f1c9397ace003145ca1. --- get-poetry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/get-poetry.py b/get-poetry.py index a8afd0d94ba..16e4bdcddfd 100644 --- a/get-poetry.py +++ b/get-poetry.py @@ -352,7 +352,7 @@ def run(self): version, current_version = self.get_version() if version is None: - return 1 + return 0 self.customize_install() self.display_pre_message() From 3a789a7a2d9d82d57d388957f70cc985fa19d22f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Wed, 14 Apr 2021 12:19:53 +0200 Subject: [PATCH 180/222] Fix incorrect package being selected with transitive markers. --- poetry/mixology/version_solver.py | 16 ++++++---- tests/puzzle/test_solver.py | 50 +++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/poetry/mixology/version_solver.py b/poetry/mixology/version_solver.py index c9025964440..2ef0c65268a 100644 --- a/poetry/mixology/version_solver.py +++ b/poetry/mixology/version_solver.py @@ -4,6 +4,7 @@ from typing import Dict from typing import List from typing import Optional +from typing import Tuple from typing import Union from poetry.core.packages.dependency import Dependency @@ -327,11 +328,11 @@ def _choose_package_version(self) -> Optional[str]: # Prefer packages with as few remaining versions as possible, # so that if a conflict is necessary it's forced quickly. - def _get_min(dependency: Dependency) -> int: + def _get_min(dependency: Dependency) -> Tuple[bool, int]: if dependency.name in self._use_latest: # If we're forced to use the latest version of a package, it effectively # only has one version to choose from. - return 1 + return not dependency.marker.is_any(), 1 locked = self._get_locked(dependency) if locked and ( @@ -339,7 +340,7 @@ def _get_min(dependency: Dependency) -> int: or locked.is_prerelease() and dependency.constraint.allows(locked.version.next_patch()) ): - return 1 + return not dependency.marker.is_any(), 1 # VCS, URL, File or Directory dependencies # represent a single version @@ -349,12 +350,15 @@ def _get_min(dependency: Dependency) -> int: or dependency.is_file() or dependency.is_directory() ): - return 1 + return not dependency.marker.is_any(), 1 try: - return len(self._provider.search_for(dependency)) + return ( + not dependency.marker.is_any(), + len(self._provider.search_for(dependency)), + ) except ValueError: - return 0 + return not dependency.marker.is_any(), 0 if len(unsatisfied) == 1: dependency = unsatisfied[0] diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 13ef289dcd5..dd193096d24 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -2791,3 +2791,53 @@ def test_solver_can_resolve_python_restricted_package_dependencies( {"job": "install", "package": pre_commit}, ], ) + + +def test_solver_should_not_raise_errors_for_irrelevant_transitive_python_constraints( + solver, repo, package +): + package.python_versions = "~2.7 || ^3.5" + solver.provider.set_package_python_versions("~2.7 || ^3.5") + package.add_dependency(Factory.create_dependency("virtualenv", "^20.4.3")) + package.add_dependency( + Factory.create_dependency("pre-commit", {"version": "^2.6", "python": "^3.6.1"}) + ) + + virtualenv = get_package("virtualenv", "20.4.3") + virtualenv.python_versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + virtualenv.add_dependency( + Factory.create_dependency( + "importlib-resources", {"version": "*", "markers": 'python_version < "3.7"'} + ) + ) + + pre_commit = Package("pre-commit", "2.7.1") + pre_commit.python_versions = ">=3.6.1" + pre_commit.add_dependency( + Factory.create_dependency( + "importlib-resources", {"version": "*", "markers": 'python_version < "3.7"'} + ) + ) + + importlib_resources = get_package("importlib-resources", "5.1.2") + importlib_resources.python_versions = ">=3.6" + + importlib_resources_3_2_1 = get_package("importlib-resources", "3.2.1") + importlib_resources_3_2_1.python_versions = ( + "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + ) + + repo.add_package(virtualenv) + repo.add_package(pre_commit) + repo.add_package(importlib_resources) + repo.add_package(importlib_resources_3_2_1) + ops = solver.solve() + + check_solver_result( + ops, + [ + {"job": "install", "package": importlib_resources_3_2_1}, + {"job": "install", "package": pre_commit}, + {"job": "install", "package": virtualenv}, + ], + ) From f8ada85487834dfea60b469f5701f593fceb6e00 Mon Sep 17 00:00:00 2001 From: Vasundhara Gautam <15020857+vgautam@users.noreply.github.com> Date: Mon, 19 Apr 2021 03:02:33 -0700 Subject: [PATCH 181/222] Tests for ansi and no-ansi installs + bugfix for ansi install (#3881) * add tests for ansi and no-ansi install output * add tests for Env.run error handling * add tests for pip_install error handling * encapsulate pip installation to handle interrupts --- poetry/installation/executor.py | 32 +++++++++--- tests/installation/test_executor.py | 80 +++++++++++++++++++++++++++++ tests/utils/test_env.py | 53 +++++++++++++++++++ tests/utils/test_pip.py | 20 ++++++++ 4 files changed, 178 insertions(+), 7 deletions(-) create mode 100644 tests/utils/test_pip.py diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index 6e45dd1318f..146efeb2be0 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -117,6 +117,26 @@ def verbose(self, verbose: bool = True) -> "Executor": return self + def pip_install( + self, req: Union[Path, str], upgrade: bool = False, editable: bool = False + ) -> int: + func = pip_install + if editable: + func = pip_editable_install + + try: + func(req, self._env, upgrade=upgrade) + except EnvCommandError as e: + output = decode(e.e.output) + if ( + "KeyboardInterrupt" in output + or "ERROR: Operation cancelled by user" in output + ): + return -2 + raise + + return 0 + def execute(self, operations: List["OperationTypes"]) -> int: self._total_operations = len(operations) for job_type in self._executed: @@ -483,9 +503,7 @@ def _install(self, operation: Union[Install, Update]) -> int: ) ) self._write(operation, message) - return pip_install( - str(archive), self._env, upgrade=operation.job_type == "update" - ) + return self.pip_install(str(archive), upgrade=operation.job_type == "update") def _update(self, operation: Union[Install, Update]) -> int: return self._install(operation) @@ -577,13 +595,13 @@ def _install_directory(self, operation: Union[Install, Update]) -> int: with builder.setup_py(): if package.develop: - return pip_editable_install(req, self._env) - return pip_install(req, self._env, upgrade=True) + return self.pip_install(req, editable=True) + return self.pip_install(req, upgrade=True) if package.develop: - return pip_editable_install(req, self._env) + return self.pip_install(req, editable=True) - return pip_install(req, self._env, upgrade=True) + return self.pip_install(req, upgrade=True) def _install_git(self, operation: Union[Install, Update]) -> int: from poetry.core.vcs import Git diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 910a4962655..0438db6a00c 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -43,6 +43,22 @@ def io(): return io +@pytest.fixture() +def io_decorated(): + io = BufferedIO(decorated=True) + io.output.formatter.set_style("c1", Style("cyan")) + io.output.formatter.set_style("success", Style("green")) + + return io + + +@pytest.fixture() +def io_not_decorated(): + io = BufferedIO(decorated=False) + + return io + + @pytest.fixture() def pool(): pool = Pool() @@ -187,6 +203,70 @@ def test_execute_should_show_errors(config, mocker, io, env): assert expected in io.fetch_output() +def test_execute_works_with_ansi_output( + mocker, config, pool, io_decorated, tmp_dir, mock_file_downloads, env +): + config = Config() + config.merge({"cache-dir": tmp_dir}) + + 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.2")), + ] + ) + env._run.assert_called_once() + + expected = [ + "\x1b[39;1mPackage operations\x1b[39;22m: \x1b[34m1\x1b[39m install, \x1b[34m0\x1b[39m updates, \x1b[34m0\x1b[39m removals", + "\x1b[34;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mpytest\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m3.5.2\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mPending...\x1b[39m", + "\x1b[34;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mpytest\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m3.5.2\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mDownloading...\x1b[39m", + "\x1b[34;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mpytest\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m3.5.2\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mInstalling...\x1b[39m", + "\x1b[32;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mpytest\x1b[39m\x1b[39m (\x1b[39m\x1b[32m3.5.2\x1b[39m\x1b[39m)\x1b[39m", # finished + ] + output = io_decorated.fetch_output() + # hint: use print(repr(output)) if you need to debug this + + for line in expected: + assert line in output + assert 0 == return_code + + +def test_execute_works_with_no_ansi_output( + mocker, config, pool, io_not_decorated, tmp_dir, mock_file_downloads, env +): + config = Config() + config.merge({"cache-dir": tmp_dir}) + + 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.2")), + ] + ) + env._run.assert_called_once() + + expected = """ +Package operations: 1 install, 0 updates, 0 removals + + • Installing pytest (3.5.2) +""" + expected = set(expected.splitlines()) + output = set(io_not_decorated.fetch_output().splitlines()) + assert expected == output + assert 0 == return_code + + def test_execute_should_show_operation_as_cancelled_on_subprocess_keyboard_interrupt( config, mocker, io, env ): diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 0cc9c6c50bd..1cfc9ad5099 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -1,5 +1,6 @@ import os import shutil +import subprocess import sys from pathlib import Path @@ -638,6 +639,58 @@ def test_run_with_input_non_zero_return(tmp_dir, tmp_venv): assert processError.value.e.returncode == 1 +def test_run_with_keyboard_interrupt(tmp_dir, tmp_venv, mocker): + mocker.patch("subprocess.run", side_effect=KeyboardInterrupt()) + with pytest.raises(KeyboardInterrupt): + tmp_venv.run("python", "-", input_=MINIMAL_SCRIPT) + subprocess.run.assert_called_once() + + +def test_call_with_input_and_keyboard_interrupt(tmp_dir, tmp_venv, mocker): + mocker.patch("subprocess.run", side_effect=KeyboardInterrupt()) + kwargs = {"call": True} + with pytest.raises(KeyboardInterrupt): + tmp_venv.run("python", "-", input_=MINIMAL_SCRIPT, **kwargs) + subprocess.run.assert_called_once() + + +def test_call_no_input_with_keyboard_interrupt(tmp_dir, tmp_venv, mocker): + mocker.patch("subprocess.call", side_effect=KeyboardInterrupt()) + kwargs = {"call": True} + with pytest.raises(KeyboardInterrupt): + tmp_venv.run("python", "-", **kwargs) + subprocess.call.assert_called_once() + + +def test_run_with_called_process_error(tmp_dir, tmp_venv, mocker): + mocker.patch( + "subprocess.run", side_effect=subprocess.CalledProcessError(42, "some_command") + ) + with pytest.raises(EnvCommandError): + tmp_venv.run("python", "-", input_=MINIMAL_SCRIPT) + subprocess.run.assert_called_once() + + +def test_call_with_input_and_called_process_error(tmp_dir, tmp_venv, mocker): + mocker.patch( + "subprocess.run", side_effect=subprocess.CalledProcessError(42, "some_command") + ) + kwargs = {"call": True} + with pytest.raises(EnvCommandError): + tmp_venv.run("python", "-", input_=MINIMAL_SCRIPT, **kwargs) + subprocess.run.assert_called_once() + + +def test_call_no_input_with_called_process_error(tmp_dir, tmp_venv, mocker): + mocker.patch( + "subprocess.call", side_effect=subprocess.CalledProcessError(42, "some_command") + ) + kwargs = {"call": True} + with pytest.raises(EnvCommandError): + tmp_venv.run("python", "-", **kwargs) + subprocess.call.assert_called_once() + + def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ones_first( manager, poetry, config, mocker, config_virtualenvs_path ): diff --git a/tests/utils/test_pip.py b/tests/utils/test_pip.py new file mode 100644 index 00000000000..d13d68992e8 --- /dev/null +++ b/tests/utils/test_pip.py @@ -0,0 +1,20 @@ +import subprocess + +import pytest + +from poetry.utils.pip import pip_install + + +def test_pip_install_successful(tmp_dir, tmp_venv, fixture_dir): + file_path = fixture_dir("distributions/demo-0.1.0-py2.py3-none-any.whl") + result = pip_install(file_path, tmp_venv) + + assert "Successfully installed demo-0.1.0" in result + + +def test_pip_install_with_keyboard_interrupt(tmp_dir, tmp_venv, fixture_dir, mocker): + file_path = fixture_dir("distributions/demo-0.1.0-py2.py3-none-any.whl") + mocker.patch("subprocess.run", side_effect=KeyboardInterrupt()) + with pytest.raises(KeyboardInterrupt): + pip_install(file_path, tmp_venv) + subprocess.run.assert_called_once() From 9a2e1577ce31caf22f45f9be8e012ac9c0cd7dac Mon Sep 17 00:00:00 2001 From: paavanb Date: Tue, 13 Apr 2021 15:08:19 -0400 Subject: [PATCH 182/222] doc: poetry lock --no-update Resolves: #1614 --- docs/docs/cli.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/docs/cli.md b/docs/docs/cli.md index af7c8b1a81a..f0b2989a9ad 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -465,6 +465,10 @@ poetry search requests pendulum This command locks (without installing) the dependencies specified in `pyproject.toml`. +!!!note + + By default, this will lock all dependencies to the latest available compatible versions. To only refresh the lock file, use the `--no-update` option. + ```bash poetry lock ``` @@ -472,6 +476,7 @@ poetry lock ### Options * `--check`: Verify that `poetry.lock` is consistent with `pyproject.toml` +* `--no-update`: Do not update locked versions, only refresh lock file. ## version From 494dec279daeb3b52348a793598eb4861163403e Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 13 Apr 2021 23:51:29 +0200 Subject: [PATCH 183/222] command/add: introduce --editable option This change allows for vcs/path dependencies to be installed with `develop=true` configured. --- docs/docs/cli.md | 10 ++++++- poetry/console/commands/add.py | 14 ++++++++++ tests/console/commands/test_add.py | 43 +++++++++++++++++++++++++----- 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/docs/docs/cli.md b/docs/docs/cli.md index f0b2989a9ad..45881bec80a 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -272,7 +272,14 @@ poetry add ../my-package/dist/my-package-0.1.0.tar.gz poetry add ../my-package/dist/my_package-0.1.0.whl ``` -If you want the dependency to be installed in editable mode you can specify it in the `pyproject.toml` file. It means that changes in the local directory will be reflected directly in environment. +If you want the dependency to be installed in editable mode you can use the `--editable` option. + +```bash +poetry add --editable ./my-package/ +poetry add --editable git+ssh://github.com/sdispater/pendulum.git#develop +``` + +Alternatively, you can specify it in the `pyproject.toml` file. It means that changes in the local directory will be reflected directly in environment. ```toml [tool.poetry.dependencies] @@ -296,6 +303,7 @@ poetry add "git+https://github.com/pallets/flask.git@1.1.1[dotenv,dev]" ### Options * `--dev (-D)`: Add package as development dependency. +* `--editable (-e)`: Add vcs/path dependencies as editable. * `--extras (-E)`: Extras to activate for the dependency. (multiple values allowed) * `--optional`: Add as an optional dependency. * `--python`: Python version for which the dependency must be installed. diff --git a/poetry/console/commands/add.py b/poetry/console/commands/add.py index adfe4d9f062..777f3744787 100644 --- a/poetry/console/commands/add.py +++ b/poetry/console/commands/add.py @@ -17,6 +17,7 @@ class AddCommand(InstallerCommand, InitCommand): arguments = [argument("name", "The packages to add.", multiple=True)] options = [ option("dev", "D", "Add as a development dependency."), + option("editable", "e", "Add vcs/path dependencies as editable."), option( "extras", "E", @@ -139,6 +140,19 @@ def handle(self) -> int: constraint["extras"] = self.option("extras") + if self.option("editable"): + if "git" in _constraint or "path" in _constraint: + constraint["develop"] = True + else: + self.line_error( + "\n" + "Failed to add packages. " + "Only vcs/path dependencies support editable installs. " + f"{_constraint['name']} is neither." + ) + self.line_error("\nNo changes were applied.") + return 1 + if self.option("python"): constraint["python"] = self.option("python") diff --git a/tests/console/commands/test_add.py b/tests/console/commands/test_add.py index 4808c53f29f..26955345457 100644 --- a/tests/console/commands/test_add.py +++ b/tests/console/commands/test_add.py @@ -50,6 +50,24 @@ def test_add_no_constraint(app, repo, tester): assert content["dependencies"]["cachy"] == "^0.2.0" +def test_add_no_constraint_editable_error(app, repo, tester): + content = app.poetry.file.read()["tool"]["poetry"] + + repo.add_package(get_package("cachy", "0.2.0")) + + tester.execute("-e cachy") + + expected = """ +Failed to add packages. Only vcs/path dependencies support editable installs. cachy is neither. + +No changes were applied. +""" + assert 1 == tester.status_code + assert expected == tester.io.fetch_error() + assert 0 == tester.command.installer.executor.installations_count + assert content == app.poetry.file.read()["tool"]["poetry"] + + def test_add_equal_constraint(app, repo, tester): repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(get_package("cachy", "0.2.0")) @@ -243,13 +261,15 @@ def test_add_git_constraint_with_extras(app, repo, tester, tmp_venv): } -def test_add_git_ssh_constraint(app, repo, tester, tmp_venv): +@pytest.mark.parametrize("editable", [False, True]) +def test_add_git_ssh_constraint(editable, app, repo, tester, tmp_venv): tester.command.set_env(tmp_venv) repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("cleo", "0.6.5")) - tester.execute("git+ssh://git@github.com/demo/demo.git@develop") + url = "git+ssh://git@github.com/demo/demo.git@develop" + tester.execute(f"{url}" if not editable else f"-e {url}") expected = """\ @@ -270,13 +290,19 @@ def test_add_git_ssh_constraint(app, repo, tester, tmp_venv): content = app.poetry.file.read()["tool"]["poetry"] assert "demo" in content["dependencies"] - assert content["dependencies"]["demo"] == { + + expected = { "git": "ssh://git@github.com/demo/demo.git", "rev": "develop", } + if editable: + expected["develop"] = True + + assert content["dependencies"]["demo"] == expected -def test_add_directory_constraint(app, repo, tester, mocker): +@pytest.mark.parametrize("editable", [False, True]) +def test_add_directory_constraint(editable, app, repo, tester, mocker): p = mocker.patch("pathlib.Path.cwd") p.return_value = Path(__file__).parent @@ -284,7 +310,7 @@ def test_add_directory_constraint(app, repo, tester, mocker): repo.add_package(get_package("cleo", "0.6.5")) path = "../git/github.com/demo/demo" - tester.execute("{}".format(path)) + tester.execute(f"{path}" if not editable else f"-e {path}") expected = """\ @@ -307,7 +333,12 @@ def test_add_directory_constraint(app, repo, tester, mocker): content = app.poetry.file.read()["tool"]["poetry"] assert "demo" in content["dependencies"] - assert content["dependencies"]["demo"] == {"path": "../git/github.com/demo/demo"} + + expected = {"path": "../git/github.com/demo/demo"} + if editable: + expected["develop"] = True + + assert content["dependencies"]["demo"] == expected def test_add_directory_with_poetry(app, repo, tester, mocker): From a3c5cf7c9559dbb0b0372aa2e30b5ce3f2a849af Mon Sep 17 00:00:00 2001 From: Tom Rochette Date: Mon, 26 Apr 2021 11:29:36 -0400 Subject: [PATCH 184/222] Add PyPI registry correctly to pool depending on other sources (#3406) In the event where we defined sources that were set as secondary = True, we would end up with PyPI being after this source when it should have acted as default in that case. The main issue stems from the fact that it's not because you have sources configured that PyPI should not be a default. Instead, PyPI should be default if there are no sources with secondary = False and not default if there are sources with secondary = True. --- poetry/factory.py | 13 +++-- poetry/repositories/pool.py | 5 ++ .../pyproject.toml | 24 ++++++++ .../pyproject.toml | 23 ++++++++ .../pyproject.toml | 19 ++++++ tests/test_factory.py | 58 +++++++++++++++++++ 6 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 tests/fixtures/with_non_default_multiple_secondary_sources/pyproject.toml create mode 100644 tests/fixtures/with_non_default_multiple_sources/pyproject.toml create mode 100644 tests/fixtures/with_non_default_secondary_source/pyproject.toml diff --git a/poetry/factory.py b/poetry/factory.py index 36078bed1e5..144bfe35871 100644 --- a/poetry/factory.py +++ b/poetry/factory.py @@ -153,14 +153,15 @@ def configure_sources( poetry.pool.add_repository(repository, is_default, secondary=is_secondary) - # Always put PyPI last to prefer private repositories - # but only if we have no other default source - if not poetry.pool.has_default(): - has_sources = bool(sources) - poetry.pool.add_repository(PyPiRepository(), not has_sources, has_sources) - else: + # 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 io.is_debug(): io.write_line("Deactivating the PyPI repository") + else: + default = not poetry.pool.has_primary_repositories() + poetry.pool.add_repository(PyPiRepository(), default, not default) @classmethod def create_legacy_repository( diff --git a/poetry/repositories/pool.py b/poetry/repositories/pool.py index 251ff92506b..b57ba155ec2 100644 --- a/poetry/repositories/pool.py +++ b/poetry/repositories/pool.py @@ -25,6 +25,7 @@ def __init__( self._lookup: Dict[str, int] = {} self._repositories: List[Repository] = [] self._default = False + self._has_primary_repositories = False self._secondary_start_idx = None for repository in repositories: @@ -41,6 +42,9 @@ def repositories(self) -> List[Repository]: def has_default(self) -> bool: return self._default + def has_primary_repositories(self) -> bool: + return self._has_primary_repositories + def has_repository(self, name: str) -> bool: name = name.lower() if name is not None else None @@ -84,6 +88,7 @@ def add_repository( self._repositories.append(repository) self._lookup[repository_name] = len(self._repositories) - 1 else: + self._has_primary_repositories = True if self._secondary_start_idx is None: self._repositories.append(repository) self._lookup[repository_name] = len(self._repositories) - 1 diff --git a/tests/fixtures/with_non_default_multiple_secondary_sources/pyproject.toml b/tests/fixtures/with_non_default_multiple_secondary_sources/pyproject.toml new file mode 100644 index 00000000000..933bee96912 --- /dev/null +++ b/tests/fixtures/with_non_default_multiple_secondary_sources/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 new file mode 100644 index 00000000000..6cacb602e8b --- /dev/null +++ b/tests/fixtures/with_non_default_multiple_sources/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 new file mode 100644 index 00000000000..453e3f9747f --- /dev/null +++ b/tests/fixtures/with_non_default_secondary_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 = "foo" +url = "https://foo.bar/simple/" +secondary = true diff --git a/tests/test_factory.py b/tests/test_factory.py index 13a0437174c..e9a9a9a5994 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -176,6 +176,64 @@ def test_poetry_with_non_default_source(): assert isinstance(poetry.pool.repositories[1], PyPiRepository) +def test_poetry_with_non_default_secondary_source(): + poetry = Factory().create_poetry(fixtures_dir / "with_non_default_secondary_source") + + assert len(poetry.pool.repositories) == 2 + + 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_secondary_sources(): + poetry = Factory().create_poetry( + fixtures_dir / "with_non_default_multiple_secondary_sources" + ) + + assert len(poetry.pool.repositories) == 3 + + 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) + + repository = poetry.pool.repositories[2] + assert repository.name == "bar" + assert isinstance(repository, LegacyRepository) + + +def test_poetry_with_non_default_multiple_sources(): + 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) + + def test_poetry_with_no_default_source(): poetry = Factory().create_poetry(fixtures_dir / "sample_project") From f23bd4a661d26cc493fd8940bce68888b94757aa Mon Sep 17 00:00:00 2001 From: Nicolas Simonds <0xDEC0DE@users.noreply.github.com> Date: Thu, 29 Apr 2021 01:31:02 -0700 Subject: [PATCH 185/222] Throw a RuntimeError on hash mismatch in Chooser._get_links (#3885) Throw a specific exception in the case of finding a matching name+version, but none of the digests for a link matching the `poetry.lock` metadata. Fixes Issue #2422 Co-authored-by: Nicolas Simonds --- poetry/installation/chooser.py | 5 +++++ tests/installation/test_chooser.py | 33 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/poetry/installation/chooser.py b/poetry/installation/chooser.py index 0069a50a47f..4f9b469d823 100644 --- a/poetry/installation/chooser.py +++ b/poetry/installation/chooser.py @@ -106,6 +106,11 @@ def _get_links(self, package: Package) -> List[Link]: selected_links.append(link) + if links and not selected_links: + raise RuntimeError( + f"Retrieved digest for link {link.filename}({h}) not in poetry.lock metadata {hashes}" + ) + return selected_links def _sort_key(self, package: Package, link: Link) -> Tuple: diff --git a/tests/installation/test_chooser.py b/tests/installation/test_chooser.py index 64d58eab95d..b7c6e155c6f 100644 --- a/tests/installation/test_chooser.py +++ b/tests/installation/test_chooser.py @@ -208,3 +208,36 @@ def test_chooser_chooses_distributions_that_match_the_package_hashes( link = chooser.choose_for(package) assert "isort-4.3.4.tar.gz" == link.filename + + +@pytest.mark.parametrize("source_type", ["", "legacy"]) +def test_chooser_throws_an_error_if_package_hashes_do_not_match( + env, + mock_pypi, + mock_legacy, + source_type, + pool, +): + chooser = Chooser(pool, env) + + package = Package("isort", "4.3.4") + files = [ + { + "hash": "sha256:0000000000000000000000000000000000000000000000000000000000000000", + "filename": "isort-4.3.4.tar.gz", + } + ] + if source_type == "legacy": + package = Package( + package.name, + package.version.text, + source_type="legacy", + source_reference="foo", + source_url="https://foo.bar/simple/", + ) + + package.files = files + + with pytest.raises(RuntimeError) as e: + chooser.choose_for(package) + assert files[0]["hash"] in str(e) From a516a8fc69ef5404183fe0d870892cec6edd626e Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 30 Apr 2021 10:14:01 +0200 Subject: [PATCH 186/222] dist: add tests to sdists (#4007) Resolves: #3952 --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index bae6619a3cc..cc0d2c5e26f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,10 @@ license = "MIT" readme = "README.md" +include = [ + { path = "tests", format = "sdist" } +] + homepage = "https://python-poetry.org/" repository = "https://github.com/python-poetry/poetry" documentation = "https://python-poetry.org/docs" From 16031f05076f0eede48984de5a3c57721d6dda27 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 30 Apr 2021 11:14:48 +0200 Subject: [PATCH 187/222] remove redundant version string transformation code (#3989) --- poetry/version/version_selector.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/poetry/version/version_selector.py b/poetry/version/version_selector.py index 94892eb5287..034294ede9b 100644 --- a/poetry/version/version_selector.py +++ b/poetry/version/version_selector.py @@ -65,19 +65,6 @@ def find_recommended_require_version(self, package: Package) -> str: def _transform_version(self, version: str, pretty_version: str) -> str: try: - parsed = Version.parse(version) - parts = [parsed.major, parsed.minor, parsed.patch] + return f"^{Version.parse(version).to_string()}" except ValueError: return pretty_version - - parts = parts[: parsed.precision] - - # check to see if we have a semver-looking version - if len(parts) < 3: - version = pretty_version - else: - version = ".".join(str(p) for p in parts) - if parsed.is_unstable(): - version += f"-{parsed.pre.to_string()}" - - return f"^{version}" From 341f04e33d7416ea80216f8663cfe3760343f1da Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 13 Apr 2021 20:08:06 +0200 Subject: [PATCH 188/222] tests: use env.python instead of env._bin("python") --- tests/masonry/builders/test_editable_builder.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/masonry/builders/test_editable_builder.py b/tests/masonry/builders/test_editable_builder.py index 86ae73ece5b..6fb0a07a445 100644 --- a/tests/masonry/builders/test_editable_builder.py +++ b/tests/masonry/builders/test_editable_builder.py @@ -153,7 +153,7 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ if __name__ == '__main__': baz.boom.bim() """.format( - python=tmp_venv._bin("python") + python=tmp_venv.python ) assert baz_script == tmp_venv._bin_dir.joinpath("baz").read_text() @@ -165,7 +165,7 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ if __name__ == '__main__': bar() """.format( - python=tmp_venv._bin("python") + python=tmp_venv.python ) assert foo_script == tmp_venv._bin_dir.joinpath("foo").read_text() @@ -177,7 +177,7 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ if __name__ == '__main__': bar.baz() """.format( - python=tmp_venv._bin("python") + python=tmp_venv.python ) assert fox_script == tmp_venv._bin_dir.joinpath("fox").read_text() From e25d7845d081ba22fda51df59437624b0b7bb825 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 13 Apr 2021 20:12:10 +0200 Subject: [PATCH 189/222] env: do not modify os.environ Replace updates of os.environ with explicit passing of `env` to subprocess calls in `Env.execute()`. Relates-to: #3199 --- poetry/utils/env.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/poetry/utils/env.py b/poetry/utils/env.py index ba9519cbc2e..8d5fa861c1b 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -1230,6 +1230,7 @@ def _run(self, cmd: List[str], **kwargs: Any) -> Union[int, str]: """ call = kwargs.pop("call", False) input_ = kwargs.pop("input_", None) + env = kwargs.pop("env", {k: v for k, v in os.environ.items()}) try: if self._is_windows: @@ -1248,10 +1249,10 @@ def _run(self, cmd: List[str], **kwargs: Any) -> Union[int, str]: **kwargs, ).stdout elif call: - return subprocess.call(cmd, stderr=subprocess.STDOUT, **kwargs) + return subprocess.call(cmd, stderr=subprocess.STDOUT, env=env, **kwargs) else: output = subprocess.check_output( - cmd, stderr=subprocess.STDOUT, **kwargs + cmd, stderr=subprocess.STDOUT, env=env, **kwargs ) except CalledProcessError as e: raise EnvCommandError(e, input=input_) From 92d92bb4b3ab36708f4d65fbfdb37b16f519367c Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Wed, 28 Apr 2021 01:56:03 +0200 Subject: [PATCH 190/222] tests: ensure ephemeral config usage Previously, pytest execution was influenced by poetry user configuration. This change ensures that a new config.toml is used each test case. --- tests/conftest.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 9481c31457b..d66ed9ba659 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -99,6 +99,15 @@ def config(config_source, auth_config_source, mocker): return c +@pytest.fixture(autouse=True) +def mock_user_config_dir(mocker): + config_dir = tempfile.mkdtemp(prefix="poetry_config_") + mocker.patch("poetry.locations.CONFIG_DIR", new=config_dir) + mocker.patch("poetry.factory.CONFIG_DIR", new=config_dir) + yield + shutil.rmtree(config_dir, ignore_errors=True) + + @pytest.fixture(autouse=True) def download_mock(mocker): # Patch download to not download anything but to just copy from fixtures From ccf1322129e832e8293ac532656aed9a716d2f33 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 30 Apr 2021 01:02:26 +0200 Subject: [PATCH 191/222] executor: ensure path is used when generating hash --- poetry/installation/executor.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index 146efeb2be0..0a3f8d58a9e 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -670,7 +670,13 @@ def _download_link(self, operation: Union[Install, Update], link: Link) -> Link: archive = self._chef.prepare(archive) if package.files: - archive_hash = "sha256:" + FileDependency(package.name, archive).hash() + archive_hash = ( + "sha256:" + + FileDependency( + package.name, + Path(archive.path) if isinstance(archive, Link) else archive, + ).hash() + ) if archive_hash not in {f["hash"] for f in package.files}: raise RuntimeError( f"Invalid hash for {package} using archive {archive.name}" From 3dceee386a3c616d23b301d8ebe9adee30c6f172 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 30 Apr 2021 01:03:28 +0200 Subject: [PATCH 192/222] upgrade dependencies --- poetry.lock | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/poetry.lock b/poetry.lock index ded07f4134e..d187aaf4f1e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -161,7 +161,7 @@ python-versions = ">=3.6, <3.7" [[package]] name = "deepdiff" -version = "5.2.3" +version = "5.5.0" description = "Deep Difference and Search of any Python object/data." category = "dev" optional = false @@ -171,7 +171,7 @@ python-versions = ">=3.6" ordered-set = "4.0.2" [package.extras] -cli = ["click (==7.1.2)", "pyyaml (==5.3.1)", "toml (==0.10.2)", "clevercsv (==0.6.6)"] +cli = ["click (==7.1.2)", "pyyaml (==5.4)", "toml (==0.10.2)", "clevercsv (==0.6.7)"] [[package]] name = "distlib" @@ -225,7 +225,7 @@ python-versions = ">=3" [[package]] name = "identify" -version = "2.2.1" +version = "2.2.4" description = "File identification library for Python" category = "dev" optional = false @@ -327,7 +327,7 @@ python-versions = "*" [[package]] name = "nodeenv" -version = "1.5.0" +version = "1.6.0" description = "Node.js virtual environment builder" category = "dev" optional = false @@ -390,7 +390,7 @@ dev = ["pre-commit", "tox"] [[package]] name = "poetry-core" -version = "1.1.0a2" +version = "1.1.0a3" description = "Poetry PEP 517 Build Backend" category = "main" optional = false @@ -398,18 +398,18 @@ python-versions = "^3.6" develop = false [package.dependencies] -dataclasses = {version = "^0.8", markers = "python_version >= \"3.6\" and python_version < \"3.7\""} -importlib-metadata = {version = "^1.7.0", markers = "python_version >= \"3.5\" and python_version < \"3.8\""} +dataclasses = {version = ">=0.8", markers = "python_version >= \"3.6\" and python_version < \"3.7\""} +importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} [package.source] type = "git" url = "https://github.com/python-poetry/poetry-core.git" reference = "master" -resolved_reference = "d3e60732ce9bd4f30dee3e594405fe6a80163b7e" +resolved_reference = "3f718c55fcda63d9bd88b8fc612970c24fc9af25" [[package]] name = "pre-commit" -version = "2.11.1" +version = "2.12.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false @@ -665,7 +665,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.4.3" +version = "20.4.4" description = "Virtual Python Environment builder" category = "main" optional = false @@ -873,8 +873,8 @@ dataclasses = [ {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"}, ] deepdiff = [ - {file = "deepdiff-5.2.3-py3-none-any.whl", hash = "sha256:3d3da4bd7e01fb5202088658ed26427104c748dda56a0ecfac9ce9a1d2d00844"}, - {file = "deepdiff-5.2.3.tar.gz", hash = "sha256:ae2cb98353309f93fbfdda4d77adb08fb303314d836bb6eac3d02ed71a10b40e"}, + {file = "deepdiff-5.5.0-py3-none-any.whl", hash = "sha256:e054fed9dfe0d83d622921cbb3a3d0b3a6dd76acd2b6955433a0a2d35147774a"}, + {file = "deepdiff-5.5.0.tar.gz", hash = "sha256:dd79b81c2d84bfa33aa9d94d456b037b68daff6bb87b80dfaa1eca04da68b349"}, ] distlib = [ {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"}, @@ -896,8 +896,8 @@ httpretty = [ {file = "httpretty-1.0.5.tar.gz", hash = "sha256:e53c927c4d3d781a0761727f1edfad64abef94e828718e12b672a678a8b3e0b5"}, ] identify = [ - {file = "identify-2.2.1-py2.py3-none-any.whl", hash = "sha256:9cc5f58996cd359b7b72f0a5917d8639de5323917e6952a3bfbf36301b576f40"}, - {file = "identify-2.2.1.tar.gz", hash = "sha256:1cfb05b578de996677836d5a2dde14b3dffde313cf7d2b3e793a0787a36e26dd"}, + {file = "identify-2.2.4-py2.py3-none-any.whl", hash = "sha256:ad9f3fa0c2316618dc4d840f627d474ab6de106392a4f00221820200f490f5a8"}, + {file = "identify-2.2.4.tar.gz", hash = "sha256:9bcc312d4e2fa96c7abebcdfb1119563b511b5e3985ac52f60d9116277865b2e"}, ] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, @@ -958,8 +958,8 @@ msgpack = [ {file = "msgpack-1.0.2.tar.gz", hash = "sha256:fae04496f5bc150eefad4e9571d1a76c55d021325dcd484ce45065ebbdd00984"}, ] nodeenv = [ - {file = "nodeenv-1.5.0-py2.py3-none-any.whl", hash = "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9"}, - {file = "nodeenv-1.5.0.tar.gz", hash = "sha256:ab45090ae383b716c4ef89e690c41ff8c2b257b85b309f01f3654df3d084bd7c"}, + {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, + {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, ] ordered-set = [ {file = "ordered-set-4.0.2.tar.gz", hash = "sha256:ba93b2df055bca202116ec44b9bead3df33ea63a7d5827ff8e16738b97f33a95"}, @@ -982,8 +982,8 @@ pluggy = [ ] poetry-core = [] pre-commit = [ - {file = "pre_commit-2.11.1-py2.py3-none-any.whl", hash = "sha256:94c82f1bf5899d56edb1d926732f4e75a7df29a0c8c092559c77420c9d62428b"}, - {file = "pre_commit-2.11.1.tar.gz", hash = "sha256:de55c5c72ce80d79106e48beb1b54104d16495ce7f95b0c7b13d4784193a00af"}, + {file = "pre_commit-2.12.1-py2.py3-none-any.whl", hash = "sha256:70c5ec1f30406250b706eda35e868b87e3e4ba099af8787e3e8b4b01e84f4712"}, + {file = "pre_commit-2.12.1.tar.gz", hash = "sha256:900d3c7e1bf4cf0374bb2893c24c23304952181405b4d88c9c40b72bda1bb8a9"}, ] ptyprocess = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, @@ -1087,8 +1087,8 @@ urllib3 = [ {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, ] virtualenv = [ - {file = "virtualenv-20.4.3-py2.py3-none-any.whl", hash = "sha256:83f95875d382c7abafe06bd2a4cdd1b363e1bb77e02f155ebe8ac082a916b37c"}, - {file = "virtualenv-20.4.3.tar.gz", hash = "sha256:49ec4eb4c224c6f7dd81bb6d0a28a09ecae5894f4e593c89b0db0885f565a107"}, + {file = "virtualenv-20.4.4-py2.py3-none-any.whl", hash = "sha256:a935126db63128861987a7d5d30e23e8ec045a73840eeccb467c148514e29535"}, + {file = "virtualenv-20.4.4.tar.gz", hash = "sha256:09c61377ef072f43568207dc8e46ddeac6bcdcaf288d49011bda0e7f4d38c4a2"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, From f0408d61c55f43add7a6ce78ff63f8dd2ee34126 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 30 Apr 2021 17:08:47 +0200 Subject: [PATCH 193/222] env: default to enabling pip/wheels/setuptools For project virtual environments, default to enabling pip, setuptools and wheel packages to retain existing stable behaviour to prevent unexpected breakages caused by development environments making assumptions of base package availability in virtual environments. Poetry itself does not require the use of these packages and will execute correctly within environments that do not have these packages. This change retains the ability to manage these packages as direct project dependency as introduced in #2826. All poetry internal execution of pip is retaining the use of the wheel embedded within the virtualenv package used by poetry. In cases where a one of these reserved packages are being managed as a project dependency, the will be treated as any other project dependency. Executing `poetry install --remove-untracked` will not remove any of these reserved packages. However, `poetry add pip` and `poetry remove pip` will trigger the update and removal of `pip` respectively. Relates-to: #2826 Relates-to: #3916 --- poetry/puzzle/solver.py | 27 +++++- poetry/utils/env.py | 107 +++++++++++++---------- poetry/utils/pip.py | 2 +- tests/console/commands/env/test_use.py | 3 + tests/inspection/test_info.py | 2 +- tests/installation/test_installer.py | 89 +++++++++++++------ tests/installation/test_installer_old.py | 83 ++++++++++++------ tests/utils/test_env.py | 30 ++++++- 8 files changed, 234 insertions(+), 109 deletions(-) diff --git a/poetry/puzzle/solver.py b/poetry/puzzle/solver.py index b5a413f15e0..2e864bc9b20 100644 --- a/poetry/puzzle/solver.py +++ b/poetry/puzzle/solver.py @@ -63,10 +63,31 @@ def __init__( self._overrides = [] self._remove_untracked = remove_untracked + self._preserved_package_names = None + @property def provider(self) -> Provider: return self._provider + @property + def preserved_package_names(self): + if self._preserved_package_names is None: + self._preserved_package_names = { + self._package.name, + *Provider.UNSAFE_PACKAGES, + } + + deps = {package.name for package in self._locked.packages} + + # preserve pip/setuptools/wheel when not managed by poetry, this is so + # to avoid externally managed virtual environments causing unnecessary + # removals. + for name in {"pip", "wheel", "setuptools"}: + if name not in deps: + self._preserved_package_names.add(name) + + return self._preserved_package_names + @contextmanager def use_environment(self, env: Env) -> None: with self.provider.use_environment(env): @@ -190,11 +211,9 @@ def solve(self, use_latest: List[str] = None) -> List["OperationTypes"]: locked_names = {locked.name for locked in self._locked.packages} for installed in self._installed.packages: - if installed.name == self._package.name: - continue - if installed.name in Provider.UNSAFE_PACKAGES: - # Never remove pip, setuptools etc. + if installed.name in self.preserved_package_names: continue + if installed.name not in locked_names: operations.append(Uninstall(installed)) diff --git a/poetry/utils/env.py b/poetry/utils/env.py index 8d5fa861c1b..195d08162de 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -877,13 +877,8 @@ def create_venv( io.write_line( "Creating virtualenv {} in {}".format(name, str(venv_path)) ) - - self.build_venv( - venv, - executable=executable, - flags=self._poetry.config.get("virtualenvs.options"), - ) else: + create_venv = False if force: if not env.is_sane(): io.write_line( @@ -895,14 +890,23 @@ def create_venv( "Recreating virtualenv {} in {}".format(name, str(venv)) ) self.remove_venv(venv) - self.build_venv( - venv, - executable=executable, - flags=self._poetry.config.get("virtualenvs.options"), - ) + create_venv = True elif io.is_very_verbose(): io.write_line(f"Virtualenv {name} already exists.") + if create_venv: + self.build_venv( + venv, + executable=executable, + flags=self._poetry.config.get("virtualenvs.options"), + # TODO: in a future version switch remove pip/setuptools/wheel + # poetry does not need them these exists today to not break developer + # environment assumptions + with_pip=True, + with_setuptools=True, + with_wheel=True, + ) + # venv detection: # stdlib venv may symlink sys.executable, so we can't use realpath. # but others can symlink *to* the venv Python, @@ -927,12 +931,29 @@ def build_venv( path: Union[Path, str], executable: Optional[Union[str, Path]] = None, flags: Dict[str, bool] = None, - with_pip: bool = False, + with_pip: Optional[bool] = None, with_wheel: Optional[bool] = None, with_setuptools: Optional[bool] = None, ) -> virtualenv.run.session.Session: flags = flags or {} + flags["no-pip"] = ( + not with_pip if with_pip is not None else flags.pop("no-pip", True) + ) + + flags["no-setuptools"] = ( + not with_setuptools + if with_setuptools is not None + else flags.pop("no-setuptools", True) + ) + + # we want wheels to be enabled when pip is required and it has not been explicitly disabled + flags["no-wheel"] = ( + not with_wheel + if with_wheel is not None + else flags.pop("no-wheel", flags["no-pip"]) + ) + if isinstance(executable, Path): executable = executable.resolve().as_posix() @@ -943,20 +964,6 @@ def build_venv( executable or sys.executable, ] - if not with_pip: - args.append("--no-pip") - else: - if with_wheel is None: - # we want wheels to be enabled when pip is required and it has - # not been explicitly disabled - with_wheel = True - - if with_wheel is None or not with_wheel: - args.append("--no-wheel") - - if with_setuptools is None or not with_setuptools: - args.append("--no-setuptools") - for flag, value in flags.items(): if value is True: args.append(f"--{flag}") @@ -1039,6 +1046,8 @@ def __init__(self, path: Path, base: Optional[Path] = None) -> None: self._platlib = None self._script_dirs = None + self._embedded_pip_path = None + @property def path(self) -> Path: return self._path @@ -1074,6 +1083,12 @@ def get_embedded_wheel(self, distribution): distribution, "{}.{}".format(self.version_info[0], self.version_info[1]) ).path + @property + def pip_embedded(self) -> str: + if self._embedded_pip_path is None: + self._embedded_pip_path = str(self.get_embedded_wheel("pip") / "pip") + return self._embedded_pip_path + @property def pip(self) -> str: """ @@ -1082,7 +1097,7 @@ def pip(self) -> str: # we do not use as_posix() here due to issues with windows pathlib2 implementation path = self._bin("pip") if not Path(path).exists(): - return str(self.get_embedded_wheel("pip") / "pip") + return str(self.pip_embedded) return path @property @@ -1187,7 +1202,7 @@ def get_python_implementation(self) -> str: def get_marker_env(self) -> Dict[str, Any]: raise NotImplementedError() - def get_pip_command(self) -> List[str]: + def get_pip_command(self, embedded: bool = False) -> List[str]: raise NotImplementedError() def get_supported_tags(self) -> List[Tag]: @@ -1208,16 +1223,20 @@ def is_sane(self) -> bool: """ return True - def run(self, bin: str, *args: str, **kwargs: Any) -> Union[str, int]: + def get_command_from_bin(self, bin: str) -> List[str]: if bin == "pip": - return self.run_pip(*args, **kwargs) + # when pip is required we need to ensure that we fallback to + # embedded pip when pip is not available in the environment + return self.get_pip_command() + + return [self._bin(bin)] - bin = self._bin(bin) - cmd = [bin] + list(args) + def run(self, bin: str, *args: str, **kwargs: Any) -> Union[str, int]: + cmd = self.get_command_from_bin(bin) + list(args) return self._run(cmd, **kwargs) def run_pip(self, *args: str, **kwargs: Any) -> Union[int, str]: - pip = self.get_pip_command() + pip = self.get_pip_command(embedded=True) cmd = pip + list(args) return self._run(cmd, **kwargs) @@ -1260,17 +1279,13 @@ def _run(self, cmd: List[str], **kwargs: Any) -> Union[int, str]: return decode(output) def execute(self, bin: str, *args: str, **kwargs: Any) -> Optional[int]: - if bin == "pip": - return self.run_pip(*args, **kwargs) - - bin = self._bin(bin) + command = self.get_command_from_bin(bin) + list(args) env = kwargs.pop("env", {k: v for k, v in os.environ.items()}) if not self._is_windows: - args = [bin] + list(args) - return os.execvpe(bin, args, env=env) + return os.execvpe(command[0], command, env=env) else: - exe = subprocess.Popen([bin] + list(args), env=env, **kwargs) + exe = subprocess.Popen([command[0]] + command[1:], env=env, **kwargs) exe.communicate() return exe.returncode @@ -1338,10 +1353,10 @@ def get_version_info(self) -> Tuple[int]: def get_python_implementation(self) -> str: return platform.python_implementation() - def get_pip_command(self) -> List[str]: + def get_pip_command(self, embedded: bool = False) -> List[str]: # If we're not in a venv, assume the interpreter we're running on # has a pip and use that - return [sys.executable, self.pip] + return [sys.executable, self.pip_embedded if embedded else self.pip] def get_paths(self) -> Dict[str, str]: # We can't use sysconfig.get_paths() because @@ -1445,10 +1460,10 @@ def get_version_info(self) -> Tuple[int]: def get_python_implementation(self) -> str: return self.marker_env["platform_python_implementation"] - def get_pip_command(self) -> List[str]: + def get_pip_command(self, embedded: bool = False) -> List[str]: # We're in a virtualenv that is known to be sane, # so assume that we have a functional pip - return [self._bin("python"), self.pip] + return [self._bin("python"), self.pip_embedded if embedded else self.pip] def get_supported_tags(self) -> List[Tag]: file_path = Path(packaging.tags.__file__) @@ -1560,8 +1575,8 @@ def __init__( self._execute = execute self.executed = [] - def get_pip_command(self) -> List[str]: - return [self._bin("python"), self.pip] + def get_pip_command(self, embedded: bool = False) -> List[str]: + return [self._bin("python"), self.pip_embedded if embedded else self.pip] def _run(self, cmd: List[str], **kwargs: Any) -> int: self.executed.append(cmd) diff --git a/poetry/utils/pip.py b/poetry/utils/pip.py index 5267cf435af..416c56b97ae 100644 --- a/poetry/utils/pip.py +++ b/poetry/utils/pip.py @@ -53,7 +53,7 @@ def pip_install( executable=environment.python, with_pip=True, with_setuptools=True ) as env: return environment.run( - env._bin("pip"), + *env.get_pip_command(), *args, env={**os.environ, "PYTHONPATH": str(env.purelib)}, ) diff --git a/tests/console/commands/env/test_use.py b/tests/console/commands/env/test_use.py index 93e96c02523..8b37c8e726e 100644 --- a/tests/console/commands/env/test_use.py +++ b/tests/console/commands/env/test_use.py @@ -55,6 +55,9 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( venv_py37, executable="python3.7", flags={"always-copy": False, "system-site-packages": False}, + with_pip=True, + with_setuptools=True, + with_wheel=True, ) envs_file = TOMLFile(venv_cache / "envs.toml") diff --git a/tests/inspection/test_info.py b/tests/inspection/test_info.py index 215109d551f..3a15e605a6f 100644 --- a/tests/inspection/test_info.py +++ b/tests/inspection/test_info.py @@ -217,7 +217,7 @@ def test_info_setup_missing_mandatory_should_trigger_pep517( except PackageInfoError: assert spy.call_count == 3 else: - assert spy.call_count == 1 + assert spy.call_count == 2 def test_info_prefer_poetry_config_over_egg_info(): diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index e9bc0e4e1c9..00986905984 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import itertools import json import sys @@ -35,6 +36,9 @@ from tests.repositories.test_pypi_repository import MockRepository +RESERVED_PACKAGES = ("pip", "setuptools", "wheel") + + class Installer(BaseInstaller): def _get_installer(self): return NoopInstaller() @@ -367,59 +371,88 @@ def test_run_install_no_dev_and_dev_only(installer, locker, repo, package, insta assert 1 == installer.executor.removals_count -def test_run_install_remove_untracked(installer, locker, repo, package, installed): +@pytest.mark.parametrize( + "managed_reserved_package_names", + [ + i + for i in itertools.chain( + [tuple()], + itertools.permutations(RESERVED_PACKAGES, 1), + itertools.permutations(RESERVED_PACKAGES, 2), + [RESERVED_PACKAGES], + ) + ], +) +def test_run_install_remove_untracked( + managed_reserved_package_names, installer, locker, repo, package, installed +): + package_a = get_package("a", "1.0") + package_b = get_package("b", "1.1") + package_c = get_package("c", "1.2") + package_pip = get_package("pip", "20.0.0") + package_setuptools = get_package("setuptools", "20.0.0") + package_wheel = get_package("wheel", "20.0.0") + + all_packages = [ + package_a, + package_b, + package_c, + package_pip, + package_setuptools, + package_wheel, + ] + + managed_reserved_packages = [ + pkg for pkg in all_packages if pkg.name in managed_reserved_package_names + ] + locked_packages = [package_a, *managed_reserved_packages] + + for pkg in all_packages: + repo.add_package(pkg) + installed.add_package(pkg) + + installed.add_package(package) # Root package never removed. + + package.add_dependency(Factory.create_dependency(package_a.name, package_a.version)) + locker.locked(True) locker.mock_lock_data( { "package": [ { - "name": "a", - "version": "1.0", + "name": pkg.name, + "version": pkg.version, "category": "main", "optional": False, "platform": "*", "python-versions": "*", "checksum": [], } + for pkg in locked_packages ], "metadata": { "python-versions": "*", "platform": "*", "content-hash": "123456789", - "hashes": {"a": []}, + "hashes": {pkg.name: [] for pkg in locked_packages}, }, } ) - package_a = get_package("a", "1.0") - package_b = get_package("b", "1.1") - package_c = get_package("c", "1.2") - package_pip = get_package("pip", "20.0.0") - package_setuptools = get_package("setuptools", "20.0.0") - - repo.add_package(package_a) - repo.add_package(package_b) - repo.add_package(package_c) - repo.add_package(package_pip) - repo.add_package(package_setuptools) - - installed.add_package(package_a) - installed.add_package(package_b) - installed.add_package(package_c) - installed.add_package(package_pip) - installed.add_package(package_setuptools) - installed.add_package(package) # Root package never removed. - - package.add_dependency(Factory.create_dependency("A", "~1.0")) installer.dev_mode(True).remove_untracked(True) installer.run() assert 0 == installer.executor.installations_count assert 0 == installer.executor.updates_count - assert 4 == installer.executor.removals_count - assert {"b", "c", "pip", "setuptools"} == set( - r.name for r in installer.executor.removals - ) + assert 2 + len(managed_reserved_packages) == installer.executor.removals_count + + expected_removals = { + package_b.name, + package_c.name, + *managed_reserved_package_names, + } + + assert expected_removals == set(r.name for r in installer.executor.removals) def test_run_whitelist_add(installer, locker, repo, package): diff --git a/tests/installation/test_installer_old.py b/tests/installation/test_installer_old.py index a8e7a9c1877..20936fe5f09 100644 --- a/tests/installation/test_installer_old.py +++ b/tests/installation/test_installer_old.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import itertools import sys from pathlib import Path @@ -28,6 +29,9 @@ from tests.repositories.test_pypi_repository import MockRepository +RESERVED_PACKAGES = ("pip", "setuptools", "wheel") + + class Installer(BaseInstaller): def _get_installer(self): return NoopInstaller() @@ -292,49 +296,73 @@ def test_run_install_no_dev(installer, locker, repo, package, installed): assert len(removals) == 1 -def test_run_install_remove_untracked(installer, locker, repo, package, installed): +@pytest.mark.parametrize( + "managed_reserved_package_names", + [ + i + for i in itertools.chain( + [tuple()], + itertools.permutations(RESERVED_PACKAGES, 1), + itertools.permutations(RESERVED_PACKAGES, 2), + [RESERVED_PACKAGES], + ) + ], +) +def test_run_install_remove_untracked( + managed_reserved_package_names, installer, locker, repo, package, installed +): + package_a = get_package("a", "1.0") + package_b = get_package("b", "1.1") + package_c = get_package("c", "1.2") + package_pip = get_package("pip", "20.0.0") + package_setuptools = get_package("setuptools", "20.0.0") + package_wheel = get_package("wheel", "20.0.0") + + all_packages = [ + package_a, + package_b, + package_c, + package_pip, + package_setuptools, + package_wheel, + ] + + managed_reserved_packages = [ + pkg for pkg in all_packages if pkg.name in managed_reserved_package_names + ] + locked_packages = [package_a, *managed_reserved_packages] + + for pkg in all_packages: + repo.add_package(pkg) + installed.add_package(pkg) + + installed.add_package(package) # Root package never removed. + + package.add_dependency(Factory.create_dependency(package_a.name, package_a.version)) + locker.locked(True) locker.mock_lock_data( { "package": [ { - "name": "a", - "version": "1.0", + "name": pkg.name, + "version": pkg.version, "category": "main", "optional": False, "platform": "*", "python-versions": "*", "checksum": [], } + for pkg in locked_packages ], "metadata": { "python-versions": "*", "platform": "*", "content-hash": "123456789", - "hashes": {"a": []}, + "hashes": {pkg.name: [] for pkg in locked_packages}, }, } ) - package_a = get_package("a", "1.0") - package_b = get_package("b", "1.1") - package_c = get_package("c", "1.2") - package_pip = get_package("pip", "20.0.0") - package_setuptools = get_package("setuptools", "20.0.0") - - repo.add_package(package_a) - repo.add_package(package_b) - repo.add_package(package_c) - repo.add_package(package_pip) - repo.add_package(package_setuptools) - - installed.add_package(package_a) - installed.add_package(package_b) - installed.add_package(package_c) - installed.add_package(package_pip) - installed.add_package(package_setuptools) - installed.add_package(package) # Root package never removed. - - package.add_dependency(Factory.create_dependency("A", "~1.0")) installer.dev_mode(True).remove_untracked(True) installer.run() @@ -346,7 +374,12 @@ def test_run_install_remove_untracked(installer, locker, repo, package, installe assert len(updates) == 0 removals = installer.installer.removals - assert set(r.name for r in removals) == {"b", "c", "pip", "setuptools"} + expected_removals = { + package_b.name, + package_c.name, + *managed_reserved_package_names, + } + assert set(r.name for r in removals) == expected_removals def test_run_whitelist_add(installer, locker, repo, package): diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 1cfc9ad5099..d09012c821f 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -4,7 +4,7 @@ import sys from pathlib import Path -from typing import Optional +from typing import Any from typing import Union import pytest @@ -118,9 +118,7 @@ def test_env_get_venv_with_venv_folder_present( assert venv.path == in_project_venv_dir -def build_venv( - path: Union[Path, str], executable: Optional[str] = None, flags: bool = None -) -> (): +def build_venv(path: Union[Path, str], **__: Any) -> (): os.mkdir(str(path)) @@ -161,6 +159,9 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( Path(tmp_dir) / "{}-py3.7".format(venv_name), executable="python3.7", flags={"always-copy": False, "system-site-packages": False}, + with_pip=True, + with_setuptools=True, + with_wheel=True, ) envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") @@ -281,6 +282,9 @@ def test_activate_activates_different_virtualenv_with_envs_file( Path(tmp_dir) / "{}-py3.6".format(venv_name), executable="python3.6", flags={"always-copy": False, "system-site-packages": False}, + with_pip=True, + with_setuptools=True, + with_wheel=True, ) assert envs_file.exists() @@ -335,6 +339,9 @@ def test_activate_activates_recreates_for_different_patch( Path(tmp_dir) / "{}-py3.7".format(venv_name), executable="python3.7", flags={"always-copy": False, "system-site-packages": False}, + with_pip=True, + with_setuptools=True, + with_wheel=True, ) remove_venv_m.assert_called_with(Path(tmp_dir) / "{}-py3.7".format(venv_name)) @@ -715,6 +722,9 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ config_virtualenvs_path / "{}-py3.7".format(venv_name), executable="python3", flags={"always-copy": False, "system-site-packages": False}, + with_pip=True, + with_setuptools=True, + with_wheel=True, ) @@ -739,6 +749,9 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific config_virtualenvs_path / "{}-py3.9".format(venv_name), executable="python3.9", flags={"always-copy": False, "system-site-packages": False}, + with_pip=True, + with_setuptools=True, + with_wheel=True, ) @@ -823,6 +836,9 @@ def test_create_venv_uses_patch_version_to_detect_compatibility( / "{}-py{}.{}".format(venv_name, version.major, version.minor), executable=None, flags={"always-copy": False, "system-site-packages": False}, + with_pip=True, + with_setuptools=True, + with_wheel=True, ) @@ -858,6 +874,9 @@ def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable( / "{}-py{}.{}".format(venv_name, version.major, version.minor - 1), executable="python{}.{}".format(version.major, version.minor - 1), flags={"always-copy": False, "system-site-packages": False}, + with_pip=True, + with_setuptools=True, + with_wheel=True, ) @@ -892,6 +911,9 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( poetry.file.parent / ".venv", executable="python3.7", flags={"always-copy": False, "system-site-packages": False}, + with_pip=True, + with_setuptools=True, + with_wheel=True, ) envs_file = TOMLFile(Path(tmp_dir) / "virtualenvs" / "envs.toml") From 7bf158f340b826ac59981ff4ad6b9992f9bbd24b Mon Sep 17 00:00:00 2001 From: "Avery Fischer (biggerfisch)" Date: Tue, 4 May 2021 20:42:12 +0200 Subject: [PATCH 194/222] Fix documentation typos (you -> your) (#4023) Some docs used "you" where they should use "your" for possessiveness. --- README.md | 4 ++-- docs/docs/index.md | 4 ++-- docs/docs/pyproject.md | 2 +- docs/docs/repositories.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b1a30d7a012..1f43666a795 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,12 @@ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install- **Warning**: The previous `get-poetry.py` installer is now deprecated, if you are currently using it you should migrate to the new, supported, `install-poetry.py` installer. -The installer installs the `poetry` tool to Poetry's `bin` directory. This location depends on you system: +The installer installs the `poetry` tool to Poetry's `bin` directory. This location depends on your system: - `$HOME/.local/bin` for Unix - `%APPDATA%\Python\Scripts` on Windows -If this directory is not on you `PATH`, you will need to add it manually +If this directory is not on your `PATH`, you will need to add it manually if you want to invoke Poetry with simply `poetry`. Alternatively, you can use the full path to `poetry` to use it. diff --git a/docs/docs/index.md b/docs/docs/index.md index b12c510d358..dc0d9295309 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -34,12 +34,12 @@ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install- The previous `get-poetry.py` installer is now deprecated, if you are currently using it you should migrate to the new, supported, `install-poetry.py` installer. -The installer installs the `poetry` tool to Poetry's `bin` directory. This location depends on you system: +The installer installs the `poetry` tool to Poetry's `bin` directory. This location depends on your system: - `$HOME/.local/bin` for Unix - `%APPDATA%\Python\Scripts` on Windows -If this directory is not on you `PATH`, you will need to add it manually +If this directory is not on your `PATH`, you will need to add it manually if you want to invoke Poetry with simply `poetry`. Alternatively, you can use the full path to `poetry` to use it. diff --git a/docs/docs/pyproject.md b/docs/docs/pyproject.md index 8430ad308c6..76cb2a86868 100644 --- a/docs/docs/pyproject.md +++ b/docs/docs/pyproject.md @@ -305,7 +305,7 @@ any custom url in the `urls` section. "Bug Tracker" = "https://github.com/python-poetry/poetry/issues" ``` -If you publish you package on PyPI, they will appear in the `Project Links` section. +If you publish your package on PyPI, they will appear in the `Project Links` section. ## Poetry and PEP-517 diff --git a/docs/docs/repositories.md b/docs/docs/repositories.md index d281f6ca77a..952e91f15d4 100644 --- a/docs/docs/repositories.md +++ b/docs/docs/repositories.md @@ -49,7 +49,7 @@ If you do not specify the password you will be prompted to write it. poetry config pypi-token.pypi my-token ``` - If you still want to use you username and password, you can do so with the following + If you still want to use your username and password, you can do so with the following call to `config`. ```bash From 06dfddece6d8865f12324664e74959884ba3ea27 Mon Sep 17 00:00:00 2001 From: Neil Girdhar Date: Wed, 28 Apr 2021 08:12:26 -0400 Subject: [PATCH 195/222] Change keyring dependency to >=21.2.0 keyring appears to be increasing the major version for minor changes every few months: https://github.com/jaraco/keyring/compare/v22.4.0...v23.0.0 Perhaps it would be better to use a >= dependency here? --- poetry.lock | 18 +++++++++++++----- pyproject.toml | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index d187aaf4f1e..d23a4709889 100644 --- a/poetry.lock +++ b/poetry.lock @@ -285,7 +285,7 @@ test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio"] [[package]] name = "keyring" -version = "21.8.0" +version = "22.3.0" description = "Store and access your passwords safely." category = "main" optional = false @@ -299,7 +299,7 @@ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "pytest-black (>=0.3.7)", "pytest-mypy"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"] [[package]] name = "lockfile" @@ -714,7 +714,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "cccd84fe6459fdb43ff8bb775ee093c7ee351f112a054ffc6ca5ecf59deba1d5" +content-hash = "c2361801df0387c1a84cf3e18aa510b4ae4bab585a203471351382eb8125c4d5" [metadata.files] appdirs = [ @@ -916,8 +916,8 @@ jeepney = [ {file = "jeepney-0.6.0.tar.gz", hash = "sha256:7d59b6622675ca9e993a6bd38de845051d315f8b0c72cca3aef733a20b648657"}, ] keyring = [ - {file = "keyring-21.8.0-py3-none-any.whl", hash = "sha256:4be9cbaaaf83e61d6399f733d113ede7d1c73bc75cb6aeb64eee0f6ac39b30ea"}, - {file = "keyring-21.8.0.tar.gz", hash = "sha256:1746d3ac913d449a090caf11e9e4af00e26c3f7f7e81027872192b2398b98675"}, + {file = "keyring-22.3.0-py3-none-any.whl", hash = "sha256:2bc8363ebdd63886126a012057a85c8cb6e143877afa02619ac7dbc9f38a207b"}, + {file = "keyring-22.3.0.tar.gz", hash = "sha256:16927a444b2c73f983520a48dec79ddab49fe76429ea05b8d528d778c8339522"}, ] lockfile = [ {file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"}, @@ -1031,18 +1031,26 @@ pyyaml = [ {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, diff --git a/pyproject.toml b/pyproject.toml index cc0d2c5e26f..67e3af8b69d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ tomlkit = ">=0.7.0,<1.0.0" pexpect = "^4.7.0" packaging = "^20.4" virtualenv = "^20.4.3" -keyring = "^21.2.0" +keyring = ">=21.2.0" entrypoints = "^0.3" importlib-metadata = {version = "^1.6.0", python = "<3.8"} dataclasses = {version = "^0.8", python = "~3.6"} From 436c2a117d7c6236663537c2c3feae6900968960 Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Thu, 6 May 2021 23:04:10 +0200 Subject: [PATCH 196/222] Add direct_url.json to RECORD upon creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves: #3964 Co-authored-by: Sébastien Eustace Co-authored-by: Arun Babu Neelicattu --- poetry/installation/executor.py | 17 +++++++++++++++++ tests/installation/test_executor.py | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index 0a3f8d58a9e..4580656c933 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -1,3 +1,4 @@ +import csv import itertools import json import os @@ -783,6 +784,22 @@ def _save_url_reference(self, operation: "OperationTypes") -> None: encoding="utf-8", ) + record = dist._path.joinpath("RECORD") + if record.exists(): + with record.open(mode="a", encoding="utf-8") as f: + writer = csv.writer(f) + writer.writerow( + [ + str( + dist._path.joinpath("direct_url.json").relative_to( + record.parent.parent + ) + ), + "", + "", + ] + ) + def _create_git_url_reference( self, package: "Package" ) -> Dict[str, Union[str, Dict[str, str]]]: diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 0438db6a00c..9b7b413b92b 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -357,7 +357,13 @@ def verify_installed_distribution(venv, package, url_reference=None): direct_url_file = distribution._path.joinpath("direct_url.json") if url_reference is not None: + record_file = distribution._path.joinpath("RECORD") + direct_url_entry = direct_url_file.relative_to(record_file.parent.parent) assert direct_url_file.exists() + assert str(direct_url_entry) in { + row.split(",")[0] + for row in record_file.read_text(encoding="utf-8").splitlines() + } assert json.loads(direct_url_file.read_text(encoding="utf-8")) == url_reference else: assert not direct_url_file.exists() From 673c9a570b839c326d357af6176d1b62a72978ab Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Thu, 6 May 2021 02:15:08 +0200 Subject: [PATCH 197/222] Update lockfile to resolve merge issues --- poetry.lock | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/poetry.lock b/poetry.lock index d23a4709889..7e7a2e95760 100644 --- a/poetry.lock +++ b/poetry.lock @@ -390,7 +390,7 @@ dev = ["pre-commit", "tox"] [[package]] name = "poetry-core" -version = "1.1.0a3" +version = "1.1.0a4" description = "Poetry PEP 517 Build Backend" category = "main" optional = false @@ -405,7 +405,7 @@ importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} type = "git" url = "https://github.com/python-poetry/poetry-core.git" reference = "master" -resolved_reference = "3f718c55fcda63d9bd88b8fc612970c24fc9af25" +resolved_reference = "c7e45179c6c2b4e44249e26824ee3f3143b82b76" [[package]] name = "pre-commit" @@ -597,7 +597,7 @@ python-versions = "!=3.0,!=3.1,!=3.2,!=3.3,>=2.6" [[package]] name = "six" -version = "1.15.0" +version = "1.16.0" description = "Python 2 and 3 compatibility utilities" category = "main" optional = false @@ -629,7 +629,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "tox" -version = "3.23.0" +version = "3.23.1" description = "tox is a generic virtualenv management and test command line tool" category = "dev" optional = false @@ -665,7 +665,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.4.4" +version = "20.4.6" description = "Virtual Python Environment builder" category = "main" optional = false @@ -1031,26 +1031,18 @@ pyyaml = [ {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, @@ -1072,8 +1064,8 @@ shellingham = [ {file = "shellingham-1.4.0.tar.gz", hash = "sha256:4855c2458d6904829bd34c299f11fdeed7cfefbf8a2c522e4caea6cd76b3171e"}, ] six = [ - {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, - {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] termcolor = [ {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"}, @@ -1087,16 +1079,16 @@ tomlkit = [ {file = "tomlkit-0.7.0.tar.gz", hash = "sha256:ac57f29693fab3e309ea789252fcce3061e19110085aa31af5446ca749325618"}, ] tox = [ - {file = "tox-3.23.0-py2.py3-none-any.whl", hash = "sha256:e007673f3595cede9b17a7c4962389e4305d4a3682a6c5a4159a1453b4f326aa"}, - {file = "tox-3.23.0.tar.gz", hash = "sha256:05a4dbd5e4d3d8269b72b55600f0b0303e2eb47ad5c6fe76d3576f4c58d93661"}, + {file = "tox-3.23.1-py2.py3-none-any.whl", hash = "sha256:b0b5818049a1c1997599d42012a637a33f24c62ab8187223fdd318fa8522637b"}, + {file = "tox-3.23.1.tar.gz", hash = "sha256:307a81ddb82bd463971a273f33e9533a24ed22185f27db8ce3386bff27d324e3"}, ] urllib3 = [ {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"}, {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, ] virtualenv = [ - {file = "virtualenv-20.4.4-py2.py3-none-any.whl", hash = "sha256:a935126db63128861987a7d5d30e23e8ec045a73840eeccb467c148514e29535"}, - {file = "virtualenv-20.4.4.tar.gz", hash = "sha256:09c61377ef072f43568207dc8e46ddeac6bcdcaf288d49011bda0e7f4d38c4a2"}, + {file = "virtualenv-20.4.6-py2.py3-none-any.whl", hash = "sha256:307a555cf21e1550885c82120eccaf5acedf42978fd362d32ba8410f9593f543"}, + {file = "virtualenv-20.4.6.tar.gz", hash = "sha256:72cf267afc04bf9c86ec932329b7e94db6a0331ae9847576daaa7ca3c86b29a4"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, From 41223798c1b8ca56ea00dc9294cf6bc6bc402367 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Thu, 6 May 2021 23:13:21 +0200 Subject: [PATCH 198/222] Clamp max virtualenv version to 20.4.4 --- poetry.lock | 22 +++++++++++----------- pyproject.toml | 3 ++- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7e7a2e95760..5c2a58ef77d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -16,17 +16,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "20.3.0" +version = "21.1.0" description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] [[package]] name = "cachecontrol" @@ -665,7 +665,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.4.6" +version = "20.4.4" description = "Virtual Python Environment builder" category = "main" optional = false @@ -714,7 +714,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "c2361801df0387c1a84cf3e18aa510b4ae4bab585a203471351382eb8125c4d5" +content-hash = "9d02b786fa736951d56bd71ef48a3c496bcb91ff39dea598f022de3538fb8e60" [metadata.files] appdirs = [ @@ -726,8 +726,8 @@ atomicwrites = [ {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, - {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, + {file = "attrs-21.1.0-py2.py3-none-any.whl", hash = "sha256:8ee1e5f5a1afc5b19bdfae4fdf0c35ed324074bdce3500c939842c8f818645d9"}, + {file = "attrs-21.1.0.tar.gz", hash = "sha256:3901be1cb7c2a780f14668691474d9252c070a756be0a9ead98cfeabfa11aeb8"}, ] cachecontrol = [ {file = "CacheControl-0.12.6-py2.py3-none-any.whl", hash = "sha256:10d056fa27f8563a271b345207402a6dcce8efab7e5b377e270329c62471b10d"}, @@ -1087,8 +1087,8 @@ urllib3 = [ {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, ] virtualenv = [ - {file = "virtualenv-20.4.6-py2.py3-none-any.whl", hash = "sha256:307a555cf21e1550885c82120eccaf5acedf42978fd362d32ba8410f9593f543"}, - {file = "virtualenv-20.4.6.tar.gz", hash = "sha256:72cf267afc04bf9c86ec932329b7e94db6a0331ae9847576daaa7ca3c86b29a4"}, + {file = "virtualenv-20.4.4-py2.py3-none-any.whl", hash = "sha256:a935126db63128861987a7d5d30e23e8ec045a73840eeccb467c148514e29535"}, + {file = "virtualenv-20.4.4.tar.gz", hash = "sha256:09c61377ef072f43568207dc8e46ddeac6bcdcaf288d49011bda0e7f4d38c4a2"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, diff --git a/pyproject.toml b/pyproject.toml index 67e3af8b69d..1e61cc4598a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,8 @@ shellingham = "^1.1" tomlkit = ">=0.7.0,<1.0.0" pexpect = "^4.7.0" packaging = "^20.4" -virtualenv = "^20.4.3" +# temporarily clamped due to https://github.com/pypa/pip/issues/9953 +virtualenv = ">=20.4.3,<20.4.5" keyring = ">=21.2.0" entrypoints = "^0.3" importlib-metadata = {version = "^1.6.0", python = "<3.8"} From 9591e88492508d4dba260952d53266a0032c04c7 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 7 May 2021 03:39:03 +0200 Subject: [PATCH 199/222] deps: update development constraints --- poetry.lock | 83 ++++++++----------- pyproject.toml | 12 ++- .../repositories/test_installed_repository.py | 4 +- 3 files changed, 42 insertions(+), 57 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5c2a58ef77d..ee407ef3ba4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -272,6 +272,14 @@ zipp = {version = ">=0.4", markers = "python_version < \"3.8\""} docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"] +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "jeepney" version = "0.6.0" @@ -309,14 +317,6 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "more-itertools" -version = "8.7.0" -description = "More routines for operating on iterables, beyond itertools" -category = "dev" -optional = false -python-versions = ">=3.5" - [[package]] name = "msgpack" version = "1.0.2" @@ -405,7 +405,7 @@ importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} type = "git" url = "https://github.com/python-poetry/poetry-core.git" reference = "master" -resolved_reference = "c7e45179c6c2b4e44249e26824ee3f3143b82b76" +resolved_reference = "8b1cc5ea8d4557a4d215c873ebf2acfe282ad64d" [[package]] name = "pre-commit" @@ -467,25 +467,24 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "pytest" -version = "5.4.3" +version = "6.2.4" description = "pytest: simple powerful testing with Python" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=17.4.0" +attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -more-itertools = ">=4.0.0" +iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<1.0" -py = ">=1.5.0" -wcwidth = "*" +pluggy = ">=0.12,<1.0.0a1" +py = ">=1.8.2" +toml = "*" [package.extras] -checkqa-mypy = ["mypy (==v0.761)"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] @@ -505,17 +504,17 @@ testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", [[package]] name = "pytest-mock" -version = "1.13.0" -description = "Thin-wrapper around the mock package for easier use with py.test" +version = "3.6.1" +description = "Thin-wrapper around the mock package for easier use with pytest" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] -pytest = ">=2.7" +pytest = ">=5.0" [package.extras] -dev = ["pre-commit", "tox"] +dev = ["pre-commit", "tox", "pytest-asyncio"] [[package]] name = "pytest-sugar" @@ -652,16 +651,16 @@ testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "psutil (>=5.6.1)", "pytes [[package]] name = "urllib3" -version = "1.25.10" +version = "1.26.4" 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.*, <4" [package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotlipy (>=0.6.0)"] [[package]] name = "virtualenv" @@ -683,14 +682,6 @@ six = ">=1.9.0,<2" docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] -[[package]] -name = "wcwidth" -version = "0.2.5" -description = "Measures the displayed width of unicode strings in a terminal" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "webencodings" version = "0.5.1" @@ -714,7 +705,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "9d02b786fa736951d56bd71ef48a3c496bcb91ff39dea598f022de3538fb8e60" +content-hash = "e38d34da68dcd63ef9f9d999f60b8e19d8da7f988031c3a6e6865d51c1fadeda" [metadata.files] appdirs = [ @@ -911,6 +902,10 @@ importlib-resources = [ {file = "importlib_resources-5.1.2-py3-none-any.whl", hash = "sha256:ebab3efe74d83b04d6bf5cd9a17f0c5c93e60fb60f30c90f56265fce4682a469"}, {file = "importlib_resources-5.1.2.tar.gz", hash = "sha256:642586fc4740bd1cad7690f836b3321309402b20b332529f25617ff18e8e1370"}, ] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] jeepney = [ {file = "jeepney-0.6.0-py3-none-any.whl", hash = "sha256:aec56c0eb1691a841795111e184e13cad504f7703b9a64f63020816afa79a8ae"}, {file = "jeepney-0.6.0.tar.gz", hash = "sha256:7d59b6622675ca9e993a6bd38de845051d315f8b0c72cca3aef733a20b648657"}, @@ -923,10 +918,6 @@ 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.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"}, - {file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"}, -] msgpack = [ {file = "msgpack-1.0.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:b6d9e2dae081aa35c44af9c4298de4ee72991305503442a5c74656d82b581fe9"}, {file = "msgpack-1.0.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a99b144475230982aee16b3d249170f1cccebf27fb0a08e9f603b69637a62192"}, @@ -1006,16 +997,16 @@ pyparsing = [ {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pytest = [ - {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, - {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, + {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, + {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, ] pytest-cov = [ {file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"}, {file = "pytest_cov-2.11.1-py2.py3-none-any.whl", hash = "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da"}, ] pytest-mock = [ - {file = "pytest-mock-1.13.0.tar.gz", hash = "sha256:e24a911ec96773022ebcc7030059b57cd3480b56d4f5d19b7c370ec635e6aed5"}, - {file = "pytest_mock-1.13.0-py2.py3-none-any.whl", hash = "sha256:67e414b3caef7bff6fc6bd83b22b5bc39147e4493f483c2679bc9d4dc485a94d"}, + {file = "pytest-mock-3.6.1.tar.gz", hash = "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"}, + {file = "pytest_mock-3.6.1-py3-none-any.whl", hash = "sha256:30c2f2cc9759e76eee674b81ea28c9f0b94f8f0445a1b87762cadf774f0df7e3"}, ] pytest-sugar = [ {file = "pytest-sugar-0.9.4.tar.gz", hash = "sha256:b1b2186b0a72aada6859bea2a5764145e3aaa2c1cfbb23c3a19b5f7b697563d3"}, @@ -1083,17 +1074,13 @@ tox = [ {file = "tox-3.23.1.tar.gz", hash = "sha256:307a81ddb82bd463971a273f33e9533a24ed22185f27db8ce3386bff27d324e3"}, ] urllib3 = [ - {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"}, - {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, + {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"}, + {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"}, ] virtualenv = [ {file = "virtualenv-20.4.4-py2.py3-none-any.whl", hash = "sha256:a935126db63128861987a7d5d30e23e8ec045a73840eeccb467c148514e29535"}, {file = "virtualenv-20.4.4.tar.gz", hash = "sha256:09c61377ef072f43568207dc8e46ddeac6bcdcaf288d49011bda0e7f4d38c4a2"}, ] -wcwidth = [ - {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, - {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, -] webencodings = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, diff --git a/pyproject.toml b/pyproject.toml index 1e61cc4598a..20bd1420866 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,17 +52,15 @@ importlib-metadata = {version = "^1.6.0", python = "<3.8"} dataclasses = {version = "^0.8", python = "~3.6"} [tool.poetry.dev-dependencies] -pytest = "^5.4.3" -pytest-cov = "^2.5" -pytest-mock = "^1.9" +pytest = "^6.2" +pytest-cov = "^2.8" +pytest-mock = "^3.5" pre-commit = { version = "^2.6", python = "^3.6.1" } tox = "^3.0" -pytest-sugar = "^0.9.2" +pytest-sugar = "^0.9" httpretty = "^1.0" zipp = { version = "^3.4", python = "<3.8"} -deepdiff = "^5.0.2" -# temporary workaround for https://github.com/python-poetry/poetry/issues/3404 -urllib3 = "1.25.10" +deepdiff = "^5.0" [tool.poetry.scripts] poetry = "poetry.console.application:main" diff --git a/tests/repositories/test_installed_repository.py b/tests/repositories/test_installed_repository.py index 5342181588d..8fd3da25ea2 100644 --- a/tests/repositories/test_installed_repository.py +++ b/tests/repositories/test_installed_repository.py @@ -3,7 +3,7 @@ import pytest -from pytest_mock.plugin import MockFixture +from pytest_mock.plugin import MockerFixture from poetry.core.packages.package import Package from poetry.repositories.installed_repository import InstalledRepository @@ -59,7 +59,7 @@ def env() -> MockEnv: @pytest.fixture -def repository(mocker: MockFixture, env: MockEnv) -> InstalledRepository: +def repository(mocker: MockerFixture, env: MockEnv) -> InstalledRepository: mocker.patch( "poetry.utils._compat.metadata.Distribution.discover", return_value=INSTALLED_RESULTS, From f51580130eb7ed2bf0d2f14c1ee297c7344c97db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Tue, 18 May 2021 15:24:38 +0200 Subject: [PATCH 200/222] Ensure we don't pick up Poetry's virtualenv as the system env --- poetry/console/commands/plugin/add.py | 2 +- poetry/console/commands/plugin/remove.py | 2 +- poetry/console/commands/plugin/show.py | 2 +- poetry/locations.py | 9 ++++++++ poetry/utils/env.py | 27 ++++++++++++++++++++++-- 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/poetry/console/commands/plugin/add.py b/poetry/console/commands/plugin/add.py index 8418aa25c89..c0a61e3843d 100644 --- a/poetry/console/commands/plugin/add.py +++ b/poetry/console/commands/plugin/add.py @@ -72,7 +72,7 @@ def handle(self) -> int: plugins = self.argument("plugins") # Plugins should be installed in the system env to be globally available - system_env = EnvManager.get_system_env() + system_env = EnvManager.get_system_env(naive=True) env_dir = Path( os.getenv("POETRY_HOME") if os.getenv("POETRY_HOME") else system_env.path diff --git a/poetry/console/commands/plugin/remove.py b/poetry/console/commands/plugin/remove.py index cb8143175f7..9d90e8fea5e 100644 --- a/poetry/console/commands/plugin/remove.py +++ b/poetry/console/commands/plugin/remove.py @@ -43,7 +43,7 @@ def handle(self) -> int: plugins = self.argument("plugins") - system_env = EnvManager.get_system_env() + system_env = EnvManager.get_system_env(naive=True) env_dir = Path( os.getenv("POETRY_HOME") if os.getenv("POETRY_HOME") else system_env.path ) diff --git a/poetry/console/commands/plugin/show.py b/poetry/console/commands/plugin/show.py index ced1a0fd282..8ca6290d870 100644 --- a/poetry/console/commands/plugin/show.py +++ b/poetry/console/commands/plugin/show.py @@ -38,7 +38,7 @@ def handle(self) -> int: + PluginManager("plugin").get_plugin_entry_points() ) - system_env = EnvManager.get_system_env() + system_env = EnvManager.get_system_env(naive=True) installed_repository = InstalledRepository.load( system_env, with_dependencies=True ) diff --git a/poetry/locations.py b/poetry/locations.py index ff38c9c9e82..b6b6f84681d 100644 --- a/poetry/locations.py +++ b/poetry/locations.py @@ -1,3 +1,5 @@ +import os + from pathlib import Path from .utils.appdirs import user_cache_dir @@ -10,3 +12,10 @@ CONFIG_DIR = user_config_dir("pypoetry") REPOSITORY_CACHE_DIR = Path(CACHE_DIR) / "cache" / "repositories" + + +def data_dir() -> Path: + if os.getenv("POETRY_HOME"): + return Path(os.getenv("POETRY_HOME")).expanduser() + + return Path(user_data_dir("pypoetry", roaming=True)) diff --git a/poetry/utils/env.py b/poetry/utils/env.py index 195d08162de..d35fb221313 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -996,8 +996,31 @@ def remove_venv(cls, path: Union[Path, str]) -> None: shutil.rmtree(str(file_path)) @classmethod - def get_system_env(cls) -> "SystemEnv": - return SystemEnv(Path(sys.prefix), cls.get_base_prefix()) + def get_system_env(cls, naive: bool = False) -> "SystemEnv": + """ + Retrieve the current Python environment. + + This can be the base Python environment or an activated virtual environment. + + This method also workaround the issue that the virtual environment + used by Poetry internally (when installed via the custom installer) + is incorrectly detected as the system environment. Note that this workaround + happens only when `naive` is False since there are times where we actually + want to retrieve Poetry's custom virtual environment + (e.g. plugin installation or self update). + """ + prefix, base_prefix = Path(sys.prefix), cls.get_base_prefix() + if naive is False: + from poetry.locations import data_dir + + try: + prefix.relative_to(data_dir()) + except ValueError: + pass + else: + prefix = base_prefix + + return SystemEnv(prefix, base_prefix) @classmethod def get_base_prefix(cls) -> Path: From c02703eeff488bb00c2d25d2bd152412c40e4bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 30 Apr 2021 18:12:42 +0200 Subject: [PATCH 201/222] Improve `self update` following the introduction of install-poetry.py --- poetry/console/commands/self/update.py | 346 ++++++++------------- poetry/packages/locker.py | 5 + tests/console/commands/self/test_update.py | 147 +++++---- 3 files changed, 207 insertions(+), 291 deletions(-) diff --git a/poetry/console/commands/self/update.py b/poetry/console/commands/self/update.py index 95e8f5dce6a..e40dc225274 100644 --- a/poetry/console/commands/self/update.py +++ b/poetry/console/commands/self/update.py @@ -1,21 +1,10 @@ -from __future__ import unicode_literals - -import hashlib import os -import re import shutil -import stat -import subprocess -import sys -import tarfile +import site from functools import cmp_to_key -from gzip import GzipFile from pathlib import Path from typing import TYPE_CHECKING -from typing import Any -from urllib.error import HTTPError -from urllib.request import urlopen from cleo.helpers import argument from cleo.helpers import option @@ -26,27 +15,7 @@ if TYPE_CHECKING: from poetry.core.packages.package import Package from poetry.core.semver.version import Version - - -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 = '@echo off\r\n{python_executable} "{poetry_bin}" %*\r\n' + from poetry.repositories.pool import Pool class SelfUpdateCommand(Command): @@ -55,48 +24,81 @@ class SelfUpdateCommand(Command): description = "Updates Poetry to the latest version." arguments = [argument("version", "The version to update to.", optional=True)] - options = [option("preview", None, "Install prereleases.")] - - REPOSITORY_URL = "https://github.com/python-poetry/poetry" - BASE_URL = REPOSITORY_URL + "/releases/download" + options = [ + option("preview", None, "Allow the installation of pre-release versions."), + option( + "dry-run", + None, + "Output the operations but do not execute anything " + "(implicitly enables --verbose).", + ), + ] + + _data_dir = None + _bin_dir = None + _pool = None @property - def home(self) -> Path: - from pathlib import Path + def data_dir(self) -> Path: + if self._data_dir is not None: + return self._data_dir - return Path(os.environ.get("POETRY_HOME", "~/.poetry")).expanduser() + from poetry.locations import data_dir - @property - def bin(self) -> Path: - return self.home / "bin" + self._data_dir = data_dir() + + return self._data_dir @property - def lib(self) -> Path: - return self.home / "lib" + def bin_dir(self) -> Path: + if self._data_dir is not None: + return self._data_dir + + from poetry.utils._compat import WINDOWS + + if os.getenv("POETRY_HOME"): + return Path(os.getenv("POETRY_HOME"), "bin").expanduser() + + user_base = site.getuserbase() + + if WINDOWS: + bin_dir = os.path.join(user_base, "Scripts") + else: + bin_dir = os.path.join(user_base, "bin") + + self._bin_dir = Path(bin_dir) + + return self._bin_dir @property - def lib_backup(self) -> Path: - return self.home / "lib-backup" + def pool(self) -> "Pool": + if self._pool is not None: + return self._pool + + from poetry.repositories.pool import Pool + from poetry.repositories.pypi_repository import PyPiRepository + + pool = Pool() + pool.add_repository(PyPiRepository()) - def handle(self) -> None: + return pool + + def handle(self) -> int: from poetry.__version__ import __version__ from poetry.core.packages.dependency import Dependency from poetry.core.semver.version import Version - from poetry.repositories.pypi_repository import PyPiRepository - - self._check_recommended_installation() version = self.argument("version") if not version: version = ">=" + __version__ - repo = PyPiRepository(fallback=False) + repo = self.pool.repositories[0] packages = repo.find_packages( Dependency("poetry", version, allows_prereleases=self.option("preview")) ) if not packages: self.line("No release found for the specified version") - return + return 1 packages.sort( key=cmp_to_key( @@ -122,205 +124,101 @@ def handle(self) -> None: if release is None: self.line("No new release found") - return + return 1 if release.version == Version.parse(__version__): self.line("You are using the latest version") - return - - self.update(release) - - def update(self, release: "Package") -> None: - version = release.version - self.line("Updating to {}".format(version)) - - if self.lib_backup.exists(): - shutil.rmtree(str(self.lib_backup)) + return 0 - # Backup the current installation - if self.lib.exists(): - shutil.copytree(str(self.lib), str(self.lib_backup)) - shutil.rmtree(str(self.lib)) - - try: - self._update(version) - except Exception: - if not self.lib_backup.exists(): - raise - - shutil.copytree(str(self.lib_backup), str(self.lib)) - shutil.rmtree(str(self.lib_backup)) - - raise - finally: - if self.lib_backup.exists(): - shutil.rmtree(str(self.lib_backup)) + self.line("Updating Poetry to {}".format(release.version)) + self.line("") - self.make_bin() + self.update(release) - self.line("") self.line("") self.line( - "Poetry ({}) is installed now. Great!".format( - version + "Poetry ({}) is installed now. Great!".format( + release.version ) ) - def _update(self, version: "Version") -> None: - from poetry.utils.helpers import temporary_directory + return 0 - release_name = self._get_release_name(version) - - checksum = "{}.sha256sum".format(release_name) - - base_url = self.BASE_URL - - try: - r = urlopen(base_url + "/{}/{}".format(version, checksum)) - except HTTPError as e: - if e.code == 404: - raise RuntimeError("Could not find {} file".format(checksum)) + def update(self, release: "Package") -> None: + from poetry.utils.env import EnvManager - raise + version = release.version - checksum = r.read().decode().strip() + env = EnvManager.get_system_env(naive=True) - # We get the payload from the remote host - name = "{}.tar.gz".format(release_name) - try: - r = urlopen(base_url + "/{}/{}".format(version, 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 - - bar = self.progress_bar(max=size) - bar.set_format(" - Downloading {} %percent%%".format(name)) - bar.start() - - sha = hashlib.sha256() - with temporary_directory(prefix="poetry-updater-") 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) - - bar.set_progress(current) - - bar.finish() - - # Checking hashes - if checksum != sha.hexdigest(): - raise RuntimeError( - "Hashes for {} do not match: {} != {}".format( - name, checksum, sha.hexdigest() - ) - ) - - gz = GzipFile(tar, mode="rb") - try: - with tarfile.TarFile(tar, fileobj=gz, format=tarfile.PAX_FORMAT) as f: - f.extractall(str(self.lib)) - finally: - gz.close() - - def process(self, *args: Any) -> str: - return subprocess.check_output(list(args), stderr=subprocess.STDOUT) - - def _check_recommended_installation(self) -> None: - from pathlib import Path - - from poetry.console.exceptions import PoetrySimpleConsoleException - - current = Path(__file__) + # We can't use is_relative_to() since it's only available in Python 3.9+ try: - current.relative_to(self.home) + env.path.relative_to(self.data_dir) except ValueError: + # Poetry was not installed using the recommended installer + from poetry.console.exceptions import PoetrySimpleConsoleException + raise PoetrySimpleConsoleException( "Poetry was not installed with the recommended installer, " "so it cannot be updated automatically." ) - def _get_release_name(self, version: "Version") -> str: - platform = sys.platform - if platform == "linux2": - platform = "linux" + self._update(version) + self._make_bin() - return "poetry-{}-{}".format(version, platform) + def _update(self, version: "Version") -> None: + from poetry.config.config import Config + from poetry.core.packages.dependency import Dependency + from poetry.core.packages.project_package import ProjectPackage + from poetry.installation.installer import Installer + from poetry.packages.locker import NullLocker + from poetry.repositories.installed_repository import InstalledRepository + from poetry.utils.env import EnvManager + + env = EnvManager.get_system_env() + installed = InstalledRepository.load(env) + + root = ProjectPackage("poetry-updater", "0.0.0") + root.python_versions = ".".join(str(c) for c in env.version_info[:3]) + root.add_dependency(Dependency("poetry", version.text)) + + installer = Installer( + self.io, + env, + root, + NullLocker(self.data_dir.joinpath("poetry.lock"), {}), + self.pool, + Config(), + installed=installed, + ) + installer.update(True) + installer.dry_run(self.option("dry-run")) + installer.run() - def make_bin(self) -> None: + def _make_bin(self) -> None: from poetry.utils._compat import WINDOWS - self.bin.mkdir(0o755, parents=True, exist_ok=True) - - python_executable = self._which_python() + self.line("") + self.line("Updating the poetry script") - if WINDOWS: - with self.bin.joinpath("poetry.bat").open("w", newline="") as f: - f.write( - BAT.format( - python_executable=python_executable, - poetry_bin=str(self.bin / "poetry").replace( - os.environ["USERPROFILE"], "%USERPROFILE%" - ), - ) - ) - - bin_content = BIN - if not WINDOWS: - bin_content = "#!/usr/bin/env {}\n".format(python_executable) + bin_content - - self.bin.joinpath("poetry").write_text(bin_content, encoding="utf-8") - - if not WINDOWS: - # Making the file executable - st = os.stat(str(self.bin.joinpath("poetry"))) - os.chmod(str(self.bin.joinpath("poetry")), st.st_mode | stat.S_IEXEC) - - def _which_python(self) -> str: - """ - Decides which python executable we'll embed in the launcher script. - """ - from poetry.utils._compat import WINDOWS + self.bin_dir.mkdir(parents=True, exist_ok=True) - allowed_executables = ["python", "python3"] + script = "poetry" + target_script = "venv/bin/poetry" 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 and tuple(map(int, match.groups())) >= (3, 0): - # favor the first py3 executable we can find. - return executable - - if fallback is None: - # keep this one as the fallback; it was the first valid executable we found. - fallback = executable + script = "poetry.exe" + target_script = "venv/Scripts/poetry.exe" - if fallback is None: - # Avoid breaking existing scripts - fallback = "python" + if self.bin_dir.joinpath(script).exists(): + self.bin_dir.joinpath(script).unlink() - return fallback + try: + self.bin_dir.joinpath(script).symlink_to( + self.data_dir.joinpath(target_script) + ) + except OSError: + # This can happen if the user + # does not have the correct permission on Windows + shutil.copy( + self.data_dir.joinpath(target_script), self.bin_dir.joinpath(script) + ) diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index 03f6e677eb9..c1cbe6609cb 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -595,3 +595,8 @@ def _dump_package(self, package: Package) -> dict: data["develop"] = package.develop return data + + +class NullLocker(Locker): + def set_lock_data(self, root: Package, packages: List[Package]) -> bool: + pass diff --git a/tests/console/commands/self/test_update.py b/tests/console/commands/self/test_update.py index 5b70b4daefe..7d5c43c01e2 100644 --- a/tests/console/commands/self/test_update.py +++ b/tests/console/commands/self/test_update.py @@ -1,13 +1,16 @@ -import os - from pathlib import Path import pytest from poetry.__version__ import __version__ +from poetry.console.exceptions import PoetrySimpleConsoleException from poetry.core.packages.package import Package from poetry.core.semver.version import Version -from poetry.utils._compat import WINDOWS +from poetry.factory import Factory +from poetry.repositories.installed_repository import InstalledRepository +from poetry.repositories.pool import Pool +from poetry.repositories.repository import Repository +from poetry.utils.env import EnvManager FIXTURES = Path(__file__).parent.joinpath("fixtures") @@ -18,75 +21,85 @@ def tester(command_tester_factory): return command_tester_factory("self update") -def test_self_update_should_install_all_necessary_elements( - tester, http, mocker, environ, tmp_dir +def test_self_update_can_update_from_recommended_installation( + tester, http, mocker, environ, tmp_venv ): - os.environ["POETRY_HOME"] = tmp_dir + mocker.patch.object(EnvManager, "get_system_env", return_value=tmp_venv) command = tester.command + command._data_dir = tmp_venv.path.parent + + new_version = Version.parse(__version__).next_minor().text + + old_poetry = Package("poetry", __version__) + old_poetry.add_dependency(Factory.create_dependency("cleo", "^0.8.2")) + + new_poetry = Package("poetry", new_version) + new_poetry.add_dependency(Factory.create_dependency("cleo", "^1.0.0")) + + installed_repository = Repository() + installed_repository.add_package(old_poetry) + installed_repository.add_package(Package("cleo", "0.8.2")) + + repository = Repository() + repository.add_package(new_poetry) + repository.add_package(Package("cleo", "1.0.0")) + + pool = Pool() + pool.add_repository(repository) - version = Version.parse(__version__).next_minor().text - mocker.patch( - "poetry.repositories.pypi_repository.PyPiRepository.find_packages", - return_value=[Package("poetry", version)], - ) - mocker.patch.object(command, "_check_recommended_installation", return_value=None) - mocker.patch.object( - command, "_get_release_name", return_value="poetry-{}-darwin".format(version) - ) - mocker.patch("subprocess.check_output", return_value=b"Python 3.8.2") - - http.register_uri( - "GET", - command.BASE_URL + "/{}/poetry-{}-darwin.sha256sum".format(version, version), - body=FIXTURES.joinpath("poetry-1.0.5-darwin.sha256sum").read_bytes(), - ) - http.register_uri( - "GET", - command.BASE_URL + "/{}/poetry-{}-darwin.tar.gz".format(version, version), - body=FIXTURES.joinpath("poetry-1.0.5-darwin.tar.gz").read_bytes(), - ) + command._pool = pool + + mocker.patch.object(InstalledRepository, "load", return_value=installed_repository) tester.execute() - bin_ = Path(tmp_dir).joinpath("bin") - lib = Path(tmp_dir).joinpath("lib") - assert bin_.exists() - - script = bin_.joinpath("poetry") - assert script.exists() - - expected_script = """\ -# -*- 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() + expected_output = """\ +Updating Poetry to 1.2.0 + +Updating dependencies +Resolving dependencies... + +Package operations: 0 installs, 2 updates, 0 removals + + - Updating cleo (0.8.2 -> 1.0.0) + - Updating poetry (1.2.0a0 -> 1.2.0) + +Updating the poetry script + +Poetry (1.2.0) is installed now. Great! """ - if not WINDOWS: - expected_script = "#!/usr/bin/env python\n" + expected_script - - assert expected_script == script.read_text() - - if WINDOWS: - bat = bin_.joinpath("poetry.bat") - expected_bat = '@echo off\r\npython "{}" %*\r\n'.format( - str(script).replace(os.environ.get("USERPROFILE", ""), "%USERPROFILE%") - ) - assert bat.exists() - with bat.open(newline="") as f: - assert expected_bat == f.read() - - assert lib.exists() - assert lib.joinpath("poetry").exists() + + assert tester.io.fetch_output() == expected_output + + +def test_self_update_does_not_update_non_recommended_installation( + tester, http, mocker, environ, tmp_venv +): + mocker.patch.object(EnvManager, "get_system_env", return_value=tmp_venv) + + command = tester.command + + new_version = Version.parse(__version__).next_minor().text + + old_poetry = Package("poetry", __version__) + old_poetry.add_dependency(Factory.create_dependency("cleo", "^0.8.2")) + + new_poetry = Package("poetry", new_version) + new_poetry.add_dependency(Factory.create_dependency("cleo", "^1.0.0")) + + installed_repository = Repository() + installed_repository.add_package(old_poetry) + installed_repository.add_package(Package("cleo", "0.8.2")) + + repository = Repository() + repository.add_package(new_poetry) + repository.add_package(Package("cleo", "1.0.0")) + + pool = Pool() + pool.add_repository(repository) + + command._pool = pool + + with pytest.raises(PoetrySimpleConsoleException): + tester.execute() From d3c65eddbd2c196630389e71eec416beba05e909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 21 May 2021 14:49:20 +0200 Subject: [PATCH 202/222] Fix self update command tests --- tests/console/commands/self/test_update.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/console/commands/self/test_update.py b/tests/console/commands/self/test_update.py index 7d5c43c01e2..396965e2e68 100644 --- a/tests/console/commands/self/test_update.py +++ b/tests/console/commands/self/test_update.py @@ -63,12 +63,14 @@ def test_self_update_can_update_from_recommended_installation( Package operations: 0 installs, 2 updates, 0 removals - Updating cleo (0.8.2 -> 1.0.0) - - Updating poetry (1.2.0a0 -> 1.2.0) + - Updating poetry ({} -> {}) Updating the poetry script -Poetry (1.2.0) is installed now. Great! -""" +Poetry ({}) is installed now. Great! +""".format( + __version__, new_version, new_version + ) assert tester.io.fetch_output() == expected_output From 5f51951f5fa441243f14aa131a898b3342ca5b3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 23 Apr 2021 14:11:25 +0200 Subject: [PATCH 203/222] Update dependencies --- poetry.lock | 61 +++++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/poetry.lock b/poetry.lock index ee407ef3ba4..26eb85c6f31 100644 --- a/poetry.lock +++ b/poetry.lock @@ -16,11 +16,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "21.1.0" +version = "21.2.0" description = "Classes Without Boilerplate" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] @@ -79,7 +79,7 @@ pycparser = "*" [[package]] name = "cfgv" -version = "3.2.0" +version = "3.3.0" description = "Validate configuration and produce human readable error messages." category = "dev" optional = false @@ -121,6 +121,9 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +[package.dependencies] +toml = {version = "*", optional = true, markers = "extra == \"toml\""} + [package.extras] toml = ["toml"] @@ -217,7 +220,7 @@ lxml = ["lxml"] [[package]] name = "httpretty" -version = "1.0.5" +version = "1.1.2" description = "HTTP client mock for Python" category = "dev" optional = false @@ -259,7 +262,7 @@ testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] [[package]] name = "importlib-resources" -version = "5.1.2" +version = "5.1.3" description = "Read resources from Python packages" category = "main" optional = false @@ -270,7 +273,7 @@ zipp = {version = ">=0.4", markers = "python_version < \"3.8\""} [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"] [[package]] name = "iniconfig" @@ -390,23 +393,16 @@ dev = ["pre-commit", "tox"] [[package]] name = "poetry-core" -version = "1.1.0a4" +version = "1.1.0a5" description = "Poetry PEP 517 Build Backend" category = "main" optional = false -python-versions = "^3.6" -develop = false +python-versions = ">=3.6,<4.0" [package.dependencies] dataclasses = {version = ">=0.8", markers = "python_version >= \"3.6\" and python_version < \"3.7\""} importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} -[package.source] -type = "git" -url = "https://github.com/python-poetry/poetry-core.git" -reference = "master" -resolved_reference = "8b1cc5ea8d4557a4d215c873ebf2acfe282ad64d" - [[package]] name = "pre-commit" version = "2.12.1" @@ -489,14 +485,14 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "pytest-cov" -version = "2.11.1" +version = "2.12.0" description = "Pytest plugin for measuring coverage." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] -coverage = ">=5.2.1" +coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] @@ -620,7 +616,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomlkit" -version = "0.7.0" +version = "0.7.2" description = "Style preserving TOML library" category = "main" optional = false @@ -705,7 +701,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "e38d34da68dcd63ef9f9d999f60b8e19d8da7f988031c3a6e6865d51c1fadeda" +content-hash = "ac67bc6eacbb6b633f9568d69533d80456f632c7bfb9a2aa61aa9dd98e862473" [metadata.files] appdirs = [ @@ -717,8 +713,8 @@ atomicwrites = [ {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-21.1.0-py2.py3-none-any.whl", hash = "sha256:8ee1e5f5a1afc5b19bdfae4fdf0c35ed324074bdce3500c939842c8f818645d9"}, - {file = "attrs-21.1.0.tar.gz", hash = "sha256:3901be1cb7c2a780f14668691474d9252c070a756be0a9ead98cfeabfa11aeb8"}, + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, ] cachecontrol = [ {file = "CacheControl-0.12.6-py2.py3-none-any.whl", hash = "sha256:10d056fa27f8563a271b345207402a6dcce8efab7e5b377e270329c62471b10d"}, @@ -772,8 +768,8 @@ cffi = [ {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, ] cfgv = [ - {file = "cfgv-3.2.0-py2.py3-none-any.whl", hash = "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d"}, - {file = "cfgv-3.2.0.tar.gz", hash = "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1"}, + {file = "cfgv-3.3.0-py2.py3-none-any.whl", hash = "sha256:b449c9c6118fe8cca7fa5e00b9ec60ba08145d281d52164230a69211c5d597a1"}, + {file = "cfgv-3.3.0.tar.gz", hash = "sha256:9e600479b3b99e8af981ecdfc80a0296104ee610cab48a5ae4ffd0b668650eb1"}, ] chardet = [ {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, @@ -884,7 +880,7 @@ html5lib = [ {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, ] httpretty = [ - {file = "httpretty-1.0.5.tar.gz", hash = "sha256:e53c927c4d3d781a0761727f1edfad64abef94e828718e12b672a678a8b3e0b5"}, + {file = "httpretty-1.1.2.tar.gz", hash = "sha256:73d3e342ce8b21a16b0ff6c2b4c2c2d11c2da28784c264fcf7fe8cd2d8a25090"}, ] identify = [ {file = "identify-2.2.4-py2.py3-none-any.whl", hash = "sha256:ad9f3fa0c2316618dc4d840f627d474ab6de106392a4f00221820200f490f5a8"}, @@ -899,8 +895,8 @@ importlib-metadata = [ {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"}, ] importlib-resources = [ - {file = "importlib_resources-5.1.2-py3-none-any.whl", hash = "sha256:ebab3efe74d83b04d6bf5cd9a17f0c5c93e60fb60f30c90f56265fce4682a469"}, - {file = "importlib_resources-5.1.2.tar.gz", hash = "sha256:642586fc4740bd1cad7690f836b3321309402b20b332529f25617ff18e8e1370"}, + {file = "importlib_resources-5.1.3-py3-none-any.whl", hash = "sha256:3b9c774e0e7e8d9c069eb2fe6aee7e9ae71759a381dec02eb45249fba7f38713"}, + {file = "importlib_resources-5.1.3.tar.gz", hash = "sha256:0786b216556e53b34156263ab654406e543a8b0d9b1381019e25a36a09263c36"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -971,7 +967,10 @@ pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] -poetry-core = [] +poetry-core = [ + {file = "poetry-core-1.1.0a5.tar.gz", hash = "sha256:1b886de26026865325eae86a5d12eb154b80c0add8067c106eb706757594d85f"}, + {file = "poetry_core-1.1.0a5-py3-none-any.whl", hash = "sha256:b347525c1417e9b5c6aee52967eff98c0886853a9e8ab1b9dfb2659913dd37bc"}, +] pre-commit = [ {file = "pre_commit-2.12.1-py2.py3-none-any.whl", hash = "sha256:70c5ec1f30406250b706eda35e868b87e3e4ba099af8787e3e8b4b01e84f4712"}, {file = "pre_commit-2.12.1.tar.gz", hash = "sha256:900d3c7e1bf4cf0374bb2893c24c23304952181405b4d88c9c40b72bda1bb8a9"}, @@ -1001,8 +1000,8 @@ pytest = [ {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, ] pytest-cov = [ - {file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"}, - {file = "pytest_cov-2.11.1-py2.py3-none-any.whl", hash = "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da"}, + {file = "pytest-cov-2.12.0.tar.gz", hash = "sha256:8535764137fecce504a49c2b742288e3d34bc09eed298ad65963616cc98fd45e"}, + {file = "pytest_cov-2.12.0-py2.py3-none-any.whl", hash = "sha256:95d4933dcbbacfa377bb60b29801daa30d90c33981ab2a79e9ab4452c165066e"}, ] pytest-mock = [ {file = "pytest-mock-3.6.1.tar.gz", hash = "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"}, @@ -1066,8 +1065,8 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomlkit = [ - {file = "tomlkit-0.7.0-py2.py3-none-any.whl", hash = "sha256:6babbd33b17d5c9691896b0e68159215a9387ebfa938aa3ac42f4a4beeb2b831"}, - {file = "tomlkit-0.7.0.tar.gz", hash = "sha256:ac57f29693fab3e309ea789252fcce3061e19110085aa31af5446ca749325618"}, + {file = "tomlkit-0.7.2-py2.py3-none-any.whl", hash = "sha256:173ad840fa5d2aac140528ca1933c29791b79a374a0861a80347f42ec9328117"}, + {file = "tomlkit-0.7.2.tar.gz", hash = "sha256:d7a454f319a7e9bd2e249f239168729327e4dd2d27b17dc68be264ad1ce36754"}, ] tox = [ {file = "tox-3.23.1-py2.py3-none-any.whl", hash = "sha256:b0b5818049a1c1997599d42012a637a33f24c62ab8187223fdd318fa8522637b"}, diff --git a/pyproject.toml b/pyproject.toml index 20bd1420866..65299b452c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ generate-setup-file = false [tool.poetry.dependencies] python = "^3.6" -poetry-core = { git = "https://github.com/python-poetry/poetry-core.git", branch = "master"} +poetry-core = "~1.1.0a5" cleo = "^1.0.0a1" crashtest = "^0.3.0" requests = "^2.18" From ea92ec14d4f999b5f3cbf07202840e606e2226f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 23 Apr 2021 15:17:35 +0200 Subject: [PATCH 204/222] Bump version to 1.2.0a1 --- CHANGELOG.md | 33 ++++++++++++++++++++++++++++++++- poetry/__version__.py | 2 +- pyproject.toml | 2 +- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7ad0bc3f69..35c22a27f68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Change Log +## [1.2.0a1] - 2021-05-21 + +This release is the first testing release of the upcoming 1.2.0 version. + +It **drops** support for Python 2.7 and 3.5. + +### Added + +- Poetry now supports a plugin system to alter or expand Poetry's functionality. ([#3733](https://github.com/python-poetry/poetry/pull/3733)) +- Poetry now supports [PEP 610](https://www.python.org/dev/peps/pep-0610/). ([#3876](https://github.com/python-poetry/poetry/pull/3876)) +- Several configuration options to better control the way virtual environments are created are now available. ([#3157](https://github.com/python-poetry/poetry/pull/3157), [#3711](https://github.com/python-poetry/poetry/pull/3711)). +- The `new` command now supports namespace packages. ([#2768](https://github.com/python-poetry/poetry/pull/2768)) +- The `add` command now supports the `--editable` option to add packages in editable mode. ([#3940](https://github.com/python-poetry/poetry/pull/3940)) + +### Changed + +- Python 2.7 and 3.5 are no longer supported. ([#3405](https://github.com/python-poetry/poetry/pull/3405)) +- The usage of the `get-poetry.py` script is now deprecated and is replaced by the `install-poetry.py` script. ([#3706](https://github.com/python-poetry/poetry/pull/3706)) +- Directory dependencies are now in non-develop mode by default. ([poetry-core#98](https://github.com/python-poetry/poetry-core/pull/98)) +- Improved support for PEP 440 specific versions that do not abide by semantic versioning. ([poetry-core#140](https://github.com/python-poetry/poetry-core/pull/140)) +- Improved the CLI experience and performance by migrating to the latest version of Cleo. ([#3618](https://github.com/python-poetry/poetry/pull/3618)) +- Packages previously considered as unsafe (`pip`, `setuptools`, `wheels` and `distribute`) can now be managed as any other package. ([#2826](https://github.com/python-poetry/poetry/pull/2826)) +- The `new` command now defaults to the Markdown format for README files. ([#2768](https://github.com/python-poetry/poetry/pull/2768)) + +### Fixed + +- Fixed an error where command line options were not taken into account when using the `run` command. ([#3618](https://github.com/python-poetry/poetry/pull/3618)) +- Fixed an error in the way custom repositories were resolved. ([#3406](https://github.com/python-poetry/poetry/pull/3406)) + + ## [1.1.5] - 2021-03-04 ### Fixed @@ -1093,7 +1123,8 @@ Initial release -[Unreleased]: https://github.com/python-poetry/poetry/compare/1.1.4...master +[Unreleased]: https://github.com/python-poetry/poetry/compare/1.2.0a1...master +[1.2.0a1]: https://github.com/python-poetry/poetry/compare/1.2.0a1 [1.1.4]: https://github.com/python-poetry/poetry/compare/1.1.4 [1.1.3]: https://github.com/python-poetry/poetry/compare/1.1.3 [1.1.2]: https://github.com/python-poetry/poetry/releases/tag/1.1.2 diff --git a/poetry/__version__.py b/poetry/__version__.py index bc0be1b6fa5..7bfbef15537 100644 --- a/poetry/__version__.py +++ b/poetry/__version__.py @@ -1 +1 @@ -__version__ = "1.2.0a0" +__version__ = "1.2.0a1" diff --git a/pyproject.toml b/pyproject.toml index 65299b452c4..68f78a25a50 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "poetry" -version = "1.2.0a0" +version = "1.2.0a1" description = "Python dependency management and packaging made easy." authors = [ "Sébastien Eustace " From c28728fbe3e69c9995a4ea132dd767074f00e6f8 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Sun, 23 May 2021 22:04:41 +0200 Subject: [PATCH 205/222] get-poetry: disallow installation of >= 1.2.0a1 Relates-to: #4040 --- get-poetry.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/get-poetry.py b/get-poetry.py index 16e4bdcddfd..cde467fbf0a 100644 --- a/get-poetry.py +++ b/get-poetry.py @@ -449,6 +449,33 @@ def _compare_versions(x, y): break + 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) + + if not _is_supported(version): + print( + colorize( + "error", + f"Version {version} does not support this installation method. Please specify a version prior to " + f"1.2.0a1 explicitly using the '--version' option.\n" + "Please see " + "https://python-poetry.org/blog/announcing-poetry-1-2-0a1.html#deprecation-of-the-get-poetry-py-script " + "for more information.", + ) + ) + return None, None + + print( + colorize( + "warning", + "This installer is deprecated. " + "Poetry versions installed using this script will not be able to use 'self update' command to upgrade to " + "1.2.0a1 or later.", + ) + ) + current_version = None if os.path.exists(POETRY_LIB): with open( @@ -824,7 +851,7 @@ def set_windows_path_var(self, value): HWND_BROADCAST, WM_SETTINGCHANGE, 0, - u"Environment", + "Environment", SMTO_ABORTIFHUNG, 5000, ctypes.byref(result), From bc814e1e1c977e983c1198995a46cafc17e37676 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Sun, 23 May 2021 22:07:27 +0200 Subject: [PATCH 206/222] install-poetry: add warning for < 1.2.0a1 When installing poetry < 1.2.0a1 add a warning indicating the breakage of `self update` command usage. Relates-to: #4040 --- install-poetry.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/install-poetry.py b/install-poetry.py index ba67d3df8c8..22e1ee7a263 100644 --- a/install-poetry.py +++ b/install-poetry.py @@ -413,6 +413,24 @@ def run(self) -> int: self.display_pre_message() self.ensure_directories() + def _is_self_upgrade_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) + + if not _is_self_upgrade_supported(version): + self._write( + colorize( + "warning", + f"You are installing {version}. When using the current installer, this version does not support " + f"updating using the 'self update' command. Please use 1.2.0a1 or later.", + ) + ) + if not self._accept_all: + continue_install = input("Do you want to continue? ([y]/n) ") or "y" + if continue_install.lower() in {"n", "no"}: + return 0 + try: self.install(version) except subprocess.CalledProcessError as e: From 73ff7bcc0f88357d68ba1efc90cc004f738db095 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Sun, 23 May 2021 22:08:21 +0200 Subject: [PATCH 207/222] doc: add warning for self update breakages Relates-to: #4040 --- README.md | 3 +++ docs/docs/index.md | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 1f43666a795..9a456733b0d 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,9 @@ python install-poetry.py --git https://github.com/python-poetry/poetry.git@maste Updating poetry to the latest stable version is as simple as calling the `self update` command. +**Warning**: Poetry versions installed using the now deprecated `get-poetry.py` installer will not be able to use this +command to update to 1.2 releases or later. Migrate to using the `install-poetry.py` installer or `pipx`. + ```bash poetry self update ``` diff --git a/docs/docs/index.md b/docs/docs/index.md index dc0d9295309..38d34317bad 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -133,6 +133,11 @@ pip install --user poetry Updating Poetry to the latest stable version is as simple as calling the `self update` command. +!!!warning + + Poetry versions installed using the now deprecated `get-poetry.py` installer will not be able to use this + command to update to 1.2 releases or later. Migrate to using the `install-poetry.py` installer or `pipx`. + ```bash poetry self update ``` From 642bae6f891a88254df144d2171e2d731a31c830 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 4 Jun 2021 11:08:42 +0200 Subject: [PATCH 208/222] installer: fix upgrade support check on scm version (#4126) --- install-poetry.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/install-poetry.py b/install-poetry.py index 22e1ee7a263..e11bed1f1ba 100644 --- a/install-poetry.py +++ b/install-poetry.py @@ -415,10 +415,15 @@ def run(self) -> int: def _is_self_upgrade_supported(x): mx = self.VERSION_REGEX.match(x) + + if mx is None: + # the version is not semver, perhaps scm or file, we assume upgrade is supported + return True + vx = tuple(int(p) for p in mx.groups()[:3]) + (mx.group(5),) return vx >= (1, 2, 0) - if not _is_self_upgrade_supported(version): + if version and not _is_self_upgrade_supported(version): self._write( colorize( "warning", From 5d0faf96176d9f24b310df5d6c245f08690fda5a Mon Sep 17 00:00:00 2001 From: finswimmer Date: Fri, 4 Jun 2021 10:20:47 +0200 Subject: [PATCH 209/222] replace f-string in get-poetry by `.format()` for python2 compatibility --- get-poetry.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/get-poetry.py b/get-poetry.py index cde467fbf0a..dc3c235eeaa 100644 --- a/get-poetry.py +++ b/get-poetry.py @@ -458,11 +458,11 @@ def _is_supported(x): print( colorize( "error", - f"Version {version} does not support this installation method. Please specify a version prior to " - f"1.2.0a1 explicitly using the '--version' option.\n" + "Version {version} does not support this installation method. Please specify a version prior to " + "1.2.0a1 explicitly using the '--version' option.\n" "Please see " "https://python-poetry.org/blog/announcing-poetry-1-2-0a1.html#deprecation-of-the-get-poetry-py-script " - "for more information.", + "for more information.".format(version=version), ) ) return None, None From bce4ac5d2c7e35c98d196e89f2325fdc11c5f6e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 4 Jun 2021 16:38:09 +0200 Subject: [PATCH 210/222] Reorganize documentation --- .github/workflows/docs.yml | 29 --- docs/{docs/index.md => _index.md} | 63 +++-- docs/{docs => }/basic-usage.md | 82 +++--- docs/{docs => }/cli.md | 81 +++--- docs/{docs => }/configuration.md | 60 +++-- docs/contributing.md | 266 ++++++++++++++++++++ docs/{docs => }/dependency-specification.md | 27 +- docs/docs/contributing.md | 1 - docs/{docs => }/faq.md | 31 ++- docs/{docs => }/libraries.md | 28 ++- docs/{docs => }/managing-environments.md | 33 ++- docs/mkdocs.yml | 31 --- docs/{docs => }/plugins.md | 54 ++-- docs/{docs => }/pyproject.md | 101 ++++---- docs/{docs => }/repositories.md | 67 +++-- docs/theme/main.html | 29 --- docs/theme/nav.html | 18 -- docs/theme/toc.html | 10 - tox.ini | 12 - 19 files changed, 653 insertions(+), 370 deletions(-) delete mode 100644 .github/workflows/docs.yml rename docs/{docs/index.md => _index.md} (80%) rename docs/{docs => }/basic-usage.md (73%) rename docs/{docs => }/cli.md (89%) rename docs/{docs => }/configuration.md (81%) create mode 100644 docs/contributing.md rename docs/{docs => }/dependency-specification.md (93%) delete mode 100644 docs/docs/contributing.md rename docs/{docs => }/faq.md (78%) rename docs/{docs => }/libraries.md (78%) rename docs/{docs => }/managing-environments.md (86%) delete mode 100644 docs/mkdocs.yml rename docs/{docs => }/plugins.md (84%) rename docs/{docs => }/pyproject.md (78%) rename docs/{docs => }/repositories.md (76%) delete mode 100644 docs/theme/main.html delete mode 100644 docs/theme/nav.html delete mode 100644 docs/theme/toc.html diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index b14884c8fa0..00000000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Documentation - -on: - push: - paths: - - 'docs/**' - - '.github/workflows/docs.yml' - branches: - - master - pull_request: - paths: - - 'docs/**' - - '.github/workflows/docs.yml' - branches: - - '**' - -jobs: - docs: - name: Documentation Build - runs-on: Ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - name: Install tox - run: pip install tox - - name: Build documentation - run: tox -e doc diff --git a/docs/docs/index.md b/docs/_index.md similarity index 80% rename from docs/docs/index.md rename to docs/_index.md index 38d34317bad..d349033d651 100644 --- a/docs/docs/index.md +++ b/docs/_index.md @@ -1,6 +1,17 @@ +--- +title: "Introduction" +draft: false +type: docs +layout: "single" + +menu: + docs: + weight: 0 +--- + # Introduction -Poetry is a tool for dependency management and packaging in Python. +Poetry is a tool for **dependency management** and **packaging** in Python. It allows you to declare the libraries your project depends on and it will manage (install/update) them for you. @@ -9,11 +20,10 @@ It allows you to declare the libraries your project depends on and it will manag Poetry requires Python 2.7 or 3.5+. It is multi-platform and the goal is to make it work equally well on Windows, Linux and OSX. -!!! note - - Python 2.7 and 3.5 will no longer be supported in the next feature release (1.2). - You should consider updating your Python version to a supported one. - +{{% note %}} +Python 2.7 and 3.5 will no longer be supported in the next feature release (1.2). +You should consider updating your Python version to a supported one. +{{% /note %}} ## Installation @@ -29,10 +39,10 @@ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install- (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | python - ``` -!!!warning - - The previous `get-poetry.py` installer is now deprecated, if you are currently using it - you should migrate to the new, supported, `install-poetry.py` installer. +{{% warning %}} +The previous `get-poetry.py` installer is now deprecated, if you are currently using it +you should migrate to the new, supported, `install-poetry.py` installer. +{{% /warning %}} The installer installs the `poetry` tool to Poetry's `bin` directory. This location depends on your system: @@ -89,9 +99,9 @@ You can also install Poetry for a `git` repository by using the `--git` option: python install-poetry.py --git https://github.com/python-poetry/poetry.git@master ```` -!!!note - - Note that the installer does not support Python < 3.6. +{{% note %}} +Note that the installer does not support Python < 3.6. +{{% /note %}} ### Alternative installation methods @@ -123,25 +133,24 @@ Using `pip` to install Poetry is possible. pip install --user poetry ``` -!!!warning - - Be aware that it will also install Poetry's dependencies - which might cause conflicts with other packages. - +{{% warning %}} +Be aware that it will also install Poetry's dependencies +which might cause conflicts with other packages. +{{% /warning %}} ## Updating `poetry` Updating Poetry to the latest stable version is as simple as calling the `self update` command. -!!!warning - - Poetry versions installed using the now deprecated `get-poetry.py` installer will not be able to use this - command to update to 1.2 releases or later. Migrate to using the `install-poetry.py` installer or `pipx`. - ```bash poetry self update ``` +{{% warning %}} +Poetry versions installed using the now deprecated `get-poetry.py` installer will not be able to use this +command to update to 1.2 releases or later. Migrate to using the `install-poetry.py` installer or `pipx`. +{{% /warning %}} + If you want to install pre-release versions, you can use the `--preview` option. ```bash @@ -187,9 +196,9 @@ poetry completions zsh > ~/.zprezto/modules/completion/external/src/_poetry ``` -!!! note - - You may need to restart your shell in order for the changes to take effect. +{{% note %}} +You may need to restart your shell in order for the changes to take effect. +{{% /note %}} For `zsh`, you must then add the following line in your `~/.zshrc` before `compinit`: @@ -199,7 +208,7 @@ fpath+=~/.zfunc For `oh-my-zsh`, you must then enable poetry in your `~/.zshrc` plugins -``` +```text plugins( poetry ... diff --git a/docs/docs/basic-usage.md b/docs/basic-usage.md similarity index 73% rename from docs/docs/basic-usage.md rename to docs/basic-usage.md index c9805bb3996..1b7dbf764f5 100644 --- a/docs/docs/basic-usage.md +++ b/docs/basic-usage.md @@ -1,7 +1,18 @@ +--- +title: "Basic usage" +draft: false +type: docs +layout: single + +menu: + docs: + weight: 10 +--- + # Basic usage For the basic usage introduction we will be installing `pendulum`, a datetime library. -If you have not yet installed Poetry, refer to the [Introduction](/docs/) chapter. +If you have not yet installed Poetry, refer to the [Introduction]({{< relref "docs" >}} "Introduction") chapter. ## Project setup @@ -77,8 +88,8 @@ It will automatically find a suitable version constraint **and install** the pac ## Using your virtual environment By default, poetry creates a virtual environment in `{cache-dir}/virtualenvs` (`{cache-dir}\virtualenvs` on Windows). -You can change the [`cache-dir`](/docs/configuration/#cache-dir) value by editing the poetry config. -Additionally, you can use the [`virtualenvs.in-project`](/docs/configuration/#virtualenvs.in-project) configuration variable +You can change the [`cache-dir`]({{< relref "configuration#cache-dir" >}} "cache-dir configuration documentation") value by editing the poetry config. +Additionally, you can use the [`virtualenvs.in-project`]({{< relref "configuration#virtualenvsin-project" >}} "#virtualenvs.in-project configuration documentation") configuration variable to create virtual environment within your project directory. @@ -96,18 +107,19 @@ The easiest way to activate the virtual environment is to create a new shell wit To deactivate the virtual environment and exit this new shell type `exit`. To deactivate the virtual environment without leaving the shell use `deactivate`. -!!!note +{{% note %}} +**Why a new shell?** - **Why a new shell?** - Child processes inherit their environment from their parents, but do not share - them. As such, any modifications made by a child process, is not persisted after - the child process exits. A Python application (Poetry), being a child process, - cannot modify the environment of the shell that it has been called from such - that an activated virtual environment remains active after the Poetry command - has completed execution. +Child processes inherit their environment from their parents, but do not share +them. As such, any modifications made by a child process, is not persisted after +the child process exits. A Python application (Poetry), being a child process, +cannot modify the environment of the shell that it has been called from such +that an activated virtual environment remains active after the Poetry command +has completed execution. - Therefore, Poetry has to create a sub-shell with the virtual environment activated - in order for the subsequent commands to run from within the virtual environment. +Therefore, Poetry has to create a sub-shell with the virtual environment activated +in order for the subsequent commands to run from within the virtual environment. +{{% /note %}} Alternatively, to avoid creating a new shell, you can manually activate the @@ -116,11 +128,11 @@ To get the path to your virtual environment run `poetry env info --path`. You can also combine these into a nice one-liner, `source $(poetry env info --path)/bin/activate` To deactivate this virtual environment simply use `deactivate`. -| | POSIX Shell | Windows | Exit/Deactivate | -|-------------------|---------------------------------------------------|---------------------------------------------|-----------------| -| New Shell | `poetry shell` | `poetry shell` | `exit` | -| Manual Activation | `source {path_to_venv}/bin/activate` | `{path_to_venv}\Scripts\activate.bat` | `deactivate` | -| One-liner | ```source `poetry env info --path`/bin/activate```| | `deactivate` | +| | POSIX Shell | Windows | Exit/Deactivate | +| ----------------- | -------------------------------------------------- | ------------------------------------- | --------------- | +| New Shell | `poetry shell` | `poetry shell` | `exit` | +| Manual Activation | `source {path_to_venv}/bin/activate` | `{path_to_venv}\Scripts\activate.bat` | `deactivate` | +| One-liner | ```source `poetry env info --path`/bin/activate``` | | `deactivate` | ### Version constraints @@ -128,21 +140,21 @@ To deactivate this virtual environment simply use `deactivate`. In our example, we are requesting the `pendulum` package with the version constraint `^1.4`. This means any version greater or equal to 1.4.0 and less than 2.0.0 (`>=1.4.0 <2.0.0`). -Please read [Dependency specification](/docs/dependency-specification) for more in-depth information on versions, +Please read [Dependency specification]({{< relref "dependency-specification" >}} "Dependency specification documentation") for more in-depth information on versions, how versions relate to each other, and on the different ways you can specify dependencies. -!!!note - - **How does Poetry download the right files?** +{{% note %}} +**How does Poetry download the right files?** - When you specify a dependency in `pyproject.toml`, Poetry first takes the name of the package - that you have requested and searches for it in any repository you have registered using the `repositories` key. - If you have not registered any extra repositories, or it does not find a package with that name in the - repositories you have specified, it falls back on PyPI. +When you specify a dependency in `pyproject.toml`, Poetry first takes the name of the package +that you have requested and searches for it in any repository you have registered using the `repositories` key. +If you have not registered any extra repositories, or it does not find a package with that name in the +repositories you have specified, it falls back on PyPI. - When Poetry finds the right package, it then attempts to find the best match - for the version constraint you have specified. +When Poetry finds the right package, it then attempts to find the best match +for the version constraint you have specified. +{{% /note %}} ## Installing dependencies @@ -189,9 +201,9 @@ Even if you develop alone, in six months when reinstalling the project you can f the dependencies installed are still working even if your dependencies released many new versions since then. (See note below about using the update command.) -!!!note - - For libraries it is not necessary to commit the lock file. +{{% note %}} +For libraries it is not necessary to commit the lock file. +{{% /note %}} ### Installing dependencies only @@ -212,7 +224,7 @@ This will fetch the latest matching versions (according to your `pyproject.toml` and update the lock file with the new versions. (This is equivalent to deleting the `poetry.lock` file and running `install` again.) -!!!note - - Poetry will display a **Warning** when executing an install command if `poetry.lock` and `pyproject.toml` - are not synchronized. +{{% note %}} +Poetry will display a **Warning** when executing an install command if `poetry.lock` and `pyproject.toml` +are not synchronized. +{{% /note %}} diff --git a/docs/docs/cli.md b/docs/cli.md similarity index 89% rename from docs/docs/cli.md rename to docs/cli.md index 45881bec80a..0a1b8a265af 100644 --- a/docs/docs/cli.md +++ b/docs/cli.md @@ -1,3 +1,15 @@ +--- +title: "Commands" +draft: false +type: docs +layout: single + +menu: + docs: + weight: 30 +--- + + # Commands You've already learned how to use the command-line interface to do some things. @@ -143,7 +155,7 @@ poetry install --remove-untracked ``` You can also specify the extras you want installed -by passing the `-E|--extras` option (See [Extras](/docs/pyproject/#extras) for more info) +by passing the `-E|--extras` option (See [Extras]({{< relref "pyproject#extras" >}}) for more info) ```bash poetry install --extras "mysql pgsql" @@ -159,7 +171,6 @@ Installing dependencies from lock file No dependencies to install or update - Installing (x.x.x) - ``` If you want to skip this installation, use the `--no-root` option. @@ -286,10 +297,10 @@ Alternatively, you can specify it in the `pyproject.toml` file. It means that ch my-package = {path = "../my/path", develop = true} ``` -!!!note - - Before poetry 1.1 path dependencies were installed in editable mode by default. You should always set the `develop` attribute explicit, - to make sure the behavior is the same for all poetry versions. +{{% note %}} +Before poetry 1.1 path dependencies were installed in editable mode by default. You should always set the `develop` attribute explicit, +to make sure the behavior is the same for all poetry versions. +{{% /note %}} If the package(s) you want to install provide extras, you can specify them when adding the package: @@ -409,7 +420,7 @@ poetry config [options] [setting-key] [setting-value1] ... [setting-valueN] ```` `setting-key` is a configuration option name and `setting-value1` is a configuration value. -See [Configuration](/docs/configuration/) for all available settings. +See [Configuration]({{< relref "configuration" >}}) for all available settings. ### Options @@ -473,9 +484,9 @@ poetry search requests pendulum This command locks (without installing) the dependencies specified in `pyproject.toml`. -!!!note - - By default, this will lock all dependencies to the latest available compatible versions. To only refresh the lock file, use the `--no-update` option. +{{% note %}} +By default, this will lock all dependencies to the latest available compatible versions. To only refresh the lock file, use the `--no-update` option. +{{% /note %}} ```bash poetry lock @@ -497,17 +508,17 @@ The new version should ideally be a valid [semver](https://semver.org/) string o The table below illustrates the effect of these rules with concrete examples. -| rule | before | after | -|------------|---------------|---------------| -| major | 1.3.0 | 2.0.0 | -| minor | 2.1.4 | 2.2.0 | -| patch | 4.1.1 | 4.1.2 | -| premajor | 1.0.2 | 2.0.0-alpha.0 | -| preminor | 1.0.2 | 1.1.0-alpha.0 | -| prepatch | 1.0.2 | 1.0.3-alpha.0 | -| prerelease | 1.0.2 | 1.0.3-alpha.0 | +| rule | before | after | +| ---------- | ------------- | ------------- | +| major | 1.3.0 | 2.0.0 | +| minor | 2.1.4 | 2.2.0 | +| patch | 4.1.1 | 4.1.2 | +| premajor | 1.0.2 | 2.0.0-alpha.0 | +| preminor | 1.0.2 | 1.1.0-alpha.0 | +| prepatch | 1.0.2 | 1.0.3-alpha.0 | +| prerelease | 1.0.2 | 1.0.3-alpha.0 | | prerelease | 1.0.3-alpha.0 | 1.0.3-alpha.1 | -| prerelease | 1.0.3-beta.0 | 1.0.3-beta.1 | +| prerelease | 1.0.3-beta.0 | 1.0.3-beta.1 | ### Options @@ -521,9 +532,9 @@ This command exports the lock file to other formats. poetry export -f requirements.txt --output requirements.txt ``` -!!!note - - Only the `requirements.txt` format is currently supported. +{{% note %}} +Only the `requirements.txt` format is currently supported. +{{% /note %}} ### Options @@ -541,7 +552,7 @@ poetry export -f requirements.txt --output requirements.txt The `env` command regroups sub commands to interact with the virtualenvs associated with a specific project. -See [Managing environments](/docs/managing-environments/) for more information about these commands. +See [Managing environments]({{< relref "managing-environments" >}}) for more information about these commands. ## cache @@ -628,18 +639,18 @@ For example, to add the `pypi-test` source, you can run: poetry source add pypi-test https://test.pypi.org/simple/ ``` -!!!note - - You cannot use the name `pypi` as it is reserved for use by the default PyPI source. +{{% note %}} +You cannot use the name `pypi` as it is reserved for use by the default PyPI source. +{{% /note %}} #### Options -* `--default`: Set this source as the [default](/docs/repositories/#disabling-the-pypi-repository) (disable PyPI). -* `--secondary`: Set this source as a [secondary](/docs/repositories/#install-dependencies-from-a-private-repository) source. +* `--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. -!!!note - - You cannot set a source as both `default` and `secondary`. +{{% note %}} +You cannot set a source as both `default` and `secondary`. +{{% /note %}} ### `source show` @@ -655,9 +666,9 @@ Optionally, you can show information of one or more sources by specifying their poetry source show pypi-test ``` -!!!note - - This command will only show sources configured via the `pyproject.toml` and does not include PyPI. +{{% note %}} +This command will only show sources configured via the `pyproject.toml` and does not include PyPI. +{{% /note %}} ### `source remove` diff --git a/docs/docs/configuration.md b/docs/configuration.md similarity index 81% rename from docs/docs/configuration.md rename to docs/configuration.md index 4743515dd43..9ad688cb828 100644 --- a/docs/docs/configuration.md +++ b/docs/configuration.md @@ -1,6 +1,17 @@ +--- +title: "Configuration" +draft: false +type: docs +layout: single + +menu: + docs: + weight: 40 +--- + # Configuration -Poetry can be configured via the `config` command ([see more about its usage here](/docs/cli/#config)) +Poetry can be configured via the `config` command ([see more about its usage here]({{< relref "docs/cli#config" >}} "config command documentation")) or directly in the `config.toml` file that will be automatically be created when you first run that command. This file can typically be found in one of the following directories: @@ -93,7 +104,9 @@ export POETRY_HTTP_BASIC_MY_REPOSITORY_PASSWORD=secret ## Available settings -### `cache-dir`: string +### `cache-dir` + +**Type**: string The path to the cache directory used by Poetry. @@ -103,28 +116,35 @@ Defaults to one of the following directories: - Windows: `C:\Users\\AppData\Local\pypoetry\Cache` - Unix: `~/.cache/pypoetry` -### `installer.parallel`: boolean +### `installer.parallel` + +**Type**: boolean Use parallel execution when using the new (`>=1.1.0`) installer. Defaults to `true`. -!!!note: - This configuration will be ignored, and parallel execution disabled when running - Python 2.7 under Windows. +{{% note %}} +This configuration will be ignored, and parallel execution disabled when running +Python 2.7 under Windows. +{{% /note %}} + +### `virtualenvs.create` -### `virtualenvs.create`: boolean +**Type**: boolean Create a new virtual environment if one doesn't already exist. Defaults to `true`. If set to `false`, poetry will install dependencies into the current python environment. -!!!note +{{% note %}} +When setting this configuration to `false`, the Python environment used must have `pip` +installed and available. +{{% /note %}} - When setting this configuration to `false`, the Python environment used must have `pip` - installed and available. +### `virtualenvs.in-project` -### `virtualenvs.in-project`: boolean +**Type**: boolean Create the virtualenv inside the project's root directory. Defaults to `None`. @@ -136,22 +156,30 @@ If not set explicitly (default), `poetry` will use the virtualenv from the `.ven directory when one is available. If set to `false`, `poetry` will ignore any existing `.venv` directory. -### `virtualenvs.path`: string +### `virtualenvs.path` + +**Type**: string Directory where virtual environments will be created. Defaults to `{cache-dir}/virtualenvs` (`{cache-dir}\virtualenvs` on Windows). -### `virtualenvs.options.always-copy`: boolean +### `virtualenvs.options.always-copy` + +**Type**: boolean If set to `true` the `--always-copy` parameter is passed to `virtualenv` on creation of the venv. Thus all needed files are copied into the venv instead of symlinked. Defaults to `false`. -### `virtualenvs.options.system-site-packages`: boolean +### `virtualenvs.options.system-site-packages` + +**Type**: boolean Give the virtual environment access to the system site-packages directory. Applies on virtualenv creation. Defaults to `false`. -### `repositories.`: string +### `repositories.` + +**Type**: string -Set a new alternative repository. See [Repositories](/docs/repositories/) for more information. +Set a new alternative repository. See [Repositories]({{< relref "repositories" >}}) for more information. diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 00000000000..d54359fde70 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,266 @@ +--- +title: "Contributing to Poetry" +draft: false +type: docs +layout: single + +menu: + docs: + weight: 100 +--- + +# Contributing to Poetry + +First off, thanks for taking the time to contribute! + +The following is a set of guidelines for contributing to Poetry on GitHub. These are mostly guidelines, not rules. Use your best judgement, and feel free to propose changes to this document in a pull request. + +#### Table of contents + +[How to contribute](#how-to-contribute) + + * [Reporting bugs](#reporting-bugs) + * [Suggesting enhancements](#suggesting-enhancements) + * [Contributing to documentation](#contributing-to-documentation) + * [Contributing to code](#contributing-to-code) + * [Issue triage](#issue-triage) + * [Git workflow](#git-workflow) + + +## How to contribute + +### Reporting bugs + +This section guides you through submitting a bug report for Poetry. +Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports. + +Before creating bug reports, please check [this list](#before-submitting-a-bug-report) to be sure that you need to create one. When you are creating a bug report, please include as many details as possible. Fill out the [required template](https://github.com/python-poetry/poetry/blob/master/.github/ISSUE_TEMPLATE/---bug-report.md), the information it asks helps the maintainers resolve the issue faster. + +> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. + +#### Before submitting a bug report + +* **Check the [FAQs on the official website](https://python-poetry.org/docs/faq)** for a list of common questions and problems. +* **Check that your issue does not already exist in the [issue tracker](https://github.com/python-poetry/poetry/issues)**. + +#### How do I submit a bug report? + +Bugs are tracked on the [official issue tracker](https://github.com/python-poetry/poetry/issues) where you can create a new one and provide the following information by filling in [the template](https://github.com/python-poetry/poetry/blob/master/.github/ISSUE_TEMPLATE/---bug-report.md). + +Explain the problem and include additional details to help maintainers reproduce the problem: + +* **Use a clear and descriptive title** for the issue to identify the problem. +* **Describe the exact steps which reproduce the problem** in as many details as possible. +* **Provide your pyproject.toml file** in a [Gist](https://gist.github.com) after removing potential private information (like private package repositories). +* **Provide specific examples to demonstrate the steps to reproduce the issue**. Include links to files or GitHub projects, or copy-paste-able snippets, which you use in those examples. +* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. +* **Explain which behavior you expected to see instead and why.** +* **If the problem is an unexpected error being raised**, execute the corresponding command in **debug** mode (the `-vvv` option). + +Provide more context by answering these questions: + +* **Did the problem start happening recently** (e.g. after updating to a new version of Poetry) or was this always a problem? +* If the problem started happening recently, **can you reproduce the problem in an older version of Poetry?** What's the most recent version in which the problem doesn't happen? +* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens. + +Include details about your configuration and environment: + +* **Which version of Poetry are you using?** You can get the exact version by running `poetry -V` in your terminal. +* **Which Python version Poetry has been installed for?** Execute the `debug:info` to get the information. +* **What's the name and version of the OS you're using**? + + +### Suggesting enhancements + +This section guides you through submitting an enhancement suggestion for Poetry, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions. + +Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-an-enhancement-suggestion). Fill in [the template](https://github.com/python-poetry/poetry/blob/master/.github/ISSUE_TEMPLATE/---feature-request.md), including the steps that you imagine you would take if the feature you're requesting existed. + +#### Before submitting an enhancement suggestion + +* **Check the [FAQs on the official website](https://python-poetry.org/docs/faq)** for a list of common questions and problems. +* **Check that your issue does not already exist in the [issue tracker](https://github.com/python-poetry/poetry/issues)**. + +#### How do I submit an Enhancement suggestion? + +Enhancement suggestions are tracked on the [official issue tracker](https://github.com/python-poetry/poetry/issues) where you can create a new one and provide the following information: + +* **Use a clear and descriptive title** for the issue to identify the suggestion. +* **Provide a step-by-step description of the suggested enhancement** in as many details as possible. +* **Provide specific examples to demonstrate the steps**.. +* **Describe the current behavior** and **explain which behavior you expected to see instead** and why. + +### Contributing to documentation + +One of the simplest ways to get started contributing to a project is through improving documentation. Poetry is constantly evolving, this means that sometimes our documentation has gaps. You can help by +adding missing sections, editing the existing content so it is more accessible or creating new content (tutorials, FAQs, etc). + +{{% note %}} +A great way to understand Poetry's design and how it all fits together, is to add FAQ entries for commonly +asked questions. Poetry members usually mark issues with [candidate/faq](https://github.com/python-poetry/poetry/issues?q=is%3Aissue+label%3Acandidate%2Ffaq+) to indicate that the issue either contains a response +that explains how something works or might benefit from an entry in the FAQ. +{{% /note %}} + +Issues pertaining to the documentation are usually marked with the [Documentation](https://github.com/python-poetry/poetry/labels/Documentation) label. + +### Contributing to code + +#### Picking an issue + +{{% note %}} +If you are a first time contributor, and are looking for an issue to take on, you might want to look for [Good First Issue](https://github.com/python-poetry/poetry/issues?q=is%3Aopen+is%3Aissue+label%3A%22Good+First+Issue%22) +labelled issues. We do our best to label such issues, however we might fall behind at times. So, ask us. +{{% /note %}} + +If you would like to take on an issue, feel free to comment on the issue tagging `@python-poetry/triage`. We are more than happy to discuss solutions on the issue. If you would like help with navigating +the code base, join us on our [Discord Server](https://discordapp.com/invite/awxPgve). + +#### Local development + +You will need Poetry to start contributing on the Poetry codebase. Refer to the [documentation](https://python-poetry.org/docs/#introduction) to start using Poetry. + +You will first need to clone the repository using `git` and place yourself in its directory: + +```bash +$ git clone git@github.com:python-poetry/poetry.git +$ cd poetry +``` + +{{% note %}} +We recommend that you use a personal [fork](https://docs.github.com/en/free-pro-team@latest/github/getting-started-with-github/fork-a-repo) for this step. If you are new to GitHub collaboration, +you can refer to the [Forking Projects Guide](https://guides.github.com/activities/forking/). +{{% /note %}} + +Now, you will need to install the required dependency for Poetry and be sure that the current +tests are passing on your machine: + +```bash +$ poetry install +$ poetry run pytest tests/ +``` + +Poetry uses the [black](https://github.com/psf/black) coding style and you must ensure that your +code follows it. If not, the CI will fail and your Pull Request will not be merged. + +Similarly, the import statements are sorted with [isort](https://github.com/timothycrosley/isort) +and special care must be taken to respect it. If you don't, the CI will fail as well. + +To make sure that you don't accidentally commit code that does not follow the coding style, you can +install a pre-commit hook that will check that everything is in order: + +```bash +$ poetry run pre-commit install +``` + +You can also run it anytime using: + +```bash +$ poetry run pre-commit run --all-files +``` + +Your code must always be accompanied by corresponding tests, if tests are not present your code +will not be merged. + +#### Pull requests + +* Fill in [the required template](https://github.com/python-poetry/poetry/blob/master/.github/PULL_REQUEST_TEMPLATE.md) +* Be sure that your pull request contains tests that cover the changed or added code. +* If your changes warrant a documentation change, the pull request must also update the documentation. + +{{% note %}} +Make sure your branch is [rebased](https://docs.github.com/en/free-pro-team@latest/github/using-git/about-git-rebase) against the latest main branch. A maintainer might ask you to ensure the branch is +up-to-date prior to merging your Pull Request if changes have conflicts. +{{% /note %}} + +All pull requests, unless otherwise instructed, need to be first accepted into the main branch (`master`). + +### Issue triage + +{{% note %}} +If you have an issue that hasn't had any attention, you can ping us `@python-poetry/triage` on the issue. Please, give us reasonable time to get to your issue first, spamming us with messages +{{% /note %}} + +If you are helping with the triage of reported issues, this section provides some useful information to assist you in your contribution. + +#### Triage steps + +1. If `pyproject.toml` is missing or `-vvv` debug logs (with stack trace) is not provided and required, request that the issue author provides it. +1. Attempt to reproduce the issue with the reported Poetry version or request further clarification from the issue author. +1. Ensure the issue is not already resolved. You can attempt to reproduce using the latest preview release and/or poetry from the main branch. +1. If the issue cannot be reproduced, + 1. clarify with the issue's author, + 1. close the issue or notify `@python-poetry/triage`. +1. If the issue can be reproduced, + 1. comment on the issue confirming so + 1. notify `@python-poetry/triage`. + 1. if possible, identify the root cause of the issue. + 1. if interested, attempt to fix it via a pull request. + +#### Multiple versions + +Often times you would want to attempt to reproduce issues with multiple versions of `poetry` at the same time. For these use cases, the [pipx project](https://pipxproject.github.io/pipx/) is useful. + +You can set your environment up like so. + +```sh +pipx install --suffix @1.0.10 'poetry==1.0.10' +pipx install --suffix @1.1.0rc1 'poetry==1.1.0rc1' +pipx install --suffix @master 'poetry @ git+https://github.com/python-poetry/poetry' +``` + +{{% note %}} +Do not forget to update your `poetry@master` installation in sync with upstream. +{{% /note %}} + +For `@local` it is recommended that you do something similar to the following as editable installs are not supported for PEP 517 projects. + +```sh +# note this will not work for Windows, and we assume you have already run `poetry install` +cd /path/to/python-poetry/poetry +ln -sf $(poetry run which poetry) ~/.local/bin/poetry@local +``` + +{{% note %}} +This mechanism can also be used to test pull requests. +{{% /note %}} + +### Git Workflow + +All development work is performed against Poetry's main branch (`master`). All changes are expected to be submitted and accepted to this +branch. + +#### Release branch + +When a release is ready, the following are required before a release is tagged. + +1. A release branch with the prefix `release-`, eg: `release-1.1.0rc1`. +1. A pull request from the release branch to the main branch (`master`) if it's a minor or major release. Otherwise, to the bug fix branch (eg: `1.0`). + 1. The pull request description MUST include the change log corresponding to the release (eg: [#2971](https://github.com/python-poetry/poetry/pull/2971)). + 1. The pull request must contain a commit that updates [CHANGELOG.md](CHANGELOG.md) and bumps the project version (eg: [#2971](https://github.com/python-poetry/poetry/pull/2971/commits/824e7b79defca435cf1d765bb633030b71b9a780)). + 1. The pull request must have the `Release` label specified. + +Once the branch pull-request is ready and approved, a member of `@python-poetry/core` will, + +1. Tag the branch with the version identifier (eg: `1.1.0rc1`). +2. Merge the pull request once the release is created and assets are uploaded by the CI. + +{{% note %}} +In this case, we prefer a merge commit instead of squash or rebase merge. +{{% /note %}} + +#### Bug fix branch + +Once a minor version (eg: `1.1.0`) is released, a new branch for the minor version (eg: `1.1`) is created for the bug fix releases. Changes identified +or acknowledged by the Poetry team as requiring a bug fix can be submitted as a pull requests against this branch. + +At the time of writing only issues meeting the following criteria may be accepted into a bug fix branch. Trivial fixes may be accepted on a +case-by-case basis. + +1. The issue breaks a core functionality and/or is a critical regression. +1. The change set does not introduce a new feature or changes an existing functionality. +1. A new minor release is not expected within a reasonable time frame. +1. If the issue affects the next minor/major release, a corresponding fix has been accepted into the main branch. + +{{% note %}} +This is subject to the interpretation of a maintainer within the context of the issue. +{{% /note %}} diff --git a/docs/docs/dependency-specification.md b/docs/dependency-specification.md similarity index 93% rename from docs/docs/dependency-specification.md rename to docs/dependency-specification.md index 97888a02cd7..e2fa0e4e021 100644 --- a/docs/docs/dependency-specification.md +++ b/docs/dependency-specification.md @@ -1,3 +1,14 @@ +--- +title: "Dependency specification" +draft: false +type: docs +layout: single + +menu: + docs: + weight: 70 +--- + # Dependency specification Dependencies for a project can be specified in various forms, which depend on the type @@ -119,10 +130,10 @@ my-package = { path = "../my-package/", develop = false } my-package = { path = "../my-package/dist/my-package-0.1.0.tar.gz" } ``` -!!!note - - Before poetry 1.1 directory path dependencies were installed in editable mode by default. You should set the `develop` attribute explicitly, - to make sure the behavior is the same for all poetry versions. +{{% note %}} +Before poetry 1.1 directory path dependencies were installed in editable mode by default. You should set the `develop` attribute explicitly, +to make sure the behavior is the same for all poetry versions. +{{% /note %}} ## `url` dependencies @@ -185,10 +196,10 @@ foo = [ ] ``` -!!!note - - The constraints **must** have different requirements (like `python`) - otherwise it will cause an error when resolving dependencies. +{{% note %}} +The constraints **must** have different requirements (like `python`) +otherwise it will cause an error when resolving dependencies. +{{% /note %}} ## Expanded dependency specification syntax diff --git a/docs/docs/contributing.md b/docs/docs/contributing.md deleted file mode 100644 index 568877b4a4f..00000000000 --- a/docs/docs/contributing.md +++ /dev/null @@ -1 +0,0 @@ -{!../CONTRIBUTING.md!} diff --git a/docs/docs/faq.md b/docs/faq.md similarity index 78% rename from docs/docs/faq.md rename to docs/faq.md index 4d464d7ebc0..122a6856e6b 100644 --- a/docs/docs/faq.md +++ b/docs/faq.md @@ -1,6 +1,17 @@ +--- +title: "FAQ" +draft: false +type: docs +layout: single + +menu: + docs: + weight: 110 +--- + # FAQ -## Why is the dependency resolution process slow? +### Why is the dependency resolution process slow? While the dependency resolver at the heart of Poetry is highly optimized and should be fast enough for most cases, sometimes, with some specific set of dependencies, @@ -13,18 +24,18 @@ operation, both in bandwidth and time, which is why it seems this is a long proc At the moment there is no way around it. -!!!note - - Once Poetry has cached the releases' information, the dependency resolution process - will be much faster. +{{% note %}} +Once Poetry has cached the releases' information, the dependency resolution process +will be much faster. +{{% /note %}} -## Why are unbound version constraints a bad idea? +### Why 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. +– you have to do a new release but the previous one stays broken. The only good alternative is to define an upper bound on your constraints, which you can increase in a new release after testing that your package is compatible @@ -33,9 +44,9 @@ 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`. The `^` operator works very well with libraries following [semantic versioning](https://semver.org). -## Is tox supported? +### Is tox supported? -Yes. By using the [isolated builds](https://tox.readthedocs.io/en/latest/config.html#conf-isolated_build) `tox` provides, +**Yes**. By using the [isolated builds](https://tox.readthedocs.io/en/latest/config.html#conf-isolated_build) `tox` provides, you can use it in combination with the PEP 517 compliant build system provided by Poetry. So, in your `pyproject.toml` file, add this section if it does not already exist: @@ -60,7 +71,7 @@ commands = poetry run pytest tests/ ``` -## I don't want Poetry to manage my virtual environments. Can I disable it? +### I don't want Poetry to manage my virtual environments. Can I disable it? While Poetry automatically creates virtual environments to always work isolated from the global Python installation, there are valid reasons why it's not necessary diff --git a/docs/docs/libraries.md b/docs/libraries.md similarity index 78% rename from docs/docs/libraries.md rename to docs/libraries.md index db4822c342d..bd7d195bd6c 100644 --- a/docs/docs/libraries.md +++ b/docs/libraries.md @@ -1,3 +1,15 @@ +--- +title: "Libraries" +draft: false +type: docs +layout: "docs" + +menu: + docs: + weight: 20 +--- + + # Libraries This chapter will tell you how to make your library installable through Poetry. @@ -9,7 +21,7 @@ While Poetry does not enforce any convention regarding package versioning, it **strongly** recommends to follow [semantic versioning](https://semver.org). This has many advantages for the end users and allows them to set appropriate -[version constraints](/docs/dependency-specification/#version-constraints). +[version constraints]({{< relref "dependency-specification#version-constraints" >}}). ## Lock file @@ -49,14 +61,14 @@ poetry publish ``` This will package and publish the library to PyPI, at the condition that you are a registered user -and you have [configured your credentials](/docs/repositories/#adding-credentials) properly. - -!!!note +and you have [configured your credentials]({{< relref "repositories#configuring-credentials" >}}) properly. - The `publish` command does not execute `build` by default. +{{% note %}} +The `publish` command does not execute `build` by default. - If you want to build and publish your packages together, - just pass the `--build` option. +If you want to build and publish your packages together, +just pass the `--build` option. +{{% /note %}} Once this is done, your library will be available to anyone. @@ -68,7 +80,7 @@ Sometimes, you may want to keep your library private but also being accessible t In this case, you will need to use a private repository. In order to publish to a private repository, you will need to add it to your -global list of repositories. See [Adding a repository](/docs/repositories/#adding-a-repository) +global list of repositories. See [Adding a repository]({{< relref "repositories#adding-a-repository" >}}) for more information. Once this is done, you can actually publish to it like so: diff --git a/docs/docs/managing-environments.md b/docs/managing-environments.md similarity index 86% rename from docs/docs/managing-environments.md rename to docs/managing-environments.md index 8b626cd813a..d9b728825a1 100644 --- a/docs/docs/managing-environments.md +++ b/docs/managing-environments.md @@ -1,3 +1,14 @@ +--- +title: "Managing environments" +draft: false +type: docs +layout: "docs" + +menu: + docs: + weight: 60 +--- + # Managing environments Poetry makes project environment isolation one of its core features. @@ -15,19 +26,19 @@ with the `python` requirement of the project. In this case, Poetry will try to find one that is and use it. If it's unable to do so then you will be prompted to activate one explicitly, see [Switching environments](#switching-between-environments). -!!!note - - To easily switch between Python versions, it is recommended to - use [pyenv](https://github.com/pyenv/pyenv) or similar tools. +{{% note %}} +To easily switch between Python versions, it is recommended to +use [pyenv](https://github.com/pyenv/pyenv) or similar tools. - For instance, if your project is Python 2.7 only, a standard workflow - would be: +For instance, if your project is Python 2.7 only, a standard workflow +would be: - ```bash - pyenv install 2.7.15 - pyenv local 2.7.15 # Activate Python 2.7 for the current project - poetry install - ``` +```bash +pyenv install 2.7.15 +pyenv local 2.7.15 # Activate Python 2.7 for the current project +poetry install +``` +{{% /note %}} ## Switching between environments diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml deleted file mode 100644 index e055a6b6370..00000000000 --- a/docs/mkdocs.yml +++ /dev/null @@ -1,31 +0,0 @@ -site_name: Poetry documentation - -theme: - name: null - custom_dir: theme - -extra: - version: 2.0 - -nav: - - Introduction: index.md - - Basic Usage: basic-usage.md - - Libraries: libraries.md - - Commands: cli.md - - Configuration: configuration.md - - Repositories: repositories.md - - Managing environments: managing-environments.md - - Dependency specification: dependency-specification.md - - Plugins: plugins.md - - The pyproject.toml file: pyproject.md - - Contributing: contributing.md - - FAQ: faq.md - -markdown_extensions: - - codehilite - - admonition - - pymdownx.superfences - - toc: - permalink:  - - markdown_include.include: - base_path: docs diff --git a/docs/docs/plugins.md b/docs/plugins.md similarity index 84% rename from docs/docs/plugins.md rename to docs/plugins.md index f31bc85b3de..96ca48e191b 100644 --- a/docs/docs/plugins.md +++ b/docs/plugins.md @@ -1,3 +1,14 @@ +--- +title: "Plugins" +draft: false +type: docs +layout: single + +menu: + docs: + weight: 80 +--- + # Plugins Poetry supports using and building plugins if you wish to @@ -18,7 +29,7 @@ and may also depend on further packages. ### Plugin package The plugin package must depend on Poetry -and declare a proper [plugin](/docs/pyproject/#plugins) in the `pyproject.toml` file. +and declare a proper [plugin]({{< relref "pyproject#plugins" >}}) in the `pyproject.toml` file. ```toml [tool.poetry] @@ -96,19 +107,19 @@ class MyApplicationPlugin(ApplicationPlugin): application.command_loader.register_factory("my-command", factory) ``` -!!!note - - It's possible to do the following to register the command: +{{% note %}} +It's possible to do the following to register the command: - ```python - application.add(MyCommand()) - ``` +```python +application.add(MyCommand()) +``` - However, it is **strongly** recommended to register a new factory - in the command loader to defer the loading of the command when it's actually - called. +However, it is **strongly** recommended to register a new factory +in the command loader to defer the loading of the command when it's actually +called. - This will help keep the performances of Poetry good. +This will help keep the performances of Poetry good. +{{% /note %}} The plugin also must be declared in the `pyproject.toml` file of the plugin package as an `application.plugin` plugin: @@ -118,9 +129,9 @@ as an `application.plugin` plugin: foo-command = "poetry_demo_plugin.plugin:MyApplicationPlugin" ``` -!!!warning - - A plugin **must not** remove or modify in any way the core commands of Poetry. +{{% warning %}} +A plugin **must not** remove or modify in any way the core commands of Poetry. +{{% /warning %}} ### Event handler @@ -152,10 +163,15 @@ from poetry.plugins.application_plugin import ApplicationPlugin class MyApplicationPlugin(ApplicationPlugin): def activate(self, application: Application): - application.event_dispatcher.add_listener(COMMAND, self.load_dotenv) + application.event_dispatcher.add_listener( + COMMAND, self.load_dotenv + ) def load_dotenv( - self, event: ConsoleCommandEvent, event_name: str, dispatcher: EventDispatcher + self, + event: ConsoleCommandEvent, + event_name: str, + dispatcher: EventDispatcher ) -> None: command = event.command if not isinstance(command, EnvCommand): @@ -164,7 +180,9 @@ class MyApplicationPlugin(ApplicationPlugin): io = event.io if io.is_debug(): - io.write_line("Loading environment variables.") + io.write_line( + "Loading environment variables." + ) load_dotenv() ``` @@ -188,7 +206,7 @@ The `plugin add` command will ensure that the plugin is compatible with the curr and install the needed packages for the plugin to work. The package specification formats supported by the `plugin add` command are the same as the ones supported -by the [`add` command](/docs/cli/#add). +by the [`add` command]({{< relref "cli#add" >}}). If you no longer need a plugin and want to uninstall it, you can use the `plugin remove` command. diff --git a/docs/docs/pyproject.md b/docs/pyproject.md similarity index 78% rename from docs/docs/pyproject.md rename to docs/pyproject.md index 76cb2a86868..e102bffc104 100644 --- a/docs/docs/pyproject.md +++ b/docs/pyproject.md @@ -1,3 +1,14 @@ +--- +title: "The pyproject.toml file" +draft: false +type: docs +layout: single + +menu: + docs: + weight: 90 +--- + # The `pyproject.toml` file The `tool.poetry` section of the `pyproject.toml` file is composed of multiple sections. @@ -40,9 +51,9 @@ The recommended notation for the most common licenses is (alphabetical): Optional, but it is highly recommended to supply this. More identifiers are listed at the [SPDX Open Source License Registry](https://spdx.org/licenses/). -!!!note - - If your project is proprietary and does not use a specific licence, you can set this value as `Proprietary`. +{{% note %}} +If your project is proprietary and does not use a specific licence, you can set this value as `Proprietary`. +{{% /note %}} ## authors @@ -91,11 +102,11 @@ classifiers = [ ] ``` -!!!note +{{% note %}} +Note that Python classifiers are still automatically added for you and are determined by your `python` requirement. - Note that Python classifiers are still automatically added for you and are determined by your `python` requirement. - - The `license` property will also set the License classifier automatically. +The `license` property will also set the License classifier automatically. +{{% /note %}} ## packages @@ -137,26 +148,26 @@ packages = [ From now on, only the `sdist` build archive will include the `my_other_package` package. -!!!note - - Using `packages` disables the package auto-detection feature meaning you have to - **explicitly** specify the "default" package. +{{% note %}} +Using `packages` disables the package auto-detection feature meaning you have to +**explicitly** specify the "default" package. - For instance, if you have a package named `my_package` and you want to also include - another package named `extra_package`, you will need to specify `my_package` explicitly: +For instance, if you have a package named `my_package` and you want to also include +another package named `extra_package`, you will need to specify `my_package` explicitly: - ```toml - packages = [ - { include = "my_package" }, - { include = "extra_package" }, - ] - ``` - -!!!note +```toml +packages = [ + { include = "my_package" }, + { include = "extra_package" }, +] +``` +{{% /note %}} - Poetry is clever enough to detect Python subpackages. +{{% note %}} +Poetry is clever enough to detect Python subpackages. - Thus, you only have to specify the directory where your root package resides. +Thus, you only have to specify the directory where your root package resides. +{{% /note %}} ## include and exclude @@ -208,15 +219,15 @@ name = 'private' url = 'http://example.com/simple' ``` -!!!note - - Be aware that declaring the python version for which your package - is compatible is mandatory: +{{% note %}} +Be aware that declaring the python version for which your package +is compatible is mandatory: - ```toml - [tool.poetry.dependencies] - python = "^3.6" - ``` +```toml +[tool.poetry.dependencies] +python = "^3.6" +``` +{{% /note %}} ## `scripts` @@ -229,9 +240,9 @@ poetry = 'poetry.console:run' Here, we will have the `poetry` script installed which will execute `console.run` in the `poetry` package. -!!!note - - When a script is added or updated, run `poetry install` to make them available in the project's virtualenv. +{{% note %}} +When a script is added or updated, run `poetry install` to make them available in the project's virtualenv. +{{% /note %}} ## `extras` @@ -276,10 +287,11 @@ the `databases` extra can be installed as shown below. pip install awesome[databases] ``` -!!!note +{{% note %}} +The dependencies specified for each `extra` must already be defined as project dependencies. - The dependencies specified for each `extra` must already be defined as project dependencies. - Dependencies listed in the `dev-dependencies` section cannot be specified as extras. +Dependencies listed in the `dev-dependencies` section cannot be specified as extras. +{{% /note %}} ## `plugins` @@ -322,12 +334,11 @@ requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" ``` -!!!note - - When using the `new` or `init` command this section will be automatically added. - - -!!!note +{{% note %}} +When using the `new` or `init` command this section will be automatically added. +{{% /note %}} - If your `pyproject.toml` file still references `poetry` directly as a build backend, - you should update it to reference `poetry-core` instead. +{{% note %}} +If your `pyproject.toml` file still references `poetry` directly as a build backend, +you should update it to reference `poetry-core` instead. +{{% /note %}} diff --git a/docs/docs/repositories.md b/docs/repositories.md similarity index 76% rename from docs/docs/repositories.md rename to docs/repositories.md index 952e91f15d4..5b253e023c0 100644 --- a/docs/docs/repositories.md +++ b/docs/repositories.md @@ -1,3 +1,14 @@ +--- +title: "Repositories" +draft: false +type: docs +layout: "docs" + +menu: + docs: + weight: 50 +--- + # Repositories ## Using the PyPI repository @@ -37,24 +48,25 @@ poetry config http-basic.foo username password If you do not specify the password you will be prompted to write it. -!!!note +{{% note %}} +To publish to PyPI, you can set your credentials for the repository named `pypi`. - To publish to PyPI, you can set your credentials for the repository named `pypi`. +Note that it is recommended to use [API tokens](https://pypi.org/help/#apitoken) +when uploading packages to PyPI. +Once you have created a new token, you can tell Poetry to use it: - Note that it is recommended to use [API tokens](https://pypi.org/help/#apitoken) - 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 +``` - ```bash - poetry config pypi-token.pypi my-token - ``` +If you still want to use your username and password, you can do so with the following +call to `config`. - If you still want to use your username and password, you can do so with the following - call to `config`. +```bash +poetry config http-basic.pypi username password +``` +{{% /note %}} - ```bash - poetry config http-basic.pypi username password - ``` You can also specify the username and password when using the `publish` command with the `--username` and `--password` options. @@ -71,7 +83,7 @@ export POETRY_HTTP_BASIC_PYPI_USERNAME=username export POETRY_HTTP_BASIC_PYPI_PASSWORD=password ``` -See [Using environment variables](/docs/configuration/#using-environment-variables) for more information +See [Using environment variables]({{< relref "configuration#using-environment-variables" >}}) for more information on how to configure Poetry with environment variables. #### Custom certificate authority and mutual TLS authentication @@ -79,9 +91,10 @@ Poetry supports repositories that are secured by a custom certificate authority certificate-based client authentication. The following will configure the "foo" repository to validate the repository's certificate using a custom certificate authority and use a client certificate (note that these config variables do not both need to be set): + ```bash - poetry config certificates.foo.cert /path/to/ca.pem - poetry config certificates.foo.client-cert /path/to/client.pem +poetry config certificates.foo.cert /path/to/ca.pem +poetry config certificates.foo.client-cert /path/to/client.pem ``` ### Install dependencies from a private repository @@ -99,19 +112,19 @@ url = "https://foo.bar/simple/" From now on, Poetry will also look for packages in your private repository. -!!!note +{{% note %}} +Any custom repository will have precedence over PyPI. - Any custom repository will have precedence over PyPI. +If you still want PyPI to be your primary source for your packages +you can declare custom repositories as secondary. - If you still want PyPI to be your primary source for your packages - you can declare custom repositories as secondary. - - ```toml - [[tool.poetry.source]] - name = "foo" - url = "https://foo.bar/simple/" - secondary = true - ``` +```toml +[[tool.poetry.source]] +name = "foo" +url = "https://foo.bar/simple/" +secondary = true +``` +{{% /note %}} If your private repository requires HTTP Basic Auth be sure to add the username and password to your `http-basic` configuration using the example above (be sure to use the diff --git a/docs/theme/main.html b/docs/theme/main.html deleted file mode 100644 index 83151151ff6..00000000000 --- a/docs/theme/main.html +++ /dev/null @@ -1,29 +0,0 @@ ---- -layout: documentation -title: {{ page.title|striptags|e }} ---- - -
-
-
-
-
-
-
    - {% set navlevel = 1 %} - - {% for nav_item in nav %} - - {% endfor %} -
-
-
- {{page.content}} -
-
-
-
-
-
diff --git a/docs/theme/nav.html b/docs/theme/nav.html deleted file mode 100644 index e9d2b383844..00000000000 --- a/docs/theme/nav.html +++ /dev/null @@ -1,18 +0,0 @@ -{{ nav_item.title }} - -{%- if nav_item == page or nav_item.children %} - -{%- endif %} diff --git a/docs/theme/toc.html b/docs/theme/toc.html deleted file mode 100644 index 4f3e8e09ab6..00000000000 --- a/docs/theme/toc.html +++ /dev/null @@ -1,10 +0,0 @@ -{% for toc_item in page.toc %} - -{% if toc_item.children %} - -{% endif %} -{% endfor %} diff --git a/tox.ini b/tox.ini index 722c0e1adf1..6e40e8b7f8a 100644 --- a/tox.ini +++ b/tox.ini @@ -8,15 +8,3 @@ whitelist_externals = poetry commands = poetry install -vv --no-root poetry run pytest {posargs} tests/ - -[testenv:doc] -whitelist_externals = -skip_install = true -deps = - markdown-include - mkdocs - pygments - pygments-github-lexers - pymdown-extensions -commands = - mkdocs build -f docs/mkdocs.yml From f55c8efe32f82cf144bb68719452760660990d14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Sat, 5 Jun 2021 00:30:31 +0200 Subject: [PATCH 211/222] Fix documentation relative reference --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 9ad688cb828..f1669d76f62 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -11,7 +11,7 @@ menu: # Configuration -Poetry can be configured via the `config` command ([see more about its usage here]({{< relref "docs/cli#config" >}} "config command documentation")) +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. This file can typically be found in one of the following directories: From ddfb35d0b73ed23039b5bcd49182aed896021a08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Wed, 23 Jun 2021 01:25:32 +0200 Subject: [PATCH 212/222] Fix locked information for path, url and VCS dependencies --- poetry/packages/locker.py | 25 ++++++- ...irectory-dependency-poetry-transitive.test | 8 +-- .../with-file-dependency-transitive.test | 4 +- tests/packages/test_locker.py | 67 +++++++++++++++++++ 4 files changed, 96 insertions(+), 8 deletions(-) diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index c1cbe6609cb..ad61421a93e 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -513,7 +513,25 @@ def _dump_package(self, package: Package) -> dict: dependencies[dependency.pretty_name] = [] constraint = inline_table() - constraint["version"] = str(dependency.pretty_constraint) + + if dependency.is_directory() or dependency.is_file(): + constraint["path"] = dependency.path.as_posix() + + if dependency.is_directory() and dependency.develop: + constraint["develop"] = True + elif dependency.is_url(): + constraint["url"] = dependency.url + elif dependency.is_vcs(): + constraint[dependency.vcs] = dependency.source + + if dependency.branch: + constraint["branch"] = dependency.branch + elif dependency.tag: + constraint["tag"] = dependency.tag + elif dependency.rev: + constraint["rev"] = dependency.rev + else: + constraint["version"] = str(dependency.pretty_constraint) if dependency.extras: constraint["extras"] = sorted(dependency.extras) @@ -529,7 +547,10 @@ def _dump_package(self, package: Package) -> dict: # All the constraints should have the same type, # but we want to simplify them if it's possible for dependency, constraints in tuple(dependencies.items()): - if all(len(constraint) == 1 for constraint in constraints): + if all( + len(constraint) == 1 and "version" in constraint + for constraint in constraints + ): dependencies[dependency] = [ constraint["version"] for constraint in constraints ] diff --git a/tests/installation/fixtures/with-directory-dependency-poetry-transitive.test b/tests/installation/fixtures/with-directory-dependency-poetry-transitive.test index 7177191e2c8..fb10b1accea 100644 --- a/tests/installation/fixtures/with-directory-dependency-poetry-transitive.test +++ b/tests/installation/fixtures/with-directory-dependency-poetry-transitive.test @@ -65,8 +65,8 @@ python-versions = "*" version = "1.2.3" [package.dependencies] -project-with-extras = "1.2.3" -project-with-transitive-file-dependencies = "1.2.3" +project-with-extras = { "path" = "../../project_with_extras" } +project-with-transitive-file-dependencies = { "path" = "../project_with_transitive_file_dependencies" } [package.source] type = "directory" @@ -82,8 +82,8 @@ python-versions = "*" version = "1.2.3" [package.dependencies] -demo = "0.1.0" -inner-directory-project = "1.2.4" +demo = { "path" = "../../distributions/demo-0.1.0-py2.py3-none-any.whl" } +inner-directory-project = { "path" = "inner-directory-project" } [package.source] type = "directory" diff --git a/tests/installation/fixtures/with-file-dependency-transitive.test b/tests/installation/fixtures/with-file-dependency-transitive.test index 6e5d92d711a..b882f262640 100644 --- a/tests/installation/fixtures/with-file-dependency-transitive.test +++ b/tests/installation/fixtures/with-file-dependency-transitive.test @@ -48,8 +48,8 @@ python-versions = "*" version = "1.2.3" [package.dependencies] -demo = "0.1.0" -inner-directory-project = "1.2.4" +demo = { "path" = "../../distributions/demo-0.1.0-py2.py3-none-any.whl" } +inner-directory-project = { "path" = "inner-directory-project" } [package.source] type = "directory" diff --git a/tests/packages/test_locker.py b/tests/packages/test_locker.py index 285b91a8788..ed3c9af411d 100644 --- a/tests/packages/test_locker.py +++ b/tests/packages/test_locker.py @@ -1,6 +1,8 @@ import logging import tempfile +from pathlib import Path + import pytest import tomlkit @@ -531,3 +533,68 @@ def test_locker_should_neither_emit_warnings_nor_raise_error_for_lower_compatibl _ = locker.lock_data assert 0 == len(caplog.records) + + +def test_locker_dumps_dependency_information_correctly(locker, root): + 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 + ) + ) + package_a.add_dependency( + Factory.create_dependency( + "C", + {"path": "directory/project_with_transitive_directory_dependencies"}, + root_dir=root_dir, + ) + ) + package_a.add_dependency( + Factory.create_dependency( + "D", {"path": "distributions/demo-0.1.0.tar.gz"}, root_dir=root_dir + ) + ) + package_a.add_dependency( + Factory.create_dependency( + "E", {"url": "https://python-poetry.org/poetry-1.2.0.tar.gz"} + ) + ) + package_a.add_dependency( + Factory.create_dependency( + "F", {"git": "https://github.com/python-poetry/poetry.git", "branch": "foo"} + ) + ) + + packages = [package_a] + + locker.set_lock_data(root, packages) + + with locker.lock.open(encoding="utf-8") as f: + content = f.read() + + expected = """[[package]] +name = "A" +version = "1.0.0" +description = "" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +B = {path = "project_with_extras", develop = true} +C = {path = "directory/project_with_transitive_directory_dependencies"} +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"} + +[metadata] +lock-version = "1.1" +python-versions = "*" +content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" + +[metadata.files] +A = [] +""" + + assert expected == content From 7fac6769f8486b079f1235ff0d121b74f31598d2 Mon Sep 17 00:00:00 2001 From: Dan Rose Date: Wed, 23 Jun 2021 13:06:32 -0500 Subject: [PATCH 213/222] handle poetry init non-interactive dependencies (#2899) * handle poetry init non-interactive dependencies 1. in non-interactive mode, suppress "This command will guide you through..." 2. in interactive mode, respect command line --dependency if the user chooses NO for "Would you like to define your main dependencies interactively?" --- poetry/console/commands/init.py | 30 +++++++++++++++++------------ tests/console/commands/test_init.py | 22 +++++++++++++++++++++ 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/poetry/console/commands/init.py b/poetry/console/commands/init.py index b2e4fd0a315..0d525dbe3e0 100644 --- a/poetry/console/commands/init.py +++ b/poetry/console/commands/init.py @@ -86,11 +86,12 @@ def handle(self) -> int: vcs_config = GitConfig() - self.line("") - self.line( - "This command will guide you through creating your pyproject.toml config." - ) - self.line("") + if self.io.is_interactive(): + self.line("") + self.line( + "This command will guide you through creating your pyproject.toml config." + ) + self.line("") name = self.option("name") if not name: @@ -154,7 +155,8 @@ def handle(self) -> int: ) python = self.ask(question) - self.line("") + if self.io.is_interactive(): + self.line("") requirements = {} if self.option("dependency"): @@ -175,12 +177,14 @@ def handle(self) -> int: ) help_displayed = False if self.confirm(question, True): - self.line(help_message) - help_displayed = True + if self.io.is_interactive(): + self.line(help_message) + help_displayed = True requirements.update( self._format_requirements(self._determine_requirements([])) ) - self.line("") + if self.io.is_interactive(): + self.line("") dev_requirements = {} if self.option("dev-dependency"): @@ -192,13 +196,14 @@ def handle(self) -> int: "Would you like to define your development dependencies interactively?" ) if self.confirm(question, True): - if not help_displayed: + if self.io.is_interactive() and not help_displayed: self.line(help_message) dev_requirements.update( self._format_requirements(self._determine_requirements([])) ) - self.line("") + if self.io.is_interactive(): + self.line("") layout_ = layout("standard")( name, @@ -317,7 +322,8 @@ def _determine_requirements( if package is not False: requires.append(constraint) - package = self.ask("\nAdd a package:") + if self.io.is_interactive(): + package = self.ask("\nAdd a package:") return requires diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py index 4ae13465399..2d6301e05a0 100644 --- a/tests/console/commands/test_init.py +++ b/tests/console/commands/test_init.py @@ -81,6 +81,28 @@ def test_basic_interactive(tester, init_basic_inputs, init_basic_toml): assert init_basic_toml in tester.io.fetch_output() +def test_noninteractive(app, mocker, poetry, repo, tmp_path): + command = app.find("init") + command._pool = poetry.pool + + repo.add_package(get_package("pytest", "3.6.0")) + + p = mocker.patch("poetry.utils._compat.Path.cwd") + p.return_value = tmp_path + + tester = CommandTester(command) + args = "--name my-package --dependency pytest" + tester.execute(args=args, interactive=False) + + expected = "Using version ^3.6.0 for pytest\n" + assert tester.io.fetch_output() == expected + assert "" == tester.io.fetch_error() + + toml_content = (tmp_path / "pyproject.toml").read_text() + assert 'name = "my-package"' in toml_content + assert 'pytest = "^3.6.0"' in toml_content + + def test_interactive_with_dependencies(tester, repo): repo.add_package(get_package("django-pendulum", "0.1.6-pre4")) repo.add_package(get_package("pendulum", "2.0.0")) From e00874ae973aa05a1cdf05ba4dd093382ec0db0b Mon Sep 17 00:00:00 2001 From: finswimmer Date: Thu, 19 Nov 2020 01:45:21 +0100 Subject: [PATCH 214/222] Fix sub-command option propagation for export When a lock file does not exist when handling export command, option propagation for lock sub-command was incorrect. This change corrects this issue. Resolves: #3309 --- poetry/console/commands/export.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry/console/commands/export.py b/poetry/console/commands/export.py index a58d96f50fe..8bce95d8af7 100644 --- a/poetry/console/commands/export.py +++ b/poetry/console/commands/export.py @@ -44,11 +44,11 @@ def handle(self) -> None: self.line("The lock file does not exist. Locking.") options = [] if self.io.is_debug(): - options.append(("-vvv", None)) + options.append("-vvv") elif self.io.is_very_verbose(): - options.append(("-vv", None)) + options.append("-vv") elif self.io.is_verbose(): - options.append(("-v", None)) + options.append("-v") self.call("lock", " ".join(options)) From 0670eab03e4dcb5320a7528275f3a6a86f42c385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Sat, 30 Jan 2021 20:03:15 +0100 Subject: [PATCH 215/222] Fix legacy repository redirection test --- tests/repositories/test_legacy_repository.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/repositories/test_legacy_repository.py b/tests/repositories/test_legacy_repository.py index a1db15952e1..fef4b6d2d68 100644 --- a/tests/repositories/test_legacy_repository.py +++ b/tests/repositories/test_legacy_repository.py @@ -352,7 +352,7 @@ def test_get_5xx_raises(http): repo._get("/foo") -def test_get_redirected_response_url(http, monkeypatch): +def test_get_redirected_response_url(http, mocker): repo = MockHttpRepository({"/foo": 200}, http) redirect_url = "http://legacy.redirect.bar" @@ -362,5 +362,5 @@ def get_mock(url): response.url = redirect_url + "/foo" return response - monkeypatch.setattr(repo.session, "get", get_mock) + mocker.patch.object(requests.Session, "get", side_effect=get_mock) assert repo._get("/foo")._url == "http://legacy.redirect.bar/foo/" From 038b38b9da4749aeb8e3c1c199a11e24fa77ae22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Thu, 4 Mar 2021 11:27:12 +0100 Subject: [PATCH 216/222] Fix build scripts --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 4633517e587..f8a9ffb361b 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,7 @@ wheel: @poetry build -v linux_release: - docker pull quay.io/pypa/manylinux2010_x86_64 + docker pull quay.io/pypa/manylinux2010_x86_64:2021-02-06-3d322a5 docker run --rm -i -v `pwd`:/io \ -e PYTHON=/opt/python/cp38-cp38/bin/python \ -e PYTHON27=/opt/python/cp27-cp27m/bin/python \ @@ -56,7 +56,7 @@ linux_release: -e PYTHON37=/opt/python/cp37-cp37m/bin/python \ -e PYTHON38=/opt/python/cp38-cp38/bin/python \ -e PYTHON39=/opt/python/cp39-cp39/bin/python \ - quay.io/pypa/manylinux2010_x86_64 sh -c "cd /io && ./make-nix-release.sh" + quay.io/pypa/manylinux2010_x86_64:2021-02-06-3d322a5 sh -c "cd /io && ./make-nix-release.sh" # run tests against all supported python versions tox: From 66fbbe2556122352b487a963a2b6e712b6d84057 Mon Sep 17 00:00:00 2001 From: finswimmer Date: Sat, 7 Nov 2020 15:42:57 +0100 Subject: [PATCH 217/222] fix (utils.patterns): recognize one digit version in filename fix (utils.patterns): version part in filename must be present according PEP491 --- poetry/utils/patterns.py | 9 ++++++--- tests/utils/test_patterns.py | 39 ++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 tests/utils/test_patterns.py diff --git a/poetry/utils/patterns.py b/poetry/utils/patterns.py index 1d6413c26fe..ec6c53d78d7 100644 --- a/poetry/utils/patterns.py +++ b/poetry/utils/patterns.py @@ -2,8 +2,11 @@ wheel_file_re = re.compile( - r"""^(?P(?P.+?)(-(?P\d.+?))?) - ((-(?P\d.*?))?-(?P.+?)-(?P.+?)-(?P.+?) - \.whl|\.dist-info)$""", + r"^(?P(?P.+?)-(?P\d.*?))" + r"(-(?P\d.*?))?" + r"-(?P.+?)" + r"-(?P.+?)" + r"-(?P.+?)" + r"\.whl|\.dist-info$", re.VERBOSE, ) diff --git a/tests/utils/test_patterns.py b/tests/utils/test_patterns.py new file mode 100644 index 00000000000..9f43db27c56 --- /dev/null +++ b/tests/utils/test_patterns.py @@ -0,0 +1,39 @@ +import pytest + +from poetry.utils import patterns + + +@pytest.mark.parametrize( + ["filename", "expected"], + [ + ( + "markdown_captions-2-py3-none-any.whl", + { + "namever": "markdown_captions-2", + "name": "markdown_captions", + "ver": "2", + "build": None, + "pyver": "py3", + "abi": "none", + "plat": "any", + }, + ), + ( + "SQLAlchemy-1.3.20-cp27-cp27mu-manylinux2010_x86_64.whl", + { + "namever": "SQLAlchemy-1.3.20", + "name": "SQLAlchemy", + "ver": "1.3.20", + "build": None, + "pyver": "cp27", + "abi": "cp27mu", + "plat": "manylinux2010_x86_64", + }, + ), + ], +) +def test_wheel_file_re(filename, expected): + match = patterns.wheel_file_re.match(filename) + groups = match.groupdict() + + assert groups == expected From e58025ca63184252b70728cb70e4d50704cd10d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Sat, 5 Jun 2021 22:02:10 +0200 Subject: [PATCH 218/222] Reorganize documentation --- docs/_index.md | 216 ------------------------------------------------- 1 file changed, 216 deletions(-) delete mode 100644 docs/_index.md diff --git a/docs/_index.md b/docs/_index.md deleted file mode 100644 index d349033d651..00000000000 --- a/docs/_index.md +++ /dev/null @@ -1,216 +0,0 @@ ---- -title: "Introduction" -draft: false -type: docs -layout: "single" - -menu: - docs: - weight: 0 ---- - -# Introduction - -Poetry is a tool for **dependency management** and **packaging** in Python. -It allows you to declare the libraries your project depends on and it will manage (install/update) them for you. - - -## System requirements - -Poetry requires Python 2.7 or 3.5+. It is multi-platform and the goal is to make it work equally well -on Windows, Linux and OSX. - -{{% note %}} -Python 2.7 and 3.5 will no longer be supported in the next feature release (1.2). -You should consider updating your Python version to a supported one. -{{% /note %}} - -## Installation - -Poetry provides a custom installer that will install `poetry` isolated -from the rest of your system. - -### osx / linux / bashonwindows install instructions -```bash -curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python - -``` -### windows powershell install instructions -```powershell -(Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | python - -``` - -{{% warning %}} -The previous `get-poetry.py` installer is now deprecated, if you are currently using it -you should migrate to the new, supported, `install-poetry.py` installer. -{{% /warning %}} - -The installer installs the `poetry` tool to Poetry's `bin` directory. This location depends on your system: - -- `$HOME/.local/bin` for Unix -- `%APPDATA%\Python\Scripts` on Windows - -If this directory is not on your `PATH`, you will need to add it manually -if you want to invoke Poetry with simply `poetry`. - -Alternatively, you can use the full path to `poetry` to use it. - -Once Poetry is installed you can execute the following: - -```bash -poetry --version -``` - -If you see something like `Poetry (version 1.2.0)` then you are ready to use Poetry. -If you decide Poetry isn't your thing, you can completely remove it from your system -by running the installer again with the `--uninstall` option or by setting -the `POETRY_UNINSTALL` environment variable before executing the installer. - -```bash -python install-poetry.py --uninstall -POETRY_UNINSTALL=1 python install-poetry.py -``` - -By default, Poetry is installed into the user's platform-specific home directory. -If you wish to change this, you may define the `POETRY_HOME` environment variable: - -```bash -POETRY_HOME=/etc/poetry python install-poetry.py -``` - -If you want to install prerelease versions, you can do so by passing `--preview` option to `install-poetry.py` -or by using the `POETRY_PREVIEW` environment variable: - -```bash -python install-poetry.py --preview -POETRY_PREVIEW=1 python install-poetry.py -``` - -Similarly, if you want to install a specific version, you can use `--version` option or the `POETRY_VERSION` -environment variable: - -```bash -python install-poetry.py --version 1.2.0 -POETRY_VERSION=1.2.0 python install-poetry.py -``` - -You can also install Poetry for a `git` repository by using the `--git` option: - -```bash -python install-poetry.py --git https://github.com/python-poetry/poetry.git@master -```` - -{{% note %}} -Note that the installer does not support Python < 3.6. -{{% /note %}} - - -### Alternative installation methods - -#### Installing with `pipx` - -Using [`pipx`](https://github.com/pipxproject/pipx) to install Poetry is also possible. -`pipx` is used to install Python CLI applications globally while still isolating them in virtual environments. -This allows for clean upgrades and uninstalls. - -```bash -pipx install poetry -``` - -```bash -pipx upgrade poetry -``` - -```bash -pipx uninstall poetry -``` - - -#### Installing with `pip` - -Using `pip` to install Poetry is possible. - -```bash -pip install --user poetry -``` - -{{% warning %}} -Be aware that it will also install Poetry's dependencies -which might cause conflicts with other packages. -{{% /warning %}} - -## Updating `poetry` - -Updating Poetry to the latest stable version is as simple as calling the `self update` command. - -```bash -poetry self update -``` - -{{% warning %}} -Poetry versions installed using the now deprecated `get-poetry.py` installer will not be able to use this -command to update to 1.2 releases or later. Migrate to using the `install-poetry.py` installer or `pipx`. -{{% /warning %}} - -If you want to install pre-release versions, you can use the `--preview` option. - -```bash -poetry self update --preview -``` - -And finally, if you want to install a specific version, you can pass it as an argument -to `self update`. - -```bash -poetry self update 1.2.0 -``` - - -## Enable tab completion for Bash, Fish, or Zsh - -`poetry` supports generating completion scripts for Bash, Fish, and Zsh. -See `poetry help completions` for full details, but the gist is as simple as using one of the following: - - -```bash -# Bash -poetry completions bash > /etc/bash_completion.d/poetry.bash-completion - -# Bash (Homebrew) -poetry completions bash > $(brew --prefix)/etc/bash_completion.d/poetry.bash-completion - -# Fish -poetry completions fish > ~/.config/fish/completions/poetry.fish - -# Fish (Homebrew) -poetry completions fish > (brew --prefix)/share/fish/vendor_completions.d/poetry.fish - -# Zsh -poetry completions zsh > ~/.zfunc/_poetry - -# Oh-My-Zsh -mkdir $ZSH_CUSTOM/plugins/poetry -poetry completions zsh > $ZSH_CUSTOM/plugins/poetry/_poetry - -# prezto -poetry completions zsh > ~/.zprezto/modules/completion/external/src/_poetry - -``` - -{{% note %}} -You may need to restart your shell in order for the changes to take effect. -{{% /note %}} - -For `zsh`, you must then add the following line in your `~/.zshrc` before `compinit`: - -```bash -fpath+=~/.zfunc -``` - -For `oh-my-zsh`, you must then enable poetry in your `~/.zshrc` plugins - -```text -plugins( - poetry - ... - ) -``` From 9fad1e0598aae5ca68e304adbd7984f9bde169ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Wed, 23 Jun 2021 01:25:32 +0200 Subject: [PATCH 219/222] Fix locked information for path, url and VCS dependencies --- tests/packages/test_locker.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/packages/test_locker.py b/tests/packages/test_locker.py index ed3c9af411d..657eed58635 100644 --- a/tests/packages/test_locker.py +++ b/tests/packages/test_locker.py @@ -11,6 +11,7 @@ from poetry.core.semver.version import Version from poetry.factory import Factory from poetry.packages.locker import Locker +from poetry.utils._compat import Path from ..helpers import get_dependency from ..helpers import get_package From 7c085943cfd4ae3a02033d82a3c2b885c9d132da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 25 Jun 2021 16:17:51 +0200 Subject: [PATCH 220/222] Update build script --- make-nix-release.sh | 3 ++- sonnet | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/make-nix-release.sh b/make-nix-release.sh index b3828db2c38..a7ca8f32f2c 100755 --- a/make-nix-release.sh +++ b/make-nix-release.sh @@ -11,9 +11,10 @@ RUNTIMES[4]="${PYTHON38:+-P "3.8:$PYTHON38"}" test -n "$PYTHON" || PYTHON="python3" if [ "$OSTYPE" == "linux-gnu" ]; then - $PYTHON get-poetry.py -y --preview + $PYTHON get-poetry.py -y POETRY="$PYTHON $HOME/.poetry/bin/poetry" RUNTIMES[5]="${PYTHON39:+-P "3.9:$PYTHON39"}" + RUNTIMES[6]="${PYTHON310:+-P "3.10:$PYTHON310"}" else $PYTHON -m pip install poetry -U --pre POETRY="$PYTHON -m poetry" diff --git a/sonnet b/sonnet index 090e31ecda2..7322d3abed1 100755 --- a/sonnet +++ b/sonnet @@ -51,6 +51,7 @@ class MakeReleaseCommand(Command): "3.7": "python3.7", "3.8": "python3.8", "3.9": "python3.9", + "3.10": "python3.10", } def handle(self): From eb769a5a687949246510efe1719f0aba1d8ad0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 25 Jun 2021 17:25:52 +0200 Subject: [PATCH 221/222] Remove build for Python 3.10 --- make-nix-release.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/make-nix-release.sh b/make-nix-release.sh index a7ca8f32f2c..4872907e193 100755 --- a/make-nix-release.sh +++ b/make-nix-release.sh @@ -14,7 +14,6 @@ if [ "$OSTYPE" == "linux-gnu" ]; then $PYTHON get-poetry.py -y POETRY="$PYTHON $HOME/.poetry/bin/poetry" RUNTIMES[5]="${PYTHON39:+-P "3.9:$PYTHON39"}" - RUNTIMES[6]="${PYTHON310:+-P "3.10:$PYTHON310"}" else $PYTHON -m pip install poetry -U --pre POETRY="$PYTHON -m poetry" From aefd0614dba59d3b08158b469a686fd6bf09f4e8 Mon Sep 17 00:00:00 2001 From: Brian Wilcox Date: Sat, 26 Jun 2021 15:34:22 -0600 Subject: [PATCH 222/222] Dropping local lable (a la PEP 440) when converting a PackageInfo into a Package. Fixes #2613 --- poetry/inspection/info.py | 9 ++++++++- tests/inspection/test_info.py | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/poetry/inspection/info.py b/poetry/inspection/info.py index ae5f59bd63f..9c602603ba2 100644 --- a/poetry/inspection/info.py +++ b/poetry/inspection/info.py @@ -141,7 +141,7 @@ def to_package( package = Package( name=name, - version=self.version, + version=self.public_version, source_type=self._source_type, source_url=self._source_url, source_reference=self._source_reference, @@ -588,3 +588,10 @@ def from_path(cls, path: Path) -> "PackageInfo": return cls.from_bdist(path=path) except PackageInfoError: return cls.from_sdist(path=path) + + @property + def public_version(self) -> str: + """ + Removes + from packages. + """ + return self.version.split("+")[0] diff --git a/tests/inspection/test_info.py b/tests/inspection/test_info.py index 3a15e605a6f..3163f629a7d 100644 --- a/tests/inspection/test_info.py +++ b/tests/inspection/test_info.py @@ -4,6 +4,8 @@ import pytest +from poetry.core.packages.package import Package +from poetry.core.semver.version import Version from poetry.inspection.info import PackageInfo from poetry.inspection.info import PackageInfoError from poetry.utils._compat import decode @@ -225,3 +227,20 @@ def test_info_prefer_poetry_config_over_egg_info(): FIXTURE_DIR_INSPECTIONS / "demo_with_obsolete_egg_info" ) demo_check_info(info) + + +def test_info_public_version(): + package_info = PackageInfo(version="1.2.3+localVersion") + assert package_info.public_version == "1.2.3" + + +def test_info_public_version_no_mutation(): + package_info = PackageInfo(version="1.2.3") + assert package_info.public_version == "1.2.3" + + +def test_package_from_info_with_public_version(): + package_info = PackageInfo(name="test-package", version="1.2.3+local_super_version") + package = package_info.to_package() # type: Package + assert type(package.version) == Version + assert package.version.text == "1.2.3"