Handle optional parameters that were never supported

Previously, pycodestyle never introspected the argument names for
classes except to require that ``tree`` be an argument it could pass.
For Flake8 3.0, we lifted that restriction, but old plugins seem to
have cargo-culted their __init__ signature to be

    def __init__(self, tree, builtins=None):

For some yet unknown reason. This was causing an AttributeError. By
updating flake8.utils.parameters_for to return a dictionary that
indicates whether the parameter is required or not, we can side-step
this by simply ignoring the parameter if it has a default value and
we cannot provide it.

Closes #151
This commit is contained in:
Ian Cordasco 2016-06-28 09:36:24 -05:00
parent ec2e601cbf
commit 2d3e277b1e
No known key found for this signature in database
GPG key ID: 656D3395E4A9791A
5 changed files with 49 additions and 20 deletions

View file

@ -38,6 +38,7 @@ class Plugin(object):
self.entry_point = entry_point
self._plugin = None
self._parameters = None
self._parameter_names = None
self._group = None
self._plugin_name = None
self._version = None
@ -77,6 +78,13 @@ class Plugin(object):
self._parameters = utils.parameters_for(self)
return self._parameters
@property
def parameter_names(self):
"""List of argument names that need to be passed to the plugin."""
if self._parameter_names is None:
self._parameter_names = list(self.parameters)
return self._parameter_names
@property
def plugin(self):
"""The loaded (and cached) plugin associated with the entry-point.
@ -416,7 +424,7 @@ class Checkers(PluginTypeManager):
Find all checker plugins that are expecting a specific argument.
"""
for plugin in self.plugins.values():
if argument_name == plugin.parameters[0]:
if argument_name == plugin.parameter_names[0]:
yield plugin
def register_options(self, optmanager):

View file

@ -206,14 +206,19 @@ class FileProcessor(object):
"""Generate the keyword arguments for a list of parameters."""
if arguments is None:
arguments = {}
for param in parameters:
for param, required in parameters.items():
if param in arguments:
continue
try:
arguments[param] = getattr(self, param)
except AttributeError as exc:
LOG.exception(exc)
raise
if required:
LOG.exception(exc)
raise
else:
LOG.warning('Plugin requested optional parameter "%s" '
'but this is not an available parameter.',
param)
return arguments
def check_physical_error(self, error_code, line):

View file

@ -249,7 +249,7 @@ def fnmatch(filename, patterns, default=True):
def parameters_for(plugin):
# type: (flake8.plugins.manager.Plugin) -> List[str]
# type: (flake8.plugins.manager.Plugin) -> Dict[str, bool]
"""Return the parameters for the plugin.
This will inspect the plugin and return either the function parameters
@ -261,9 +261,10 @@ def parameters_for(plugin):
:type plugin:
flake8.plugins.manager.Plugin
:returns:
Parameters to the plugin.
A dictionary mapping the parameter name to whether or not it is
required (a.k.a., is positional only/does not have a default).
:rtype:
list(str)
dict([(str, bool)])
"""
func = plugin.plugin
is_class = not inspect.isfunction(func)
@ -271,15 +272,21 @@ def parameters_for(plugin):
func = plugin.plugin.__init__
if sys.version_info < (3, 3):
parameters = inspect.getargspec(func)[0]
argspec = inspect.getargspec(func)
start_of_optional_args = len(argspec[0]) - len(argspec[-1] or [])
parameter_names = argspec[0]
parameters = collections.OrderedDict([
(name, position < start_of_optional_args)
for position, name in enumerate(parameter_names)
])
else:
parameters = [
parameter.name
parameters = collections.OrderedDict([
(parameter.name, parameter.default is parameter.empty)
for parameter in inspect.signature(func).parameters.values()
if parameter.kind == parameter.POSITIONAL_OR_KEYWORD
]
])
if is_class:
parameters.remove('self')
parameters.pop('self', None)
return parameters