From 6c0d900a381c21ce678b6391215fa1a48583310a Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 22 Jun 2016 08:40:53 -0500 Subject: [PATCH] Write plugin cross-compatibility documentation --- docs/source/dev/cross_compatibility.rst | 149 ++++++++++++++++++++++++ docs/source/dev/index.rst | 1 + 2 files changed, 150 insertions(+) create mode 100644 docs/source/dev/cross_compatibility.rst diff --git a/docs/source/dev/cross_compatibility.rst b/docs/source/dev/cross_compatibility.rst new file mode 100644 index 0000000..c546f03 --- /dev/null +++ b/docs/source/dev/cross_compatibility.rst @@ -0,0 +1,149 @@ +==================================== + 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 + + 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: + +.. code-block:: python + + 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('-')) + +.. code-block:: python + + def register_options(self, 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. diff --git a/docs/source/dev/index.rst b/docs/source/dev/index.rst index 9ef8138..858ec83 100644 --- a/docs/source/dev/index.rst +++ b/docs/source/dev/index.rst @@ -12,6 +12,7 @@ the authoritative documentation for those pieces. .. toctree:: :maxdepth: 2 + cross_compatibility plugin_parameters registering_plugins formatters