flake8/docs/source/plugin-development/cross-compatibility.rst
Fabian Neundorf fabb13c5d1 Use option name provided by the parser
When calling `add_option` it returns an `Option` object which can return the
primary name of the option via `get_opt_name`. This should be used primarily
in the cross compatible implementation so that the order of parameters does
not matter.
2016-07-09 21:27:19 +02:00

156 lines
5.1 KiB
ReStructuredText

====================================
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:
.. code-block:: python
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
:meth:`~flake8.options.manager.OptionManager.add_option` method you will call
on the parser you receive:
- ``parse_from_config`` which expects ``True`` or ``False``
When ``True``, |Flake8| will parse the option from the config files |Flake8|
finds.
- ``comma_separated_list`` which expects ``True`` or ``False``
When ``True``, |Flake8| will split the string intelligently and handle
extra whitespace. The parsed value will be a list.
- ``normalize_paths`` which expects ``True`` or ``False``
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
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:
.. code-block:: python
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:
.. code-block:: ini
example-flag =
first,
second,
third,
fourth,
fifth
And |Flake8| will just return the list:
.. code-block:: python
["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.
.. code-block:: python
import optparse
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 (optparse.OptionError, TypeError):
# Flake8 2.x registration
parse_from_config = option_kwargs.pop('parse_from_config', False)
option = parser.add_option(*option_args, **option_kwargs)
if parse_from_config:
parser.config_options.append(option.get_opt_string().lstrip('-'))
Or, you can write a tiny helper function:
.. code-block:: python
import optparse
def register_opt(parser, *args, **kwargs):
try:
# Flake8 3.x registration
parser.add_option(*args, **kwargs)
except (optparse.OptionError, TypeError):
# Flake8 2.x registration
parse_from_config = kwargs.pop('parse_from_config', False)
kwargs.pop('comma_separated_list', False)
kwargs.pop('normalize_paths', False)
option = parser.add_option(*args, **kwargs)
if parse_from_config:
parser.config_options.append(option.get_opt_string().lstrip('-'))
.. code-block:: python
@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.