From 37e61a57634c47b6fd42e411702933020f6a37fb Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Tue, 4 Jul 2023 22:28:55 -0700 Subject: [PATCH 1/4] Drop support for Python 3.7 --- .github/workflows/fuzz.yml | 2 +- .github/workflows/test.yml | 2 +- docs/integrations/editors.md | 54 ------------------ mypy.ini | 2 +- pyproject.toml | 10 +--- src/black/_width_table.py | 5 +- src/black/brackets.py | 5 +- src/black/comments.py | 5 +- src/black/mode.py | 5 +- src/black/nodes.py | 5 +- src/black/parsing.py | 100 +++++---------------------------- src/black/strings.py | 5 +- src/black/trans.py | 5 +- src/blib2to3/pgen2/token.py | 5 +- src/blib2to3/pgen2/tokenize.py | 5 +- 15 files changed, 29 insertions(+), 186 deletions(-) diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 373e1500ee9..4439148a1c7 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -22,7 +22,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 608c58af2ee..92d7d411510 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,7 +31,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "pypy-3.7", "pypy-3.8"] + python-version: ["3.8", "3.9", "3.10", "3.11", "pypy-3.8"] os: [ubuntu-latest, macOS-latest, windows-latest] steps: diff --git a/docs/integrations/editors.md b/docs/integrations/editors.md index 74c6a283ab8..ff563068e79 100644 --- a/docs/integrations/editors.md +++ b/docs/integrations/editors.md @@ -334,60 +334,6 @@ To run _Black_ on a key press (e.g. F9 below), add this: nnoremap :Black ``` -#### Troubleshooting - -**How to get Vim with Python 3.6?** On Ubuntu 17.10 Vim comes with Python 3.6 by -default. On macOS with Homebrew run: `brew install vim`. When building Vim from source, -use: `./configure --enable-python3interp=yes`. There's many guides online how to do -this. - -**I get an import error when using _Black_ from a virtual environment**: If you get an -error message like this: - -```text -Traceback (most recent call last): - File "", line 63, in - File "/home/gui/.vim/black/lib/python3.7/site-packages/black.py", line 45, in - from typed_ast import ast3, ast27 - File "/home/gui/.vim/black/lib/python3.7/site-packages/typed_ast/ast3.py", line 40, in - from typed_ast import _ast3 -ImportError: /home/gui/.vim/black/lib/python3.7/site-packages/typed_ast/_ast3.cpython-37m-x86_64-linux-gnu.so: undefined symbool: PyExc_KeyboardInterrupt -``` - -Then you need to install `typed_ast` directly from the source code. The error happens -because `pip` will download [Python wheels](https://pythonwheels.com/) if they are -available. Python wheels are a new standard of distributing Python packages and packages -that have Cython and extensions written in C are already compiled, so the installation -is much more faster. The problem here is that somehow the Python environment inside Vim -does not match with those already compiled C extensions and these kind of errors are the -result. Luckily there is an easy fix: installing the packages from the source code. - -The package that causes problems is: - -- [typed-ast](https://pypi.org/project/typed-ast/) - -Now remove those two packages: - -```console -$ pip uninstall typed-ast -y -``` - -And now you can install them with: - -```console -$ pip install --no-binary :all: typed-ast -``` - -The C extensions will be compiled and now Vim's Python environment will match. Note that -you need to have the GCC compiler and the Python development files installed (on -Ubuntu/Debian do `sudo apt-get install build-essential python3-dev`). - -If you later want to update _Black_, you should do it like this: - -```console -$ pip install -U black --no-binary typed-ast -``` - ### With ALE 1. Install [`ale`](https://github.com/dense-analysis/ale) diff --git a/mypy.ini b/mypy.ini index 58bb7536173..95ec22d65be 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,7 +2,7 @@ # Specify the target platform details in config, so your developers are # free to run mypy on Windows, Linux, or macOS and get consistent # results. -python_version=3.7 +python_version=3.8 mypy_path=src diff --git a/pyproject.toml b/pyproject.toml index d44623fdbd3..2d8da88f6c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ build-backend = "hatchling.build" name = "black" description = "The uncompromising code formatter." license = { text = "MIT" } -requires-python = ">=3.7" +requires-python = ">=3.8" authors = [ { name = "Ɓukasz Langa", email = "lukasz@langa.pl" }, ] @@ -69,7 +69,6 @@ dependencies = [ "pathspec>=0.9.0", "platformdirs>=2", "tomli>=1.1.0; python_version < '3.11'", - "typed-ast>=1.4.2; python_version < '3.8' and implementation_name == 'cpython'", "typing_extensions>=3.10.0.0; python_version < '3.10'", ] dynamic = ["readme", "version"] @@ -121,8 +120,6 @@ enable-by-default = false dependencies = [ "hatch-mypyc>=0.16.0", "mypy==1.3", - # Required stubs to be removed when the packages support PEP 561 themselves - "types-typed-ast>=1.4.2", ] require-runtime-dependencies = true exclude = [ @@ -145,7 +142,7 @@ options = { debug_level = "0" } [tool.cibuildwheel] build-verbosity = 1 # So these are the environments we target: -# - Python: CPython 3.7+ only +# - Python: CPython 3.8+ only # - Architecture (64-bit only): amd64 / x86_64, universal2, and arm64 # - OS: Linux (no musl), Windows, and macOS build = "cp3*-*" @@ -208,9 +205,6 @@ filterwarnings = [ # this is mitigated by a try/catch in https://github.com/psf/black/pull/3198/ # this ignore can be removed when support for aiohttp 3.x is dropped. '''ignore:Middleware decorator is deprecated since 4\.0 and its behaviour is default, you can simply remove this decorator:DeprecationWarning''', - # this is mitigated by https://github.com/python/cpython/issues/79071 in python 3.8+ - # this ignore can be removed when support for 3.7 is dropped. - '''ignore:Bare functions are deprecated, use async ones:DeprecationWarning''', # aiohttp is using deprecated cgi modules - Safe to remove when fixed: # https://github.com/aio-libs/aiohttp/issues/6905 '''ignore:'cgi' is deprecated and slated for removal in Python 3.13:DeprecationWarning''', diff --git a/src/black/_width_table.py b/src/black/_width_table.py index 6923f597687..1bf64cb5811 100644 --- a/src/black/_width_table.py +++ b/src/black/_width_table.py @@ -4,10 +4,7 @@ import sys from typing import List, Tuple -if sys.version_info < (3, 8): - from typing_extensions import Final -else: - from typing import Final +from typing import Final WIDTH_TABLE: Final[List[Tuple[int, int, int]]] = [ (0, 0, 0), diff --git a/src/black/brackets.py b/src/black/brackets.py index 343f0608d50..639b3d64f7e 100644 --- a/src/black/brackets.py +++ b/src/black/brackets.py @@ -4,10 +4,7 @@ from dataclasses import dataclass, field from typing import Dict, Iterable, List, Optional, Sequence, Set, Tuple, Union -if sys.version_info < (3, 8): - from typing_extensions import Final -else: - from typing import Final +from typing import Final from black.nodes import ( BRACKET, diff --git a/src/black/comments.py b/src/black/comments.py index 619123ab4be..38916449bb4 100644 --- a/src/black/comments.py +++ b/src/black/comments.py @@ -4,10 +4,7 @@ from functools import lru_cache from typing import Iterator, List, Optional, Union -if sys.version_info >= (3, 8): - from typing import Final -else: - from typing_extensions import Final +from typing import Final from black.nodes import ( CLOSING_BRACKETS, diff --git a/src/black/mode.py b/src/black/mode.py index 1091494afac..9a48e02a1e3 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -12,10 +12,7 @@ from typing import Dict, Set from warnings import warn -if sys.version_info < (3, 8): - from typing_extensions import Final -else: - from typing import Final +from typing import Final from black.const import DEFAULT_LINE_LENGTH diff --git a/src/black/nodes.py b/src/black/nodes.py index b019b0c6440..416f28cbe01 100644 --- a/src/black/nodes.py +++ b/src/black/nodes.py @@ -5,10 +5,7 @@ import sys from typing import Generic, Iterator, List, Optional, Set, Tuple, TypeVar, Union -if sys.version_info >= (3, 8): - from typing import Final -else: - from typing_extensions import Final +from typing import Final if sys.version_info >= (3, 10): from typing import TypeGuard else: diff --git a/src/black/parsing.py b/src/black/parsing.py index 70ed99c1549..d636bb4fa47 100644 --- a/src/black/parsing.py +++ b/src/black/parsing.py @@ -2,14 +2,10 @@ Parse Python code and perform AST validation. """ import ast -import platform import sys -from typing import Any, Iterable, Iterator, List, Set, Tuple, Type, Union +from typing import Any, Iterable, Iterator, List, Set, Tuple, Type -if sys.version_info < (3, 8): - from typing_extensions import Final -else: - from typing import Final +from typing import Final from black.mode import VERSION_TO_FEATURES, Feature, TargetVersion, supports_feature from black.nodes import syms @@ -20,25 +16,6 @@ from blib2to3.pgen2.tokenize import TokenError from blib2to3.pytree import Leaf, Node -ast3: Any - -_IS_PYPY = platform.python_implementation() == "PyPy" - -try: - from typed_ast import ast3 -except ImportError: - if sys.version_info < (3, 8) and not _IS_PYPY: - print( - "The typed_ast package is required but not installed.\n" - "You can upgrade to Python 3.8+ or install typed_ast with\n" - "`python3 -m pip install typed-ast`.", - file=sys.stderr, - ) - sys.exit(1) - else: - ast3 = ast - - PY2_HINT: Final = "Python 2 support was removed in version 22.0." @@ -147,31 +124,14 @@ def lib2to3_unparse(node: Node) -> str: def parse_single_version( src: str, version: Tuple[int, int], *, type_comments: bool -) -> Union[ast.AST, ast3.AST]: +) -> ast.AST: filename = "" - # typed-ast is needed because of feature version limitations in the builtin ast 3.8> - if sys.version_info >= (3, 8) and version >= (3,): - return ast.parse( - src, filename, feature_version=version, type_comments=type_comments - ) - - if _IS_PYPY: - # PyPy 3.7 doesn't support type comment tracking which is not ideal, but there's - # not much we can do as typed-ast won't work either. - if sys.version_info >= (3, 8): - return ast3.parse(src, filename, type_comments=type_comments) - else: - return ast3.parse(src, filename) - else: - if type_comments: - # Typed-ast is guaranteed to be used here and automatically tracks type - # comments separately. - return ast3.parse(src, filename, feature_version=version[1]) - else: - return ast.parse(src, filename) + return ast.parse( + src, filename, feature_version=version, type_comments=type_comments + ) -def parse_ast(src: str) -> Union[ast.AST, ast3.AST]: +def parse_ast(src: str) -> ast.AST: # TODO: support Python 4+ ;) versions = [(3, minor) for minor in range(3, sys.version_info[1] + 1)] @@ -193,9 +153,6 @@ def parse_ast(src: str) -> Union[ast.AST, ast3.AST]: raise SyntaxError(first_error) -ast3_AST: Final[Type[ast3.AST]] = ast3.AST - - def _normalize(lineend: str, value: str) -> str: # To normalize, we strip any leading and trailing space from # each line... @@ -206,23 +163,16 @@ def _normalize(lineend: str, value: str) -> str: return normalized.strip() -def stringify_ast(node: Union[ast.AST, ast3.AST], depth: int = 0) -> Iterator[str]: +def stringify_ast(node: ast.AST, depth: int = 0) -> Iterator[str]: """Simple visitor generating strings to compare ASTs by content.""" - node = fixup_ast_constants(node) - yield f"{' ' * depth}{node.__class__.__name__}(" type_ignore_classes: Tuple[Type[Any], ...] for field in sorted(node._fields): # noqa: F402 - # TypeIgnore will not be present using pypy < 3.8, so need for this - if not (_IS_PYPY and sys.version_info < (3, 8)): - # TypeIgnore has only one field 'lineno' which breaks this comparison - type_ignore_classes = (ast3.TypeIgnore,) - if sys.version_info >= (3, 8): - type_ignore_classes += (ast.TypeIgnore,) - if isinstance(node, type_ignore_classes): - break + # TypeIgnore has only one field 'lineno' which breaks this comparison + if isinstance(node, ast.TypeIgnore): + break try: value: object = getattr(node, field) @@ -237,22 +187,16 @@ def stringify_ast(node: Union[ast.AST, ast3.AST], depth: int = 0) -> Iterator[st # parentheses and they change the AST. if ( field == "targets" - and isinstance(node, (ast.Delete, ast3.Delete)) - and isinstance(item, (ast.Tuple, ast3.Tuple)) + and isinstance(node, ast.Delete) + and isinstance(item, ast.Tuple) ): for elt in item.elts: yield from stringify_ast(elt, depth + 2) - elif isinstance(item, (ast.AST, ast3.AST)): + elif isinstance(item, ast.AST): yield from stringify_ast(item, depth + 2) - # Note that we are referencing the typed-ast ASTs via global variables and not - # direct module attribute accesses because that breaks mypyc. It's probably - # something to do with the ast3 variables being marked as Any leading - # mypy to think this branch is always taken, leaving the rest of the code - # unanalyzed. Tighting up the types for the typed-ast AST types avoids the - # mypyc crash. - elif isinstance(value, (ast.AST, ast3_AST)): + elif isinstance(value, ast.AST): yield from stringify_ast(value, depth + 2) else: @@ -271,17 +215,3 @@ def stringify_ast(node: Union[ast.AST, ast3.AST], depth: int = 0) -> Iterator[st yield f"{' ' * (depth+2)}{normalized!r}, # {value.__class__.__name__}" yield f"{' ' * depth}) # /{node.__class__.__name__}" - - -def fixup_ast_constants(node: Union[ast.AST, ast3.AST]) -> Union[ast.AST, ast3.AST]: - """Map ast nodes deprecated in 3.8 to Constant.""" - if isinstance(node, (ast.Str, ast3.Str, ast.Bytes, ast3.Bytes)): - return ast.Constant(value=node.s) - - if isinstance(node, (ast.Num, ast3.Num)): - return ast.Constant(value=node.n) - - if isinstance(node, (ast.NameConstant, ast3.NameConstant)): - return ast.Constant(value=node.value) - - return node diff --git a/src/black/strings.py b/src/black/strings.py index ac18aef51ed..1197fee23cc 100644 --- a/src/black/strings.py +++ b/src/black/strings.py @@ -9,10 +9,7 @@ from blib2to3.pytree import Leaf -if sys.version_info < (3, 8): - from typing_extensions import Final -else: - from typing import Final +from typing import Final from black._width_table import WIDTH_TABLE diff --git a/src/black/trans.py b/src/black/trans.py index 4d40cb4bdf6..7431f2e5261 100644 --- a/src/black/trans.py +++ b/src/black/trans.py @@ -23,10 +23,7 @@ Union, ) -if sys.version_info < (3, 8): - from typing_extensions import Final, Literal -else: - from typing import Literal, Final +from typing import Literal, Final from mypy_extensions import trait diff --git a/src/blib2to3/pgen2/token.py b/src/blib2to3/pgen2/token.py index 1e0dec9c714..c939531d7c8 100644 --- a/src/blib2to3/pgen2/token.py +++ b/src/blib2to3/pgen2/token.py @@ -3,10 +3,7 @@ import sys from typing import Dict -if sys.version_info < (3, 8): - from typing_extensions import Final -else: - from typing import Final +from typing import Final # Taken from Python (r53757) and modified to include some tokens # originally monkeypatched in by pgen2.tokenize diff --git a/src/blib2to3/pgen2/tokenize.py b/src/blib2to3/pgen2/tokenize.py index 2d0cc4324ce..a5e89188d87 100644 --- a/src/blib2to3/pgen2/tokenize.py +++ b/src/blib2to3/pgen2/tokenize.py @@ -42,10 +42,7 @@ cast, ) -if sys.version_info >= (3, 8): - from typing import Final -else: - from typing_extensions import Final +from typing import Final from blib2to3.pgen2.token import * from blib2to3.pgen2.grammar import Grammar From 70d11022857b8bb7c38a6b9826c43cdddc0ab820 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Tue, 4 Jul 2023 22:42:03 -0700 Subject: [PATCH 2/4] changelog --- CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index acb5a822674..a0529cf344d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,9 @@ +- Runtime support for Python 3.7 has been removed. Formatting 3.7 code will still be + supported until further notice (#3765) + ### Stable style From 8c961687d4675bd4111c5fdb92b7b1afee565fc3 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Tue, 4 Jul 2023 22:42:14 -0700 Subject: [PATCH 3/4] lint and make width fixes --- scripts/make_width_table.py | 8 +------- src/black/_width_table.py | 5 +---- src/black/brackets.py | 5 +---- src/black/comments.py | 5 +---- src/black/mode.py | 5 +---- src/black/nodes.py | 3 +-- src/black/parsing.py | 4 +--- src/black/strings.py | 7 ++----- src/black/trans.py | 5 ++--- 9 files changed, 11 insertions(+), 36 deletions(-) diff --git a/scripts/make_width_table.py b/scripts/make_width_table.py index 89c202553d3..30fd32c34b0 100644 --- a/scripts/make_width_table.py +++ b/scripts/make_width_table.py @@ -52,13 +52,7 @@ def main() -> None: f.write(f"""# Generated by {basename(__file__)} # wcwidth {wcwidth.__version__} # Unicode {wcwidth.list_versions()[-1]} -import sys -from typing import List, Tuple - -if sys.version_info < (3, 8): - from typing_extensions import Final -else: - from typing import Final +from typing import Final, List, Tuple WIDTH_TABLE: Final[List[Tuple[int, int, int]]] = [ """) diff --git a/src/black/_width_table.py b/src/black/_width_table.py index 1bf64cb5811..f3304e48ed0 100644 --- a/src/black/_width_table.py +++ b/src/black/_width_table.py @@ -1,10 +1,7 @@ # Generated by make_width_table.py # wcwidth 0.2.6 # Unicode 15.0.0 -import sys -from typing import List, Tuple - -from typing import Final +from typing import Final, List, Tuple WIDTH_TABLE: Final[List[Tuple[int, int, int]]] = [ (0, 0, 0), diff --git a/src/black/brackets.py b/src/black/brackets.py index 639b3d64f7e..85dac6edd1e 100644 --- a/src/black/brackets.py +++ b/src/black/brackets.py @@ -1,10 +1,7 @@ """Builds on top of nodes.py to track brackets.""" -import sys from dataclasses import dataclass, field -from typing import Dict, Iterable, List, Optional, Sequence, Set, Tuple, Union - -from typing import Final +from typing import Dict, Final, Iterable, List, Optional, Sequence, Set, Tuple, Union from black.nodes import ( BRACKET, diff --git a/src/black/comments.py b/src/black/comments.py index 38916449bb4..226968bff98 100644 --- a/src/black/comments.py +++ b/src/black/comments.py @@ -1,10 +1,7 @@ import re -import sys from dataclasses import dataclass from functools import lru_cache -from typing import Iterator, List, Optional, Union - -from typing import Final +from typing import Final, Iterator, List, Optional, Union from black.nodes import ( CLOSING_BRACKETS, diff --git a/src/black/mode.py b/src/black/mode.py index 9a48e02a1e3..4d979afd84d 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -4,16 +4,13 @@ chosen by the user. """ -import sys from dataclasses import dataclass, field from enum import Enum, auto from hashlib import sha256 from operator import attrgetter -from typing import Dict, Set +from typing import Dict, Final, Set from warnings import warn -from typing import Final - from black.const import DEFAULT_LINE_LENGTH diff --git a/src/black/nodes.py b/src/black/nodes.py index 416f28cbe01..ef42278d83f 100644 --- a/src/black/nodes.py +++ b/src/black/nodes.py @@ -3,9 +3,8 @@ """ import sys -from typing import Generic, Iterator, List, Optional, Set, Tuple, TypeVar, Union +from typing import Final, Generic, Iterator, List, Optional, Set, Tuple, TypeVar, Union -from typing import Final if sys.version_info >= (3, 10): from typing import TypeGuard else: diff --git a/src/black/parsing.py b/src/black/parsing.py index d636bb4fa47..2a455bacb0d 100644 --- a/src/black/parsing.py +++ b/src/black/parsing.py @@ -3,9 +3,7 @@ """ import ast import sys -from typing import Any, Iterable, Iterator, List, Set, Tuple, Type - -from typing import Final +from typing import Any, Final, Iterable, Iterator, List, Set, Tuple, Type from black.mode import VERSION_TO_FEATURES, Feature, TargetVersion, supports_feature from black.nodes import syms diff --git a/src/black/strings.py b/src/black/strings.py index 1197fee23cc..0d30f09ed11 100644 --- a/src/black/strings.py +++ b/src/black/strings.py @@ -5,13 +5,10 @@ import re import sys from functools import lru_cache -from typing import List, Match, Pattern - -from blib2to3.pytree import Leaf - -from typing import Final +from typing import Final, List, Match, Pattern from black._width_table import WIDTH_TABLE +from blib2to3.pytree import Leaf STRING_PREFIX_CHARS: Final = "furbFURB" # All possible string prefix characters. STRING_PREFIX_RE: Final = re.compile( diff --git a/src/black/trans.py b/src/black/trans.py index 7431f2e5261..daed26427d7 100644 --- a/src/black/trans.py +++ b/src/black/trans.py @@ -2,7 +2,6 @@ String transformers that can split and merge strings. """ import re -import sys from abc import ABC, abstractmethod from collections import defaultdict from dataclasses import dataclass @@ -12,9 +11,11 @@ ClassVar, Collection, Dict, + Final, Iterable, Iterator, List, + Literal, Optional, Sequence, Set, @@ -23,8 +24,6 @@ Union, ) -from typing import Literal, Final - from mypy_extensions import trait from black.comments import contains_pragma_comment From 1ac61187ef857665ba1d1afe409376d94a128cb9 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Tue, 4 Jul 2023 22:58:07 -0700 Subject: [PATCH 4/4] fix tests --- src/black/parsing.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/black/parsing.py b/src/black/parsing.py index 2a455bacb0d..455c5eed968 100644 --- a/src/black/parsing.py +++ b/src/black/parsing.py @@ -3,7 +3,7 @@ """ import ast import sys -from typing import Any, Final, Iterable, Iterator, List, Set, Tuple, Type +from typing import Final, Iterable, Iterator, List, Set, Tuple from black.mode import VERSION_TO_FEATURES, Feature, TargetVersion, supports_feature from black.nodes import syms @@ -164,9 +164,18 @@ def _normalize(lineend: str, value: str) -> str: def stringify_ast(node: ast.AST, depth: int = 0) -> Iterator[str]: """Simple visitor generating strings to compare ASTs by content.""" + if ( + isinstance(node, ast.Constant) + and isinstance(node.value, str) + and node.kind == "u" + ): + # It's a quirk of history that we strip the u prefix over here. We used to + # rewrite the AST nodes for Python version compatibility and we never copied + # over the kind + node.kind = None + yield f"{' ' * depth}{node.__class__.__name__}(" - type_ignore_classes: Tuple[Type[Any], ...] for field in sorted(node._fields): # noqa: F402 # TypeIgnore has only one field 'lineno' which breaks this comparison if isinstance(node, ast.TypeIgnore):