have OptionManager take plugin versions directly

This commit is contained in:
Anthony Sottile 2021-12-08 15:49:17 -05:00
parent fed77cd60a
commit f98d52a398
9 changed files with 73 additions and 255 deletions

View file

@ -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)

View file

@ -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()))
]

View file

@ -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))

View file

@ -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()