# Types and Exceptions

Exception classes for error handling, constants for default configuration, type definitions, and utility functions for encoding detection and reporting functionality.

## Capabilities

### Exception Classes

Exception hierarchy for handling various error conditions during code formatting.

```python { .api }
class NothingChanged(UserWarning):
    """
    Raised when reformatted code is identical to source.
    
    Used to indicate that formatting was successful but no changes
    were needed. Commonly caught and handled gracefully in tooling.
    """
    pass

class InvalidInput(ValueError):
    """
    Raised when source code fails all parse attempts.
    
    Indicates that the input code contains syntax errors or uses
    unsupported Python syntax that cannot be parsed by Black.
    """
    pass

class ASTSafetyError(Exception):
    """
    Raised when Black's generated code is not equivalent to source AST.
    
    This indicates a bug in Black's formatting logic where the
    output is not semantically equivalent to the input.
    """
    pass
```

### Report Classes

Classes for tracking and reporting formatting results and statistics.

```python { .api }
class Changed(Enum):
    """File change status enumeration."""
    NO = 0      # File was not changed
    CACHED = 1  # File was not changed (result from cache)  
    YES = 2     # File was changed

@dataclass
class Report:
    """
    Tracks formatting results and statistics.
    
    Used by CLI and batch processing to accumulate results
    across multiple files and provide summary statistics.
    """
    check: bool = False
    diff: bool = False  
    quiet: bool = False
    verbose: bool = False
    change_count: int = 0
    same_count: int = 0
    failure_count: int = 0
    
    def done(self, src: Path, changed: Changed) -> None:
        """
        Record completion of file processing.
        
        Parameters:
        - src: Path of processed file
        - changed: Whether file was modified
        """
        
    def failed(self, src: Path, message: str) -> None:
        """
        Record processing failure.
        
        Parameters:
        - src: Path of failed file
        - message: Error message
        """
        
    def path_ignored(self, path: Path, message: str) -> None:
        """
        Record ignored path with reason.
        
        Parameters:
        - path: Ignored path
        - message: Reason for ignoring
        """
    
    @property
    def return_code(self) -> int:
        """
        Calculate exit code for CLI usage.
        
        Returns:
        - 0: Success (no changes needed or changes made successfully)
        - 1: Changes needed (when check=True) or formatting errors
        """
```

### Cache Management

Classes for managing formatting result caching to improve performance.

```python { .api }
@dataclass
class Cache:
    """
    File formatting cache management.
    
    Caches formatting results based on file content hash and
    configuration to avoid re-formatting unchanged files.
    """
    mode: Mode
    cache_file: Path
    file_data: dict[str, FileData] = field(default_factory=dict)
    
    @classmethod
    def read(cls, mode: Mode) -> 'Cache':
        """
        Read existing cache from disk.
        
        Parameters:
        - mode: Mode configuration for cache key generation
        
        Returns:
        Cache instance loaded from disk or new empty cache
        """
        
    @staticmethod  
    def hash_digest(path: Path) -> str:
        """
        Generate hash digest for file content.
        
        Parameters:
        - path: Path to file
        
        Returns:
        SHA-256 hash of file content and metadata
        """
        
    @staticmethod
    def get_file_data(path: Path) -> 'FileData':
        """
        Get file metadata for caching.
        
        Parameters:
        - path: Path to file
        
        Returns:
        FileData with hash and modification time
        """

@dataclass
class FileData:
    """File metadata for cache entries."""
    hash: str
    mtime: float
```

### Output Functions

Functions for formatted output with styling support.

```python { .api }
def out(message: Optional[str] = None, nl: bool = True, **styles: Any) -> None:
    """
    Standard output with optional styling.
    
    Parameters:
    - message: Message to output (None for newline only)
    - nl: Add newline after message
    - **styles: Click styling options (fg, bg, bold, etc.)
    """

def err(message: Optional[str] = None, nl: bool = True, **styles: Any) -> None:
    """
    Error output with optional styling.
    
    Parameters:  
    - message: Error message to output
    - nl: Add newline after message
    - **styles: Click styling options
    """

def diff(a: str, b: str, a_name: str, b_name: str) -> str:
    """
    Generate unified diff between strings.
    
    Parameters:
    - a: Original content
    - b: Modified content  
    - a_name: Name/label for original
    - b_name: Name/label for modified
    
    Returns:
    Unified diff string
    """

def color_diff(contents: str) -> str:
    """
    Add ANSI color codes to diff output.
    
    Parameters:
    - contents: Plain diff text
    
    Returns:
    Colorized diff with ANSI escape sequences
    """

def ipynb_diff(a: str, b: str, a_name: str, b_name: str) -> str:
    """
    Generate diff for Jupyter notebook cells.
    
    Parameters:
    - a: Original notebook cell content
    - b: Modified notebook cell content
    - a_name: Label for original
    - b_name: Label for modified
    
    Returns:
    Diff formatted for notebook context
    """

def dump_to_file(*output: str, ensure_final_newline: bool = True) -> str:
    """
    Write output to temporary file and return path.
    
    Parameters:
    - *output: Content strings to write
    - ensure_final_newline: Add final newline if missing
    
    Returns:
    Path to created temporary file
    """
```

### Parsing Utilities

Core parsing functions and AST processing utilities.

```python { .api }
def lib2to3_parse(src_txt: str, target_versions: Collection[TargetVersion] = ()) -> Node:
    """
    Parse Python source using lib2to3 with version targeting.
    
    Parameters:
    - src_txt: Python source code to parse
    - target_versions: Python versions to attempt parsing with
    
    Returns:
    lib2to3 Node representing parsed AST
    
    Raises:
    - InvalidInput: If parsing fails for all target versions
    """

def parse_ast(src: str) -> ast.AST:
    """
    Parse source to standard Python AST.
    
    Parameters:
    - src: Python source code
    
    Returns:
    Python ast.AST object
    
    Raises:
    - SyntaxError: If source contains syntax errors
    """

def stringify_ast(node: ast.AST) -> Iterator[str]:
    """
    Convert AST to comparable string representation.
    
    Parameters:
    - node: AST node to stringify
    
    Yields:
    String tokens representing AST structure
    """
```

## Constants

### Default Configuration Values

```python { .api }
# Line length default
DEFAULT_LINE_LENGTH: int = 88

# File pattern constants  
DEFAULT_EXCLUDES: str = r"/(\.direnv|\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|venv|\.svn|\.ipynb_checkpoints|_build|buck-out|build|dist)/"

DEFAULT_INCLUDES: str = r"(\.pyi?|\.ipynb)$"

# Stdin placeholder for file processing
STDIN_PLACEHOLDER: str = "__BLACK_STDIN_FILENAME__"
```

### Type Aliases

```python { .api }
# Content types
FileContent = str
Encoding = str  
NewLine = str

# Legacy compatibility
FileMode = Mode

# AST types (from blib2to3)
Node = blib2to3.pytree.Node
Leaf = blib2to3.pytree.Leaf
LN = Union[Leaf, Node]  # Leaf or Node
NL = blib2to3.pytree.Base  # Node or Leaf base

# Path types
PathLike = Union[str, Path, os.PathLike[str]]

# Collection types
Collection = typing.Collection
Sequence = typing.Sequence  
Iterator = typing.Iterator
```

## Usage Examples

### Exception Handling

```python
import black
from pathlib import Path

def safe_format_file(file_path: Path) -> tuple[bool, str]:
    """Safely format file with comprehensive error handling."""
    try:
        changed = black.format_file_in_place(
            file_path,
            fast=False,
            mode=black.Mode(),
            write_back=black.WriteBack.YES
        )
        return changed, "Success"
        
    except black.NothingChanged:
        return False, "No changes needed"
        
    except black.InvalidInput as e:
        return False, f"Syntax error: {e}"
        
    except black.ASTSafetyError as e:
        return False, f"AST safety check failed: {e}"
        
    except PermissionError:
        return False, "Permission denied"
        
    except Exception as e:
        return False, f"Unexpected error: {e}"

# Usage
changed, message = safe_format_file(Path("example.py"))
print(f"Result: {message}")
```

### Report Usage

```python
from pathlib import Path

# Create report for batch processing
report = black.Report(check=False, diff=True, verbose=True)

files = [Path("file1.py"), Path("file2.py"), Path("file3.py")]

for file_path in files:
    try:
        changed = black.format_file_in_place(
            file_path,
            fast=False,
            mode=black.Mode(),
            write_back=black.WriteBack.YES
        )
        
        # Record result
        status = black.Changed.YES if changed else black.Changed.NO
        report.done(file_path, status)
        
    except Exception as e:
        report.failed(file_path, str(e))

# Print summary  
print(f"Files changed: {report.change_count}")
print(f"Files unchanged: {report.same_count}")
print(f"Files failed: {report.failure_count}")
print(f"Exit code: {report.return_code}")
```

### Cache Usage

```python
# Read existing cache
mode = black.Mode(line_length=88)
cache = black.Cache.read(mode)

# Check if file needs formatting
file_path = Path("example.py")
file_data = black.Cache.get_file_data(file_path)
file_hash = black.Cache.hash_digest(file_path)

if file_hash in cache.file_data:
    print("File already processed (cached)")
else:
    print("File needs processing")
    # ... format file ...
    # Update cache with new file data
    cache.file_data[file_hash] = file_data
```

### Output Functions Usage

```python
import black

# Styled output
black.out("Formatting completed successfully", fg="green", bold=True)
black.err("Formatting failed", fg="red", bold=True)

# Generate and display diff
original = "def hello(name):print(f'Hello {name}!')"
formatted = black.format_str(original, mode=black.Mode())

diff_text = black.diff(original, formatted, "original", "formatted")
colored_diff = black.color_diff(diff_text)

print("Plain diff:")
print(diff_text)
print("\nColored diff:")
print(colored_diff)

# Write to temporary file
temp_path = black.dump_to_file(formatted, ensure_final_newline=True)
print(f"Wrote formatted code to: {temp_path}")
```

### Parsing and AST Usage

```python
import ast
import black

code = """
def example_function(x: int, y: str = "default") -> str:
    return f"{x}: {y}"
"""

# Parse with lib2to3 (Black's parser)
lib2to3_tree = black.lib2to3_parse(code)
print(f"lib2to3 tree type: {type(lib2to3_tree)}")

# Parse with standard AST
ast_tree = black.parse_ast(code)
print(f"AST tree type: {type(ast_tree)}")

# Convert AST to comparable format
ast_strings = list(black.stringify_ast(ast_tree))
print(f"AST representation: {ast_strings[:5]}...")  # First 5 tokens

# Use in equivalence checking
formatted = black.format_str(code, mode=black.Mode())
try:
    black.assert_equivalent(code, formatted)
    print("Formatted code is AST-equivalent to original")
except AssertionError as e:
    print(f"AST equivalence check failed: {e}")
```

### Type Checking Integration

```python
from typing import Union, Optional
import black

def format_code_safe(
    code: str,
    *,
    mode: Optional[black.Mode] = None,
    lines: Optional[black.Collection[tuple[int, int]]] = None
) -> Union[str, None]:
    """
    Type-safe code formatting function.
    
    Returns None if formatting fails, formatted string if successful.
    """
    if mode is None:
        mode = black.Mode()
    
    if lines is None:
        lines = ()
    
    try:
        return black.format_str(code, mode=mode, lines=lines)
    except (black.InvalidInput, black.NothingChanged):
        return None
    except Exception:
        return None

# Usage with type checking
result = format_code_safe("def hello(): pass")
if result is not None:
    print("Formatted successfully")
    print(result)
else:
    print("Formatting failed")
```