YAML parser and emitter for Python with complete YAML 1.1 support, Unicode handling, and optional LibYAML bindings for high performance
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
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
"""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
"""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
"""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)}")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}")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")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}")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}")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")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")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
""")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")Install with Tessl CLI
npx tessl i tessl/pypi-pyyaml