From d673342816d71b22d7006730d34a9026fce38944 Mon Sep 17 00:00:00 2001 From: Nicklas Tegner Date: Thu, 1 Oct 2020 21:35:51 +0200 Subject: [PATCH 01/19] 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 02/19] 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 03/19] 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 04/19] 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 05/19] 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 06/19] 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 07/19] 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 08/19] 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 09/19] 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 10/19] 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 11/19] 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 12/19] 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 13/19] 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 14/19] 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 15/19] 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 16/19] 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 17/19] 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 18/19] 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 2bf020377b78dbe1ed5afe166fde36d66a0d51db Mon Sep 17 00:00:00 2001 From: Michel Albert Date: Thu, 8 Oct 2020 16:35:20 +0200 Subject: [PATCH 19/19] 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):