Skip to content
Open
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/13018.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Preserve primary output from display commands when ``--quiet`` is used.
14 changes: 7 additions & 7 deletions src/pip/_internal/commands/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from pip._internal.exceptions import CommandError, PipError
from pip._internal.utils import filesystem
from pip._internal.utils.logging import getLogger
from pip._internal.utils.misc import format_size
from pip._internal.utils.misc import format_size, write_output

logger = getLogger(__name__)

Expand Down Expand Up @@ -88,7 +88,7 @@ def get_cache_dir(self, options: Values, args: list[str]) -> None:
if args:
raise CommandError("Too many arguments")

logger.info(options.cache_dir)
write_output(options.cache_dir)

def get_cache_info(self, options: Values, args: list[str]) -> None:
if args:
Expand Down Expand Up @@ -128,7 +128,7 @@ def get_cache_info(self, options: Values, args: list[str]) -> None:
.strip()
)

logger.info(message)
write_output(message)

def list_cache_items(self, options: Values, args: list[str]) -> None:
if len(args) > 1:
Expand All @@ -147,20 +147,20 @@ def list_cache_items(self, options: Values, args: list[str]) -> None:

def format_for_human(self, files: list[str]) -> None:
if not files:
logger.info("No locally built wheels cached.")
write_output("No locally built wheels cached.")
return

results = []
for filename in files:
wheel = os.path.basename(filename)
size = filesystem.format_file_size(filename)
results.append(f" - {wheel} ({size})")
logger.info("Cache contents:\n")
logger.info("\n".join(sorted(results)))
write_output("Cache contents:\n")
write_output("\n".join(sorted(results)))

def format_for_abspath(self, files: list[str]) -> None:
if files:
logger.info("\n".join(sorted(files)))
write_output("\n".join(sorted(files)))

def remove_cache_items(self, options: Values, args: list[str]) -> None:
if len(args) > 1:
Expand Down
16 changes: 8 additions & 8 deletions src/pip/_internal/commands/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@
from pip._internal.metadata import get_environment
from pip._internal.utils.compat import get_locale_encoding, open_text_resource
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import get_pip_version
from pip._internal.utils.misc import get_pip_version, write_output

logger = logging.getLogger(__name__)


def show_value(name: str, value: Any) -> None:
logger.info("%s: %s", name, value)
write_output("%s: %s", name, value)


def show_sys_implementation() -> None:
logger.info("sys.implementation:")
write_output("sys.implementation:")
implementation_name = sys.implementation.name
with indent_log():
show_value("name", implementation_name)
Expand Down Expand Up @@ -91,11 +91,11 @@ def show_actual_vendor_versions(vendor_txt_versions: dict[str, str]) -> None:
" (CONFLICT: vendor.txt suggests version should"
f" be {expected_version})"
)
logger.info("%s==%s%s", module_name, actual_version, extra_message)
write_output("%s==%s%s", module_name, actual_version, extra_message)


def show_vendor_versions() -> None:
logger.info("vendored library versions:")
write_output("vendored library versions:")

vendor_txt_versions = create_vendor_txt_map()
with indent_log():
Expand All @@ -115,7 +115,7 @@ def show_tags(options: Values) -> None:
suffix = f" (target: {formatted_target})"

msg = f"Compatible tags: {len(tags)}{suffix}"
logger.info(msg)
write_output(msg)

if options.verbose < 1 and len(tags) > tag_limit:
tags_limited = True
Expand All @@ -125,11 +125,11 @@ def show_tags(options: Values) -> None:

with indent_log():
for tag in tags:
logger.info(str(tag))
write_output(str(tag))

if tags_limited:
msg = f"...\n[First {tag_limit} tags shown. Pass --verbose to show all.]"
logger.info(msg)
write_output(msg)


def ca_bundle_info(config: Configuration) -> str:
Expand Down
6 changes: 5 additions & 1 deletion src/pip/_internal/commands/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ def run(self, options: Values, args: list[str]) -> int:
downloaded.append(req.name)

if downloaded:
write_output("Successfully downloaded %s", " ".join(downloaded))
write_output(
"Successfully downloaded %s",
" ".join(downloaded),
show_on_quiet=False,
)

return SUCCESS
6 changes: 5 additions & 1 deletion src/pip/_internal/commands/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ def run(self, options: Values, args: list[str]) -> int:
)

try:
# 'pip help' with no args is handled by pip.__init__.parseopt()
cmd_name = args[0] # the command we need help for
except IndexError:
# 'pip help' with no args is handled by parse_command(), but
# command-level options like 'pip help --quiet' reach this point.
from pip._internal.cli.main_parser import create_main_parser

create_main_parser().print_help()
return SUCCESS

if cmd_name not in commands_dict:
Expand Down
3 changes: 2 additions & 1 deletion src/pip/_internal/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,7 @@ def run(self, options: Values, args: list[str]) -> int:
write_output(
"Would install %s",
" ".join("-".join(item) for item in would_install_items),
show_on_quiet=False,
)
return SUCCESS

Expand Down Expand Up @@ -580,7 +581,7 @@ def run(self, options: Values, args: list[str]) -> int:
resolver_variant=self.determine_resolver_variant(options),
)
if summary := installed_packages_summary(installed, env):
write_output(summary)
write_output(summary, show_on_quiet=False)
except OSError as error:
show_traceback = self.verbosity >= 1

Expand Down
7 changes: 4 additions & 3 deletions src/pip/_internal/commands/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,17 +117,18 @@ def print_dist_installation_info(latest: str, dist: BaseDistribution | None) ->
if dist is not None:
with indent_log():
if dist.version == latest:
write_output("INSTALLED: %s (latest)", dist.version)
write_output("INSTALLED: %s (latest)", dist.version, show_on_quiet=True)
else:
write_output("INSTALLED: %s", dist.version)
write_output("INSTALLED: %s", dist.version, show_on_quiet=True)
if parse_version(latest).pre:
write_output(
"LATEST: %s (pre-release; install"
" with `pip install --pre`)",
latest,
show_on_quiet=True,
)
else:
write_output("LATEST: %s", latest)
write_output("LATEST: %s", latest, show_on_quiet=True)


def get_installed_distribution(name: str) -> BaseDistribution | None:
Expand Down
36 changes: 36 additions & 0 deletions src/pip/_internal/utils/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,42 @@ def handleError(self, record: logging.LogRecord) -> None:
return super().handleError(record)


def _format_output_message(msg: Any, *args: Any) -> str:
# Match logging's %-style argument handling; write_output passes the
# same message and args to logger.info when the console can show INFO.
if args:
return str(msg) % args
return str(msg)


def should_directly_write_output(logger: logging.Logger) -> bool:
if not logger.isEnabledFor(logging.INFO):
return True

has_console_handler = False
for handler in logging.getLogger().handlers:
if not isinstance(handler, RichPipStreamHandler):
continue
has_console_handler = True
if handler.level <= logging.INFO:
return False

return has_console_handler


def write_output_direct(msg: Any, *args: Any) -> None:
text = _format_output_message(msg, *args)
indentation = get_indentation()
if indentation:
prefix = " " * indentation
text = "".join(prefix + line for line in text.splitlines(True))

try:
get_console().print(text, overflow="ignore", crop=False, highlight=False)
except BrokenPipeError as exc:
raise BrokenStdoutLoggingError() from exc


class BetterRotatingFileHandler(logging.handlers.RotatingFileHandler):
def _open(self) -> TextIOWrapper:
ensure_dir(os.path.dirname(self.baseFilename))
Expand Down
12 changes: 11 additions & 1 deletion src/pip/_internal/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,17 @@ def is_local(path: str) -> bool:
return path.startswith(normalize_path(sys.prefix))


def write_output(msg: Any, *args: Any) -> None:
def write_output(msg: Any, *args: Any, show_on_quiet: bool = True) -> None:
if show_on_quiet:
from pip._internal.utils.logging import (
should_directly_write_output,
write_output_direct,
)

if should_directly_write_output(logger):
write_output_direct(msg, *args)
return

logger.info(msg, *args)


Expand Down
7 changes: 7 additions & 0 deletions tests/functional/test_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ def test_cache_dir(script: PipTestEnvironment, cache_dir: str) -> None:
assert os.path.normcase(cache_dir) == result.stdout.strip()


def test_cache_dir_quiet(script: PipTestEnvironment, cache_dir: str) -> None:
Comment thread
ychampion marked this conversation as resolved.
"""Test that quiet mode does not suppress the cache dir output."""
result = script.pip("cache", "dir", "--quiet")

assert os.path.normcase(cache_dir) == result.stdout.strip()


def test_cache_dir_too_many_args(script: PipTestEnvironment, cache_dir: str) -> None:
result = script.pip("cache", "dir", "aaa", expect_error=True)

Expand Down
9 changes: 9 additions & 0 deletions tests/functional/test_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ def test_basic_check_clean(script: PipTestEnvironment) -> None:
assert result.returncode == 0


def test_basic_check_clean_quiet(script: PipTestEnvironment) -> None:
"""Quiet mode should not suppress check's dependency report."""
result = script.pip("check", "--quiet")

expected_lines = ("No broken requirements found.",)
assert matches_expected_lines(result.stdout, expected_lines)
assert result.returncode == 0


def test_basic_check_missing_dependency(script: PipTestEnvironment) -> None:
# Setup a small project
pkga_path = create_test_package_with_setup(
Expand Down
3 changes: 3 additions & 0 deletions tests/functional/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ def test_basic_modification_pipeline(self, script: PipTestEnvironment) -> None:
result = script.pip("config", "get", "test.blah")
assert result.stdout.strip() == "1"

result = script.pip("config", "get", "test.blah", "--quiet")
assert result.stdout.strip() == "1"

script.pip("config", "unset", "test.blah")
script.pip("config", "get", "test.blah", expect_error=True)

Expand Down
10 changes: 10 additions & 0 deletions tests/functional/test_debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ def test_debug(script: PipTestEnvironment, expected_text: str) -> None:
assert expected_text in stdout


def test_debug_quiet(script: PipTestEnvironment) -> None:
"""
Check that quiet mode does not suppress debug's primary output.
"""
result = script.pip("debug", "--quiet", allow_stderr_warning=True)

assert "pip version: " in result.stdout
assert "Compatible tags: " in result.stdout


def test_debug__library_versions(script: PipTestEnvironment) -> None:
"""
Check the library versions normal output.
Expand Down
10 changes: 10 additions & 0 deletions tests/functional/test_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ def test_basic_hash(script: PipTestEnvironment, tmpdir: Path) -> None:
assert expected in str(result)


def test_basic_hash_quiet(script: PipTestEnvironment, tmpdir: Path) -> None:
"""Quiet mode should not suppress the generated hash."""
expected = (
"--hash=sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425"
"e73043362938b9824"
)
result = script.pip("hash", "--quiet", _hello_file(tmpdir))
assert expected in str(result)


def test_good_algo_option(script: PipTestEnvironment, tmpdir: Path) -> None:
"""Make sure the -a option works."""
expected = (
Expand Down
10 changes: 10 additions & 0 deletions tests/functional/test_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ def test_help_command_should_exit_status_ok_when_no_cmd_is_specified(
assert result.returncode == SUCCESS


def test_help_command_quiet_shows_general_help(script: PipTestEnvironment) -> None:
"""
Test that quiet mode does not suppress the no-argument help command.
"""
result = script.pip("help", "--quiet")
assert result.returncode == SUCCESS
assert "Usage:" in result.stdout
assert "Commands:" in result.stdout


def test_help_command_should_exit_status_error_when_cmd_does_not_exist(
script: PipTestEnvironment,
) -> None:
Expand Down
20 changes: 20 additions & 0 deletions tests/functional/test_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,26 @@ def test_index_versions_all_releases_for_package(script: PipTestEnvironment) ->
assert "2.0a1" in result.stdout


def test_index_versions_quiet_shows_versions(script: PipTestEnvironment) -> None:
"""Test that quiet mode does not suppress index versions output."""
wheelhouse_path = script.scratch_path / "wheelhouse"
wheelhouse_path.mkdir()
make_wheel("simple", "1.0").save_to_dir(wheelhouse_path)

result = script.pip(
"index",
"versions",
"--quiet",
"--no-index",
"--find-links",
wheelhouse_path,
"simple",
)

assert "simple (1.0)" in result.stdout
assert "Available versions: 1.0" in result.stdout


def test_index_versions_only_final_for_package(script: PipTestEnvironment) -> None:
"""Test that --only-final filters prereleases for specific package."""
# Create fake local package index with prerelease
Expand Down
9 changes: 9 additions & 0 deletions tests/functional/test_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ def test_basic_list(simple_script: PipTestEnvironment) -> None:
assert "simple2 3.0" in result.stdout, str(result)


def test_basic_list_quiet(simple_script: PipTestEnvironment) -> None:
"""
Test that quiet mode does not suppress the list command's package output.
"""
result = simple_script.pip("list", "--quiet")
assert "simple 1.0" in result.stdout, str(result)
assert "simple2 3.0" in result.stdout, str(result)


def test_verbose_flag(simple_script: PipTestEnvironment) -> None:
"""
Test the list command with the '-v' option
Expand Down
11 changes: 11 additions & 0 deletions tests/functional/test_show.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ def test_basic_show(script: PipTestEnvironment) -> None:
assert "Requires: " in lines


def test_basic_show_quiet(script: PipTestEnvironment) -> None:
"""
Test that quiet mode does not suppress the show command's package output.
"""
result = script.pip("show", "--quiet", "pip")
lines = result.stdout.splitlines()
assert "Name: pip" in lines
assert f"Version: {__version__}" in lines
assert any(line.startswith("Location: ") for line in lines)


def test_show_without_files_does_not_read_installed_files(
monkeypatch: pytest.MonkeyPatch,
) -> None:
Expand Down
Loading