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 changelog.d/1389.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix passing constraints file path into `pipx install` operation via `pip` args
4 changes: 3 additions & 1 deletion src/pipx/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,9 @@ def get_pip_args(parsed_args: Dict[str, str]) -> List[str]:
pip_args += ["--index-url", parsed_args["index_url"]]

if parsed_args.get("pip_args"):
pip_args += shlex.split(parsed_args.get("pip_args", ""), posix=not WINDOWS)
# Stripping the single quote that can be parsed from several shells
pip_args_striped = parsed_args["pip_args"].strip("'")
pip_args += shlex.split(pip_args_striped, posix=not WINDOWS)

# make sure --editable is last because it needs to be right before
# package specification
Expand Down
22 changes: 21 additions & 1 deletion src/pipx/package_specifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,27 @@ def parse_specifier_for_install(package_spec: str, pip_args: List[str]) -> Tuple
)
pip_args.remove("--editable")

return (package_or_url, pip_args)
for index, option in enumerate(pip_args):
if not option.startswith(("-c", "--constraint")):
continue

if option in ("-c", "--constraint"):
argument_index = index + 1
if argument_index < len(pip_args):
constraints_file = pip_args[argument_index]
pip_args[argument_index] = str(Path(constraints_file).expanduser().resolve())

else: # option == "--constraint=some_path"
option_list = option.split("=")

if len(option_list) == 2:
key, value = option_list
value_path = Path(value).expanduser().resolve()
pip_args[index] = f"{key}={value_path}"

break

return package_or_url, pip_args


def parse_specifier_for_metadata(package_spec: str) -> str:
Expand Down
33 changes: 33 additions & 0 deletions tests/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,39 @@ def test_pip_args_with_windows_path(pipx_temp_env, capsys):
assert r"D:\\TEST\\DIR" in captured.err


@pytest.mark.parametrize("constraint_flag", ["-c ", "--constraint ", "--constraint="])
def test_pip_args_with_constraint_relative_path(constraint_flag, pipx_temp_env, tmp_path, caplog):
constraint_file_name = "constraints.txt"
package_name = "ipython"
package_version = "8.23.0"

os.chdir(tmp_path)
constraints_file = tmp_path / constraint_file_name
constraints_file.write_text(f"{package_name}!={package_version}")
constraints_file.touch()

assert not run_pipx_cli(["install", f"--pip-args='{constraint_flag}{constraint_file_name}'", package_name])

assert f"{constraint_flag}{constraints_file}" in caplog.text

subprocess_package_version = subprocess.run([package_name, "--version"], capture_output=True, text=True, check=False)
subprocess_package_version_output = subprocess_package_version.stdout.strip()
assert subprocess_package_version_output != package_version


@pytest.mark.parametrize("constraint_flag", ["-c ", "--constraint ", "--constraint="])
def test_pip_args_with_wrong_constraint_fail(constraint_flag, pipx_ultra_temp_env, tmp_path, capsys):
constraint_file_name = "constraints.txt"
os.chdir(tmp_path)

assert run_pipx_cli(["install", f"--pip-args='{constraint_flag}{constraint_file_name}'", "pycowsay"])

assert (
f"ERROR: Could not open requirements file: [Errno 2] No such file or directory: '{constraint_file_name}'"
in capsys.readouterr().err
)


def test_install_suffix(pipx_temp_env, capsys):
name = "pbr"

Expand Down