Skip to content

Commit 6305bf1

Browse files
cobaltt7KaiSforza
andauthored
Prepare 2026.1.0 release (#4892)
Co-authored-by: cobalt <61329810+cobaltt7@users.noreply.github.com> Co-authored-by: Kai Sforza <kai@kaictl.me>
1 parent e71305b commit 6305bf1

36 files changed

+162
-714
lines changed

CHANGES.md

Lines changed: 30 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,37 @@
11
# Change Log
22

3-
## Unreleased
4-
5-
<!-- PR authors:
6-
Please include the PR number in the changelog entry, not the issue number -->
3+
## 26.1.0
74

85
### Highlights
96

10-
<!-- Include any especially major or disruptive changes here -->
11-
12-
This release alo bumps `pathspec` to v1.0.0 and fixes inconsistencies with Git's
7+
Introduces the 2026 stable style (#4892), stabilizing the following changes:
8+
9+
- `always_one_newline_after_import`: Always force one blank line after import
10+
statements, except when the line after the import is a comment or an import statement
11+
(#4489)
12+
- `fix_fmt_skip_in_one_liners`: Fix `# fmt: skip` behavior on one-liner declarations,
13+
such as `def foo(): return "mock" # fmt: skip`, where previously the declaration would
14+
have been incorrectly collapsed (#4800)
15+
- `fix_module_docstring_detection`: Fix module docstrings being treated as normal
16+
strings if preceded by comments (#4764)
17+
- `fix_type_expansion_split`: Fix type expansions split in generic functions (#4777)
18+
- `multiline_string_handling`: Make expressions involving multiline strings more compact
19+
(#1879)
20+
- `normalize_cr_newlines`: Add `\r` style newlines to the potential newlines to
21+
normalize file newlines both from and to (#4710)
22+
- `remove_parens_around_except_types`: Remove parentheses around multiple exception
23+
types in `except` and `except*` without `as` (#4720)
24+
- `remove_parens_from_assignment_lhs`: Remove unnecessary parentheses from the left-hand
25+
side of assignments while preserving magic trailing commas and intentional multiline
26+
formatting (#4865)
27+
- `standardize_type_comments`: Format type comments which have zero or more spaces
28+
between `#` and `type:` or between `type:` and value to `# type: (value)` (#4645)
29+
30+
The following change was not in any previous stable release:
31+
32+
- Regenerated the `_width_table.py` and added tests for the Khmer language (#4253)
33+
34+
This release alo bumps `pathspec` to v1 and fixes inconsistencies with Git's
1335
`.gitignore` logic (#4958). Now, files will be ignored if a pattern matches them, even
1436
if the parent directory is directly unignored. For example, Black would previously
1537
format `exclude/not_this/foo.py` with this `.gitignore`:
@@ -31,49 +53,14 @@ This new behavior matches Git. The leading `*/` are only necessary if you wish t
3153
matching subdirectories (like the previous behavior did), and not just matching root
3254
directories.
3355

34-
### Stable style
35-
36-
<!-- Changes that affect Black's stable style -->
37-
38-
### Preview style
39-
40-
<!-- Changes that affect Black's preview style -->
41-
42-
### Configuration
43-
44-
<!-- Changes to how Black can be configured -->
45-
46-
### Packaging
47-
48-
<!-- Changes to how Black is packaged, such as dependency requirements -->
49-
50-
### Parser
51-
52-
<!-- Changes to the parser or to version autodetection -->
53-
54-
### Performance
55-
56-
<!-- Changes that improve Black's performance. -->
57-
5856
### Output
5957

60-
<!-- Changes to Black's terminal output and error messages -->
61-
62-
### _Blackd_
63-
64-
<!-- Changes to blackd -->
58+
- Explicitly shutdown the multiprocessing manager when run in diff mode too (#4952)
6559

6660
### Integrations
6761

68-
<!-- For example, Docker, GitHub Actions, pre-commit, editors -->
69-
7062
- Upgraded PyPI upload workflow to use Trusted Publishing (#4611)
7163

72-
### Documentation
73-
74-
<!-- Major changes to documentation and policies. Small docs changes
75-
don't need a changelog entry. -->
76-
7764
## 25.12.0
7865

7966
### Highlights
@@ -82,7 +69,6 @@ directories.
8269

8370
### Stable style
8471

85-
- Fix Shutdown multiprocessing Manager in schedule_formatting (#4952)
8672
- Fix bug where comments preceding `# fmt: off`/`# fmt: on` blocks were incorrectly
8773
removed, particularly affecting Jupytext's `# %% [markdown]` comments (#4845)
8874
- Fix crash when multiple `# fmt: skip` comments are used in a multi-part if-clause, on

docs/integrations/source_version_control.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Use [pre-commit](https://pre-commit.com/). Once you
88
repos:
99
# Using this mirror lets us use mypyc-compiled black, which is about 2x faster
1010
- repo: https://github.com/psf/black-pre-commit-mirror
11-
rev: 25.12.0
11+
rev: 26.1.0
1212
hooks:
1313
- id: black
1414
# It is recommended to specify the latest version of Python
@@ -35,7 +35,7 @@ include Jupyter Notebooks. To use this hook, simply replace the hook's `id: blac
3535
repos:
3636
# Using this mirror lets us use mypyc-compiled black, which is about 2x faster
3737
- repo: https://github.com/psf/black-pre-commit-mirror
38-
rev: 25.12.0
38+
rev: 26.1.0
3939
hooks:
4040
- id: black-jupyter
4141
# It is recommended to specify the latest version of Python
@@ -66,7 +66,7 @@ Configure exclusions directly in `.pre-commit-config.yaml`:
6666
```yaml
6767
repos:
6868
- repo: https://github.com/psf/black-pre-commit-mirror
69-
rev: 25.12.0
69+
rev: 26.1.0
7070
hooks:
7171
- id: black
7272
exclude: ^migrations/|^generated/

docs/the_black_code_style/future_style.md

Lines changed: 5 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,10 @@ experimental, feedback and issue reports are highly encouraged!
1313

1414
Currently, the following features are included in the preview style:
1515

16-
- `always_one_newline_after_import`: Always force one blank line after import
17-
statements, except when the line after the import is a comment or an import statement
18-
- `wrap_long_dict_values_in_parens`: Add parentheses around long values in dictionaries
19-
([see below](labels/wrap-long-dict-values))
20-
- `fix_fmt_skip_in_one_liners`: Fix `# fmt: skip` behaviour on one-liner declarations,
21-
such as `def foo(): return "mock" # fmt: skip`, where previously the declaration would
22-
have been incorrectly collapsed.
23-
- `standardize_type_comments`: Format type comments which have zero or more spaces
24-
between `#` and `type:` or between `type:` and value to `# type: (value)`
2516
- `wrap_comprehension_in`: Wrap the `in` clause of list and dictionary comprehensions
2617
across lines if it would otherwise exceed the maximum line length.
27-
- `remove_parens_around_except_types`: Remove parentheses around multiple exception
28-
types in `except` and `except*` without `as`. See PEP 758 for details.
29-
- `normalize_cr_newlines`: Add `\r` style newlines to the potential newlines to
30-
normalize file newlines both from and to.
31-
- `fix_module_docstring_detection`: Fix module docstrings being treated as normal
32-
strings if preceeded by comments.
33-
- `fix_type_expansion_split`: Fix type expansions split in generic functions.
34-
- `remove_parens_from_assignment_lhs`: Remove unnecessary parentheses from the left-hand
35-
side of assignments while preserving magic trailing commas and intentional multiline
36-
formatting. For example, `(b) = a()[0]` becomes `b = a()[0]`, and `(c, *_) = a()`
37-
becomes `c, *_ = a()`, but `(d,) = a()` is preserved as it defines a single-element
38-
tuple.
39-
- `multiline_string_handling`: more compact formatting of expressions involving
40-
multiline strings ([see below](labels/multiline-string-handling))
41-
- `fix_module_docstring_detection`: Fix module docstrings being treated as normal
42-
strings if preceeded by comments.
18+
- `wrap_long_dict_values_in_parens`: Add parentheses around long values in dictionaries.
19+
([see below](labels/wrap-long-dict-values))
4320

4421
(labels/wrap-long-dict-values)=
4522

@@ -68,56 +45,6 @@ my_dict = {
6845
}
6946
```
7047

71-
(labels/multiline-string-handling)=
72-
73-
### Improved multiline string handling
74-
75-
_Black_ is smarter when formatting multiline strings, especially in function arguments,
76-
to avoid introducing extra line breaks. Previously, it would always consider multiline
77-
strings as not fitting on a single line. With this new feature, _Black_ looks at the
78-
context around the multiline string to decide if it should be inlined or split to a
79-
separate line. For example, when a multiline string is passed to a function, _Black_
80-
will only split the multiline string if a line is too long or if multiple arguments are
81-
being passed.
82-
83-
For example, _Black_ will reformat
84-
85-
```python
86-
textwrap.dedent(
87-
"""\
88-
This is a
89-
multiline string
90-
"""
91-
)
92-
```
93-
94-
to:
95-
96-
```python
97-
textwrap.dedent("""\
98-
This is a
99-
multiline string
100-
""")
101-
```
102-
103-
And:
104-
105-
```python
106-
MULTILINE = """
107-
foobar
108-
""".replace(
109-
"\n", ""
110-
)
111-
```
112-
113-
to:
114-
115-
```python
116-
MULTILINE = """
117-
foobar
118-
""".replace("\n", "")
119-
```
120-
12148
## Unstable style
12249

12350
(labels/unstable-style)=
@@ -135,9 +62,9 @@ demoted from the `--preview` to the `--unstable` style, users can use the
13562

13663
The unstable style additionally includes the following features:
13764

138-
- `hug_parens_with_braces_and_square_brackets`: more compact formatting of nested
139-
brackets ([see below](labels/hug-parens))
140-
- `string_processing`: split long string literals and related changes
65+
- `hug_parens_with_braces_and_square_brackets`: More compact formatting of nested
66+
brackets. ([see below](labels/hug-parens))
67+
- `string_processing`: Split long string literals and related changes.
14168
([see below](labels/string-processing))
14269

14370
(labels/hug-parens)=

docs/usage_and_configuration/the_basics.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,8 @@ configuration file for consistent results across environments.
278278

279279
```console
280280
$ black --version
281-
black, 25.12.0 (compiled: yes)
282-
$ black --required-version 25.12.0 -c "format = 'this'"
281+
black, 26.1.0 (compiled: yes)
282+
$ black --required-version 26.1.0 -c "format = 'this'"
283283
format = "this"
284284
$ black --required-version 31.5b2 -c "still = 'beta?!'"
285285
Oh no! 💥 💔 💥 The required version does not match the running version!
@@ -380,7 +380,7 @@ You can check the version of _Black_ you have installed using the `--version` fl
380380

381381
```console
382382
$ black --version
383-
black, 25.12.0
383+
black, 26.1.0
384384
```
385385

386386
#### `--config`

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ diff-shades = [
100100
]
101101
diff-shades-comment = ["click>=8.1.7", "packaging>=22.0", "urllib3"]
102102

103-
width-table = ["wcwidth>=0.2.6"]
103+
width-table = ["wcwidth>=0.2.14"]
104104

105105
[project.scripts]
106106
black = "black:patched_main"

scripts/fuzz.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,6 @@
3838
def test_idempotent_any_syntatically_valid_python(
3939
src_contents: str, mode: black.FileMode
4040
) -> None:
41-
if (
42-
"#\r" in src_contents or "\\\n" in src_contents
43-
) and black.Preview.normalize_cr_newlines not in mode:
44-
return
45-
4641
# Before starting, let's confirm that the input string is valid Python:
4742
compile(src_contents, "<string>", "exec") # else the bug is in hypothesmith
4843

src/black/__init__.py

Lines changed: 25 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,10 +1010,8 @@ def format_stdin_to_stdout(
10101010

10111011
if content is None:
10121012
src, encoding, newline = decode_bytes(sys.stdin.buffer.read(), mode)
1013-
elif Preview.normalize_cr_newlines in mode:
1014-
src, encoding, newline = content, "utf-8", "\n"
10151013
else:
1016-
src, encoding, newline = content, "utf-8", ""
1014+
src, encoding, newline = content, "utf-8", "\n"
10171015

10181016
dst = src
10191017
try:
@@ -1029,12 +1027,8 @@ def format_stdin_to_stdout(
10291027
)
10301028
if write_back == WriteBack.YES:
10311029
# Make sure there's a newline after the content
1032-
if Preview.normalize_cr_newlines in mode:
1033-
if dst and dst[-1] != "\n" and dst[-1] != "\r":
1034-
dst += newline
1035-
else:
1036-
if dst and dst[-1] != "\n":
1037-
dst += "\n"
1030+
if dst and dst[-1] != "\n" and dst[-1] != "\r":
1031+
dst += newline
10381032
f.write(dst)
10391033
elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
10401034
now = datetime.now(timezone.utc)
@@ -1224,16 +1218,13 @@ def f(
12241218
def _format_str_once(
12251219
src_contents: str, *, mode: Mode, lines: Collection[tuple[int, int]] = ()
12261220
) -> str:
1227-
if Preview.normalize_cr_newlines in mode:
1228-
normalized_contents, _, newline_type = decode_bytes(
1229-
src_contents.encode("utf-8"), mode
1230-
)
1221+
normalized_contents, _, newline_type = decode_bytes(
1222+
src_contents.encode("utf-8"), mode
1223+
)
12311224

1232-
src_node = lib2to3_parse(
1233-
normalized_contents.lstrip(), target_versions=mode.target_versions
1234-
)
1235-
else:
1236-
src_node = lib2to3_parse(src_contents.lstrip(), mode.target_versions)
1225+
src_node = lib2to3_parse(
1226+
normalized_contents.lstrip(), target_versions=mode.target_versions
1227+
)
12371228

12381229
dst_blocks: list[LinesBlock] = []
12391230
if mode.target_versions:
@@ -1280,22 +1271,9 @@ def _format_str_once(
12801271
for block in dst_blocks:
12811272
dst_contents.extend(block.all_lines())
12821273
if not dst_contents:
1283-
if Preview.normalize_cr_newlines in mode:
1284-
if "\n" in normalized_contents:
1285-
return newline_type
1286-
else:
1287-
# Use decode_bytes to retrieve the correct source newline (CRLF or LF),
1288-
# and check if normalized_content has more than one line
1289-
normalized_content, _, newline = decode_bytes(
1290-
src_contents.encode("utf-8"), mode
1291-
)
1292-
if "\n" in normalized_content:
1293-
return newline
1294-
return ""
1295-
if Preview.normalize_cr_newlines in mode:
1296-
return "".join(dst_contents).replace("\n", newline_type)
1297-
else:
1298-
return "".join(dst_contents)
1274+
if "\n" in normalized_contents:
1275+
return newline_type
1276+
return "".join(dst_contents).replace("\n", newline_type)
12991277

13001278

13011279
def decode_bytes(src: bytes, mode: Mode) -> tuple[FileContent, Encoding, NewLine]:
@@ -1309,24 +1287,21 @@ def decode_bytes(src: bytes, mode: Mode) -> tuple[FileContent, Encoding, NewLine
13091287
if not lines:
13101288
return "", encoding, "\n"
13111289

1312-
if Preview.normalize_cr_newlines in mode:
1313-
if lines[0][-2:] == b"\r\n":
1314-
if b"\r" in lines[0][:-2]:
1315-
newline = "\r"
1316-
else:
1317-
newline = "\r\n"
1318-
elif lines[0][-1:] == b"\n":
1319-
if b"\r" in lines[0][:-1]:
1320-
newline = "\r"
1321-
else:
1322-
newline = "\n"
1290+
if lines[0][-2:] == b"\r\n":
1291+
if b"\r" in lines[0][:-2]:
1292+
newline = "\r"
13231293
else:
1324-
if b"\r" in lines[0]:
1325-
newline = "\r"
1326-
else:
1327-
newline = "\n"
1294+
newline = "\r\n"
1295+
elif lines[0][-1:] == b"\n":
1296+
if b"\r" in lines[0][:-1]:
1297+
newline = "\r"
1298+
else:
1299+
newline = "\n"
13281300
else:
1329-
newline = "\r\n" if lines[0][-2:] == b"\r\n" else "\n"
1301+
if b"\r" in lines[0]:
1302+
newline = "\r"
1303+
else:
1304+
newline = "\n"
13301305

13311306
srcbuf.seek(0)
13321307
with io.TextIOWrapper(srcbuf, encoding) as tiow:

0 commit comments

Comments
 (0)