From 8c96de00509bf392b46582eeae18e0f28cd0dc87 Mon Sep 17 00:00:00 2001 From: Ian Stapleton Cordasco Date: Sun, 10 Oct 2021 19:08:29 -0500 Subject: [PATCH] Drop support for Home and XDG config files This has been a huge support burden for us. I seriously considered doing this in 3.0 but caved to a vocal minority and the desire to keep as much backwards compatibility as possible. At this point, however, I'm done witnessing the abuse Anthony has to suffer over this and I'm done with the undue hostility that people who don't bother to read the docs display. Hopefully, this eases that a bit. --- docs/source/internal/option_handling.rst | 31 +++---- docs/source/release-notes/4.0.0.rst | 16 ++++ docs/source/release-notes/index.rst | 6 ++ src/flake8/options/aggregator.py | 2 +- src/flake8/options/config.py | 64 ++------------ ...config_parser.py => test_config_parser.py} | 85 +++---------------- 6 files changed, 49 insertions(+), 155 deletions(-) create mode 100644 docs/source/release-notes/4.0.0.rst rename tests/unit/{test_merged_config_parser.py => test_config_parser.py} (66%) diff --git a/docs/source/internal/option_handling.rst b/docs/source/internal/option_handling.rst index 21d63628d..00c688fa4 100644 --- a/docs/source/internal/option_handling.rst +++ b/docs/source/internal/option_handling.rst @@ -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 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 -management. -As part of managing this ourselves, we decided to change management/discovery -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. +management in 3.0.0. In 4.0.0 we have once again changed how this works and we +removed support for user-level config files. - 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 @@ -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 1.7 configuration discovery and management was centralized in `66 lines of 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 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). @@ -176,23 +171,19 @@ to parse those configuration files. .. note:: ``local_config_files`` also filters out non-existent files. 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, 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.MergedConfigParser.parse_cli_config` -- :meth:`~flake8.options.config.MergedConfigParser.parse_local_config` -- :meth:`~flake8.options.config.MergedConfigParser.parse_user_config` +- :meth:`~flake8.options.config.ConfigParser.parse_cli_config` +- :meth:`~flake8.options.config.ConfigParser.parse_local_config` -Finally, -:meth:`~flake8.options.config.MergedConfigParser.merge_user_and_local_config` -takes the user and local configuration files that are parsed by -: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 +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`. 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 :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.MergedConfigParser`. +of :class:`~flake8.options.config.ConfigParser`. After parsing the configuration file, we determine the default ignore list. We use the defaults from the OptionManager and update those with the parsed @@ -229,6 +220,6 @@ API Documentation :members: :special-members: -.. autoclass:: flake8.options.config.MergedConfigParser +.. autoclass:: flake8.options.config.ConfigParser :members: :special-members: diff --git a/docs/source/release-notes/4.0.0.rst b/docs/source/release-notes/4.0.0.rst new file mode 100644 index 000000000..e997d090c --- /dev/null +++ b/docs/source/release-notes/4.0.0.rst @@ -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 diff --git a/docs/source/release-notes/index.rst b/docs/source/release-notes/index.rst index dd2025357..3617ff410 100644 --- a/docs/source/release-notes/index.rst +++ b/docs/source/release-notes/index.rst @@ -5,6 +5,12 @@ All of the release notes that have been recorded for Flake8 are organized here with the newest releases first. +4.x Release Series +================== + +.. toctree:: + 4.0.0 + 3.x Release Series ================== diff --git a/src/flake8/options/aggregator.py b/src/flake8/options/aggregator.py index 40848a24f..73a0f36ee 100644 --- a/src/flake8/options/aggregator.py +++ b/src/flake8/options/aggregator.py @@ -38,7 +38,7 @@ def aggregate_options( default_values, _ = manager.parse_args([]) # Make our new configuration file mergerator - config_parser = config.MergedConfigParser( + config_parser = config.ConfigParser( option_manager=manager, config_finder=config_finder ) diff --git a/src/flake8/options/config.py b/src/flake8/options/config.py index e920e5828..fc3b20545 100644 --- a/src/flake8/options/config.py +++ b/src/flake8/options/config.py @@ -11,7 +11,7 @@ LOG = logging.getLogger(__name__) -__all__ = ("ConfigFileFinder", "MergedConfigParser") +__all__ = ("ConfigFileFinder", "ConfigParser") class ConfigFileFinder: @@ -48,26 +48,12 @@ def __init__( # User configuration file. 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 self.project_filenames = ("setup.cfg", "tox.ini", f".{program_name}") 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 def _read_config( *files: str, @@ -146,15 +132,8 @@ def local_configs(self): """Parse all local config files into one config object.""" 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 MergedConfigParser: +class ConfigParser: """Encapsulate merging different types of configuration files. This parses out the options registered that were specified in the @@ -167,7 +146,7 @@ class MergedConfigParser: GETBOOL_ACTIONS = {"store_true", "store_false"} def __init__(self, option_manager, config_finder): - """Initialize the MergedConfigParser instance. + """Initialize the ConfigParser instance. :param flake8.options.manager.OptionManager option_manager: Initialized OptionManager. @@ -239,19 +218,6 @@ def parse_local_config(self): LOG.debug("Parsing local configuration files.") 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): """Parse and return the file specified by --config.""" config = self.config_finder.cli_config(config_path) @@ -265,28 +231,8 @@ def parse_cli_config(self, config_path): LOG.debug("Parsing CLI configuration files.") 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): - """Parse and return the local and user 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. + """Parse and return the local config files. :returns: Dictionary of parsed configuration options @@ -309,7 +255,7 @@ def parse(self): ) 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): diff --git a/tests/unit/test_merged_config_parser.py b/tests/unit/test_config_parser.py similarity index 66% rename from tests/unit/test_merged_config_parser.py rename to tests/unit/test_config_parser.py index b19291c6f..c27b6aaa6 100644 --- a/tests/unit/test_merged_config_parser.py +++ b/tests/unit/test_config_parser.py @@ -1,4 +1,4 @@ -"""Unit tests for flake8.options.config.MergedConfigParser.""" +"""Unit tests for flake8.options.config.ConfigParser.""" import os 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 ) 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" 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.""" 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 -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): """Verify parsing of local config files.""" optmanager.add_option( @@ -108,7 +78,7 @@ def test_parse_local_config(optmanager, config_finder): "--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) + parser = config.ConfigParser(optmanager, config_finder) with mock.patch.object(config_finder, "local_config_files") as localcfs: 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): """Verify behaviour of the parse method with isolated=True.""" config_finder = mock.MagicMock() config_finder.ignore_config_files = True - parser = config.MergedConfigParser(optmanager, config_finder) + parser = config.ConfigParser(optmanager, config_finder) assert parser.parse() == {} assert config_finder.local_configs.called is False - assert config_finder.user_config.called is False def test_parse_uses_cli_config(optmanager): @@ -176,7 +113,7 @@ def test_parse_uses_cli_config(optmanager): config_finder = mock.MagicMock() config_finder.config_file = config_file_value config_finder.ignore_config_files = False - parser = config.MergedConfigParser(optmanager, config_finder) + parser = config.ConfigParser(optmanager, config_finder) parser.parse() config_finder.cli_config.assert_called_once_with(config_file_value) @@ -206,13 +143,11 @@ def test_parsed_configs_are_equivalent( optmanager.add_option( "--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: localcfs.return_value = [config_fixture_path] - with mock.patch.object(config_finder, "user_config_file") as usercf: - usercf.return_value = "" - parsed_config = parser.merge_user_and_local_config() + parsed_config = parser.parse() assert parsed_config["ignore"] == ["E123", "W234", "E111"] assert parsed_config["exclude"] == [ @@ -243,13 +178,13 @@ def test_parsed_hyphenated_and_underscored_names( 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: localcfs.return_value = [config_file] with mock.patch.object(config_finder, "user_config_file") as usercf: usercf.return_value = "" - parsed_config = parser.merge_user_and_local_config() + parsed_config = parser.parse() assert parsed_config["max_line_length"] == 110 assert parsed_config["enable_extensions"] == ["H101", "H235"]