Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Python 2 support #2740

Merged
merged 9 commits into from Jan 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Expand Up @@ -16,7 +16,7 @@ current development version. To confirm this, you have three options:
3. Or run _Black_ on your machine:
- create a new virtualenv (make sure it's the same Python version);
- clone this repository;
- run `pip install -e .[d,python2]`;
- run `pip install -e .[d]`;
- run `pip install -r test_requirements.txt`
- make sure it's sane by running `python -m pytest`; and
- run `black` like you did last time.
Expand Down
1 change: 1 addition & 0 deletions CHANGES.md
Expand Up @@ -4,6 +4,7 @@

### _Black_

- **Remove Python 2 support** (#2740)
- Do not accept bare carriage return line endings in pyproject.toml (#2408)
- Improve error message for invalid regular expression (#2678)
- Improve error message when parsing fails during AST safety check by embedding the
Expand Down
4 changes: 1 addition & 3 deletions README.md
Expand Up @@ -40,9 +40,7 @@ Try it out now using the [Black Playground](https://black.vercel.app). Watch the
### Installation

_Black_ can be installed by running `pip install black`. It requires Python 3.6.2+ to
run. If you want to format Python 2 code as well, install with
`pip install black[python2]`. If you want to format Jupyter Notebooks, install with
`pip install black[jupyter]`.
run. If you want to format Jupyter Notebooks, install with `pip install black[jupyter]`.

If you can't wait for the latest _hotness_ and want to install from GitHub, use:

Expand Down
2 changes: 1 addition & 1 deletion action/main.py
Expand Up @@ -14,7 +14,7 @@

run([sys.executable, "-m", "venv", str(ENV_PATH)], check=True)

req = "black[colorama,python2]"
req = "black[colorama]"
if VERSION:
req += f"=={VERSION}"
pip_proc = run(
Expand Down
14 changes: 2 additions & 12 deletions docs/faq.md
Expand Up @@ -75,16 +75,7 @@ disabled-by-default counterpart W504. E203 should be disabled while changes are

## Does Black support Python 2?

```{warning}
Python 2 support has been deprecated since 21.10b0.
This support will be dropped in the first stable release, expected for January 2022.
See [The Black Code Style](the_black_code_style/index.rst) for details.
```

For formatting, yes! [Install](getting_started.md#installation) with the `python2` extra
to format Python 2 files too! In terms of running _Black_ though, Python 3.6 or newer is
required.
Support for formatting Python 2 code was removed in version 22.0.

## Why does my linter or typechecker complain after I format my code?

Expand All @@ -96,8 +87,7 @@ codebase with _Black_.

## Can I run Black with PyPy?

Yes, there is support for PyPy 3.7 and higher. You cannot format Python 2 files under
PyPy, because PyPy's inbuilt ast module does not support this.
Yes, there is support for PyPy 3.7 and higher.

## Why does Black not detect syntax errors in my code?

Expand Down
4 changes: 1 addition & 3 deletions docs/getting_started.md
Expand Up @@ -17,9 +17,7 @@ Also, you can try out _Black_ online for minimal fuss on the
## Installation

_Black_ can be installed by running `pip install black`. It requires Python 3.6.2+ to
run, but can format Python 2 code too. Python 2 support needs the `typed_ast`
dependency, which be installed with `pip install black[python2]`. If you want to format
Jupyter Notebooks, install with `pip install black[jupyter]`.
run. If you want to format Jupyter Notebooks, install with `pip install black[jupyter]`.

If you can't wait for the latest _hotness_ and want to install from GitHub, use:

Expand Down
4 changes: 2 additions & 2 deletions docs/integrations/github_actions.md
Expand Up @@ -8,8 +8,8 @@ environment. Great for enforcing that your code matches the _Black_ code style.
This action is known to support all GitHub-hosted runner OSes. In addition, only
published versions of _Black_ are supported (i.e. whatever is available on PyPI).

Finally, this action installs _Black_ with both the `colorama` and `python2` extras so
the `--color` flag and formatting Python 2 code are supported.
Finally, this action installs _Black_ with the `colorama` extra so the `--color` flag
should work fine.

## Usage

Expand Down
3 changes: 1 addition & 2 deletions docs/the_black_code_style/current_style.md
Expand Up @@ -281,8 +281,7 @@ removed.

_Black_ standardizes most numeric literals to use lowercase letters for the syntactic
parts and uppercase letters for the digits themselves: `0xAB` instead of `0XAB` and
`1e10` instead of `1E10`. Python 2 long literals are styled as `2L` instead of `2l` to
avoid confusion between `l` and `1`.
`1e10` instead of `1E10`.

### Line breaks & binary operators

Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Expand Up @@ -29,7 +29,6 @@ build-backend = "setuptools.build_meta"
[tool.pytest.ini_options]
# Option below requires `tests/optional.py`
optional-tests = [
"no_python2: run when `python2` extra NOT installed",
"no_blackd: run when `d` extra NOT installed",
"no_jupyter: run when `jupyter` extra NOT installed",
]
Expand Down
1 change: 0 additions & 1 deletion setup.py
Expand Up @@ -112,7 +112,6 @@ def find_python_files(base: Path) -> List[Path]:
extras_require={
"d": ["aiohttp>=3.7.4"],
"colorama": ["colorama>=0.4.3"],
"python2": ["typed-ast>=1.4.3"],
"uvloop": ["uvloop>=0.15.2"],
"jupyter": ["ipython>=7.8.0", "tokenize-rt>=3.2.0"],
},
Expand Down
48 changes: 1 addition & 47 deletions src/black/__init__.py
Expand Up @@ -1083,20 +1083,8 @@ def f(
else:
versions = detect_target_versions(src_node, future_imports=future_imports)

# TODO: fully drop support and this code hopefully in January 2022 :D
if TargetVersion.PY27 in mode.target_versions or versions == {TargetVersion.PY27}:
msg = (
"DEPRECATION: Python 2 support will be removed in the first stable release "
"expected in January 2022."
)
err(msg, fg="yellow", bold=True)

normalize_fmt_off(src_node)
lines = LineGenerator(
mode=mode,
remove_u_prefix="unicode_literals" in future_imports
or supports_feature(versions, Feature.UNICODE_LITERALS),
)
lines = LineGenerator(mode=mode)
elt = EmptyLineTracker(is_pyi=mode.is_pyi)
empty_line = Line(mode=mode)
after = 0
Expand Down Expand Up @@ -1166,14 +1154,6 @@ def get_features_used( # noqa: C901
assert isinstance(n, Leaf)
if "_" in n.value:
features.add(Feature.NUMERIC_UNDERSCORES)
elif n.value.endswith(("L", "l")):
# Python 2: 10L
features.add(Feature.LONG_INT_LITERAL)
elif len(n.value) >= 2 and n.value[0] == "0" and n.value[1].isdigit():
# Python 2: 0123; 00123; ...
if not all(char == "0" for char in n.value):
# although we don't want to match 0000 or similar
features.add(Feature.OCTAL_INT_LITERAL)

elif n.type == token.SLASH:
if n.parent and n.parent.type in {
Expand Down Expand Up @@ -1226,32 +1206,6 @@ def get_features_used( # noqa: C901
):
features.add(Feature.ANN_ASSIGN_EXTENDED_RHS)

# Python 2 only features (for its deprecation) except for integers, see above
elif n.type == syms.print_stmt:
features.add(Feature.PRINT_STMT)
elif n.type == syms.exec_stmt:
features.add(Feature.EXEC_STMT)
elif n.type == syms.tfpdef:
# def set_position((x, y), value):
# ...
features.add(Feature.AUTOMATIC_PARAMETER_UNPACKING)
elif n.type == syms.except_clause:
# try:
# ...
# except Exception, err:
# ...
if len(n.children) >= 4:
if n.children[-2].type == token.COMMA:
features.add(Feature.COMMA_STYLE_EXCEPT)
elif n.type == syms.raise_stmt:
# raise Exception, "msg"
if len(n.children) >= 4:
if n.children[-2].type == token.COMMA:
features.add(Feature.COMMA_STYLE_RAISE)
elif n.type == token.BACKQUOTE:
# `i'm surprised this ever existed`
features.add(Feature.BACKQUOTE_REPR)

return features


Expand Down
9 changes: 3 additions & 6 deletions src/black/linegen.py
Expand Up @@ -48,9 +48,8 @@ class LineGenerator(Visitor[Line]):
in ways that will no longer stringify to valid Python code on the tree.
"""

def __init__(self, mode: Mode, remove_u_prefix: bool = False) -> None:
def __init__(self, mode: Mode) -> None:
self.mode = mode
self.remove_u_prefix = remove_u_prefix
self.current_line: Line
self.__post_init__()

Expand Down Expand Up @@ -92,9 +91,7 @@ def visit_default(self, node: LN) -> Iterator[Line]:

normalize_prefix(node, inside_brackets=any_open_brackets)
if self.mode.string_normalization and node.type == token.STRING:
node.value = normalize_string_prefix(
node.value, remove_u_prefix=self.remove_u_prefix
)
node.value = normalize_string_prefix(node.value)
node.value = normalize_string_quotes(node.value)
if node.type == token.NUMBER:
normalize_numeric_literal(node)
Expand Down Expand Up @@ -236,7 +233,7 @@ def visit_STRING(self, leaf: Leaf) -> Iterator[Line]:
if is_docstring(leaf) and "\\\n" not in leaf.value:
# We're ignoring docstrings with backslash newline escapes because changing
# indentation of those changes the AST representation of the code.
docstring = normalize_string_prefix(leaf.value, self.remove_u_prefix)
docstring = normalize_string_prefix(leaf.value)
prefix = get_string_prefix(docstring)
docstring = docstring[len(prefix) :] # Remove the prefix
quote_char = docstring[0]
Expand Down
42 changes: 3 additions & 39 deletions src/black/mode.py
Expand Up @@ -20,7 +20,6 @@


class TargetVersion(Enum):
PY27 = 2
PY33 = 3
PY34 = 4
PY35 = 5
Expand All @@ -30,13 +29,8 @@ class TargetVersion(Enum):
PY39 = 9
PY310 = 10

def is_python2(self) -> bool:
return self is TargetVersion.PY27


class Feature(Enum):
ichard26 marked this conversation as resolved.
Show resolved Hide resolved
# All string literals are unicode
UNICODE_LITERALS = 1
F_STRINGS = 2
NUMERIC_UNDERSCORES = 3
TRAILING_COMMA_IN_CALL = 4
Expand All @@ -56,51 +50,24 @@ class Feature(Enum):
# __future__ flags
FUTURE_ANNOTATIONS = 51

# temporary for Python 2 deprecation
PRINT_STMT = 200
EXEC_STMT = 201
AUTOMATIC_PARAMETER_UNPACKING = 202
COMMA_STYLE_EXCEPT = 203
COMMA_STYLE_RAISE = 204
LONG_INT_LITERAL = 205
OCTAL_INT_LITERAL = 206
BACKQUOTE_REPR = 207


FUTURE_FLAG_TO_FEATURE: Final = {
"annotations": Feature.FUTURE_ANNOTATIONS,
}


VERSION_TO_FEATURES: Dict[TargetVersion, Set[Feature]] = {
TargetVersion.PY27: {
Feature.ASYNC_IDENTIFIERS,
Feature.PRINT_STMT,
Feature.EXEC_STMT,
Feature.AUTOMATIC_PARAMETER_UNPACKING,
Feature.COMMA_STYLE_EXCEPT,
Feature.COMMA_STYLE_RAISE,
Feature.LONG_INT_LITERAL,
Feature.OCTAL_INT_LITERAL,
Feature.BACKQUOTE_REPR,
},
TargetVersion.PY33: {Feature.UNICODE_LITERALS, Feature.ASYNC_IDENTIFIERS},
TargetVersion.PY34: {Feature.UNICODE_LITERALS, Feature.ASYNC_IDENTIFIERS},
TargetVersion.PY35: {
Feature.UNICODE_LITERALS,
Feature.TRAILING_COMMA_IN_CALL,
Feature.ASYNC_IDENTIFIERS,
},
TargetVersion.PY33: {Feature.ASYNC_IDENTIFIERS},
TargetVersion.PY34: {Feature.ASYNC_IDENTIFIERS},
TargetVersion.PY35: {Feature.TRAILING_COMMA_IN_CALL, Feature.ASYNC_IDENTIFIERS},
TargetVersion.PY36: {
Feature.UNICODE_LITERALS,
Feature.F_STRINGS,
Feature.NUMERIC_UNDERSCORES,
Feature.TRAILING_COMMA_IN_CALL,
Feature.TRAILING_COMMA_IN_DEF,
Feature.ASYNC_IDENTIFIERS,
},
TargetVersion.PY37: {
Feature.UNICODE_LITERALS,
Feature.F_STRINGS,
Feature.NUMERIC_UNDERSCORES,
Feature.TRAILING_COMMA_IN_CALL,
Expand All @@ -109,7 +76,6 @@ class Feature(Enum):
Feature.FUTURE_ANNOTATIONS,
},
TargetVersion.PY38: {
Feature.UNICODE_LITERALS,
Feature.F_STRINGS,
Feature.NUMERIC_UNDERSCORES,
Feature.TRAILING_COMMA_IN_CALL,
Expand All @@ -122,7 +88,6 @@ class Feature(Enum):
Feature.ANN_ASSIGN_EXTENDED_RHS,
},
TargetVersion.PY39: {
Feature.UNICODE_LITERALS,
Feature.F_STRINGS,
Feature.NUMERIC_UNDERSCORES,
Feature.TRAILING_COMMA_IN_CALL,
Expand All @@ -136,7 +101,6 @@ class Feature(Enum):
Feature.ANN_ASSIGN_EXTENDED_RHS,
},
TargetVersion.PY310: {
Feature.UNICODE_LITERALS,
Feature.F_STRINGS,
Feature.NUMERIC_UNDERSCORES,
Feature.TRAILING_COMMA_IN_CALL,
Expand Down
10 changes: 0 additions & 10 deletions src/black/nodes.py
Expand Up @@ -259,16 +259,6 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa: C901
):
return NO

elif (
prevp.type == token.RIGHTSHIFT
and prevp.parent
and prevp.parent.type == syms.shift_expr
and prevp.prev_sibling
and is_name_token(prevp.prev_sibling)
and prevp.prev_sibling.value == "print"
):
# Python 2 print chevron
return NO
elif prevp.type == token.AT and p.parent and p.parent.type == syms.decorator:
# no space in decorators
return NO
Expand Down
15 changes: 5 additions & 10 deletions src/black/numerics.py
Expand Up @@ -25,13 +25,10 @@ def format_scientific_notation(text: str) -> str:
return f"{before}e{sign}{after}"


def format_long_or_complex_number(text: str) -> str:
"""Formats a long or complex string like `10L` or `10j`"""
def format_complex_number(text: str) -> str:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice rename with the function change.

"""Formats a complex string like `10j`"""
number = text[:-1]
suffix = text[-1]
# Capitalize in "2L" because "l" looks too similar to "1".
if suffix == "l":
suffix = "L"
return f"{format_float_or_int_string(number)}{suffix}"


Expand All @@ -47,9 +44,7 @@ def format_float_or_int_string(text: str) -> str:
def normalize_numeric_literal(leaf: Leaf) -> None:
"""Normalizes numeric (float, int, and complex) literals.

All letters used in the representation are normalized to lowercase (except
in Python 2 long literals).
"""
All letters used in the representation are normalized to lowercase."""
text = leaf.value.lower()
if text.startswith(("0o", "0b")):
# Leave octal and binary literals alone.
Expand All @@ -58,8 +53,8 @@ def normalize_numeric_literal(leaf: Leaf) -> None:
text = format_hex(text)
elif "e" in text:
text = format_scientific_notation(text)
elif text.endswith(("j", "l")):
text = format_long_or_complex_number(text)
elif text.endswith("j"):
text = format_complex_number(text)
else:
text = format_float_or_int_string(text)
leaf.value = text