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"]