Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/12480.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support per requirement options for editable installs.
48 changes: 25 additions & 23 deletions src/pip/_internal/req/req_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,16 @@
cmdoptions.config_settings,
]

SUPPORTED_OPTIONS_EDITABLE_REQ: List[Callable[..., optparse.Option]] = [
cmdoptions.config_settings,
]


# the 'dest' string values
SUPPORTED_OPTIONS_REQ_DEST = [str(o().dest) for o in SUPPORTED_OPTIONS_REQ]
SUPPORTED_OPTIONS_EDITABLE_REQ_DEST = [
str(o().dest) for o in SUPPORTED_OPTIONS_EDITABLE_REQ
]

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -178,31 +186,25 @@ def handle_requirement_line(

assert line.is_requirement

# get the options that apply to requirements
if line.is_editable:
# For editable requirements, we don't support per-requirement
# options, so just return the parsed requirement.
return ParsedRequirement(
requirement=line.requirement,
is_editable=line.is_editable,
comes_from=line_comes_from,
constraint=line.constraint,
)
supported_dest = SUPPORTED_OPTIONS_EDITABLE_REQ_DEST
else:
# get the options that apply to requirements
req_options = {}
for dest in SUPPORTED_OPTIONS_REQ_DEST:
if dest in line.opts.__dict__ and line.opts.__dict__[dest]:
req_options[dest] = line.opts.__dict__[dest]

line_source = f"line {line.lineno} of {line.filename}"
return ParsedRequirement(
requirement=line.requirement,
is_editable=line.is_editable,
comes_from=line_comes_from,
constraint=line.constraint,
options=req_options,
line_source=line_source,
)
supported_dest = SUPPORTED_OPTIONS_REQ_DEST
req_options = {}
for dest in supported_dest:
if dest in line.opts.__dict__ and line.opts.__dict__[dest]:
req_options[dest] = line.opts.__dict__[dest]

line_source = f"line {line.lineno} of {line.filename}"
return ParsedRequirement(
requirement=line.requirement,
is_editable=line.is_editable,
comes_from=line_comes_from,
constraint=line.constraint,
options=req_options,
line_source=line_source,
)


def handle_option_line(
Expand Down
42 changes: 39 additions & 3 deletions tests/functional/test_pep660.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):

def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
with open("log.txt", "a") as f:
print(":build_wheel called", file=f)
print(f":build_wheel called with config_settings={config_settings}", file=f)
return _build_wheel(wheel_directory, config_settings, metadata_directory)
"""

Expand All @@ -55,7 +55,7 @@ def prepare_metadata_for_build_editable(metadata_directory, config_settings=None

def build_editable(wheel_directory, config_settings=None, metadata_directory=None):
with open("log.txt", "a") as f:
print(":build_editable called", file=f)
print(f":build_editable called with config_settings={config_settings}", file=f)
return _build_wheel(wheel_directory, config_settings, metadata_directory)
"""
# fmt: on
Expand Down Expand Up @@ -88,6 +88,16 @@ def _assert_hook_called(project_dir: Path, hook: str) -> None:
assert f":{hook} called" in log, f"{hook} has not been called"


def _assert_hook_called_with_config_settings(
project_dir: Path, hook: str, config_settings: Dict[str, str]
) -> None:
log = project_dir.joinpath("log.txt").read_text()
assert f":{hook} called" in log, f"{hook} has not been called"
assert (
f":{hook} called with config_settings={config_settings}" in log
), f"{hook} has not been called with the expected config settings:\n{log}"


def _assert_hook_not_called(project_dir: Path, hook: str) -> None:
log = project_dir.joinpath("log.txt").read_text()
assert f":{hook} called" not in log, f"{hook} should not have been called"
Expand Down Expand Up @@ -119,9 +129,35 @@ def test_install_pep660_basic(tmpdir: Path, script: PipTestEnvironment) -> None:
"--no-build-isolation",
"--editable",
project_dir,
"--config-setting",
"x=y",
)
_assert_hook_called(project_dir, "prepare_metadata_for_build_editable")
_assert_hook_called_with_config_settings(project_dir, "build_editable", {"x": "y"})
assert (
result.test_env.site_packages.joinpath("project.egg-link")
not in result.files_created
), "a .egg-link file should not have been created"


def test_install_pep660_from_reqs_file(
tmpdir: Path, script: PipTestEnvironment
) -> None:
"""
Test with backend that supports build_editable.
"""
project_dir = _make_project(tmpdir, BACKEND_WITH_PEP660, with_setup_py=False)
reqs_file = tmpdir / "requirements.txt"
reqs_file.write_text(f"-e {project_dir.as_uri()} --config-setting x=y\n")
result = script.pip(
"install",
"--no-index",
"--no-build-isolation",
"-r",
reqs_file,
)
_assert_hook_called(project_dir, "prepare_metadata_for_build_editable")
_assert_hook_called(project_dir, "build_editable")
_assert_hook_called_with_config_settings(project_dir, "build_editable", {"x": "y"})
assert (
result.test_env.site_packages.joinpath("project.egg-link")
not in result.files_created
Expand Down