Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserve relative paths for editable dependencies in output #2087

Merged
merged 11 commits into from
May 10, 2024
18 changes: 17 additions & 1 deletion piptools/_compat/pip_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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
macro1 marked this conversation as resolved.
Show resolved Hide resolved
# 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:
Expand Down
3 changes: 3 additions & 0 deletions tests/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
)
32 changes: 32 additions & 0 deletions tests/test_pip_compat.py
chrysle marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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(repository):
macro1 marked this conversation as resolved.
Show resolved Hide resolved
test_package_path = str(
PurePosixPath(Path(PACKAGES_RELATIVE_PATH)) / "small_fake_a"
)

infile = tempfile.NamedTemporaryFile("w", delete=False)
macro1 marked this conversation as resolved.
Show resolved Hide resolved
try:
infile.write(f"-e {test_package_path}")
infile.close()

[install_requirement] = parse_requirements(
infile.name, session=repository.session
)
finally:
os.unlink(infile.name)

assert install_requirement.link.url == test_package_path
assert install_requirement.link.file_path == test_package_path
Loading