Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/pip/_internal/configuration.py @ 5:9b1c78e6ba9c draft default tip
"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
| author | shellac | 
|---|---|
| date | Mon, 01 Jun 2020 08:59:25 -0400 | 
| parents | 79f47841a781 | 
| children | 
   comparison
  equal
  deleted
  inserted
  replaced
| 4:79f47841a781 | 5:9b1c78e6ba9c | 
|---|---|
| 1 """Configuration management setup | |
| 2 | |
| 3 Some terminology: | |
| 4 - name | |
| 5 As written in config files. | |
| 6 - value | |
| 7 Value associated with a name | |
| 8 - key | |
| 9 Name combined with it's section (section.name) | |
| 10 - variant | |
| 11 A single word describing where the configuration key-value pair came from | |
| 12 """ | |
| 13 | |
| 14 # The following comment should be removed at some point in the future. | |
| 15 # mypy: strict-optional=False | |
| 16 | |
| 17 import locale | |
| 18 import logging | |
| 19 import os | |
| 20 import sys | |
| 21 | |
| 22 from pip._vendor.six.moves import configparser | |
| 23 | |
| 24 from pip._internal.exceptions import ( | |
| 25 ConfigurationError, | |
| 26 ConfigurationFileCouldNotBeLoaded, | |
| 27 ) | |
| 28 from pip._internal.utils import appdirs | |
| 29 from pip._internal.utils.compat import WINDOWS, expanduser | |
| 30 from pip._internal.utils.misc import ensure_dir, enum | |
| 31 from pip._internal.utils.typing import MYPY_CHECK_RUNNING | |
| 32 | |
| 33 if MYPY_CHECK_RUNNING: | |
| 34 from typing import ( | |
| 35 Any, Dict, Iterable, List, NewType, Optional, Tuple | |
| 36 ) | |
| 37 | |
| 38 RawConfigParser = configparser.RawConfigParser # Shorthand | |
| 39 Kind = NewType("Kind", str) | |
| 40 | |
| 41 logger = logging.getLogger(__name__) | |
| 42 | |
| 43 | |
| 44 # NOTE: Maybe use the optionx attribute to normalize keynames. | |
| 45 def _normalize_name(name): | |
| 46 # type: (str) -> str | |
| 47 """Make a name consistent regardless of source (environment or file) | |
| 48 """ | |
| 49 name = name.lower().replace('_', '-') | |
| 50 if name.startswith('--'): | |
| 51 name = name[2:] # only prefer long opts | |
| 52 return name | |
| 53 | |
| 54 | |
| 55 def _disassemble_key(name): | |
| 56 # type: (str) -> List[str] | |
| 57 if "." not in name: | |
| 58 error_message = ( | |
| 59 "Key does not contain dot separated section and key. " | |
| 60 "Perhaps you wanted to use 'global.{}' instead?" | |
| 61 ).format(name) | |
| 62 raise ConfigurationError(error_message) | |
| 63 return name.split(".", 1) | |
| 64 | |
| 65 | |
| 66 # The kinds of configurations there are. | |
| 67 kinds = enum( | |
| 68 USER="user", # User Specific | |
| 69 GLOBAL="global", # System Wide | |
| 70 SITE="site", # [Virtual] Environment Specific | |
| 71 ENV="env", # from PIP_CONFIG_FILE | |
| 72 ENV_VAR="env-var", # from Environment Variables | |
| 73 ) | |
| 74 | |
| 75 | |
| 76 CONFIG_BASENAME = 'pip.ini' if WINDOWS else 'pip.conf' | |
| 77 | |
| 78 | |
| 79 def get_configuration_files(): | |
| 80 # type: () -> Dict[Kind, List[str]] | |
| 81 global_config_files = [ | |
| 82 os.path.join(path, CONFIG_BASENAME) | |
| 83 for path in appdirs.site_config_dirs('pip') | |
| 84 ] | |
| 85 | |
| 86 site_config_file = os.path.join(sys.prefix, CONFIG_BASENAME) | |
| 87 legacy_config_file = os.path.join( | |
| 88 expanduser('~'), | |
| 89 'pip' if WINDOWS else '.pip', | |
| 90 CONFIG_BASENAME, | |
| 91 ) | |
| 92 new_config_file = os.path.join( | |
| 93 appdirs.user_config_dir("pip"), CONFIG_BASENAME | |
| 94 ) | |
| 95 return { | |
| 96 kinds.GLOBAL: global_config_files, | |
| 97 kinds.SITE: [site_config_file], | |
| 98 kinds.USER: [legacy_config_file, new_config_file], | |
| 99 } | |
| 100 | |
| 101 | |
| 102 class Configuration(object): | |
| 103 """Handles management of configuration. | |
| 104 | |
| 105 Provides an interface to accessing and managing configuration files. | |
| 106 | |
| 107 This class converts provides an API that takes "section.key-name" style | |
| 108 keys and stores the value associated with it as "key-name" under the | |
| 109 section "section". | |
| 110 | |
| 111 This allows for a clean interface wherein the both the section and the | |
| 112 key-name are preserved in an easy to manage form in the configuration files | |
| 113 and the data stored is also nice. | |
| 114 """ | |
| 115 | |
| 116 def __init__(self, isolated, load_only=None): | |
| 117 # type: (bool, Kind) -> None | |
| 118 super(Configuration, self).__init__() | |
| 119 | |
| 120 _valid_load_only = [kinds.USER, kinds.GLOBAL, kinds.SITE, None] | |
| 121 if load_only not in _valid_load_only: | |
| 122 raise ConfigurationError( | |
| 123 "Got invalid value for load_only - should be one of {}".format( | |
| 124 ", ".join(map(repr, _valid_load_only[:-1])) | |
| 125 ) | |
| 126 ) | |
| 127 self.isolated = isolated # type: bool | |
| 128 self.load_only = load_only # type: Optional[Kind] | |
| 129 | |
| 130 # The order here determines the override order. | |
| 131 self._override_order = [ | |
| 132 kinds.GLOBAL, kinds.USER, kinds.SITE, kinds.ENV, kinds.ENV_VAR | |
| 133 ] | |
| 134 | |
| 135 self._ignore_env_names = ["version", "help"] | |
| 136 | |
| 137 # Because we keep track of where we got the data from | |
| 138 self._parsers = { | |
| 139 variant: [] for variant in self._override_order | |
| 140 } # type: Dict[Kind, List[Tuple[str, RawConfigParser]]] | |
| 141 self._config = { | |
| 142 variant: {} for variant in self._override_order | |
| 143 } # type: Dict[Kind, Dict[str, Any]] | |
| 144 self._modified_parsers = [] # type: List[Tuple[str, RawConfigParser]] | |
| 145 | |
| 146 def load(self): | |
| 147 # type: () -> None | |
| 148 """Loads configuration from configuration files and environment | |
| 149 """ | |
| 150 self._load_config_files() | |
| 151 if not self.isolated: | |
| 152 self._load_environment_vars() | |
| 153 | |
| 154 def get_file_to_edit(self): | |
| 155 # type: () -> Optional[str] | |
| 156 """Returns the file with highest priority in configuration | |
| 157 """ | |
| 158 assert self.load_only is not None, \ | |
| 159 "Need to be specified a file to be editing" | |
| 160 | |
| 161 try: | |
| 162 return self._get_parser_to_modify()[0] | |
| 163 except IndexError: | |
| 164 return None | |
| 165 | |
| 166 def items(self): | |
| 167 # type: () -> Iterable[Tuple[str, Any]] | |
| 168 """Returns key-value pairs like dict.items() representing the loaded | |
| 169 configuration | |
| 170 """ | |
| 171 return self._dictionary.items() | |
| 172 | |
| 173 def get_value(self, key): | |
| 174 # type: (str) -> Any | |
| 175 """Get a value from the configuration. | |
| 176 """ | |
| 177 try: | |
| 178 return self._dictionary[key] | |
| 179 except KeyError: | |
| 180 raise ConfigurationError("No such key - {}".format(key)) | |
| 181 | |
| 182 def set_value(self, key, value): | |
| 183 # type: (str, Any) -> None | |
| 184 """Modify a value in the configuration. | |
| 185 """ | |
| 186 self._ensure_have_load_only() | |
| 187 | |
| 188 fname, parser = self._get_parser_to_modify() | |
| 189 | |
| 190 if parser is not None: | |
| 191 section, name = _disassemble_key(key) | |
| 192 | |
| 193 # Modify the parser and the configuration | |
| 194 if not parser.has_section(section): | |
| 195 parser.add_section(section) | |
| 196 parser.set(section, name, value) | |
| 197 | |
| 198 self._config[self.load_only][key] = value | |
| 199 self._mark_as_modified(fname, parser) | |
| 200 | |
| 201 def unset_value(self, key): | |
| 202 # type: (str) -> None | |
| 203 """Unset a value in the configuration. | |
| 204 """ | |
| 205 self._ensure_have_load_only() | |
| 206 | |
| 207 if key not in self._config[self.load_only]: | |
| 208 raise ConfigurationError("No such key - {}".format(key)) | |
| 209 | |
| 210 fname, parser = self._get_parser_to_modify() | |
| 211 | |
| 212 if parser is not None: | |
| 213 section, name = _disassemble_key(key) | |
| 214 | |
| 215 # Remove the key in the parser | |
| 216 modified_something = False | |
| 217 if parser.has_section(section): | |
| 218 # Returns whether the option was removed or not | |
| 219 modified_something = parser.remove_option(section, name) | |
| 220 | |
| 221 if modified_something: | |
| 222 # name removed from parser, section may now be empty | |
| 223 section_iter = iter(parser.items(section)) | |
| 224 try: | |
| 225 val = next(section_iter) | |
| 226 except StopIteration: | |
| 227 val = None | |
| 228 | |
| 229 if val is None: | |
| 230 parser.remove_section(section) | |
| 231 | |
| 232 self._mark_as_modified(fname, parser) | |
| 233 else: | |
| 234 raise ConfigurationError( | |
| 235 "Fatal Internal error [id=1]. Please report as a bug." | |
| 236 ) | |
| 237 | |
| 238 del self._config[self.load_only][key] | |
| 239 | |
| 240 def save(self): | |
| 241 # type: () -> None | |
| 242 """Save the current in-memory state. | |
| 243 """ | |
| 244 self._ensure_have_load_only() | |
| 245 | |
| 246 for fname, parser in self._modified_parsers: | |
| 247 logger.info("Writing to %s", fname) | |
| 248 | |
| 249 # Ensure directory exists. | |
| 250 ensure_dir(os.path.dirname(fname)) | |
| 251 | |
| 252 with open(fname, "w") as f: | |
| 253 parser.write(f) | |
| 254 | |
| 255 # | |
| 256 # Private routines | |
| 257 # | |
| 258 | |
| 259 def _ensure_have_load_only(self): | |
| 260 # type: () -> None | |
| 261 if self.load_only is None: | |
| 262 raise ConfigurationError("Needed a specific file to be modifying.") | |
| 263 logger.debug("Will be working with %s variant only", self.load_only) | |
| 264 | |
| 265 @property | |
| 266 def _dictionary(self): | |
| 267 # type: () -> Dict[str, Any] | |
| 268 """A dictionary representing the loaded configuration. | |
| 269 """ | |
| 270 # NOTE: Dictionaries are not populated if not loaded. So, conditionals | |
| 271 # are not needed here. | |
| 272 retval = {} | |
| 273 | |
| 274 for variant in self._override_order: | |
| 275 retval.update(self._config[variant]) | |
| 276 | |
| 277 return retval | |
| 278 | |
| 279 def _load_config_files(self): | |
| 280 # type: () -> None | |
| 281 """Loads configuration from configuration files | |
| 282 """ | |
| 283 config_files = dict(self._iter_config_files()) | |
| 284 if config_files[kinds.ENV][0:1] == [os.devnull]: | |
| 285 logger.debug( | |
| 286 "Skipping loading configuration files due to " | |
| 287 "environment's PIP_CONFIG_FILE being os.devnull" | |
| 288 ) | |
| 289 return | |
| 290 | |
| 291 for variant, files in config_files.items(): | |
| 292 for fname in files: | |
| 293 # If there's specific variant set in `load_only`, load only | |
| 294 # that variant, not the others. | |
| 295 if self.load_only is not None and variant != self.load_only: | |
| 296 logger.debug( | |
| 297 "Skipping file '%s' (variant: %s)", fname, variant | |
| 298 ) | |
| 299 continue | |
| 300 | |
| 301 parser = self._load_file(variant, fname) | |
| 302 | |
| 303 # Keeping track of the parsers used | |
| 304 self._parsers[variant].append((fname, parser)) | |
| 305 | |
| 306 def _load_file(self, variant, fname): | |
| 307 # type: (Kind, str) -> RawConfigParser | |
| 308 logger.debug("For variant '%s', will try loading '%s'", variant, fname) | |
| 309 parser = self._construct_parser(fname) | |
| 310 | |
| 311 for section in parser.sections(): | |
| 312 items = parser.items(section) | |
| 313 self._config[variant].update(self._normalized_keys(section, items)) | |
| 314 | |
| 315 return parser | |
| 316 | |
| 317 def _construct_parser(self, fname): | |
| 318 # type: (str) -> RawConfigParser | |
| 319 parser = configparser.RawConfigParser() | |
| 320 # If there is no such file, don't bother reading it but create the | |
| 321 # parser anyway, to hold the data. | |
| 322 # Doing this is useful when modifying and saving files, where we don't | |
| 323 # need to construct a parser. | |
| 324 if os.path.exists(fname): | |
| 325 try: | |
| 326 parser.read(fname) | |
| 327 except UnicodeDecodeError: | |
| 328 # See https://github.com/pypa/pip/issues/4963 | |
| 329 raise ConfigurationFileCouldNotBeLoaded( | |
| 330 reason="contains invalid {} characters".format( | |
| 331 locale.getpreferredencoding(False) | |
| 332 ), | |
| 333 fname=fname, | |
| 334 ) | |
| 335 except configparser.Error as error: | |
| 336 # See https://github.com/pypa/pip/issues/4893 | |
| 337 raise ConfigurationFileCouldNotBeLoaded(error=error) | |
| 338 return parser | |
| 339 | |
| 340 def _load_environment_vars(self): | |
| 341 # type: () -> None | |
| 342 """Loads configuration from environment variables | |
| 343 """ | |
| 344 self._config[kinds.ENV_VAR].update( | |
| 345 self._normalized_keys(":env:", self._get_environ_vars()) | |
| 346 ) | |
| 347 | |
| 348 def _normalized_keys(self, section, items): | |
| 349 # type: (str, Iterable[Tuple[str, Any]]) -> Dict[str, Any] | |
| 350 """Normalizes items to construct a dictionary with normalized keys. | |
| 351 | |
| 352 This routine is where the names become keys and are made the same | |
| 353 regardless of source - configuration files or environment. | |
| 354 """ | |
| 355 normalized = {} | |
| 356 for name, val in items: | |
| 357 key = section + "." + _normalize_name(name) | |
| 358 normalized[key] = val | |
| 359 return normalized | |
| 360 | |
| 361 def _get_environ_vars(self): | |
| 362 # type: () -> Iterable[Tuple[str, str]] | |
| 363 """Returns a generator with all environmental vars with prefix PIP_""" | |
| 364 for key, val in os.environ.items(): | |
| 365 should_be_yielded = ( | |
| 366 key.startswith("PIP_") and | |
| 367 key[4:].lower() not in self._ignore_env_names | |
| 368 ) | |
| 369 if should_be_yielded: | |
| 370 yield key[4:].lower(), val | |
| 371 | |
| 372 # XXX: This is patched in the tests. | |
| 373 def _iter_config_files(self): | |
| 374 # type: () -> Iterable[Tuple[Kind, List[str]]] | |
| 375 """Yields variant and configuration files associated with it. | |
| 376 | |
| 377 This should be treated like items of a dictionary. | |
| 378 """ | |
| 379 # SMELL: Move the conditions out of this function | |
| 380 | |
| 381 # environment variables have the lowest priority | |
| 382 config_file = os.environ.get('PIP_CONFIG_FILE', None) | |
| 383 if config_file is not None: | |
| 384 yield kinds.ENV, [config_file] | |
| 385 else: | |
| 386 yield kinds.ENV, [] | |
| 387 | |
| 388 config_files = get_configuration_files() | |
| 389 | |
| 390 # at the base we have any global configuration | |
| 391 yield kinds.GLOBAL, config_files[kinds.GLOBAL] | |
| 392 | |
| 393 # per-user configuration next | |
| 394 should_load_user_config = not self.isolated and not ( | |
| 395 config_file and os.path.exists(config_file) | |
| 396 ) | |
| 397 if should_load_user_config: | |
| 398 # The legacy config file is overridden by the new config file | |
| 399 yield kinds.USER, config_files[kinds.USER] | |
| 400 | |
| 401 # finally virtualenv configuration first trumping others | |
| 402 yield kinds.SITE, config_files[kinds.SITE] | |
| 403 | |
| 404 def _get_parser_to_modify(self): | |
| 405 # type: () -> Tuple[str, RawConfigParser] | |
| 406 # Determine which parser to modify | |
| 407 parsers = self._parsers[self.load_only] | |
| 408 if not parsers: | |
| 409 # This should not happen if everything works correctly. | |
| 410 raise ConfigurationError( | |
| 411 "Fatal Internal error [id=2]. Please report as a bug." | |
| 412 ) | |
| 413 | |
| 414 # Use the highest priority parser. | |
| 415 return parsers[-1] | |
| 416 | |
| 417 # XXX: This is patched in the tests. | |
| 418 def _mark_as_modified(self, fname, parser): | |
| 419 # type: (str, RawConfigParser) -> None | |
| 420 file_parser_tuple = (fname, parser) | |
| 421 if file_parser_tuple not in self._modified_parsers: | |
| 422 self._modified_parsers.append(file_parser_tuple) | |
| 423 | |
| 424 def __repr__(self): | |
| 425 # type: () -> str | |
| 426 return "{}({!r})".format(self.__class__.__name__, self._dictionary) | 
