diff --git a/.appveyor.yml b/.appveyor.yml index 47cc8d6..174c6f3 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,8 +1,8 @@ # To activate, change the Appveyor settings to use `.appveyor.yml`. install: - - python -m pip install --upgrade setuptools tox virtualenv + - C:\Python38-x64\python.exe -m pip install --upgrade setuptools tox virtualenv build: off test_script: - - python -m tox -e py27,py36,py37,dogfood + - C:\Python38-x64\python.exe -m tox -e py36,py37,dogfood diff --git a/.coveragerc b/.coveragerc index 7d2dd68..d1b8806 100644 --- a/.coveragerc +++ b/.coveragerc @@ -30,3 +30,4 @@ exclude_lines = # Don't complain if non-runnable code isn't run: ^if __name__ == ['"]__main__['"]:$ ^\s*if False: + ^\s*if TYPE_CHECKING: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0ee5ccf..036190a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -13,26 +13,11 @@ after_script: - pip install codecov - codecov --token=7d117e6b-aab6-4283-ab19-166dafc38cf5 -pypy2: - image: pypy:2.7-7.2.0 - stage: test - script: tox -e pypy - pypy3: image: pypy:3.6-7.2.0 stage: test script: tox -e pypy3 -python2: - image: python:2.7 - stage: test - script: tox -e py27 - -python35: - image: python:3.5 - stage: test - script: tox -e py35 - python36: image: python:3.6 stage: test diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e1b34f3..7ab32ea 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,4 @@ +exclude: ^tests/fixtures/example-code/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.3.0 @@ -7,12 +8,22 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace exclude: ^tests/fixtures/diffs/ +- repo: https://github.com/asottile/reorder_python_imports + rev: v2.4.0 + hooks: + - id: reorder-python-imports + args: [--application-directories, '.:src', --py36-plus] - repo: https://github.com/psf/black rev: 20.8b1 hooks: - id: black args: [--line-length=78] files: ^src/ +- repo: https://github.com/asottile/pyupgrade + rev: v2.11.0 + hooks: + - id: pyupgrade + args: [--py36-plus] - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.720 hooks: diff --git a/.travis.yml b/.travis.yml index 36d1a6a..5825520 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,18 +11,12 @@ notifications: matrix: include: - - python: 2.7 - env: TOXENV=py27 - - python: 3.5 - env: TOXENV=py35 - python: 3.6 env: TOXENV=py36 - python: 3.7 env: TOXENV=py37 - python: 3.8 env: TOXENV=py38 - - python: pypy - env: TOXENV=pypy - python: 3.7 env: TOXENV=readme - python: 3.7 diff --git a/docs/source/conf.py b/docs/source/conf.py index dd2003f..97860c9 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # flake8 documentation build configuration file, created by # sphinx-quickstart on Tue Jan 19 07:14:10 2016. @@ -11,9 +10,8 @@ # # All configuration values have a default; values that are commented out # serve to show the default. - -import sys import os +import sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -53,11 +51,12 @@ source_suffix = '.rst' master_doc = 'index' # General information about the project. -project = u'flake8' -copyright = u'2016, Ian Stapleton Cordasco' -author = u'Ian Stapleton Cordasco' +project = 'flake8' +copyright = '2016, Ian Stapleton Cordasco' +author = 'Ian Stapleton Cordasco' import flake8 + # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. @@ -234,8 +233,8 @@ latex_elements = { # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'flake8.tex', u'flake8 Documentation', - u'Ian Stapleton Cordasco', 'manual'), + (master_doc, 'flake8.tex', 'flake8 Documentation', + 'Ian Stapleton Cordasco', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -264,7 +263,7 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('manpage', 'flake8', u'Flake8 Command Line Documentation', + ('manpage', 'flake8', 'Flake8 Command Line Documentation', [author], 1) ] @@ -278,7 +277,7 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'Flake8', u'Flake8 Documentation', u'Tarek Ziade', + ('index', 'Flake8', 'Flake8 Documentation', 'Tarek Ziade', 'Flake8', 'Code checking using pycodestyle, pyflakes and mccabe', 'Miscellaneous'), ] diff --git a/docs/source/internal/releases.rst b/docs/source/internal/releases.rst index 0ed0ac9..03178a4 100644 --- a/docs/source/internal/releases.rst +++ b/docs/source/internal/releases.rst @@ -81,13 +81,11 @@ for users. Before releasing, the following tox test environments must pass: -- Python 2.7 (a.k.a., ``tox -e py27``) - - Python 3.6 (a.k.a., ``tox -e py36``) - Python 3.7 (a.k.a., ``tox -e py37``) -- PyPy (a.k.a., ``tox -e pypy``) +- PyPy 3 (a.k.a., ``tox -e pypy3``) - Linters (a.k.a., ``tox -e linters``) diff --git a/docs/source/internal/writing-code.rst b/docs/source/internal/writing-code.rst index d8c63fe..daf1d57 100644 --- a/docs/source/internal/writing-code.rst +++ b/docs/source/internal/writing-code.rst @@ -175,11 +175,7 @@ across multiple lines, insert a new-line after the opening parenthesis, e.g., statistic = next(stats_for_error_code) count = statistic.count count += sum(stat.count for stat in stats_for_error_code) - self._write('{count:<5} {error_code} {message}'.format( - count=count, - error_code=error_code, - message=statistic.message, - )) + self._write(f'{count:<5} {error_code} {statistic.message}') In the first example, we put a few of the parameters all on one line, and then added the last two on their own. In the second example, each parameter has its diff --git a/docs/source/plugin-development/registering-plugins.rst b/docs/source/plugin-development/registering-plugins.rst index 34b368a..d1718bf 100644 --- a/docs/source/plugin-development/registering-plugins.rst +++ b/docs/source/plugin-development/registering-plugins.rst @@ -56,7 +56,6 @@ like: "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python", - "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Quality Assurance", diff --git a/docs/source/requirements.txt b/docs/source/requirements.txt index 3a7ffae..984ca54 100644 --- a/docs/source/requirements.txt +++ b/docs/source/requirements.txt @@ -1,5 +1,4 @@ sphinx>=1.3.0,!=3.1.0 sphinx_rtd_theme sphinx-prompt -configparser flake8-polyfill diff --git a/docs/source/user/error-codes.rst b/docs/source/user/error-codes.rst index e74675f..9eab3f7 100644 --- a/docs/source/user/error-codes.rst +++ b/docs/source/user/error-codes.rst @@ -100,8 +100,6 @@ generates its own :term:`error code`\ s for ``pyflakes``: +------+---------------------------------------------------------------------+ | F811 | redefinition of unused ``name`` from line ``N`` | +------+---------------------------------------------------------------------+ -| F812 | list comprehension redefines ``name`` from line ``N`` | -+------+---------------------------------------------------------------------+ | F821 | undefined name ``name`` | +------+---------------------------------------------------------------------+ | F822 | undefined name ``name`` in ``__all__`` | @@ -116,9 +114,6 @@ generates its own :term:`error code`\ s for ``pyflakes``: | F901 | ``raise NotImplemented`` should be ``raise NotImplementedError`` | +------+---------------------------------------------------------------------+ -Note that some of these entries behave differently on Python 2 and Python 3, -for example F812 is specific to Python 2 only. - We also report one extra error: ``E999``. We report ``E999`` when we fail to compile a file into an Abstract Syntax Tree for the plugins that require it. diff --git a/docs/source/user/invocation.rst b/docs/source/user/invocation.rst index 099b72a..4c141fd 100644 --- a/docs/source/user/invocation.rst +++ b/docs/source/user/invocation.rst @@ -14,25 +14,25 @@ like so: Where you simply allow the shell running in your terminal to locate |Flake8|. In some cases, though, you may have installed |Flake8| for multiple versions -of Python (e.g., Python 2.7 and Python 3.5) and you need to call a specific +of Python (e.g., Python 3.8 and Python 3.9) and you need to call a specific version. In that case, you will have much better results using: .. prompt:: bash - python2.7 -m flake8 + python3.8 -m flake8 Or .. prompt:: bash - python3.5 -m flake8 + python3.9 -m flake8 Since that will tell the correct version of Python to run |Flake8|. .. note:: - Installing |Flake8| once will not install it on both Python 2.7 and - Python 3.5. It will only install it for the version of Python that + Installing |Flake8| once will not install it on both Python 3.8 and + Python 3.9. It will only install it for the version of Python that is running pip. It is also possible to specify command-line options directly to |Flake8|: diff --git a/docs/source/user/using-plugins.rst b/docs/source/user/using-plugins.rst index 3971578..eddb258 100644 --- a/docs/source/user/using-plugins.rst +++ b/docs/source/user/using-plugins.rst @@ -24,10 +24,8 @@ appropriate of: pip install pip3 install python -m pip install - python2.7 -m pip install python3 -m pip install - python3.4 -m pip install - python3.5 -m pip install + python3.9 -m pip install To install the plugin, where ```` is the package name on PyPI_. To verify installation use: diff --git a/example-plugin/setup.py b/example-plugin/setup.py index 2da68f6..0facd57 100644 --- a/example-plugin/setup.py +++ b/example-plugin/setup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import setuptools setuptools.setup( @@ -21,11 +20,9 @@ setuptools.setup( 'Framework :: Flake8', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Quality Assurance', ], diff --git a/example-plugin/src/flake8_example_plugin/__init__.py b/example-plugin/src/flake8_example_plugin/__init__.py index 33d76f3..420ce73 100644 --- a/example-plugin/src/flake8_example_plugin/__init__.py +++ b/example-plugin/src/flake8_example_plugin/__init__.py @@ -1,7 +1,6 @@ """Module for an example Flake8 plugin.""" - -from .on_by_default import ExampleOne from .off_by_default import ExampleTwo +from .on_by_default import ExampleOne __all__ = ( 'ExampleOne', diff --git a/example-plugin/src/flake8_example_plugin/off_by_default.py b/example-plugin/src/flake8_example_plugin/off_by_default.py index d768328..50afa81 100644 --- a/example-plugin/src/flake8_example_plugin/off_by_default.py +++ b/example-plugin/src/flake8_example_plugin/off_by_default.py @@ -1,7 +1,7 @@ """Our first example plugin.""" -class ExampleTwo(object): +class ExampleTwo: """Second Example Plugin.""" name = 'off-by-default-example-plugin' version = '1.0.0' diff --git a/example-plugin/src/flake8_example_plugin/on_by_default.py b/example-plugin/src/flake8_example_plugin/on_by_default.py index a324297..c748822 100644 --- a/example-plugin/src/flake8_example_plugin/on_by_default.py +++ b/example-plugin/src/flake8_example_plugin/on_by_default.py @@ -1,7 +1,7 @@ """Our first example plugin.""" -class ExampleOne(object): +class ExampleOne: """First Example Plugin.""" name = 'on-by-default-example-plugin' version = '1.0.0' @@ -11,5 +11,4 @@ class ExampleOne(object): def run(self): """Do nothing.""" - for message in []: - yield message + yield from [] diff --git a/pytest.ini b/pytest.ini index 4a00d9a..a72e0e1 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,10 +1,4 @@ [pytest] norecursedirs = .git .* *.egg* old docs dist build addopts = -rw -filterwarnings = - error - # python3.4 raises this when importing setuptools - ignore:The value of convert_charrefs will become True in 3.5.*:DeprecationWarning - # python3 raises this when importing setuptools - ignore:the imp module is deprecated in favour of importlib.*:PendingDeprecationWarning - ignore:the imp module is deprecated in favour of importlib.*:DeprecationWarning +filterwarnings = error diff --git a/setup.cfg b/setup.cfg index 9103522..5ad0bb6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,10 +20,7 @@ classifiers = Intended Audience :: Developers License :: OSI Approved :: MIT License Programming Language :: Python - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 @@ -44,13 +41,9 @@ install_requires= pyflakes >= 2.3.0, < 2.4.0 pycodestyle >= 2.7.0, < 2.8.0 mccabe >= 0.6.0, < 0.7.0 - enum34; python_version<"3.4" - typing; python_version<"3.5" - configparser; python_version<"3.2" - functools32; python_version<"3.2" importlib-metadata; python_version<"3.8" -python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* +python_requires = >=3.6 [options.packages.find] where = src diff --git a/setup.py b/setup.py index a64bdf2..e718512 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Packaging logic for Flake8.""" import os import sys diff --git a/src/flake8/__init__.py b/src/flake8/__init__.py index ea2f541..7b39e56 100644 --- a/src/flake8/__init__.py +++ b/src/flake8/__init__.py @@ -11,9 +11,7 @@ This module """ import logging import sys - -if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2 - from typing import Type # `typing.Type` was introduced in 3.5.2 +from typing import Type LOG = logging.getLogger(__name__) LOG.addHandler(logging.NullHandler()) @@ -64,7 +62,7 @@ def configure_logging(verbosity, filename=None, logformat=LOG_FORMAT): if not filename or filename in ("stderr", "stdout"): fileobj = getattr(sys, filename or "stderr") - handler_cls = logging.StreamHandler # type: Type[logging.Handler] + handler_cls: Type[logging.Handler] = logging.StreamHandler else: fileobj = filename handler_cls = logging.FileHandler diff --git a/src/flake8/_compat.py b/src/flake8/_compat.py index 85af0a3..18809e2 100644 --- a/src/flake8/_compat.py +++ b/src/flake8/_compat.py @@ -1,14 +1,9 @@ """Expose backports in a single place.""" import sys -if sys.version_info >= (3,): # pragma: no cover (PY3+) - from functools import lru_cache -else: # pragma: no cover (= (3, 8): # pragma: no cover (PY38+) import importlib.metadata as importlib_metadata else: # pragma: no cover ( argparse.Namespace + def options(self) -> argparse.Namespace: """Return application's options. An instance of :class:`argparse.Namespace` containing parsed options. @@ -170,7 +170,7 @@ class StyleGuide(object): return self.check_files([filename]) -class Report(object): +class Report: """Public facing object that mimic's Flake8 2.0's API. .. note:: @@ -210,6 +210,6 @@ class Report(object): list """ return [ - "{} {} {}".format(s.count, s.error_code, s.message) + f"{s.count} {s.error_code} {s.message}" for s in self._stats.statistics_for(violation) ] diff --git a/src/flake8/checker.py b/src/flake8/checker.py index 224bf64..421d461 100644 --- a/src/flake8/checker.py +++ b/src/flake8/checker.py @@ -4,20 +4,22 @@ import errno import itertools import logging import signal -import sys import tokenize -from typing import Dict, List, Optional, Tuple - -try: - import multiprocessing.pool -except ImportError: - multiprocessing = None # type: ignore +from typing import Dict +from typing import List +from typing import Optional +from typing import Tuple from flake8 import defaults from flake8 import exceptions from flake8 import processor from flake8 import utils +try: + import multiprocessing.pool +except ImportError: + multiprocessing = None # type: ignore + LOG = logging.getLogger(__name__) SERIAL_RETRY_ERRNOS = { @@ -38,17 +40,14 @@ SERIAL_RETRY_ERRNOS = { def _multiprocessing_is_fork(): # type () -> bool """Class state is only preserved when using the `fork` strategy.""" - if sys.version_info >= (3, 4): - return ( - multiprocessing - # https://github.com/python/typeshed/pull/3415 - and multiprocessing.get_start_method() == "fork" # type: ignore - ) - else: - return multiprocessing and not utils.is_windows() + return ( + multiprocessing + # https://github.com/python/typeshed/pull/3415 + and multiprocessing.get_start_method() == "fork" # type: ignore + ) -class Manager(object): +class Manager: """Manage the parallelism and checker instances for each plugin and file. This class will be responsible for the following: @@ -86,8 +85,8 @@ class Manager(object): self.options = style_guide.options self.checks = checker_plugins self.jobs = self._job_count() - self._all_checkers = [] # type: List[FileChecker] - self.checkers = [] # type: List[FileChecker] + self._all_checkers: List[FileChecker] = [] + self.checkers: List[FileChecker] = [] self.statistics = { "files": 0, "logical lines": 0, @@ -104,8 +103,7 @@ class Manager(object): self.statistics[statistic] += checker.statistics[statistic] self.statistics["files"] += len(self.checkers) - def _job_count(self): - # type: () -> int + def _job_count(self) -> int: # First we walk through all of our error cases: # - multiprocessing library is not present # - we're running on windows in which case we know we have significant @@ -166,8 +164,7 @@ class Manager(object): ) return reported_results_count - def is_path_excluded(self, path): - # type: (str) -> bool + def is_path_excluded(self, path: str) -> bool: """Check if a path is excluded. :param str path: @@ -190,8 +187,7 @@ class Manager(object): logger=LOG, ) - def make_checkers(self, paths=None): - # type: (Optional[List[str]]) -> None + def make_checkers(self, paths: Optional[List[str]] = None) -> None: """Create checkers for each file.""" if paths is None: paths = self.arguments @@ -236,8 +232,7 @@ class Manager(object): self.checkers = [c for c in self._all_checkers if c.should_process] LOG.info("Checking %d files", len(self.checkers)) - def report(self): - # type: () -> Tuple[int, int] + def report(self) -> Tuple[int, int]: """Report all of the errors found in the managed file checkers. This iterates over each of the checkers and reports the errors sorted @@ -259,11 +254,11 @@ class Manager(object): results_found += len(results) return (results_found, results_reported) - def run_parallel(self): # type: () -> None + def run_parallel(self) -> None: """Run the checkers in parallel.""" # fmt: off - final_results = collections.defaultdict(list) # type: Dict[str, List[Tuple[str, int, int, str, Optional[str]]]] # noqa: E501 - final_statistics = collections.defaultdict(dict) # type: Dict[str, Dict[str, int]] # noqa: E501 + final_results: Dict[str, List[Tuple[str, int, int, str, Optional[str]]]] = collections.defaultdict(list) # noqa: E501 + final_statistics: Dict[str, Dict[str, int]] = collections.defaultdict(dict) # noqa: E501 # fmt: on pool = _try_initialize_processpool(self.jobs) @@ -298,12 +293,12 @@ class Manager(object): checker.results = final_results[filename] checker.statistics = final_statistics[filename] - def run_serial(self): # type: () -> None + def run_serial(self) -> None: """Run the checkers in serial.""" for checker in self.checkers: checker.run_checks() - def run(self): # type: () -> None + def run(self) -> None: """Run all the checkers. This will intelligently decide whether to run the checks in parallel @@ -337,7 +332,7 @@ class Manager(object): self._process_statistics() -class FileChecker(object): +class FileChecker: """Manage running checks for a file and aggregate the results.""" def __init__(self, filename, checks, options): @@ -357,9 +352,7 @@ class FileChecker(object): self.options = options self.filename = filename self.checks = checks - # fmt: off - self.results = [] # type: List[Tuple[str, int, int, str, Optional[str]]] # noqa: E501 - # fmt: on + self.results: List[Tuple[str, int, int, str, Optional[str]]] = [] self.statistics = { "tokens": 0, "logical lines": 0, @@ -373,27 +366,30 @@ class FileChecker(object): self.should_process = not self.processor.should_ignore_file() self.statistics["physical lines"] = len(self.processor.lines) - def __repr__(self): # type: () -> str + def __repr__(self) -> str: """Provide helpful debugging representation.""" - return "FileChecker for {}".format(self.filename) + return f"FileChecker for {self.filename}" - def _make_processor(self): - # type: () -> Optional[processor.FileProcessor] + def _make_processor(self) -> Optional[processor.FileProcessor]: try: return processor.FileProcessor(self.filename, self.options) - except IOError as e: + except OSError as e: # 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) # then we need to format that exception for the user. # NOTE(sigmavirus24): Historically, pep8 has always reported this # as an E902. We probably *want* a better error code for this # going forward. - message = "{0}: {1}".format(type(e).__name__, e) - self.report("E902", 0, 0, message) + self.report("E902", 0, 0, f"{type(e).__name__}: {e}") return None - def report(self, error_code, line_number, column, text): - # type: (Optional[str], int, int, str) -> str + def report( + self, + error_code: Optional[str], + line_number: int, + column: int, + text: str, + ) -> str: """Report an error by storing it in the results list.""" if error_code is None: error_code, text = text.split(" ", 1) @@ -469,14 +465,14 @@ class FileChecker(object): column -= column_offset return row, column - def run_ast_checks(self): # type: () -> None + def run_ast_checks(self) -> None: """Run all checks expecting an abstract syntax tree.""" try: ast = self.processor.build_ast() except (ValueError, SyntaxError, TypeError) as e: row, column = self._extract_syntax_information(e) self.report( - "E999", row, column, "%s: %s" % (type(e).__name__, e.args[0]) + "E999", row, column, f"{type(e).__name__}: {e.args[0]}" ) return @@ -608,8 +604,9 @@ class FileChecker(object): else: self.run_logical_checks() - def check_physical_eol(self, token, prev_physical): - # type: (processor._Token, str) -> None + def check_physical_eol( + self, token: processor._Token, prev_physical: str + ) -> None: """Run physical checks if and only if it is at the end of the line.""" # a newline token ends a single physical line. if processor.is_eol_token(token): @@ -638,13 +635,14 @@ class FileChecker(object): self.run_physical_checks(line + "\n") -def _pool_init(): # type: () -> None +def _pool_init() -> None: """Ensure correct signaling of ^C using multiprocessing.Pool.""" signal.signal(signal.SIGINT, signal.SIG_IGN) -def _try_initialize_processpool(job_count): - # type: (int) -> Optional[multiprocessing.pool.Pool] +def _try_initialize_processpool( + job_count: int, +) -> Optional[multiprocessing.pool.Pool]: """Return a new process pool instance if we are able to create one.""" try: return multiprocessing.Pool(job_count, _pool_init) @@ -673,8 +671,9 @@ def _run_checks(checker): return checker.run_checks() -def find_offset(offset, mapping): - # type: (int, processor._LogicalMapping) -> Tuple[int, int] +def find_offset( + offset: int, mapping: processor._LogicalMapping +) -> Tuple[int, int]: """Find the offset tuple for a single offset.""" if isinstance(offset, tuple): return offset diff --git a/src/flake8/exceptions.py b/src/flake8/exceptions.py index ea9bc98..a8d2f6e 100644 --- a/src/flake8/exceptions.py +++ b/src/flake8/exceptions.py @@ -19,14 +19,13 @@ class FailedToLoadPlugin(Flake8Exception): FORMAT = 'Flake8 failed to load plugin "%(name)s" due to %(exc)s.' - def __init__(self, plugin_name, exception): - # type: (str, Exception) -> None + def __init__(self, plugin_name: str, exception: Exception) -> None: """Initialize our FailedToLoadPlugin exception.""" self.plugin_name = plugin_name self.original_exception = exception - super(FailedToLoadPlugin, self).__init__(plugin_name, exception) + super().__init__(plugin_name, exception) - def __str__(self): # type: () -> str + def __str__(self) -> str: """Format our exception message.""" return self.FORMAT % { "name": self.plugin_name, @@ -37,18 +36,18 @@ class FailedToLoadPlugin(Flake8Exception): class InvalidSyntax(Flake8Exception): """Exception raised when tokenizing a file fails.""" - def __init__(self, exception): # type: (Exception) -> None + def __init__(self, exception: Exception) -> None: """Initialize our InvalidSyntax exception.""" self.original_exception = exception - self.error_message = "{0}: {1}".format( - exception.__class__.__name__, exception.args[0] + self.error_message = ( + f"{type(exception).__name__}: {exception.args[0]}" ) self.error_code = "E902" self.line_number = 1 self.column_number = 0 - super(InvalidSyntax, self).__init__(exception) + super().__init__(exception) - def __str__(self): # type: () -> str + def __str__(self) -> str: """Format our exception message.""" return self.error_message @@ -58,16 +57,13 @@ class PluginRequestedUnknownParameters(Flake8Exception): FORMAT = '"%(name)s" requested unknown parameters causing %(exc)s' - def __init__(self, plugin, exception): - # type: (Dict[str, str], Exception) -> None + def __init__(self, plugin: Dict[str, str], exception: Exception) -> None: """Pop certain keyword arguments for initialization.""" self.plugin = plugin self.original_exception = exception - super(PluginRequestedUnknownParameters, self).__init__( - plugin, exception - ) + super().__init__(plugin, exception) - def __str__(self): # type: () -> str + def __str__(self) -> str: """Format our exception message.""" return self.FORMAT % { "name": self.plugin["plugin_name"], @@ -80,14 +76,13 @@ class PluginExecutionFailed(Flake8Exception): FORMAT = '"%(name)s" failed during execution due to "%(exc)s"' - def __init__(self, plugin, exception): - # type: (Dict[str, str], Exception) -> None + def __init__(self, plugin: Dict[str, str], exception: Exception) -> None: """Utilize keyword arguments for message generation.""" self.plugin = plugin self.original_exception = exception - super(PluginExecutionFailed, self).__init__(plugin, exception) + super().__init__(plugin, exception) - def __str__(self): # type: () -> str + def __str__(self) -> str: """Format our exception message.""" return self.FORMAT % { "name": self.plugin["plugin_name"], diff --git a/src/flake8/formatting/base.py b/src/flake8/formatting/base.py index ae78f49..e362e65 100644 --- a/src/flake8/formatting/base.py +++ b/src/flake8/formatting/base.py @@ -1,15 +1,17 @@ """The base class and interface for all formatting plugins.""" -from __future__ import print_function - import argparse -from typing import IO, List, Optional, Tuple +from typing import IO +from typing import List +from typing import Optional +from typing import Tuple +from typing import TYPE_CHECKING -if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2 +if TYPE_CHECKING: from flake8.statistics import Statistics from flake8.style_guide import Violation -class BaseFormatter(object): +class BaseFormatter: """Class defining the formatter interface. .. attribute:: options @@ -31,8 +33,7 @@ class BaseFormatter(object): output filename has been specified. """ - def __init__(self, options): - # type: (argparse.Namespace) -> None + def __init__(self, options: argparse.Namespace) -> None: """Initialize with the options parsed from config and cli. This also calls a hook, :meth:`after_init`, so subclasses do not need @@ -46,14 +47,14 @@ class BaseFormatter(object): """ self.options = options self.filename = options.output_file - self.output_fd = None # type: Optional[IO[str]] + self.output_fd: Optional[IO[str]] = None self.newline = "\n" self.after_init() - def after_init(self): # type: () -> None + def after_init(self) -> None: """Initialize the formatter further.""" - def beginning(self, filename): # type: (str) -> None + def beginning(self, filename: str) -> None: """Notify the formatter that we're starting to process a file. :param str filename: @@ -61,7 +62,7 @@ class BaseFormatter(object): from. """ - def finished(self, filename): # type: (str) -> None + def finished(self, filename: str) -> None: """Notify the formatter that we've finished processing a file. :param str filename: @@ -69,7 +70,7 @@ class BaseFormatter(object): from. """ - def start(self): # type: () -> None + def start(self) -> None: """Prepare the formatter to receive input. This defaults to initializing :attr:`output_fd` if :attr:`filename` @@ -77,7 +78,7 @@ class BaseFormatter(object): if self.filename: self.output_fd = open(self.filename, "a") - def handle(self, error): # type: (Violation) -> None + def handle(self, error: "Violation") -> None: """Handle an error reported by Flake8. This defaults to calling :meth:`format`, :meth:`show_source`, and @@ -94,7 +95,7 @@ class BaseFormatter(object): source = self.show_source(error) self.write(line, source) - def format(self, error): # type: (Violation) -> Optional[str] + def format(self, error: "Violation") -> Optional[str]: """Format an error reported by Flake8. This method **must** be implemented by subclasses. @@ -113,23 +114,16 @@ class BaseFormatter(object): "Subclass of BaseFormatter did not implement" " format." ) - def show_statistics(self, statistics): # type: (Statistics) -> None + def show_statistics(self, statistics: "Statistics") -> None: """Format and print the statistics.""" for error_code in statistics.error_codes(): stats_for_error_code = statistics.statistics_for(error_code) statistic = next(stats_for_error_code) count = statistic.count count += sum(stat.count for stat in stats_for_error_code) - self._write( - "{count:<5} {error_code} {message}".format( - count=count, - error_code=error_code, - message=statistic.message, - ) - ) + self._write(f"{count:<5} {error_code} {statistic.message}") - def show_benchmarks(self, benchmarks): - # type: (List[Tuple[str, float]]) -> None + def show_benchmarks(self, benchmarks: List[Tuple[str, float]]) -> None: """Format and print the benchmarks.""" # NOTE(sigmavirus24): The format strings are a little confusing, even # to me, so here's a quick explanation: @@ -150,7 +144,7 @@ class BaseFormatter(object): benchmark = float_format(statistic=statistic, value=value) self._write(benchmark) - def show_source(self, error): # type: (Violation) -> Optional[str] + def show_source(self, error: "Violation") -> Optional[str]: """Show the physical line generating the error. This also adds an indicator for the particular part of the line that @@ -179,17 +173,16 @@ class BaseFormatter(object): ) # Physical lines have a newline at the end, no need to add an extra # one - return "{}{}^".format(error.physical_line, indent) + return f"{error.physical_line}{indent}^" - def _write(self, output): # type: (str) -> None + def _write(self, output: str) -> None: """Handle logic of whether to use an output file or print().""" if self.output_fd is not None: self.output_fd.write(output + self.newline) if self.output_fd is None or self.options.tee: print(output, end=self.newline) - def write(self, line, source): - # type: (Optional[str], Optional[str]) -> None + def write(self, line: Optional[str], source: Optional[str]) -> None: """Write the line either to the output file or stdout. This handles deciding whether to write to a file or print to standard @@ -207,7 +200,7 @@ class BaseFormatter(object): if source: self._write(source) - def stop(self): # type: () -> None + def stop(self) -> None: """Clean up after reporting is finished.""" if self.output_fd is not None: self.output_fd.close() diff --git a/src/flake8/formatting/default.py b/src/flake8/formatting/default.py index 55a5d01..0a8e09d 100644 --- a/src/flake8/formatting/default.py +++ b/src/flake8/formatting/default.py @@ -1,9 +1,11 @@ """Default formatting class for Flake8.""" -from typing import Optional, Set +from typing import Optional +from typing import Set +from typing import TYPE_CHECKING from flake8.formatting import base -if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2 +if TYPE_CHECKING: from flake8.style_guide import Violation @@ -23,9 +25,9 @@ class SimpleFormatter(base.BaseFormatter): """ - error_format = None # type: str + error_format: str - def format(self, error): # type: (Violation) -> Optional[str] + def format(self, error: "Violation") -> Optional[str]: """Format and write error out. If an output filename is specified, write formatted errors to that @@ -49,7 +51,7 @@ class Default(SimpleFormatter): error_format = "%(path)s:%(row)d:%(col)d: %(code)s %(text)s" - def after_init(self): # type: () -> None + def after_init(self) -> None: """Check for a custom format string.""" if self.options.format.lower() != "default": self.error_format = self.options.format @@ -66,18 +68,18 @@ class FilenameOnly(SimpleFormatter): error_format = "%(path)s" - def after_init(self): # type: () -> None + def after_init(self) -> None: """Initialize our set of filenames.""" - self.filenames_already_printed = set() # type: Set[str] + self.filenames_already_printed: Set[str] = set() - def show_source(self, error): # type: (Violation) -> Optional[str] + def show_source(self, error: "Violation") -> Optional[str]: """Do not include the source code.""" - def format(self, error): # type: (Violation) -> Optional[str] + def format(self, error: "Violation") -> Optional[str]: """Ensure we only print each error once.""" if error.filename not in self.filenames_already_printed: self.filenames_already_printed.add(error.filename) - return super(FilenameOnly, self).format(error) + return super().format(error) else: return None @@ -85,8 +87,8 @@ class FilenameOnly(SimpleFormatter): class Nothing(base.BaseFormatter): """Print absolutely nothing.""" - def format(self, error): # type: (Violation) -> Optional[str] + def format(self, error: "Violation") -> Optional[str]: """Do nothing.""" - def show_source(self, error): # type: (Violation) -> Optional[str] + def show_source(self, error: "Violation") -> Optional[str]: """Do not print the source.""" diff --git a/src/flake8/main/application.py b/src/flake8/main/application.py index a247062..6e99ec2 100644 --- a/src/flake8/main/application.py +++ b/src/flake8/main/application.py @@ -1,11 +1,15 @@ """Module containing the application logic for Flake8.""" -from __future__ import print_function - import argparse import logging import sys import time -from typing import Dict, List, Optional, Set, Tuple +from typing import Dict +from typing import List +from typing import Optional +from typing import Set +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING import flake8 from flake8 import checker @@ -14,19 +18,19 @@ from flake8 import exceptions from flake8 import style_guide from flake8 import utils from flake8.main import options -from flake8.options import aggregator, config +from flake8.options import aggregator +from flake8.options import config from flake8.options import manager from flake8.plugins import manager as plugin_manager -if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2 - from typing import Type # `typing.Type` was introduced in 3.5.2 +if TYPE_CHECKING: from flake8.formatting.base import BaseFormatter LOG = logging.getLogger(__name__) -class Application(object): +class Application: """Abstract our application into a class.""" def __init__(self, program="flake8", version=flake8.__version__): @@ -40,7 +44,7 @@ class Application(object): #: The timestamp when the Application instance was instantiated. self.start_time = time.time() #: The timestamp when the Application finished reported errors. - self.end_time = None # type: float + self.end_time: float = None #: The name of the program being run self.program = program #: The version of the program being run @@ -59,26 +63,24 @@ class Application(object): options.register_default_options(self.option_manager) #: The instance of :class:`flake8.plugins.manager.Checkers` - self.check_plugins = None # type: plugin_manager.Checkers - # fmt: off + self.check_plugins: plugin_manager.Checkers = None #: The instance of :class:`flake8.plugins.manager.ReportFormatters` - self.formatting_plugins = None # type: plugin_manager.ReportFormatters - # fmt: on + self.formatting_plugins: plugin_manager.ReportFormatters = None #: The user-selected formatter from :attr:`formatting_plugins` - self.formatter = None # type: BaseFormatter + self.formatter: BaseFormatter = None #: The :class:`flake8.style_guide.StyleGuideManager` built from the #: user's options - self.guide = None # type: style_guide.StyleGuideManager + self.guide: style_guide.StyleGuideManager = None #: The :class:`flake8.checker.Manager` that will handle running all of #: the checks selected by the user. - self.file_checker_manager = None # type: checker.Manager + self.file_checker_manager: checker.Manager = None #: The user-supplied options parsed into an instance of #: :class:`argparse.Namespace` - self.options = None # type: argparse.Namespace + self.options: argparse.Namespace = None #: The left over arguments that were not parsed by #: :attr:`option_manager` - self.args = None # type: List[str] + self.args: List[str] = None #: The number of errors, warnings, and other messages after running #: flake8 and taking into account ignored errors and lines. self.result_count = 0 @@ -92,10 +94,11 @@ class Application(object): #: Whether the program is processing a diff or not self.running_against_diff = False #: The parsed diff information - self.parsed_diff = {} # type: Dict[str, Set[int]] + self.parsed_diff: Dict[str, Set[int]] = {} - def parse_preliminary_options(self, argv): - # type: (List[str]) -> Tuple[argparse.Namespace, List[str]] + def parse_preliminary_options( + self, argv: List[str] + ) -> Tuple[argparse.Namespace, List[str]]: """Get preliminary options from the CLI, pre-plugin-loading. We need to know the values of a few standard options so that we can @@ -119,8 +122,7 @@ class Application(object): rest.extend(("--output-file", args.output_file)) return args, rest - def exit(self): - # type: () -> None + def exit(self) -> None: """Handle finalization and exiting the program. This should be the last thing called on the application instance. It @@ -136,8 +138,7 @@ class Application(object): (self.result_count > 0) or self.catastrophic_failure ) - def find_plugins(self, config_finder): - # type: (config.ConfigFileFinder) -> None + def find_plugins(self, config_finder: config.ConfigFileFinder) -> None: """Find and load the plugins for this application. Set the :attr:`check_plugins` and :attr:`formatting_plugins` attributes @@ -159,8 +160,7 @@ class Application(object): self.check_plugins.load_plugins() self.formatting_plugins.load_plugins() - def register_plugin_options(self): - # type: () -> None + def register_plugin_options(self) -> None: """Register options provided by plugins to our option manager.""" self.check_plugins.register_options(self.option_manager) self.check_plugins.register_plugin_versions(self.option_manager) @@ -168,10 +168,9 @@ class Application(object): def parse_configuration_and_cli( self, - config_finder, # type: config.ConfigFileFinder - argv, # type: List[str] - ): - # type: (...) -> None + config_finder: config.ConfigFileFinder, + argv: List[str], + ) -> None: """Parse configuration files and the CLI options. :param config.ConfigFileFinder config_finder: @@ -211,8 +210,9 @@ class Application(object): return formatter_plugin.execute - def make_formatter(self, formatter_class=None): - # type: (Optional[Type[BaseFormatter]]) -> None + def make_formatter( + self, formatter_class: Optional[Type["BaseFormatter"]] = None + ) -> None: """Initialize a formatter based on the parsed options.""" format_plugin = self.options.format if 1 <= self.options.quiet < 2: @@ -225,8 +225,7 @@ class Application(object): self.formatter = formatter_class(self.options) - def make_guide(self): - # type: () -> None + def make_guide(self) -> None: """Initialize our StyleGuide.""" self.guide = style_guide.StyleGuideManager( self.options, self.formatter @@ -235,8 +234,7 @@ class Application(object): if self.running_against_diff: self.guide.add_diff_ranges(self.parsed_diff) - def make_file_checker_manager(self): - # type: () -> None + def make_file_checker_manager(self) -> None: """Initialize our FileChecker Manager.""" self.file_checker_manager = checker.Manager( style_guide=self.guide, @@ -244,8 +242,7 @@ class Application(object): checker_plugins=self.check_plugins, ) - def run_checks(self, files=None): - # type: (Optional[List[str]]) -> None + def run_checks(self, files: Optional[List[str]] = None) -> None: """Run the actual checks with the FileChecker Manager. This method encapsulates the logic to make a @@ -278,15 +275,14 @@ class Application(object): add_statistic = statistics.append for statistic in defaults.STATISTIC_NAMES + ("files",): value = self.file_checker_manager.statistics[statistic] - total_description = "total " + statistic + " processed" + total_description = f"total {statistic} processed" add_statistic((total_description, value)) - per_second_description = statistic + " processed per second" + per_second_description = f"{statistic} processed per second" add_statistic((per_second_description, int(value / time_elapsed))) self.formatter.show_benchmarks(statistics) - def report_errors(self): - # type: () -> None + def report_errors(self) -> None: """Report all the errors found by flake8 3.0. This also updates the :attr:`result_count` attribute with the total @@ -308,8 +304,7 @@ class Application(object): self.formatter.show_statistics(self.guide.stats) - def initialize(self, argv): - # type: (List[str]) -> None + def initialize(self, argv: List[str]) -> None: """Initialize the application to be run. This finds the plugins, registers their options, and parses the @@ -343,14 +338,12 @@ class Application(object): self.report_benchmarks() self.formatter.stop() - def _run(self, argv): - # type: (List[str]) -> None + def _run(self, argv: List[str]) -> None: self.initialize(argv) self.run_checks() self.report() - def run(self, argv): - # type: (List[str]) -> None + def run(self, argv: List[str]) -> None: """Run our application. This method will also handle KeyboardInterrupt exceptions for the diff --git a/src/flake8/main/cli.py b/src/flake8/main/cli.py index 1bc9155..ddbc7c0 100644 --- a/src/flake8/main/cli.py +++ b/src/flake8/main/cli.py @@ -1,12 +1,12 @@ """Command-line implementation of flake8.""" import sys -from typing import List, Optional +from typing import List +from typing import Optional from flake8.main import application -def main(argv=None): - # type: (Optional[List[str]]) -> None +def main(argv: Optional[List[str]] = None) -> None: """Execute the main bit of the application. This handles the creation of an instance of :class:`Application`, runs it, diff --git a/src/flake8/main/debug.py b/src/flake8/main/debug.py index 83a5a11..9f087c9 100644 --- a/src/flake8/main/debug.py +++ b/src/flake8/main/debug.py @@ -1,10 +1,9 @@ """Module containing the logic for our debugging logic.""" -from __future__ import print_function - import argparse import json import platform -from typing import Dict, List +from typing import Dict +from typing import List class DebugAction(argparse.Action): @@ -17,7 +16,7 @@ class DebugAction(argparse.Action): used to delay response. """ self._option_manager = kwargs.pop("option_manager") - super(DebugAction, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def __call__(self, parser, namespace, values, option_string=None): """Perform the argparse action for printing debug information.""" @@ -60,6 +59,6 @@ def plugins_from(option_manager): ] -def dependencies(): # type: () -> List[Dict[str, str]] +def dependencies() -> List[Dict[str, str]]: """Generate the list of dependencies we care about.""" return [] diff --git a/src/flake8/main/options.py b/src/flake8/main/options.py index fdde9e1..3dfb482 100644 --- a/src/flake8/main/options.py +++ b/src/flake8/main/options.py @@ -6,8 +6,7 @@ from flake8 import defaults from flake8.main import debug -def register_preliminary_options(parser): - # type: (argparse.ArgumentParser) -> None +def register_preliminary_options(parser: argparse.ArgumentParser) -> None: """Register the preliminary options on our OptionManager. The preliminary options include: @@ -64,7 +63,7 @@ def register_preliminary_options(parser): class JobsArgument: """Type callback for the --jobs argument.""" - def __init__(self, arg): # type: (str) -> None + def __init__(self, arg: str) -> None: """Parse and validate the --jobs argument. :param str arg: @@ -78,7 +77,7 @@ class JobsArgument: self.n_jobs = int(arg) else: raise argparse.ArgumentTypeError( - "{!r} must be 'auto' or an integer.".format(arg), + f"{arg!r} must be 'auto' or an integer.", ) def __str__(self): diff --git a/src/flake8/options/aggregator.py b/src/flake8/options/aggregator.py index bdcd6a3..ac9aec5 100644 --- a/src/flake8/options/aggregator.py +++ b/src/flake8/options/aggregator.py @@ -5,7 +5,8 @@ applies the user-specified command-line configuration on top of it. """ import argparse import logging -from typing import List, Tuple +from typing import List +from typing import Tuple from flake8.options import config from flake8.options.manager import OptionManager @@ -14,10 +15,10 @@ LOG = logging.getLogger(__name__) def aggregate_options( - manager, # type: OptionManager - config_finder, # type: config.ConfigFileFinder - argv, # type: List[str] -): # type: (...) -> Tuple[argparse.Namespace, List[str]] + manager: OptionManager, + config_finder: config.ConfigFileFinder, + argv: List[str], +) -> Tuple[argparse.Namespace, List[str]]: """Aggregate and merge CLI and config file options. :param flake8.options.manager.OptionManager manager: diff --git a/src/flake8/options/config.py b/src/flake8/options/config.py index 3dbaae7..0a2ee63 100644 --- a/src/flake8/options/config.py +++ b/src/flake8/options/config.py @@ -3,7 +3,9 @@ import collections import configparser import logging import os.path -from typing import List, Optional, Tuple +from typing import List +from typing import Optional +from typing import Tuple from flake8 import utils @@ -12,17 +14,16 @@ LOG = logging.getLogger(__name__) __all__ = ("ConfigFileFinder", "MergedConfigParser") -class ConfigFileFinder(object): +class ConfigFileFinder: """Encapsulate the logic for finding and reading config files.""" def __init__( self, - program_name, - extra_config_files=None, - config_file=None, - ignore_config_files=False, - ): - # type: (str, Optional[List[str]], Optional[str], bool) -> None + program_name: str, + extra_config_files: Optional[List[str]] = None, + config_file: Optional[str] = None, + ignore_config_files: bool = False, + ) -> None: """Initialize object to find config files. :param str program_name: @@ -50,16 +51,15 @@ class ConfigFileFinder(object): self.user_config_file = self._user_config_file(program_name) # List of filenames to find in the local/project directory - self.project_filenames = ("setup.cfg", "tox.ini", "." + program_name) + self.project_filenames = ("setup.cfg", "tox.ini", f".{program_name}") self.local_directory = os.path.abspath(os.curdir) @staticmethod - def _user_config_file(program_name): - # type: (str) -> str + def _user_config_file(program_name: str) -> str: if utils.is_windows(): home_dir = os.path.expanduser("~") - config_file_basename = "." + program_name + config_file_basename = f".{program_name}" else: home_dir = os.environ.get( "XDG_CONFIG_HOME", os.path.expanduser("~/.config") @@ -69,8 +69,9 @@ class ConfigFileFinder(object): return os.path.join(home_dir, config_file_basename) @staticmethod - def _read_config(*files): - # type: (*str) -> Tuple[configparser.RawConfigParser, List[str]] + def _read_config( + *files: str, + ) -> Tuple[configparser.RawConfigParser, List[str]]: config = configparser.RawConfigParser() found_files = [] @@ -91,8 +92,7 @@ class ConfigFileFinder(object): ) return (config, found_files) - def cli_config(self, files): - # type: (str) -> configparser.RawConfigParser + def cli_config(self, files: str) -> configparser.RawConfigParser: """Read and parse the config file specified on the command-line.""" config, found_files = self._read_config(files) if found_files: @@ -154,7 +154,7 @@ class ConfigFileFinder(object): return config -class MergedConfigParser(object): +class MergedConfigParser: """Encapsulate merging different types of configuration files. This parses out the options registered that were specified in the @@ -344,7 +344,7 @@ def get_local_plugins(config_finder): base_dirs = {os.path.dirname(cf) for cf in config_files} - section = "%s:local-plugins" % config_finder.program_name + section = f"{config_finder.program_name}:local-plugins" for plugin_type in ["extension", "report"]: if config.has_option(section, plugin_type): local_plugins_string = config.get(section, plugin_type).strip() @@ -358,7 +358,7 @@ def get_local_plugins(config_finder): raw_paths = utils.parse_comma_separated_list( config.get(section, "paths").strip() ) - norm_paths = [] # type: List[str] + norm_paths: List[str] = [] for base_dir in base_dirs: norm_paths.extend( path diff --git a/src/flake8/options/manager.py b/src/flake8/options/manager.py index 09bf862..28cee4d 100644 --- a/src/flake8/options/manager.py +++ b/src/flake8/options/manager.py @@ -5,14 +5,25 @@ import contextlib import enum import functools import logging -from typing import Any, Callable, cast, Dict, Generator, List, Mapping -from typing import Optional, Sequence, Set, Tuple, Union +from typing import Any +from typing import Callable +from typing import cast +from typing import Dict +from typing import Generator +from typing import List +from typing import Mapping +from typing import Optional +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import Union from flake8 import utils -if False: # TYPE_CHECKING +if TYPE_CHECKING: from typing import NoReturn - from typing import Type LOG = logging.getLogger(__name__) @@ -21,7 +32,7 @@ LOG = logging.getLogger(__name__) _ARG = enum.Enum("_ARG", "NO") -_optparse_callable_map = { +_optparse_callable_map: Dict[str, Union[Type[Any], _ARG]] = { "int": int, "long": int, "string": str, @@ -30,27 +41,25 @@ _optparse_callable_map = { "choice": _ARG.NO, # optparse allows this but does not document it "str": str, -} # type: Dict[str, Union[Type[Any], _ARG]] +} class _CallbackAction(argparse.Action): """Shim for optparse-style callback actions.""" - def __init__(self, *args, **kwargs): - # type: (*Any, **Any) -> None + def __init__(self, *args: Any, **kwargs: Any) -> None: self._callback = kwargs.pop("callback") self._callback_args = kwargs.pop("callback_args", ()) self._callback_kwargs = kwargs.pop("callback_kwargs", {}) - super(_CallbackAction, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def __call__( self, - parser, # type: argparse.ArgumentParser - namespace, # type: argparse.Namespace - values, # type: Optional[Union[Sequence[str], str]] - option_string=None, # type: Optional[str] - ): - # type: (...) -> None + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: Optional[Union[Sequence[str], str]], + option_string: Optional[str] = None, + ) -> None: if not values: values = None elif isinstance(values, list) and len(values) > 1: @@ -61,23 +70,24 @@ class _CallbackAction(argparse.Action): values, parser, *self._callback_args, - **self._callback_kwargs + **self._callback_kwargs, ) -def _flake8_normalize(value, *args, **kwargs): - # type: (str, *str, **bool) -> Union[str, List[str]] +def _flake8_normalize( + value: str, *args: str, **kwargs: bool +) -> Union[str, List[str]]: comma_separated_list = kwargs.pop("comma_separated_list", False) normalize_paths = kwargs.pop("normalize_paths", False) if kwargs: - raise TypeError("Unexpected keyword args: {}".format(kwargs)) + raise TypeError(f"Unexpected keyword args: {kwargs}") - ret = value # type: Union[str, List[str]] - if comma_separated_list and isinstance(ret, utils.string_types): + ret: Union[str, List[str]] = value + if comma_separated_list and isinstance(ret, str): ret = utils.parse_comma_separated_list(value) if normalize_paths: - if isinstance(ret, utils.string_types): + if isinstance(ret, str): ret = utils.normalize_path(ret, *args) else: ret = utils.normalize_paths(ret, *args) @@ -85,34 +95,34 @@ def _flake8_normalize(value, *args, **kwargs): return ret -class Option(object): +class Option: """Our wrapper around an argparse argument parsers to add features.""" def __init__( self, - short_option_name=_ARG.NO, # type: Union[str, _ARG] - long_option_name=_ARG.NO, # type: Union[str, _ARG] + short_option_name: Union[str, _ARG] = _ARG.NO, + long_option_name: Union[str, _ARG] = _ARG.NO, # Options below here are taken from the optparse.Option class - action=_ARG.NO, # type: Union[str, Type[argparse.Action], _ARG] - default=_ARG.NO, # type: Union[Any, _ARG] - type=_ARG.NO, # type: Union[str, Callable[..., Any], _ARG] - dest=_ARG.NO, # type: Union[str, _ARG] - nargs=_ARG.NO, # type: Union[int, str, _ARG] - const=_ARG.NO, # type: Union[Any, _ARG] - choices=_ARG.NO, # type: Union[Sequence[Any], _ARG] - help=_ARG.NO, # type: Union[str, _ARG] - metavar=_ARG.NO, # type: Union[str, _ARG] + action: Union[str, Type[argparse.Action], _ARG] = _ARG.NO, + default: Union[Any, _ARG] = _ARG.NO, + type: Union[str, Callable[..., Any], _ARG] = _ARG.NO, + dest: Union[str, _ARG] = _ARG.NO, + nargs: Union[int, str, _ARG] = _ARG.NO, + const: Union[Any, _ARG] = _ARG.NO, + choices: Union[Sequence[Any], _ARG] = _ARG.NO, + help: Union[str, _ARG] = _ARG.NO, + metavar: Union[str, _ARG] = _ARG.NO, # deprecated optparse-only options - callback=_ARG.NO, # type: Union[Callable[..., Any], _ARG] - callback_args=_ARG.NO, # type: Union[Sequence[Any], _ARG] - callback_kwargs=_ARG.NO, # type: Union[Mapping[str, Any], _ARG] + callback: Union[Callable[..., Any], _ARG] = _ARG.NO, + callback_args: Union[Sequence[Any], _ARG] = _ARG.NO, + callback_kwargs: Union[Mapping[str, Any], _ARG] = _ARG.NO, # Options below are taken from argparse.ArgumentParser.add_argument - required=_ARG.NO, # type: Union[bool, _ARG] + required: Union[bool, _ARG] = _ARG.NO, # Options below here are specific to Flake8 - parse_from_config=False, # type: bool - comma_separated_list=False, # type: bool - normalize_paths=False, # type: bool - ): # type: (...) -> None + parse_from_config: bool = False, + comma_separated_list: bool = False, + normalize_paths: bool = False, + ) -> None: """Initialize an Option instance. The following are all passed directly through to argparse. @@ -203,7 +213,7 @@ class Option(object): nargs = 0 # optparse -> argparse for `type` - if isinstance(type, utils.string_types): + if isinstance(type, str): LOG.warning( "option %s: please update from optparse string `type=` to " "argparse callable `type=` -- this will be an error in the " @@ -240,7 +250,7 @@ class Option(object): self.help = help self.metavar = metavar self.required = required - self.option_kwargs = { + self.option_kwargs: Dict[str, Union[Any, _ARG]] = { "action": self.action, "default": self.default, "type": self.type, @@ -254,14 +264,14 @@ class Option(object): "help": self.help, "metavar": self.metavar, "required": self.required, - } # type: Dict[str, Union[Any, _ARG]] + } # Set our custom attributes self.parse_from_config = parse_from_config self.comma_separated_list = comma_separated_list self.normalize_paths = normalize_paths - self.config_name = None # type: Optional[str] + self.config_name: Optional[str] = None if parse_from_config: if long_option_name is _ARG.NO: raise ValueError( @@ -273,26 +283,23 @@ class Option(object): self._opt = None @property - def filtered_option_kwargs(self): # type: () -> Dict[str, Any] + def filtered_option_kwargs(self) -> Dict[str, Any]: """Return any actually-specified arguments.""" return { k: v for k, v in self.option_kwargs.items() if v is not _ARG.NO } - def __repr__(self): # type: () -> str # noqa: D105 + def __repr__(self) -> str: # noqa: D105 parts = [] for arg in self.option_args: parts.append(arg) for k, v in self.filtered_option_kwargs.items(): - parts.append("{}={!r}".format(k, v)) - return "Option({})".format(", ".join(parts)) + parts.append(f"{k}={v!r}") + return f"Option({', '.join(parts)})" - def normalize(self, value, *normalize_args): - # type: (Any, *str) -> Any + def normalize(self, value: Any, *normalize_args: str) -> Any: """Normalize the value based on the option configuration.""" - if self.comma_separated_list and isinstance( - value, utils.string_types - ): + if self.comma_separated_list and isinstance(value, str): value = utils.parse_comma_separated_list(value) if self.normalize_paths: @@ -303,8 +310,9 @@ class Option(object): return value - def normalize_from_setuptools(self, value): - # type: (str) -> Union[int, float, complex, bool, str] + def normalize_from_setuptools( + self, value: str + ) -> Union[int, float, complex, bool, str]: """Normalize the value received from setuptools.""" value = self.normalize(value) if self.type is int or self.action == "count": @@ -321,13 +329,12 @@ class Option(object): return False return value - def to_argparse(self): - # type: () -> Tuple[List[str], Dict[str, Any]] + def to_argparse(self) -> Tuple[List[str], Dict[str, Any]]: """Convert a Flake8 Option to argparse ``add_argument`` arguments.""" return self.option_args, self.filtered_option_kwargs @property - def to_optparse(self): # type: () -> NoReturn + def to_optparse(self) -> "NoReturn": """No longer functional.""" raise AttributeError("to_optparse: flake8 now uses argparse") @@ -337,16 +344,16 @@ PluginVersion = collections.namedtuple( ) -class OptionManager(object): +class OptionManager: """Manage Options and OptionParser while adding post-processing.""" def __init__( self, - prog, - version, - usage="%(prog)s [options] file file ...", - parents=None, - ): # type: (str, str, str, Optional[List[argparse.ArgumentParser]]) -> None # noqa: E501 + prog: str, + version: str, + usage: str = "%(prog)s [options] file file ...", + parents: Optional[List[argparse.ArgumentParser]] = None, + ) -> None: # noqa: E501 """Initialize an instance of an OptionManager. :param str prog: @@ -362,10 +369,10 @@ class OptionManager(object): if parents is None: parents = [] - self.parser = argparse.ArgumentParser( + self.parser: argparse.ArgumentParser = argparse.ArgumentParser( prog=prog, usage=usage, parents=parents - ) # type: argparse.ArgumentParser - self._current_group = None # type: Optional[argparse._ArgumentGroup] + ) + self._current_group: Optional[argparse._ArgumentGroup] = None self.version_action = cast( "argparse._VersionAction", self.parser.add_argument( @@ -373,16 +380,16 @@ class OptionManager(object): ), ) self.parser.add_argument("filenames", nargs="*", metavar="filename") - self.config_options_dict = {} # type: Dict[str, Option] - self.options = [] # type: List[Option] + self.config_options_dict: Dict[str, Option] = {} + self.options: List[Option] = [] self.program_name = prog self.version = version - self.registered_plugins = set() # type: Set[PluginVersion] - self.extended_default_ignore = set() # type: Set[str] - self.extended_default_select = set() # type: Set[str] + self.registered_plugins: Set[PluginVersion] = set() + self.extended_default_ignore: Set[str] = set() + self.extended_default_select: Set[str] = set() @contextlib.contextmanager - def group(self, name): # type: (str) -> Generator[None, None, None] + def group(self, name: str) -> Generator[None, None, None]: """Attach options to an argparse group during this context.""" group = self.parser.add_argument_group(name) self._current_group, orig_group = group, self._current_group @@ -391,7 +398,7 @@ class OptionManager(object): finally: self._current_group = orig_group - def add_option(self, *args, **kwargs): # type: (*Any, **Any) -> None + def add_option(self, *args: Any, **kwargs: Any) -> None: """Create and register a new option. See parameters for :class:`~flake8.options.manager.Option` for @@ -416,8 +423,7 @@ class OptionManager(object): self.config_options_dict[name.replace("_", "-")] = option LOG.debug('Registered option "%s".', option) - def remove_from_default_ignore(self, error_codes): - # type: (Sequence[str]) -> None + def remove_from_default_ignore(self, error_codes: Sequence[str]) -> None: """Remove specified error codes from the default ignore list. :param list error_codes: @@ -435,8 +441,7 @@ class OptionManager(object): error_code, ) - def extend_default_ignore(self, error_codes): - # type: (Sequence[str]) -> None + def extend_default_ignore(self, error_codes: Sequence[str]) -> None: """Extend the default ignore list with the error codes provided. :param list error_codes: @@ -446,8 +451,7 @@ class OptionManager(object): LOG.debug("Extending default ignore list with %r", error_codes) self.extended_default_ignore.update(error_codes) - def extend_default_select(self, error_codes): - # type: (Sequence[str]) -> None + def extend_default_select(self, error_codes: Sequence[str]) -> None: """Extend the default select list with the error codes provided. :param list error_codes: @@ -458,22 +462,21 @@ class OptionManager(object): self.extended_default_select.update(error_codes) def generate_versions( - self, format_str="%(name)s: %(version)s", join_on=", " - ): - # type: (str, str) -> str + self, format_str: str = "%(name)s: %(version)s", join_on: str = ", " + ) -> str: """Generate a comma-separated list of versions of plugins.""" return join_on.join( format_str % plugin._asdict() for plugin in sorted(self.registered_plugins) ) - def update_version_string(self): # type: () -> None + def update_version_string(self) -> None: """Update the flake8 version string.""" self.version_action.version = "{} ({}) {}".format( self.version, self.generate_versions(), utils.get_python_version() ) - def generate_epilog(self): # type: () -> None + def generate_epilog(self) -> None: """Create an epilog with the version and name of each of plugin.""" plugin_version_format = "%(name)s: %(version)s" self.parser.epilog = "Installed plugins: " + self.generate_versions( @@ -482,10 +485,9 @@ class OptionManager(object): def parse_args( self, - args=None, # type: Optional[List[str]] - values=None, # type: Optional[argparse.Namespace] - ): - # type: (...) -> Tuple[argparse.Namespace, List[str]] + args: Optional[List[str]] = None, + values: Optional[argparse.Namespace] = None, + ) -> Tuple[argparse.Namespace, List[str]]: """Proxy to calling the OptionParser's parse_args method.""" self.generate_epilog() self.update_version_string() @@ -495,8 +497,9 @@ class OptionManager(object): # TODO: refactor callers to not need this return parsed_args, parsed_args.filenames - def parse_known_args(self, args=None): - # type: (Optional[List[str]]) -> Tuple[argparse.Namespace, List[str]] + def parse_known_args( + self, args: Optional[List[str]] = None + ) -> Tuple[argparse.Namespace, List[str]]: """Parse only the known arguments from the argument values. Replicate a little argparse behaviour while we're still on @@ -506,8 +509,9 @@ class OptionManager(object): self.update_version_string() return self.parser.parse_known_args(args) - def register_plugin(self, name, version, local=False): - # type: (str, str, bool) -> None + def register_plugin( + self, name: str, version: str, local: bool = False + ) -> None: """Register a plugin relying on the OptionManager. :param str name: diff --git a/src/flake8/plugins/manager.py b/src/flake8/plugins/manager.py index abfd14b..a7f285a 100644 --- a/src/flake8/plugins/manager.py +++ b/src/flake8/plugins/manager.py @@ -1,6 +1,10 @@ """Plugin loading and management logic and classes.""" import logging -from typing import Any, Dict, List, Optional, Set +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import Set from flake8 import exceptions from flake8 import utils @@ -13,7 +17,7 @@ __all__ = ("Checkers", "Plugin", "PluginManager", "ReportFormatters") NO_GROUP_FOUND = object() -class Plugin(object): +class Plugin: """Wrap an EntryPoint from setuptools and other logic.""" def __init__(self, name, entry_point, local=False): @@ -31,16 +35,16 @@ class Plugin(object): self.name = name self.entry_point = entry_point self.local = local - self._plugin = None # type: Any + self._plugin: Any = None self._parameters = None - self._parameter_names = None # type: Optional[List[str]] + self._parameter_names: Optional[List[str]] = None self._group = None self._plugin_name = None self._version = None - def __repr__(self): # type: () -> str + def __repr__(self) -> str: """Provide an easy to read description of the current plugin.""" - return 'Plugin(name="{0}", entry_point="{1}")'.format( + return 'Plugin(name="{}", entry_point="{}")'.format( self.name, self.entry_point.value ) @@ -84,7 +88,7 @@ class Plugin(object): return self._parameters @property - def parameter_names(self): # type: () -> List[str] + def parameter_names(self) -> List[str]: """List of argument names that need to be passed to the plugin.""" if self._parameter_names is None: self._parameter_names = list(self.parameters) @@ -100,7 +104,7 @@ class Plugin(object): return self._plugin @property - def version(self): # type: () -> str + def version(self) -> str: """Return the version of the plugin.""" version = self._version if version is None: @@ -134,9 +138,9 @@ class Plugin(object): self._plugin = self.entry_point.load() if not callable(self._plugin): msg = ( - "Plugin %r is not a callable. It might be written for an" - " older version of flake8 and might not work with this" - " version" % self._plugin + f"Plugin {self._plugin!r} is not a callable. It might be " + f"written for an older version of flake8 and might not work " + f"with this version" ) LOG.critical(msg) raise TypeError(msg) @@ -219,11 +223,12 @@ class Plugin(object): self.disable(optmanager) -class PluginManager(object): # pylint: disable=too-few-public-methods +class PluginManager: # pylint: disable=too-few-public-methods """Find and manage plugins consistently.""" - def __init__(self, namespace, local_plugins=None): - # type: (str, Optional[List[str]]) -> None + def __init__( + self, namespace: str, local_plugins: Optional[List[str]] = None + ) -> None: """Initialize the manager. :param str namespace: @@ -232,8 +237,8 @@ class PluginManager(object): # pylint: disable=too-few-public-methods Plugins from config (as "X = path.to:Plugin" strings). """ self.namespace = namespace - self.plugins = {} # type: Dict[str, Plugin] - self.names = [] # type: List[str] + self.plugins: Dict[str, Plugin] = {} + self.names: List[str] = [] self._load_local_plugins(local_plugins or []) self._load_entrypoint_plugins() @@ -310,7 +315,7 @@ class PluginManager(object): # pylint: disable=too-few-public-methods :rtype: tuple """ - plugins_seen = set() # type: Set[str] + plugins_seen: Set[str] = set() for entry_point_name in self.names: plugin = self.plugins[entry_point_name] plugin_name = plugin.plugin_name @@ -342,10 +347,10 @@ def version_for(plugin): return getattr(module, "__version__", None) -class PluginTypeManager(object): +class PluginTypeManager: """Parent class for most of the specific plugin types.""" - namespace = None # type: str + namespace: str def __init__(self, local_plugins=None): """Initialize the plugin type's manager. diff --git a/src/flake8/plugins/pyflakes.py b/src/flake8/plugins/pyflakes.py index e845483..4d1d7b8 100644 --- a/src/flake8/plugins/pyflakes.py +++ b/src/flake8/plugins/pyflakes.py @@ -1,16 +1,11 @@ """Plugin built-in to Flake8 to treat pyflakes as a plugin.""" -# -*- coding: utf-8 -*- -from __future__ import absolute_import - import os from typing import List -import pyflakes import pyflakes.checker from flake8 import utils - FLAKE8_PYFLAKES_CODES = { "UnusedImport": "F401", "ImportShadowedByLoopVar": "F402", @@ -69,8 +64,8 @@ class FlakesChecker(pyflakes.checker.Checker): name = "pyflakes" version = pyflakes.__version__ with_doctest = False - include_in_doctest = [] # type: List[str] - exclude_from_doctest = [] # type: List[str] + include_in_doctest: List[str] = [] + exclude_from_doctest: List[str] = [] def __init__(self, tree, file_tokens, filename): """Initialize the PyFlakes plugin with an AST tree and filename.""" @@ -96,7 +91,7 @@ class FlakesChecker(pyflakes.checker.Checker): if overlaped_by: with_doctest = True - super(FlakesChecker, self).__init__( + super().__init__( tree, filename=filename, withDoctest=with_doctest, @@ -150,7 +145,7 @@ class FlakesChecker(pyflakes.checker.Checker): if included_file == "": continue if not included_file.startswith((os.sep, "./", "~/")): - included_files.append("./" + included_file) + included_files.append(f"./{included_file}") else: included_files.append(included_file) cls.include_in_doctest = utils.normalize_paths(included_files) @@ -160,7 +155,7 @@ class FlakesChecker(pyflakes.checker.Checker): if excluded_file == "": continue if not excluded_file.startswith((os.sep, "./", "~/")): - excluded_files.append("./" + excluded_file) + excluded_files.append(f"./{excluded_file}") else: excluded_files.append(excluded_file) cls.exclude_from_doctest = utils.normalize_paths(excluded_files) @@ -170,10 +165,10 @@ class FlakesChecker(pyflakes.checker.Checker): ) if inc_exc: raise ValueError( - '"%s" was specified in both the ' - "include-in-doctest and exclude-from-doctest " - "options. You are not allowed to specify it in " - "both for doctesting." % inc_exc + f"{inc_exc!r} was specified in both the " + f"include-in-doctest and exclude-from-doctest " + f"options. You are not allowed to specify it in " + f"both for doctesting." ) def run(self): diff --git a/src/flake8/processor.py b/src/flake8/processor.py index ad011c3..7c4672a 100644 --- a/src/flake8/processor.py +++ b/src/flake8/processor.py @@ -3,9 +3,13 @@ import argparse import ast import contextlib import logging -import sys import tokenize -from typing import Any, Dict, Generator, List, Optional, Tuple +from typing import Any +from typing import Dict +from typing import Generator +from typing import List +from typing import Optional +from typing import Tuple import flake8 from flake8 import defaults @@ -25,7 +29,7 @@ _LogicalMapping = List[Tuple[int, Tuple[int, int]]] _Logical = Tuple[List[str], List[str], _LogicalMapping] -class FileProcessor(object): +class FileProcessor: """Processes a file and holdes state. This processes a file by generating tokens, logical and physical lines, @@ -56,8 +60,12 @@ class FileProcessor(object): #: always ``False``, included for compatibility noqa = False - def __init__(self, filename, options, lines=None): - # type: (str, argparse.Namespace, Optional[List[str]]) -> None + def __init__( + self, + filename: str, + options: argparse.Namespace, + lines: Optional[List[str]] = None, + ) -> None: """Initialice our file processor. :param str filename: @@ -74,13 +82,13 @@ class FileProcessor(object): #: Number of blank lines self.blank_lines = 0 #: Checker states for each plugin? - self._checker_states = {} # type: Dict[str, Dict[Any, Any]] + self._checker_states: Dict[str, Dict[Any, Any]] = {} #: Current checker state - self.checker_state = {} # type: Dict[Any, Any] + self.checker_state: Dict[Any, Any] = {} #: User provided option for hang closing self.hang_closing = options.hang_closing #: Character used for indentation - self.indent_char = None # type: Optional[str] + self.indent_char: Optional[str] = None #: Current level of indentation self.indent_level = 0 #: Number of spaces used for indentation @@ -104,19 +112,19 @@ class FileProcessor(object): #: Previous unindented (i.e. top-level) logical line self.previous_unindented_logical_line = "" #: Current set of tokens - self.tokens = [] # type: List[_Token] + self.tokens: List[_Token] = [] #: Total number of lines in the file self.total_lines = len(self.lines) #: Verbosity level of Flake8 self.verbose = options.verbose #: Statistics dictionary self.statistics = {"logical lines": 0} - self._file_tokens = None # type: Optional[List[_Token]] + self._file_tokens: Optional[List[_Token]] = None # map from line number to the line we'll search for `noqa` in - self._noqa_line_mapping = None # type: Optional[Dict[int, str]] + self._noqa_line_mapping: Optional[Dict[int, str]] = None @property - def file_tokens(self): # type: () -> List[_Token] + def file_tokens(self) -> List[_Token]: """Return the complete set of tokens for a file. Accessing this attribute *may* raise an InvalidSyntax exception. @@ -135,28 +143,28 @@ class FileProcessor(object): return self._file_tokens @contextlib.contextmanager - def inside_multiline(self, line_number): - # type: (int) -> Generator[None, None, None] + def inside_multiline( + self, line_number: int + ) -> Generator[None, None, None]: """Context-manager to toggle the multiline attribute.""" self.line_number = line_number self.multiline = True yield self.multiline = False - def reset_blank_before(self): # type: () -> None + def reset_blank_before(self) -> None: """Reset the blank_before attribute to zero.""" self.blank_before = 0 - def delete_first_token(self): # type: () -> None + def delete_first_token(self) -> None: """Delete the first token in the list of tokens.""" del self.tokens[0] - def visited_new_blank_line(self): # type: () -> None + def visited_new_blank_line(self) -> None: """Note that we visited a new blank line.""" self.blank_lines += 1 - def update_state(self, mapping): - # type: (_LogicalMapping) -> None + def update_state(self, mapping: _LogicalMapping) -> None: """Update the indent level based on the logical line mapping.""" (start_row, start_col) = mapping[0][1] start_line = self.lines[start_row - 1] @@ -164,15 +172,14 @@ class FileProcessor(object): if self.blank_before < self.blank_lines: self.blank_before = self.blank_lines - def update_checker_state_for(self, plugin): - # type: (Dict[str, Any]) -> None + def update_checker_state_for(self, plugin: Dict[str, Any]) -> None: """Update the checker_state attribute for the plugin.""" if "checker_state" in plugin["parameters"]: self.checker_state = self._checker_states.setdefault( plugin["name"], {} ) - def next_logical_line(self): # type: () -> None + def next_logical_line(self) -> None: """Record the previous logical line. This also resets the tokens list and the blank_lines count. @@ -185,11 +192,11 @@ class FileProcessor(object): self.blank_lines = 0 self.tokens = [] - def build_logical_line_tokens(self): # type: () -> _Logical + def build_logical_line_tokens(self) -> _Logical: """Build the mapping, comments, and logical line lists.""" logical = [] comments = [] - mapping = [] # type: _LogicalMapping + mapping: _LogicalMapping = [] length = 0 previous_row = previous_column = None for token_type, text, start, end, line in self.tokens: @@ -211,7 +218,7 @@ class FileProcessor(object): if previous_text == "," or ( previous_text not in "{[(" and text not in "}])" ): - text = " " + text + text = f" {text}" elif previous_column != start_column: text = line[previous_column:start_column] + text logical.append(text) @@ -220,12 +227,11 @@ class FileProcessor(object): (previous_row, previous_column) = end return comments, logical, mapping - def build_ast(self): # type: () -> ast.AST + def build_ast(self) -> ast.AST: """Build an abstract syntax tree from the list of lines.""" return ast.parse("".join(self.lines)) - def build_logical_line(self): - # type: () -> Tuple[str, str, _LogicalMapping] + def build_logical_line(self) -> Tuple[str, str, _LogicalMapping]: """Build a logical line from the current tokens list.""" comments, logical, mapping_list = self.build_logical_line_tokens() joined_comments = "".join(comments) @@ -233,8 +239,7 @@ class FileProcessor(object): self.statistics["logical lines"] += 1 return joined_comments, self.logical_line, mapping_list - def split_line(self, token): - # type: (_Token) -> Generator[str, None, None] + def split_line(self, token: _Token) -> Generator[str, None, None]: """Split a physical line's line based on new-lines. This also auto-increments the line number for the caller. @@ -243,8 +248,11 @@ class FileProcessor(object): yield line self.line_number += 1 - def keyword_arguments_for(self, parameters, arguments=None): - # type: (Dict[str, bool], Optional[Dict[str, Any]]) -> Dict[str, Any] + def keyword_arguments_for( + self, + parameters: Dict[str, bool], + arguments: Optional[Dict[str, Any]] = None, + ) -> Dict[str, Any]: """Generate the keyword arguments for a list of parameters.""" if arguments is None: arguments = {} @@ -265,7 +273,7 @@ class FileProcessor(object): ) return arguments - def generate_tokens(self): # type: () -> Generator[_Token, None, None] + def generate_tokens(self) -> Generator[_Token, None, None]: """Tokenize the file and yield the tokens. :raises flake8.exceptions.InvalidSyntax: @@ -281,13 +289,14 @@ class FileProcessor(object): except (tokenize.TokenError, SyntaxError) as exc: raise exceptions.InvalidSyntax(exception=exc) - def _noqa_line_range(self, min_line, max_line): - # type: (int, int) -> Dict[int, str] + def _noqa_line_range( + self, min_line: int, max_line: int + ) -> Dict[int, str]: line_range = range(min_line, max_line + 1) joined = "".join(self.lines[min_line - 1 : max_line]) return dict.fromkeys(line_range, joined) - def noqa_line_for(self, line_number): # type: (int) -> Optional[str] + def noqa_line_for(self, line_number: int) -> Optional[str]: """Retrieve the line which will be used to determine noqa.""" if self._noqa_line_mapping is None: try: @@ -327,7 +336,7 @@ class FileProcessor(object): # retrieve a physical line (since none exist). return self._noqa_line_mapping.get(line_number) - def next_line(self): # type: () -> str + def next_line(self) -> str: """Get the next line from the list.""" if self.line_number >= self.total_lines: return "" @@ -337,8 +346,7 @@ class FileProcessor(object): self.indent_char = line[0] return line - def read_lines(self): - # type: () -> List[str] + def read_lines(self) -> List[str]: """Read the lines for this file checker.""" if self.filename is None or self.filename == "-": self.filename = self.options.stdin_display_name or "stdin" @@ -347,13 +355,8 @@ class FileProcessor(object): lines = self.read_lines_from_filename() return lines - def _readlines_py2(self): - # type: () -> List[str] - with open(self.filename, "rU") as fd: - return fd.readlines() - - def _readlines_py3(self): - # type: () -> List[str] + def read_lines_from_filename(self) -> List[str]: + """Read the lines for a file.""" try: with tokenize.open(self.filename) as fd: return fd.readlines() @@ -363,22 +366,11 @@ class FileProcessor(object): with open(self.filename, encoding="latin-1") as fd: return fd.readlines() - def read_lines_from_filename(self): - # type: () -> List[str] - """Read the lines for a file.""" - if (2, 6) <= sys.version_info < (3, 0): - readlines = self._readlines_py2 - elif (3, 0) <= sys.version_info < (4, 0): - readlines = self._readlines_py3 - return readlines() - - def read_lines_from_stdin(self): - # type: () -> List[str] + def read_lines_from_stdin(self) -> List[str]: """Read the lines from standard in.""" return utils.stdin_get_lines() - def should_ignore_file(self): - # type: () -> bool + def should_ignore_file(self) -> bool: """Check if ``flake8: noqa`` is in the file to be ignored. :returns: @@ -400,8 +392,7 @@ class FileProcessor(object): else: return False - def strip_utf_bom(self): - # type: () -> None + def strip_utf_bom(self) -> None: """Strip the UTF bom from the lines of the file.""" if not self.lines: # If we have nothing to analyze quit early @@ -418,23 +409,22 @@ class FileProcessor(object): self.lines[0] = self.lines[0][3:] -def is_eol_token(token): # type: (_Token) -> bool +def is_eol_token(token: _Token) -> bool: """Check if the token is an end-of-line token.""" return token[0] in NEWLINE or token[4][token[3][1] :].lstrip() == "\\\n" -def is_multiline_string(token): # type: (_Token) -> bool +def is_multiline_string(token: _Token) -> bool: """Check if this is a multiline string.""" return token[0] == tokenize.STRING and "\n" in token[1] -def token_is_newline(token): # type: (_Token) -> bool +def token_is_newline(token: _Token) -> bool: """Check if the token type is a newline token type.""" return token[0] in NEWLINE -def count_parentheses(current_parentheses_count, token_text): - # type: (int, str) -> int +def count_parentheses(current_parentheses_count: int, token_text: str) -> int: """Count the number of parentheses.""" if token_text in "([{": # nosec return current_parentheses_count + 1 @@ -443,12 +433,12 @@ def count_parentheses(current_parentheses_count, token_text): return current_parentheses_count -def log_token(log, token): # type: (logging.Logger, _Token) -> None +def log_token(log: logging.Logger, token: _Token) -> None: """Log a token to a provided logging object.""" if token[2][0] == token[3][0]: - pos = "[%s:%s]" % (token[2][1] or "", token[3][1]) + pos = "[{}:{}]".format(token[2][1] or "", token[3][1]) else: - pos = "l.%s" % token[3][0] + pos = f"l.{token[3][0]}" log.log( flake8._EXTRA_VERBOSE, "l.%s\t%s\t%s\t%r" @@ -458,7 +448,7 @@ def log_token(log, token): # type: (logging.Logger, _Token) -> None # NOTE(sigmavirus24): This was taken wholesale from # https://github.com/PyCQA/pycodestyle -def expand_indent(line): # type: (str) -> int +def expand_indent(line: str) -> int: r"""Return the amount of indentation. Tabs are expanded to the next multiple of 8. @@ -488,7 +478,7 @@ def expand_indent(line): # type: (str) -> int # NOTE(sigmavirus24): This was taken wholesale from # https://github.com/PyCQA/pycodestyle. The in-line comments were edited to be # more descriptive. -def mutate_string(text): # type: (str) -> str +def mutate_string(text: str) -> str: """Replace contents with 'xxx' to prevent syntax matching. >>> mutate_string('"abc"') diff --git a/src/flake8/statistics.py b/src/flake8/statistics.py index 100227f..073bfe4 100644 --- a/src/flake8/statistics.py +++ b/src/flake8/statistics.py @@ -1,19 +1,23 @@ """Statistic collection logic for Flake8.""" import collections -from typing import Dict, Generator, List, Optional +from typing import Dict +from typing import Generator +from typing import List +from typing import Optional +from typing import TYPE_CHECKING -if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2 +if TYPE_CHECKING: from flake8.style_guide import Violation -class Statistics(object): +class Statistics: """Manager of aggregated statistics for a run of Flake8.""" - def __init__(self): # type: () -> None + def __init__(self) -> None: """Initialize the underlying dictionary for our statistics.""" - self._store = {} # type: Dict[Key, Statistic] + self._store: Dict[Key, "Statistic"] = {} - def error_codes(self): # type: () -> List[str] + def error_codes(self) -> List[str]: """Return all unique error codes stored. :returns: @@ -23,7 +27,7 @@ class Statistics(object): """ return sorted({key.code for key in self._store}) - def record(self, error): # type: (Violation) -> None + def record(self, error: "Violation") -> None: """Add the fact that the error was seen in the file. :param error: @@ -37,8 +41,9 @@ class Statistics(object): self._store[key] = Statistic.create_from(error) self._store[key].increment() - def statistics_for(self, prefix, filename=None): - # type: (str, Optional[str]) -> Generator[Statistic, None, None] + def statistics_for( + self, prefix: str, filename: Optional[str] = None + ) -> Generator["Statistic", None, None]: """Generate statistics for the prefix and filename. If you have a :class:`Statistics` object that has recorded errors, @@ -79,11 +84,11 @@ class Key(collections.namedtuple("Key", ["filename", "code"])): __slots__ = () @classmethod - def create_from(cls, error): # type: (Violation) -> Key + def create_from(cls, error: "Violation") -> "Key": """Create a Key from :class:`flake8.style_guide.Violation`.""" return cls(filename=error.filename, code=error.code) - def matches(self, prefix, filename): # type: (str, Optional[str]) -> bool + def matches(self, prefix: str, filename: Optional[str]) -> bool: """Determine if this key matches some constraints. :param str prefix: @@ -102,7 +107,7 @@ class Key(collections.namedtuple("Key", ["filename", "code"])): ) -class Statistic(object): +class Statistic: """Simple wrapper around the logic of each statistic. Instead of maintaining a simple but potentially hard to reason about @@ -110,8 +115,9 @@ class Statistic(object): convenience methods on it. """ - def __init__(self, error_code, filename, message, count): - # type: (str, str, str, int) -> None + def __init__( + self, error_code: str, filename: str, message: str, count: int + ) -> None: """Initialize our Statistic.""" self.error_code = error_code self.filename = filename @@ -119,7 +125,7 @@ class Statistic(object): self.count = count @classmethod - def create_from(cls, error): # type: (Violation) -> Statistic + def create_from(cls, error: "Violation") -> "Statistic": """Create a Statistic from a :class:`flake8.style_guide.Violation`.""" return cls( error_code=error.code, @@ -128,6 +134,6 @@ class Statistic(object): count=0, ) - def increment(self): # type: () -> None + def increment(self) -> None: """Increment the number of times we've seen this error in this file.""" self.count += 1 diff --git a/src/flake8/style_guide.py b/src/flake8/style_guide.py index 9be0fe8..c35c739 100644 --- a/src/flake8/style_guide.py +++ b/src/flake8/style_guide.py @@ -4,16 +4,23 @@ import collections import contextlib import copy import enum +import functools import itertools import linecache import logging -from typing import Dict, Generator, List, Match, Optional, Sequence, Set -from typing import Tuple, Union +from typing import Dict +from typing import Generator +from typing import List +from typing import Match +from typing import Optional +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import Union from flake8 import defaults from flake8 import statistics from flake8 import utils -from flake8._compat import lru_cache from flake8.formatting import base as base_formatter __all__ = ("StyleGuide",) @@ -42,8 +49,8 @@ class Decision(enum.Enum): Selected = "selected error" -@lru_cache(maxsize=512) -def find_noqa(physical_line): # type: (str) -> Optional[Match[str]] +@functools.lru_cache(maxsize=512) +def find_noqa(physical_line: str) -> Optional[Match[str]]: return defaults.NOQA_INLINE_REGEXP.search(physical_line) @@ -63,8 +70,7 @@ _Violation = collections.namedtuple( class Violation(_Violation): """Class representing a violation reported by Flake8.""" - def is_inline_ignored(self, disable_noqa): - # type: (bool) -> bool + def is_inline_ignored(self, disable_noqa: bool) -> bool: """Determine if a comment has been added to ignore this line. :param bool disable_noqa: @@ -105,8 +111,7 @@ class Violation(_Violation): ) return False - def is_in(self, diff): - # type: (Dict[str, Set[int]]) -> bool + def is_in(self, diff: Dict[str, Set[int]]) -> bool: """Determine if the violation is included in a diff's line ranges. This function relies on the parsed data added via @@ -142,16 +147,16 @@ class Violation(_Violation): return self.line_number in line_numbers -class DecisionEngine(object): +class DecisionEngine: """A class for managing the decision process around violations. This contains the logic for whether a violation should be reported or ignored. """ - def __init__(self, options): # type: (argparse.Namespace) -> None + def __init__(self, options: argparse.Namespace) -> None: """Initialize the engine.""" - self.cache = {} # type: Dict[str, Decision] + self.cache: Dict[str, Decision] = {} self.selected = tuple(options.select) self.extended_selected = tuple( sorted(options.extended_default_select, reverse=True) @@ -169,16 +174,15 @@ class DecisionEngine(object): self.using_default_ignore = set(self.ignored) == set(defaults.IGNORE) self.using_default_select = set(self.selected) == set(defaults.SELECT) - def _in_all_selected(self, code): # type: (str) -> bool + def _in_all_selected(self, code: str) -> bool: return bool(self.all_selected) and code.startswith(self.all_selected) - def _in_extended_selected(self, code): # type: (str) -> bool + def _in_extended_selected(self, code: str) -> bool: return bool(self.extended_selected) and code.startswith( self.extended_selected ) - def was_selected(self, code): - # type: (str) -> Union[Selected, Ignored] + def was_selected(self, code: str) -> Union[Selected, Ignored]: """Determine if the code has been selected by the user. :param str code: @@ -201,8 +205,7 @@ class DecisionEngine(object): return Ignored.Implicitly - def was_ignored(self, code): - # type: (str) -> Union[Selected, Ignored] + def was_ignored(self, code: str) -> Union[Selected, Ignored]: """Determine if the code has been ignored by the user. :param str code: @@ -219,8 +222,7 @@ class DecisionEngine(object): return Selected.Implicitly - def more_specific_decision_for(self, code): - # type: (str) -> Decision + def more_specific_decision_for(self, code: str) -> Decision: select = find_first_match(code, self.all_selected) extra_select = find_first_match(code, self.extended_selected) ignore = find_first_match(code, self.ignored) @@ -268,8 +270,7 @@ class DecisionEngine(object): return Decision.Ignored return Decision.Selected - def make_decision(self, code): - # type: (str) -> Decision + def make_decision(self, code: str) -> Decision: """Decide if code should be ignored or selected.""" LOG.debug('Deciding if "%s" should be reported', code) selected = self.was_selected(code) @@ -295,8 +296,7 @@ class DecisionEngine(object): decision = Decision.Ignored # pylint: disable=R0204 return decision - def decision_for(self, code): - # type: (str) -> Decision + def decision_for(self, code: str) -> Decision: """Return the decision for a specific code. This method caches the decisions for codes to avoid retracing the same @@ -318,15 +318,15 @@ class DecisionEngine(object): return decision -class StyleGuideManager(object): +class StyleGuideManager: """Manage multiple style guides for a single run.""" def __init__( self, - options, # type: argparse.Namespace - formatter, # type: base_formatter.BaseFormatter - decider=None, # type: Optional[DecisionEngine] - ): # type: (...) -> None + options: argparse.Namespace, + formatter: base_formatter.BaseFormatter, + decider: Optional[DecisionEngine] = None, + ) -> None: """Initialize our StyleGuide. .. todo:: Add parameter documentation. @@ -335,7 +335,7 @@ class StyleGuideManager(object): self.formatter = formatter self.stats = statistics.Statistics() self.decider = decider or DecisionEngine(options) - self.style_guides = [] # type: List[StyleGuide] + self.style_guides: List[StyleGuide] = [] self.default_style_guide = StyleGuide( options, formatter, self.stats, decider=decider ) @@ -346,8 +346,9 @@ class StyleGuideManager(object): ) ) - def populate_style_guides_with(self, options): - # type: (argparse.Namespace) -> Generator[StyleGuide, None, None] + def populate_style_guides_with( + self, options: argparse.Namespace + ) -> Generator["StyleGuide", None, None]: """Generate style guides from the per-file-ignores option. :param options: @@ -367,8 +368,8 @@ class StyleGuideManager(object): filename=filename, extend_ignore_with=violations ) - @lru_cache(maxsize=None) - def style_guide_for(self, filename): # type: (str) -> StyleGuide + @functools.lru_cache(maxsize=None) + def style_guide_for(self, filename: str) -> "StyleGuide": """Find the StyleGuide for the filename in particular.""" guides = sorted( (g for g in self.style_guides if g.applies_to(filename)), @@ -379,8 +380,9 @@ class StyleGuideManager(object): return guides[0] @contextlib.contextmanager - def processing_file(self, filename): - # type: (str) -> Generator[StyleGuide, None, None] + def processing_file( + self, filename: str + ) -> Generator["StyleGuide", None, None]: """Record the fact that we're processing the file's results.""" guide = self.style_guide_for(filename) with guide.processing_file(filename): @@ -388,14 +390,13 @@ class StyleGuideManager(object): def handle_error( self, - code, - filename, - line_number, - column_number, - text, - physical_line=None, - ): - # type: (str, str, int, Optional[int], str, Optional[str]) -> int + code: str, + filename: str, + line_number: int, + column_number: Optional[int], + text: str, + physical_line: Optional[str] = None, + ) -> int: """Handle an error reported by a check. :param str code: @@ -423,8 +424,7 @@ class StyleGuideManager(object): code, filename, line_number, column_number, text, physical_line ) - def add_diff_ranges(self, diffinfo): - # type: (Dict[str, Set[int]]) -> None + def add_diff_ranges(self, diffinfo: Dict[str, Set[int]]) -> None: """Update the StyleGuides to filter out information not in the diff. This provides information to the underlying StyleGuides so that only @@ -437,16 +437,16 @@ class StyleGuideManager(object): guide.add_diff_ranges(diffinfo) -class StyleGuide(object): +class StyleGuide: """Manage a Flake8 user's style guide.""" def __init__( self, - options, # type: argparse.Namespace - formatter, # type: base_formatter.BaseFormatter - stats, # type: statistics.Statistics - filename=None, # type: Optional[str] - decider=None, # type: Optional[DecisionEngine] + options: argparse.Namespace, + formatter: base_formatter.BaseFormatter, + stats: statistics.Statistics, + filename: Optional[str] = None, + decider: Optional[DecisionEngine] = None, ): """Initialize our StyleGuide. @@ -459,14 +459,17 @@ class StyleGuide(object): self.filename = filename if self.filename: self.filename = utils.normalize_path(self.filename) - self._parsed_diff = {} # type: Dict[str, Set[int]] + self._parsed_diff: Dict[str, Set[int]] = {} - def __repr__(self): # type: () -> str + def __repr__(self) -> str: """Make it easier to debug which StyleGuide we're using.""" - return "".format(self.filename) + return f"" - def copy(self, filename=None, extend_ignore_with=None): - # type: (Optional[str], Optional[Sequence[str]]) -> StyleGuide + def copy( + self, + filename: Optional[str] = None, + extend_ignore_with: Optional[Sequence[str]] = None, + ) -> "StyleGuide": """Create a copy of this style guide with different values.""" filename = filename or self.filename options = copy.deepcopy(self.options) @@ -476,14 +479,15 @@ class StyleGuide(object): ) @contextlib.contextmanager - def processing_file(self, filename): - # type: (str) -> Generator[StyleGuide, None, None] + def processing_file( + self, filename: str + ) -> Generator["StyleGuide", None, None]: """Record the fact that we're processing the file's results.""" self.formatter.beginning(filename) yield self self.formatter.finished(filename) - def applies_to(self, filename): # type: (str) -> bool + def applies_to(self, filename: str) -> bool: """Check if this StyleGuide applies to the file. :param str filename: @@ -499,12 +503,11 @@ class StyleGuide(object): return utils.matches_filename( filename, patterns=[self.filename], - log_message='{!r} does %(whether)smatch "%(path)s"'.format(self), + log_message=f'{self!r} does %(whether)smatch "%(path)s"', logger=LOG, ) - def should_report_error(self, code): - # type: (str) -> Decision + def should_report_error(self, code: str) -> Decision: """Determine if the error code should be reported or ignored. This method only cares about the select and ignore rules as specified @@ -520,14 +523,13 @@ class StyleGuide(object): def handle_error( self, - code, - filename, - line_number, - column_number, - text, - physical_line=None, - ): - # type: (str, str, int, Optional[int], str, Optional[str]) -> int + code: str, + filename: str, + line_number: int, + column_number: Optional[int], + text: str, + physical_line: Optional[str] = None, + ) -> int: """Handle an error reported by a check. :param str code: @@ -579,8 +581,7 @@ class StyleGuide(object): return 1 return 0 - def add_diff_ranges(self, diffinfo): - # type: (Dict[str, Set[int]]) -> None + def add_diff_ranges(self, diffinfo: Dict[str, Set[int]]) -> None: """Update the StyleGuide to filter out information not in the diff. This provides information to the StyleGuide so that only the errors @@ -592,14 +593,15 @@ class StyleGuide(object): self._parsed_diff = diffinfo -def find_more_specific(selected, ignored): # type: (str, str) -> Decision +def find_more_specific(selected: str, ignored: str) -> Decision: if selected.startswith(ignored) and selected != ignored: return Decision.Selected return Decision.Ignored -def find_first_match(error_code, code_list): - # type: (str, Tuple[str, ...]) -> Optional[str] +def find_first_match( + error_code: str, code_list: Tuple[str, ...] +) -> Optional[str]: startswith = error_code.startswith for code in code_list: if startswith(code): diff --git a/src/flake8/utils.py b/src/flake8/utils.py index 29dc554..fe5d907 100644 --- a/src/flake8/utils.py +++ b/src/flake8/utils.py @@ -1,6 +1,7 @@ """Utility methods for flake8.""" import collections import fnmatch as _fnmatch +import functools import inspect import io import logging @@ -8,24 +9,33 @@ import os import platform import re import sys +import textwrap import tokenize -from typing import Callable, Dict, Generator, List, Optional, Pattern -from typing import Sequence, Set, Tuple, Union +from typing import Callable +from typing import Dict +from typing import Generator +from typing import List +from typing import Optional +from typing import Pattern +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union from flake8 import exceptions -from flake8._compat import lru_cache -if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2 +if TYPE_CHECKING: from flake8.plugins.manager import Plugin DIFF_HUNK_REGEXP = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$") COMMA_SEPARATED_LIST_RE = re.compile(r"[,\s]") LOCAL_PLUGIN_LIST_RE = re.compile(r"[,\t\n\r\f\v]") -string_types = (str, type(u"")) -def parse_comma_separated_list(value, regexp=COMMA_SEPARATED_LIST_RE): - # type: (str, Pattern[str]) -> List[str] +def parse_comma_separated_list( + value: str, regexp: Pattern[str] = COMMA_SEPARATED_LIST_RE +) -> List[str]: """Parse a comma-separated list. :param value: @@ -40,7 +50,7 @@ def parse_comma_separated_list(value, regexp=COMMA_SEPARATED_LIST_RE): :rtype: list """ - assert isinstance(value, string_types), value + assert isinstance(value, str), value separated = regexp.split(value) item_gen = (item.strip() for item in separated) @@ -59,8 +69,7 @@ _FILE_LIST_TOKEN_TYPES = [ ] -def _tokenize_files_to_codes_mapping(value): - # type: (str) -> List[_Token] +def _tokenize_files_to_codes_mapping(value: str) -> List[_Token]: tokens = [] i = 0 while i < len(value): @@ -77,8 +86,9 @@ def _tokenize_files_to_codes_mapping(value): return tokens -def parse_files_to_codes_mapping(value_): # noqa: C901 - # type: (Union[Sequence[str], str]) -> List[Tuple[str, List[str]]] +def parse_files_to_codes_mapping( # noqa: C901 + value_: Union[Sequence[str], str] +) -> List[Tuple[str, List[str]]]: """Parse a files-to-codes mapping. A files-to-codes mapping a sequence of values specified as @@ -88,22 +98,22 @@ def parse_files_to_codes_mapping(value_): # noqa: C901 :param value: String to be parsed and normalized. :type value: str """ - if not isinstance(value_, string_types): + if not isinstance(value_, str): value = "\n".join(value_) else: value = value_ - ret = [] # type: List[Tuple[str, List[str]]] + ret: List[Tuple[str, List[str]]] = [] if not value.strip(): return ret class State: seen_sep = True seen_colon = False - filenames = [] # type: List[str] - codes = [] # type: List[str] + filenames: List[str] = [] + codes: List[str] = [] - def _reset(): # type: () -> None + def _reset() -> None: if State.codes: for filename in State.filenames: ret.append((filename, State.codes)) @@ -112,16 +122,12 @@ def parse_files_to_codes_mapping(value_): # noqa: C901 State.filenames = [] State.codes = [] - def _unexpected_token(): # type: () -> exceptions.ExecutionError - def _indent(s): # type: (str) -> str - return " " + s.strip().replace("\n", "\n ") - + def _unexpected_token() -> exceptions.ExecutionError: return exceptions.ExecutionError( - "Expected `per-file-ignores` to be a mapping from file exclude " - "patterns to ignore codes.\n\n" - "Configured `per-file-ignores` setting:\n\n{}".format( - _indent(value) - ) + f"Expected `per-file-ignores` to be a mapping from file exclude " + f"patterns to ignore codes.\n\n" + f"Configured `per-file-ignores` setting:\n\n" + f"{textwrap.indent(value.strip(), ' ')}" ) for token in _tokenize_files_to_codes_mapping(value): @@ -155,8 +161,9 @@ def parse_files_to_codes_mapping(value_): # noqa: C901 return ret -def normalize_paths(paths, parent=os.curdir): - # type: (Sequence[str], str) -> List[str] +def normalize_paths( + paths: Sequence[str], parent: str = os.curdir +) -> List[str]: """Normalize a list of paths relative to a parent directory. :returns: @@ -168,8 +175,7 @@ def normalize_paths(paths, parent=os.curdir): return [normalize_path(p, parent) for p in paths] -def normalize_path(path, parent=os.curdir): - # type: (str, str) -> str +def normalize_path(path: str, parent: str = os.curdir) -> str: """Normalize a single-path. :returns: @@ -190,7 +196,9 @@ def normalize_path(path, parent=os.curdir): return path.rstrip(separator + alternate_separator) -def _stdin_get_value_py3(): # type: () -> str +@functools.lru_cache(maxsize=1) +def stdin_get_value() -> str: + """Get and cache it so plugins can use it.""" stdin_value = sys.stdin.buffer.read() fd = io.BytesIO(stdin_value) try: @@ -201,25 +209,12 @@ def _stdin_get_value_py3(): # type: () -> str return stdin_value.decode("utf-8") -@lru_cache(maxsize=1) -def stdin_get_value(): # type: () -> str - """Get and cache it so plugins can use it.""" - if sys.version_info < (3,): - return sys.stdin.read() - else: - return _stdin_get_value_py3() - - -def stdin_get_lines(): # type: () -> List[str] +def stdin_get_lines() -> List[str]: """Return lines of stdin split according to file splitting.""" - if sys.version_info < (3,): - return list(io.BytesIO(stdin_get_value())) - else: - return list(io.StringIO(stdin_get_value())) + return list(io.StringIO(stdin_get_value())) -def parse_unified_diff(diff=None): - # type: (Optional[str]) -> Dict[str, Set[int]] +def parse_unified_diff(diff: Optional[str] = None) -> Dict[str, Set[int]]: """Parse the unified diff passed on stdin. :returns: @@ -233,7 +228,7 @@ def parse_unified_diff(diff=None): number_of_rows = None current_path = None - parsed_paths = collections.defaultdict(set) # type: Dict[str, Set[int]] + parsed_paths: Dict[str, Set[int]] = collections.defaultdict(set) for line in diff.splitlines(): if number_of_rows: # NOTE(sigmavirus24): Below we use a slice because stdin may be @@ -291,8 +286,7 @@ def parse_unified_diff(diff=None): return parsed_paths -def is_windows(): - # type: () -> bool +def is_windows() -> bool: """Determine if we're running on Windows. :returns: @@ -303,8 +297,7 @@ def is_windows(): return os.name == "nt" -def is_using_stdin(paths): - # type: (List[str]) -> bool +def is_using_stdin(paths: List[str]) -> bool: """Determine if we're going to read from stdin. :param list paths: @@ -317,12 +310,14 @@ def is_using_stdin(paths): return "-" in paths -def _default_predicate(*args): # type: (*str) -> bool +def _default_predicate(*args: str) -> bool: return False -def filenames_from(arg, predicate=None): - # type: (str, Optional[Callable[[str], bool]]) -> Generator[str, None, None] # noqa: E501 +def filenames_from( + arg: str, predicate: Optional[Callable[[str], bool]] = None +) -> Generator[str, None, None]: + # noqa: E501 """Generate filenames from an argument. :param str arg: @@ -362,8 +357,7 @@ def filenames_from(arg, predicate=None): yield arg -def fnmatch(filename, patterns): - # type: (str, Sequence[str]) -> bool +def fnmatch(filename: str, patterns: Sequence[str]) -> bool: """Wrap :func:`fnmatch.fnmatch` to add some functionality. :param str filename: @@ -381,8 +375,7 @@ def fnmatch(filename, patterns): return any(_fnmatch.fnmatch(filename, pattern) for pattern in patterns) -def parameters_for(plugin): - # type: (Plugin) -> Dict[str, bool] +def parameters_for(plugin: "Plugin") -> Dict[str, bool]: """Return the parameters for the plugin. This will inspect the plugin and return either the function parameters @@ -404,24 +397,11 @@ def parameters_for(plugin): if is_class: # The plugin is a class func = plugin.plugin.__init__ - if sys.version_info < (3, 3): - argspec = inspect.getargspec(func) - start_of_optional_args = len(argspec[0]) - len(argspec[-1] or []) - parameter_names = argspec[0] - parameters = collections.OrderedDict( - [ - (name, position < start_of_optional_args) - for position, name in enumerate(parameter_names) - ] - ) - else: - parameters = collections.OrderedDict( - [ - (parameter.name, parameter.default is parameter.empty) - for parameter in inspect.signature(func).parameters.values() - if parameter.kind == parameter.POSITIONAL_OR_KEYWORD - ] - ) + parameters = { + parameter.name: parameter.default is parameter.empty + for parameter in inspect.signature(func).parameters.values() + if parameter.kind == parameter.POSITIONAL_OR_KEYWORD + } if is_class: parameters.pop("self", None) @@ -429,8 +409,12 @@ def parameters_for(plugin): return parameters -def matches_filename(path, patterns, log_message, logger): - # type: (str, Sequence[str], str, logging.Logger) -> bool +def matches_filename( + path: str, + patterns: Sequence[str], + log_message: str, + logger: logging.Logger, +) -> bool: """Use fnmatch to discern if a path exists in patterns. :param str path: @@ -462,7 +446,7 @@ def matches_filename(path, patterns, log_message, logger): return match -def get_python_version(): # type: () -> str +def get_python_version() -> str: """Find and format the python implementation and version. :returns: @@ -470,7 +454,7 @@ def get_python_version(): # type: () -> str :rtype: str """ - return "%s %s on %s" % ( + return "{} {} on {}".format( platform.python_implementation(), platform.python_version(), platform.system(), diff --git a/tests/fixtures/example-code/inline-ignores/E501.py b/tests/fixtures/example-code/inline-ignores/E501.py index 62e5c0c..64a5dca 100644 --- a/tests/fixtures/example-code/inline-ignores/E501.py +++ b/tests/fixtures/example-code/inline-ignores/E501.py @@ -1,3 +1,4 @@ -from some.module.that.has.nested.sub.modules import ClassWithVeryVeryVeryVeryLongName # noqa: E501,F401 +from some.module.that.has.nested.sub.modules import \ + ClassWithVeryVeryVeryVeryLongName # noqa: E501,F401 # ClassWithVeryVeryVeryVeryLongName() diff --git a/tests/integration/subdir/aplugin.py b/tests/integration/subdir/aplugin.py index bc5f812..266e8d0 100644 --- a/tests/integration/subdir/aplugin.py +++ b/tests/integration/subdir/aplugin.py @@ -1,7 +1,7 @@ """Module that is off sys.path by default, for testing local-plugin-paths.""" -class ExtensionTestPlugin2(object): +class ExtensionTestPlugin2: """Extension test plugin in its own directory.""" name = 'ExtensionTestPlugin2' diff --git a/tests/integration/test_checker.py b/tests/integration/test_checker.py index 836b543..1254bf0 100644 --- a/tests/integration/test_checker.py +++ b/tests/integration/test_checker.py @@ -1,5 +1,6 @@ """Integration tests for the checker submodule.""" -import mock +from unittest import mock + import pytest from flake8 import checker @@ -20,7 +21,7 @@ EXPECTED_RESULT_PHYSICAL_LINE = ( ) -class PluginClass(object): +class PluginClass: """Simple file plugin class yielding the expected report.""" name = 'test' diff --git a/tests/integration/test_main.py b/tests/integration/test_main.py index e2f58c5..10ccc54 100644 --- a/tests/integration/test_main.py +++ b/tests/integration/test_main.py @@ -1,8 +1,8 @@ """Integration tests for the main entrypoint of flake8.""" import json import os +from unittest import mock -import mock import pytest from flake8 import utils @@ -280,13 +280,13 @@ def test_obtaining_args_from_sys_argv_when_not_explicity_provided(capsys): def test_cli_config_option_respected(tmp_path): """Test --config is used.""" config = tmp_path / "flake8.ini" - config.write_text(u"""\ + config.write_text("""\ [flake8] ignore = F401 """) py_file = tmp_path / "t.py" - py_file.write_text(u"import os\n") + py_file.write_text("import os\n") _call_main(["--config", str(config), str(py_file)]) @@ -294,13 +294,13 @@ ignore = F401 def test_cli_isolated_overrides_config_option(tmp_path): """Test --isolated overrides --config.""" config = tmp_path / "flake8.ini" - config.write_text(u"""\ + config.write_text("""\ [flake8] ignore = F401 """) py_file = tmp_path / "t.py" - py_file.write_text(u"import os\n") + py_file.write_text("import os\n") _call_main(["--isolated", "--config", str(config), str(py_file)], retv=1) diff --git a/tests/integration/test_plugins.py b/tests/integration/test_plugins.py index 859fb69..1b6203e 100644 --- a/tests/integration/test_plugins.py +++ b/tests/integration/test_plugins.py @@ -1,12 +1,11 @@ """Integration tests for plugin loading.""" from flake8.main import application - LOCAL_PLUGIN_CONFIG = 'tests/fixtures/config_files/local-plugin.ini' LOCAL_PLUGIN_PATH_CONFIG = 'tests/fixtures/config_files/local-plugin-path.ini' -class ExtensionTestPlugin(object): +class ExtensionTestPlugin: """Extension test plugin.""" name = 'ExtensionTestPlugin' @@ -24,7 +23,7 @@ class ExtensionTestPlugin(object): parser.add_option('--anopt') -class ReportTestPlugin(object): +class ReportTestPlugin: """Report test plugin.""" name = 'ReportTestPlugin' diff --git a/tests/unit/test_application.py b/tests/unit/test_application.py index 51adefb..d675eaa 100644 --- a/tests/unit/test_application.py +++ b/tests/unit/test_application.py @@ -1,8 +1,8 @@ """Tests for the Application class.""" import argparse import sys +from unittest import mock -import mock import pytest from flake8.main import application as app diff --git a/tests/unit/test_base_formatter.py b/tests/unit/test_base_formatter.py index ff2df7c..c48adf2 100644 --- a/tests/unit/test_base_formatter.py +++ b/tests/unit/test_base_formatter.py @@ -1,7 +1,7 @@ """Tests for the BaseFormatter object.""" import argparse +from unittest import mock -import mock import pytest from flake8 import style_guide diff --git a/tests/unit/test_checker_manager.py b/tests/unit/test_checker_manager.py index d3e7e61..b94d0c5 100644 --- a/tests/unit/test_checker_manager.py +++ b/tests/unit/test_checker_manager.py @@ -1,7 +1,7 @@ """Tests for the Manager object for FileCheckers.""" import errno +from unittest import mock -import mock import pytest from flake8 import checker diff --git a/tests/unit/test_config_file_finder.py b/tests/unit/test_config_file_finder.py index d21b03d..d31a692 100644 --- a/tests/unit/test_config_file_finder.py +++ b/tests/unit/test_config_file_finder.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- """Tests for the ConfigFileFinder.""" import configparser import os +from unittest import mock -import mock import pytest from flake8.options import config diff --git a/tests/unit/test_debug.py b/tests/unit/test_debug.py index 6398cf9..bc31fec 100644 --- a/tests/unit/test_debug.py +++ b/tests/unit/test_debug.py @@ -1,5 +1,6 @@ """Tests for our debugging module.""" -import mock +from unittest import mock + import pytest from flake8.main import debug diff --git a/tests/unit/test_file_checker.py b/tests/unit/test_file_checker.py index c4ee2bf..2b80919 100644 --- a/tests/unit/test_file_checker.py +++ b/tests/unit/test_file_checker.py @@ -1,5 +1,6 @@ """Unit tests for the FileChecker class.""" -import mock +from unittest import mock + import pytest import flake8 @@ -50,7 +51,7 @@ def test_nonexistent_file(): def test_raises_exception_on_failed_plugin(tmp_path, default_options): """Checks that a failing plugin results in PluginExecutionFailed.""" foobar = tmp_path / 'foobar.py' - foobar.write_text(u"I exist!") # Create temp file + foobar.write_text("I exist!") # Create temp file plugin = { "name": "failure", "plugin_name": "failure", # Both are necessary diff --git a/tests/unit/test_file_processor.py b/tests/unit/test_file_processor.py index 0215ddf..ca7f4f6 100644 --- a/tests/unit/test_file_processor.py +++ b/tests/unit/test_file_processor.py @@ -1,8 +1,8 @@ """Tests for the FileProcessor class.""" import ast import tokenize +from unittest import mock -import mock import pytest from flake8 import processor @@ -46,7 +46,7 @@ def test_read_lines_unknown_encoding(tmpdir, default_options): @pytest.mark.parametrize('first_line', [ '\xEF\xBB\xBF"""Module docstring."""\n', - u'\uFEFF"""Module docstring."""\n', + '\uFEFF"""Module docstring."""\n', ]) def test_strip_utf_bom(first_line, default_options): r"""Verify that we strip '\xEF\xBB\xBF' from the first line.""" @@ -58,7 +58,7 @@ def test_strip_utf_bom(first_line, default_options): @pytest.mark.parametrize('lines, expected', [ (['\xEF\xBB\xBF"""Module docstring."""\n'], False), - ([u'\uFEFF"""Module docstring."""\n'], False), + (['\uFEFF"""Module docstring."""\n'], False), (['#!/usr/bin/python', '# flake8 is great', 'a = 1'], False), (['#!/usr/bin/python', '# flake8: noqa', 'a = 1'], True), (['#!/usr/bin/python', '# flake8:noqa', 'a = 1'], True), @@ -130,7 +130,7 @@ def test_noqa_line_for(default_options): ]) for i in range(1, 4): - assert file_processor.noqa_line_for(i) == 'Line {0}\n'.format(i) + assert file_processor.noqa_line_for(i) == f'Line {i}\n' def test_noqa_line_for_continuation(default_options): @@ -182,7 +182,7 @@ def test_next_line(default_options): ]) for i in range(1, 4): - assert file_processor.next_line() == 'Line {}'.format(i) + assert file_processor.next_line() == f'Line {i}' assert file_processor.line_number == i diff --git a/tests/unit/test_get_local_plugins.py b/tests/unit/test_get_local_plugins.py index ec11998..0817550 100644 --- a/tests/unit/test_get_local_plugins.py +++ b/tests/unit/test_get_local_plugins.py @@ -1,5 +1,5 @@ """Tests for get_local_plugins.""" -import mock +from unittest import mock from flake8.options import config diff --git a/tests/unit/test_legacy_api.py b/tests/unit/test_legacy_api.py index 0ec839f..a1e7d50 100644 --- a/tests/unit/test_legacy_api.py +++ b/tests/unit/test_legacy_api.py @@ -1,8 +1,8 @@ """Tests for Flake8's legacy API.""" import argparse import os.path +from unittest import mock -import mock import pytest from flake8.api import legacy as api diff --git a/tests/unit/test_merged_config_parser.py b/tests/unit/test_merged_config_parser.py index d446ad8..8def01b 100644 --- a/tests/unit/test_merged_config_parser.py +++ b/tests/unit/test_merged_config_parser.py @@ -1,7 +1,7 @@ """Unit tests for flake8.options.config.MergedConfigParser.""" import os +from unittest import mock -import mock import pytest from flake8.options import config diff --git a/tests/unit/test_option.py b/tests/unit/test_option.py index ba6b672..fc0e288 100644 --- a/tests/unit/test_option.py +++ b/tests/unit/test_option.py @@ -1,7 +1,7 @@ """Unit tests for flake8.options.manager.Option.""" import functools +from unittest import mock -import mock import pytest from flake8.options import manager diff --git a/tests/unit/test_option_manager.py b/tests/unit/test_option_manager.py index 09714b9..4dcbaa8 100644 --- a/tests/unit/test_option_manager.py +++ b/tests/unit/test_option_manager.py @@ -1,8 +1,8 @@ """Unit tests for flake.options.manager.OptionManager.""" import argparse import os +from unittest import mock -import mock import pytest from flake8 import utils diff --git a/tests/unit/test_plugin.py b/tests/unit/test_plugin.py index 402eac8..204a4c2 100644 --- a/tests/unit/test_plugin.py +++ b/tests/unit/test_plugin.py @@ -1,7 +1,7 @@ """Tests for flake8.plugins.manager.Plugin.""" import argparse +from unittest import mock -import mock import pytest from flake8 import exceptions diff --git a/tests/unit/test_plugin_manager.py b/tests/unit/test_plugin_manager.py index 9ad6aba..55c3e24 100644 --- a/tests/unit/test_plugin_manager.py +++ b/tests/unit/test_plugin_manager.py @@ -1,5 +1,5 @@ """Tests for flake8.plugins.manager.PluginManager.""" -import mock +from unittest import mock from flake8._compat import importlib_metadata from flake8.plugins import manager diff --git a/tests/unit/test_plugin_type_manager.py b/tests/unit/test_plugin_type_manager.py index 939aa8c..9d00b78 100644 --- a/tests/unit/test_plugin_type_manager.py +++ b/tests/unit/test_plugin_type_manager.py @@ -1,5 +1,6 @@ """Tests for flake8.plugins.manager.PluginTypeManager.""" -import mock +from unittest import mock + import pytest from flake8 import exceptions diff --git a/tests/unit/test_statistics.py b/tests/unit/test_statistics.py index 16896f0..6aa4e6f 100644 --- a/tests/unit/test_statistics.py +++ b/tests/unit/test_statistics.py @@ -111,8 +111,8 @@ def test_statistic_for_retrieves_more_than_one_value(): """Show this works for more than a couple statistic values.""" aggregator = stats.Statistics() for i in range(50): - aggregator.record(make_error(code='E1{:02d}'.format(i))) - aggregator.record(make_error(code='W2{:02d}'.format(i))) + aggregator.record(make_error(code=f'E1{i:02d}')) + aggregator.record(make_error(code=f'W2{i:02d}')) statistics = list(aggregator.statistics_for('E')) assert len(statistics) == 50 diff --git a/tests/unit/test_style_guide.py b/tests/unit/test_style_guide.py index 5503d7e..ac781f3 100644 --- a/tests/unit/test_style_guide.py +++ b/tests/unit/test_style_guide.py @@ -1,7 +1,7 @@ """Tests for the flake8.style_guide.StyleGuide class.""" import argparse +from unittest import mock -import mock import pytest from flake8 import statistics diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 4b89484..4304057 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -3,8 +3,8 @@ import io import logging import os import sys +from unittest import mock -import mock import pytest from flake8 import exceptions @@ -134,7 +134,7 @@ def test_normalize_path(value, expected): (["flake8", "pep8", "pyflakes", "mccabe"], ["flake8", "pep8", "pyflakes", "mccabe"]), (["../flake8", "../pep8", "../pyflakes", "../mccabe"], - [os.path.abspath("../" + p) for p in RELATIVE_PATHS]), + [os.path.abspath(f"../{p}") for p in RELATIVE_PATHS]), ]) def test_normalize_paths(value, expected): """Verify we normalizes a sequence of paths provided to the tool.""" @@ -242,7 +242,7 @@ def test_filenames_from_exclude_doesnt_exclude_directory_names(tmpdir): def test_parameters_for_class_plugin(): """Verify that we can retrieve the parameters for a class plugin.""" - class FakeCheck(object): + class FakeCheck: def __init__(self, tree): raise NotImplementedError @@ -268,7 +268,7 @@ def test_parameters_for_function_plugin(): def read_diff_file(filename): """Read the diff file in its entirety.""" - with open(filename, 'r') as fd: + with open(filename) as fd: content = fd.read() return content @@ -308,7 +308,6 @@ def test_matches_filename_for_excluding_dotfiles(): assert not utils.matches_filename('..', ('.*',), '', logger) -@pytest.mark.xfail(sys.version_info < (3,), reason='py3+ only behaviour') def test_stdin_get_value_crlf(): """Ensure that stdin is normalized from crlf to lf.""" stdin = io.TextIOWrapper(io.BytesIO(b'1\r\n2\r\n'), 'UTF-8') diff --git a/tests/unit/test_violation.py b/tests/unit/test_violation.py index a3a56f0..284cd2a 100644 --- a/tests/unit/test_violation.py +++ b/tests/unit/test_violation.py @@ -1,5 +1,6 @@ """Tests for the flake8.style_guide.Violation class.""" -import mock +from unittest import mock + import pytest from flake8 import style_guide diff --git a/tox.ini b/tox.ini index 85423ec..821c5ce 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,9 @@ [tox] minversion=2.3.1 -envlist = py27,py35,py36,py37,py38,flake8,linters,docs +envlist = py36,py37,py38,flake8,linters,docs [testenv] deps = - mock>=2.0.0 pytest!=3.0.5,!=5.2.3 coverage commands = @@ -14,14 +13,8 @@ commands = # ensure 100% coverage of tests coverage report --fail-under 100 --include tests/* -[testenv:venv] -deps = - . -commands = {posargs} - # Dogfood our current master version [testenv:dogfood] -basepython = python3 skip_install = true deps = wheel @@ -33,20 +26,17 @@ commands = # Linters [testenv:flake8] -basepython = python3 skip_install = true deps = flake8 flake8-bugbear flake8-docstrings>=1.3.1 - flake8-import-order>=0.9 flake8-typing-imports>=1.1 pep8-naming commands = flake8 src/flake8/ tests/ setup.py [testenv:pylint] -basepython = python3 skip_install = true deps = pyflakes @@ -55,7 +45,6 @@ commands = pylint src/flake8 [testenv:doc8] -basepython = python3 skip_install = true deps = sphinx @@ -64,14 +53,12 @@ commands = doc8 docs/source/ [testenv:pre-commit] -basepython = python3 skip_install = true deps = pre-commit commands = pre-commit run --all-files --show-diff-on-failure [testenv:bandit] -basepython = python3 skip_install = true deps = bandit @@ -79,7 +66,6 @@ commands = bandit -r src/flake8/ -c .bandit.yml [testenv:linters] -basepython = python3 skip_install = true deps = {[testenv:flake8]deps} @@ -96,7 +82,6 @@ commands = # Documentation [testenv:docs] -basepython = python3 deps = -rdocs/source/requirements.txt commands = @@ -104,7 +89,6 @@ commands = sphinx-build -E -W -c docs/source/ -b man docs/source/ docs/build/man [testenv:serve-docs] -basepython = python3 skip_install = true changedir = docs/build/html deps = @@ -112,7 +96,6 @@ commands = python -m http.server {posargs} [testenv:readme] -basepython = python3 deps = readme_renderer commands = @@ -120,7 +103,6 @@ commands = # Release tooling [testenv:build] -basepython = python3 skip_install = true deps = wheel @@ -129,7 +111,6 @@ commands = python setup.py -q sdist bdist_wheel [testenv:release] -basepython = python3 skip_install = true deps = {[testenv:build]deps} @@ -159,5 +140,3 @@ exclude = .cache, .eggs max-complexity = 10 -import-order-style = google -application-import-names = flake8