mirror of
https://github.com/PyCQA/flake8.git
synced 2026-03-29 10:36:53 +00:00
Merge pull request #1472 from asottile/config_discovery
refactor and simplify configuration loading
This commit is contained in:
commit
841489e312
20 changed files with 351 additions and 883 deletions
|
|
@ -41,7 +41,7 @@ three new parameters:
|
|||
|
||||
The last two are not specifically for configuration file handling, but they
|
||||
do improve that dramatically. We found that there were options that, when
|
||||
specified in a configuration file, often necessitated being spit
|
||||
specified in a configuration file, often necessitated being split across
|
||||
multiple lines and those options were almost always comma-separated. For
|
||||
example, let's consider a user's list of ignored error codes for a project:
|
||||
|
||||
|
|
@ -157,42 +157,22 @@ problems with pep8's 1.6 series. As such, |Flake8| has separated out
|
|||
discovery, management, and merging into a module to make reasoning about each
|
||||
of these pieces easier and more explicit (as well as easier to test).
|
||||
|
||||
Configuration file discovery is managed by the
|
||||
:class:`~flake8.options.config.ConfigFileFinder` object. This object needs to
|
||||
know information about the program's name, any extra arguments passed to it,
|
||||
and any configuration files that should be appended to the list of discovered
|
||||
files. It provides methods for finding the files and similar methods for
|
||||
parsing those fles. For example, it provides
|
||||
:meth:`~flake8.options.config.ConfigFileFinder.local_config_files` to find
|
||||
known local config files (and append the extra configuration files) and it
|
||||
also provides :meth:`~flake8.options.config.ConfigFileFinder.local_configs`
|
||||
to parse those configuration files.
|
||||
Configuration file discovery and raw ini reading is managed by
|
||||
:func:`~flake8.options.config.load_config`. This produces a loaded
|
||||
:class:`~configparser.RawConfigParser` and a config directory (which will be
|
||||
used later to normalize paths).
|
||||
|
||||
.. note:: ``local_config_files`` also filters out non-existent files.
|
||||
Next, :func:`~flake8.options.config.parse_config` parses options using the
|
||||
types in the ``OptionManager``.
|
||||
|
||||
Configuration file merging and managemnt is controlled by the
|
||||
:class:`~flake8.options.config.ConfigParser`. This requires the instance
|
||||
of :class:`~flake8.options.manager.OptionManager` that the program is using,
|
||||
the list of appended config files, and the list of extra arguments. This
|
||||
object is currently the sole user of the
|
||||
:class:`~flake8.options.config.ConfigFileFinder` object. It appropriately
|
||||
initializes the object and uses it in each of
|
||||
|
||||
- :meth:`~flake8.options.config.ConfigParser.parse_cli_config`
|
||||
- :meth:`~flake8.options.config.ConfigParser.parse_local_config`
|
||||
|
||||
Finally, :meth:`~flake8.options.config.ConfigParser.parse` returns the
|
||||
appropriate configuration dictionary for this execution of |Flake8|. The
|
||||
main usage of the ``ConfigParser`` is in
|
||||
:func:`~flake8.options.aggregator.aggregate_options`.
|
||||
Most of this is done in :func:`~flake8.options.aggregator.aggregate_options`.
|
||||
|
||||
Aggregating Configuration File and Command Line Arguments
|
||||
---------------------------------------------------------
|
||||
|
||||
:func:`~flake8.options.aggregator.aggregate_options` accepts an instance of
|
||||
:class:`~flake8.options.manager.OptionManager` and does the work to parse the
|
||||
command-line arguments passed by the user necessary for creating an instance
|
||||
of :class:`~flake8.options.config.ConfigParser`.
|
||||
command-line arguments.
|
||||
|
||||
After parsing the configuration file, we determine the default ignore list. We
|
||||
use the defaults from the OptionManager and update those with the parsed
|
||||
|
|
@ -216,10 +196,6 @@ API Documentation
|
|||
:members:
|
||||
:special-members:
|
||||
|
||||
.. autoclass:: flake8.options.config.ConfigFileFinder
|
||||
:members:
|
||||
:special-members:
|
||||
.. autofunction:: flake8.options.config.load_config
|
||||
|
||||
.. autoclass:: flake8.options.config.ConfigParser
|
||||
:members:
|
||||
:special-members:
|
||||
.. autofunction:: flake8.options.config.parse_config
|
||||
|
|
|
|||
|
|
@ -31,19 +31,16 @@ def get_style_guide(**kwargs):
|
|||
application = app.Application()
|
||||
prelim_opts, remaining_args = application.parse_preliminary_options([])
|
||||
flake8.configure_logging(prelim_opts.verbose, prelim_opts.output_file)
|
||||
config_finder = config.ConfigFileFinder(
|
||||
application.program,
|
||||
prelim_opts.append_config,
|
||||
config_file=prelim_opts.config,
|
||||
ignore_config_files=prelim_opts.isolated,
|
||||
|
||||
cfg, cfg_dir = config.load_config(
|
||||
config=prelim_opts.config,
|
||||
extra=prelim_opts.append_config,
|
||||
isolated=prelim_opts.isolated,
|
||||
)
|
||||
|
||||
application.find_plugins(config_finder)
|
||||
application.find_plugins(cfg, cfg_dir)
|
||||
application.register_plugin_options()
|
||||
application.parse_configuration_and_cli(
|
||||
config_finder,
|
||||
remaining_args,
|
||||
)
|
||||
application.parse_configuration_and_cli(cfg, cfg_dir, remaining_args)
|
||||
# We basically want application.initialize to be called but with these
|
||||
# options set instead before we make our formatter, notifier, internal
|
||||
# style guide and file checker manager.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"""Module containing the application logic for Flake8."""
|
||||
import argparse
|
||||
import configparser
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
|
|
@ -130,24 +131,35 @@ class Application:
|
|||
else:
|
||||
return int((self.result_count > 0) or self.catastrophic_failure)
|
||||
|
||||
def find_plugins(self, config_finder: config.ConfigFileFinder) -> None:
|
||||
def find_plugins(
|
||||
self,
|
||||
cfg: configparser.RawConfigParser,
|
||||
cfg_dir: str,
|
||||
) -> None:
|
||||
"""Find and load the plugins for this application.
|
||||
|
||||
Set the :attr:`check_plugins` and :attr:`formatting_plugins` attributes
|
||||
based on the discovered plugins found.
|
||||
|
||||
:param config.ConfigFileFinder config_finder:
|
||||
The finder for finding and reading configuration files.
|
||||
"""
|
||||
local_plugins = config.get_local_plugins(config_finder)
|
||||
|
||||
sys.path.extend(local_plugins.paths)
|
||||
|
||||
self.check_plugins = plugin_manager.Checkers(local_plugins.extension)
|
||||
|
||||
self.formatting_plugins = plugin_manager.ReportFormatters(
|
||||
local_plugins.report
|
||||
# TODO: move to src/flake8/plugins/finder.py
|
||||
extension_local = utils.parse_comma_separated_list(
|
||||
cfg.get("flake8:local-plugins", "extension", fallback="").strip(),
|
||||
regexp=utils.LOCAL_PLUGIN_LIST_RE,
|
||||
)
|
||||
report_local = utils.parse_comma_separated_list(
|
||||
cfg.get("flake8:local-plugins", "report", fallback="").strip(),
|
||||
regexp=utils.LOCAL_PLUGIN_LIST_RE,
|
||||
)
|
||||
|
||||
paths_s = cfg.get("flake8:local-plugins", "paths", fallback="").strip()
|
||||
local_paths = utils.parse_comma_separated_list(paths_s)
|
||||
local_paths = utils.normalize_paths(local_paths, cfg_dir)
|
||||
|
||||
sys.path.extend(local_paths)
|
||||
|
||||
self.check_plugins = plugin_manager.Checkers(extension_local)
|
||||
|
||||
self.formatting_plugins = plugin_manager.ReportFormatters(report_local)
|
||||
|
||||
self.check_plugins.load_plugins()
|
||||
self.formatting_plugins.load_plugins()
|
||||
|
|
@ -162,19 +174,15 @@ class Application:
|
|||
|
||||
def parse_configuration_and_cli(
|
||||
self,
|
||||
config_finder: config.ConfigFileFinder,
|
||||
cfg: configparser.RawConfigParser,
|
||||
cfg_dir: str,
|
||||
argv: List[str],
|
||||
) -> None:
|
||||
"""Parse configuration files and the CLI options.
|
||||
|
||||
:param config.ConfigFileFinder config_finder:
|
||||
The finder for finding and reading configuration files.
|
||||
:param list argv:
|
||||
Command-line arguments passed in directly.
|
||||
"""
|
||||
"""Parse configuration files and the CLI options."""
|
||||
self.options = aggregator.aggregate_options(
|
||||
self.option_manager,
|
||||
config_finder,
|
||||
cfg,
|
||||
cfg_dir,
|
||||
argv,
|
||||
)
|
||||
|
||||
|
|
@ -329,18 +337,16 @@ class Application:
|
|||
# our legacy API calls to these same methods.
|
||||
prelim_opts, remaining_args = self.parse_preliminary_options(argv)
|
||||
flake8.configure_logging(prelim_opts.verbose, prelim_opts.output_file)
|
||||
config_finder = config.ConfigFileFinder(
|
||||
self.program,
|
||||
prelim_opts.append_config,
|
||||
config_file=prelim_opts.config,
|
||||
ignore_config_files=prelim_opts.isolated,
|
||||
|
||||
cfg, cfg_dir = config.load_config(
|
||||
config=prelim_opts.config,
|
||||
extra=prelim_opts.append_config,
|
||||
isolated=prelim_opts.isolated,
|
||||
)
|
||||
self.find_plugins(config_finder)
|
||||
|
||||
self.find_plugins(cfg, cfg_dir)
|
||||
self.register_plugin_options()
|
||||
self.parse_configuration_and_cli(
|
||||
config_finder,
|
||||
remaining_args,
|
||||
)
|
||||
self.parse_configuration_and_cli(cfg, cfg_dir, remaining_args)
|
||||
self.make_formatter()
|
||||
self.make_guide()
|
||||
self.make_file_checker_manager()
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ def register_preliminary_options(parser: argparse.ArgumentParser) -> None:
|
|||
add_argument(
|
||||
"--append-config",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Provide extra config files to parse in addition to the files "
|
||||
"found by Flake8 by default. These files are the last ones read "
|
||||
"and so they take the highest precedence when multiple files "
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ This holds the logic that uses the collected and merged config files and
|
|||
applies the user-specified command-line configuration on top of it.
|
||||
"""
|
||||
import argparse
|
||||
import configparser
|
||||
import logging
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
from flake8.options import config
|
||||
from flake8.options.manager import OptionManager
|
||||
|
|
@ -15,34 +17,16 @@ LOG = logging.getLogger(__name__)
|
|||
|
||||
def aggregate_options(
|
||||
manager: OptionManager,
|
||||
config_finder: config.ConfigFileFinder,
|
||||
argv: List[str],
|
||||
cfg: configparser.RawConfigParser,
|
||||
cfg_dir: str,
|
||||
argv: Optional[Sequence[str]],
|
||||
) -> argparse.Namespace:
|
||||
"""Aggregate and merge CLI and config file options.
|
||||
|
||||
:param flake8.options.manager.OptionManager manager:
|
||||
The instance of the OptionManager that we're presently using.
|
||||
:param flake8.options.config.ConfigFileFinder config_finder:
|
||||
The config file finder to use.
|
||||
:param list argv:
|
||||
The list of remaining command-line arguments that were unknown during
|
||||
preliminary option parsing to pass to ``manager.parse_args``.
|
||||
:returns:
|
||||
Tuple of the parsed options and extra arguments returned by
|
||||
``manager.parse_args``.
|
||||
:rtype:
|
||||
tuple(argparse.Namespace, list)
|
||||
"""
|
||||
"""Aggregate and merge CLI and config file options."""
|
||||
# Get defaults from the option parser
|
||||
default_values = manager.parse_args([])
|
||||
|
||||
# Make our new configuration file mergerator
|
||||
config_parser = config.ConfigParser(
|
||||
option_manager=manager, config_finder=config_finder
|
||||
)
|
||||
|
||||
# Get the parsed config
|
||||
parsed_config = config_parser.parse()
|
||||
parsed_config = config.parse_config(manager, cfg, cfg_dir)
|
||||
|
||||
# Extend the default ignore value with the extended default ignore list,
|
||||
# registered by plugins.
|
||||
|
|
@ -70,7 +54,9 @@ def aggregate_options(
|
|||
# If the config name is somehow different from the destination name,
|
||||
# fetch the destination name from our Option
|
||||
if not hasattr(default_values, config_name):
|
||||
dest_name = config_parser.config_options[config_name].dest
|
||||
dest_val = manager.config_options_dict[config_name].dest
|
||||
assert isinstance(dest_val, str)
|
||||
dest_name = dest_val
|
||||
|
||||
LOG.debug(
|
||||
'Overriding default value of (%s) for "%s" with (%s)',
|
||||
|
|
|
|||
|
|
@ -1,318 +1,108 @@
|
|||
"""Config handling logic for Flake8."""
|
||||
import collections
|
||||
import configparser
|
||||
import logging
|
||||
import os.path
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from flake8 import utils
|
||||
from flake8.options.manager import OptionManager
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
__all__ = ("ConfigFileFinder", "ConfigParser")
|
||||
|
||||
|
||||
class ConfigFileFinder:
|
||||
"""Encapsulate the logic for finding and reading config files."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
program_name: str,
|
||||
extra_config_files: Optional[List[str]] = None,
|
||||
config_file: Optional[str] = None,
|
||||
ignore_config_files: bool = False,
|
||||
) -> None:
|
||||
"""Initialize object to find config files.
|
||||
|
||||
:param str program_name:
|
||||
Name of the current program (e.g., flake8).
|
||||
:param list extra_config_files:
|
||||
Extra configuration files specified by the user to read.
|
||||
:param str config_file:
|
||||
Configuration file override to only read configuration from.
|
||||
:param bool ignore_config_files:
|
||||
Determine whether to ignore configuration files or not.
|
||||
"""
|
||||
# The values of --append-config from the CLI
|
||||
if extra_config_files is None:
|
||||
extra_config_files = []
|
||||
self.extra_config_files = utils.normalize_paths(extra_config_files)
|
||||
|
||||
# The value of --config from the CLI.
|
||||
self.config_file = config_file
|
||||
|
||||
# The value of --isolated from the CLI.
|
||||
self.ignore_config_files = ignore_config_files
|
||||
|
||||
# User configuration file.
|
||||
self.program_name = program_name
|
||||
|
||||
# List of filenames to find in the local/project directory
|
||||
self.project_filenames = ("setup.cfg", "tox.ini", f".{program_name}")
|
||||
|
||||
self.local_directory = os.path.abspath(os.curdir)
|
||||
|
||||
@staticmethod
|
||||
def _read_config(
|
||||
*files: str,
|
||||
) -> Tuple[configparser.RawConfigParser, List[str]]:
|
||||
config = configparser.RawConfigParser()
|
||||
|
||||
found_files = []
|
||||
for filename in files:
|
||||
def _find_config_file(path: str) -> Optional[str]:
|
||||
cfg = configparser.RawConfigParser()
|
||||
while True:
|
||||
for candidate in ("setup.cfg", "tox.ini", ".flake8"):
|
||||
cfg_path = os.path.join(path, candidate)
|
||||
try:
|
||||
found_files.extend(config.read(filename))
|
||||
except UnicodeDecodeError:
|
||||
LOG.exception(
|
||||
"There was an error decoding a config file."
|
||||
"The file with a problem was %s.",
|
||||
filename,
|
||||
)
|
||||
except configparser.ParsingError:
|
||||
LOG.exception(
|
||||
"There was an error trying to parse a config "
|
||||
"file. The file with a problem was %s.",
|
||||
filename,
|
||||
)
|
||||
return (config, found_files)
|
||||
cfg.read(cfg_path)
|
||||
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
|
||||
|
||||
def cli_config(self, files: str) -> configparser.RawConfigParser:
|
||||
"""Read and parse the config file specified on the command-line."""
|
||||
config, found_files = self._read_config(files)
|
||||
if found_files:
|
||||
LOG.debug("Found cli configuration files: %s", found_files)
|
||||
return config
|
||||
new_path = os.path.dirname(path)
|
||||
if new_path == path:
|
||||
break
|
||||
else:
|
||||
path = new_path
|
||||
|
||||
def generate_possible_local_files(self):
|
||||
"""Find and generate all local config files."""
|
||||
parent = tail = os.getcwd()
|
||||
found_config_files = False
|
||||
while tail and not found_config_files:
|
||||
for project_filename in self.project_filenames:
|
||||
filename = os.path.abspath(
|
||||
os.path.join(parent, project_filename)
|
||||
)
|
||||
if os.path.exists(filename):
|
||||
yield filename
|
||||
found_config_files = True
|
||||
self.local_directory = parent
|
||||
(parent, tail) = os.path.split(parent)
|
||||
|
||||
def local_config_files(self):
|
||||
"""Find all local config files which actually exist.
|
||||
|
||||
Filter results from
|
||||
:meth:`~ConfigFileFinder.generate_possible_local_files` based
|
||||
on whether the filename exists or not.
|
||||
|
||||
:returns:
|
||||
List of files that exist that are local project config files with
|
||||
extra config files appended to that list (which also exist).
|
||||
:rtype:
|
||||
[str]
|
||||
"""
|
||||
exists = os.path.exists
|
||||
return [
|
||||
filename for filename in self.generate_possible_local_files()
|
||||
] + [f for f in self.extra_config_files if exists(f)]
|
||||
|
||||
def local_configs_with_files(self):
|
||||
"""Parse all local config files into one config object.
|
||||
|
||||
Return (config, found_config_files) tuple.
|
||||
"""
|
||||
config, found_files = self._read_config(*self.local_config_files())
|
||||
if found_files:
|
||||
LOG.debug("Found local configuration files: %s", found_files)
|
||||
return (config, found_files)
|
||||
|
||||
def local_configs(self):
|
||||
"""Parse all local config files into one config object."""
|
||||
return self.local_configs_with_files()[0]
|
||||
# did not find any configuration file
|
||||
return None
|
||||
|
||||
|
||||
class ConfigParser:
|
||||
"""Encapsulate merging different types of configuration files.
|
||||
def load_config(
|
||||
config: Optional[str],
|
||||
extra: List[str],
|
||||
*,
|
||||
isolated: bool = False,
|
||||
) -> Tuple[configparser.RawConfigParser, str]:
|
||||
"""Load the configuration given the user options.
|
||||
|
||||
This parses out the options registered that were specified in the
|
||||
configuration files, handles extra configuration files, and returns
|
||||
dictionaries with the parsed values.
|
||||
- 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(".")
|
||||
|
||||
#: Set of actions that should use the
|
||||
#: :meth:`~configparser.RawConfigParser.getbool` method.
|
||||
GETBOOL_ACTIONS = {"store_true", "store_false"}
|
||||
if isolated:
|
||||
return configparser.RawConfigParser(), pwd
|
||||
|
||||
def __init__(self, option_manager, config_finder):
|
||||
"""Initialize the ConfigParser instance.
|
||||
if config is None:
|
||||
config = _find_config_file(pwd)
|
||||
|
||||
:param flake8.options.manager.OptionManager option_manager:
|
||||
Initialized OptionManager.
|
||||
:param flake8.options.config.ConfigFileFinder config_finder:
|
||||
Initialized ConfigFileFinder.
|
||||
"""
|
||||
#: Our instance of flake8.options.manager.OptionManager
|
||||
self.option_manager = option_manager
|
||||
#: The prog value for the cli parser
|
||||
self.program_name = option_manager.program_name
|
||||
#: Mapping of configuration option names to
|
||||
#: :class:`~flake8.options.manager.Option` instances
|
||||
self.config_options = option_manager.config_options_dict
|
||||
#: Our instance of our :class:`~ConfigFileFinder`
|
||||
self.config_finder = config_finder
|
||||
|
||||
def _normalize_value(self, option, value, parent=None):
|
||||
if parent is None:
|
||||
parent = self.config_finder.local_directory
|
||||
|
||||
final_value = option.normalize(value, parent)
|
||||
LOG.debug(
|
||||
'%r has been normalized to %r for option "%s"',
|
||||
value,
|
||||
final_value,
|
||||
option.config_name,
|
||||
)
|
||||
return final_value
|
||||
|
||||
def _parse_config(self, config_parser, parent=None):
|
||||
config_dict = {}
|
||||
for option_name in config_parser.options(self.program_name):
|
||||
if option_name not in self.config_options:
|
||||
LOG.debug(
|
||||
'Option "%s" is not registered. Ignoring.', option_name
|
||||
)
|
||||
continue
|
||||
option = self.config_options[option_name]
|
||||
|
||||
# Use the appropriate method to parse the config value
|
||||
method = config_parser.get
|
||||
if option.type is int or option.action == "count":
|
||||
method = config_parser.getint
|
||||
elif option.action in self.GETBOOL_ACTIONS:
|
||||
method = config_parser.getboolean
|
||||
|
||||
value = method(self.program_name, option_name)
|
||||
LOG.debug('Option "%s" returned value: %r', option_name, value)
|
||||
|
||||
final_value = self._normalize_value(option, value, parent)
|
||||
config_dict[option.config_name] = final_value
|
||||
|
||||
return config_dict
|
||||
|
||||
def is_configured_by(self, config):
|
||||
"""Check if the specified config parser has an appropriate section."""
|
||||
return config.has_section(self.program_name)
|
||||
|
||||
def parse_local_config(self):
|
||||
"""Parse and return the local configuration files."""
|
||||
config = self.config_finder.local_configs()
|
||||
if not self.is_configured_by(config):
|
||||
LOG.debug(
|
||||
"Local configuration files have no %s section",
|
||||
self.program_name,
|
||||
)
|
||||
return {}
|
||||
|
||||
LOG.debug("Parsing local configuration files.")
|
||||
return self._parse_config(config)
|
||||
|
||||
def parse_cli_config(self, config_path):
|
||||
"""Parse and return the file specified by --config."""
|
||||
config = self.config_finder.cli_config(config_path)
|
||||
if not self.is_configured_by(config):
|
||||
LOG.debug(
|
||||
"CLI configuration files have no %s section",
|
||||
self.program_name,
|
||||
)
|
||||
return {}
|
||||
|
||||
LOG.debug("Parsing CLI configuration files.")
|
||||
return self._parse_config(config, os.path.dirname(config_path))
|
||||
|
||||
def parse(self):
|
||||
"""Parse and return the local config files.
|
||||
|
||||
:returns:
|
||||
Dictionary of parsed configuration options
|
||||
:rtype:
|
||||
dict
|
||||
"""
|
||||
if self.config_finder.ignore_config_files:
|
||||
LOG.debug(
|
||||
"Refusing to parse configuration files due to user-"
|
||||
"requested isolation"
|
||||
)
|
||||
return {}
|
||||
|
||||
if self.config_finder.config_file:
|
||||
LOG.debug(
|
||||
"Ignoring user and locally found configuration files. "
|
||||
'Reading only configuration from "%s" specified via '
|
||||
"--config by the user",
|
||||
self.config_finder.config_file,
|
||||
)
|
||||
return self.parse_cli_config(self.config_finder.config_file)
|
||||
|
||||
return self.parse_local_config()
|
||||
|
||||
|
||||
def get_local_plugins(config_finder):
|
||||
"""Get local plugins lists from config files.
|
||||
|
||||
:param flake8.options.config.ConfigFileFinder config_finder:
|
||||
The config file finder to use.
|
||||
:returns:
|
||||
LocalPlugins namedtuple containing two lists of plugin strings,
|
||||
one for extension (checker) plugins and one for report plugins.
|
||||
:rtype:
|
||||
flake8.options.config.LocalPlugins
|
||||
"""
|
||||
local_plugins = LocalPlugins(extension=[], report=[], paths=[])
|
||||
if config_finder.ignore_config_files:
|
||||
LOG.debug(
|
||||
"Refusing to look for local plugins in configuration"
|
||||
"files due to user-requested isolation"
|
||||
)
|
||||
return local_plugins
|
||||
|
||||
if config_finder.config_file:
|
||||
LOG.debug(
|
||||
'Reading local plugins only from "%s" specified via '
|
||||
"--config by the user",
|
||||
config_finder.config_file,
|
||||
)
|
||||
config = config_finder.cli_config(config_finder.config_file)
|
||||
config_files = [config_finder.config_file]
|
||||
cfg = configparser.RawConfigParser()
|
||||
if config is not None:
|
||||
cfg.read(config)
|
||||
cfg_dir = os.path.dirname(config)
|
||||
else:
|
||||
config, config_files = config_finder.local_configs_with_files()
|
||||
cfg_dir = pwd
|
||||
|
||||
base_dirs = {os.path.dirname(cf) for cf in config_files}
|
||||
# TODO: remove this and replace it with configuration modifying plugins
|
||||
# read the additional configs afterwards
|
||||
for filename in extra:
|
||||
cfg.read(filename)
|
||||
|
||||
section = f"{config_finder.program_name}:local-plugins"
|
||||
for plugin_type in ["extension", "report"]:
|
||||
if config.has_option(section, plugin_type):
|
||||
local_plugins_string = config.get(section, plugin_type).strip()
|
||||
plugin_type_list = getattr(local_plugins, plugin_type)
|
||||
plugin_type_list.extend(
|
||||
utils.parse_comma_separated_list(
|
||||
local_plugins_string, regexp=utils.LOCAL_PLUGIN_LIST_RE
|
||||
)
|
||||
)
|
||||
if config.has_option(section, "paths"):
|
||||
raw_paths = utils.parse_comma_separated_list(
|
||||
config.get(section, "paths").strip()
|
||||
)
|
||||
norm_paths: List[str] = []
|
||||
for base_dir in base_dirs:
|
||||
norm_paths.extend(
|
||||
path
|
||||
for path in utils.normalize_paths(raw_paths, parent=base_dir)
|
||||
if os.path.exists(path)
|
||||
)
|
||||
local_plugins.paths.extend(norm_paths)
|
||||
return local_plugins
|
||||
return cfg, cfg_dir
|
||||
|
||||
|
||||
LocalPlugins = collections.namedtuple("LocalPlugins", "extension report paths")
|
||||
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)
|
||||
assert option.config_name is not None
|
||||
config_dict[option.config_name] = final_value
|
||||
|
||||
return config_dict
|
||||
|
|
|
|||
|
|
@ -462,7 +462,7 @@ class OptionManager:
|
|||
|
||||
def parse_args(
|
||||
self,
|
||||
args: Optional[List[str]] = None,
|
||||
args: Optional[Sequence[str]] = None,
|
||||
values: Optional[argparse.Namespace] = None,
|
||||
) -> argparse.Namespace:
|
||||
"""Proxy to calling the OptionParser's parse_args method."""
|
||||
|
|
|
|||
10
tests/fixtures/config_files/README.rst
vendored
10
tests/fixtures/config_files/README.rst
vendored
|
|
@ -16,17 +16,7 @@ Files that should not be created
|
|||
Purposes of existing fixtures
|
||||
-----------------------------
|
||||
|
||||
``tests/fixtures/config_files/cli-specified.ini``
|
||||
|
||||
This should only be used when providing config file(s) specified by the
|
||||
user on the command-line.
|
||||
|
||||
``tests/fixtures/config_files/local-plugin.ini``
|
||||
|
||||
This is for testing configuring a plugin via flake8 config file instead of
|
||||
setuptools entry-point.
|
||||
|
||||
``tests/fixtures/config_files/no-flake8-section.ini``
|
||||
|
||||
This should be used when parsing an ini file without a ``[flake8]``
|
||||
section.
|
||||
|
|
|
|||
9
tests/fixtures/config_files/broken.ini
vendored
9
tests/fixtures/config_files/broken.ini
vendored
|
|
@ -1,9 +0,0 @@
|
|||
[flake8]
|
||||
exclude =
|
||||
<<<<<<< 642f88cb1b6027e184d9a662b255f7fea4d9eacc
|
||||
tests/fixtures/,
|
||||
=======
|
||||
tests/,
|
||||
>>>>>>> HEAD
|
||||
docs/
|
||||
ignore = D203
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
[flake8]
|
||||
# This is a flake8 config, there are many like it, but this is mine
|
||||
ignore =
|
||||
# Disable E123
|
||||
E123,
|
||||
# Disable W234
|
||||
W234,
|
||||
# Also disable E111
|
||||
E111
|
||||
exclude =
|
||||
# Exclude foo/
|
||||
foo/,
|
||||
# Exclude bar/ while we're at it
|
||||
bar/,
|
||||
# Exclude bogus/
|
||||
bogus/
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
[flake8]
|
||||
# This is a flake8 config, there are many like it, but this is mine
|
||||
# Disable E123
|
||||
# Disable W234
|
||||
# Also disable E111
|
||||
ignore =
|
||||
E123,
|
||||
W234,
|
||||
E111
|
||||
# Exclude foo/
|
||||
# Exclude bar/ while we're at it
|
||||
# Exclude bogus/
|
||||
exclude =
|
||||
foo/,
|
||||
bar/,
|
||||
bogus/
|
||||
10
tests/fixtures/config_files/cli-specified.ini
vendored
10
tests/fixtures/config_files/cli-specified.ini
vendored
|
|
@ -1,10 +0,0 @@
|
|||
[flake8]
|
||||
ignore =
|
||||
E123,
|
||||
W234,
|
||||
E111
|
||||
exclude =
|
||||
foo/,
|
||||
bar/,
|
||||
bogus/
|
||||
quiet = 1
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[flake8]
|
||||
max-line-length = 110
|
||||
enable_extensions =
|
||||
H101,
|
||||
H235
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
[tox]
|
||||
minversion=2.3.1
|
||||
envlist = py26,py27,py32,py33,py34,py35,flake8
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
mock
|
||||
pytest
|
||||
commands =
|
||||
py.test {posargs}
|
||||
|
||||
[testenv:flake8]
|
||||
skipsdist = true
|
||||
skip_install = true
|
||||
use_develop = false
|
||||
deps =
|
||||
flake8
|
||||
flake8-docstrings
|
||||
commands =
|
||||
flake8
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
"""Test aggregation of config files and command-line options."""
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
|
@ -9,24 +8,38 @@ from flake8.options import aggregator
|
|||
from flake8.options import config
|
||||
from flake8.options import manager
|
||||
|
||||
CLI_SPECIFIED_CONFIG = "tests/fixtures/config_files/cli-specified.ini"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def optmanager():
|
||||
"""Create a new OptionManager."""
|
||||
prelim_parser = argparse.ArgumentParser(add_help=False)
|
||||
options.register_preliminary_options(prelim_parser)
|
||||
option_manager = manager.OptionManager(
|
||||
prog="flake8",
|
||||
version="3.0.0",
|
||||
parents=[prelim_parser],
|
||||
)
|
||||
options.register_default_options(option_manager)
|
||||
return option_manager
|
||||
|
||||
|
||||
def test_aggregate_options_with_config(optmanager):
|
||||
@pytest.fixture
|
||||
def flake8_config(tmp_path):
|
||||
cfg_s = """\
|
||||
[flake8]
|
||||
ignore =
|
||||
E123,
|
||||
W234,
|
||||
E111
|
||||
exclude =
|
||||
foo/,
|
||||
bar/,
|
||||
bogus/
|
||||
quiet = 1
|
||||
"""
|
||||
cfg = tmp_path.joinpath("tox.ini")
|
||||
cfg.write_text(cfg_s)
|
||||
return str(cfg)
|
||||
|
||||
|
||||
def test_aggregate_options_with_config(optmanager, flake8_config):
|
||||
"""Verify we aggregate options and config values appropriately."""
|
||||
arguments = [
|
||||
"flake8",
|
||||
|
|
@ -35,11 +48,12 @@ def test_aggregate_options_with_config(optmanager):
|
|||
"--exclude",
|
||||
"tests/*",
|
||||
]
|
||||
config_finder = config.ConfigFileFinder(
|
||||
"flake8", config_file=CLI_SPECIFIED_CONFIG
|
||||
)
|
||||
cfg, cfg_dir = config.load_config(flake8_config, [])
|
||||
options = aggregator.aggregate_options(
|
||||
optmanager, config_finder, arguments
|
||||
optmanager,
|
||||
cfg,
|
||||
cfg_dir,
|
||||
arguments,
|
||||
)
|
||||
|
||||
assert options.select == ["E11", "E34", "E402", "W", "F"]
|
||||
|
|
@ -47,7 +61,7 @@ def test_aggregate_options_with_config(optmanager):
|
|||
assert options.exclude == [os.path.abspath("tests/*")]
|
||||
|
||||
|
||||
def test_aggregate_options_when_isolated(optmanager):
|
||||
def test_aggregate_options_when_isolated(optmanager, flake8_config):
|
||||
"""Verify we aggregate options and config values appropriately."""
|
||||
arguments = [
|
||||
"flake8",
|
||||
|
|
@ -56,11 +70,9 @@ def test_aggregate_options_when_isolated(optmanager):
|
|||
"--exclude",
|
||||
"tests/*",
|
||||
]
|
||||
config_finder = config.ConfigFileFinder("flake8", ignore_config_files=True)
|
||||
cfg, cfg_dir = config.load_config(flake8_config, [], isolated=True)
|
||||
optmanager.extend_default_ignore(["E8"])
|
||||
options = aggregator.aggregate_options(
|
||||
optmanager, config_finder, arguments
|
||||
)
|
||||
options = aggregator.aggregate_options(optmanager, cfg, cfg_dir, arguments)
|
||||
|
||||
assert options.select == ["E11", "E34", "E402", "W", "F"]
|
||||
assert sorted(options.ignore) == [
|
||||
|
|
|
|||
|
|
@ -1,143 +0,0 @@
|
|||
"""Tests for the ConfigFileFinder."""
|
||||
import configparser
|
||||
import os
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from flake8.options import config
|
||||
|
||||
CLI_SPECIFIED_FILEPATH = "tests/fixtures/config_files/cli-specified.ini"
|
||||
BROKEN_CONFIG_PATH = "tests/fixtures/config_files/broken.ini"
|
||||
|
||||
|
||||
def test_cli_config():
|
||||
"""Verify opening and reading the file specified via the cli."""
|
||||
cli_filepath = CLI_SPECIFIED_FILEPATH
|
||||
finder = config.ConfigFileFinder("flake8")
|
||||
|
||||
parsed_config = finder.cli_config(cli_filepath)
|
||||
assert parsed_config.has_section("flake8")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"cwd,expected",
|
||||
[
|
||||
# Root directory of project
|
||||
(
|
||||
os.path.abspath("."),
|
||||
[os.path.abspath("setup.cfg"), os.path.abspath("tox.ini")],
|
||||
),
|
||||
# Subdirectory of project directory
|
||||
(
|
||||
os.path.abspath("src"),
|
||||
[os.path.abspath("setup.cfg"), os.path.abspath("tox.ini")],
|
||||
),
|
||||
# Outside of project directory
|
||||
(os.path.abspath("/"), []),
|
||||
],
|
||||
)
|
||||
def test_generate_possible_local_files(cwd, expected):
|
||||
"""Verify generation of all possible config paths."""
|
||||
finder = config.ConfigFileFinder("flake8")
|
||||
|
||||
with mock.patch.object(os, "getcwd", return_value=cwd):
|
||||
config_files = list(finder.generate_possible_local_files())
|
||||
|
||||
assert config_files == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"extra_config_files,expected",
|
||||
[
|
||||
# Extra config files specified
|
||||
(
|
||||
[CLI_SPECIFIED_FILEPATH],
|
||||
[
|
||||
os.path.abspath("setup.cfg"),
|
||||
os.path.abspath("tox.ini"),
|
||||
os.path.abspath(CLI_SPECIFIED_FILEPATH),
|
||||
],
|
||||
),
|
||||
# Missing extra config files specified
|
||||
(
|
||||
[
|
||||
CLI_SPECIFIED_FILEPATH,
|
||||
"tests/fixtures/config_files/missing.ini",
|
||||
],
|
||||
[
|
||||
os.path.abspath("setup.cfg"),
|
||||
os.path.abspath("tox.ini"),
|
||||
os.path.abspath(CLI_SPECIFIED_FILEPATH),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_local_config_files(extra_config_files, expected):
|
||||
"""Verify discovery of local config files."""
|
||||
finder = config.ConfigFileFinder("flake8", extra_config_files)
|
||||
|
||||
assert list(finder.local_config_files()) == expected
|
||||
|
||||
|
||||
def test_local_configs():
|
||||
"""Verify we return a ConfigParser."""
|
||||
finder = config.ConfigFileFinder("flake8")
|
||||
|
||||
assert isinstance(finder.local_configs(), configparser.RawConfigParser)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"files",
|
||||
[
|
||||
[BROKEN_CONFIG_PATH],
|
||||
[CLI_SPECIFIED_FILEPATH, BROKEN_CONFIG_PATH],
|
||||
],
|
||||
)
|
||||
def test_read_config_catches_broken_config_files(files):
|
||||
"""Verify that we do not allow the exception to bubble up."""
|
||||
_, parsed = config.ConfigFileFinder._read_config(*files)
|
||||
assert BROKEN_CONFIG_PATH not in parsed
|
||||
|
||||
|
||||
def test_read_config_catches_decoding_errors(tmpdir):
|
||||
"""Verify that we do not allow the exception to bubble up."""
|
||||
setup_cfg = tmpdir.join("setup.cfg")
|
||||
# pick bytes that are unlikely to decode
|
||||
setup_cfg.write_binary(b"[x]\ny = \x81\x8d\x90\x9d")
|
||||
_, parsed = config.ConfigFileFinder._read_config(setup_cfg.strpath)
|
||||
assert parsed == []
|
||||
|
||||
|
||||
def test_config_file_default_value():
|
||||
"""Verify the default 'config_file' attribute value."""
|
||||
finder = config.ConfigFileFinder("flake8")
|
||||
assert finder.config_file is None
|
||||
|
||||
|
||||
def test_setting_config_file_value():
|
||||
"""Verify the 'config_file' attribute matches constructed value."""
|
||||
config_file_value = "flake8.ini"
|
||||
finder = config.ConfigFileFinder("flake8", config_file=config_file_value)
|
||||
assert finder.config_file == config_file_value
|
||||
|
||||
|
||||
def test_ignore_config_files_default_value():
|
||||
"""Verify the default 'ignore_config_files' attribute value."""
|
||||
finder = config.ConfigFileFinder("flake8")
|
||||
assert finder.ignore_config_files is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"ignore_config_files_arg",
|
||||
[
|
||||
False,
|
||||
True,
|
||||
],
|
||||
)
|
||||
def test_setting_ignore_config_files_value(ignore_config_files_arg):
|
||||
"""Verify the 'ignore_config_files' attribute matches constructed value."""
|
||||
finder = config.ConfigFileFinder(
|
||||
"flake8", ignore_config_files=ignore_config_files_arg
|
||||
)
|
||||
assert finder.ignore_config_files is ignore_config_files_arg
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
"""Unit tests for flake8.options.config.ConfigParser."""
|
||||
import os
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from flake8.options import config
|
||||
from flake8.options import manager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def optmanager():
|
||||
"""Generate an OptionManager with simple values."""
|
||||
return manager.OptionManager(prog="flake8", version="3.0.0a1")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config_finder():
|
||||
"""Generate a simple ConfigFileFinder."""
|
||||
return config.ConfigFileFinder("flake8")
|
||||
|
||||
|
||||
def test_parse_cli_config(optmanager, config_finder):
|
||||
"""Parse the specified config file as a cli config file."""
|
||||
optmanager.add_option(
|
||||
"--exclude",
|
||||
parse_from_config=True,
|
||||
comma_separated_list=True,
|
||||
normalize_paths=True,
|
||||
)
|
||||
optmanager.add_option(
|
||||
"--ignore", parse_from_config=True, comma_separated_list=True
|
||||
)
|
||||
optmanager.add_option("--quiet", parse_from_config=True, action="count")
|
||||
parser = config.ConfigParser(optmanager, config_finder)
|
||||
|
||||
config_file = "tests/fixtures/config_files/cli-specified.ini"
|
||||
parsed_config = parser.parse_cli_config(config_file)
|
||||
|
||||
config_dir = os.path.dirname(config_file)
|
||||
assert parsed_config == {
|
||||
"ignore": ["E123", "W234", "E111"],
|
||||
"exclude": [
|
||||
os.path.abspath(os.path.join(config_dir, "foo/")),
|
||||
os.path.abspath(os.path.join(config_dir, "bar/")),
|
||||
os.path.abspath(os.path.join(config_dir, "bogus/")),
|
||||
],
|
||||
"quiet": 1,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filename,is_configured_by",
|
||||
[
|
||||
("tests/fixtures/config_files/cli-specified.ini", True),
|
||||
("tests/fixtures/config_files/no-flake8-section.ini", False),
|
||||
],
|
||||
)
|
||||
def test_is_configured_by(
|
||||
filename, is_configured_by, optmanager, config_finder
|
||||
):
|
||||
"""Verify the behaviour of the is_configured_by method."""
|
||||
parsed_config, _ = config.ConfigFileFinder._read_config(filename)
|
||||
parser = config.ConfigParser(optmanager, config_finder)
|
||||
|
||||
assert parser.is_configured_by(parsed_config) is is_configured_by
|
||||
|
||||
|
||||
def test_parse_local_config(optmanager, config_finder):
|
||||
"""Verify parsing of local config files."""
|
||||
optmanager.add_option(
|
||||
"--exclude",
|
||||
parse_from_config=True,
|
||||
comma_separated_list=True,
|
||||
normalize_paths=True,
|
||||
)
|
||||
optmanager.add_option(
|
||||
"--ignore", parse_from_config=True, comma_separated_list=True
|
||||
)
|
||||
optmanager.add_option("--quiet", parse_from_config=True, action="count")
|
||||
parser = config.ConfigParser(optmanager, config_finder)
|
||||
|
||||
with mock.patch.object(config_finder, "local_config_files") as localcfs:
|
||||
localcfs.return_value = [
|
||||
"tests/fixtures/config_files/cli-specified.ini"
|
||||
]
|
||||
parsed_config = parser.parse_local_config()
|
||||
|
||||
assert parsed_config == {
|
||||
"ignore": ["E123", "W234", "E111"],
|
||||
"exclude": [
|
||||
os.path.abspath("foo/"),
|
||||
os.path.abspath("bar/"),
|
||||
os.path.abspath("bogus/"),
|
||||
],
|
||||
"quiet": 1,
|
||||
}
|
||||
|
||||
|
||||
def test_parse_isolates_config(optmanager):
|
||||
"""Verify behaviour of the parse method with isolated=True."""
|
||||
config_finder = mock.MagicMock()
|
||||
config_finder.ignore_config_files = True
|
||||
parser = config.ConfigParser(optmanager, config_finder)
|
||||
|
||||
assert parser.parse() == {}
|
||||
assert config_finder.local_configs.called is False
|
||||
|
||||
|
||||
def test_parse_uses_cli_config(optmanager):
|
||||
"""Verify behaviour of the parse method with a specified config."""
|
||||
config_file_value = "foo.ini"
|
||||
config_finder = mock.MagicMock()
|
||||
config_finder.config_file = config_file_value
|
||||
config_finder.ignore_config_files = False
|
||||
parser = config.ConfigParser(optmanager, config_finder)
|
||||
|
||||
parser.parse()
|
||||
config_finder.cli_config.assert_called_once_with(config_file_value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"config_fixture_path",
|
||||
[
|
||||
"tests/fixtures/config_files/cli-specified.ini",
|
||||
"tests/fixtures/config_files/cli-specified-with-inline-comments.ini",
|
||||
"tests/fixtures/config_files/cli-specified-without-inline-comments.ini", # noqa: E501
|
||||
],
|
||||
)
|
||||
def test_parsed_configs_are_equivalent(
|
||||
optmanager, config_finder, config_fixture_path
|
||||
):
|
||||
"""Verify the each file matches the expected parsed output.
|
||||
|
||||
This is used to ensure our documented behaviour does not regress.
|
||||
"""
|
||||
optmanager.add_option(
|
||||
"--exclude",
|
||||
parse_from_config=True,
|
||||
comma_separated_list=True,
|
||||
normalize_paths=True,
|
||||
)
|
||||
optmanager.add_option(
|
||||
"--ignore", parse_from_config=True, comma_separated_list=True
|
||||
)
|
||||
parser = config.ConfigParser(optmanager, config_finder)
|
||||
|
||||
with mock.patch.object(config_finder, "local_config_files") as localcfs:
|
||||
localcfs.return_value = [config_fixture_path]
|
||||
parsed_config = parser.parse()
|
||||
|
||||
assert parsed_config["ignore"] == ["E123", "W234", "E111"]
|
||||
assert parsed_config["exclude"] == [
|
||||
os.path.abspath("foo/"),
|
||||
os.path.abspath("bar/"),
|
||||
os.path.abspath("bogus/"),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"config_file",
|
||||
["tests/fixtures/config_files/config-with-hyphenated-options.ini"],
|
||||
)
|
||||
def test_parsed_hyphenated_and_underscored_names(
|
||||
optmanager, config_finder, config_file
|
||||
):
|
||||
"""Verify we find hyphenated option names as well as underscored.
|
||||
|
||||
This tests for options like --max-line-length and --enable-extensions
|
||||
which are able to be specified either as max-line-length or
|
||||
max_line_length in our config files.
|
||||
"""
|
||||
optmanager.add_option(
|
||||
"--max-line-length", parse_from_config=True, type=int
|
||||
)
|
||||
optmanager.add_option(
|
||||
"--enable-extensions",
|
||||
parse_from_config=True,
|
||||
comma_separated_list=True,
|
||||
)
|
||||
parser = config.ConfigParser(optmanager, config_finder)
|
||||
|
||||
with mock.patch.object(config_finder, "local_config_files") as localcfs:
|
||||
localcfs.return_value = [config_file]
|
||||
parsed_config = parser.parse()
|
||||
|
||||
assert parsed_config["max_line_length"] == 110
|
||||
assert parsed_config["enable_extensions"] == ["H101", "H235"]
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
"""Tests for get_local_plugins."""
|
||||
from unittest import mock
|
||||
|
||||
from flake8.options import config
|
||||
|
||||
|
||||
def test_get_local_plugins_respects_isolated():
|
||||
"""Verify behaviour of get_local_plugins with isolated=True."""
|
||||
config_finder = mock.MagicMock()
|
||||
config_finder.ignore_config_files = True
|
||||
|
||||
local_plugins = config.get_local_plugins(config_finder)
|
||||
|
||||
assert local_plugins.extension == []
|
||||
assert local_plugins.report == []
|
||||
assert config_finder.local_configs.called is False
|
||||
assert config_finder.user_config.called is False
|
||||
|
||||
|
||||
def test_get_local_plugins_uses_cli_config():
|
||||
"""Verify behaviour of get_local_plugins with a specified config."""
|
||||
config_obj = mock.Mock()
|
||||
config_finder = mock.MagicMock()
|
||||
config_finder.cli_config.return_value = config_obj
|
||||
config_finder.ignore_config_files = False
|
||||
config_obj.get.return_value = ""
|
||||
config_file_value = "foo.ini"
|
||||
config_finder.config_file = config_file_value
|
||||
|
||||
config.get_local_plugins(config_finder)
|
||||
|
||||
config_finder.cli_config.assert_called_once_with(config_file_value)
|
||||
|
||||
|
||||
def test_get_local_plugins():
|
||||
"""Verify get_local_plugins returns expected plugins."""
|
||||
config_fixture_path = "tests/fixtures/config_files/local-plugin.ini"
|
||||
config_finder = config.ConfigFileFinder("flake8")
|
||||
|
||||
with mock.patch.object(config_finder, "local_config_files") as localcfs:
|
||||
localcfs.return_value = [config_fixture_path]
|
||||
local_plugins = config.get_local_plugins(config_finder)
|
||||
|
||||
assert local_plugins.extension == [
|
||||
"XE = tests.integration.test_plugins:ExtensionTestPlugin"
|
||||
]
|
||||
assert local_plugins.report == [
|
||||
"XR = tests.integration.test_plugins:ReportTestPlugin"
|
||||
]
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
"""Tests for Flake8's legacy API."""
|
||||
import argparse
|
||||
import configparser
|
||||
import os.path
|
||||
from unittest import mock
|
||||
|
||||
|
|
@ -7,7 +8,7 @@ import pytest
|
|||
|
||||
from flake8.api import legacy as api
|
||||
from flake8.formatting import base as formatter
|
||||
from flake8.options.config import ConfigFileFinder
|
||||
from flake8.options import config
|
||||
|
||||
|
||||
def test_get_style_guide():
|
||||
|
|
@ -22,22 +23,21 @@ def test_get_style_guide():
|
|||
mockedapp = mock.Mock()
|
||||
mockedapp.parse_preliminary_options.return_value = (prelim_opts, [])
|
||||
mockedapp.program = "flake8"
|
||||
with mock.patch(
|
||||
"flake8.api.legacy.config.ConfigFileFinder"
|
||||
) as mock_config_finder: # noqa: E501
|
||||
config_finder = ConfigFileFinder(mockedapp.program)
|
||||
mock_config_finder.return_value = config_finder
|
||||
|
||||
cfg = configparser.RawConfigParser()
|
||||
cfg_dir = os.getcwd()
|
||||
|
||||
with mock.patch.object(config, "load_config", return_value=(cfg, cfg_dir)):
|
||||
with mock.patch("flake8.main.application.Application") as application:
|
||||
application.return_value = mockedapp
|
||||
style_guide = api.get_style_guide()
|
||||
|
||||
application.assert_called_once_with()
|
||||
mockedapp.parse_preliminary_options.assert_called_once_with([])
|
||||
mockedapp.find_plugins.assert_called_once_with(config_finder)
|
||||
mockedapp.find_plugins.assert_called_once_with(cfg, cfg_dir)
|
||||
mockedapp.register_plugin_options.assert_called_once_with()
|
||||
mockedapp.parse_configuration_and_cli.assert_called_once_with(
|
||||
config_finder, []
|
||||
cfg, cfg_dir, []
|
||||
)
|
||||
mockedapp.make_formatter.assert_called_once_with()
|
||||
mockedapp.make_guide.assert_called_once_with()
|
||||
|
|
|
|||
166
tests/unit/test_options_config.py
Normal file
166
tests/unit/test_options_config.py
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
import configparser
|
||||
|
||||
import pytest
|
||||
|
||||
from flake8.main.options import register_default_options
|
||||
from flake8.options import config
|
||||
from flake8.options.manager import OptionManager
|
||||
|
||||
|
||||
def test_config_not_found_returns_none(tmp_path):
|
||||
assert config._find_config_file(str(tmp_path)) is None
|
||||
|
||||
|
||||
def test_config_file_without_section_is_not_considered(tmp_path):
|
||||
tmp_path.joinpath("setup.cfg").touch()
|
||||
|
||||
assert config._find_config_file(str(tmp_path)) is None
|
||||
|
||||
|
||||
def test_config_file_with_parse_error_is_not_considered(tmp_path, caplog):
|
||||
tmp_path.joinpath("setup.cfg").write_text("[error")
|
||||
|
||||
assert config._find_config_file(str(tmp_path)) is None
|
||||
|
||||
assert len(caplog.record_tuples) == 1
|
||||
((mod, level, msg),) = caplog.record_tuples
|
||||
assert (mod, level) == ("flake8.options.config", 30)
|
||||
assert msg.startswith("ignoring unparseable config ")
|
||||
|
||||
|
||||
def test_config_file_with_encoding_error_is_not_considered(tmp_path, caplog):
|
||||
tmp_path.joinpath("setup.cfg").write_bytes(b"\xa0\xef\xfe\x12")
|
||||
|
||||
assert config._find_config_file(str(tmp_path)) is None
|
||||
|
||||
assert len(caplog.record_tuples) == 1
|
||||
((mod, level, msg),) = caplog.record_tuples
|
||||
assert (mod, level) == ("flake8.options.config", 30)
|
||||
assert msg.startswith("ignoring unparseable config ")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cfg_name", ("setup.cfg", "tox.ini", ".flake8"))
|
||||
def test_find_config_file_exists_at_path(tmp_path, cfg_name):
|
||||
expected = tmp_path.joinpath(cfg_name)
|
||||
expected.write_text("[flake8]")
|
||||
|
||||
assert config._find_config_file(str(tmp_path)) == str(expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("section", ("flake8", "flake8:local-plugins"))
|
||||
def test_find_config_either_section(tmp_path, section):
|
||||
expected = tmp_path.joinpath("setup.cfg")
|
||||
expected.write_text(f"[{section}]")
|
||||
|
||||
assert config._find_config_file(str(tmp_path)) == str(expected)
|
||||
|
||||
|
||||
def test_find_config_searches_upwards(tmp_path):
|
||||
subdir = tmp_path.joinpath("d")
|
||||
subdir.mkdir()
|
||||
|
||||
expected = tmp_path.joinpath("setup.cfg")
|
||||
expected.write_text("[flake8]")
|
||||
|
||||
assert config._find_config_file(str(subdir)) == str(expected)
|
||||
|
||||
|
||||
def test_load_config_config_specified_skips_discovery(tmpdir):
|
||||
tmpdir.join("setup.cfg").write("[flake8]\nindent-size=2\n")
|
||||
custom_cfg = tmpdir.join("custom.cfg")
|
||||
custom_cfg.write("[flake8]\nindent-size=8\n")
|
||||
|
||||
with tmpdir.as_cwd():
|
||||
cfg, cfg_dir = config.load_config(str(custom_cfg), [], isolated=False)
|
||||
|
||||
assert cfg.get("flake8", "indent-size") == "8"
|
||||
assert cfg_dir == str(tmpdir)
|
||||
|
||||
|
||||
def test_load_config_no_config_file_does_discovery(tmpdir):
|
||||
tmpdir.join("setup.cfg").write("[flake8]\nindent-size=2\n")
|
||||
|
||||
with tmpdir.as_cwd():
|
||||
cfg, cfg_dir = config.load_config(None, [], isolated=False)
|
||||
|
||||
assert cfg.get("flake8", "indent-size") == "2"
|
||||
assert cfg_dir == str(tmpdir)
|
||||
|
||||
|
||||
def test_load_config_no_config_found_sets_cfg_dir_to_pwd(tmpdir):
|
||||
with tmpdir.as_cwd():
|
||||
cfg, cfg_dir = config.load_config(None, [], isolated=False)
|
||||
|
||||
assert cfg.sections() == []
|
||||
assert cfg_dir == str(tmpdir)
|
||||
|
||||
|
||||
def test_load_config_isolated_ignores_configuration(tmpdir):
|
||||
tmpdir.join("setup.cfg").write("[flake8]\nindent-size=2\n")
|
||||
|
||||
with tmpdir.as_cwd():
|
||||
cfg, cfg_dir = config.load_config(None, [], isolated=True)
|
||||
|
||||
assert cfg.sections() == []
|
||||
assert cfg_dir == str(tmpdir)
|
||||
|
||||
|
||||
def test_load_config_append_config(tmpdir):
|
||||
tmpdir.join("setup.cfg").write("[flake8]\nindent-size=2\n")
|
||||
other = tmpdir.join("other.cfg")
|
||||
other.write("[flake8]\nindent-size=8\n")
|
||||
|
||||
with tmpdir.as_cwd():
|
||||
cfg, cfg_dir = config.load_config(None, [str(other)], isolated=False)
|
||||
|
||||
assert cfg.get("flake8", "indent-size") == "8"
|
||||
assert cfg_dir == str(tmpdir)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def opt_manager():
|
||||
ret = OptionManager(prog="flake8", version="123")
|
||||
register_default_options(ret)
|
||||
return ret
|
||||
|
||||
|
||||
def test_parse_config_no_values(tmp_path, opt_manager):
|
||||
cfg = configparser.RawConfigParser()
|
||||
ret = config.parse_config(opt_manager, cfg, tmp_path)
|
||||
assert ret == {}
|
||||
|
||||
|
||||
def test_parse_config_typed_values(tmp_path, opt_manager):
|
||||
cfg = configparser.RawConfigParser()
|
||||
cfg.add_section("flake8")
|
||||
cfg.set("flake8", "indent_size", "2")
|
||||
cfg.set("flake8", "hang_closing", "true")
|
||||
# test normalizing dashed-options
|
||||
cfg.set("flake8", "extend-exclude", "d/1,d/2")
|
||||
|
||||
ret = config.parse_config(opt_manager, cfg, str(tmp_path))
|
||||
assert ret == {
|
||||
"indent_size": 2,
|
||||
"hang_closing": True,
|
||||
"extend_exclude": [
|
||||
str(tmp_path.joinpath("d/1")),
|
||||
str(tmp_path.joinpath("d/2")),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def test_parse_config_ignores_unknowns(tmp_path, opt_manager, caplog):
|
||||
cfg = configparser.RawConfigParser()
|
||||
cfg.add_section("flake8")
|
||||
cfg.set("flake8", "wat", "wat")
|
||||
|
||||
ret = config.parse_config(opt_manager, cfg, str(tmp_path))
|
||||
assert ret == {}
|
||||
|
||||
assert caplog.record_tuples == [
|
||||
(
|
||||
"flake8.options.config",
|
||||
10,
|
||||
'Option "wat" is not registered. Ignoring.',
|
||||
)
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue