Writing Plugins For Flake8 2 and 3¶
Plugins have existed for Flake8 2.x for a few years. There are a number of these on PyPI already. While it did not seem reasonable for Flake8 to attempt to provide a backwards compatible shim for them, we did decide to try to document the easiest way to write a plugin that’s compatible across both versions.
Note
If your plugin does not register options, it should Just Work.
The only breaking change in Flake8 3.0 is the fact that we no longer check the option parser for a list of strings to parse from a config file. On Flake8 2.x, to have an option parsed from the configuration files that Flake8 finds and parses you would have to do something like:
parser.add_option('-X', '--example-flag', type='string',
help='...')
parser.config_options.append('example-flag')
For Flake8 3.0, we have added three arguments to the
add_option()
method you will call
on the parser you receive:
parse_from_config
which expectsTrue
orFalse
When
True
, Flake8 will parse the option from the config files Flake8 finds.comma_separated_list
which expectsTrue
orFalse
When
True
, Flake8 will split the string intelligently and handle extra whitespace. The parsed value will be a list.normalize_paths
which expectsTrue
orFalse
When
True
, Flake8 will:- remove trailing path separators (i.e.,
os.path.sep
) - return the absolute path for values that have the separator in them
- remove trailing path separators (i.e.,
All three of these options can be combined or used separately.
Parsing Options from Configuration Files¶
The example from Flake8 2.x now looks like:
parser.add_option('-X', '--example-flag', type='string',
parse_from_config=True,
help='...')
Parsing Comma-Separated Lists¶
Now let’s imagine that the option we want to add is expecting a comma-separatd
list of values from the user (e.g., --select E123,W503,F405
). Flake8 2.x
often forced users to parse these lists themselves since pep8 special-cased
certain flags and left others on their own. Flake8 3.0 adds
comma_separated_list
so that the parsed option is already a list for
plugin authors. When combined with parse_from_config
this means that users
can also do something like:
example-flag =
first,
second,
third,
fourth,
fifth
And Flake8 will just return the list:
["first", "second", "third", "fourth", "fifth"]
Normalizing Values that Are Paths¶
Finally, let’s imagine that our new option wants a path or list of paths. To
ensure that these paths are semi-normalized (the way Flake8 2.x used to
work) we need only pass normalize_paths=True
. If you have specified
comma_separated_list=True
then this will parse the value as a list of
paths that have been normalized. Otherwise, this will parse the value
as a single path.
Option Handling on Flake8 2 and 3¶
So, in conclusion, we can now write our plugin that relies on registering options with Flake8 and have it work on Flake8 2.x and 3.x.
option_args = ('-X', '--example-flag')
option_kwargs = {
'type': 'string',
'parse_from_config': True,
'help': '...',
}
try:
# Flake8 3.x registration
parser.add_option(*option_args, **option_kwargs)
except TypeError:
# Flake8 2.x registration
parse_from_config = option_kwargs.pop('parse_from_config', False)
parser.add_option(*option_args, **option_kwargs)
if parse_from_config:
parser.config_options.append(option_args[-1].lstrip('-'))
Or, you can write a tiny helper function:
def register_opt(parser, *args, **kwargs):
try:
# Flake8 3.x registration
parser.add_option(*args, **kwargs)
except TypeError:
# Flake8 2.x registration
parse_from_config = kwargs.pop('parse_from_config', False)
parser.add_option(*args, **kwargs)
if parse_from_config:
parser.config_options.append(args[-1].lstrip('-'))
@classmethod
def register_options(cls, parser):
register_opt(parser, '-X', '--example-flag', type='string',
parse_from_config=True, help='...')
The transition period is admittedly not fantastic, but we believe that this is a worthwhile change for plugin developers going forward. We also hope to help with the transition phase for as many plugins as we can manage.