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 consistent quiet and verbose modes for the pipx commands #1159

Merged
merged 2 commits into from
Dec 28, 2023
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.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## dev

- Add `--quiet` and `--verbose` options for the `pipx` subcommands
- [docs] Add Scoop installation instructions
- Add ability to install multiple packages at once

Expand Down
113 changes: 69 additions & 44 deletions src/pipx/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,16 +327,16 @@ def add_include_dependencies(parser: argparse.ArgumentParser) -> None:
parser.add_argument("--include-deps", help="Include apps of dependent packages", action="store_true")


def _add_install(subparsers: argparse._SubParsersAction) -> None:
def _add_install(subparsers: argparse._SubParsersAction, shared_parser: argparse.ArgumentParser) -> None:
p = subparsers.add_parser(
"install",
help="Install a package",
formatter_class=LineWrapRawTextHelpFormatter,
description=INSTALL_DESCRIPTION,
parents=[shared_parser],
)
p.add_argument("package_spec", help="package name(s) or pip installation spec(s)", nargs="*")
add_include_dependencies(p)
p.add_argument("--verbose", action="store_true")
p.add_argument(
"--force",
"-f",
Expand Down Expand Up @@ -368,11 +368,12 @@ def _add_install(subparsers: argparse._SubParsersAction) -> None:
add_pip_venv_args(p)


def _add_inject(subparsers, venv_completer: VenvCompleter) -> None:
def _add_inject(subparsers, venv_completer: VenvCompleter, shared_parser: argparse.ArgumentParser) -> None:
p = subparsers.add_parser(
"inject",
help="Install packages into an existing Virtual Environment",
description="Installs packages to an existing pipx-managed virtual environment.",
parents=[shared_parser],
)
p.add_argument(
"package",
Expand Down Expand Up @@ -400,19 +401,19 @@ def _add_inject(subparsers, venv_completer: VenvCompleter) -> None:
action="store_true",
help="Modify existing virtual environment and files in PIPX_BIN_DIR and PIPX_MAN_DIR",
)
p.add_argument("--verbose", action="store_true")
p.add_argument(
"--with-suffix",
action="store_true",
help="Add the suffix (if given) of the Virtual Environment to the packages to inject",
)


def _add_uninject(subparsers, venv_completer: VenvCompleter):
def _add_uninject(subparsers, venv_completer: VenvCompleter, shared_parser: argparse.ArgumentParser):
p = subparsers.add_parser(
"uninject",
help="Uninstall injected packages from an existing Virtual Environment",
description="Uninstalls injected packages from an existing pipx-managed virtual environment.",
parents=[shared_parser],
)
p.add_argument(
"package",
Expand All @@ -428,14 +429,14 @@ def _add_uninject(subparsers, venv_completer: VenvCompleter):
action="store_true",
help="Only uninstall the main injected package but leave its dependencies installed.",
)
p.add_argument("--verbose", action="store_true")


def _add_upgrade(subparsers, venv_completer: VenvCompleter) -> None:
def _add_upgrade(subparsers, venv_completer: VenvCompleter, shared_parser: argparse.ArgumentParser) -> None:
p = subparsers.add_parser(
"upgrade",
help="Upgrade a package",
description="Upgrade a package in a pipx-managed Virtual Environment by running 'pip install --upgrade PACKAGE'",
parents=[shared_parser],
)
p.add_argument("package").completer = venv_completer
p.add_argument(
Expand All @@ -450,14 +451,14 @@ def _add_upgrade(subparsers, venv_completer: VenvCompleter) -> None:
help="Modify existing virtual environment and files in PIPX_BIN_DIR and PIPX_MAN_DIR",
)
add_pip_venv_args(p)
p.add_argument("--verbose", action="store_true")


def _add_upgrade_all(subparsers: argparse._SubParsersAction) -> None:
def _add_upgrade_all(subparsers: argparse._SubParsersAction, shared_parser: argparse.ArgumentParser) -> None:
p = subparsers.add_parser(
"upgrade-all",
help="Upgrade all packages. Runs `pip install -U <pkgname>` for each package.",
description="Upgrades all packages within their virtual environments by running 'pip install --upgrade PACKAGE'",
parents=[shared_parser],
)
p.add_argument(
"--include-injected",
Expand All @@ -471,29 +472,28 @@ def _add_upgrade_all(subparsers: argparse._SubParsersAction) -> None:
action="store_true",
help="Modify existing virtual environment and files in PIPX_BIN_DIR and PIPX_MAN_DIR",
)
p.add_argument("--verbose", action="store_true")


def _add_uninstall(subparsers, venv_completer: VenvCompleter) -> None:
def _add_uninstall(subparsers, venv_completer: VenvCompleter, shared_parser: argparse.ArgumentParser) -> None:
p = subparsers.add_parser(
"uninstall",
help="Uninstall a package",
description="Uninstalls a pipx-managed Virtual Environment by deleting it and any files that point to its apps.",
parents=[shared_parser],
)
p.add_argument("package").completer = venv_completer
p.add_argument("--verbose", action="store_true")


def _add_uninstall_all(subparsers: argparse._SubParsersAction) -> None:
p = subparsers.add_parser(
def _add_uninstall_all(subparsers: argparse._SubParsersAction, shared_parser: argparse.ArgumentParser) -> None:
subparsers.add_parser(
"uninstall-all",
help="Uninstall all packages",
description="Uninstall all pipx-managed packages",
parents=[shared_parser],
)
p.add_argument("--verbose", action="store_true")


def _add_reinstall(subparsers, venv_completer: VenvCompleter) -> None:
def _add_reinstall(subparsers, venv_completer: VenvCompleter, shared_parser: argparse.ArgumentParser) -> None:
p = subparsers.add_parser(
"reinstall",
formatter_class=LineWrapRawTextHelpFormatter,
Expand All @@ -507,6 +507,7 @@ def _add_reinstall(subparsers, venv_completer: VenvCompleter) -> None:

"""
),
parents=[shared_parser],
)
p.add_argument("package").completer = venv_completer
p.add_argument(
Expand All @@ -518,10 +519,9 @@ def _add_reinstall(subparsers, venv_completer: VenvCompleter) -> None:
f"Requires Python {MINIMUM_PYTHON_VERSION} or above."
),
)
p.add_argument("--verbose", action="store_true")


def _add_reinstall_all(subparsers: argparse._SubParsersAction) -> None:
def _add_reinstall_all(subparsers: argparse._SubParsersAction, shared_parser: argparse.ArgumentParser) -> None:
p = subparsers.add_parser(
"reinstall-all",
formatter_class=LineWrapRawTextHelpFormatter,
Expand All @@ -537,6 +537,7 @@ def _add_reinstall_all(subparsers: argparse._SubParsersAction) -> None:

"""
),
parents=[shared_parser],
)
p.add_argument(
"--python",
Expand All @@ -548,14 +549,14 @@ def _add_reinstall_all(subparsers: argparse._SubParsersAction) -> None:
),
)
p.add_argument("--skip", nargs="+", default=[], help="skip these packages")
p.add_argument("--verbose", action="store_true")


def _add_list(subparsers: argparse._SubParsersAction) -> None:
def _add_list(subparsers: argparse._SubParsersAction, shared_parser: argparse.ArgumentParser) -> None:
p = subparsers.add_parser(
"list",
help="List installed packages",
description="List packages and apps installed with pipx",
parents=[shared_parser],
)
p.add_argument(
"--include-injected",
Expand All @@ -565,10 +566,9 @@ def _add_list(subparsers: argparse._SubParsersAction) -> None:
g = p.add_mutually_exclusive_group()
g.add_argument("--json", action="store_true", help="Output rich data in json format.")
g.add_argument("--short", action="store_true", help="List packages only.")
p.add_argument("--verbose", action="store_true")


def _add_run(subparsers: argparse._SubParsersAction) -> None:
def _add_run(subparsers: argparse._SubParsersAction, shared_parser: argparse.ArgumentParser) -> None:
p = subparsers.add_parser(
"run",
formatter_class=LineWrapRawTextHelpFormatter,
Expand All @@ -591,6 +591,7 @@ def _add_run(subparsers: argparse._SubParsersAction) -> None:
removed in the future. See https://github.com/cs01/pythonloc.
"""
),
parents=[shared_parser],
)
p.add_argument(
"--no-cache",
Expand All @@ -611,7 +612,6 @@ def _add_run(subparsers: argparse._SubParsersAction) -> None:
help="Require app to be run from local __pypackages__ directory",
)
p.add_argument("--spec", help=SPEC_HELP)
p.add_argument("--verbose", action="store_true")
p.add_argument(
"--python",
default=DEFAULT_PYTHON,
Expand All @@ -630,11 +630,12 @@ def _add_run(subparsers: argparse._SubParsersAction) -> None:
p.usage = re.sub(r"\.\.\.", "app ...", p.usage)


def _add_runpip(subparsers, venv_completer: VenvCompleter) -> None:
def _add_runpip(subparsers, venv_completer: VenvCompleter, shared_parser: argparse.ArgumentParser) -> None:
p = subparsers.add_parser(
"runpip",
help="Run pip in an existing pipx-managed Virtual Environment",
description="Run pip in an existing pipx-managed Virtual Environment",
parents=[shared_parser],
)
p.add_argument(
"package",
Expand All @@ -646,10 +647,9 @@ def _add_runpip(subparsers, venv_completer: VenvCompleter) -> None:
default=[],
help="Arguments to forward to pip command",
)
p.add_argument("--verbose", action="store_true")


def _add_ensurepath(subparsers: argparse._SubParsersAction) -> None:
def _add_ensurepath(subparsers: argparse._SubParsersAction, shared_parser: argparse.ArgumentParser) -> None:
p = subparsers.add_parser(
"ensurepath",
help=("Ensure directories necessary for pipx operation are in your " "PATH environment variable."),
Expand All @@ -660,6 +660,7 @@ def _add_ensurepath(subparsers: argparse._SubParsersAction) -> None:
"Note that running this may modify "
"your shell's configuration file(s) such as '~/.bashrc'."
),
parents=[shared_parser],
)
p.add_argument(
"--force",
Expand All @@ -672,7 +673,7 @@ def _add_ensurepath(subparsers: argparse._SubParsersAction) -> None:
)


def _add_environment(subparsers: argparse._SubParsersAction) -> None:
def _add_environment(subparsers: argparse._SubParsersAction, shared_parser: argparse.ArgumentParser) -> None:
p = subparsers.add_parser(
"environment",
formatter_class=LineWrapRawTextHelpFormatter,
Expand All @@ -688,8 +689,9 @@ def _add_environment(subparsers: argparse._SubParsersAction) -> None:
PIPX_LOG_DIR, PIPX_TRASH_DIR, PIPX_VENV_CACHEDIR, PIPX_DEFAULT_PYTHON, USE_EMOJI
"""
),
parents=[shared_parser],
)
p.add_argument("--value", "-v", metavar="VARIABLE", help="Print the value of the variable.")
p.add_argument("--value", "-V", metavar="VARIABLE", help="Print the value of the variable.")


def get_command_parser() -> argparse.ArgumentParser:
Expand All @@ -706,26 +708,42 @@ def get_command_parser() -> argparse.ArgumentParser:

subparsers = parser.add_subparsers(dest="command", description="Get help for commands with pipx COMMAND --help")

_add_install(subparsers)
_add_uninject(subparsers, completer_venvs.use)
_add_inject(subparsers, completer_venvs.use)
_add_upgrade(subparsers, completer_venvs.use)
_add_upgrade_all(subparsers)
_add_uninstall(subparsers, completer_venvs.use)
_add_uninstall_all(subparsers)
_add_reinstall(subparsers, completer_venvs.use)
_add_reinstall_all(subparsers)
_add_list(subparsers)
_add_run(subparsers)
_add_runpip(subparsers, completer_venvs.use)
_add_ensurepath(subparsers)
_add_environment(subparsers)
shared_parser = argparse.ArgumentParser(add_help=False)

shared_parser.add_argument(
"--quiet",
"-q",
action="count",
default=0,
help=(
"Give less output. May be used multiple times corresponding to the"
" WARNING, ERROR, and CRITICAL logging levels."
),
)

shared_parser.add_argument("--verbose", "-v", action="count", default=0, help=("Give more output."))

_add_install(subparsers, shared_parser)
_add_uninject(subparsers, completer_venvs.use, shared_parser)
_add_inject(subparsers, completer_venvs.use, shared_parser)
_add_upgrade(subparsers, completer_venvs.use, shared_parser)
_add_upgrade_all(subparsers, shared_parser)
_add_uninstall(subparsers, completer_venvs.use, shared_parser)
_add_uninstall_all(subparsers, shared_parser)
_add_reinstall(subparsers, completer_venvs.use, shared_parser)
_add_reinstall_all(subparsers, shared_parser)
_add_list(subparsers, shared_parser)
_add_run(subparsers, shared_parser)
_add_runpip(subparsers, completer_venvs.use, shared_parser)
_add_ensurepath(subparsers, shared_parser)
_add_environment(subparsers, shared_parser)

parser.add_argument("--version", action="store_true", help="Print version and exit")
subparsers.add_parser(
"completions",
help="Print instructions on enabling shell completions for pipx",
description="Print instructions on enabling shell completions for pipx",
parents=[shared_parser],
)
return parser

Expand Down Expand Up @@ -772,6 +790,11 @@ def setup_logging(verbose: bool) -> None:
pipx_str = bold(green("pipx >")) if sys.stdout.isatty() else "pipx >"
pipx.constants.pipx_log_file = setup_log_file()

# Determine logging level
level_number = max(0, 2 - verbose) * 10

level = logging.getLevelName(level_number)

# "incremental" is False so previous pytest tests don't accumulate handlers
logging_config = {
"version": 1,
Expand All @@ -796,7 +819,7 @@ def setup_logging(verbose: bool) -> None:
"stream": {
"class": "logging.StreamHandler",
"formatter": "stream_verbose" if verbose else "stream_nonverbose",
"level": "INFO" if verbose else "WARNING",
"level": level,
},
"file": {
"class": "logging.FileHandler",
Expand All @@ -817,7 +840,9 @@ def setup(args: argparse.Namespace) -> None:
print_version()
sys.exit(0)

setup_logging("verbose" in args and args.verbose)
verbose = args.verbose - args.quiet

setup_logging(verbose)

logger.debug(f"{time.strftime('%Y-%m-%d %H:%M:%S')}")
logger.debug(f"{' '.join(sys.argv)}")
Expand Down
2 changes: 1 addition & 1 deletion tests/test_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def test_list_json(pipx_temp_env, capsys):
assert not run_pipx_cli(["inject", "pylint", PKG["isort"]["spec"]])
captured = capsys.readouterr()

assert not run_pipx_cli(["list", "--json"])
assert not run_pipx_cli(["list", "-q", "--json"])
captured = capsys.readouterr()

assert not re.search(r"\S", captured.err)
Expand Down