Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
13 changes: 9 additions & 4 deletions src/pip/_internal/commands/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,21 +113,26 @@ def transform_hits(hits: list[dict[str, str]]) -> list[TransformedHit]:
return list(packages.values())


def print_dist_installation_info(latest: str, dist: BaseDistribution | None) -> None:
def print_dist_installation_info(
latest: str, dist: BaseDistribution | None, show_on_quiet: bool = True
Comment thread
ychampion marked this conversation as resolved.
Outdated
) -> 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=show_on_quiet
)
else:
write_output("INSTALLED: %s", dist.version)
write_output("INSTALLED: %s", dist.version, show_on_quiet=show_on_quiet)
if parse_version(latest).pre:
write_output(
"LATEST: %s (pre-release; install"
" with `pip install --pre`)",
latest,
show_on_quiet=show_on_quiet,
)
else:
write_output("LATEST: %s", latest)
write_output("LATEST: %s", latest, show_on_quiet=show_on_quiet)


def get_installed_distribution(name: str) -> BaseDistribution | None:
Expand Down
1 change: 1 addition & 0 deletions src/pip/_internal/commands/show.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ def print_results(
Print the information from installed distributions found.
"""
results_printed = False

Comment thread
ychampion marked this conversation as resolved.
Outdated
for i, dist in enumerate(distributions):
results_printed = True
if i > 0:
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
Loading
Loading