From b66ebd7034090f96cb0806e5e2d8026b6e35d045 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 10 Aug 2019 18:28:32 -0700 Subject: [PATCH 1/2] move from optparse to argparse --- .coveragerc | 22 +- .pylintrc | 4 - docs/source/internal/option_handling.rst | 4 +- .../plugin-development/plugin-parameters.rst | 22 +- src/flake8/api/legacy.py | 5 +- src/flake8/checker.py | 2 +- src/flake8/formatting/base.py | 6 +- src/flake8/main/application.py | 12 +- src/flake8/main/debug.py | 47 ++- src/flake8/main/options.py | 37 +-- src/flake8/main/vcs.py | 33 +- src/flake8/options/aggregator.py | 4 +- src/flake8/options/config.py | 8 +- src/flake8/options/manager.py | 296 +++++++++++------- src/flake8/plugins/pyflakes.py | 2 - src/flake8/style_guide.py | 2 +- tests/integration/test_main.py | 36 ++- tests/unit/conftest.py | 4 +- tests/unit/test_application.py | 6 +- tests/unit/test_base_formatter.py | 6 +- tests/unit/test_debug.py | 14 +- tests/unit/test_decision_engine.py | 6 +- tests/unit/test_filenameonly_formatter.py | 6 +- tests/unit/test_merged_config_parser.py | 2 +- tests/unit/test_nothing_formatter.py | 6 +- tests/unit/test_option.py | 55 ++-- tests/unit/test_option_manager.py | 103 +++++- tests/unit/test_plugin.py | 4 +- tests/unit/test_style_guide.py | 6 +- tox.ini | 4 +- 30 files changed, 462 insertions(+), 302 deletions(-) diff --git a/.coveragerc b/.coveragerc index 0a3db04..df0a929 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,11 +1,31 @@ [run] +parallel = True branch = True source = flake8 - +omit = + # Don't complain if non-runnable code isn't run + */__main__.py [paths] source = src/flake8 .tox/*/lib/python*/site-packages/flake8 .tox/pypy/site-packages/flake8 + +[report] +show_missing = True +skip_covered = True +exclude_lines = + # Have to re-enable the standard pragma + \#\s*pragma: no cover + + # Don't complain if tests don't hit defensive assertion code: + ^\s*raise AssertionError\b + ^\s*raise NotImplementedError\b + ^\s*return NotImplemented\b + ^\s*raise$ + + # Don't complain if non-runnable code isn't run: + ^if __name__ == ['"]__main__['"]:$ + ^\s*if False: diff --git a/.pylintrc b/.pylintrc index d6e52a8..a23de97 100644 --- a/.pylintrc +++ b/.pylintrc @@ -354,10 +354,6 @@ max-bool-expr=5 [IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=optparse - # Create a graph of every (i.e. internal and external) dependencies in the # given file (report RP0402 must not be disabled) import-graph= diff --git a/docs/source/internal/option_handling.rst b/docs/source/internal/option_handling.rst index 86b7021..dd76706 100644 --- a/docs/source/internal/option_handling.rst +++ b/docs/source/internal/option_handling.rst @@ -28,7 +28,7 @@ undocumented attribute that pep8 looks for, |Flake8| 3 now accepts a parameter to ``add_option``, specifically ``parse_from_config`` which is a boolean value. -|Flake8| does this by creating its own abstractions on top of :mod:`optparse`. +|Flake8| does this by creating its own abstractions on top of :mod:`argparse`. The first abstraction is the :class:`flake8.options.manager.Option` class. The second is the :class:`flake8.options.manager.OptionManager`. In fact, we add three new parameters: @@ -219,7 +219,7 @@ API Documentation .. autofunction:: flake8.options.aggregator.aggregate_options .. autoclass:: flake8.options.manager.Option - :members: __init__, normalize, to_optparse + :members: __init__, normalize, to_argparse .. autoclass:: flake8.options.manager.OptionManager :members: diff --git a/docs/source/plugin-development/plugin-parameters.rst b/docs/source/plugin-development/plugin-parameters.rst index b7a4221..6dae857 100644 --- a/docs/source/plugin-development/plugin-parameters.rst +++ b/docs/source/plugin-development/plugin-parameters.rst @@ -72,7 +72,7 @@ Any plugin that has callable attributes ``add_options`` and Your ``add_options`` function should expect to receive an instance of |OptionManager|. An |OptionManager| instance behaves very similarly to :class:`optparse.OptionParser`. It, however, uses the layer that |Flake8| has -developed on top of :mod:`optparse` to also handle configuration file parsing. +developed on top of :mod:`argparse` to also handle configuration file parsing. :meth:`~flake8.options.manager.OptionManager.add_option` creates an |Option| which accepts the same parameters as :mod:`optparse` as well as three extra boolean parameters: @@ -115,13 +115,13 @@ couple examples from |Flake8|. In each example, we will have '--max-line-length', type='int', metavar='n', default=defaults.MAX_LINE_LENGTH, parse_from_config=True, help='Maximum allowed line length for the entirety of this run. ' - '(Default: %default)', + '(Default: %(default)s)', ) Here we are adding the ``--max-line-length`` command-line option which is always an integer and will be parsed from the configuration file. Since we -provide a default, we take advantage of :mod:`optparse`\ 's willingness to -display that in the help text with ``%default``. +provide a default, we take advantage of :mod:`argparse`\ 's willingness to +display that in the help text with ``%(default)s``. .. code-block:: python @@ -129,7 +129,7 @@ display that in the help text with ``%default``. '--select', metavar='errors', default='', parse_from_config=True, comma_separated_list=True, help='Comma-separated list of errors and warnings to enable.' - ' For example, ``--select=E4,E51,W234``. (Default: %default)', + ' For example, ``--select=E4,E51,W234``. (Default: %(default)s)', ) In adding the ``--select`` command-line option, we're also indicating to the @@ -143,7 +143,7 @@ as a comma-separated list. comma_separated_list=True, parse_from_config=True, normalize_paths=True, help='Comma-separated list of files or directories to exclude.' - '(Default: %default)', + '(Default: %(default)s)', ) Finally, we show an option that uses all three extra flags. Values from @@ -152,7 +152,7 @@ list, and then each item will be normalized. For information about other parameters to :meth:`~flake8.options.manager.OptionManager.add_option` refer to the -documentation of :mod:`optparse`. +documentation of :mod:`argparse`. Accessing Parsed Options @@ -160,10 +160,10 @@ Accessing Parsed Options When a plugin has a callable ``parse_options`` attribute, |Flake8| will call it and attempt to provide the |OptionManager| instance, the parsed options -which will be an instance of :class:`optparse.Values`, and the extra arguments -that were not parsed by the |OptionManager|. If that fails, we will just pass -the :class:`optparse.Values`. In other words, your ``parse_options`` -callable will have one of the following signatures: +which will be an instance of :class:`argparse.Namespace`, and the extra +arguments that were not parsed by the |OptionManager|. If that fails, we will +just pass the :class:`argparse.Namespace`. In other words, your +``parse_options`` callable will have one of the following signatures: .. code-block:: python diff --git a/src/flake8/api/legacy.py b/src/flake8/api/legacy.py index fd06a72..16a33f4 100644 --- a/src/flake8/api/legacy.py +++ b/src/flake8/api/legacy.py @@ -3,6 +3,7 @@ Previously, users would import :func:`get_style_guide` from ``flake8.engine``. In 3.0 we no longer have an "engine" module but we maintain the API from it. """ +import argparse import logging import os.path @@ -72,10 +73,10 @@ class StyleGuide(object): self._file_checker_manager = application.file_checker_manager @property - def options(self): + def options(self): # type: () -> argparse.Namespace """Return application's options. - An instance of :class:`optparse.Values` containing parsed options. + An instance of :class:`argparse.Namespace` containing parsed options. """ return self._application.options diff --git a/src/flake8/checker.py b/src/flake8/checker.py index 6170d88..a08f39c 100644 --- a/src/flake8/checker.py +++ b/src/flake8/checker.py @@ -367,7 +367,7 @@ class FileChecker(object): :param options: Parsed option values from config and command-line. :type options: - optparse.Values + argparse.Namespace """ self.options = options self.filename = filename diff --git a/src/flake8/formatting/base.py b/src/flake8/formatting/base.py index 520dcbc..36dc905 100644 --- a/src/flake8/formatting/base.py +++ b/src/flake8/formatting/base.py @@ -1,7 +1,7 @@ """The base class and interface for all formatting plugins.""" from __future__ import print_function -import optparse +import argparse from typing import IO, List, Optional, Tuple if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2 @@ -32,13 +32,13 @@ class BaseFormatter(object): """ def __init__(self, options): - # type: (optparse.Values) -> None + # type: (argparse.Namespace) -> None """Initialize with the options parsed from config and cli. This also calls a hook, :meth:`after_init`, so subclasses do not need to call super to call this method. - :param optparse.Values options: + :param argparse.Namespace options: User specified configuration parsed from both configuration files and the command-line interface. """ diff --git a/src/flake8/main/application.py b/src/flake8/main/application.py index 4bbee28..6e5373f 100644 --- a/src/flake8/main/application.py +++ b/src/flake8/main/application.py @@ -1,8 +1,8 @@ """Module containing the application logic for Flake8.""" from __future__ import print_function +import argparse import logging -import optparse import sys import time from typing import Dict, List, Optional, Set @@ -52,8 +52,8 @@ class Application(object): ) options.register_default_options(self.option_manager) #: The preliminary options parsed from CLI before plugins are loaded, - #: into a :class:`optparse.Values` instance - self.prelim_opts = None # type: optparse.Values + #: into a :class:`argparse.Namespace` instance + self.prelim_opts = None # type: argparse.Namespace #: The preliminary arguments parsed from CLI before plugins are loaded self.prelim_args = None # type: List[str] #: The instance of :class:`flake8.options.config.ConfigFileFinder` @@ -77,8 +77,8 @@ class Application(object): self.file_checker_manager = None # type: checker.Manager #: The user-supplied options parsed into an instance of - #: :class:`optparse.Values` - self.options = None # type: optparse.Values + #: :class:`argparse.Namespace` + self.options = None # type: argparse.Namespace #: The left over arguments that were not parsed by #: :attr:`option_manager` self.args = None # type: List[str] @@ -117,7 +117,7 @@ class Application(object): # printing the version until we aggregate options from config files # and the command-line. First, let's clone our arguments on the CLI, # then we'll attempt to remove ``--version`` so that we can avoid - # triggering the "version" action in optparse. If it's not there, we + # triggering the "version" action in argparse. If it's not there, we # do not need to worry and we can continue. If it is, we successfully # defer printing the version until just a little bit later. # Similarly we have to defer printing the help text until later. diff --git a/src/flake8/main/debug.py b/src/flake8/main/debug.py index e02da6b..1d56189 100644 --- a/src/flake8/main/debug.py +++ b/src/flake8/main/debug.py @@ -1,41 +1,38 @@ """Module containing the logic for our debugging logic.""" from __future__ import print_function +import argparse import json import platform import entrypoints -def print_information( - option, option_string, value, parser, option_manager=None -): - """Print debugging information used in bug reports. +class DebugAction(argparse.Action): + """argparse action to print debug information.""" - :param option: - The optparse Option instance. - :type option: - optparse.Option - :param str option_string: - The option name - :param value: - The value passed to the callback parsed from the command-line - :param parser: - The optparse OptionParser instance - :type parser: - optparse.OptionParser - :param option_manager: - The Flake8 OptionManager instance. - :type option_manager: - flake8.options.manager.OptionManager - """ - if not option_manager.registered_plugins: + def __init__(self, *args, **kwargs): + """Initialize the action. + + This takes an extra `option_manager` keyword argument which will be + used to delay response. + """ + self._option_manager = kwargs.pop("option_manager") + super(DebugAction, self).__init__(*args, **kwargs) + + def __call__(self, parser, namespace, values, option_string=None): + """Perform the argparse action for printing debug information.""" # NOTE(sigmavirus24): Flake8 parses options twice. The first time, we # will not have any registered plugins. We can skip this one and only # take action on the second time we're called. - return - print(json.dumps(information(option_manager), indent=2, sort_keys=True)) - raise SystemExit(False) + if not self._option_manager.registered_plugins: + return + print( + json.dumps( + information(self._option_manager), indent=2, sort_keys=True + ) + ) + raise SystemExit(0) def information(option_manager): diff --git a/src/flake8/main/options.py b/src/flake8/main/options.py index bef5630..75d2c8f 100644 --- a/src/flake8/main/options.py +++ b/src/flake8/main/options.py @@ -1,4 +1,6 @@ """Contains the logic for all of the default options for Flake8.""" +import functools + from flake8 import defaults from flake8.main import debug from flake8.main import vcs @@ -83,7 +85,7 @@ def register_default_options(option_manager): parse_from_config=True, normalize_paths=True, help="Comma-separated list of files or directories to exclude." - " (Default: %default)", + " (Default: %(default)s)", ) add_option( @@ -103,7 +105,7 @@ def register_default_options(option_manager): parse_from_config=True, comma_separated_list=True, help="Only check for filenames matching the patterns in this comma-" - "separated list. (Default: %default)", + "separated list. (Default: %(default)s)", ) add_option( @@ -111,7 +113,7 @@ def register_default_options(option_manager): default="stdin", help="The name used when reporting errors from code passed via stdin." " This is useful for editors piping the file contents to flake8." - " (Default: %default)", + " (Default: %(default)s)", ) # TODO(sigmavirus24): Figure out --first/--repeat @@ -142,7 +144,7 @@ def register_default_options(option_manager): parse_from_config=True, comma_separated_list=True, help="Comma-separated list of errors and warnings to ignore (or skip)." - " For example, ``--ignore=E4,E51,W234``. (Default: %default)", + " For example, ``--ignore=E4,E51,W234``. (Default: %(default)s)", ) add_option( @@ -168,22 +170,22 @@ def register_default_options(option_manager): add_option( "--max-line-length", - type="int", + type=int, metavar="n", default=defaults.MAX_LINE_LENGTH, parse_from_config=True, help="Maximum allowed line length for the entirety of this run. " - "(Default: %default)", + "(Default: %(default)s)", ) add_option( "--max-doc-length", - type="int", + type=int, metavar="n", default=None, parse_from_config=True, help="Maximum allowed doc line length for the entirety of this run. " - "(Default: %default)", + "(Default: %(default)s)", ) add_option( @@ -193,7 +195,7 @@ def register_default_options(option_manager): parse_from_config=True, comma_separated_list=True, help="Comma-separated list of errors and warnings to enable." - " For example, ``--select=E4,E51,W234``. (Default: %default)", + " For example, ``--select=E4,E51,W234``. (Default: %(default)s)", ) add_option( @@ -227,7 +229,6 @@ def register_default_options(option_manager): default="", parse_from_config=True, comma_separated_list=True, - type="string", help="Enable plugins and extensions that are otherwise disabled " "by default", ) @@ -240,10 +241,8 @@ def register_default_options(option_manager): add_option( "--install-hook", - action="callback", - type="choice", + action=vcs.InstallAction, choices=vcs.choices(), - callback=vcs.install, help="Install a hook that is run prior to a commit for the supported " "version control system.", ) @@ -251,21 +250,18 @@ def register_default_options(option_manager): add_option( "-j", "--jobs", - type="string", default="auto", parse_from_config=True, help="Number of subprocesses to use to run checks in parallel. " 'This is ignored on Windows. The default, "auto", will ' "auto-detect the number of processors available to use." - " (Default: %default)", + " (Default: %(default)s)", ) add_option( "--output-file", default=None, - type="string", parse_from_config=True, - # callback=callbacks.redirect_stdout, help="Redirect report to a file.", ) @@ -316,8 +312,9 @@ def register_default_options(option_manager): add_option( "--bug-report", - action="callback", - callback=debug.print_information, - callback_kwargs={"option_manager": option_manager}, + action=functools.partial( + debug.DebugAction, option_manager=option_manager + ), + nargs=0, help="Print information necessary when preparing a bug report", ) diff --git a/src/flake8/main/vcs.py b/src/flake8/main/vcs.py index 398643c..cbe6972 100644 --- a/src/flake8/main/vcs.py +++ b/src/flake8/main/vcs.py @@ -1,4 +1,6 @@ """Module containing some of the logic for our VCS installation logic.""" +import argparse + from flake8 import exceptions as exc from flake8.main import git from flake8.main import mercurial @@ -11,24 +13,23 @@ from flake8.main import mercurial _INSTALLERS = {"git": git.install, "mercurial": mercurial.install} -def install(option, option_string, value, parser): - """Determine which version control hook to install. +class InstallAction(argparse.Action): + """argparse action to run the hook installation.""" - For more information about the callback signature, see: - https://docs.python.org/3/library/optparse.html#optparse-option-callbacks - """ - installer = _INSTALLERS[value] - errored = False - successful = False - try: - successful = installer() - except exc.HookInstallationError as hook_error: - print(str(hook_error)) - errored = True + def __call__(self, parser, namespace, value, option_string=None): + """Perform the argparse action for installing vcs hooks.""" + installer = _INSTALLERS[value] + errored = False + successful = False + try: + successful = installer() + except exc.HookInstallationError as hook_error: + print(str(hook_error)) + errored = True - if not successful: - print("Could not find the {0} directory".format(value)) - raise SystemExit(not successful and errored) + if not successful: + print("Could not find the {0} directory".format(value)) + raise SystemExit(not successful and errored) def choices(): diff --git a/src/flake8/options/aggregator.py b/src/flake8/options/aggregator.py index 304f53c..1b9c60c 100644 --- a/src/flake8/options/aggregator.py +++ b/src/flake8/options/aggregator.py @@ -21,13 +21,13 @@ def aggregate_options(manager, config_finder, arglist=None, values=None): The list of arguments to pass to ``manager.parse_args``. In most cases this will be None so ``parse_args`` uses ``sys.argv``. This is mostly available to make testing easier. - :param optparse.Values values: + :param argparse.Namespace values: Previously parsed set of parsed options. :returns: Tuple of the parsed options and extra arguments returned by ``manager.parse_args``. :rtype: - tuple(optparse.Values, list) + tuple(argparse.Namespace, list) """ # Get defaults from the option parser default_values, _ = manager.parse_args([], values=values) diff --git a/src/flake8/options/config.py b/src/flake8/options/config.py index 2af8cc8..0a43625 100644 --- a/src/flake8/options/config.py +++ b/src/flake8/options/config.py @@ -167,9 +167,6 @@ class MergedConfigParser(object): dictionaries with the parsed values. """ - #: Set of types that should use the - #: :meth:`~configparser.RawConfigParser.getint` method. - GETINT_TYPES = {"int", "count"} #: Set of actions that should use the #: :meth:`~configparser.RawConfigParser.getbool` method. GETBOOL_ACTIONS = {"store_true", "store_false"} @@ -216,10 +213,7 @@ class MergedConfigParser(object): # Use the appropriate method to parse the config value method = config_parser.get - if ( - option.type in self.GETINT_TYPES - or option.action in self.GETINT_TYPES - ): + if option.type is int or option.action == "count": method = config_parser.getint elif option.action in self.GETBOOL_ACTIONS: method = config_parser.getboolean diff --git a/src/flake8/options/manager.py b/src/flake8/options/manager.py index cf94927..219e0ad 100644 --- a/src/flake8/options/manager.py +++ b/src/flake8/options/manager.py @@ -1,55 +1,100 @@ """Option handling and Option management logic.""" +import argparse import collections +import functools import logging -import optparse # pylint: disable=deprecated-module -from typing import Dict, List, Optional, Set +from typing import Any, Dict, List, Optional, Set from flake8 import utils LOG = logging.getLogger(__name__) +_NOARG = object() + + +class _CallbackAction(argparse.Action): + """Shim for optparse-style callback actions.""" + + def __init__(self, *args, **kwargs): + self._callback = kwargs.pop("callback") + self._callback_args = kwargs.pop("callback_args", ()) + self._callback_kwargs = kwargs.pop("callback_kwargs", {}) + super(_CallbackAction, self).__init__(*args, **kwargs) + + def __call__(self, parser, namespace, values, option_string=None): + if not values: + values = None + elif isinstance(values, list) and len(values) > 1: + values = tuple(values) + self._callback( + self, + option_string, + values, + parser, + *self._callback_args, + **self._callback_kwargs + ) + + +def _flake8_normalize(value, *args, **kwargs): + comma_separated_list = kwargs.pop("comma_separated_list", False) + normalize_paths = kwargs.pop("normalize_paths", False) + if kwargs: + raise TypeError("Unexpected keyword args: {}".format(kwargs)) + + if comma_separated_list and isinstance(value, utils.string_types): + value = utils.parse_comma_separated_list(value) + + if normalize_paths: + if isinstance(value, list): + value = utils.normalize_paths(value, *args) + else: + value = utils.normalize_path(value, *args) + + return value + class Option(object): - """Our wrapper around an optparse.Option object to add features.""" + """Our wrapper around an argparse argument parsers to add features.""" def __init__( self, - short_option_name=None, - long_option_name=None, + short_option_name=_NOARG, + long_option_name=_NOARG, # Options below here are taken from the optparse.Option class - action=None, - default=None, - type=None, - dest=None, - nargs=None, - const=None, - choices=None, - callback=None, - callback_args=None, - callback_kwargs=None, - help=None, - metavar=None, + action=_NOARG, + default=_NOARG, + type=_NOARG, + dest=_NOARG, + nargs=_NOARG, + const=_NOARG, + choices=_NOARG, + help=_NOARG, + metavar=_NOARG, + # deprecated optparse-only options + callback=_NOARG, + callback_args=_NOARG, + callback_kwargs=_NOARG, + # Options below are taken from argparse.ArgumentParser.add_argument + required=_NOARG, # Options below here are specific to Flake8 parse_from_config=False, comma_separated_list=False, normalize_paths=False, ): - """Initialize an Option instance wrapping optparse.Option. + """Initialize an Option instance. - The following are all passed directly through to optparse. + The following are all passed directly through to argparse. :param str short_option_name: The short name of the option (e.g., ``-x``). This will be the - first argument passed to :class:`~optparse.Option`. + first argument passed to ``ArgumentParser.add_argument`` :param str long_option_name: The long name of the option (e.g., ``--xtra-long-option``). This - will be the second argument passed to :class:`~optparse.Option`. - :param str action: - Any action allowed by :mod:`optparse`. + will be the second argument passed to + ``ArgumentParser.add_argument`` :param default: Default value of the option. - :param type: - Any type allowed by :mod:`optparse`. :param dest: Attribute name to store parsed option value as. :param nargs: @@ -59,16 +104,33 @@ class Option(object): conjuntion with ``action="store_const"``. :param iterable choices: Possible values for the option. - :param callable callback: - Callback used if the action is ``"callback"``. - :param iterable callback_args: - Additional positional arguments to the callback callable. - :param dictionary callback_kwargs: - Keyword arguments to the callback callable. :param str help: Help text displayed in the usage information. :param str metavar: Name to use instead of the long option name for help text. + :param bool required: + Whether this option is required or not. + + The following options may be passed directly through to :mod:`argparse` + but may need some massaging. + + :param type: + A callable to normalize the type (as is the case in + :mod:`argparse`). Deprecated: you can also pass through type + strings such as ``'int'`` which are handled by :mod:`optparse`. + :param str action: + Any action allowed by :mod:`argparse`. Deprecated: this also + understands the ``action='callback'`` action from :mod:`optparse`. + :param callable callback: + Callback used if the action is ``"callback"``. Deprecated: please + use ``action=`` instead. + :param iterable callback_args: + Additional positional arguments to the callback callable. + Deprecated: please use ``action=`` instead (probably with + ``functools.partial``). + :param dictionary callback_kwargs: + Keyword arguments to the callback callable. Deprecated: please + use ``action=`` instead (probably with ``functools.partial``). The following parameters are for Flake8's option handling alone. @@ -81,16 +143,66 @@ class Option(object): Whether the option is expecting a path or list of paths and should attempt to normalize the paths to absolute paths. """ + if long_option_name is _NOARG and short_option_name.startswith("--"): + short_option_name, long_option_name = _NOARG, short_option_name + + # optparse -> argparse `%default` => `%(default)s` + if help is not _NOARG and "%default" in help: + LOG.warning( + "option %s: please update `help=` text to use %%(default)s " + "instead of %%default -- this will be an error in the future", + long_option_name, + ) + help = help.replace("%default", "%(default)s") + + # optparse -> argparse for `callback` + if action == "callback": + LOG.warning( + "option %s: please update from optparse `action='callback'` " + "to argparse action classes -- this will be an error in the " + "future", + long_option_name, + ) + action = _CallbackAction + if type is _NOARG: + nargs = 0 + + # optparse -> argparse for `type` + if isinstance(type, utils.string_types): + LOG.warning( + "option %s: please update from optparse string `type=` to " + "argparse callable `type=` -- this will be an error in the " + "future" + ) + type = { + "int": int, + "long": int, + "string": str, + "float": float, + "complex": complex, + "choice": _NOARG, + }[type] + + # flake8 special type normalization + if comma_separated_list or normalize_paths: + type = functools.partial( + _flake8_normalize, + comma_separated_list=comma_separated_list, + normalize_paths=normalize_paths, + ) + self.short_option_name = short_option_name self.long_option_name = long_option_name self.option_args = [ - x for x in (short_option_name, long_option_name) if x is not None + x + for x in (short_option_name, long_option_name) + if x is not _NOARG ] self.option_kwargs = { "action": action, "default": default, "type": type, - "dest": self._make_dest(dest), + "dest": dest, "nargs": nargs, "const": const, "choices": choices, @@ -111,7 +223,7 @@ class Option(object): self.config_name = None # type: Optional[str] if parse_from_config: - if not long_option_name: + if long_option_name is _NOARG: raise ValueError( "When specifying parse_from_config=True, " "a long_option_name must also be specified." @@ -120,25 +232,20 @@ class Option(object): self._opt = None + @property + def filtered_option_kwargs(self): # type: () -> Dict[str, Any] + """Return any actually-specified arguments.""" + return { + k: v for k, v in self.option_kwargs.items() if v is not _NOARG + } + def __repr__(self): # noqa: D105 - return ( - "Option({0}, {1}, action={action}, default={default}, " - "dest={dest}, type={type}, callback={callback}, help={help}," - " callback={callback}, callback_args={callback_args}, " - "callback_kwargs={callback_kwargs}, metavar={metavar})" - ).format( - self.short_option_name, - self.long_option_name, - **self.option_kwargs - ) - - def _make_dest(self, dest): - if dest: - return dest - - if self.long_option_name: - return self.long_option_name[2:].replace("-", "_") - return self.short_option_name[1] + parts = [] + for arg in self.option_args: + parts.append(arg) + for k, v in self.filtered_option_kwargs.items(): + parts.append("{}={!r}".format(k, v)) + return "Option({})".format(", ".join(parts)) def normalize(self, value, *normalize_args): """Normalize the value based on the option configuration.""" @@ -158,11 +265,11 @@ class Option(object): def normalize_from_setuptools(self, value): """Normalize the value received from setuptools.""" value = self.normalize(value) - if self.type == "int" or self.action == "count": + if self.type is int or self.action == "count": return int(value) - elif self.type == "float": + elif self.type is float: return float(value) - elif self.type == "complex": + elif self.type is complex: return complex(value) if self.action in ("store_true", "store_false"): value = str(value).upper() @@ -172,13 +279,14 @@ class Option(object): return False return value + def to_argparse(self): + """Convert a Flake8 Option to argparse ``add_argument`` arguments.""" + return self.option_args, self.filtered_option_kwargs + + @property def to_optparse(self): - """Convert a Flake8 Option to an optparse Option.""" - if self._opt is None: - self._opt = optparse.Option( - *self.option_args, **self.option_kwargs - ) - return self._opt + """No longer functional.""" + raise AttributeError("to_optparse: flake8 now uses argparse") PluginVersion = collections.namedtuple( @@ -190,7 +298,10 @@ class OptionManager(object): """Manage Options and OptionParser while adding post-processing.""" def __init__( - self, prog=None, version=None, usage="%prog [options] file file ..." + self, + prog=None, + version=None, + usage="%(prog)s [options] file file ...", ): """Initialize an instance of an OptionManager. @@ -201,9 +312,11 @@ class OptionManager(object): :param str usage: Basic usage string used by the OptionParser. """ - self.parser = optparse.OptionParser( - prog=prog, version=version, usage=usage + self.parser = argparse.ArgumentParser(prog=prog, usage=usage) + self.version_action = self.parser.add_argument( + "--version", action="version", version=version ) + self.parser.add_argument("filenames", nargs="*", metavar="filename") self.config_options_dict = {} # type: Dict[str, Option] self.options = [] # type: List[Option] self.program_name = prog @@ -226,12 +339,11 @@ class OptionManager(object): .. note:: ``short_option_name`` and ``long_option_name`` may be specified - positionally as they are with optparse normally. + positionally as they are with argparse normally. """ - if len(args) == 1 and args[0].startswith("--"): - args = (None, args[0]) option = Option(*args, **kwargs) - self.parser.add_option(option.to_optparse()) + option_args, option_kwargs = option.to_argparse() + self.parser.add_argument(*option_args, **option_kwargs) self.options.append(option) if option.parse_from_config: name = option.config_name @@ -289,12 +401,8 @@ class OptionManager(object): def update_version_string(self): """Update the flake8 version string.""" - self.parser.version = ( - self.version - + " (" - + self.generate_versions() - + ") " - + utils.get_python_version() + self.version_action.version = "{} ({}) {}".format( + self.version, self.generate_versions(), utils.get_python_version() ) def generate_epilog(self): @@ -304,18 +412,13 @@ class OptionManager(object): plugin_version_format ) - def _normalize(self, options): - for option in self.options: - old_value = getattr(options, option.dest) - setattr(options, option.dest, option.normalize(old_value)) - def parse_args(self, args=None, values=None): """Proxy to calling the OptionParser's parse_args method.""" self.generate_epilog() self.update_version_string() - options, xargs = self.parser.parse_args(args, values) - self._normalize(options) - return options, xargs + args = self.parser.parse_args(args, values) + # TODO: refactor callers to not need this + return args, args.filenames def parse_known_args(self, args=None, values=None): """Parse only the known arguments from the argument values. @@ -325,33 +428,8 @@ class OptionManager(object): """ self.generate_epilog() self.update_version_string() - # Taken from optparse.OptionParser.parse_args - rargs = self.parser._get_args(args) - if values is None: - values = self.parser.get_default_values() - - self.parser.rargs = rargs - largs = [] # type: List[str] - self.parser.values = values - - while rargs: - # NOTE(sigmavirus24): If we only care about *known* options, then - # we should just shift the bad option over to the largs list and - # carry on. - # Unfortunately, we need to rely on a private method here. - try: - self.parser._process_args(largs, rargs, values) - except ( - optparse.BadOptionError, - optparse.OptionValueError, - ) as err: - # TODO: https://gitlab.com/pycqa/flake8/issues/541 - largs.append(err.opt_str) # type: ignore - - args = largs + rargs - options, xargs = self.parser.check_values(values, args) - self._normalize(options) - return options, xargs + args, rest = self.parser.parse_known_args(args, values) + return args, rest def register_plugin(self, name, version, local=False): """Register a plugin relying on the OptionManager. diff --git a/src/flake8/plugins/pyflakes.py b/src/flake8/plugins/pyflakes.py index 018d1c9..b216b62 100644 --- a/src/flake8/plugins/pyflakes.py +++ b/src/flake8/plugins/pyflakes.py @@ -118,7 +118,6 @@ class FlakesChecker(pyflakes.checker.Checker): comma_separated_list=True, normalize_paths=True, help="Run doctests only on these files", - type="string", ) parser.add_option( "--exclude-from-doctest", @@ -128,7 +127,6 @@ class FlakesChecker(pyflakes.checker.Checker): comma_separated_list=True, normalize_paths=True, help="Skip these files when running doctests", - type="string", ) @classmethod diff --git a/src/flake8/style_guide.py b/src/flake8/style_guide.py index b644f8b..8d3c8cf 100644 --- a/src/flake8/style_guide.py +++ b/src/flake8/style_guide.py @@ -349,7 +349,7 @@ class StyleGuideManager(object): :param options: The original options parsed from the CLI and config file. :type options: - :class:`~optparse.Values` + :class:`~argparse.Namespace` :returns: A copy of the default style guide with overridden values. :rtype: diff --git a/tests/integration/test_main.py b/tests/integration/test_main.py index ccc70e1..001f1ff 100644 --- a/tests/integration/test_main.py +++ b/tests/integration/test_main.py @@ -1,10 +1,18 @@ """Integration tests for the main entrypoint of flake8.""" +import json import os import mock +import pytest from flake8 import utils -from flake8.main import application +from flake8.main import cli + + +def _call_main(argv, retv=0): + with pytest.raises(SystemExit) as excinfo: + cli.main(argv) + assert excinfo.value.code == retv def test_diff_option(tmpdir, capsys): @@ -36,9 +44,7 @@ index d64ac39..7d943de 100644 with mock.patch.object(utils, 'stdin_get_value', return_value=diff): with tmpdir.as_cwd(): tmpdir.join('t.py').write(t_py_contents) - - app = application.Application() - app.run(['--diff']) + _call_main(['--diff'], retv=1) out, err = capsys.readouterr() assert out == "t.py:8:1: F821 undefined name 'y'\n" @@ -49,9 +55,7 @@ def test_statistics_option(tmpdir, capsys): """Ensure that `flake8 --statistics` works.""" with tmpdir.as_cwd(): tmpdir.join('t.py').write('import os\nimport sys\n') - - app = application.Application() - app.run(['--statistics', 't.py']) + _call_main(['--statistics', 't.py'], retv=1) out, err = capsys.readouterr() assert out == '''\ @@ -68,7 +72,7 @@ def test_extend_exclude(tmpdir, capsys): tmpdir.mkdir(d).join('t.py').write('import os\nimport sys\n') with tmpdir.as_cwd(): - application.Application().run(['--extend-exclude=vendor,legacy']) + _call_main(['--extend-exclude=vendor,legacy'], retv=1) out, err = capsys.readouterr() expected_out = '''\ @@ -90,9 +94,7 @@ per-file-ignores = with tmpdir.as_cwd(): tmpdir.join('setup.cfg').write(setup_cfg) - - app = application.Application() - app.run(['.']) + _call_main(['.'], retv=1) out, err = capsys.readouterr() assert out == '''\ @@ -111,10 +113,16 @@ def test_tokenization_error_but_not_syntax_error(tmpdir, capsys): with tmpdir.as_cwd(): # this is a crash in the tokenizer, but not in the ast tmpdir.join('t.py').write("b'foo' \\\n") - - app = application.Application() - app.run(['t.py']) + _call_main(['t.py'], retv=1) out, err = capsys.readouterr() assert out == 't.py:1:1: E902 TokenError: EOF in multi-line statement\n' assert err == '' + + +def test_bug_report_successful(capsys): + """Test that --bug-report does not crash.""" + _call_main(['--bug-report']) + out, err = capsys.readouterr() + assert json.loads(out) + assert err == '' diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index d570564..3541ec3 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -1,5 +1,5 @@ """Shared fixtures between unit tests.""" -import optparse +import argparse import pytest @@ -11,7 +11,7 @@ def options_from(**kwargs): kwargs.setdefault('max_doc_length', None) kwargs.setdefault('verbose', False) kwargs.setdefault('stdin_display_name', 'stdin') - return optparse.Values(kwargs) + return argparse.Namespace(**kwargs) @pytest.fixture diff --git a/tests/unit/test_application.py b/tests/unit/test_application.py index cb8372b..8e5c9d5 100644 --- a/tests/unit/test_application.py +++ b/tests/unit/test_application.py @@ -1,5 +1,5 @@ """Tests for the Application class.""" -import optparse +import argparse import sys import mock @@ -9,12 +9,12 @@ from flake8.main import application as app def options(**kwargs): - """Generate optparse.Values for our Application.""" + """Generate argparse.Namespace for our Application.""" kwargs.setdefault('verbose', 0) kwargs.setdefault('output_file', None) kwargs.setdefault('count', False) kwargs.setdefault('exit_zero', False) - return optparse.Values(kwargs) + return argparse.Namespace(**kwargs) @pytest.fixture diff --git a/tests/unit/test_base_formatter.py b/tests/unit/test_base_formatter.py index ee589aa..7a85554 100644 --- a/tests/unit/test_base_formatter.py +++ b/tests/unit/test_base_formatter.py @@ -1,5 +1,5 @@ """Tests for the BaseFormatter object.""" -import optparse +import argparse import mock import pytest @@ -9,10 +9,10 @@ from flake8.formatting import base def options(**kwargs): - """Create an optparse.Values instance.""" + """Create an argparse.Namespace instance.""" kwargs.setdefault('output_file', None) kwargs.setdefault('tee', False) - return optparse.Values(kwargs) + return argparse.Namespace(**kwargs) @pytest.mark.parametrize('filename', [None, 'out.txt']) diff --git a/tests/unit/test_debug.py b/tests/unit/test_debug.py index 2b7995d..c973abb 100644 --- a/tests/unit/test_debug.py +++ b/tests/unit/test_debug.py @@ -73,9 +73,10 @@ def test_information(system, pyversion, pyimpl): def test_print_information_no_plugins(dumps, information, print_mock): """Verify we print and exit only when we have plugins.""" option_manager = mock.Mock(registered_plugins=set()) - assert debug.print_information( - None, None, None, None, option_manager=option_manager, - ) is None + action = debug.DebugAction( + "--bug-report", dest="bug_report", option_manager=option_manager, + ) + assert action(None, None, None, None) is None assert dumps.called is False assert information.called is False assert print_mock.called is False @@ -91,10 +92,11 @@ def test_print_information(dumps, information, print_mock): manager.PluginVersion('mccabe', '0.5.9', False), ] option_manager = mock.Mock(registered_plugins=set(plugins)) + action = debug.DebugAction( + "--bug-report", dest="bug_report", option_manager=option_manager, + ) with pytest.raises(SystemExit): - debug.print_information( - None, None, None, None, option_manager=option_manager, - ) + action(None, None, None, None) print_mock.assert_called_once_with('{}') dumps.assert_called_once_with({}, indent=2, sort_keys=True) information.assert_called_once_with(option_manager) diff --git a/tests/unit/test_decision_engine.py b/tests/unit/test_decision_engine.py index 635c9d8..7d92309 100644 --- a/tests/unit/test_decision_engine.py +++ b/tests/unit/test_decision_engine.py @@ -1,5 +1,5 @@ """Tests for the flake8.style_guide.DecisionEngine class.""" -import optparse +import argparse import pytest @@ -8,14 +8,14 @@ from flake8 import style_guide def create_options(**kwargs): - """Create and return an instance of optparse.Values.""" + """Create and return an instance of argparse.Namespace.""" kwargs.setdefault('select', []) kwargs.setdefault('extended_default_select', []) kwargs.setdefault('ignore', []) kwargs.setdefault('extend_ignore', []) kwargs.setdefault('disable_noqa', False) kwargs.setdefault('enable_extensions', []) - return optparse.Values(kwargs) + return argparse.Namespace(**kwargs) @pytest.mark.parametrize('ignore_list,extend_ignore,error_code', [ diff --git a/tests/unit/test_filenameonly_formatter.py b/tests/unit/test_filenameonly_formatter.py index b423ee3..8d0c88f 100644 --- a/tests/unit/test_filenameonly_formatter.py +++ b/tests/unit/test_filenameonly_formatter.py @@ -1,15 +1,15 @@ """Tests for the FilenameOnly formatter object.""" -import optparse +import argparse from flake8 import style_guide from flake8.formatting import default def options(**kwargs): - """Create an optparse.Values instance.""" + """Create an argparse.Namespace instance.""" kwargs.setdefault('output_file', None) kwargs.setdefault('tee', False) - return optparse.Values(kwargs) + return argparse.Namespace(**kwargs) def test_caches_filenames_already_printed(): diff --git a/tests/unit/test_merged_config_parser.py b/tests/unit/test_merged_config_parser.py index 37d5e6f..1112a0d 100644 --- a/tests/unit/test_merged_config_parser.py +++ b/tests/unit/test_merged_config_parser.py @@ -215,7 +215,7 @@ def test_parsed_hyphenated_and_underscored_names( max_line_length in our config files. """ optmanager.add_option('--max-line-length', parse_from_config=True, - type='int') + type=int) optmanager.add_option('--enable-extensions', parse_from_config=True, comma_separated_list=True) parser = config.MergedConfigParser(optmanager, config_finder) diff --git a/tests/unit/test_nothing_formatter.py b/tests/unit/test_nothing_formatter.py index a1fd683..85a2e76 100644 --- a/tests/unit/test_nothing_formatter.py +++ b/tests/unit/test_nothing_formatter.py @@ -1,15 +1,15 @@ """Tests for the Nothing formatter obbject.""" -import optparse +import argparse from flake8 import style_guide from flake8.formatting import default def options(**kwargs): - """Create an optparse.Values instance.""" + """Create an argparse.Namespace instance.""" kwargs.setdefault('output_file', None) kwargs.setdefault('tee', False) - return optparse.Values(kwargs) + return argparse.Namespace(**kwargs) def test_format_returns_nothing(): diff --git a/tests/unit/test_option.py b/tests/unit/test_option.py index e931d13..d4607b3 100644 --- a/tests/unit/test_option.py +++ b/tests/unit/test_option.py @@ -1,12 +1,14 @@ """Unit tests for flake8.options.manager.Option.""" +import functools + import mock import pytest from flake8.options import manager -def test_to_optparse(): - """Test conversion to an optparse.Option class.""" +def test_to_argparse(): + """Test conversion to an argparse arguments.""" opt = manager.Option( short_option_name='-t', long_option_name='--test', @@ -17,45 +19,26 @@ def test_to_optparse(): assert opt.normalize_paths is True assert opt.parse_from_config is True - optparse_opt = opt.to_optparse() - assert not hasattr(optparse_opt, 'parse_from_config') - assert not hasattr(optparse_opt, 'normalize_paths') - assert optparse_opt.action == 'count' + args, kwargs = opt.to_argparse() + assert args == ['-t', '--test'] + assert kwargs == {'action': 'count', 'type': mock.ANY} + assert isinstance(kwargs['type'], functools.partial) -@pytest.mark.parametrize('opttype,str_val,expected', [ - ('float', '2', 2.0), - ('complex', '2', (2 + 0j)), -]) -def test_to_support_optparses_standard_types(opttype, str_val, expected): - """Show that optparse converts float and complex types correctly.""" - opt = manager.Option('-t', '--test', type=opttype) - assert opt.normalize_from_setuptools(str_val) == expected +def test_to_optparse(): + """Test that .to_optparse() produces a useful error message.""" + with pytest.raises(AttributeError) as excinfo: + manager.Option('--foo').to_optparse() + msg, = excinfo.value.args + assert msg == 'to_optparse: flake8 now uses argparse' -@mock.patch('optparse.Option') -def test_to_optparse_creates_an_option_as_we_expect(Option): # noqa: N803 - """Show that we pass all keyword args to optparse.Option.""" +def test_to_argparse_creates_an_option_as_we_expect(): + """Show that we pass all keyword args to argparse.""" opt = manager.Option('-t', '--test', action='count') - opt.to_optparse() - option_kwargs = { - 'action': 'count', - 'default': None, - 'type': None, - 'dest': 'test', - 'nargs': None, - 'const': None, - 'choices': None, - 'callback': None, - 'callback_args': None, - 'callback_kwargs': None, - 'help': None, - 'metavar': None, - } - - Option.assert_called_once_with( - '-t', '--test', **option_kwargs - ) + args, kwargs = opt.to_argparse() + assert args == ['-t', '--test'] + assert kwargs == {'action': 'count'} def test_config_name_generation(): diff --git a/tests/unit/test_option_manager.py b/tests/unit/test_option_manager.py index df1f9a2..ebcd9e7 100644 --- a/tests/unit/test_option_manager.py +++ b/tests/unit/test_option_manager.py @@ -1,5 +1,5 @@ """Unit tests for flake.options.manager.OptionManager.""" -import optparse +import argparse import os import mock @@ -19,8 +19,7 @@ def optmanager(): def test_option_manager_creates_option_parser(optmanager): """Verify that a new manager creates a new parser.""" - assert optmanager.parser is not None - assert isinstance(optmanager.parser, optparse.OptionParser) is True + assert isinstance(optmanager.parser, argparse.ArgumentParser) def test_add_option_short_option_only(optmanager): @@ -38,7 +37,7 @@ def test_add_option_long_option_only(optmanager): assert optmanager.config_options_dict == {} optmanager.add_option('--long', help='Test long opt') - assert optmanager.options[0].short_option_name is None + assert optmanager.options[0].short_option_name is manager._NOARG assert optmanager.options[0].long_option_name == '--long' @@ -171,7 +170,7 @@ def test_generate_versions_with_format_string(optmanager): def test_update_version_string(optmanager): """Verify we update the version string idempotently.""" assert optmanager.version == TEST_VERSION - assert optmanager.parser.version == TEST_VERSION + assert optmanager.version_action.version == TEST_VERSION optmanager.registered_plugins = [ manager.PluginVersion('Testing 100', '0.0.0', False), @@ -182,7 +181,7 @@ def test_update_version_string(optmanager): optmanager.update_version_string() assert optmanager.version == TEST_VERSION - assert (optmanager.parser.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()) @@ -211,9 +210,7 @@ def test_extend_default_ignore(optmanager): assert optmanager.extended_default_ignore == set() optmanager.extend_default_ignore(['T100', 'T101', 'T102']) - assert optmanager.extended_default_ignore == {'T100', - 'T101', - 'T102'} + assert optmanager.extended_default_ignore == {'T100', 'T101', 'T102'} def test_parse_known_args(optmanager): @@ -222,3 +219,91 @@ def test_parse_known_args(optmanager): 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() + optmanager.add_option( + '--foo', + action='callback', + callback=callback_foo, + callback_args=(1, 2), + callback_kwargs={'a': 'b'}, + ) + callback_bar = mock.Mock() + optmanager.add_option( + '--bar', + action='callback', + type='string', + callback=callback_bar, + ) + callback_baz = mock.Mock() + optmanager.add_option( + '--baz', + action='callback', + type='string', + nargs=2, + callback=callback_baz, + ) + + optmanager.parse_args(['--foo', '--bar', 'bararg', '--baz', '1', '2']) + + callback_foo.assert_called_once_with( + mock.ANY, # the option / action instance + '--foo', + None, + mock.ANY, # the OptionParser / ArgumentParser + 1, + 2, + a='b', + ) + callback_bar.assert_called_once_with( + mock.ANY, # the option / action instance + '--bar', + 'bararg', + mock.ANY, # the OptionParser / ArgumentParser + ) + callback_baz.assert_called_once_with( + mock.ANY, # the option / action instance + '--baz', + ('1', '2'), + mock.ANY, # the OptionParser / ArgumentParser + ) + + +@pytest.mark.parametrize( + ('type_s', 'input_val', 'expected'), + ( + ('int', '5', 5), + ('long', '6', 6), + ('string', 'foo', 'foo'), + ('float', '1.5', 1.5), + ('complex', '1+5j', 1 + 5j), + ), +) +def test_optparse_normalize_types(optmanager, type_s, input_val, expected): + """Test the optparse shim for type="typename".""" + optmanager.add_option('--foo', type=type_s) + opts, args = optmanager.parse_args(['--foo', input_val]) + assert opts.foo == expected + + +def test_optparse_normalize_choice_type(optmanager): + """Test the optparse shim for type="choice".""" + optmanager.add_option('--foo', type='choice', choices=('1', '2', '3')) + opts, args = optmanager.parse_args(['--foo', '1']) + assert opts.foo == '1' + # fails to parse + with pytest.raises(SystemExit): + optmanager.parse_args(['--foo', '4']) + + +def test_optparse_normalize_help(optmanager, capsys): + """Test the optparse shim for %default in help text.""" + optmanager.add_option('--foo', default='bar', help='default: %default') + with pytest.raises(SystemExit): + optmanager.parse_args(['--help']) + out, err = capsys.readouterr() + output = out + err + assert 'default: bar' in output diff --git a/tests/unit/test_plugin.py b/tests/unit/test_plugin.py index 4d5510f..cf87ea1 100644 --- a/tests/unit/test_plugin.py +++ b/tests/unit/test_plugin.py @@ -1,5 +1,5 @@ """Tests for flake8.plugins.manager.Plugin.""" -import optparse +import argparse import mock import pytest @@ -124,7 +124,7 @@ def test_provide_options(): entry_point = mock.Mock(spec=['load']) plugin_obj = mock.Mock(spec_set=['name', 'version', 'add_options', 'parse_options']) - option_values = optparse.Values({'enable_extensions': []}) + option_values = argparse.Namespace(enable_extensions=[]) option_manager = mock.Mock() plugin = manager.Plugin('T000', entry_point) plugin._plugin = plugin_obj diff --git a/tests/unit/test_style_guide.py b/tests/unit/test_style_guide.py index 46ab28e..38121c1 100644 --- a/tests/unit/test_style_guide.py +++ b/tests/unit/test_style_guide.py @@ -1,5 +1,5 @@ """Tests for the flake8.style_guide.StyleGuide class.""" -import optparse +import argparse import mock import pytest @@ -11,7 +11,7 @@ from flake8.formatting import base def create_options(**kwargs): - """Create and return an instance of optparse.Values.""" + """Create and return an instance of argparse.Namespace.""" kwargs.setdefault('select', []) kwargs.setdefault('extended_default_select', []) kwargs.setdefault('ignore', []) @@ -19,7 +19,7 @@ def create_options(**kwargs): kwargs.setdefault('disable_noqa', False) kwargs.setdefault('enable_extensions', []) kwargs.setdefault('per_file_ignores', []) - return optparse.Values(kwargs) + return argparse.Namespace(**kwargs) def test_handle_error_does_not_raise_type_errors(): diff --git a/tox.ini b/tox.ini index 6765fd8..b7d4fc0 100644 --- a/tox.ini +++ b/tox.ini @@ -8,9 +8,9 @@ deps = pytest!=3.0.5 coverage commands = - coverage run --parallel-mode -m pytest {posargs} + coverage run -m pytest {posargs} coverage combine - coverage report -m + coverage report [testenv:venv] deps = From 76515bbb8e0f5679d5ad69f0b38eae059ef593ce Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 18 Aug 2019 22:05:00 +0000 Subject: [PATCH 2/2] Apply suggestion to src/flake8/formatting/base.py --- src/flake8/formatting/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/flake8/formatting/base.py b/src/flake8/formatting/base.py index 36dc905..ae78f49 100644 --- a/src/flake8/formatting/base.py +++ b/src/flake8/formatting/base.py @@ -38,9 +38,11 @@ class BaseFormatter(object): This also calls a hook, :meth:`after_init`, so subclasses do not need to call super to call this method. - :param argparse.Namespace options: + :param options: User specified configuration parsed from both configuration files and the command-line interface. + :type options: + :class:`argparse.Namespace` """ self.options = options self.filename = options.output_file