diff --git a/setup.cfg b/setup.cfg index bb8f6c3..47f8feb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -113,8 +113,6 @@ disallow_untyped_defs = false disallow_untyped_defs = false [mypy-flake8.main.application] disallow_untyped_defs = false -[mypy-flake8.main.debug] -disallow_untyped_defs = false [mypy-flake8.plugins.manager] disallow_untyped_defs = false diff --git a/src/flake8/main/application.py b/src/flake8/main/application.py index 9d4ff27..bf43826 100644 --- a/src/flake8/main/application.py +++ b/src/flake8/main/application.py @@ -37,7 +37,7 @@ LOG = logging.getLogger(__name__) class Application: """Abstract our application into a class.""" - def __init__(self, program="flake8", version=flake8.__version__): + def __init__(self) -> None: """Initialize our application. :param str program: @@ -49,21 +49,12 @@ class Application: self.start_time = time.time() #: The timestamp when the Application finished reported errors. self.end_time: Optional[float] = None - #: The name of the program being run - self.program = program - #: The version of the program being run - self.version = version #: The prelimary argument parser for handling options required for #: obtaining and parsing the configuration file. self.prelim_arg_parser = options.stage1_arg_parser() #: The instance of :class:`flake8.options.manager.OptionManager` used #: to parse and handle the options and arguments passed by the user - self.option_manager = manager.OptionManager( - prog="flake8", - version=flake8.__version__, - parents=[self.prelim_arg_parser], - ) - options.register_default_options(self.option_manager) + self.option_manager: Optional[manager.OptionManager] = None #: The instance of :class:`flake8.plugins.manager.Checkers` self.check_plugins: Optional[plugin_manager.Checkers] = None @@ -166,9 +157,19 @@ class Application: def register_plugin_options(self) -> None: """Register options provided by plugins to our option manager.""" assert self.check_plugins is not None - self.check_plugins.register_options(self.option_manager) - self.check_plugins.register_plugin_versions(self.option_manager) assert self.formatting_plugins is not None + + versions = sorted(set(self.check_plugins.manager.versions())) + self.option_manager = manager.OptionManager( + version=flake8.__version__, + plugin_versions=", ".join( + f"{name}: {version}" for name, version in versions + ), + parents=[self.prelim_arg_parser], + ) + options.register_default_options(self.option_manager) + + self.check_plugins.register_options(self.option_manager) self.formatting_plugins.register_options(self.option_manager) def parse_configuration_and_cli( @@ -178,6 +179,7 @@ class Application: argv: List[str], ) -> None: """Parse configuration files and the CLI options.""" + assert self.option_manager is not None self.options = aggregator.aggregate_options( self.option_manager, cfg, @@ -186,7 +188,8 @@ class Application: ) if self.options.bug_report: - info = debug.information(self.option_manager) + assert self.check_plugins is not None + info = debug.information(flake8.__version__, self.check_plugins) print(json.dumps(info, indent=2, sort_keys=True)) raise SystemExit(0) diff --git a/src/flake8/main/debug.py b/src/flake8/main/debug.py index cca6857..4cd9024 100644 --- a/src/flake8/main/debug.py +++ b/src/flake8/main/debug.py @@ -1,12 +1,20 @@ """Module containing the logic for our debugging logic.""" import platform +from typing import Any +from typing import Dict +from typing import List + +from flake8.plugins.manager import PluginTypeManager -def information(option_manager): +def information( + version: str, + plugins: PluginTypeManager, +) -> Dict[str, Any]: """Generate the information to be printed for the bug report.""" return { - "version": option_manager.version, - "plugins": plugins_from(option_manager), + "version": version, + "plugins": plugins_from(plugins), "platform": { "python_implementation": platform.python_implementation(), "python_version": platform.python_version(), @@ -15,13 +23,9 @@ def information(option_manager): } -def plugins_from(option_manager): +def plugins_from(plugins: PluginTypeManager) -> List[Dict[str, str]]: """Generate the list of plugins installed.""" return [ - { - "plugin": plugin.name, - "version": plugin.version, - "is_local": plugin.local, - } - for plugin in sorted(option_manager.registered_plugins) + {"plugin": name, "version": version} + for name, version in sorted(set(plugins.manager.versions())) ] diff --git a/src/flake8/options/manager.py b/src/flake8/options/manager.py index 164712e..5d48ba9 100644 --- a/src/flake8/options/manager.py +++ b/src/flake8/options/manager.py @@ -1,13 +1,11 @@ """Option handling and Option management logic.""" import argparse -import collections import contextlib import enum import functools import logging from typing import Any from typing import Callable -from typing import cast from typing import Dict from typing import Generator from typing import List @@ -316,20 +314,15 @@ class Option: return self.option_args, self.filtered_option_kwargs -PluginVersion = collections.namedtuple( - "PluginVersion", ["name", "version", "local"] -) - - class OptionManager: """Manage Options and OptionParser while adding post-processing.""" def __init__( self, - prog: str, + *, version: str, - usage: str = "%(prog)s [options] file file ...", - parents: Optional[List[argparse.ArgumentParser]] = None, + plugin_versions: str, + parents: List[argparse.ArgumentParser], ) -> None: """Initialize an instance of an OptionManager. @@ -343,28 +336,28 @@ class OptionManager: A list of ArgumentParser objects whose arguments should also be included. """ - if parents is None: - parents = [] - - self.parser: argparse.ArgumentParser = argparse.ArgumentParser( - prog=prog, usage=usage, parents=parents + self.parser = argparse.ArgumentParser( + prog="flake8", + usage="%(prog)s [options] file file ...", + parents=parents, + epilog=f"Installed plugins: {plugin_versions}", ) - self._current_group: Optional[argparse._ArgumentGroup] = None - self.version_action = cast( - "argparse._VersionAction", - self.parser.add_argument( - "--version", action="version", version=version + self.parser.add_argument( + "--version", + action="version", + version=( + f"{version} ({plugin_versions}) " + f"{utils.get_python_version()}" ), ) self.parser.add_argument("filenames", nargs="*", metavar="filename") self.config_options_dict: Dict[str, Option] = {} self.options: List[Option] = [] - self.program_name = prog - self.version = version - self.registered_plugins: Set[PluginVersion] = set() self.extended_default_ignore: Set[str] = set() self.extended_default_select: Set[str] = set() + self._current_group: Optional[argparse._ArgumentGroup] = None + @contextlib.contextmanager def group(self, name: str) -> Generator[None, None, None]: """Attach options to an argparse group during this context.""" @@ -395,7 +388,7 @@ class OptionManager: self.options.append(option) if option.parse_from_config: name = option.config_name - assert name is not None # nosec (for mypy) + assert name is not None self.config_options_dict[name] = option self.config_options_dict[name.replace("_", "-")] = option LOG.debug('Registered option "%s".', option) @@ -438,63 +431,12 @@ class OptionManager: LOG.debug("Extending default select list with %r", error_codes) self.extended_default_select.update(error_codes) - def generate_versions( - self, format_str: str = "%(name)s: %(version)s", join_on: str = ", " - ) -> str: - """Generate a comma-separated list of versions of plugins.""" - return join_on.join( - format_str % plugin._asdict() - for plugin in sorted(self.registered_plugins) - ) - - def update_version_string(self) -> None: - """Update the flake8 version string.""" - self.version_action.version = "{} ({}) {}".format( - self.version, self.generate_versions(), utils.get_python_version() - ) - - def generate_epilog(self) -> None: - """Create an epilog with the version and name of each of plugin.""" - plugin_version_format = "%(name)s: %(version)s" - self.parser.epilog = "Installed plugins: " + self.generate_versions( - plugin_version_format - ) - def parse_args( self, args: Optional[Sequence[str]] = None, values: Optional[argparse.Namespace] = None, ) -> argparse.Namespace: """Proxy to calling the OptionParser's parse_args method.""" - self.generate_epilog() - self.update_version_string() if values: self.parser.set_defaults(**vars(values)) return self.parser.parse_args(args) - - def parse_known_args( - self, args: Optional[List[str]] = None - ) -> Tuple[argparse.Namespace, List[str]]: - """Parse only the known arguments from the argument values. - - Replicate a little argparse behaviour while we're still on - optparse. - """ - self.generate_epilog() - self.update_version_string() - return self.parser.parse_known_args(args) - - def register_plugin( - self, name: str, version: str, local: bool = False - ) -> None: - """Register a plugin relying on the OptionManager. - - :param str name: - The name of the checker itself. This will be the ``name`` - attribute of the class or function loaded from the entry-point. - :param str version: - The version of the checker that we're using. - :param bool local: - Whether the plugin is local to the project/repository or not. - """ - self.registered_plugins.add(PluginVersion(name, version, local)) diff --git a/src/flake8/plugins/manager.py b/src/flake8/plugins/manager.py index d2b9187..fe9b038 100644 --- a/src/flake8/plugins/manager.py +++ b/src/flake8/plugins/manager.py @@ -427,12 +427,6 @@ class PluginTypeManager: # Do not set plugins_loaded if we run into an exception self.plugins_loaded = True - def register_plugin_versions(self, optmanager): - """Register the plugins and their versions with the OptionManager.""" - self.load_plugins() - for (plugin_name, version) in self.manager.versions(): - optmanager.register_plugin(name=plugin_name, version=version) - def register_options(self, optmanager): """Register all of the checkers' options to the OptionManager.""" self.load_plugins() diff --git a/tests/integration/test_aggregator.py b/tests/integration/test_aggregator.py index cdc7281..de9e6fb 100644 --- a/tests/integration/test_aggregator.py +++ b/tests/integration/test_aggregator.py @@ -13,8 +13,9 @@ from flake8.options import manager def optmanager(): """Create a new OptionManager.""" option_manager = manager.OptionManager( - prog="flake8", version="3.0.0", + plugin_versions="", + parents=[], ) options.register_default_options(option_manager) return option_manager diff --git a/tests/unit/test_debug.py b/tests/unit/test_debug.py index e686c5c..28ab48e 100644 --- a/tests/unit/test_debug.py +++ b/tests/unit/test_debug.py @@ -4,58 +4,24 @@ from unittest import mock import pytest from flake8.main import debug -from flake8.options import manager @pytest.mark.parametrize( - "plugins, expected", - [ + ("versions", "expected"), + ( ([], []), ( - [manager.PluginVersion("pycodestyle", "2.0.0", False)], + [("p1", "1"), ("p2", "2"), ("p1", "1")], [ - { - "plugin": "pycodestyle", - "version": "2.0.0", - "is_local": False, - } + {"plugin": "p1", "version": "1"}, + {"plugin": "p2", "version": "2"}, ], ), - ( - [ - manager.PluginVersion("pycodestyle", "2.0.0", False), - manager.PluginVersion("mccabe", "0.5.9", False), - ], - [ - {"plugin": "mccabe", "version": "0.5.9", "is_local": False}, - { - "plugin": "pycodestyle", - "version": "2.0.0", - "is_local": False, - }, - ], - ), - ( - [ - manager.PluginVersion("pycodestyle", "2.0.0", False), - manager.PluginVersion("my-local", "0.0.1", True), - manager.PluginVersion("mccabe", "0.5.9", False), - ], - [ - {"plugin": "mccabe", "version": "0.5.9", "is_local": False}, - {"plugin": "my-local", "version": "0.0.1", "is_local": True}, - { - "plugin": "pycodestyle", - "version": "2.0.0", - "is_local": False, - }, - ], - ), - ], + ), ) -def test_plugins_from(plugins, expected): +def test_plugins_from(versions, expected): """Test that we format plugins appropriately.""" - option_manager = mock.Mock(registered_plugins=set(plugins)) + option_manager = mock.Mock(**{"manager.versions.return_value": versions}) assert expected == debug.plugins_from(option_manager) @@ -67,8 +33,8 @@ def test_information(system, pyversion, pyimpl): expected = { "version": "3.1.0", "plugins": [ - {"plugin": "mccabe", "version": "0.5.9", "is_local": False}, - {"plugin": "pycodestyle", "version": "2.0.0", "is_local": False}, + {"plugin": "mccabe", "version": "0.5.9"}, + {"plugin": "pycodestyle", "version": "2.0.0"}, ], "platform": { "python_implementation": "CPython", @@ -76,14 +42,15 @@ def test_information(system, pyversion, pyimpl): "system": "Linux", }, } - option_manager = mock.Mock( - registered_plugins={ - manager.PluginVersion("pycodestyle", "2.0.0", False), - manager.PluginVersion("mccabe", "0.5.9", False), - }, - version="3.1.0", + plugins = mock.Mock( + **{ + "manager.versions.return_value": [ + ("pycodestyle", "2.0.0"), + ("mccabe", "0.5.9"), + ] + } ) - assert expected == debug.information(option_manager) + assert expected == debug.information("3.1.0", plugins) pyimpl.assert_called_once_with() pyversion.assert_called_once_with() system.assert_called_once_with() diff --git a/tests/unit/test_option_manager.py b/tests/unit/test_option_manager.py index 0dee442..c6006d3 100644 --- a/tests/unit/test_option_manager.py +++ b/tests/unit/test_option_manager.py @@ -5,7 +5,6 @@ from unittest import mock import pytest -from flake8 import utils from flake8.main.options import JobsArgument from flake8.options import manager @@ -15,7 +14,9 @@ TEST_VERSION = "3.0.0b1" @pytest.fixture def optmanager(): """Generate a simple OptionManager with default test arguments.""" - return manager.OptionManager(prog="flake8", version=TEST_VERSION) + return manager.OptionManager( + version=TEST_VERSION, plugin_versions="", parents=[] + ) def test_option_manager_creates_option_parser(optmanager): @@ -31,7 +32,7 @@ def test_option_manager_including_parent_options(): # WHEN optmanager = manager.OptionManager( - prog="flake8", version=TEST_VERSION, parents=[parent_parser] + version=TEST_VERSION, plugin_versions="", parents=[parent_parser] ) options = optmanager.parse_args(["--parent", "foo"]) @@ -153,90 +154,6 @@ def test_parse_args_normalize_paths(optmanager): ] -def test_generate_versions(optmanager): - """Verify a comma-separated string is generated of registered plugins.""" - optmanager.registered_plugins = [ - manager.PluginVersion("Testing 100", "0.0.0", False), - manager.PluginVersion("Testing 101", "0.0.0", False), - manager.PluginVersion("Testing 300", "0.0.0", True), - ] - assert ( - optmanager.generate_versions() - == "Testing 100: 0.0.0, Testing 101: 0.0.0, Testing 300: 0.0.0" - ) - - -def test_plugins_are_sorted_in_generate_versions(optmanager): - """Verify we sort before joining strings in generate_versions.""" - optmanager.registered_plugins = [ - manager.PluginVersion("pyflakes", "1.5.0", False), - manager.PluginVersion("mccabe", "0.7.0", False), - manager.PluginVersion("pycodestyle", "2.2.0", False), - manager.PluginVersion("flake8-docstrings", "0.6.1", False), - manager.PluginVersion("flake8-bugbear", "2016.12.1", False), - ] - assert ( - optmanager.generate_versions() == "flake8-bugbear: 2016.12.1, " - "flake8-docstrings: 0.6.1, " - "mccabe: 0.7.0, " - "pycodestyle: 2.2.0, " - "pyflakes: 1.5.0" - ) - - -def test_generate_versions_with_format_string(optmanager): - """Verify a comma-separated string is generated of registered plugins.""" - optmanager.registered_plugins.update( - [ - manager.PluginVersion("Testing", "0.0.0", False), - manager.PluginVersion("Testing", "0.0.0", False), - manager.PluginVersion("Testing", "0.0.0", False), - ] - ) - assert optmanager.generate_versions() == "Testing: 0.0.0" - - -def test_update_version_string(optmanager): - """Verify we update the version string idempotently.""" - assert optmanager.version == TEST_VERSION - assert optmanager.version_action.version == TEST_VERSION - - optmanager.registered_plugins = [ - manager.PluginVersion("Testing 100", "0.0.0", False), - manager.PluginVersion("Testing 101", "0.0.0", False), - manager.PluginVersion("Testing 300", "0.0.0", False), - ] - - optmanager.update_version_string() - - assert optmanager.version == TEST_VERSION - assert ( - optmanager.version_action.version - == TEST_VERSION - + " (Testing 100: 0.0.0, Testing 101: 0.0.0, Testing 300: 0.0.0) " - + utils.get_python_version() - ) - - -def test_generate_epilog(optmanager): - """Verify how we generate the epilog for help text.""" - assert optmanager.parser.epilog is None - - optmanager.registered_plugins = [ - manager.PluginVersion("Testing 100", "0.0.0", False), - manager.PluginVersion("Testing 101", "0.0.0", False), - manager.PluginVersion("Testing 300", "0.0.0", False), - ] - - expected_value = ( - "Installed plugins: Testing 100: 0.0.0, Testing 101: 0.0.0, Testing" - " 300: 0.0.0" - ) - - optmanager.generate_epilog() - assert optmanager.parser.epilog == expected_value - - def test_extend_default_ignore(optmanager): """Verify that we update the extended default ignore list.""" assert optmanager.extended_default_ignore == set() @@ -245,14 +162,6 @@ def test_extend_default_ignore(optmanager): assert optmanager.extended_default_ignore == {"T100", "T101", "T102"} -def test_parse_known_args(optmanager): - """Verify we ignore unknown options.""" - with mock.patch("sys.exit") as sysexit: - optmanager.parse_known_args(["--max-complexity", "5"]) - - assert sysexit.called is False - - def test_optparse_normalize_callback_option_legacy(optmanager): """Test the optparse shim for `callback=`.""" callback_foo = mock.Mock() diff --git a/tests/unit/test_options_config.py b/tests/unit/test_options_config.py index 75bfac5..b288de0 100644 --- a/tests/unit/test_options_config.py +++ b/tests/unit/test_options_config.py @@ -119,7 +119,7 @@ def test_load_config_append_config(tmpdir): @pytest.fixture def opt_manager(): - ret = OptionManager(prog="flake8", version="123") + ret = OptionManager(version="123", plugin_versions="", parents=[]) register_default_options(ret) return ret