Path: blob/main/test/lib/python3.9/site-packages/pip/_internal/cli/parser.py
4804 views
"""Base option parser setup"""12import logging3import optparse4import shutil5import sys6import textwrap7from contextlib import suppress8from typing import Any, Dict, Generator, List, Tuple910from pip._internal.cli.status_codes import UNKNOWN_ERROR11from pip._internal.configuration import Configuration, ConfigurationError12from pip._internal.utils.misc import redact_auth_from_url, strtobool1314logger = logging.getLogger(__name__)151617class PrettyHelpFormatter(optparse.IndentedHelpFormatter):18"""A prettier/less verbose help formatter for optparse."""1920def __init__(self, *args: Any, **kwargs: Any) -> None:21# help position must be aligned with __init__.parseopts.description22kwargs["max_help_position"] = 3023kwargs["indent_increment"] = 124kwargs["width"] = shutil.get_terminal_size()[0] - 225super().__init__(*args, **kwargs)2627def format_option_strings(self, option: optparse.Option) -> str:28return self._format_option_strings(option)2930def _format_option_strings(31self, option: optparse.Option, mvarfmt: str = " <{}>", optsep: str = ", "32) -> str:33"""34Return a comma-separated list of option strings and metavars.3536:param option: tuple of (short opt, long opt), e.g: ('-f', '--format')37:param mvarfmt: metavar format string38:param optsep: separator39"""40opts = []4142if option._short_opts:43opts.append(option._short_opts[0])44if option._long_opts:45opts.append(option._long_opts[0])46if len(opts) > 1:47opts.insert(1, optsep)4849if option.takes_value():50assert option.dest is not None51metavar = option.metavar or option.dest.lower()52opts.append(mvarfmt.format(metavar.lower()))5354return "".join(opts)5556def format_heading(self, heading: str) -> str:57if heading == "Options":58return ""59return heading + ":\n"6061def format_usage(self, usage: str) -> str:62"""63Ensure there is only one newline between usage and the first heading64if there is no description.65"""66msg = "\nUsage: {}\n".format(self.indent_lines(textwrap.dedent(usage), " "))67return msg6869def format_description(self, description: str) -> str:70# leave full control over description to us71if description:72if hasattr(self.parser, "main"):73label = "Commands"74else:75label = "Description"76# some doc strings have initial newlines, some don't77description = description.lstrip("\n")78# some doc strings have final newlines and spaces, some don't79description = description.rstrip()80# dedent, then reindent81description = self.indent_lines(textwrap.dedent(description), " ")82description = f"{label}:\n{description}\n"83return description84else:85return ""8687def format_epilog(self, epilog: str) -> str:88# leave full control over epilog to us89if epilog:90return epilog91else:92return ""9394def indent_lines(self, text: str, indent: str) -> str:95new_lines = [indent + line for line in text.split("\n")]96return "\n".join(new_lines)979899class UpdatingDefaultsHelpFormatter(PrettyHelpFormatter):100"""Custom help formatter for use in ConfigOptionParser.101102This is updates the defaults before expanding them, allowing103them to show up correctly in the help listing.104105Also redact auth from url type options106"""107108def expand_default(self, option: optparse.Option) -> str:109default_values = None110if self.parser is not None:111assert isinstance(self.parser, ConfigOptionParser)112self.parser._update_defaults(self.parser.defaults)113assert option.dest is not None114default_values = self.parser.defaults.get(option.dest)115help_text = super().expand_default(option)116117if default_values and option.metavar == "URL":118if isinstance(default_values, str):119default_values = [default_values]120121# If its not a list, we should abort and just return the help text122if not isinstance(default_values, list):123default_values = []124125for val in default_values:126help_text = help_text.replace(val, redact_auth_from_url(val))127128return help_text129130131class CustomOptionParser(optparse.OptionParser):132def insert_option_group(133self, idx: int, *args: Any, **kwargs: Any134) -> optparse.OptionGroup:135"""Insert an OptionGroup at a given position."""136group = self.add_option_group(*args, **kwargs)137138self.option_groups.pop()139self.option_groups.insert(idx, group)140141return group142143@property144def option_list_all(self) -> List[optparse.Option]:145"""Get a list of all options, including those in option groups."""146res = self.option_list[:]147for i in self.option_groups:148res.extend(i.option_list)149150return res151152153class ConfigOptionParser(CustomOptionParser):154"""Custom option parser which updates its defaults by checking the155configuration files and environmental variables"""156157def __init__(158self,159*args: Any,160name: str,161isolated: bool = False,162**kwargs: Any,163) -> None:164self.name = name165self.config = Configuration(isolated)166167assert self.name168super().__init__(*args, **kwargs)169170def check_default(self, option: optparse.Option, key: str, val: Any) -> Any:171try:172return option.check_value(key, val)173except optparse.OptionValueError as exc:174print(f"An error occurred during configuration: {exc}")175sys.exit(3)176177def _get_ordered_configuration_items(178self,179) -> Generator[Tuple[str, Any], None, None]:180# Configuration gives keys in an unordered manner. Order them.181override_order = ["global", self.name, ":env:"]182183# Pool the options into different groups184section_items: Dict[str, List[Tuple[str, Any]]] = {185name: [] for name in override_order186}187for section_key, val in self.config.items():188# ignore empty values189if not val:190logger.debug(191"Ignoring configuration key '%s' as it's value is empty.",192section_key,193)194continue195196section, key = section_key.split(".", 1)197if section in override_order:198section_items[section].append((key, val))199200# Yield each group in their override order201for section in override_order:202for key, val in section_items[section]:203yield key, val204205def _update_defaults(self, defaults: Dict[str, Any]) -> Dict[str, Any]:206"""Updates the given defaults with values from the config files and207the environ. Does a little special handling for certain types of208options (lists)."""209210# Accumulate complex default state.211self.values = optparse.Values(self.defaults)212late_eval = set()213# Then set the options with those values214for key, val in self._get_ordered_configuration_items():215# '--' because configuration supports only long names216option = self.get_option("--" + key)217218# Ignore options not present in this parser. E.g. non-globals put219# in [global] by users that want them to apply to all applicable220# commands.221if option is None:222continue223224assert option.dest is not None225226if option.action in ("store_true", "store_false"):227try:228val = strtobool(val)229except ValueError:230self.error(231"{} is not a valid value for {} option, " # noqa232"please specify a boolean value like yes/no, "233"true/false or 1/0 instead.".format(val, key)234)235elif option.action == "count":236with suppress(ValueError):237val = strtobool(val)238with suppress(ValueError):239val = int(val)240if not isinstance(val, int) or val < 0:241self.error(242"{} is not a valid value for {} option, " # noqa243"please instead specify either a non-negative integer "244"or a boolean value like yes/no or false/true "245"which is equivalent to 1/0.".format(val, key)246)247elif option.action == "append":248val = val.split()249val = [self.check_default(option, key, v) for v in val]250elif option.action == "callback":251assert option.callback is not None252late_eval.add(option.dest)253opt_str = option.get_opt_string()254val = option.convert_value(opt_str, val)255# From take_action256args = option.callback_args or ()257kwargs = option.callback_kwargs or {}258option.callback(option, opt_str, val, self, *args, **kwargs)259else:260val = self.check_default(option, key, val)261262defaults[option.dest] = val263264for key in late_eval:265defaults[key] = getattr(self.values, key)266self.values = None267return defaults268269def get_default_values(self) -> optparse.Values:270"""Overriding to make updating the defaults after instantiation of271the option parser possible, _update_defaults() does the dirty work."""272if not self.process_default_values:273# Old, pre-Optik 1.5 behaviour.274return optparse.Values(self.defaults)275276# Load the configuration, or error out in case of an error277try:278self.config.load()279except ConfigurationError as err:280self.exit(UNKNOWN_ERROR, str(err))281282defaults = self._update_defaults(self.defaults.copy()) # ours283for option in self._get_all_options():284assert option.dest is not None285default = defaults.get(option.dest)286if isinstance(default, str):287opt_str = option.get_opt_string()288defaults[option.dest] = option.check_value(opt_str, default)289return optparse.Values(defaults)290291def error(self, msg: str) -> None:292self.print_usage(sys.stderr)293self.exit(UNKNOWN_ERROR, f"{msg}\n")294295296