diff --git a/piptools/_compat/pip_compat.py b/piptools/_compat/pip_compat.py index 6409fbc2a..a5f843ad9 100644 --- a/piptools/_compat/pip_compat.py +++ b/piptools/_compat/pip_compat.py @@ -10,6 +10,7 @@ from pip._internal.metadata import BaseDistribution from pip._internal.metadata.pkg_resources import Distribution as _PkgResourcesDist from pip._internal.models.direct_url import DirectUrl +from pip._internal.models.link import Link from pip._internal.network.session import PipSession from pip._internal.req import InstallRequirement from pip._internal.req import parse_requirements as _parse_requirements @@ -63,6 +64,14 @@ def _from_importlib(cls, dist: _ImportLibDist) -> Distribution: return cls(dist._dist.name, dist._dist.version, requires, dist.direct_url) +class FileLink(Link): + + @property + def file_path(self) -> str: + # overriding the actual property to bypass some validation + return self._url + + def parse_requirements( filename: str, session: PipSession, @@ -74,7 +83,14 @@ def parse_requirements( for parsed_req in _parse_requirements( filename, session, finder=finder, options=options, constraint=constraint ): - yield install_req_from_parsed_requirement(parsed_req, isolated=isolated) + install_req = install_req_from_parsed_requirement(parsed_req, isolated=isolated) + if install_req.editable and not parsed_req.requirement.startswith("file://"): + # ``Link.url`` is what is saved to the output file + # we set the url directly to undo the transformation in pip's Link class + file_link = FileLink(install_req.link.url) + file_link._url = parsed_req.requirement + install_req.link = file_link + yield install_req def create_wheel_cache(cache_dir: str, format_control: str | None = None) -> WheelCache: diff --git a/tests/constants.py b/tests/constants.py index 0a33a278a..3bbfa0bf8 100644 --- a/tests/constants.py +++ b/tests/constants.py @@ -5,3 +5,6 @@ TEST_DATA_PATH = os.path.join(os.path.dirname(__file__), "test_data") MINIMAL_WHEELS_PATH = os.path.join(TEST_DATA_PATH, "minimal_wheels") PACKAGES_PATH = os.path.join(TEST_DATA_PATH, "packages") +PACKAGES_RELATIVE_PATH = os.path.relpath( + PACKAGES_PATH, os.path.commonpath([os.getcwd(), PACKAGES_PATH]) +) diff --git a/tests/test_pip_compat.py b/tests/test_pip_compat.py new file mode 100644 index 000000000..583addfb9 --- /dev/null +++ b/tests/test_pip_compat.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +import os +import tempfile +from pathlib import Path, PurePosixPath + +import pytest + +from piptools._compat.pip_compat import parse_requirements +from piptools.repositories import PyPIRepository + +from .constants import PACKAGES_RELATIVE_PATH + + +def test_parse_requirements_preserve_editable_relative_path(tmp_path, repository): + test_package_path = str( + PurePosixPath(Path(PACKAGES_RELATIVE_PATH)) / "small_fake_a" + ) + requirements_in_path = str(tmp_path / "requirements.in") + + with open(requirements_in_path, "w") as requirements_in_file: + requirements_in_file.write(f"-e {test_package_path}") + + [install_requirement] = parse_requirements( + requirements_in_path, session=repository.session + ) + + assert install_requirement.link.url == test_package_path + assert install_requirement.link.file_path == test_package_path