| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- """Config handling logic for Flake8."""
- from __future__ import annotations
- import configparser
- import logging
- import os.path
- from typing import Any
- from flake8 import exceptions
- from flake8.defaults import VALID_CODE_PREFIX
- from flake8.options.manager import OptionManager
- LOG = logging.getLogger(__name__)
- def _stat_key(s: str) -> tuple[int, int]:
- # same as what's used by samefile / samestat
- st = os.stat(s)
- return st.st_ino, st.st_dev
- def _find_config_file(path: str) -> str | None:
- # on windows if the homedir isn't detected this returns back `~`
- home = os.path.expanduser("~")
- try:
- home_stat = _stat_key(home) if home != "~" else None
- except OSError: # FileNotFoundError / PermissionError / etc.
- home_stat = None
- dir_stat = _stat_key(path)
- while True:
- for candidate in ("setup.cfg", "tox.ini", ".flake8"):
- cfg = configparser.RawConfigParser()
- cfg_path = os.path.join(path, candidate)
- try:
- cfg.read(cfg_path, encoding="UTF-8")
- except (UnicodeDecodeError, configparser.ParsingError) as e:
- LOG.warning("ignoring unparseable config %s: %s", cfg_path, e)
- else:
- # only consider it a config if it contains flake8 sections
- if "flake8" in cfg or "flake8:local-plugins" in cfg:
- return cfg_path
- new_path = os.path.dirname(path)
- new_dir_stat = _stat_key(new_path)
- if new_dir_stat == dir_stat or new_dir_stat == home_stat:
- break
- else:
- path = new_path
- dir_stat = new_dir_stat
- # did not find any configuration file
- return None
- def load_config(
- config: str | None,
- extra: list[str],
- *,
- isolated: bool = False,
- ) -> tuple[configparser.RawConfigParser, str]:
- """Load the configuration given the user options.
- - in ``isolated`` mode, return an empty configuration
- - if a config file is given in ``config`` use that, otherwise attempt to
- discover a configuration using ``tox.ini`` / ``setup.cfg`` / ``.flake8``
- - finally, load any ``extra`` configuration files
- """
- pwd = os.path.abspath(".")
- if isolated:
- return configparser.RawConfigParser(), pwd
- if config is None:
- config = _find_config_file(pwd)
- cfg = configparser.RawConfigParser()
- if config is not None:
- if not cfg.read(config, encoding="UTF-8"):
- raise exceptions.ExecutionError(
- f"The specified config file does not exist: {config}"
- )
- cfg_dir = os.path.dirname(config)
- else:
- cfg_dir = pwd
- # TODO: remove this and replace it with configuration modifying plugins
- # read the additional configs afterwards
- for filename in extra:
- if not cfg.read(filename, encoding="UTF-8"):
- raise exceptions.ExecutionError(
- f"The specified config file does not exist: {filename}"
- )
- return cfg, cfg_dir
- def parse_config(
- option_manager: OptionManager,
- cfg: configparser.RawConfigParser,
- cfg_dir: str,
- ) -> dict[str, Any]:
- """Parse and normalize the typed configuration options."""
- if "flake8" not in cfg:
- return {}
- config_dict = {}
- for option_name in cfg["flake8"]:
- option = option_manager.config_options_dict.get(option_name)
- if option is None:
- LOG.debug('Option "%s" is not registered. Ignoring.', option_name)
- continue
- # Use the appropriate method to parse the config value
- value: Any
- if option.type is int or option.action == "count":
- value = cfg.getint("flake8", option_name)
- elif option.action in {"store_true", "store_false"}:
- value = cfg.getboolean("flake8", option_name)
- else:
- value = cfg.get("flake8", option_name)
- LOG.debug('Option "%s" returned value: %r', option_name, value)
- final_value = option.normalize(value, cfg_dir)
- if option_name in {"ignore", "extend-ignore"}:
- for error_code in final_value:
- if not VALID_CODE_PREFIX.match(error_code):
- raise ValueError(
- f"Error code {error_code!r} "
- f"supplied to {option_name!r} option "
- f"does not match {VALID_CODE_PREFIX.pattern!r}"
- )
- assert option.config_name is not None
- config_dict[option.config_name] = final_value
- return config_dict
|