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 +``` 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. 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): 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): 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) 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/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/factory.py b/poetry/factory.py old mode 100755 new mode 100644 index 90028a110f2..fd863ba5991 --- 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/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/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) 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 diff --git a/poetry/installation/installer.py b/poetry/installation/installer.py index f0c9a62d65d..2164d39f4b1 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,32 @@ 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)) + + 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) + + self._write_lock_file(local_repo, force=True) + + return 0 + def _do_install(self, local_repo): from poetry.puzzle import Solver @@ -285,8 +315,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/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/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/puzzle/provider.py b/poetry/puzzle/provider.py old mode 100755 new mode 100644 index 44d8b6e3796..e2838370a79 --- 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, ), ) @@ -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: @@ -482,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/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 index 556c77e9862..1ebe702bb9c --- 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/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): diff --git a/poetry/utils/exporter.py b/poetry/utils/exporter.py index 19b48452346..4200abd146d 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: @@ -132,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] 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/console/commands/test_init.py b/tests/console/commands/test_init.py index c64bc23316a..7a9212ab262 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" @@ -595,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 ): 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/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 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/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/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): diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index bf9075e186a..1393b13f1ef 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", "*")) @@ -1515,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") @@ -2435,7 +2646,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 +2675,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 ): 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 ):