From 0cb2b70d63eee240f5b93b9c4d43395d931e9530 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Mon, 5 Oct 2020 01:22:00 +0200 Subject: [PATCH 1/5] 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 c59b1cec45b6432326a4e668afc6e5f168a8442b Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Mon, 5 Oct 2020 01:23:16 +0200 Subject: [PATCH 2/5] 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 692eb6d04420a4e2f01e82381a41efb297756e32 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 2 Oct 2020 14:23:46 +0200 Subject: [PATCH 3/5] 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 676d1b24ac3d65b56cf2a1e91c65463d5d08d5ce Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 2 Oct 2020 12:10:39 +0200 Subject: [PATCH 4/5] 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 a94c40eddc9b58b565e94fb023b080b07300974f Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 2 Oct 2020 11:16:37 +0200 Subject: [PATCH 5/5] 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", "*"))