CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-black

The uncompromising code formatter.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

cli.mddocs/

Command Line Interface

Complete CLI implementation providing file discovery, configuration loading, batch processing, and various output modes for formatting Python code from the command line.

Capabilities

Main CLI Entry Point

The primary command-line interface function with comprehensive option parsing and file processing.

def main(
    ctx: click.Context,
    code: Optional[str] = None,
    line_length: int = DEFAULT_LINE_LENGTH,
    target_version: list[TargetVersion] = [],
    pyi: bool = False,
    ipynb: bool = False,
    python_cell_magics: Sequence[str] = [],
    skip_source_first_line: bool = False,
    skip_string_normalization: bool = False,
    skip_magic_trailing_comma: bool = False,
    preview: bool = False,
    unstable: bool = False,
    enable_unstable_feature: list[Preview] = [],
    check: bool = False,
    diff: bool = False,
    color: bool = False,
    line_ranges: Sequence[str] = [],
    fast: bool = False,
    required_version: Optional[str] = None,
    exclude: Optional[Pattern[str]] = None,
    extend_exclude: Optional[Pattern[str]] = None,
    force_exclude: Optional[Pattern[str]] = None,
    stdin_filename: Optional[str] = None,
    include: Pattern[str] = DEFAULT_INCLUDES,
    workers: Optional[int] = None,
    quiet: bool = False,
    verbose: bool = False,
    src: tuple[str, ...] = (),
    config: Optional[str] = None
) -> None:
    """
    Main CLI entry point with comprehensive option parsing.

    Parameters:
    - ctx: Click context for CLI handling
    - code: Optional code string to format (instead of files)  
    - line_length: Maximum line length (default 88)
    - target_version: List of Python versions to target
    - pyi: Format as .pyi stub file
    - ipynb: Format as Jupyter notebook
    - python_cell_magics: IPython cell magics to recognize as Python
    - skip_source_first_line: Skip shebang lines
    - skip_string_normalization: Don't normalize string quotes
    - skip_magic_trailing_comma: Don't use trailing comma splitting
    - preview: Enable preview features
    - unstable: Enable unstable features  
    - enable_unstable_feature: List of specific preview features to enable
    - check: Exit with error if reformatting needed
    - diff: Output unified diff instead of rewriting
    - color: Colorize diff output
    - line_ranges: Line ranges to format (e.g., "1-10,20-30")
    - fast: Skip AST safety checks
    - required_version: Require specific Black version
    - exclude: File pattern to exclude (regex)
    - extend_exclude: Additional exclude pattern
    - force_exclude: Force exclude pattern (overrides include)
    - stdin_filename: Filename for stdin input
    - include: File pattern to include (default: Python files)
    - workers: Number of parallel workers
    - quiet: Minimal output
    - verbose: Detailed output
    - src: Source files/directories to format
    - config: Configuration file path

    Note:
    Decorated with comprehensive Click options for all CLI functionality
    """

File Discovery

Functions for discovering and filtering source files to format.

def get_sources(
    *,
    root: Path,
    src: tuple[str, ...], 
    quiet: bool,
    verbose: bool,
    include: Pattern[str],
    exclude: Optional[Pattern[str]],
    extend_exclude: Optional[Pattern[str]], 
    force_exclude: Optional[Pattern[str]],
    report: Report,
    stdin_filename: Optional[str]
) -> set[Path]:
    """
    Compute the set of files to format.

    Parameters:
    - root: Project root directory
    - src: Source file/directory paths
    - quiet: Suppress output messages
    - verbose: Enable detailed output
    - include: File pattern to include
    - exclude: Base exclude pattern
    - extend_exclude: Additional exclude pattern
    - force_exclude: Force exclude (overrides include)
    - report: Report object for tracking results
    - stdin_filename: Filename when reading from stdin

    Returns:
    Set of Path objects to format

    Note:
    - Automatically discovers .py, .pyi, and .ipynb files
    - Respects .gitignore patterns
    - Handles recursive directory traversal
    """

Configuration Loading

Functions for loading and parsing Black configuration from pyproject.toml files.

def read_pyproject_toml(ctx: click.Context, param: click.Parameter, value: Optional[str]) -> Optional[str]:
    """
    Parse Black configuration from pyproject.toml files.

    Parameters:
    - ctx: Click context
    - param: Click parameter being processed
    - value: Path to configuration file (optional)

    Returns:
    Path to found configuration file, or None

    Note:
    - Searches for pyproject.toml in project hierarchy
    - Loads [tool.black] section configuration
    - Merges with command-line arguments
    """

def validate_regex(ctx: click.Context, param: click.Parameter, value: Optional[str]) -> Optional[Pattern[str]]:
    """
    Validate and compile regex patterns for file filtering.

    Parameters:
    - ctx: Click context
    - param: Click parameter being validated
    - value: Regex pattern string

    Returns:
    Compiled regex Pattern object, or None

    Raises:
    - click.BadParameter: If regex is invalid
    """

File Processing Functions

Project Discovery

Utilities from the black.files module for project structure analysis.

def find_project_root(srcs: tuple[str, ...], stdin_filename: Optional[str] = None) -> tuple[Path, str]:
    """
    Find the project root directory and reason.

    Parameters:
    - srcs: Source file/directory paths
    - stdin_filename: Filename for stdin processing

    Returns:
    Tuple of (project_root_path, reason_string)
    """

def find_pyproject_toml(path_search_start: tuple[str, ...], stdin_filename: Optional[str] = None) -> Optional[str]:
    """
    Find pyproject.toml configuration file.

    Parameters:
    - path_search_start: Paths to start search from  
    - stdin_filename: Filename for stdin processing

    Returns:
    Path to pyproject.toml file, or None if not found
    """

def find_user_pyproject_toml() -> Path:
    """
    Find user-level pyproject.toml configuration.

    Returns:
    Path to user configuration file
    """

def parse_pyproject_toml(path_config: str) -> dict[str, Any]:
    """
    Parse Black configuration from pyproject.toml file.

    Parameters:
    - path_config: Path to configuration file

    Returns:  
    Dictionary of configuration values

    Raises:
    - OSError: If file cannot be read
    - tomllib.TOMLDecodeError: If TOML is invalid
    """

File Generation and Filtering

def gen_python_files(
    paths: Iterable[Path],
    root: Path,
    include: Pattern[str], 
    exclude: Pattern[str],
    extend_exclude: Optional[Pattern[str]],
    force_exclude: Optional[Pattern[str]],
    report: Report,
    gitignore: Optional[PathSpec] = None
) -> Iterator[Path]:
    """
    Generate Python files from given paths with filtering.

    Parameters:
    - paths: Iterable of source paths
    - root: Project root directory
    - include: Include pattern
    - exclude: Exclude pattern  
    - extend_exclude: Additional exclude pattern
    - force_exclude: Force exclude pattern
    - report: Report object for tracking
    - gitignore: Optional gitignore patterns

    Yields:
    Path objects for files to process
    """

def get_gitignore(root: Path) -> PathSpec:
    """
    Get gitignore patterns for the project.

    Parameters:
    - root: Project root directory

    Returns:
    PathSpec object with gitignore patterns
    """

def path_is_excluded(
    normalized_path: str,
    pattern: Optional[Pattern[str]]
) -> bool:
    """
    Check if path matches exclusion pattern.

    Parameters:
    - normalized_path: Normalized file path
    - pattern: Regex pattern to match against

    Returns:
    True if path should be excluded
    """

Line Range Processing

Functions for parsing and handling line range specifications.

def parse_line_ranges(line_ranges: Sequence[str]) -> list[tuple[int, int]]:
    """
    Parse line range specifications from CLI arguments.

    Parameters:
    - line_ranges: List of line range strings (e.g., ["1-10", "20-30"])

    Returns:
    List of (start_line, end_line) tuples

    Note:
    - Supports formats: "N", "N-M", "N-", "-M"
    - Line numbers are 1-indexed
    - Overlapping ranges are merged
    
    Raises:
    - ValueError: If range format is invalid
    """

def sanitized_lines(
    src_contents: str,
    lines: Collection[tuple[int, int]]
) -> Collection[tuple[int, int]]:
    """
    Sanitize line ranges against actual file content.

    Parameters:
    - src_contents: Source file content
    - lines: Line ranges to sanitize

    Returns:
    Sanitized line ranges within file bounds
    """

def adjusted_lines(
    original_lines: Collection[tuple[int, int]],
    src_contents: str,
    dst_contents: str
) -> Collection[tuple[int, int]]:
    """
    Adjust line ranges after formatting changes.

    Parameters:
    - original_lines: Original line ranges
    - src_contents: Original file content
    - dst_contents: Formatted file content

    Returns:
    Adjusted line ranges accounting for formatting changes
    """

Usage Examples

Basic CLI Usage

import subprocess
import sys

# Format single file
result = subprocess.run([
    sys.executable, "-m", "black", "script.py"
], capture_output=True, text=True)

# Format with options
result = subprocess.run([
    sys.executable, "-m", "black", 
    "--line-length", "79",
    "--target-version", "py39",
    "--diff",
    "src/"
], capture_output=True, text=True)

print(result.stdout)

Programmatic CLI Access

import click
import black

# Create Click context and call main
ctx = click.Context(black.main)
try:
    black.main.invoke(ctx, 
        src=('src/',),
        line_length=79,
        target_version=[black.TargetVersion.PY39],
        diff=True,
        check=False
    )
except SystemExit as e:
    print(f"Exit code: {e.code}")

File Discovery

from pathlib import Path
import re

# Setup for file discovery
root = Path(".")
src_paths = ("src/", "tests/")
include = re.compile(r"\.pyi?$")
exclude = re.compile(r"/(\.git|__pycache__|build)/")

report = black.Report()

# Find files to format
sources = black.get_sources(
    root=root,
    src=src_paths,
    quiet=False,
    verbose=True, 
    include=include,
    exclude=exclude,
    extend_exclude=None,
    force_exclude=None,
    report=report,
    stdin_filename=None
)

print(f"Found {len(sources)} files to format:")
for source in sorted(sources):
    print(f"  {source}")

Configuration Loading

# Find and load configuration
config_path = black.find_pyproject_toml((".",), stdin_filename=None)
if config_path:
    config = black.parse_pyproject_toml(config_path)
    print(f"Loaded config from {config_path}:")
    print(config)
    
    # Apply config to mode
    mode_kwargs = {}
    if "line_length" in config:
        mode_kwargs["line_length"] = config["line_length"]
    if "target_version" in config:
        target_versions = {
            black.TargetVersion[v.upper().replace(".", "")]
            for v in config["target_version"]
        }
        mode_kwargs["target_versions"] = target_versions
    
    mode = black.Mode(**mode_kwargs)

Line Range Formatting

# Parse line ranges from CLI-style strings
range_specs = ["1-10", "20-25", "50-"]
line_ranges = black.parse_line_ranges(range_specs)
print(f"Parsed ranges: {line_ranges}")

# Format specific lines only
code = open("example.py").read()
formatted = black.format_str(
    code, 
    mode=black.Mode(),
    lines=line_ranges
)

Batch Processing

from concurrent.futures import ThreadPoolExecutor
from pathlib import Path

def format_file(file_path: Path) -> bool:
    """Format a single file and return if changed."""
    try:
        return black.format_file_in_place(
            file_path,
            fast=False,
            mode=black.Mode(),
            write_back=black.WriteBack.YES
        )
    except Exception as e:
        print(f"Error formatting {file_path}: {e}")
        return False

# Process files in parallel
source_files = list(Path("src").glob("**/*.py"))
with ThreadPoolExecutor(max_workers=4) as executor:
    results = list(executor.map(format_file, source_files))

changed_count = sum(results)
print(f"Formatted {changed_count} files")

Custom Output Handling

import io
import contextlib

# Capture CLI output
output_buffer = io.StringIO()
with contextlib.redirect_stdout(output_buffer):
    try:
        ctx = click.Context(black.main)
        black.main.invoke(ctx,
            src=('example.py',),
            diff=True,
            color=False
        )
    except SystemExit:
        pass

diff_output = output_buffer.getvalue()
print("Captured diff:")
print(diff_output)

Types

# Path processing types
PathLike = Union[str, Path]
PathSpec = pathspec.PathSpec
Pattern = re.Pattern[str]

# File processing
FileList = set[Path]
SourcePaths = tuple[str, ...]

# Configuration
ConfigDict = dict[str, Any]
LineRange = tuple[int, int]
LineRanges = Collection[LineRange]

Install with Tessl CLI

npx tessl i tessl/pypi-black

docs

cli.md

configuration.md

formatting.md

index.md

jupyter.md

server.md

types.md

tile.json