mirror of
https://github.com/PyCQA/flake8.git
synced 2026-04-15 16:49:52 +00:00
commit
1dad1e3a95
7 changed files with 118 additions and 64 deletions
|
|
@ -12,10 +12,11 @@ versions.
|
||||||
|
|
||||||
If your plugin does not register options, it *should* Just Work.
|
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
|
The **only two** breaking changes in |Flake8| 3.0 is the fact that we no
|
||||||
check the option parser for a list of strings to parse from a config file. On
|
longer check the option parser for a list of strings to parse from a config
|
||||||
|Flake8| 2.x, to have an option parsed from the configuration files that
|
file and we no longer patch pep8 or pycodestyle's ``stdin_get_value``
|
||||||
|Flake8| finds and parses you would have to do something like:
|
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:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
|
@ -101,56 +102,86 @@ as a single path.
|
||||||
Option Handling on Flake8 2 and 3
|
Option Handling on Flake8 2 and 3
|
||||||
=================================
|
=================================
|
||||||
|
|
||||||
So, in conclusion, we can now write our plugin that relies on registering
|
To ease the transition, the |Flake8| maintainers have released
|
||||||
options with |Flake8| and have it work on |Flake8| 2.x and 3.x.
|
`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
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import optparse
|
from flake8_polyfill import options
|
||||||
|
|
||||||
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:
|
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
|
||||||
|
plugin developers via it's :mod:`flake8_polyfill.stdin` module.
|
||||||
|
|
||||||
|
If a plugin needs to read the content from stdin, it can do the following:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import optparse
|
from flake8_polyfill import stdin
|
||||||
|
|
||||||
def register_opt(parser, *args, **kwargs):
|
stdin.monkey_patch('pep8') # To monkey-patch only pep8
|
||||||
try:
|
stdin.monkey_patch('pycodestyle') # To monkey-patch only pycodestyle
|
||||||
# Flake8 3.x registration
|
stdin.monkey_patch('all') # To monkey-patch both pep8 and pycodestyle
|
||||||
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
|
Further, when using ``all``, |polyfill| does not require both packages to be
|
||||||
def register_options(cls, parser):
|
installed but will attempt to monkey-patch both and will silently ignore the
|
||||||
register_opt(parser, '-X', '--example-flag', type='string',
|
fact that pep8 or pycodestyle is not installed.
|
||||||
parse_from_config=True, help='...')
|
|
||||||
|
|
||||||
The transition period is admittedly not fantastic, but we believe that this
|
.. autofunction:: flake8_polyfill.stdin.monkey_patch
|
||||||
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.
|
|
||||||
|
.. links
|
||||||
|
.. _flake8-polyfill: https://pypi.io/project/flake8-polyfill/
|
||||||
|
|
||||||
|
.. |polyfill| replace:: ``flake8-polyfill``
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,4 @@ sphinx>=1.3.0
|
||||||
sphinx_rtd_theme
|
sphinx_rtd_theme
|
||||||
sphinx-prompt
|
sphinx-prompt
|
||||||
configparser
|
configparser
|
||||||
|
flake8-polyfill
|
||||||
|
|
|
||||||
|
|
@ -234,6 +234,11 @@ class Manager(object):
|
||||||
:rtype:
|
:rtype:
|
||||||
bool
|
bool
|
||||||
"""
|
"""
|
||||||
|
if path == '-':
|
||||||
|
if self.options.stdin_display_name == 'stdin':
|
||||||
|
return False
|
||||||
|
path = self.options.stdin_display_name
|
||||||
|
|
||||||
exclude = self.options.exclude
|
exclude = self.options.exclude
|
||||||
if not exclude:
|
if not exclude:
|
||||||
return False
|
return False
|
||||||
|
|
@ -271,7 +276,7 @@ class Manager(object):
|
||||||
return (file_exists and matches_filename_patterns) or is_stdin
|
return (file_exists and matches_filename_patterns) or is_stdin
|
||||||
|
|
||||||
self.checkers = [
|
self.checkers = [
|
||||||
FileChecker(filename, self.checks, self.style_guide)
|
FileChecker(filename, self.checks, self.options)
|
||||||
for argument in paths
|
for argument in paths
|
||||||
for filename in utils.filenames_from(argument,
|
for filename in utils.filenames_from(argument,
|
||||||
self.is_path_excluded)
|
self.is_path_excluded)
|
||||||
|
|
@ -294,7 +299,7 @@ class Manager(object):
|
||||||
results_reported = results_found = 0
|
results_reported = results_found = 0
|
||||||
for checker in self.checkers:
|
for checker in self.checkers:
|
||||||
results = sorted(checker.results, key=lambda tup: (tup[2], tup[3]))
|
results = sorted(checker.results, key=lambda tup: (tup[2], tup[3]))
|
||||||
results_reported += self._handle_results(checker.filename,
|
results_reported += self._handle_results(checker.display_name,
|
||||||
results)
|
results)
|
||||||
results_found += len(results)
|
results_found += len(results)
|
||||||
return (results_found, results_reported)
|
return (results_found, results_reported)
|
||||||
|
|
@ -315,9 +320,9 @@ class Manager(object):
|
||||||
final_results[filename] = results
|
final_results[filename] = results
|
||||||
|
|
||||||
for checker in self.checkers:
|
for checker in self.checkers:
|
||||||
filename = checker.filename
|
filename = checker.display_name
|
||||||
checker.results = sorted(final_results.get(filename, []),
|
checker.results = sorted(final_results.get(filename, []),
|
||||||
key=lambda tup: (tup[1], tup[2]))
|
key=lambda tup: (tup[2], tup[2]))
|
||||||
|
|
||||||
def run_serial(self):
|
def run_serial(self):
|
||||||
"""Run the checkers in serial."""
|
"""Run the checkers in serial."""
|
||||||
|
|
@ -379,7 +384,7 @@ class Manager(object):
|
||||||
class FileChecker(object):
|
class FileChecker(object):
|
||||||
"""Manage running checks for a file and aggregate the results."""
|
"""Manage running checks for a file and aggregate the results."""
|
||||||
|
|
||||||
def __init__(self, filename, checks, style_guide):
|
def __init__(self, filename, checks, options):
|
||||||
"""Initialize our file checker.
|
"""Initialize our file checker.
|
||||||
|
|
||||||
:param str filename:
|
:param str filename:
|
||||||
|
|
@ -388,16 +393,17 @@ class FileChecker(object):
|
||||||
The plugins registered to check the file.
|
The plugins registered to check the file.
|
||||||
:type checks:
|
:type checks:
|
||||||
flake8.plugins.manager.Checkers
|
flake8.plugins.manager.Checkers
|
||||||
:param style_guide:
|
:param options:
|
||||||
The initialized StyleGuide for this particular run.
|
Parsed option values from config and command-line.
|
||||||
:type style_guide:
|
:type options:
|
||||||
flake8.style_guide.StyleGuide
|
optparse.Values
|
||||||
"""
|
"""
|
||||||
|
self.options = options
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.checks = checks
|
self.checks = checks
|
||||||
self.style_guide = style_guide
|
|
||||||
self.results = []
|
self.results = []
|
||||||
self.processor = self._make_processor()
|
self.processor = self._make_processor()
|
||||||
|
self.display_name = self.processor.filename
|
||||||
self.statistics = {
|
self.statistics = {
|
||||||
'tokens': 0,
|
'tokens': 0,
|
||||||
'logical lines': 0,
|
'logical lines': 0,
|
||||||
|
|
@ -406,8 +412,7 @@ class FileChecker(object):
|
||||||
|
|
||||||
def _make_processor(self):
|
def _make_processor(self):
|
||||||
try:
|
try:
|
||||||
return processor.FileProcessor(self.filename,
|
return processor.FileProcessor(self.filename, self.options)
|
||||||
self.style_guide.options)
|
|
||||||
except IOError:
|
except IOError:
|
||||||
# If we can not read the file due to an IOError (e.g., the file
|
# If we can not read the file due to an IOError (e.g., the file
|
||||||
# does not exist or we do not have the permissions to open it)
|
# does not exist or we do not have the permissions to open it)
|
||||||
|
|
|
||||||
|
|
@ -55,12 +55,12 @@ class FileProcessor(object):
|
||||||
:param str filename:
|
:param str filename:
|
||||||
Name of the file to process
|
Name of the file to process
|
||||||
"""
|
"""
|
||||||
|
self.options = options
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.lines = lines
|
self.lines = lines
|
||||||
if lines is None:
|
if lines is None:
|
||||||
self.lines = self.read_lines()
|
self.lines = self.read_lines()
|
||||||
self.strip_utf_bom()
|
self.strip_utf_bom()
|
||||||
self.options = options
|
|
||||||
|
|
||||||
# Defaults for public attributes
|
# Defaults for public attributes
|
||||||
#: Number of preceding blank lines
|
#: Number of preceding blank lines
|
||||||
|
|
@ -272,9 +272,11 @@ class FileProcessor(object):
|
||||||
# type: () -> List[str]
|
# type: () -> List[str]
|
||||||
"""Read the lines for this file checker."""
|
"""Read the lines for this file checker."""
|
||||||
if self.filename is None or self.filename == '-':
|
if self.filename is None or self.filename == '-':
|
||||||
self.filename = 'stdin'
|
self.filename = self.options.stdin_display_name
|
||||||
return self.read_lines_from_stdin()
|
lines = self.read_lines_from_stdin()
|
||||||
return self.read_lines_from_filename()
|
else:
|
||||||
|
lines = self.read_lines_from_filename()
|
||||||
|
return lines
|
||||||
|
|
||||||
def _readlines_py2(self):
|
def _readlines_py2(self):
|
||||||
# type: () -> List[str]
|
# type: () -> List[str]
|
||||||
|
|
|
||||||
|
|
@ -260,8 +260,6 @@ class StyleGuide(object):
|
||||||
"""
|
"""
|
||||||
error = Error(code, filename, line_number, column_number, text,
|
error = Error(code, filename, line_number, column_number, text,
|
||||||
physical_line)
|
physical_line)
|
||||||
if error.filename is None or error.filename == '-':
|
|
||||||
error = error._replace(filename=self.options.stdin_display_name)
|
|
||||||
error_is_selected = (self.should_report_error(error.code) is
|
error_is_selected = (self.should_report_error(error.code) is
|
||||||
Decision.Selected)
|
Decision.Selected)
|
||||||
is_not_inline_ignored = self.is_inline_ignored(error) is False
|
is_not_inline_ignored = self.is_inline_ignored(error) is False
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,10 @@ def filenames_from(arg, predicate=None):
|
||||||
"""
|
"""
|
||||||
if predicate is None:
|
if predicate is None:
|
||||||
predicate = _default_predicate
|
predicate = _default_predicate
|
||||||
|
|
||||||
|
if predicate(arg):
|
||||||
|
return
|
||||||
|
|
||||||
if os.path.isdir(arg):
|
if os.path.isdir(arg):
|
||||||
for root, sub_directories, files in os.walk(arg):
|
for root, sub_directories, files in os.walk(arg):
|
||||||
if predicate(root):
|
if predicate(root):
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ def options_from(**kwargs):
|
||||||
kwargs.setdefault('hang_closing', True)
|
kwargs.setdefault('hang_closing', True)
|
||||||
kwargs.setdefault('max_line_length', 79)
|
kwargs.setdefault('max_line_length', 79)
|
||||||
kwargs.setdefault('verbose', False)
|
kwargs.setdefault('verbose', False)
|
||||||
|
kwargs.setdefault('stdin_display_name', 'stdin')
|
||||||
return optparse.Values(kwargs)
|
return optparse.Values(kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -63,7 +64,7 @@ def test_read_lines_from_stdin(stdin_get_value):
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('flake8.utils.stdin_get_value')
|
@mock.patch('flake8.utils.stdin_get_value')
|
||||||
def test_read_lines_sets_filename_attribute(stdin_get_value):
|
def test_stdin_filename_attribute(stdin_get_value):
|
||||||
"""Verify that we update the filename attribute."""
|
"""Verify that we update the filename attribute."""
|
||||||
stdin_value = mock.Mock()
|
stdin_value = mock.Mock()
|
||||||
stdin_value.splitlines.return_value = []
|
stdin_value.splitlines.return_value = []
|
||||||
|
|
@ -72,6 +73,18 @@ def test_read_lines_sets_filename_attribute(stdin_get_value):
|
||||||
assert file_processor.filename == 'stdin'
|
assert file_processor.filename == 'stdin'
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('flake8.utils.stdin_get_value')
|
||||||
|
def test_read_lines_uses_display_name(stdin_get_value):
|
||||||
|
"""Verify that when processing stdin we use a display name if present."""
|
||||||
|
stdin_value = mock.Mock()
|
||||||
|
stdin_value.splitlines.return_value = []
|
||||||
|
stdin_get_value.return_value = stdin_value
|
||||||
|
file_processor = processor.FileProcessor('-', options_from(
|
||||||
|
stdin_display_name='display_name.py'
|
||||||
|
))
|
||||||
|
assert file_processor.filename == 'display_name.py'
|
||||||
|
|
||||||
|
|
||||||
def test_line_for():
|
def test_line_for():
|
||||||
"""Verify we grab the correct line from the cached lines."""
|
"""Verify we grab the correct line from the cached lines."""
|
||||||
file_processor = processor.FileProcessor('-', options_from(), lines=[
|
file_processor = processor.FileProcessor('-', options_from(), lines=[
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue