From 68a7dc9b85a3598fea1aba9eb73bfa2fc796eb40 Mon Sep 17 00:00:00 2001 From: Ken Odegard Date: Fri, 25 Aug 2023 06:18:02 -0400 Subject: [PATCH] Inject new detailed verbosity level (#12985) --- conda/base/context.py | 66 ++++++++++++++++++++-- conda/cli/conda_argparse.py | 73 ++++++++++++++----------- conda/cli/main.py | 38 +++++++++---- conda/cli/main_clean.py | 36 ++++++------ conda/cli/main_info.py | 4 +- conda/cli/main_search.py | 6 +- conda/core/initialize.py | 32 +++++------ conda/core/link.py | 8 +-- conda/core/package_cache_data.py | 6 +- conda/core/solve.py | 4 +- conda/exception_handler.py | 3 +- conda/exceptions.py | 9 +-- conda/gateways/logging.py | 38 +++++++++---- conda/gateways/subprocess.py | 2 +- conda/plugins/subcommands/doctor/cli.py | 16 +++--- conda/testing/__init__.py | 2 +- conda/testing/integration.py | 2 +- conda_env/cli/main.py | 2 +- conda_env/installers/pip.py | 2 +- news/12985-verbosity | 19 +++++++ tests/cli/test_main_run.py | 2 +- 21 files changed, 240 insertions(+), 130 deletions(-) create mode 100644 news/12985-verbosity diff --git a/conda/base/context.py b/conda/base/context.py index 828ec540d61..53d4795e641 100644 --- a/conda/base/context.py +++ b/conda/base/context.py @@ -7,6 +7,7 @@ """ from __future__ import annotations +import logging import os import platform import struct @@ -15,7 +16,6 @@ from errno import ENOENT from functools import lru_cache from itertools import chain -from logging import getLogger from os.path import abspath, expanduser, isdir, isfile, join from os.path import split as path_split @@ -45,6 +45,7 @@ from ..common.path import expand, paths_equal from ..common.url import has_scheme, path_to_url, split_scheme_auth_token from ..deprecations import deprecated +from ..gateways.logging import TRACE from .constants import ( APP_NAME, DEFAULT_AGGRESSIVE_UPDATE_PACKAGES, @@ -81,7 +82,7 @@ else: raise -log = getLogger(__name__) +log = logging.getLogger(__name__) _platform_map = { "freebsd13": "freebsd", @@ -378,7 +379,8 @@ class Context(Configuration): always_yes = ParameterLoader( PrimitiveParameter(None, element_type=(bool, NoneType)), aliases=("yes",) ) - debug = ParameterLoader(PrimitiveParameter(False)) + _debug = ParameterLoader(PrimitiveParameter(False), aliases=["debug"]) + _trace = ParameterLoader(PrimitiveParameter(False), aliases=["trace"]) dev = ParameterLoader(PrimitiveParameter(False)) dry_run = ParameterLoader(PrimitiveParameter(False)) error_upload_url = ParameterLoader(PrimitiveParameter(ERROR_UPLOAD_URL)) @@ -941,8 +943,61 @@ def binstar_upload(self): return self.anaconda_upload @property - def verbosity(self): - return 2 if self.debug else self._verbosity + def trace(self) -> bool: + """Alias for context.verbosity >=4.""" + return self.verbosity >= 4 + + @property + def debug(self) -> bool: + """Alias for context.verbosity >=3.""" + return self.verbosity >= 3 + + @property + def info(self) -> bool: + """Alias for context.verbosity >=2.""" + return self.verbosity >= 2 + + @property + def verbose(self) -> bool: + """Alias for context.verbosity >=1.""" + return self.verbosity >= 1 + + @property + def verbosity(self) -> int: + """Verbosity level. + + For cleaner and readable code it is preferable to use the following alias properties: + context.trace + context.debug + context.info + context.verbose + context.log_level + """ + # 0 → logging.WARNING, standard output + # -v = 1 → logging.WARNING, detailed output + # -vv = 2 → logging.INFO + # --debug = -vvv = 3 → logging.DEBUG + # --trace = -vvvv = 4 → conda.gateways.logging.TRACE + if self._trace: + return 4 + elif self._debug: + return 3 + else: + return self._verbosity + + @property + def log_level(self) -> int: + """Map context.verbosity to logging level.""" + if 4 < self.verbosity: + return logging.NOTSET # 0 + elif 3 < self.verbosity <= 4: + return TRACE # 5 + elif 2 < self.verbosity <= 3: + return logging.DEBUG # 10 + elif 1 < self.verbosity <= 2: + return logging.INFO # 20 + else: + return logging.WARNING # 30 @memoizedproperty def user_agent(self): @@ -1156,6 +1211,7 @@ def category_map(self): "add_pip_as_python_dependency", "channel_settings", "debug", + "trace", "dev", "default_python", "enable_private_envs", diff --git a/conda/cli/conda_argparse.py b/conda/cli/conda_argparse.py index 794a436e207..675956d78d4 100644 --- a/conda/cli/conda_argparse.py +++ b/conda/cli/conda_argparse.py @@ -8,7 +8,12 @@ import sys from argparse import REMAINDER, SUPPRESS, Action from argparse import ArgumentParser as ArgumentParserBase -from argparse import RawDescriptionHelpFormatter, _CountAction, _HelpAction +from argparse import ( + RawDescriptionHelpFormatter, + _CountAction, + _HelpAction, + _StoreTrueAction, +) from importlib import import_module from logging import getLogger from os.path import abspath, expanduser, join @@ -65,12 +70,7 @@ def generate_pre_parser(**kwargs) -> ArgumentParser: **kwargs, ) - pre_parser.add_argument( - "--debug", - action="store_true", - default=NULL, - help=SUPPRESS, - ) + add_parser_verbose(pre_parser) pre_parser.add_argument( "--json", action="store_true", @@ -471,8 +471,13 @@ def configure_parser_info(sub_parsers): p.add_argument( "-a", "--all", - action="store_true", - help="Show all information.", + dest="verbosity", + action=deprecated.action( + "24.3", + "24.9", + _StoreTrueAction, + addendum="Use `--verbose` instead.", + ), ) p.add_argument( "--base", @@ -1322,14 +1327,7 @@ def configure_parser_run(sub_parsers): ) add_parser_prefix(p) - p.add_argument( - "-v", - "--verbose", - action=NullCountAction, - help="Use once for info, twice for debug, three times for trace.", - dest="verbosity", - default=NULL, - ) + add_parser_verbose(p) p.add_argument( "--dev", @@ -1744,26 +1742,13 @@ def add_parser_json(p): output_and_prompt_options = p.add_argument_group( "Output, Prompt, and Flow Control Options" ) - output_and_prompt_options.add_argument( - "--debug", - action="store_true", - default=NULL, - help=SUPPRESS, - ) output_and_prompt_options.add_argument( "--json", action="store_true", default=NULL, help="Report all output as json. Suitable for using conda programmatically.", ) - output_and_prompt_options.add_argument( - "-v", - "--verbose", - action=NullCountAction, - help="Can be used multiple times. Once for INFO, twice for DEBUG, three times for TRACE.", - dest="verbosity", - default=NULL, - ) + add_parser_verbose(output_and_prompt_options) output_and_prompt_options.add_argument( "-q", "--quiet", @@ -2066,3 +2051,29 @@ def add_parser_default_packages(p): action="store_true", help="Ignore create_default_packages in the .condarc file.", ) + + +def add_parser_verbose(parser): + parser.add_argument( + "-v", + "--verbose", + action=NullCountAction, + help=( + "Can be used multiple times. Once for detailed output, twice for INFO logging, " + "thrice for DEBUG logging, four times for TRACE logging." + ), + dest="verbosity", + default=NULL, + ) + parser.add_argument( + "--debug", + action="store_true", + help=SUPPRESS, + default=NULL, + ) + parser.add_argument( + "--trace", + action="store_true", + help=SUPPRESS, + default=NULL, + ) diff --git a/conda/cli/main.py b/conda/cli/main.py index ced39b5fe28..6156ec1690d 100644 --- a/conda/cli/main.py +++ b/conda/cli/main.py @@ -3,21 +3,30 @@ """Entry point for all conda subcommands.""" import sys +from ..deprecations import deprecated -def init_loggers(context=None): - from logging import CRITICAL, getLogger - from ..gateways.logging import initialize_logging, set_verbosity +@deprecated.argument( + "24.3", + "24.9", + "context", + addendum="The context is a global state, no need to pass it around.", +) +def init_loggers(): + import logging + + from ..base.context import context + from ..gateways.logging import initialize_logging, set_log_level initialize_logging() - if context and context.json: - # Silence logging info to avoid interfering with JSON output + + # silence logging info to avoid interfering with JSON output + if context.json: for logger in ("conda.stdout.verbose", "conda.stdoutlog", "conda.stderrlog"): - getLogger(logger).setLevel(CRITICAL + 1) + logging.getLogger(logger).setLevel(logging.CRITICAL + 10) - if context: - if context.verbosity: - set_verbosity(context.verbosity) + # set log_level + set_log_level(context.log_level) def generate_parser(*args, **kwargs): @@ -42,7 +51,12 @@ def main_subshell(*args, post_parse_hook=None, **kwargs): pre_args, _ = pre_parser.parse_known_args(args) # the arguments that we want to pass to the main parser later on - override_args = {"json": pre_args.json, "debug": pre_args.debug} + override_args = { + "json": pre_args.json, + "debug": pre_args.debug, + "trace": pre_args.trace, + "verbosity": pre_args.verbosity, + } context.__init__(argparse_args=pre_args) if context.no_plugins: @@ -55,7 +69,7 @@ def main_subshell(*args, post_parse_hook=None, **kwargs): args = parser.parse_args(args, override_args=override_args, namespace=pre_args) context.__init__(argparse_args=args) - init_loggers(context) + init_loggers() # used with main_pip.py if post_parse_hook: @@ -76,7 +90,7 @@ def main_sourced(shell, *args, **kwargs): from ..base.context import context context.__init__() - init_loggers(context) + init_loggers() from ..activate import _build_activator_cls diff --git a/conda/cli/main_clean.py b/conda/cli/main_clean.py index 344b845784a..8389cb679e1 100644 --- a/conda/cli/main_clean.py +++ b/conda/cli/main_clean.py @@ -51,18 +51,18 @@ def _get_total_size(pkg_sizes: dict[str, dict[str, int]]) -> int: return sum(sum(pkgs.values()) for pkgs in pkg_sizes.values()) -def _rm_rf(*parts: str, verbose: bool, verbosity: bool) -> None: +def _rm_rf(*parts: str, quiet: bool, verbose: bool) -> None: from ..gateways.disk.delete import rm_rf path = join(*parts) try: if rm_rf(path): - if verbose and verbosity: + if not quiet and verbose: print(f"Removed {path}") - elif verbose: + elif not quiet: print(f"WARNING: cannot remove, file permissions: {path}") except OSError as e: - if verbose: + if not quiet: print(f"WARNING: cannot remove, file permissions: {path}\n{e!r}") else: log.info("%r", e) @@ -132,25 +132,25 @@ def rm_pkgs( total_size: int, pkg_sizes: dict[str, dict[str, int]], *, + quiet: bool, verbose: bool, - verbosity: bool, dry_run: bool, name: str, ) -> None: from ..utils import human_bytes from .common import confirm_yn - if verbose and warnings: + if not quiet and warnings: for warning in warnings: print(warning) if not any(pkgs for pkgs in pkg_sizes.values()): - if verbose: + if not quiet: print(f"There are no unused {name} to remove.") return - if verbose: - if verbosity: + if not quiet: + if verbose: print(f"Will remove the following {name}:") for pkgs_dir, pkgs in pkg_sizes.items(): print(f" {pkgs_dir}") @@ -172,7 +172,7 @@ def rm_pkgs( for pkgs_dir, pkgs in pkg_sizes.items(): for pkg in pkgs: - _rm_rf(pkgs_dir, pkg, verbose=verbose, verbosity=verbosity) + _rm_rf(pkgs_dir, pkg, quiet=quiet, verbose=verbose) def find_index_cache() -> list[str]: @@ -226,20 +226,20 @@ def find_logfiles() -> list[str]: def rm_items( items: list[str], *, + quiet: bool, verbose: bool, - verbosity: bool, dry_run: bool, name: str, ) -> None: from .common import confirm_yn if not items: - if verbose: + if not quiet: print(f"There are no {name} to remove.") return - if verbose: - if verbosity: + if not quiet: + if verbose: print(f"Will remove the following {name}:") for item in items: print(f" - {item}") @@ -253,15 +253,15 @@ def rm_items( confirm_yn() for item in items: - _rm_rf(item, verbose=verbose, verbosity=verbosity) + _rm_rf(item, quiet=quiet, verbose=verbose) def _execute(args, parser): json_result = {"success": True} kwargs = { - "verbose": not (context.json or context.quiet), - "verbosity": args.verbosity, - "dry_run": args.dry_run, + "quiet": context.json or context.quiet, + "verbose": context.verbose, + "dry_run": context.dry_run, } if args.force_pkgs_dirs: diff --git a/conda/cli/main_info.py b/conda/cli/main_info.py index 94352db7518..1cfecbbd6e9 100644 --- a/conda/cli/main_info.py +++ b/conda/cli/main_info.py @@ -341,13 +341,13 @@ def execute(args, parser): options = "envs", "system" - if args.all or context.json: + if context.verbose or context.json: for option in options: setattr(args, option, True) info_dict = get_info_dict(args.system) if ( - args.all or all(not getattr(args, opt) for opt in options) + context.verbose or all(not getattr(args, opt) for opt in options) ) and not context.json: print(get_main_info_str(info_dict) + "\n") diff --git a/conda/cli/main_search.py b/conda/cli/main_search.py index be3656c3724..eda82c8e98c 100644 --- a/conda/cli/main_search.py +++ b/conda/cli/main_search.py @@ -29,7 +29,7 @@ def execute(args, parser): if args.envs: with Spinner( "Searching environments for %s" % spec, - not context.verbosity and not context.quiet, + not context.verbose and not context.quiet, context.json, ): prefix_matches = query_all_prefixes(spec) @@ -81,7 +81,9 @@ def execute(args, parser): return 0 with Spinner( - "Loading channels", not context.verbosity and not context.quiet, context.json + "Loading channels", + not context.verbose and not context.quiet, + context.json, ): spec_channel = spec.get_exact_value("channel") channel_urls = (spec_channel,) if spec_channel else context.channels diff --git a/conda/core/initialize.py b/conda/core/initialize.py index 6940c1fa3ab..e5ceb40dfb1 100644 --- a/conda/core/initialize.py +++ b/conda/core/initialize.py @@ -21,7 +21,7 @@ a) return a `Result` (i.e. NEEDS_SUDO, MODIFIED, or NO_CHANGE) b) have no side effects if context.dry_run is True - c) be verbose and descriptive about the changes being made or proposed is context.verbosity >= 1 + c) be verbose and descriptive about the changes being made or proposed is context.verbose The plan runner functions take the plan (list of dicts) as an argument, and then coordinate the execution of each individual operation. The docstring for `run_plan_elevated()` has details on @@ -196,7 +196,7 @@ def initialize_dev(shell, dev_env_prefix=None, conda_source_root=None): run_plan(plan) - if context.dry_run or context.verbosity: + if context.dry_run or context.verbose: print_plan_results(plan, sys.stderr) if any(step["result"] == Result.NEEDS_SUDO for step in plan): # pragma: no cover @@ -229,7 +229,7 @@ def initialize_dev(shell, dev_env_prefix=None, conda_source_root=None): if not context.dry_run: with open("dev-init.bat", "w") as fh: fh.write("\n".join(script)) - if context.verbosity: + if context.verbose: print("\n".join(script)) print("now run > .\\dev-init.bat") else: @@ -953,7 +953,7 @@ def make_entry_point(target_path, conda_prefix, module, func): ) if new_ep_content != original_ep_content: - if context.verbosity: + if context.verbose: print("\n") print(target_path) print(make_diff(original_ep_content, new_ep_content)) @@ -1030,7 +1030,7 @@ def _install_file(target_path, file_content): new_content = file_content if new_content != original_content: - if context.verbosity: + if context.verbose: print("\n") print(target_path) print(make_diff(original_content, new_content)) @@ -1279,7 +1279,7 @@ def init_fish_user(target_path, conda_prefix, reverse): rc_content += "\n%s\n" % conda_initialize_content if rc_content != rc_original_content: - if context.verbosity: + if context.verbose: print("\n") print(target_path) print(make_diff(rc_original_content, rc_content)) @@ -1368,7 +1368,7 @@ def init_xonsh_user(target_path, conda_prefix, reverse): rc_content += f"\n{conda_initialize_content}\n" if rc_content != rc_original_content: - if context.verbosity: + if context.verbose: print("\n") print(target_path) print(make_diff(rc_original_content, rc_content)) @@ -1543,7 +1543,7 @@ def init_sh_user(target_path, conda_prefix, shell, reverse=False): rc_content += "\n%s\n" % conda_initialize_content if rc_content != rc_original_content: - if context.verbosity: + if context.verbose: print("\n") print(target_path) print(make_diff(rc_original_content, rc_content)) @@ -1571,7 +1571,7 @@ def init_sh_system(target_path, conda_prefix, reverse=False): else: conda_sh_contents = _bashrc_content(conda_prefix, "posix") if conda_sh_system_contents != conda_sh_contents: - if context.verbosity: + if context.verbose: print("\n") print(target_path) print(make_diff(conda_sh_contents, conda_sh_system_contents)) @@ -1686,7 +1686,7 @@ def init_cmd_exe_registry(target_path, conda_prefix, reverse=False): new_value = new_hook if prev_value != new_value: - if context.verbosity: + if context.verbose: print("\n") print(target_path) print(make_diff(prev_value, new_value)) @@ -1703,7 +1703,7 @@ def init_long_path(target_path): if int(win_ver) >= 10 and int(win_rev) >= 14352: prev_value, value_type = _read_windows_registry(target_path) if str(prev_value) != "1": - if context.verbosity: + if context.verbose: print("\n") print(target_path) print(make_diff(str(prev_value), "1")) @@ -1713,7 +1713,7 @@ def init_long_path(target_path): else: return Result.NO_CHANGE else: - if context.verbosity: + if context.verbose: print("\n") print( "Not setting long path registry key; Windows version must be at least 10 with " @@ -1784,7 +1784,7 @@ def init_powershell_user(target_path, conda_prefix, reverse): ).replace("__CONDA_REPLACE_ME_123__", conda_initialize_content) if profile_content != profile_original_content: - if context.verbosity: + if context.verbose: print("\n") print(target_path) print(make_diff(profile_original_content, profile_content)) @@ -1844,7 +1844,7 @@ def make_conda_egg_link(target_path, conda_source_root): conda_egg_link_contents_old = "" if conda_egg_link_contents_old != conda_egg_link_contents: - if context.verbosity: + if context.verbose: print("\n", file=sys.stderr) print(target_path, file=sys.stderr) print( @@ -1884,7 +1884,7 @@ def modify_easy_install_pth(target_path, conda_source_root): + os.linesep ) - if context.verbosity: + if context.verbose: print("\n", file=sys.stderr) print(target_path, file=sys.stderr) print(make_diff(old_contents, new_contents), file=sys.stderr) @@ -1919,7 +1919,7 @@ def make_dev_egg_info_file(target_path): if old_contents == new_contents: return Result.NO_CHANGE - if context.verbosity: + if context.verbose: print("\n", file=sys.stderr) print(target_path, file=sys.stderr) print(make_diff(old_contents, new_contents), file=sys.stderr) diff --git a/conda/core/link.py b/conda/core/link.py index e2480620cb1..6c8284b9b5f 100644 --- a/conda/core/link.py +++ b/conda/core/link.py @@ -263,7 +263,7 @@ def prepare(self): with Spinner( "Preparing transaction", - not context.verbosity and not context.quiet, + not context.verbose and not context.quiet, context.json, ): for stp in self.prefix_setups.values(): @@ -293,7 +293,7 @@ def verify(self): with Spinner( "Verifying transaction", - not context.verbosity and not context.quiet, + not context.verbose and not context.quiet, context.json, ): exceptions = self._verify(self.prefix_setups, self.prefix_action_groups) @@ -844,7 +844,7 @@ def _execute(self, all_action_groups): exceptions = [] with Spinner( "Executing transaction", - not context.verbosity and not context.quiet, + not context.verbose and not context.quiet, context.json, ): # Execute unlink actions @@ -954,7 +954,7 @@ def _execute(self, all_action_groups): if context.rollback_enabled: with Spinner( "Rolling back transaction", - not context.verbosity and not context.quiet, + not context.verbose and not context.quiet, context.json, ): reverse_actions = reversed(tuple(all_action_groups)) diff --git a/conda/core/package_cache_data.py b/conda/core/package_cache_data.py index 0e112b82401..5c8acb668ab 100644 --- a/conda/core/package_cache_data.py +++ b/conda/core/package_cache_data.py @@ -759,7 +759,7 @@ def execute(self): if not self.paired_actions: return - if not context.verbosity and not context.quiet and not context.json: + if not context.verbose and not context.quiet and not context.json: print( "\nDownloading and Extracting Packages:", end="\n" if IS_INTERACTIVE else " ...working...", @@ -851,7 +851,7 @@ def execute(self): for bar in progress_bars.values(): bar.close() - if not context.verbosity and not context.quiet and not context.json: + if not context.verbose and not context.quiet and not context.json: if IS_INTERACTIVE: print("\r") # move to column 0 else: @@ -876,7 +876,7 @@ def _progress_bar(prec_or_spec, position=None, leave=False) -> ProgressBar: progress_bar = ProgressBar( desc, - not context.verbosity and not context.quiet and IS_INTERACTIVE, + not context.verbose and not context.quiet and IS_INTERACTIVE, context.json, position=position, leave=leave, diff --git a/conda/core/solve.py b/conda/core/solve.py index 53d3a580ca7..dc23df4e43e 100644 --- a/conda/core/solve.py +++ b/conda/core/solve.py @@ -339,7 +339,7 @@ def solve_final_state( if not ssc.r: with Spinner( "Collecting package metadata (%s)" % self._repodata_fn, - (not context.verbosity and not context.quiet and not retrying), + not context.verbose and not context.quiet and not retrying, context.json, ): ssc = self._collect_all_metadata(ssc) @@ -359,7 +359,7 @@ def solve_final_state( with Spinner( "Solving environment", - not context.verbosity and not context.quiet, + not context.verbose and not context.quiet, context.json, fail_message=fail_message, ): diff --git a/conda/exception_handler.py b/conda/exception_handler.py index d9e55d18ac9..fe0d80f537e 100644 --- a/conda/exception_handler.py +++ b/conda/exception_handler.py @@ -22,10 +22,9 @@ def __call__(self, func, *args, **kwargs): def write_out(self, *content): from logging import getLogger - from .base.context import context from .cli.main import init_loggers - init_loggers(context) + init_loggers() getLogger("conda.stderr").info("\n".join(content)) @property diff --git a/conda/exceptions.py b/conda/exceptions.py index 75267464b95..285afc270ca 100644 --- a/conda/exceptions.py +++ b/conda/exceptions.py @@ -292,10 +292,9 @@ def __init__(self, command): "render", "skeleton", } - from .base.context import context from .cli.main import init_loggers - init_loggers(context) + init_loggers() if command in activate_commands: # TODO: Point users to a page at conda-docs, which explains this context in more detail builder = [ @@ -1245,11 +1244,7 @@ def print_conda_exception(exc_val, exc_tb=None): from .base.context import context rc = getattr(exc_val, "return_code", None) - if ( - context.debug - or context.verbosity > 2 - or (not isinstance(exc_val, DryRunExit) and context.verbosity > 0) - ): + if context.debug or (not isinstance(exc_val, DryRunExit) and context.info): print(_format_exc(exc_val, exc_tb), file=sys.stderr) elif context.json: if isinstance(exc_val, DryRunExit): diff --git a/conda/gateways/logging.py b/conda/gateways/logging.py index 656e776c541..f38110e2f55 100644 --- a/conda/gateways/logging.py +++ b/conda/gateways/logging.py @@ -19,10 +19,18 @@ from .. import CondaError from ..common.io import _FORMATTER, attach_stderr_handler +from ..deprecations import deprecated log = getLogger(__name__) -TRACE = 5 # TRACE LOG LEVEL -VERBOSITY_LEVELS = (WARN, INFO, DEBUG, TRACE) + +_VERBOSITY_LEVELS = { + 0: WARN, # standard output + 1: WARN, # -v, detailed output + 2: INFO, # -vv, info logging + 3: DEBUG, # -vvv, debug logging + 4: (TRACE := 5), # -vvvv, trace logging +} +deprecated.constant("24.3", "24.9", "VERBOSITY_LEVELS", _VERBOSITY_LEVELS) class TokenURLFilter(Filter): @@ -207,15 +215,21 @@ def set_file_logging(logger_name=None, level=DEBUG, path=None): conda_logger.addHandler(handler) -def set_verbosity(verbosity_level): +@deprecated( + "24.3", + "24.9", + addendum="Use `conda.gateways.logging.set_log_level` instead.", +) +def set_verbosity(verbosity: int): try: - set_all_logger_level(VERBOSITY_LEVELS[verbosity_level]) - except IndexError: - raise CondaError( - "Invalid verbosity level: %(verbosity_level)s", - verbosity_level=verbosity_level, - ) - log.debug("verbosity set to %s", verbosity_level) + set_log_level(_VERBOSITY_LEVELS[verbosity]) + except KeyError: + raise CondaError(f"Invalid verbosity level: {verbosity}") from None + + +def set_log_level(log_level: int): + set_all_logger_level(log_level) + log.debug("log_level set to %d", log_level) def trace(self, message, *args, **kwargs): @@ -224,7 +238,7 @@ def trace(self, message, *args, **kwargs): logging.addLevelName(TRACE, "TRACE") -logging.Logger.trace = trace +logging.Logger.trace = trace # type: ignore[attr-defined] # suppress DeprecationWarning for warn method -logging.Logger.warn = logging.Logger.warning +logging.Logger.warn = logging.Logger.warning # type: ignore[method-assign] diff --git a/conda/gateways/subprocess.py b/conda/gateways/subprocess.py index 93971617c50..7bc8f2438d4 100644 --- a/conda/gateways/subprocess.py +++ b/conda/gateways/subprocess.py @@ -44,7 +44,7 @@ def any_subprocess(args, prefix, env=None, cwd=None): context.root_prefix, prefix, context.dev, - context.verbosity >= 2, + context.debug, args, ) process = Popen( diff --git a/conda/plugins/subcommands/doctor/cli.py b/conda/plugins/subcommands/doctor/cli.py index 71edfa69fb5..40400259acd 100644 --- a/conda/plugins/subcommands/doctor/cli.py +++ b/conda/plugins/subcommands/doctor/cli.py @@ -6,7 +6,12 @@ import argparse from ....base.context import context -from ....cli.conda_argparse import ArgumentParser, add_parser_help, add_parser_prefix +from ....cli.conda_argparse import ( + ArgumentParser, + add_parser_help, + add_parser_prefix, + add_parser_verbose, +) from ....deprecations import deprecated from ... import CondaSubcommand, hookimpl @@ -20,12 +25,7 @@ def get_prefix(args: argparse.Namespace) -> str: def configure_parser(parser: ArgumentParser): - parser.add_argument( - "-v", - "--verbose", - action="store_true", - help="Generate a detailed environment health report.", - ) + add_parser_verbose(parser) add_parser_help(parser) add_parser_prefix(parser) @@ -34,7 +34,7 @@ def execute(args: argparse.Namespace) -> None: """Run conda doctor subcommand.""" from .health_checks import display_health_checks - display_health_checks(context.target_prefix, verbose=args.verbose) + display_health_checks(context.target_prefix, verbose=context.verbose) @hookimpl diff --git a/conda/testing/__init__.py b/conda/testing/__init__.py index 0cb683d7b60..0d45f4ea690 100644 --- a/conda/testing/__init__.py +++ b/conda/testing/__init__.py @@ -193,7 +193,7 @@ def __call__(self, *argv: str) -> tuple[str, str, int]: # initialize context and loggers context.__init__(argparse_args=args) - init_loggers(context) + init_loggers() # run command code = do_call(args, parser) diff --git a/conda/testing/integration.py b/conda/testing/integration.py index b96fe4c4255..ac201be0502 100644 --- a/conda/testing/integration.py +++ b/conda/testing/integration.py @@ -270,7 +270,7 @@ def run_command(command, prefix, *arguments, **kwargs): args = p.parse_args(arguments) context._set_argparse_args(args) - init_loggers(context) + init_loggers() cap_args = () if not kwargs.get("no_capture") else (None, None) # list2cmdline is not exact, but it is only informational. print( diff --git a/conda_env/cli/main.py b/conda_env/cli/main.py index 1f7d2c59cdf..e5436d23a0e 100644 --- a/conda_env/cli/main.py +++ b/conda_env/cli/main.py @@ -65,7 +65,7 @@ def main(): args = parser.parse_args() os.environ["CONDA_AUTO_UPDATE_CONDA"] = "false" context.__init__(argparse_args=args) - init_loggers(context) + init_loggers() return conda_exception_handler(do_call, args, parser) diff --git a/conda_env/installers/pip.py b/conda_env/installers/pip.py index 1fc551d178e..a904e367b90 100644 --- a/conda_env/installers/pip.py +++ b/conda_env/installers/pip.py @@ -70,7 +70,7 @@ def _pip_install_via_requirements(prefix, specs, args, *_, **kwargs): def install(*args, **kwargs): with Spinner( "Installing pip dependencies", - not context.verbosity and not context.quiet, + not context.verbose and not context.quiet, context.json, ): return _pip_install_via_requirements(*args, **kwargs) diff --git a/news/12985-verbosity b/news/12985-verbosity new file mode 100644 index 00000000000..188a3716f41 --- /dev/null +++ b/news/12985-verbosity @@ -0,0 +1,19 @@ +### Enhancements + +* Inject a new detailed output verbosity level (i.e., the old debug level `-vv` is now `-vvv`). (#12985, #12977, #12420) + +### Bug fixes + +* + +### Deprecations + +* + +### Docs + +* + +### Other + +* diff --git a/tests/cli/test_main_run.py b/tests/cli/test_main_run.py index 08702e7f9d0..ad0ac521ec5 100644 --- a/tests/cli/test_main_run.py +++ b/tests/cli/test_main_run.py @@ -41,7 +41,7 @@ def test_run_returns_nonzero_errorlevel( stdout, stderr, err = conda_cli("run", "--prefix", prefix, "exit", "5") assert not stdout - assert not stderr + assert stderr assert err == 5