diff --git a/poetry_monoranger_plugin/path_rewriter.py b/poetry_monoranger_plugin/path_rewriter.py index 8fef214..6b806da 100644 --- a/poetry_monoranger_plugin/path_rewriter.py +++ b/poetry_monoranger_plugin/path_rewriter.py @@ -6,8 +6,11 @@ from __future__ import annotations +from contextlib import suppress from typing import TYPE_CHECKING, cast +import poetry.__version__ +from mypy.checkexpr import defaultdict from poetry.console.commands.build import BuildCommand from poetry.core.constraints.version import Version from poetry.core.packages.dependency import Dependency @@ -15,12 +18,18 @@ from poetry.core.packages.directory_dependency import DirectoryDependency from poetry.core.pyproject.toml import PyProjectTOML +with suppress(ImportError): + from poetry.core.pyproject.exceptions import PyProjectError # type: ignore[attr-defined] # exists only in >=2.0.0 + if TYPE_CHECKING: from cleo.events.console_command_event import ConsoleCommandEvent + from poetry.core.packages.dependency_group import DependencyGroup from poetry.poetry import Poetry from poetry_monoranger_plugin.config import MonorangerConfig +POETRY_V2 = poetry.__version__.__version__.startswith("2.") + class PathRewriter: """A class to handle the rewriting of directory dependencies in a Poetry project.""" @@ -43,7 +52,7 @@ def execute(self, event: ConsoleCommandEvent): poetry = command.poetry main_deps_group = poetry.package.dependency_group(MAIN_GROUP) - directory_deps = [dep for dep in main_deps_group.dependencies if isinstance(dep, DirectoryDependency)] + directory_deps = self._get_directory_deps(main_deps_group) for dependency in directory_deps: try: @@ -55,6 +64,37 @@ def execute(self, event: ConsoleCommandEvent): main_deps_group.remove_dependency(dependency.name) main_deps_group.add_dependency(pinned) + @staticmethod + def _get_directory_deps(dep_grp: DependencyGroup) -> list[DirectoryDependency]: + if not POETRY_V2: + return [dep for dep in dep_grp.dependencies if isinstance(dep, DirectoryDependency)] + + # Collect extras + features: defaultdict[str, set] = defaultdict(set) + for dep_set in [dep_grp._poetry_dependencies, dep_grp.dependencies, dep_grp.dependencies_for_locking]: # type: ignore[attr-defined] + # Collect all extras for each dependency from all three ways of accessing deps + # (._poetry_dependencies, .dependencies, .dependencies_for_locking) + if dep_set is not None: + for dep in dep_set: + if dep.features: + features[dep.name].update(dep.features) + + # Required to have type: ignore[attr-defined] as the attribute is only defined in Poetry >=2.0.0 + deps_for_locking = {dep.name: dep for dep in dep_grp.dependencies_for_locking} # type: ignore[attr-defined] + + directory_deps = [] + for dep in dep_grp.dependencies: + if isinstance(dep, DirectoryDependency): + dir_dep = dep + elif isinstance(deps_for_locking.get(dep.name, None), DirectoryDependency): + dir_dep = cast(DirectoryDependency, deps_for_locking[dep.name]) + else: + continue + + directory_deps.append(dir_dep.with_features(features[dir_dep.name])) + + return directory_deps + def _get_dependency_pyproject(self, poetry: Poetry, dependency: DirectoryDependency) -> PyProjectTOML: pyproject_file = poetry.pyproject_path.parent / dependency.path / "pyproject.toml" @@ -84,8 +124,14 @@ def _pin_dependency(self, poetry: Poetry, dependency: DirectoryDependency): """ dep_pyproject: PyProjectTOML = self._get_dependency_pyproject(poetry, dependency) - name = cast(str, dep_pyproject.poetry_config["name"]) - version = cast(str, dep_pyproject.poetry_config["version"]) + try: + name = cast(str, dep_pyproject.poetry_config["name"]) + version = cast(str, dep_pyproject.poetry_config["version"]) + except PyProjectError: + # Fallback to the project section since Poetry V2 also supports PEP 621 pyproject.toml files + name = cast(str, dep_pyproject.data["project"]["name"]) + version = cast(str, dep_pyproject.data["project"]["version"]) + if self.plugin_conf.version_rewrite_rule in ["~", "^"]: pinned_version = f"{self.plugin_conf.version_rewrite_rule}{version}" elif self.plugin_conf.version_rewrite_rule == "==": diff --git a/tests/fixtures/v1/pkg_one/pyproject.toml b/tests/fixtures/v1/pkg_one/pyproject.toml index c3324f0..84d6839 100644 --- a/tests/fixtures/v1/pkg_one/pyproject.toml +++ b/tests/fixtures/v1/pkg_one/pyproject.toml @@ -8,12 +8,12 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.9" -# A list of all of the optional dependencies, some of which are included in the +# A list of all the optional dependencies, some of which are included in the # below `extras`. They can be opted into by apps. -pandas = { version = "^2.0.0", optional = true } +fsspec = { version = "^2024.12.0", optional = true } [tool.poetry.extras] -withpandas = ["pandas"] +withfsspec = ["fsspec"] [tool.poetry-monoranger-plugin] enabled = true diff --git a/tests/fixtures/v1/pkg_three/pyproject.toml b/tests/fixtures/v1/pkg_three/pyproject.toml index dc54e4a..cf4b2f6 100644 --- a/tests/fixtures/v1/pkg_three/pyproject.toml +++ b/tests/fixtures/v1/pkg_three/pyproject.toml @@ -7,7 +7,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.9" -pkg-two = { path = '../pkg_two', develop = true, extras = ["withpandas"] } +pkg-one = { path = '../pkg_one', develop = true, extras = ["withfsspec"] } [tool.poetry-monoranger-plugin] enabled = true diff --git a/tests/fixtures/v1/poetry.lock b/tests/fixtures/v1/poetry.lock index 6d8e3ec..37b63ee 100644 --- a/tests/fixtures/v1/poetry.lock +++ b/tests/fixtures/v1/poetry.lock @@ -451,6 +451,45 @@ docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3) testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] typing = ["typing-extensions (>=4.12.2)"] +[[package]] +name = "fsspec" +version = "2024.12.0" +description = "File-system specification" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fsspec-2024.12.0-py3-none-any.whl", hash = "sha256:b520aed47ad9804237ff878b504267a3b0b441e97508bd6d2d8774e3db85cee2"}, + {file = "fsspec-2024.12.0.tar.gz", hash = "sha256:670700c977ed2fb51e0d9f9253177ed20cbde4a3e5c0283cc5385b5870c8533f"}, +] + +[package.extras] +abfs = ["adlfs"] +adl = ["adlfs"] +arrow = ["pyarrow (>=1)"] +dask = ["dask", "distributed"] +dev = ["pre-commit", "ruff"] +doc = ["numpydoc", "sphinx", "sphinx-design", "sphinx-rtd-theme", "yarl"] +dropbox = ["dropbox", "dropboxdrivefs", "requests"] +full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] +fuse = ["fusepy"] +gcs = ["gcsfs"] +git = ["pygit2"] +github = ["requests"] +gs = ["gcsfs"] +gui = ["panel"] +hdfs = ["pyarrow (>=1)"] +http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"] +libarchive = ["libarchive-c"] +oci = ["ocifs"] +s3 = ["s3fs"] +sftp = ["paramiko"] +smb = ["smbprotocol"] +ssh = ["paramiko"] +test = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "numpy", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "requests"] +test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask-expr", "dask[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"] +test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard"] +tqdm = ["tqdm"] + [[package]] name = "idna" version = "3.10" @@ -673,8 +712,11 @@ python-versions = "^3.9" files = [] develop = true +[package.dependencies] +fsspec = {version = "^2024.12.0", optional = true} + [package.extras] -withpandas = ["pandas (>=2.0.0,<3.0.0)"] +withfsspec = ["fsspec (>=2024.12.0,<2025.0.0)"] [package.source] type = "directory" @@ -690,7 +732,7 @@ files = [] develop = true [package.dependencies] -pkg-two = {path = "../pkg_two", develop = true, extras = ["withpandas"]} +pkg-one = {path = "../pkg_one", develop = true, extras = ["withfsspec"]} [package.source] type = "directory" diff --git a/tests/fixtures/v2/pkg_one/README.md b/tests/fixtures/v2/pkg_one/README.md new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/v2/pkg_one/pkg_one/__init__.py b/tests/fixtures/v2/pkg_one/pkg_one/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/v2/pkg_one/poetry.toml b/tests/fixtures/v2/pkg_one/poetry.toml new file mode 100644 index 0000000..084377a --- /dev/null +++ b/tests/fixtures/v2/pkg_one/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +create = false diff --git a/tests/fixtures/v2/pkg_one/pyproject.toml b/tests/fixtures/v2/pkg_one/pyproject.toml new file mode 100644 index 0000000..5d6f9dc --- /dev/null +++ b/tests/fixtures/v2/pkg_one/pyproject.toml @@ -0,0 +1,23 @@ +[project] +name = "pkg-one" +version = "0.1.0" +description = "" +authors = [ + {name = "Example Example",email = "example@example.com"} +] +readme = "README.md" +requires-python = ">=3.9" +dependencies = [ +] + +[project.optional-dependencies] +withfsspec = ["fsspec (>=2024.12.0,<2025.0.0)"] + +[tool.poetry-monoranger-plugin] +enabled = true +monorepo-root = "../" +version-rewrite-rule = '==' + +[build-system] +requires = ["poetry-core>=2.0.0,<3.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/fixtures/v2/pkg_three/README.md b/tests/fixtures/v2/pkg_three/README.md new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/v2/pkg_three/pkg_three/__init__.py b/tests/fixtures/v2/pkg_three/pkg_three/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/v2/pkg_three/poetry.toml b/tests/fixtures/v2/pkg_three/poetry.toml new file mode 100644 index 0000000..084377a --- /dev/null +++ b/tests/fixtures/v2/pkg_three/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +create = false diff --git a/tests/fixtures/v2/pkg_three/pyproject.toml b/tests/fixtures/v2/pkg_three/pyproject.toml new file mode 100644 index 0000000..9d1f393 --- /dev/null +++ b/tests/fixtures/v2/pkg_three/pyproject.toml @@ -0,0 +1,24 @@ +[project] +name = "pkg-three" +version = "0.1.0" +description = "" +authors = [ + {name = "Example Example",email = "example@example.com"} +] +readme = "README.md" +requires-python = ">=3.9" +dependencies = [ + "pkg-one" +] + +[tool.poetry-monoranger-plugin] +enabled = true +monorepo-root = "../" +version-rewrite-rule = '==' + +[build-system] +requires = ["poetry-core>=2.0.0,<3.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.dependencies] +pkg-one = {path = "../pkg_one", develop = true, extras = ["withfsspec"]} diff --git a/tests/fixtures/v2/pkg_two/README.md b/tests/fixtures/v2/pkg_two/README.md new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/v2/pkg_two/pkg_two/__init__.py b/tests/fixtures/v2/pkg_two/pkg_two/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/v2/pkg_two/poetry.toml b/tests/fixtures/v2/pkg_two/poetry.toml new file mode 100644 index 0000000..084377a --- /dev/null +++ b/tests/fixtures/v2/pkg_two/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +create = false diff --git a/tests/fixtures/v2/pkg_two/pyproject.toml b/tests/fixtures/v2/pkg_two/pyproject.toml new file mode 100644 index 0000000..03ab92b --- /dev/null +++ b/tests/fixtures/v2/pkg_two/pyproject.toml @@ -0,0 +1,21 @@ +[project] +name = "pkg-two" +version = "0.1.0" +description = "" +authors = [ + {name = "Example Example",email = "example@example.com"} +] +readme = "README.md" +requires-python = ">=3.9" +dependencies = [ + "tqdm (>=4.67.1,<5.0.0)" +] + +[tool.poetry-monoranger-plugin] +enabled = true +monorepo-root = "../" +version-rewrite-rule = '==' + +[build-system] +requires = ["poetry-core>=2.0.0,<3.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/fixtures/v2/poetry.lock b/tests/fixtures/v2/poetry.lock new file mode 100644 index 0000000..890d327 --- /dev/null +++ b/tests/fixtures/v2/poetry.lock @@ -0,0 +1,120 @@ +# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main"] +markers = "platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "pkg-one" +version = "0.1.0" +description = "" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [] +develop = true + +[package.extras] +withfsspec = ["fsspec (>=2024.12.0,<2025.0.0)"] + +[package.source] +type = "directory" +url = "pkg_one" + +[[package]] +name = "pkg-three" +version = "0.1.0" +description = "" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [] +develop = true + +[package.dependencies] +pkg-one = "*" + +[package.source] +type = "directory" +url = "pkg_three" + +[[package]] +name = "pkg-two" +version = "0.1.0" +description = "" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [] +develop = true + +[package.dependencies] +tqdm = ">=4.67.1,<5.0.0" + +[package.source] +type = "directory" +url = "pkg_two" + +[[package]] +name = "ruff" +version = "0.9.3" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624"}, + {file = "ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c"}, + {file = "ruff-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6"}, + {file = "ruff-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730"}, + {file = "ruff-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2"}, + {file = "ruff-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519"}, + {file = "ruff-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b"}, + {file = "ruff-0.9.3-py3-none-win32.whl", hash = "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c"}, + {file = "ruff-0.9.3-py3-none-win_amd64.whl", hash = "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4"}, + {file = "ruff-0.9.3-py3-none-win_arm64.whl", hash = "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b"}, + {file = "ruff-0.9.3.tar.gz", hash = "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a"}, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, + {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] +discord = ["requests"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[metadata] +lock-version = "2.1" +python-versions = ">=3.9" +content-hash = "b96906f67130c5ee9ccc43bb0950ba59b7d6fc998479d16f7858524907302a1c" diff --git a/tests/fixtures/v2/poetry.toml b/tests/fixtures/v2/poetry.toml new file mode 100644 index 0000000..ab1033b --- /dev/null +++ b/tests/fixtures/v2/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +in-project = true diff --git a/tests/fixtures/v2/pyproject.toml b/tests/fixtures/v2/pyproject.toml new file mode 100644 index 0000000..094e4f7 --- /dev/null +++ b/tests/fixtures/v2/pyproject.toml @@ -0,0 +1,29 @@ +[project] +name = "v2" +version = "0.1.0" +description = "" +authors = [ + {name = "Example Example",email = "example@example.com"} +] +readme = "README.md" +requires-python = ">=3.9" +dependencies = [ + "pkg-one", + "pkg-two", + "pkg-three" +] + +[build-system] +requires = ["poetry-core>=2.0.0,<3.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry] +package-mode = false + +[tool.poetry.dependencies] +pkg-one = {path = "./pkg_one", develop = true} +pkg-two = {path = "./pkg_two", develop = true} +pkg-three = {path = "./pkg_three", develop = true} + +[tool.poetry.group.dev.dependencies] +ruff = "^0.9.3" diff --git a/tests/helpers.py b/tests/helpers.py index 2ede331..b49578b 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -56,8 +56,8 @@ class MockRepoManager: """A helper class to generate test repositories""" def __init__(self): - self._src_repos: dict[str, Path] = {"v1": None} # type: ignore[assignment, dict-item] - self._preinstalled_repos: dict[str, Path] = {"v1": None} # type: ignore[assignment, dict-item] + self._src_repos: dict[str, Path] = {"v1": None, "v2": None} # type: ignore[assignment, dict-item] + self._preinstalled_repos: dict[str, Path] = {"v1": None, "v2": None} # type: ignore[assignment, dict-item] self._tmp_path_obj = tempfile.TemporaryDirectory() self._tmp_path = Path(self._tmp_path_obj.name) diff --git a/tests/test_commands.py b/tests/test_commands.py index 927b666..3cad497 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -4,142 +4,153 @@ import pytest -from tests.helpers import POETRY_V2, is_system_env +from tests.helpers import POETRY_V2, is_system_env, only_poetry_v2 -def test_add(repo_manager, poetry_run): +@pytest.mark.parametrize("repo_name", [pytest.param("v1"), pytest.param("v2", marks=only_poetry_v2)]) +def test_add(repo_manager, poetry_run, repo_name): # Arrange - v1_dir = repo_manager.get_repo("v1", preinstalled=True) - envs = repo_manager.get_envs(v1_dir) - root_lock = (v1_dir / "poetry.lock").read_text() - root_pyproject = (v1_dir / "pyproject.toml").read_text() + root_dir = repo_manager.get_repo(repo_name, preinstalled=True) + envs = repo_manager.get_envs(root_dir) + root_lock = (root_dir / "poetry.lock").read_text() + root_pyproject = (root_dir / "pyproject.toml").read_text() assert envs.root_env.site_packages.find_distribution("numpy") is None for env in envs.pkg_envs: assert env.site_packages.find_distribution("numpy") is None # Act - result = poetry_run(v1_dir, "pkg_one", "add numpy==1.25") + result = poetry_run(root_dir, "pkg_one", "add numpy==1.25") # Assert assert result.exit_code == 0 assert "Installing numpy" in result.stdout assert "Skipping virtualenv creation" in result.stderr - assert root_lock != (v1_dir / "poetry.lock").read_text() # lock file is modified - assert not (v1_dir / "pkg_one" / "poetry.lock").exists() # pkg_one lockfile is not created - assert root_pyproject == (v1_dir / "pyproject.toml").read_text() # root pyproject.toml is not modified - assert (v1_dir / "pkg_one" / "pyproject.toml").read_text().count("numpy") == 1 # pkg_one pyproject.toml is modified + assert root_lock != (root_dir / "poetry.lock").read_text() # lock file is modified + assert not (root_dir / "pkg_one" / "poetry.lock").exists() # pkg_one lockfile is not created + assert root_pyproject == (root_dir / "pyproject.toml").read_text() # root pyproject.toml is not modified + assert (root_dir / "pkg_one" / "pyproject.toml").read_text().count( + "numpy" + ) == 1 # pkg_one pyproject.toml is modified assert envs.root_env.site_packages.find_distribution("numpy") is not None for env in envs.pkg_envs: assert env.site_packages.find_distribution("numpy") is None -def test_remove(repo_manager, poetry_run): +@pytest.mark.parametrize("repo_name", [pytest.param("v1"), pytest.param("v2", marks=only_poetry_v2)]) +def test_remove(repo_manager, poetry_run, repo_name): # Arrange - v1_dir = repo_manager.get_repo("v1", preinstalled=True) - envs = repo_manager.get_envs(v1_dir) - root_lock = (v1_dir / "poetry.lock").read_text() - root_pyproject = (v1_dir / "pyproject.toml").read_text() + root_dir = repo_manager.get_repo(repo_name, preinstalled=True) + envs = repo_manager.get_envs(root_dir) + root_lock = (root_dir / "poetry.lock").read_text() + root_pyproject = (root_dir / "pyproject.toml").read_text() assert envs.root_env.site_packages.find_distribution("tqdm") is not None for env in envs.pkg_envs: assert env.site_packages.find_distribution("tqdm") is None # Act - result = poetry_run(v1_dir, "pkg_two", "remove tqdm") + result = poetry_run(root_dir, "pkg_two", "remove tqdm") # Assert assert result.exit_code == 0 assert "Removing tqdm" in result.stdout assert "Skipping virtualenv creation" in result.stderr - assert root_lock != (v1_dir / "poetry.lock").read_text() # lock file is modified - assert not (v1_dir / "pkg_two" / "poetry.lock").exists() # pkg_two lockfile is not created - assert root_pyproject == (v1_dir / "pyproject.toml").read_text() # root pyproject.toml is not modified - assert (v1_dir / "pkg_two" / "pyproject.toml").read_text().count("tqdm") == 0 # pkg_two pyproject.toml is modified + assert root_lock != (root_dir / "poetry.lock").read_text() # lock file is modified + assert not (root_dir / "pkg_two" / "poetry.lock").exists() # pkg_two lockfile is not created + assert root_pyproject == (root_dir / "pyproject.toml").read_text() # root pyproject.toml is not modified + assert (root_dir / "pkg_two" / "pyproject.toml").read_text().count( + "tqdm" + ) == 0 # pkg_two pyproject.toml is modified assert envs.root_env.site_packages.find_distribution("tqdm") is None for env in envs.pkg_envs: assert env.site_packages.find_distribution("numpy") is None -def test_update(repo_manager, poetry_run): +@pytest.mark.parametrize("repo_name", [pytest.param("v1"), pytest.param("v2", marks=only_poetry_v2)]) +def test_update(repo_manager, poetry_run, repo_name): # Arrange - v1_dir = repo_manager.get_repo("v1", preinstalled=True) - envs = repo_manager.get_envs(v1_dir) + root_dir = repo_manager.get_repo(repo_name, preinstalled=True) + envs = repo_manager.get_envs(root_dir) - poetry_run(v1_dir, "pkg_one", "add numpy<=1.25") - pkg_one_pyproject = (v1_dir / "pkg_one" / "pyproject.toml").read_text() - (v1_dir / "pkg_one" / "pyproject.toml").write_text(pkg_one_pyproject.replace("<=1.25", "<=1.26.4")) + poetry_run(root_dir, "pkg_one", "add numpy<=1.25") + pkg_one_pyproject = (root_dir / "pkg_one" / "pyproject.toml").read_text() + (root_dir / "pkg_one" / "pyproject.toml").write_text(pkg_one_pyproject.replace("<=1.25", "<=1.26.4")) if POETRY_V2: - poetry_run(v1_dir, "pkg_one", "lock") + poetry_run(root_dir, "pkg_one", "lock") else: - poetry_run(v1_dir, "pkg_one", "lock --no-update") + poetry_run(root_dir, "pkg_one", "lock --no-update") # This results in a lockfile with numpy==1.25 but pyproject.toml permits up to 1.26.5 - root_lock = (v1_dir / "poetry.lock").read_text() - root_pyproject = (v1_dir / "pyproject.toml").read_text() + root_lock = (root_dir / "poetry.lock").read_text() + root_pyproject = (root_dir / "pyproject.toml").read_text() assert envs.root_env.site_packages.find_distribution("numpy") is not None for env in envs.pkg_envs: assert env.site_packages.find_distribution("numpy") is None # Act - result = poetry_run(v1_dir, "pkg_one", "update numpy") + result = poetry_run(root_dir, "pkg_one", "update numpy") # Assert assert result.exit_code == 0 assert "Updating numpy" in result.stdout assert "Skipping virtualenv creation" in result.stderr - assert root_lock != (v1_dir / "poetry.lock").read_text() # lock file is modified - assert not (v1_dir / "pkg_one" / "poetry.lock").exists() # pkg_one lockfile is not created - assert root_pyproject == (v1_dir / "pyproject.toml").read_text() # root pyproject.toml is not modified - assert (v1_dir / "pkg_one" / "pyproject.toml").read_text().count("numpy") == 1 + assert root_lock != (root_dir / "poetry.lock").read_text() # lock file is modified + assert not (root_dir / "pkg_one" / "poetry.lock").exists() # pkg_one lockfile is not created + assert root_pyproject == (root_dir / "pyproject.toml").read_text() # root pyproject.toml is not modified + assert (root_dir / "pkg_one" / "pyproject.toml").read_text().count("numpy") == 1 assert envs.root_env.site_packages.find_distribution("numpy") is not None for env in envs.pkg_envs: assert env.site_packages.find_distribution("numpy") is None -def test_lock(repo_manager, poetry_run): +@pytest.mark.parametrize("repo_name", [pytest.param("v1"), pytest.param("v2", marks=only_poetry_v2)]) +def test_lock(repo_manager, poetry_run, repo_name): # Arrange - v1_dir = repo_manager.get_repo("v1", preinstalled=False) - (v1_dir / "poetry.lock").unlink() - root_pyproject = (v1_dir / "pyproject.toml").read_text() + root_dir = repo_manager.get_repo(repo_name, preinstalled=False) + (root_dir / "poetry.lock").unlink() + root_pyproject = (root_dir / "pyproject.toml").read_text() # Act - result = poetry_run(v1_dir, "pkg_one", "lock") + result = poetry_run(root_dir, "pkg_one", "lock") # Assert assert result.exit_code == 0 assert "Writing lock file" in result.stdout - assert (v1_dir / "poetry.lock").exists() # lock file is created at root instead of pkg_one - assert not (v1_dir / "pkg_one" / "poetry.lock").exists() # pkg_one lockfile is not created - assert root_pyproject == (v1_dir / "pyproject.toml").read_text() # root pyproject.toml is not modified + assert (root_dir / "poetry.lock").exists() # lock file is created at root instead of pkg_one + assert not (root_dir / "pkg_one" / "poetry.lock").exists() # pkg_one lockfile is not created + assert root_pyproject == (root_dir / "pyproject.toml").read_text() # root pyproject.toml is not modified +@pytest.mark.parametrize("repo_name", [pytest.param("v1"), pytest.param("v2", marks=only_poetry_v2)]) @pytest.mark.parametrize("sync", [True, False]) -def test_install(repo_manager, poetry_run, sync: bool): +def test_install(repo_manager, poetry_run, sync: bool, repo_name): # Arrange - v1_dir = repo_manager.get_repo("v1", preinstalled=False) - envs_before = repo_manager.get_envs(v1_dir) + root_dir = repo_manager.get_repo(repo_name, preinstalled=False) + envs_before = repo_manager.get_envs(root_dir) - root_lock = (v1_dir / "poetry.lock").read_text() - root_pyproject = (v1_dir / "pyproject.toml").read_text() + root_lock = (root_dir / "poetry.lock").read_text() + root_pyproject = (root_dir / "pyproject.toml").read_text() for env in [envs_before.root_env, *envs_before.pkg_envs]: assert env.is_venv() is False assert is_system_env(env) # Act if sync: - result = poetry_run(v1_dir, "pkg_one", "sync") if POETRY_V2 else poetry_run(v1_dir, "pkg_one", "install --sync") + result = ( + poetry_run(root_dir, "pkg_one", "sync") if POETRY_V2 else poetry_run(root_dir, "pkg_one", "install --sync") + ) else: - result = poetry_run(v1_dir, "pkg_one", "install") + result = poetry_run(root_dir, "pkg_one", "install") # Assert assert result.exit_code == 0 assert "Installing dependencies from lock file" in result.stdout - assert root_lock == (v1_dir / "poetry.lock").read_text() # lock file is not modified - assert not (v1_dir / "pkg_one" / "poetry.lock").exists() # pkg_one lockfile is not created - assert root_pyproject == (v1_dir / "pyproject.toml").read_text() # root pyproject.toml is not modified + assert root_lock == (root_dir / "poetry.lock").read_text() # lock file is not modified + assert not (root_dir / "pkg_one" / "poetry.lock").exists() # pkg_one lockfile is not created + assert root_pyproject == (root_dir / "pyproject.toml").read_text() # root pyproject.toml is not modified - envs_after = repo_manager.get_envs(v1_dir) + envs_after = repo_manager.get_envs(root_dir) assert not is_system_env(envs_after.root_env) assert envs_after.root_env.is_venv() is True for env in envs_after.pkg_envs: @@ -147,20 +158,21 @@ def test_install(repo_manager, poetry_run, sync: bool): assert is_system_env(env) -def test_build(repo_manager, poetry_run): +@pytest.mark.parametrize("repo_name", [pytest.param("v1"), pytest.param("v2", marks=only_poetry_v2)]) +def test_build(repo_manager, poetry_run, repo_name): # Arrange - v1_dir = repo_manager.get_repo("v1", preinstalled=False) + root_dir = repo_manager.get_repo(repo_name, preinstalled=False) # Act - result = poetry_run(v1_dir, "pkg_three", "build") + result = poetry_run(root_dir, "pkg_three", "build") # Assert assert result.exit_code == 0 assert "Building pkg-three" in result.stdout - tarfile_path = next((v1_dir / "pkg_three" / "dist").glob("*.tar.gz")) + tarfile_path = next((root_dir / "pkg_three" / "dist").glob("*.tar.gz")) with tarfile.open(tarfile_path, "r:gz") as tar: - tar.extractall(path=v1_dir / "pkg_three" / "dist") - pkg_info_path = next((v1_dir / "pkg_three" / "dist").rglob("PKG-INFO")) + tar.extractall(path=root_dir / "pkg_three" / "dist") + pkg_info_path = next((root_dir / "pkg_three" / "dist").rglob("PKG-INFO")) # Ensure extras are included - assert "Requires-Dist: pkg-two[withpandas] (==0.1.0)" in pkg_info_path.read_text() + assert "Requires-Dist: pkg-one[withfsspec] (==0.1.0)" in pkg_info_path.read_text()