Apply Black formatting only in regions changed since last commit
npx @tessl/cli install tessl/pypi-darker@3.0.0Darker is a Python utility that reformats Python source code files by comparing an old revision of the source tree to a newer revision, applying formatting only to regions that have changed. This selective approach allows teams to adopt code formatters incrementally without disrupting unchanged code.
pip install darkerpip install darker[black] - for Black formattingpip install darker[ruff] - for Ruff formattingpip install darker[pyupgrade] - for Python syntax upgradingpip install darker[flynt] - for f-string conversionpip install darker[isort] - for import sortingpip install darker[color] - for colored outputimport darker
from darker import main, format_edited_parts
from darker.command_line import parse_command_line
from darker.config import DarkerConfig, OutputModeFor formatter integration:
from darker.formatters import create_formatter, get_formatter_names
from darker.formatters.base_formatter import BaseFormatter# Format only changed regions since last commit
darker --check --diff myproject.py
# Format multiple files with Black and isort
darker --black --isort src/
# Format with Ruff formatter
darker --formatter=ruff --check --diff .
# Format changes since specific revision
darker --revision=HEAD~2 --diff src/from darker import format_edited_parts
from darker.config import OutputMode, Exclusions
from darkgraylib.git import RevisionRange
from pathlib import Path
# Format edited parts of files
results = format_edited_parts(
common_root=Path("."),
paths={Path("myfile.py")},
exclusions=Exclusions(),
revrange=RevisionRange.parse_with_common_ancestor("HEAD", ":WORKTREE:"),
formatter=create_formatter("black"),
report_unmodified=False,
workers=1
)
for path, old_content, new_content in results:
if old_content != new_content:
print(f"Formatted {path}")Darker is built around several key components:
main() function orchestrates the entire formatting processEditedLinenumsDifferCore entry points for running darker formatting operations, including the main function that orchestrates the entire process and utility functions for file modification and output.
def main(argv: List[str] = None) -> int: ...
def main_with_error_handling() -> int: ...
def format_edited_parts(
root: Path,
changed_files: Collection[Path],
exclude: Exclusions,
revrange: RevisionRange,
formatter: BaseFormatter,
report_unmodified: bool,
workers: int = 1,
) -> Generator[Tuple[Path, TextDocument, TextDocument], None, None]: ...Command line argument parsing, configuration management, and option validation for the darker command-line tool.
def make_argument_parser(require_src: bool) -> ArgumentParser: ...
def parse_command_line(
argv: Optional[List[str]]
) -> Tuple[Namespace, DarkerConfig, DarkerConfig]: ...Configuration classes, validation functions, and output mode management for controlling darker's behavior through config files and command-line options.
class DarkerConfig(BaseConfig, total=False):
check: bool
diff: bool
flynt: bool
isort: bool
line_length: int
lint: list[str]
skip_magic_trailing_comma: bool
skip_string_normalization: bool
target_version: str
formatter: str
class OutputMode:
NOTHING = "NOTHING"
DIFF = "DIFF"
CONTENT = "CONTENT"
def validate_config_output_mode(
diff: bool,
stdout: bool
) -> OutputMode: ...Pluggable formatter system supporting multiple code formatters like Black, Ruff, and Pyupgrade through a common interface and entry point system.
def create_formatter(name: str) -> BaseFormatter: ...
def get_formatter_names() -> list[str]: ...
class BaseFormatter(ABC):
name: str
preserves_ast: bool
config_section: str
def read_config(self, src: tuple[str, ...], args: Namespace) -> None: ...
def run(self, content: TextDocument, path_from_cwd: Path) -> TextDocument: ...Git repository interaction, revision comparison, and modified file discovery for determining which files and regions need formatting.
def git_is_repository(path: Path) -> bool: ...
def git_get_modified_python_files(
paths: Collection[str],
revrange: RevisionRange,
cwd: Path,
) -> Set[str]: ...
class EditedLinenumsDiffer:
def __init__(self, root: Path, revrange: RevisionRange): ...
def revision_vs_lines(
self, path_in_repo: Path, content: TextDocument, context_lines: int
) -> List[int]: ...Integration with code pre-processors like isort for import sorting and flynt for f-string conversion, applied before main formatting.
def apply_isort(
content: TextDocument,
src: Tuple[str, ...],
config: str,
) -> TextDocument: ...
def apply_flynt(
content: TextDocument,
line_length: int,
) -> TextDocument: ...from typing import Tuple, List, Set, Collection, Generator, Optional
from pathlib import Path
from darkgraylib.utils import TextDocument
from darkgraylib.git import RevisionRange
# Type aliases
ProcessedDocument = Tuple[Path, TextDocument, TextDocument]
class Exclusions:
"""File exclusion patterns for pre-processing steps"""
formatter: Set[str]
isort: Set[str]
flynt: Set[str]
class TargetVersion(Enum):
"""Python version targets for formatting"""
PY33 = (3, 3)
PY34 = (3, 4)
# ... up to PY313class DependencyError(Exception):
"""Parent class for dependency problems"""
class IncompatiblePackageError(DependencyError):
"""Incompatible version of required package"""
class MissingPackageError(DependencyError):
"""Required/optional package is missing"""
class NotEquivalentError(Exception):
"""Two ASTs being compared are not equivalent"""