Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Black Admin & Architecture Guide

Generated June 2026 from psf/black at ce1897a

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

This guide is for the engineers, architects, and integrators who need to understand Black from the inside: how its formatting pipeline is built, how it discovers and parses files, how to deploy it at scale (including the blackd server), its security posture, and how the codebase is organized for contributors. Everything here is derived from Black's source at commit ce1897a.

Note

Black is a command-line tool and a library, not a long-running service (with the single exception of the optional blackd HTTP server). "Deployment" for Black mostly means embedding it in developer workflows, CI pipelines, and editor tooling rather than running a daemon.


1. Executive Summary

Black is the uncompromising Python code formatter — an opinionated, deterministic tool that rewrites Python source into a single canonical style. It is maintained under the Python Software Foundation (psf/black), authored by Łukasz Langa and contributors, and licensed MIT.

Attribute Value
Language Python (≥ 3.10 to run)
Distribution PyPI package black; mypyc-compiled wheels for speed
Entry points black (CLI), blackd (HTTP server)
Core dependencies click, mypy-extensions, packaging, pathspec, platformdirs, pytokens
Parser blib2to3 — a vendored, modified fork of lib2to3
Safety model AST-equivalence + idempotence checks after every format
Configuration pyproject.toml [tool.black]
Lines targeted Python 3.3 → 3.15 output

Architectural pillars:

  1. Lossless concrete-syntax parsing. Black parses with a forked lib2to3 (blib2to3) that preserves comments and whitespace, so it can reformat without losing information the standard ast module discards.
  2. A two-pass, idempotent formatting pipeline. Source → CST → line generation → line splitting → output, run twice to converge on a stable result.
  3. Correctness over speed by default. Every format is verified to produce an equivalent AST before it is written; the check is opt-out (--fast), not opt-in.
  4. Sensible-defaults configuration. A tiny set of knobs, all settable in pyproject.toml, with target Python version auto-inferred from project metadata.
  5. Embarrassingly parallel. Multi-file runs fan out across a process pool.

2. System Architecture

2.1 High-level flow

                        ┌──────────────────────────────────────────────┐
                        │  black CLI (click)  src/black/__init__.py     │
                        │  parse options → build Mode → find root       │
                        └───────────────┬──────────────────────────────┘
                                        │
                 ┌──────────────────────┼───────────────────────────┐
                 │                      │                            │
          -c/--code              single SRC                  multiple SRC
                 │                      │                            │
                 ▼                      ▼                            ▼
         reformat_code          reformat_one              reformat_many
        (string, no procs)    (cache-aware,           (concurrency.py:
                 │             single file)            ProcessPoolExecutor)
                 └──────────────────────┼───────────────────────────┘
                                        ▼
                          format_file_contents()
                                        │
                          ┌─────────────┴─────────────┐
                          │  is_ipynb?                 │
                          ▼                            ▼
                   format_ipynb_string          format_str()
                   (cell-by-cell)                     │
                                                      ▼
                                         _format_str_once()  ×2 (forced 2nd pass)
                                                      │
        ┌─────────────────────────────────────────────┼─────────────────────────────┐
        ▼                          ▼                   ▼                ▼              ▼
  lib2to3_parse           normalize_fmt_off     LineGenerator    transform_line   EmptyLineTracker
 (blib2to3 → CST)         (# fmt: off/skip)     (visit CST →     (split long      (blank-line
                                                 logical Lines)   lines, strings)   policy)
                                                      │
                                                      ▼
                                              assembled output string
                                                      │
                                   ┌──────────────────┴──────────────────┐
                                   │  not --fast?                        │
                                   ▼                                     ▼
                       assert_equivalent (AST match)        assert_stable (idempotence)

2.2 The formatting pipeline in detail

The heart of Black is format_str() (src/black/__init__.py), which calls _format_str_once() and then — crucially — calls it a second time if the first pass changed anything:

def format_str(src_contents, *, mode, lines=()):
    dst_contents = _format_str_once(src_contents, mode=mode, lines=lines)
    # Forced second pass to work around optional trailing commas interacting
    # with optional parentheses.
    if src_contents != dst_contents:
        return _format_str_once(dst_contents, mode=mode, lines=lines)
    return dst_contents

Note

The forced second pass exists because optional trailing commas can become forced trailing commas on the next pass, which interacts with optional parentheses. Running twice guarantees the output is stable. The assert_stable safety check later verifies this convergence held.

Inside _format_str_once() the stages are:

  1. Decode & normalize newlines (decode_bytes) — detects the file's encoding (honoring magic encoding comments) and newline style (\n, \r\n, or \r), normalizing internally to \n.
  2. Parse (lib2to3_parse) — produces a concrete syntax tree using blib2to3.
  3. Determine target versions — uses mode.target_versions if set, otherwise detects __future__ imports and calls detect_target_versions() to infer from syntax features used.
  4. Process # fmt: directives (normalize_fmt_off) — converts # fmt: off/on regions into standalone, untouchable comment nodes.
  5. Apply line ranges (convert_unchanged_lines) — when --line-ranges is set, marks out-of-range lines as unchanged.
  6. Generate logical lines — LineGenerator visits the CST and emits Line objects (one per logical line of output).
  7. Track blank lines — EmptyLineTracker decides how many blank lines go before/after each line per Black's rules.
  8. Split lines — transform_line() applies splitting transforms (bracket splitting, string handling) to fit the line-length budget.
  9. Reassemble — blocks are joined and the internal \n is converted back to the original newline style.

2.3 File discovery

When given paths rather than a string, get_sources() (__init__.py) computes the set of files to format:

  • For directories, it recurses via gen_python_files() (files.py), applying, in order: .gitignore rules → --exclude → --extend-exclude → --force-exclude, then keeping only paths matching --include.
  • For explicit files, it skips the include/exclude logic except --force-exclude (which is why --force-exclude exists: to exclude files even when passed explicitly, e.g. by a pre-commit hook).
  • Symlinks pointing outside the project root are skipped (resolves_outside_root_or_cannot_stat).
  • .gitignore files are honored hierarchically: each subdirectory's .gitignore is layered on top of its parents'.

Important

Default exclusions are baked in: .git, .hg, .venv, .tox, .nox, .mypy_cache, .pytest_cache, .ruff_cache, build, dist, __pypackages__, and several others (see DEFAULT_EXCLUDES in const.py). Setting --exclude replaces this list; use --extend-exclude to add to it.

2.4 The blib2to3 parser

Black does not use Python's built-in ast module for parsing (only for the safety check). Instead it vendors blib2to3 — a fork of the standard library's lib2to3 — under src/blib2to3/.

Concern Why a fork is needed
Comment & whitespace preservation lib2to3 produces a concrete syntax tree (CST) that keeps prefixes (whitespace/comments), which a formatter needs and ast throws away.
New syntax support The grammar (Grammar.txt, PatternGrammar.txt) and tokenizer are extended to parse syntax from Python versions newer than the running interpreter.
Decoupling from the runtime Black on Python 3.10 can still parse match statements, except*, type parameters, t-strings, etc.

Key modules: pytree.py (the Node/Leaf/Base CST classes and pattern-matching machinery), pygram.py (symbol tables), and pgen2/ (the parser generator and tokenizer).

2.5 Module inventory (src/black/)

Module Responsibility
__init__.py CLI definition, top-level format orchestration, public API.
mode.py Mode dataclass, TargetVersion/Feature/Preview enums, version→feature map.
linegen.py LineGenerator — turns the CST into Line objects; line-splitting entry (transform_line).
lines.py Line, EmptyLineTracker, LinesBlock — line model and blank-line policy.
trans.py String transformers (StringMerger, StringSplitter, StringParenWrapper, …) for the preview string-processing style.
brackets.py BracketTracker, delimiter priority — the bracket-matching that drives splitting.
comments.py Comment normalization and # fmt: off/on/skip handling.
nodes.py CST helpers, the Visitor base, symbol constants.
parsing.py lib2to3_parse, parse_ast, stringify_ast; defines InvalidInput, ASTSafetyError, SourceASTParseError.
files.py Project-root discovery, pyproject.toml parsing, gitignore/exclude logic, target-version inference.
mode.py/const.py Configuration model and constants (DEFAULT_LINE_LENGTH=88, exclude/include regexes).
cache.py Cache — feature-keyed, mtime/size/hash-based incremental cache.
concurrency.py reformat_many, schedule_formatting, maybe_use_uvloop — the parallel path.
report.py Report (counters, return_code), Changed, NothingChanged.
handle_ipynb_magics.py Masking/unmasking IPython magics so notebook cells can be formatted.
ranges.py --line-ranges parsing and line-mapping across passes.
numerics.py, strings.py Numeric literal and string normalization helpers.
output.py Colored terminal output, diffs (diff, color_diff, ipynb_diff).
rusty.py Ok/Err/Result — a small Rust-style result type used internally.
schema.py Loads the JSON schema for [tool.black] (used by validate-pyproject).
debug.py DebugVisitor for pretty-printing the CST while debugging.

3. Requirements

3.1 Runtime

Requirement Detail
Python 3.10+ (asserted at startup).
OS OS-independent (Linux, macOS, Windows).
Disk Negligible, plus a per-version cache directory.
Memory Proportional to the largest single file; the parallel path holds several files at once.

Caution

Python 3.12.5 is explicitly rejected at startup: it has a memory-safety issue that can cause Black's AST safety check to fail. Use 3.12.6+ or 3.12.4.

3.2 Dependencies

Core (always installed):

Package Constraint Role
click >=8.0.0 CLI framework.
mypy-extensions >=0.4.3 @mypyc_attr decorators for the compiled build.
packaging >=22.0 Parse requires-python for target-version inference.
pathspec >=1.0.0 .gitignore matching (GitIgnoreSpec).
platformdirs >=2 Locate the user cache directory.
pytokens ~=0.4.0 Tokenizer support.
tomli >=1.1.0 (Python < 3.11) TOML parsing (stdlib tomllib on 3.11+).
typing-extensions >=4.0.1 (Python < 3.11) Back-ported typing features.

Optional extras:

Extra Pulls in Enables
colorama colorama>=0.4.3 Colored diffs on Windows.
uvloop uvloop / winloop Faster event loop for parallel runs.
d aiohttp>=3.10 The blackd server.
jupyter ipython>=7.8.0, tokenize-rt>=3.2.0 Notebook formatting.

4. Configuration Mastery

4.1 Resolution order

Black builds each option's effective value from (lowest to highest precedence):

  1. Built-in default (e.g. line length 88).
  2. Config file — [tool.black] in the discovered pyproject.toml (or --config / user-level file), injected as Click's default_map.
  3. Command-line flags.

The config injection happens in read_pyproject_toml() (an eager Click callback on --config), which calls parse_pyproject_toml() in files.py. Keys are normalized: --key/key-name → key_name.

4.2 Project-root and config discovery

find_project_root() walks up from the common parent of the source paths and stops at the first directory containing .git, .hg, or a pyproject.toml with a [tool.black] table. That directory is the root; its pyproject.toml is the config. The discovery method is reported under -v.

User-level fallback config:

Platform Path
Windows ~\.black
Unix $XDG_CONFIG_HOME/black, defaulting to ~/.config/black

4.3 The Mode object

All formatting behavior is captured in the Mode dataclass (mode.py). This is the single source of truth for "how to format", and is what blackd and library callers construct directly.

Mode field Default CLI origin
target_versions set() (auto-detect) --target-version
line_length 88 --line-length
string_normalization True --skip-string-normalization (inverts)
is_pyi False --pyi
is_ipynb False --ipynb
skip_source_first_line False --skip-source-first-line
magic_trailing_comma True --skip-magic-trailing-comma (inverts)
python_cell_magics set() --python-cell-magics
preview False --preview
unstable False --unstable
enabled_features set() --enable-unstable-feature

Note

Mode is also exported as FileMode for backwards compatibility with integrations. Mode.get_cache_key() derives the cache filename, so any change to these fields invalidates the cache (see Section 6).

4.4 Target version inference

When target-version is unset, infer_target_version() (files.py) reads requires-python from [project] and maps it to a set of TargetVersion values (handling both exact versions like "3.10" and specifiers like ">=3.9,<3.12"). If that yields nothing, Black falls back to per-file detection: get_features_used() scans the CST for version-gated syntax (f-strings, match, except*, type params, t-strings, lazy imports, etc.) and detect_target_versions() returns every version whose feature set is a superset.

The full TargetVersion enum spans PY33 through PY315. Feature gating is defined by the VERSION_TO_FEATURES map and consulted via supports_feature() — for example, trailing commas after *args (TRAILING_COMMA_IN_CALL/_IN_DEF) are only emitted when all targets support them.

4.5 Preview and unstable styles

Experimental formatting lives behind three gates, resolved by Mode.__contains__:

  • --unstable → every preview feature is on.
  • --preview → every preview feature except those in UNSTABLE_FEATURES is on.
  • --enable-unstable-feature NAME → that one Preview feature is on (requires --preview).

The current Preview features (names usable with --enable-unstable-feature) include string_processing, hug_parens_with_braces_and_square_brackets, wrap_comprehension_in, simplify_power_operator_hugging, wrap_long_dict_values_in_parens, fix_if_guard_explosion_in_case_statement, pyi_overload_group_blank_lines, pyi_blank_line_before_decorated_class, and hug_comparator. string_processing and hug_parens_with_braces_and_square_brackets are classified unstable (known bugs).

Warning

The set of preview/unstable features changes between releases with no compatibility guarantees. Don't pin workflows to a specific unstable feature name.


5. Deployment & Integration Strategies

Black is consumed in four main ways. Pick based on where formatting needs to happen.

5.1 Local CLI / library

The simplest integration. As a library, the stable public surface is:

import black

# Format a string (raises black.NothingChanged if already formatted)
formatted = black.format_str(source, mode=black.Mode(line_length=100))

# Format a whole file's contents (adds the safety check unless fast=True)
result = black.format_file_contents(source, fast=False, mode=black.Mode())

Important

format_str and format_file_contents raise black.NothingChanged when the input is already formatted — catch it. format_file_contents also performs the equivalence/stability checks (skippable with fast=True).

5.2 pre-commit

The canonical developer-machine integration. Black ships two hooks (.pre-commit-hooks.yaml): black (.py/.pyi) and black-jupyter (adds .ipynb via the jupyter extra).

Tip

Use the psf/black-pre-commit-mirror repo rather than psf/black in .pre-commit-config.yaml — it's ~2× faster because it installs a built wheel instead of compiling from source. Both hooks set require_serial: true and a minimum pre-commit version of 2.9.2.

5.3 GitHub Action

The repository is a composite GitHub Action (action.yml → action/main.py). The action script:

  1. Creates a fresh virtualenv (.black-env).
  2. Resolves the version to install from the version input, or from pyproject.toml when use_pyproject: true (reading required-version, then dependency groups/project.dependencies), or from the action's own git-archival tag.
  3. pip installs Black (plus colorama, and jupyter when jupyter: true).
  4. Runs Black with options + src, tears the venv down, optionally writes output to a file and the job summary, and exits with Black's exit code.
Input Default Notes
options --check --diff Flags passed to Black.
src . Paths.
jupyter false Installs the jupyter extra and colorama.
version "" PEP 440 specifier; mutually exclusive with use_pyproject.
use_pyproject false Read version from pyproject.toml (requires Python 3.11+ in the runner for tomllib).
summary true Append output to the job summary.
output-file "" Write output to a file too.
black_args "" Deprecated — use options + src.

5.4 blackd HTTP server

A persistent formatter for editors/tooling. See Section 6.4 for operations. Architecturally it is a thin aiohttp app: a single POST / route handled by handle(), which parses X-* headers into a Mode (parse_mode), runs black.format_file_contents inside a ProcessPoolExecutor, and maps Black exceptions to HTTP status codes. A CORS middleware (middlewares.py) gates cross-origin browser access.


6. Operations

6.1 Exit codes and reporting

The Report object (report.py) accumulates counters and computes the process exit code via return_code:

Condition Exit code
Any file failed to format 123
Files changed and --check is set 1
Otherwise 0

str(report) renders the human summary ("N files reformatted, M files left unchanged."). Under --check/--diff the verbs switch to the conditional mood ("would be reformatted").

6.2 The cache

Black caches results to skip unchanged files on subsequent runs (cache.py):

  • Location: platformdirs.user_cache_dir("black")/<black-version>/, overridable with BLACK_CACHE_DIR.
  • Keying: one cache file per Mode, named cache.<mode.get_cache_key()>.pickle. Changing line length, target version, preview flags, etc. produces a different cache file — so toggling options never serves stale results.
  • Invalidation: a file is considered changed if its size differs, or its mtime differs and its SHA-256 differs. (mtime-only changes with identical content don't trigger a reformat.)
  • Writes: atomic — written to a temp file in the same directory, then os.replaced in. Cache write failures are swallowed (OSError → no-op), so a read-only cache dir never breaks formatting.

Tip

In CI, persisting the Black cache between runs across an unchanged dependency set speeds up --check. Key any CI cache on the Black version and your pyproject.toml, mirroring Black's own keying.

6.3 Parallelism

reformat_many() (concurrency.py) handles multi-file runs:

  • Worker count = --workers, else BLACK_NUM_WORKERS, else os.cpu_count().
  • On Windows the count is capped at 60 (CPython limitation); in frozen/PyInstaller builds it drops to 1.
  • Uses a ProcessPoolExecutor; if the platform can't fork/spawn (AWS Lambda, Termux), it gracefully falls back to a single-worker ThreadPoolExecutor.
  • maybe_use_uvloop() swaps in uvloop/winloop if installed, else stock asyncio.
  • For --diff output across processes, a multiprocessing.Manager().Lock() serializes writes so diffs don't interleave.

6.4 Running blackd

pip install "black[d]"
blackd --bind-host=127.0.0.1 --bind-port=45484 --max-body-size=5242880
Operational concern Detail
Default bind localhost:45484.
Concurrency A ProcessPoolExecutor sized to os.cpu_count(), fronted by a bounded semaphore.
Body size limit --max-body-size, default 5 MiB; oversized requests are rejected by aiohttp.
Protocol Only X-Protocol-Version: 1 is accepted (else 501).
Shutdown Handles signals; closes the event loop on exit.
Client blackd.client.BlackDClient is a reference async client mapping options to X-* headers.

Status-code contract: 200 formatted · 204 unchanged · 400 syntax/header error · 500 unexpected error · 501 bad protocol version.


7. Security Analysis

Caution

Black executes as a developer/CI tool with the privileges of whoever runs it. It reads and overwrites source files in place. Treat the paths you hand it, and any config it reads, with the same trust you'd give arbitrary code in the repo.

7.1 Trust boundaries

Surface Exposure Notes
Source files Read + overwritten in place under --write (default). Use --check/--diff for a dry run before trusting a batch operation.
pyproject.toml Read and trusted. A malicious config can set regexes/flags but cannot execute code — TOML is data-only, parsed by tomllib/tomli.
.gitignore files Read. Parsed by pathspec; a malformed gitignore aborts the run with an error rather than misbehaving silently.
Symlinks Resolved; links pointing outside the project root are skipped. Prevents Black from following a symlink out of the tree (resolves_outside_root_or_cannot_stat).
Cache pickle Read with pickle.load. See the warning below.

Warning

The cache is a pickle file (cache.py). Pickle deserialization can execute arbitrary code if the file is attacker-controlled. The cache lives in a per-user, per-version cache directory, so the risk is low in normal use — but do not point BLACK_CACHE_DIR at a shared or untrusted location, and don't restore an untrusted cache into CI. Unpickling errors are caught and treated as a cache miss, but that does not protect against a deliberately malicious payload.

7.2 Correctness as a safety property

Black's defining safety feature is that it will not silently change your program's meaning:

  • assert_equivalent() re-parses source and output with Python's ast and compares a normalized stringification. A mismatch raises ASTSafetyError and the file is not written.
  • assert_stable() re-formats the output and asserts it doesn't change (idempotence).
  • Both run by default; --fast (or X-Fast-Or-Safe: fast in blackd) opts out for speed.

This means a Black bug surfaces as a loud internal error with a bug-report link and a diff dump — never as corrupted code on disk.

7.3 blackd network exposure

blackd has no authentication and no TLS. Its security model is "bind to localhost". The only access control is the CORS middleware, which rejects any Origin not listed in --cors-allow-origin (returning 403) and is otherwise permissive only to same-host, no-Origin clients.

Hardening checklist for blackd:

  • [ ] Bind to 127.0.0.1 (the default), never 0.0.0.0, unless fronted by an authenticating reverse proxy.
  • [ ] Set --max-body-size to bound memory from oversized requests.
  • [ ] Restrict --cors-allow-origin to the exact browser origins you trust; omit it entirely if no browser client exists.
  • [ ] Run it as an unprivileged user with no write access to anything important — it formats request bodies, not files, so it needs no source-tree access.
  • [ ] Treat it as a single-tenant developer convenience, not a multi-tenant service.

7.4 Supply chain

  • Black is a pure-data-config tool with a small, well-known dependency set, all version-pinned with minimums. The mypyc-compiled wheels are built in CI (cibuildwheel).
  • Pin Black's version across pyproject.toml, pre-commit rev, and the GitHub Action (version/use_pyproject) so formatting is reproducible and a compromised release can't silently change your diffs. --required-version enforces this at runtime.

8. Code Quality & Conventions

Aspect Practice
Type checking mypy in strict mode (strict_bytes, warn_unreachable, local_partial_types); python_version = "3.10".
Lint flake8 (with bugbear, comprehensions, simplify), isort (profile black), prettier for md/yaml/json — all wired through pre-commit.
Self-formatting Black formats itself, using unstable = true in its own [tool.black] (the project dogfoods its bleeding edge).
Tests pytest with --strict-config --strict-markers, xfail_strict, and filterwarnings = ["error"] (warnings are errors). Parallelized with pytest-xdist. Optional-dependency tests are gated by custom markers (no_blackd, no_jupyter).
Fuzzing hypothesis + hypothesmith generate random valid Python to stress the formatter.
Regression corpus diff-shades formats large real-world projects to catch unintended style changes.
Compilation Optional mypyc build for speed; several modules are explicitly excluded from compilation (blackd, __main__, debug.py, output.py, files.py, etc.).
Error model A small Rust-inspired Ok/Err/Result type (rusty.py) plus typed exceptions (InvalidInput, CannotSplit, CannotTransform, ASTSafetyError).

Note

The CST classes (Node, Leaf, BasePattern and its subclasses) and the StringTransformer hierarchy in trans.py are the most intricate parts of the codebase. The string transformers (StringMerger, StringSplitter, StringParenWrapper, StringParenStripper) implement the preview "string processing" style and are where most known preview bugs live.


9. Internals: How a Line Gets Split

Understanding line splitting is the key to understanding Black. The pipeline from CST to output text:

  1. LineGenerator.visit(node) walks the CST and yields Line objects. A Line is a sequence of Leafs plus depth/bracket metadata. Visit methods (visit_stmt, visit_decorators, visit_match_case, …) handle each construct.
  2. EmptyLineTracker.maybe_empty_lines(line) returns a LinesBlock describing how many blank lines precede this line, enforcing Black's blank-line rules (2 before top-level defs, 1 inside classes, special-casing stubs, etc.).
  3. transform_line(line, mode, features) decides whether the line fits in line_length. If not, it applies transforms in order — delimiter split, bracket split (left_hand_split/right_hand_split), optional-parenthesis insertion, and (in preview) string transforms — guided by BracketTracker delimiter priorities (brackets.py).
  4. Transforms communicate failure with CannotSplit/CannotTransform exceptions, letting Black try the next strategy.
  5. The magic trailing comma: if a bracket pair contains a trailing comma, the line is forced to explode regardless of length (unless --skip-magic-trailing-comma).

This is why Black's output is deterministic: every construct has exactly one preferred shape, and the splitter tries strategies in a fixed order until one produces a fitting, stable result — which the second pass and assert_stable then confirm.


10. Potential Improvements & Known Issues

The following are drawn from the project's public issue tracker as of this snapshot — useful context for integrators and contributors evaluating edge cases.

Open formatting issues

Issue Summary
#4349 Unnecessary parentheses added to an expression in an indexed assignment.
#3946 # pyright: ignore comments placed on the wrong line.
#2156 Request to use optional parentheses more often.
#4651 Preserve alignment of inline comments.
#809 Long lambda with many parameters reported as "line too long".
#5164 Unexpected version warning.

Open feature requests

Issue Summary
#359 Cython grammar support.
#3668 Exceptions to comment spacing.
#4025 Support for percent-format notebooks.
#5120 Make releases immutable.

Architectural observations

  • String-processing preview style is the main source of instability. The StringMerger/StringSplitter transforms (trans.py) are explicitly flagged UNSTABLE_FEATURES. Recently-closed bugs like #5058 (different code on the second pass with long type annotations) and #5001 (two distinct stable states for consecutive @overloads) live in this area. The forced second pass and assert_stable are the guardrails that keep these from shipping bad output.
  • # fmt: skip with multiline constructs has historically been fragile (#4513); the directive handling in comments.py is intricate and a good area for hardening.
  • The pickle cache (Section 7.1) is the one notable security sharp edge; a content-addressed or signed cache format would remove it.

11. Quick Reference

Key files

Path What lives there
src/black/__init__.py CLI + format orchestration + public API.
src/black/mode.py Mode, TargetVersion/Feature/Preview.
src/black/files.py Config & file discovery.
src/black/linegen.py, lines.py, trans.py, brackets.py The formatting engine.
src/black/concurrency.py Parallel formatting.
src/black/cache.py Incremental cache.
src/blackd/ The HTTP format server.
src/blib2to3/ The vendored CST parser.
pyproject.toml Deps, [tool.black], entry points, build config.
action.yml, action/main.py The GitHub Action.

Constants

Constant Value
DEFAULT_LINE_LENGTH 88
DEFAULT_INCLUDES (\.pyi?|\.ipynb)$
blackd default port 45484
blackd default max body 5 MiB
Supported target versions py33 … py315

Environment variables

Variable Effect
BLACK_CACHE_DIR Cache directory override.
BLACK_NUM_WORKERS Default worker-process count.
XDG_CONFIG_HOME User-config base on Unix.

Operational commands

# Dry-run check for CI (exit 1 if anything would change)
black --check --diff .

# Format with an explicit config and version guard
black --config=pyproject.toml --required-version=25.1.0 src/

# Maximum-throughput run
black --workers=$(nproc) .            # uvloop extra recommended

# Run the format server
blackd --bind-host=127.0.0.1 --bind-port=45484