Merge pull request #1973 from PyCQA/py39-plus

py39+
This commit is contained in:
Anthony Sottile 2025-03-29 15:38:33 -04:00 committed by GitHub
commit a7e8f6250c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 64 additions and 78 deletions

View file

@ -15,9 +15,6 @@ jobs:
- os: ubuntu-latest - os: ubuntu-latest
python: pypy-3.9 python: pypy-3.9
toxenv: py toxenv: py
- os: ubuntu-latest
python: 3.8
toxenv: py
- os: ubuntu-latest - os: ubuntu-latest
python: 3.9 python: 3.9
toxenv: py toxenv: py
@ -28,11 +25,14 @@ jobs:
python: '3.11' python: '3.11'
toxenv: py toxenv: py
- os: ubuntu-latest - os: ubuntu-latest
python: '3.12-dev' python: '3.12'
toxenv: py
- os: ubuntu-latest
python: '3.13'
toxenv: py toxenv: py
# windows # windows
- os: windows-latest - os: windows-latest
python: 3.8 python: 3.9
toxenv: py toxenv: py
# misc # misc
- os: ubuntu-latest - os: ubuntu-latest

View file

@ -12,19 +12,19 @@ repos:
hooks: hooks:
- id: setup-cfg-fmt - id: setup-cfg-fmt
- repo: https://github.com/asottile/reorder-python-imports - repo: https://github.com/asottile/reorder-python-imports
rev: v3.12.0 rev: v3.14.0
hooks: hooks:
- id: reorder-python-imports - id: reorder-python-imports
args: [ args: [
--application-directories, '.:src', --application-directories, '.:src',
--py38-plus, --py39-plus,
--add-import, 'from __future__ import annotations', --add-import, 'from __future__ import annotations',
] ]
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.15.0 rev: v3.19.1
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py38-plus] args: [--py39-plus]
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 23.12.1 rev: 23.12.1
hooks: hooks:
@ -35,7 +35,7 @@ repos:
hooks: hooks:
- id: flake8 - id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0 rev: v1.15.0
hooks: hooks:
- id: mypy - id: mypy
exclude: ^(docs/|example-plugin/) exclude: ^(docs/|example-plugin/)

View file

@ -3,9 +3,9 @@ from __future__ import annotations
import inspect import inspect
import os.path import os.path
from collections.abc import Generator
from typing import Any from typing import Any
from typing import Callable from typing import Callable
from typing import Generator
from typing import NamedTuple from typing import NamedTuple
import pycodestyle import pycodestyle
@ -42,7 +42,7 @@ class Call(NamedTuple):
return cls(func.__name__, inspect.isgeneratorfunction(func), params) return cls(func.__name__, inspect.isgeneratorfunction(func), params)
def lines() -> Generator[str, None, None]: def lines() -> Generator[str]:
logical = [] logical = []
physical = [] physical = []
@ -58,8 +58,8 @@ def lines() -> Generator[str, None, None]:
yield "# fmt: off" yield "# fmt: off"
yield "from __future__ import annotations" yield "from __future__ import annotations"
yield "" yield ""
yield "from collections.abc import Generator"
yield "from typing import Any" yield "from typing import Any"
yield "from typing import Generator"
yield "" yield ""
imports = sorted(call.name for call in logical + physical) imports = sorted(call.name for call in logical + physical)
for name in imports: for name in imports:
@ -71,7 +71,7 @@ def lines() -> Generator[str, None, None]:
logical_params = {param for call in logical for param in call.params} logical_params = {param for call in logical for param in call.params}
for param in sorted(logical_params): for param in sorted(logical_params):
yield f" {param}: Any," yield f" {param}: Any,"
yield ") -> Generator[tuple[int, str], None, None]:" yield ") -> Generator[tuple[int, str]]:"
yield ' """Run pycodestyle logical checks."""' yield ' """Run pycodestyle logical checks."""'
for call in sorted(logical): for call in sorted(logical):
yield call.to_src() yield call.to_src()
@ -82,7 +82,7 @@ def lines() -> Generator[str, None, None]:
physical_params = {param for call in physical for param in call.params} physical_params = {param for call in physical for param in call.params}
for param in sorted(physical_params): for param in sorted(physical_params):
yield f" {param}: Any," yield f" {param}: Any,"
yield ") -> Generator[tuple[int, str], None, None]:" yield ") -> Generator[tuple[int, str]]:"
yield ' """Run pycodestyle physical checks."""' yield ' """Run pycodestyle physical checks."""'
for call in sorted(physical): for call in sorted(physical):
yield call.to_src() yield call.to_src()

View file

@ -81,9 +81,9 @@ for users.
Before releasing, the following tox test environments must pass: Before releasing, the following tox test environments must pass:
- Python 3.8 (a.k.a., ``tox -e py38``) - Python 3.9 (a.k.a., ``tox -e py39``)
- Python 3.12 (a.k.a., ``tox -e py312``) - Python 3.13 (a.k.a., ``tox -e py313``)
- PyPy 3 (a.k.a., ``tox -e pypy3``) - PyPy 3 (a.k.a., ``tox -e pypy3``)

View file

@ -14,25 +14,25 @@ like so:
Where you simply allow the shell running in your terminal to locate |Flake8|. 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 In some cases, though, you may have installed |Flake8| for multiple versions
of Python (e.g., Python 3.8 and Python 3.9) and you need to call a specific of Python (e.g., Python 3.13 and Python 3.14) and you need to call a specific
version. In that case, you will have much better results using: version. In that case, you will have much better results using:
.. prompt:: bash .. prompt:: bash
python3.8 -m flake8 python3.13 -m flake8
Or Or
.. prompt:: bash .. prompt:: bash
python3.9 -m flake8 python3.14 -m flake8
Since that will tell the correct version of Python to run |Flake8|. Since that will tell the correct version of Python to run |Flake8|.
.. note:: .. note::
Installing |Flake8| once will not install it on both Python 3.8 and Installing |Flake8| once will not install it on both Python 3.13 and
Python 3.9. It will only install it for the version of Python that Python 3.14. It will only install it for the version of Python that
is running pip. is running pip.
It is also possible to specify command-line options directly to |Flake8|: It is also possible to specify command-line options directly to |Flake8|:

View file

@ -23,8 +23,6 @@ setuptools.setup(
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Software Development :: Quality Assurance", "Topic :: Software Development :: Quality Assurance",
], ],

View file

@ -31,7 +31,7 @@ install_requires =
mccabe>=0.7.0,<0.8.0 mccabe>=0.7.0,<0.8.0
pycodestyle>=2.12.0,<2.13.0 pycodestyle>=2.12.0,<2.13.0
pyflakes>=3.2.0,<3.3.0 pyflakes>=3.2.0,<3.3.0
python_requires = >=3.8.1 python_requires = >=3.9
package_dir = package_dir =
=src =src

View file

@ -9,12 +9,10 @@ import multiprocessing.pool
import operator import operator
import signal import signal
import tokenize import tokenize
from collections.abc import Generator
from collections.abc import Sequence
from typing import Any from typing import Any
from typing import Generator
from typing import List
from typing import Optional from typing import Optional
from typing import Sequence
from typing import Tuple
from flake8 import defaults from flake8 import defaults
from flake8 import exceptions from flake8 import exceptions
@ -27,7 +25,7 @@ from flake8.plugins.finder import Checkers
from flake8.plugins.finder import LoadedPlugin from flake8.plugins.finder import LoadedPlugin
from flake8.style_guide import StyleGuideManager from flake8.style_guide import StyleGuideManager
Results = List[Tuple[str, int, int, str, Optional[str]]] Results = list[tuple[str, int, int, str, Optional[str]]]
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -53,7 +51,7 @@ _mp_options: argparse.Namespace
@contextlib.contextmanager @contextlib.contextmanager
def _mp_prefork( def _mp_prefork(
plugins: Checkers, options: argparse.Namespace plugins: Checkers, options: argparse.Namespace
) -> Generator[None, None, None]: ) -> Generator[None]:
# we can save significant startup work w/ `fork` multiprocessing # we can save significant startup work w/ `fork` multiprocessing
global _mp_plugins, _mp_options global _mp_plugins, _mp_options
_mp_plugins, _mp_options = plugins, options _mp_plugins, _mp_options = plugins, options

View file

@ -3,9 +3,9 @@ from __future__ import annotations
import logging import logging
import os.path import os.path
from collections.abc import Generator
from collections.abc import Sequence
from typing import Callable from typing import Callable
from typing import Generator
from typing import Sequence
from flake8 import utils from flake8 import utils
@ -16,7 +16,7 @@ def _filenames_from(
arg: str, arg: str,
*, *,
predicate: Callable[[str], bool], predicate: Callable[[str], bool],
) -> Generator[str, None, None]: ) -> Generator[str]:
"""Generate filenames from an argument. """Generate filenames from an argument.
:param arg: :param arg:
@ -55,7 +55,7 @@ def expand_paths(
stdin_display_name: str, stdin_display_name: str,
filename_patterns: Sequence[str], filename_patterns: Sequence[str],
exclude: Sequence[str], exclude: Sequence[str],
) -> Generator[str, None, None]: ) -> Generator[str]:
"""Expand out ``paths`` from commandline to the lintable files.""" """Expand out ``paths`` from commandline to the lintable files."""
if not paths: if not paths:
paths = ["."] paths = ["."]

View file

@ -5,7 +5,7 @@ import argparse
import json import json
import logging import logging
import time import time
from typing import Sequence from collections.abc import Sequence
import flake8 import flake8
from flake8 import checker from flake8 import checker

View file

@ -2,7 +2,7 @@
from __future__ import annotations from __future__ import annotations
import sys import sys
from typing import Sequence from collections.abc import Sequence
from flake8.main import application from flake8.main import application

View file

@ -8,7 +8,7 @@ from __future__ import annotations
import argparse import argparse
import configparser import configparser
import logging import logging
from typing import Sequence from collections.abc import Sequence
from flake8.options import config from flake8.options import config
from flake8.options.manager import OptionManager from flake8.options.manager import OptionManager

View file

@ -5,9 +5,9 @@ import argparse
import enum import enum
import functools import functools
import logging import logging
from collections.abc import Sequence
from typing import Any from typing import Any
from typing import Callable from typing import Callable
from typing import Sequence
from flake8 import utils from flake8 import utils
from flake8.plugins.finder import Plugins from flake8.plugins.finder import Plugins

View file

@ -2,7 +2,7 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
from typing import Sequence from collections.abc import Sequence
import flake8 import flake8
from flake8.main import options from flake8.main import options

View file

@ -7,9 +7,9 @@ import inspect
import itertools import itertools
import logging import logging
import sys import sys
from collections.abc import Generator
from collections.abc import Iterable
from typing import Any from typing import Any
from typing import Generator
from typing import Iterable
from typing import NamedTuple from typing import NamedTuple
from flake8 import utils from flake8 import utils
@ -68,7 +68,7 @@ class Plugins(NamedTuple):
reporters: dict[str, LoadedPlugin] reporters: dict[str, LoadedPlugin]
disabled: list[LoadedPlugin] disabled: list[LoadedPlugin]
def all_plugins(self) -> Generator[LoadedPlugin, None, None]: def all_plugins(self) -> Generator[LoadedPlugin]:
"""Return an iterator over all :class:`LoadedPlugin`s.""" """Return an iterator over all :class:`LoadedPlugin`s."""
yield from self.checkers.tree yield from self.checkers.tree
yield from self.checkers.logical_line yield from self.checkers.logical_line
@ -151,7 +151,7 @@ def _flake8_plugins(
eps: Iterable[importlib.metadata.EntryPoint], eps: Iterable[importlib.metadata.EntryPoint],
name: str, name: str,
version: str, version: str,
) -> Generator[Plugin, None, None]: ) -> Generator[Plugin]:
pyflakes_meta = importlib.metadata.distribution("pyflakes").metadata pyflakes_meta = importlib.metadata.distribution("pyflakes").metadata
pycodestyle_meta = importlib.metadata.distribution("pycodestyle").metadata pycodestyle_meta = importlib.metadata.distribution("pycodestyle").metadata
@ -173,7 +173,7 @@ def _flake8_plugins(
yield Plugin(name, version, ep) yield Plugin(name, version, ep)
def _find_importlib_plugins() -> Generator[Plugin, None, None]: def _find_importlib_plugins() -> Generator[Plugin]:
# some misconfigured pythons (RHEL) have things on `sys.path` twice # some misconfigured pythons (RHEL) have things on `sys.path` twice
seen = set() seen = set()
for dist in importlib.metadata.distributions(): for dist in importlib.metadata.distributions():
@ -212,7 +212,7 @@ def _find_importlib_plugins() -> Generator[Plugin, None, None]:
def _find_local_plugins( def _find_local_plugins(
cfg: configparser.RawConfigParser, cfg: configparser.RawConfigParser,
) -> Generator[Plugin, None, None]: ) -> Generator[Plugin]:
for plugin_type in ("extension", "report"): for plugin_type in ("extension", "report"):
group = f"flake8.{plugin_type}" group = f"flake8.{plugin_type}"
for plugin_s in utils.parse_comma_separated_list( for plugin_s in utils.parse_comma_separated_list(

View file

@ -2,8 +2,8 @@
# fmt: off # fmt: off
from __future__ import annotations from __future__ import annotations
from collections.abc import Generator
from typing import Any from typing import Any
from typing import Generator
from pycodestyle import ambiguous_identifier as _ambiguous_identifier from pycodestyle import ambiguous_identifier as _ambiguous_identifier
from pycodestyle import bare_except as _bare_except from pycodestyle import bare_except as _bare_except
@ -55,7 +55,7 @@ def pycodestyle_logical(
previous_unindented_logical_line: Any, previous_unindented_logical_line: Any,
tokens: Any, tokens: Any,
verbose: Any, verbose: Any,
) -> Generator[tuple[int, str], None, None]: ) -> Generator[tuple[int, str]]:
"""Run pycodestyle logical checks.""" """Run pycodestyle logical checks."""
yield from _ambiguous_identifier(logical_line, tokens) yield from _ambiguous_identifier(logical_line, tokens)
yield from _bare_except(logical_line, noqa) yield from _bare_except(logical_line, noqa)
@ -93,7 +93,7 @@ def pycodestyle_physical(
noqa: Any, noqa: Any,
physical_line: Any, physical_line: Any,
total_lines: Any, total_lines: Any,
) -> Generator[tuple[int, str], None, None]: ) -> Generator[tuple[int, str]]:
"""Run pycodestyle physical checks.""" """Run pycodestyle physical checks."""
ret = _maximum_line_length(physical_line, max_line_length, multiline, line_number, noqa) # noqa: E501 ret = _maximum_line_length(physical_line, max_line_length, multiline, line_number, noqa) # noqa: E501
if ret is not None: if ret is not None:

View file

@ -4,8 +4,8 @@ from __future__ import annotations
import argparse import argparse
import ast import ast
import logging import logging
from collections.abc import Generator
from typing import Any from typing import Any
from typing import Generator
import pyflakes.checker import pyflakes.checker
@ -97,7 +97,7 @@ class FlakesChecker(pyflakes.checker.Checker):
cls.builtIns = cls.builtIns.union(options.builtins) cls.builtIns = cls.builtIns.union(options.builtins)
cls.with_doctest = options.doctests cls.with_doctest = options.doctests
def run(self) -> Generator[tuple[int, int, str, type[Any]], None, None]: def run(self) -> Generator[tuple[int, int, str, type[Any]]]:
"""Run the plugin.""" """Run the plugin."""
for message in self.messages: for message in self.messages:
col = getattr(message, "col", 0) col = getattr(message, "col", 0)

View file

@ -6,10 +6,8 @@ import ast
import functools import functools
import logging import logging
import tokenize import tokenize
from collections.abc import Generator
from typing import Any from typing import Any
from typing import Generator
from typing import List
from typing import Tuple
from flake8 import defaults from flake8 import defaults
from flake8 import utils from flake8 import utils
@ -24,8 +22,8 @@ SKIP_TOKENS = frozenset(
[tokenize.NL, tokenize.NEWLINE, tokenize.INDENT, tokenize.DEDENT] [tokenize.NL, tokenize.NEWLINE, tokenize.INDENT, tokenize.DEDENT]
) )
_LogicalMapping = List[Tuple[int, Tuple[int, int]]] _LogicalMapping = list[tuple[int, tuple[int, int]]]
_Logical = Tuple[List[str], List[str], _LogicalMapping] _Logical = tuple[list[str], list[str], _LogicalMapping]
class FileProcessor: class FileProcessor:
@ -127,9 +125,7 @@ class FileProcessor:
"""Signal the beginning of an fstring.""" """Signal the beginning of an fstring."""
self._fstring_start = lineno self._fstring_start = lineno
def multiline_string( def multiline_string(self, token: tokenize.TokenInfo) -> Generator[str]:
self, token: tokenize.TokenInfo
) -> Generator[str, None, None]:
"""Iterate through the lines of a multiline string.""" """Iterate through the lines of a multiline string."""
if token.type == FSTRING_END: # pragma: >=3.12 cover if token.type == FSTRING_END: # pragma: >=3.12 cover
start = self._fstring_start start = self._fstring_start
@ -210,7 +206,7 @@ class FileProcessor:
brace_offset = text.count("{") + text.count("}") brace_offset = text.count("{") + text.count("}")
text = "x" * (len(text) + brace_offset) text = "x" * (len(text) + brace_offset)
end = (end[0], end[1] + brace_offset) end = (end[0], end[1] + brace_offset)
if previous_row: if previous_row is not None and previous_column is not None:
(start_row, start_column) = start (start_row, start_column) = start
if previous_row != start_row: if previous_row != start_row:
row_index = previous_row - 1 row_index = previous_row - 1
@ -263,7 +259,7 @@ class FileProcessor:
) )
return ret return ret
def generate_tokens(self) -> Generator[tokenize.TokenInfo, None, None]: def generate_tokens(self) -> Generator[tokenize.TokenInfo]:
"""Tokenize the file and yield the tokens.""" """Tokenize the file and yield the tokens."""
for token in tokenize.generate_tokens(self.next_line): for token in tokenize.generate_tokens(self.next_line):
if token[2][0] > self.total_lines: if token[2][0] > self.total_lines:

View file

@ -1,7 +1,7 @@
"""Statistic collection logic for Flake8.""" """Statistic collection logic for Flake8."""
from __future__ import annotations from __future__ import annotations
from typing import Generator from collections.abc import Generator
from typing import NamedTuple from typing import NamedTuple
from flake8.violation import Violation from flake8.violation import Violation
@ -36,7 +36,7 @@ class Statistics:
def statistics_for( def statistics_for(
self, prefix: str, filename: str | None = None self, prefix: str, filename: str | None = None
) -> Generator[Statistic, None, None]: ) -> Generator[Statistic]:
"""Generate statistics for the prefix and filename. """Generate statistics for the prefix and filename.
If you have a :class:`Statistics` object that has recorded errors, If you have a :class:`Statistics` object that has recorded errors,

View file

@ -7,8 +7,8 @@ import copy
import enum import enum
import functools import functools
import logging import logging
from typing import Generator from collections.abc import Generator
from typing import Sequence from collections.abc import Sequence
from flake8 import defaults from flake8 import defaults
from flake8 import statistics from flake8 import statistics
@ -225,13 +225,11 @@ class StyleGuideManager:
*self.populate_style_guides_with(options), *self.populate_style_guides_with(options),
] ]
self.style_guide_for = functools.lru_cache(maxsize=None)( self.style_guide_for = functools.cache(self._style_guide_for)
self._style_guide_for
)
def populate_style_guides_with( def populate_style_guides_with(
self, options: argparse.Namespace self, options: argparse.Namespace
) -> Generator[StyleGuide, None, None]: ) -> Generator[StyleGuide]:
"""Generate style guides from the per-file-ignores option. """Generate style guides from the per-file-ignores option.
:param options: :param options:
@ -253,9 +251,7 @@ class StyleGuideManager:
) )
@contextlib.contextmanager @contextlib.contextmanager
def processing_file( def processing_file(self, filename: str) -> Generator[StyleGuide]:
self, filename: str
) -> Generator[StyleGuide, None, None]:
"""Record the fact that we're processing the file's results.""" """Record the fact that we're processing the file's results."""
guide = self.style_guide_for(filename) guide = self.style_guide_for(filename)
with guide.processing_file(filename): with guide.processing_file(filename):
@ -338,9 +334,7 @@ class StyleGuide:
) )
@contextlib.contextmanager @contextlib.contextmanager
def processing_file( def processing_file(self, filename: str) -> Generator[StyleGuide]:
self, filename: str
) -> Generator[StyleGuide, None, None]:
"""Record the fact that we're processing the file's results.""" """Record the fact that we're processing the file's results."""
self.formatter.beginning(filename) self.formatter.beginning(filename)
yield self yield self

View file

@ -11,9 +11,9 @@ import re
import sys import sys
import textwrap import textwrap
import tokenize import tokenize
from collections.abc import Sequence
from re import Pattern
from typing import NamedTuple from typing import NamedTuple
from typing import Pattern
from typing import Sequence
from flake8 import exceptions from flake8 import exceptions

View file

@ -4,7 +4,7 @@ from __future__ import annotations
import functools import functools
import linecache import linecache
import logging import logging
from typing import Match from re import Match
from typing import NamedTuple from typing import NamedTuple
from flake8 import defaults from flake8 import defaults