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

Add --requirement option for pipx inject #1037

Closed
wants to merge 7 commits into from
Closed
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.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [docs] Add subsection to make README easier to read
- Add `pipx install --preinstall` to support preinstalling build requirements
- Pass `--no-input` to pip when output is not piped to parent stdout
- Add `--requirement` option for `pipx inject`

## 1.2.0

Expand Down
20 changes: 19 additions & 1 deletion src/pipx/commands/inject.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,32 @@ def inject(
include_apps: bool,
include_dependencies: bool,
force: bool,
requirement_files: List[str],
) -> ExitCode:
"""Returns pipx exit code."""
if not include_apps and include_dependencies:
raise PipxError(
"Cannot pass --include-deps if --include-apps is not passed as well"
)
if not package_specs and not requirement_files:
raise PipxError(
"Dependencies or requirements files must be provided. See `pipx inject -h` for more info"
)

if package_specs and requirement_files:
raise PipxError(
"Dependencies and --requirement/-r option cannot be passed at the same time"
)

if requirement_files:
packages_list = []
for file in requirement_files:
with open(Path(file), "r") as f:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of reading the files, should we just forward the path to pip to let it parse instead? Requirements files are technically a pip-specific format and parsing them ourselves seems like a big future source of code bloat down the road. Instead we should just redirect all the responsibilities to pip.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the question is that how should we determine package names?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need the package names? This is inject not install.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we need to forward those package names to pipx_metadata.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be viable to do a pip freeze before and after installation and record the diff?

This comment was marked as off-topic.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it looks like the installation report will also report the dependencies of a package, which are not specified in requirements.txt. Though we can exclude them from pipx_metadata by checking whether they are in the requires_dist field, I personally think this is too complicated

There’s a field requested for this, we can simply filter out entries with this set to true (meaning it’s from user input i.e. the requirements file).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, didn't notice that, thanks for pointing it out!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for the report to include options passed to pip (such as --pre, --index-url) in the requirements file?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think either is strictly needed since pipx would install those packages with the exact URL (which the report contains), and the flag is irrelevant in this case.

for line in f.readlines():
packages_list.append(line.rstrip())

all_success = True
for dep in package_specs:
for dep in package_specs or packages_list:
all_success &= inject_dep(
venv_dir,
None,
Expand Down
18 changes: 16 additions & 2 deletions src/pipx/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ def run_pipx_command(args: argparse.Namespace) -> ExitCode: # noqa: C901
None,
args.dependencies,
pip_args,
requirement_files=args.requirement,
verbose=verbose,
include_apps=args.include_apps,
include_dependencies=args.include_deps,
Expand Down Expand Up @@ -374,8 +375,11 @@ def _add_inject(subparsers, venv_completer: VenvCompleter) -> None:
).completer = venv_completer
p.add_argument(
"dependencies",
nargs="+",
help="the packages to inject into the Virtual Environment--either package name or pip package spec",
nargs="*",
help=(
"The packages to inject into the Virtual Environment--either package name or pip package spec. "
"If --requirement or -r is passed, a requirements file is required instead of packages."
),
)
p.add_argument(
"--include-apps",
Expand All @@ -390,6 +394,16 @@ def _add_inject(subparsers, venv_completer: VenvCompleter) -> None:
action="store_true",
help="Modify existing virtual environment and files in PIPX_BIN_DIR",
)
p.add_argument(
"--requirement",
"-r",
metavar="FILE",
action="append",
help=(
"Inject packages from the given requirements file. "
"Once this option is passed, dependencies passing from the command line will be omitted"
),
)
p.add_argument("--verbose", action="store_true")


Expand Down
22 changes: 22 additions & 0 deletions tests/test_inject.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import textwrap

import pytest # type: ignore

from helpers import mock_legacy_venv, run_pipx_cli
Expand Down Expand Up @@ -65,3 +67,23 @@ def test_inject_include_apps(pipx_temp_env, capsys, with_suffix):
"--include-apps",
]
)


def test_inject_with_req_file(pipx_temp_env, capsys, tmp_path):
req_file = tmp_path / "requirements.txt"
req_file.write_text(
textwrap.dedent(
f"""
{PKG["black"]["spec"]}
{PKG["nox"]["spec"]}
{PKG["pylint"]["spec"]}
"""
).strip()
)
assert not run_pipx_cli(["install", "pycowsay"])
assert not run_pipx_cli(["inject", "pycowsay", "--requirement", str(req_file)])
assert run_pipx_cli(
["inject", "pycowsay", PKG["black"]["spec"], "--requirement", str(req_file)]
)
captured = capsys.readouterr()
assert "cannot be passed at the same time" in captured.err
Loading