Merge pull request #1404 from PyCQA/drop-xdg-config

Drop support for Home and XDG config files
This commit is contained in:
Ian Stapleton Cordasco 2021-10-10 19:21:35 -05:00 committed by GitHub
commit 283f0c8124
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 49 additions and 157 deletions

View file

@ -129,13 +129,8 @@ In |Flake8| 2, configuration file discovery and management was handled by
pep8. In pep8's 1.6 release series, it drastically broke how discovery and pep8. In pep8's 1.6 release series, it drastically broke how discovery and
merging worked (as a result of trying to improve it). To avoid a dependency merging worked (as a result of trying to improve it). To avoid a dependency
breaking |Flake8| again in the future, we have created our own discovery and breaking |Flake8| again in the future, we have created our own discovery and
management. management in 3.0.0. In 4.0.0 we have once again changed how this works and we
As part of managing this ourselves, we decided to change management/discovery removed support for user-level config files.
for 3.0.0. We have done the following:
- User files (files stored in a user's home directory or in the XDG directory
inside their home directory) are the first files read. For example, if the
user has a ``~/.flake8`` file, we will read that first.
- Project files (files stored in the current directory) are read next and - Project files (files stored in the current directory) are read next and
merged on top of the user file. In other words, configuration in project merged on top of the user file. In other words, configuration in project
@ -157,7 +152,7 @@ To facilitate the configuration file management, we've taken a different
approach to discovery and management of files than pep8. In pep8 1.5, 1.6, and approach to discovery and management of files than pep8. In pep8 1.5, 1.6, and
1.7 configuration discovery and management was centralized in `66 lines of 1.7 configuration discovery and management was centralized in `66 lines of
very terse python`_ which was confusing and not very explicit. The terseness very terse python`_ which was confusing and not very explicit. The terseness
of this function (|Flake8|'s authors believe) caused the confusion and of this function (|Flake8| 3.0.0's authors believe) caused the confusion and
problems with pep8's 1.6 series. As such, |Flake8| has separated out 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 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). of these pieces easier and more explicit (as well as easier to test).
@ -176,23 +171,19 @@ to parse those configuration files.
.. note:: ``local_config_files`` also filters out non-existent files. .. note:: ``local_config_files`` also filters out non-existent files.
Configuration file merging and managemnt is controlled by the Configuration file merging and managemnt is controlled by the
:class:`~flake8.options.config.MergedConfigParser`. This requires the instance :class:`~flake8.options.config.ConfigParser`. This requires the instance
of :class:`~flake8.options.manager.OptionManager` that the program is using, of :class:`~flake8.options.manager.OptionManager` that the program is using,
the list of appended config files, and the list of extra arguments. This the list of appended config files, and the list of extra arguments. This
object is currently the sole user of the object is currently the sole user of the
:class:`~flake8.options.config.ConfigFileFinder` object. It appropriately :class:`~flake8.options.config.ConfigFileFinder` object. It appropriately
initializes the object and uses it in each of initializes the object and uses it in each of
- :meth:`~flake8.options.config.MergedConfigParser.parse_cli_config` - :meth:`~flake8.options.config.ConfigParser.parse_cli_config`
- :meth:`~flake8.options.config.MergedConfigParser.parse_local_config` - :meth:`~flake8.options.config.ConfigParser.parse_local_config`
- :meth:`~flake8.options.config.MergedConfigParser.parse_user_config`
Finally, Finally, :meth:`~flake8.options.config.ConfigParser.parse` returns the
:meth:`~flake8.options.config.MergedConfigParser.merge_user_and_local_config` appropriate configuration dictionary for this execution of |Flake8|. The
takes the user and local configuration files that are parsed by main usage of the ``ConfigParser`` is in
:meth:`~flake8.options.config.MergedConfigParser.parse_local_config` and
:meth:`~flake8.options.config.MergedConfigParser.parse_user_config`. The
main usage of the ``MergedConfigParser`` is in
:func:`~flake8.options.aggregator.aggregate_options`. :func:`~flake8.options.aggregator.aggregate_options`.
Aggregating Configuration File and Command Line Arguments Aggregating Configuration File and Command Line Arguments
@ -201,7 +192,7 @@ Aggregating Configuration File and Command Line Arguments
:func:`~flake8.options.aggregator.aggregate_options` accepts an instance of :func:`~flake8.options.aggregator.aggregate_options` accepts an instance of
:class:`~flake8.options.manager.OptionManager` and does the work to parse the :class:`~flake8.options.manager.OptionManager` and does the work to parse the
command-line arguments passed by the user necessary for creating an instance command-line arguments passed by the user necessary for creating an instance
of :class:`~flake8.options.config.MergedConfigParser`. of :class:`~flake8.options.config.ConfigParser`.
After parsing the configuration file, we determine the default ignore list. We After parsing the configuration file, we determine the default ignore list. We
use the defaults from the OptionManager and update those with the parsed use the defaults from the OptionManager and update those with the parsed
@ -229,6 +220,6 @@ API Documentation
:members: :members:
:special-members: :special-members:
.. autoclass:: flake8.options.config.MergedConfigParser .. autoclass:: flake8.options.config.ConfigParser
:members: :members:
:special-members: :special-members:

View file

@ -0,0 +1,16 @@
4.0.0 -- 202x-mm-dd
-------------------
You can view the `4.0.0 milestone`_ on GitHub for more details.
Backwards Incompatible Changes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Due to constant confusion by users, user-level |Flake8| configuration files
are no longer supported. Files will not be searched for in the user's home
directory (e.g., ``~/.flake8``) nor in the XDG config directory (e.g.,
``~/.config/flake8``).
.. all links
.. _4.0.0 milestone:
https://github.com/PyCQA/flake8/milestone/39

View file

@ -5,6 +5,12 @@
All of the release notes that have been recorded for Flake8 are organized here All of the release notes that have been recorded for Flake8 are organized here
with the newest releases first. with the newest releases first.
4.x Release Series
==================
.. toctree::
4.0.0
3.x Release Series 3.x Release Series
================== ==================

View file

@ -38,7 +38,7 @@ def aggregate_options(
default_values, _ = manager.parse_args([]) default_values, _ = manager.parse_args([])
# Make our new configuration file mergerator # Make our new configuration file mergerator
config_parser = config.MergedConfigParser( config_parser = config.ConfigParser(
option_manager=manager, config_finder=config_finder option_manager=manager, config_finder=config_finder
) )

View file

@ -11,7 +11,7 @@ from flake8 import utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
__all__ = ("ConfigFileFinder", "MergedConfigParser") __all__ = ("ConfigFileFinder", "ConfigParser")
class ConfigFileFinder: class ConfigFileFinder:
@ -48,26 +48,12 @@ class ConfigFileFinder:
# User configuration file. # User configuration file.
self.program_name = program_name self.program_name = program_name
self.user_config_file = self._user_config_file(program_name)
# List of filenames to find in the local/project directory # List of filenames to find in the local/project directory
self.project_filenames = ("setup.cfg", "tox.ini", f".{program_name}") self.project_filenames = ("setup.cfg", "tox.ini", f".{program_name}")
self.local_directory = os.path.abspath(os.curdir) self.local_directory = os.path.abspath(os.curdir)
@staticmethod
def _user_config_file(program_name: str) -> str:
if utils.is_windows():
home_dir = os.path.expanduser("~")
config_file_basename = f".{program_name}"
else:
home_dir = os.environ.get(
"XDG_CONFIG_HOME", os.path.expanduser("~/.config")
)
config_file_basename = program_name
return os.path.join(home_dir, config_file_basename)
@staticmethod @staticmethod
def _read_config( def _read_config(
*files: str, *files: str,
@ -146,15 +132,8 @@ class ConfigFileFinder:
"""Parse all local config files into one config object.""" """Parse all local config files into one config object."""
return self.local_configs_with_files()[0] return self.local_configs_with_files()[0]
def user_config(self):
"""Parse the user config file into a config object."""
config, found_files = self._read_config(self.user_config_file)
if found_files:
LOG.debug("Found user configuration files: %s", found_files)
return config
class ConfigParser:
class MergedConfigParser:
"""Encapsulate merging different types of configuration files. """Encapsulate merging different types of configuration files.
This parses out the options registered that were specified in the This parses out the options registered that were specified in the
@ -167,7 +146,7 @@ class MergedConfigParser:
GETBOOL_ACTIONS = {"store_true", "store_false"} GETBOOL_ACTIONS = {"store_true", "store_false"}
def __init__(self, option_manager, config_finder): def __init__(self, option_manager, config_finder):
"""Initialize the MergedConfigParser instance. """Initialize the ConfigParser instance.
:param flake8.options.manager.OptionManager option_manager: :param flake8.options.manager.OptionManager option_manager:
Initialized OptionManager. Initialized OptionManager.
@ -239,19 +218,6 @@ class MergedConfigParser:
LOG.debug("Parsing local configuration files.") LOG.debug("Parsing local configuration files.")
return self._parse_config(config) return self._parse_config(config)
def parse_user_config(self):
"""Parse and return the user configuration files."""
config = self.config_finder.user_config()
if not self.is_configured_by(config):
LOG.debug(
"User configuration files have no %s section",
self.program_name,
)
return {}
LOG.debug("Parsing user configuration files.")
return self._parse_config(config)
def parse_cli_config(self, config_path): def parse_cli_config(self, config_path):
"""Parse and return the file specified by --config.""" """Parse and return the file specified by --config."""
config = self.config_finder.cli_config(config_path) config = self.config_finder.cli_config(config_path)
@ -265,28 +231,8 @@ class MergedConfigParser:
LOG.debug("Parsing CLI configuration files.") LOG.debug("Parsing CLI configuration files.")
return self._parse_config(config, os.path.dirname(config_path)) return self._parse_config(config, os.path.dirname(config_path))
def merge_user_and_local_config(self):
"""Merge the parsed user and local configuration files.
:returns:
Dictionary of the parsed and merged configuration options.
:rtype:
dict
"""
user_config = self.parse_user_config()
config = self.parse_local_config()
for option, value in user_config.items():
config.setdefault(option, value)
return config
def parse(self): def parse(self):
"""Parse and return the local and user config files. """Parse and return the local config files.
First this copies over the parsed local configuration and then
iterates over the options in the user configuration and sets them if
they were not set by the local configuration file.
:returns: :returns:
Dictionary of parsed configuration options Dictionary of parsed configuration options
@ -309,7 +255,7 @@ class MergedConfigParser:
) )
return self.parse_cli_config(self.config_finder.config_file) return self.parse_cli_config(self.config_finder.config_file)
return self.merge_user_and_local_config() return self.parse_local_config()
def get_local_plugins(config_finder): def get_local_plugins(config_finder):

View file

@ -1,4 +1,4 @@
"""Unit tests for flake8.options.config.MergedConfigParser.""" """Unit tests for flake8.options.config.ConfigParser."""
import os import os
from unittest import mock from unittest import mock
@ -32,7 +32,7 @@ def test_parse_cli_config(optmanager, config_finder):
"--ignore", parse_from_config=True, comma_separated_list=True "--ignore", parse_from_config=True, comma_separated_list=True
) )
optmanager.add_option("--quiet", parse_from_config=True, action="count") optmanager.add_option("--quiet", parse_from_config=True, action="count")
parser = config.MergedConfigParser(optmanager, config_finder) parser = config.ConfigParser(optmanager, config_finder)
config_file = "tests/fixtures/config_files/cli-specified.ini" config_file = "tests/fixtures/config_files/cli-specified.ini"
parsed_config = parser.parse_cli_config(config_file) parsed_config = parser.parse_cli_config(config_file)
@ -61,41 +61,11 @@ def test_is_configured_by(
): ):
"""Verify the behaviour of the is_configured_by method.""" """Verify the behaviour of the is_configured_by method."""
parsed_config, _ = config.ConfigFileFinder._read_config(filename) parsed_config, _ = config.ConfigFileFinder._read_config(filename)
parser = config.MergedConfigParser(optmanager, config_finder) parser = config.ConfigParser(optmanager, config_finder)
assert parser.is_configured_by(parsed_config) is is_configured_by assert parser.is_configured_by(parsed_config) is is_configured_by
def test_parse_user_config(optmanager, config_finder):
"""Verify parsing of user 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.MergedConfigParser(optmanager, config_finder)
config_finder.user_config_file = (
"tests/fixtures/config_files/" "cli-specified.ini"
)
parsed_config = parser.parse_user_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_local_config(optmanager, config_finder): def test_parse_local_config(optmanager, config_finder):
"""Verify parsing of local config files.""" """Verify parsing of local config files."""
optmanager.add_option( optmanager.add_option(
@ -108,7 +78,7 @@ def test_parse_local_config(optmanager, config_finder):
"--ignore", parse_from_config=True, comma_separated_list=True "--ignore", parse_from_config=True, comma_separated_list=True
) )
optmanager.add_option("--quiet", parse_from_config=True, action="count") optmanager.add_option("--quiet", parse_from_config=True, action="count")
parser = config.MergedConfigParser(optmanager, config_finder) parser = config.ConfigParser(optmanager, config_finder)
with mock.patch.object(config_finder, "local_config_files") as localcfs: with mock.patch.object(config_finder, "local_config_files") as localcfs:
localcfs.return_value = [ localcfs.return_value = [
@ -127,47 +97,14 @@ def test_parse_local_config(optmanager, config_finder):
} }
def test_merge_user_and_local_config(optmanager, config_finder):
"""Verify merging of parsed user and 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(
"--select", parse_from_config=True, comma_separated_list=True
)
parser = config.MergedConfigParser(optmanager, config_finder)
with mock.patch.object(config_finder, "local_config_files") as localcfs:
localcfs.return_value = [
"tests/fixtures/config_files/local-config.ini"
]
config_finder.user_config_file = (
"tests/fixtures/config_files/" "user-config.ini"
)
parsed_config = parser.merge_user_and_local_config()
assert parsed_config == {
"exclude": [os.path.abspath("docs/")],
"ignore": ["D203"],
"select": ["E", "W", "F"],
}
def test_parse_isolates_config(optmanager): def test_parse_isolates_config(optmanager):
"""Verify behaviour of the parse method with isolated=True.""" """Verify behaviour of the parse method with isolated=True."""
config_finder = mock.MagicMock() config_finder = mock.MagicMock()
config_finder.ignore_config_files = True config_finder.ignore_config_files = True
parser = config.MergedConfigParser(optmanager, config_finder) parser = config.ConfigParser(optmanager, config_finder)
assert parser.parse() == {} assert parser.parse() == {}
assert config_finder.local_configs.called is False assert config_finder.local_configs.called is False
assert config_finder.user_config.called is False
def test_parse_uses_cli_config(optmanager): def test_parse_uses_cli_config(optmanager):
@ -176,7 +113,7 @@ def test_parse_uses_cli_config(optmanager):
config_finder = mock.MagicMock() config_finder = mock.MagicMock()
config_finder.config_file = config_file_value config_finder.config_file = config_file_value
config_finder.ignore_config_files = False config_finder.ignore_config_files = False
parser = config.MergedConfigParser(optmanager, config_finder) parser = config.ConfigParser(optmanager, config_finder)
parser.parse() parser.parse()
config_finder.cli_config.assert_called_once_with(config_file_value) config_finder.cli_config.assert_called_once_with(config_file_value)
@ -206,13 +143,11 @@ def test_parsed_configs_are_equivalent(
optmanager.add_option( optmanager.add_option(
"--ignore", parse_from_config=True, comma_separated_list=True "--ignore", parse_from_config=True, comma_separated_list=True
) )
parser = config.MergedConfigParser(optmanager, config_finder) parser = config.ConfigParser(optmanager, config_finder)
with mock.patch.object(config_finder, "local_config_files") as localcfs: with mock.patch.object(config_finder, "local_config_files") as localcfs:
localcfs.return_value = [config_fixture_path] localcfs.return_value = [config_fixture_path]
with mock.patch.object(config_finder, "user_config_file") as usercf: parsed_config = parser.parse()
usercf.return_value = ""
parsed_config = parser.merge_user_and_local_config()
assert parsed_config["ignore"] == ["E123", "W234", "E111"] assert parsed_config["ignore"] == ["E123", "W234", "E111"]
assert parsed_config["exclude"] == [ assert parsed_config["exclude"] == [
@ -243,13 +178,11 @@ def test_parsed_hyphenated_and_underscored_names(
parse_from_config=True, parse_from_config=True,
comma_separated_list=True, comma_separated_list=True,
) )
parser = config.MergedConfigParser(optmanager, config_finder) parser = config.ConfigParser(optmanager, config_finder)
with mock.patch.object(config_finder, "local_config_files") as localcfs: with mock.patch.object(config_finder, "local_config_files") as localcfs:
localcfs.return_value = [config_file] localcfs.return_value = [config_file]
with mock.patch.object(config_finder, "user_config_file") as usercf: parsed_config = parser.parse()
usercf.return_value = ""
parsed_config = parser.merge_user_and_local_config()
assert parsed_config["max_line_length"] == 110 assert parsed_config["max_line_length"] == 110
assert parsed_config["enable_extensions"] == ["H101", "H235"] assert parsed_config["enable_extensions"] == ["H101", "H235"]