CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pyyaml

YAML parser and emitter for Python with complete YAML 1.1 support, Unicode handling, and optional LibYAML bindings for high performance

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

error-handling.mddocs/

Error Handling

Comprehensive exception hierarchy for handling different types of YAML processing errors with detailed position information. PyYAML provides specific exception types for each processing stage, enabling precise error handling and debugging.

Capabilities

Base Exception Classes

Foundation exception classes that other YAML errors inherit from.

class YAMLError(Exception):
    """
    Base exception class for all YAML errors.
    
    All PyYAML-specific exceptions inherit from this class, making it easy
    to catch any YAML-related error.
    """

class MarkedYAMLError(YAMLError):
    """
    Exception with position information in the YAML stream.
    
    Attributes:
        context (str): Context where the error occurred
        context_mark (Mark): Position of the context
        problem (str): Description of the problem
        problem_mark (Mark): Position where the problem was detected
        note (str): Additional notes about the error
    """

Processing Stage Errors

Specific exception types for each stage of YAML processing.

class ReaderError(YAMLError):
    """
    Errors during input stream reading.
    
    Raised when there are issues with:
    - Character encoding detection or conversion
    - Invalid Unicode sequences
    - I/O errors reading from streams
    """

class ScannerError(MarkedYAMLError):
    """
    Errors during lexical analysis (scanning).
    
    Raised when the scanner encounters:
    - Invalid character sequences
    - Malformed YAML syntax at the character level
    - Unclosed quotes or brackets
    """

class ParserError(MarkedYAMLError):
    """
    Errors during syntactic analysis (parsing).
    
    Raised when the parser encounters:
    - Invalid YAML document structure
    - Malformed block or flow constructs
    - Inconsistent indentation
    """

class ComposerError(MarkedYAMLError):
    """
    Errors during tree composition.
    
    Raised when composing the representation tree:
    - Duplicate anchor names
    - Invalid alias references
    - Circular references
    """

class ConstructorError(MarkedYAMLError):
    """
    Errors during object construction.
    
    Raised when constructing Python objects:
    - Unknown or invalid YAML tags
    - Type conversion failures
    - Security restrictions violated
    """

class RepresenterError(YAMLError):
    """
    Errors during object representation.
    
    Raised when representing Python objects:
    - Objects that cannot be represented
    - Circular references in data structures
    - Type representation conflicts
    """

class EmitterError(YAMLError):
    """
    Errors during YAML text emission.
    
    Raised when emitting YAML:
    - I/O errors writing to streams
    - Encoding issues
    - Invalid emitter state
    """

class SerializerError(YAMLError):
    """
    Errors during tree serialization.
    
    Raised when serializing representation trees:
    - Invalid node structures
    - Serialization state conflicts
    """

Position Tracking

Detailed position information for debugging YAML syntax errors.

class Mark:
    """
    Represents a position in the YAML stream.
    
    Attributes:
        name (str): Name of the input source (filename, etc.)
        index (int): Character index in the stream
        line (int): Line number (0-based)
        column (int): Column number (0-based)
        buffer (str): Buffer content around the position
        pointer (int): Pointer position in the buffer
    """
    
    def get_snippet(self, indent=4, max_length=75):
        """
        Get a snippet of the input around this position.
        
        Args:
            indent (int): Number of spaces to indent the snippet
            max_length (int): Maximum length of the snippet
            
        Returns:
            str: Formatted snippet showing the error location
        """

Usage Examples

Basic Error Handling

import yaml

def safe_load_yaml(content):
    """Safely load YAML with comprehensive error handling."""
    try:
        return yaml.safe_load(content)
    except yaml.YAMLError as e:
        print(f"YAML Error: {e}")
        return None
    except Exception as e:
        print(f"Unexpected error: {e}")
        return None

# Test with various error conditions
test_cases = [
    "valid: yaml",                    # Valid YAML
    "invalid: [unclosed list",        # ScannerError  
    "duplicate: key\nduplicate: key", # ComposerError (in some contexts)
    "invalid:\n  - item\n not_aligned", # ParserError
]

for i, content in enumerate(test_cases):
    print(f"Test {i + 1}: {safe_load_yaml(content)}")

Detailed Error Information

import yaml

yaml_content = """
name: John Doe
items:
  - item1
  - item2
    - nested  # Invalid indentation
"""

try:
    data = yaml.safe_load(yaml_content)
except yaml.MarkedYAMLError as e:
    print(f"Error Type: {type(e).__name__}")
    print(f"Problem: {e.problem}")
    
    if e.problem_mark:
        mark = e.problem_mark
        print(f"Position: Line {mark.line + 1}, Column {mark.column + 1}")
        print(f"Character index: {mark.index}")
        
        # Show snippet if available
        snippet = mark.get_snippet()
        if snippet:
            print("Context:")
            print(snippet)
    
    if e.context:
        print(f"Context: {e.context}")
        if e.context_mark:
            ctx_mark = e.context_mark
            print(f"Context position: Line {ctx_mark.line + 1}, Column {ctx_mark.column + 1}")

except yaml.YAMLError as e:
    print(f"YAML Error: {e}")

Specific Error Type Handling

import yaml

def load_config_file(filename):
    """Load configuration with specific error handling."""
    try:
        with open(filename, 'r') as f:
            return yaml.safe_load(f)
            
    except FileNotFoundError:
        print(f"Configuration file not found: {filename}")
        return {}
        
    except yaml.ReaderError as e:
        print(f"File encoding error in {filename}: {e}")
        return None
        
    except yaml.ScannerError as e:
        print(f"YAML syntax error in {filename}:")
        print(f"  Problem: {e.problem}")
        if e.problem_mark:
            print(f"  Line {e.problem_mark.line + 1}, Column {e.problem_mark.column + 1}")
        return None
        
    except yaml.ParserError as e:
        print(f"YAML structure error in {filename}:")
        print(f"  Problem: {e.problem}")
        if e.problem_mark:
            print(f"  Line {e.problem_mark.line + 1}, Column {e.problem_mark.column + 1}")
        return None
        
    except yaml.ConstructorError as e:
        print(f"YAML construction error in {filename}:")
        print(f"  Problem: {e.problem}")
        if e.problem_mark:
            print(f"  Line {e.problem_mark.line + 1}, Column {e.problem_mark.column + 1}")
        return None
        
    except yaml.YAMLError as e:
        print(f"Other YAML error in {filename}: {e}")
        return None

# Usage
config = load_config_file('app.yaml')
if config is not None:
    print("Configuration loaded successfully")

Custom Error Handling in Constructors

import yaml
from datetime import datetime

def strict_datetime_constructor(loader, node):
    """Strict datetime constructor with custom error handling."""
    try:
        value = loader.construct_scalar(node)
        return datetime.fromisoformat(value)
    except ValueError as e:
        # Convert to ConstructorError with position info
        raise yaml.ConstructorError(
            None, None,
            f"Invalid datetime format: {e}",
            node.start_mark
        )

yaml.add_constructor('!datetime', strict_datetime_constructor)

yaml_content = """
created: !datetime 2023-01-01T10:00:00
invalid: !datetime not-a-date
"""

try:
    data = yaml.load(yaml_content, yaml.Loader)
except yaml.ConstructorError as e:
    print(f"Constructor error: {e.problem}")
    if e.problem_mark:
        print(f"At line {e.problem_mark.line + 1}, column {e.problem_mark.column + 1}")

Validation with Error Context

import yaml

class ValidatingLoader(yaml.SafeLoader):
    """Loader that validates data structure during construction."""
    pass

def validated_mapping_constructor(loader, node):
    """Construct mapping with validation."""
    # First construct normally
    mapping = loader.construct_mapping(node, deep=True)
    
    # Then validate
    if 'name' not in mapping:
        raise yaml.ConstructorError(
            "while constructing a validated mapping", node.start_mark,
            "missing required field 'name'", node.start_mark
        )
    
    if not isinstance(mapping.get('age'), int):
        raise yaml.ConstructorError(
            "while constructing a validated mapping", node.start_mark,
            "field 'age' must be an integer", node.start_mark
        )
    
    return mapping

ValidatingLoader.add_constructor('!person', validated_mapping_constructor)

yaml_content = """
person1: !person
  name: John
  age: 30

person2: !person
  name: Jane
  age: "not a number"  # This will cause an error
"""

try:
    data = yaml.load(yaml_content, ValidatingLoader)
except yaml.ConstructorError as e:
    print(f"Validation error: {e.problem}")
    print(f"Context: {e.context}")
    if e.problem_mark:
        print(f"Position: Line {e.problem_mark.line + 1}")

Error Recovery Strategies

Partial Loading

import yaml

def load_yaml_partially(content):
    """Load YAML documents, skipping invalid ones."""
    documents = []
    errors = []
    
    try:
        for doc in yaml.safe_load_all(content):
            documents.append(doc)
    except yaml.YAMLError as e:
        errors.append(e)
    
    return documents, errors

multi_doc_yaml = """
---
valid: document1
---
invalid: [unclosed
---
valid: document3
"""

docs, errs = load_yaml_partially(multi_doc_yaml)
print(f"Loaded {len(docs)} documents, {len(errs)} errors")

Fallback Loading

import yaml

def load_with_fallback(content):
    """Try different loaders in order of safety."""
    loaders = [yaml.SafeLoader, yaml.FullLoader, yaml.Loader]
    
    for loader_class in loaders:
        try:
            return yaml.load(content, Loader=loader_class), loader_class.__name__
        except yaml.ConstructorError:
            continue  # Try next loader
        except yaml.YAMLError:
            break  # Syntax error, don't try other loaders
    
    return None, None

# YAML with Python-specific tag
yaml_content = """
data: !!python/tuple [1, 2, 3]
"""

result, loader_used = load_with_fallback(yaml_content)
if result:
    print(f"Loaded successfully with {loader_used}")
else:
    print("Failed to load with any loader")

Debugging Techniques

Verbose Error Reporting

import yaml
import traceback

def debug_yaml_load(content, filename="<string>"):
    """Load YAML with detailed debugging information."""
    try:
        return yaml.safe_load(content)
    except yaml.MarkedYAMLError as e:
        print(f"=== YAML Error in {filename} ===")
        print(f"Error Type: {type(e).__name__}")
        print(f"Problem: {e.problem}")
        
        if e.problem_mark:
            mark = e.problem_mark
            print(f"Position: Line {mark.line + 1}, Column {mark.column + 1}")
            
            # Show context lines
            lines = content.split('\n')
            start_line = max(0, mark.line - 2)
            end_line = min(len(lines), mark.line + 3)
            
            print("\nContext:")
            for i in range(start_line, end_line):
                prefix = ">>> " if i == mark.line else "    "
                print(f"{prefix}{i + 1:3}: {lines[i]}")
                if i == mark.line:
                    print(f"    {' ' * (3 + mark.column)}^")
        
        print(f"\nPython traceback:")
        traceback.print_exc()
        return None
        
    except Exception as e:
        print(f"Unexpected error: {e}")
        traceback.print_exc()
        return None

# Test with error
debug_yaml_load("""
name: John
items:
  - one
  - two
    - invalid indentation
""")

Processing Stage Identification

import yaml

def identify_processing_stage(content):
    """Identify which processing stage fails."""
    try:
        # Test scanning
        list(yaml.scan(content, yaml.SafeLoader))
        print("✓ Scanning successful")
        
        # Test parsing
        list(yaml.parse(content, yaml.SafeLoader))
        print("✓ Parsing successful")
        
        # Test composition
        yaml.compose(content, yaml.SafeLoader)
        print("✓ Composition successful")
        
        # Test construction
        yaml.safe_load(content)
        print("✓ Construction successful")
        
    except yaml.ScannerError as e:
        print(f"✗ Scanning failed: {e.problem}")
    except yaml.ParserError as e:
        print(f"✗ Parsing failed: {e.problem}")
    except yaml.ComposerError as e:
        print(f"✗ Composition failed: {e.problem}")
    except yaml.ConstructorError as e:
        print(f"✗ Construction failed: {e.problem}")

# Test problematic YAML
identify_processing_stage("invalid: [unclosed")

Best Practices

Error Handling Guidelines

  1. Always handle YAMLError as the base exception type
  2. Use specific exception types when you need different handling
  3. Check for position information to provide better error messages
  4. Validate data after loading rather than relying solely on YAML validation
  5. Provide meaningful error messages to users

Production Error Handling

  1. Log errors with context for debugging
  2. Provide fallback behavior for non-critical failures
  3. Sanitize error messages before showing to users
  4. Monitor error patterns to identify common issues
  5. Have recovery strategies for partial failures

Development and Testing

  1. Test error conditions explicitly in unit tests
  2. Use debug modes during development
  3. Validate error message quality and usefulness
  4. Test with malformed input to ensure robustness
  5. Document expected error scenarios for API users

Install with Tessl CLI

npx tessl i tessl/pypi-pyyaml

docs

customization.md

dumping-serialization.md

error-handling.md

index.md

loaders-dumpers.md

loading-parsing.md

safe-operations.md

tile.json