cross-compatibility.rst 6.55 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
====================================
 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.

15
The **only two** breaking changes in |Flake8| 3.0 is the fact that we no
16 17 18 19
longer check the option parser for a list of strings to parse from a config
file and we no longer patch pep8 or pycodestyle's ``stdin_get_value``
functions. 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:
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104

.. 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
=================================

105 106 107 108 109
To ease the transition, the |Flake8| maintainers have released
`flake8-polyfill`_. |polyfill| provides a convenience function to help users
transition between Flake8 2 and 3 without issue. For example, if your plugin
has to work on Flake8 2.x and 3.x but you want to take advantage of some of
the new options to ``add_option``, you can do
110 111 112

.. code-block:: python

113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
    from flake8_polyfill import options


    class MyPlugin(object):
        @classmethod
        def add_options(cls, parser):
            options.register(
                parser,
                '--application-names', default='', type='string',
                help='Names of the applications to be checked.',
                parse_from_config=True,
                comma_separated_list=True,
            )
            options.register(
                parser,
                '--style-name', default='', type='string',
                help='The name of the style convention you want to use',
                parse_from_config=True,
            )
            options.register(
                parser,
                '--application-paths', default='', type='string',
                help='Locations of the application code',
                parse_from_config=True,
                comma_separated_list=True,
                normalize_paths=True,
            )

        @classmethod
        def parse_options(cls, parsed_options):
            cls.application_names = parsed_options.application_names
            cls.style_name = parsed_options.style_name
            cls.application_paths = parsed_options.application_paths

|polyfill| will handle these extra options using *callbacks* to the option
parser. The project has direct replications of the functions that |Flake8|
uses to provide the same functionality. This means that the values you receive
should be identically parsed whether you're using Flake8 2.x or 3.x.

.. autofunction:: flake8_polyfill.options.register


Standard In Handling on Flake8 2.5, 2.6, and 3
==============================================

After releasing |Flake8| 2.6, handling standard-in became a bit trickier for
some plugins. |Flake8| 2.5 and earlier had started monkey-patching pep8's
``stdin_get_value`` function. 2.6 switched to pycodestyle and only
monkey-patched that. 3.0 has its own internal implementation and uses that but
does not directly provide anything for plugins using pep8 and pycodestyle's
``stdin_get_value`` function. |polyfill| provides this functionality for
164
plugin developers via its :mod:`flake8_polyfill.stdin` module.
165 166

If a plugin needs to read the content from stdin, it can do the following:
Ian Cordasco's avatar
Ian Cordasco committed
167

168
.. code-block:: python
169

170
    from flake8_polyfill import stdin
171

172 173 174
    stdin.monkey_patch('pep8')  # To monkey-patch only pep8
    stdin.monkey_patch('pycodestyle')  # To monkey-patch only pycodestyle
    stdin.monkey_patch('all')  # To monkey-patch both pep8 and pycodestyle
175 176


177 178 179 180 181
Further, when using ``all``, |polyfill| does not require both packages to be
installed but will attempt to monkey-patch both and will silently ignore the
fact that pep8 or pycodestyle is not installed.

.. autofunction:: flake8_polyfill.stdin.monkey_patch
182 183


184
.. links
185
.. _flake8-polyfill: https://pypi.org/project/flake8-polyfill/
186

187
.. |polyfill| replace:: ``flake8-polyfill``