0
# Error Handling
1
2
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.
3
4
## Capabilities
5
6
### Base Exception Classes
7
8
Foundation exception classes that other YAML errors inherit from.
9
10
```python { .api }
11
class YAMLError(Exception):
12
"""
13
Base exception class for all YAML errors.
14
15
All PyYAML-specific exceptions inherit from this class, making it easy
16
to catch any YAML-related error.
17
"""
18
19
class MarkedYAMLError(YAMLError):
20
"""
21
Exception with position information in the YAML stream.
22
23
Attributes:
24
context (str): Context where the error occurred
25
context_mark (Mark): Position of the context
26
problem (str): Description of the problem
27
problem_mark (Mark): Position where the problem was detected
28
note (str): Additional notes about the error
29
"""
30
```
31
32
### Processing Stage Errors
33
34
Specific exception types for each stage of YAML processing.
35
36
```python { .api }
37
class ReaderError(YAMLError):
38
"""
39
Errors during input stream reading.
40
41
Raised when there are issues with:
42
- Character encoding detection or conversion
43
- Invalid Unicode sequences
44
- I/O errors reading from streams
45
"""
46
47
class ScannerError(MarkedYAMLError):
48
"""
49
Errors during lexical analysis (scanning).
50
51
Raised when the scanner encounters:
52
- Invalid character sequences
53
- Malformed YAML syntax at the character level
54
- Unclosed quotes or brackets
55
"""
56
57
class ParserError(MarkedYAMLError):
58
"""
59
Errors during syntactic analysis (parsing).
60
61
Raised when the parser encounters:
62
- Invalid YAML document structure
63
- Malformed block or flow constructs
64
- Inconsistent indentation
65
"""
66
67
class ComposerError(MarkedYAMLError):
68
"""
69
Errors during tree composition.
70
71
Raised when composing the representation tree:
72
- Duplicate anchor names
73
- Invalid alias references
74
- Circular references
75
"""
76
77
class ConstructorError(MarkedYAMLError):
78
"""
79
Errors during object construction.
80
81
Raised when constructing Python objects:
82
- Unknown or invalid YAML tags
83
- Type conversion failures
84
- Security restrictions violated
85
"""
86
87
class RepresenterError(YAMLError):
88
"""
89
Errors during object representation.
90
91
Raised when representing Python objects:
92
- Objects that cannot be represented
93
- Circular references in data structures
94
- Type representation conflicts
95
"""
96
97
class EmitterError(YAMLError):
98
"""
99
Errors during YAML text emission.
100
101
Raised when emitting YAML:
102
- I/O errors writing to streams
103
- Encoding issues
104
- Invalid emitter state
105
"""
106
107
class SerializerError(YAMLError):
108
"""
109
Errors during tree serialization.
110
111
Raised when serializing representation trees:
112
- Invalid node structures
113
- Serialization state conflicts
114
"""
115
```
116
117
### Position Tracking
118
119
Detailed position information for debugging YAML syntax errors.
120
121
```python { .api }
122
class Mark:
123
"""
124
Represents a position in the YAML stream.
125
126
Attributes:
127
name (str): Name of the input source (filename, etc.)
128
index (int): Character index in the stream
129
line (int): Line number (0-based)
130
column (int): Column number (0-based)
131
buffer (str): Buffer content around the position
132
pointer (int): Pointer position in the buffer
133
"""
134
135
def get_snippet(self, indent=4, max_length=75):
136
"""
137
Get a snippet of the input around this position.
138
139
Args:
140
indent (int): Number of spaces to indent the snippet
141
max_length (int): Maximum length of the snippet
142
143
Returns:
144
str: Formatted snippet showing the error location
145
"""
146
```
147
148
## Usage Examples
149
150
### Basic Error Handling
151
152
```python
153
import yaml
154
155
def safe_load_yaml(content):
156
"""Safely load YAML with comprehensive error handling."""
157
try:
158
return yaml.safe_load(content)
159
except yaml.YAMLError as e:
160
print(f"YAML Error: {e}")
161
return None
162
except Exception as e:
163
print(f"Unexpected error: {e}")
164
return None
165
166
# Test with various error conditions
167
test_cases = [
168
"valid: yaml", # Valid YAML
169
"invalid: [unclosed list", # ScannerError
170
"duplicate: key\nduplicate: key", # ComposerError (in some contexts)
171
"invalid:\n - item\n not_aligned", # ParserError
172
]
173
174
for i, content in enumerate(test_cases):
175
print(f"Test {i + 1}: {safe_load_yaml(content)}")
176
```
177
178
### Detailed Error Information
179
180
```python
181
import yaml
182
183
yaml_content = """
184
name: John Doe
185
items:
186
- item1
187
- item2
188
- nested # Invalid indentation
189
"""
190
191
try:
192
data = yaml.safe_load(yaml_content)
193
except yaml.MarkedYAMLError as e:
194
print(f"Error Type: {type(e).__name__}")
195
print(f"Problem: {e.problem}")
196
197
if e.problem_mark:
198
mark = e.problem_mark
199
print(f"Position: Line {mark.line + 1}, Column {mark.column + 1}")
200
print(f"Character index: {mark.index}")
201
202
# Show snippet if available
203
snippet = mark.get_snippet()
204
if snippet:
205
print("Context:")
206
print(snippet)
207
208
if e.context:
209
print(f"Context: {e.context}")
210
if e.context_mark:
211
ctx_mark = e.context_mark
212
print(f"Context position: Line {ctx_mark.line + 1}, Column {ctx_mark.column + 1}")
213
214
except yaml.YAMLError as e:
215
print(f"YAML Error: {e}")
216
```
217
218
### Specific Error Type Handling
219
220
```python
221
import yaml
222
223
def load_config_file(filename):
224
"""Load configuration with specific error handling."""
225
try:
226
with open(filename, 'r') as f:
227
return yaml.safe_load(f)
228
229
except FileNotFoundError:
230
print(f"Configuration file not found: {filename}")
231
return {}
232
233
except yaml.ReaderError as e:
234
print(f"File encoding error in {filename}: {e}")
235
return None
236
237
except yaml.ScannerError as e:
238
print(f"YAML syntax error in {filename}:")
239
print(f" Problem: {e.problem}")
240
if e.problem_mark:
241
print(f" Line {e.problem_mark.line + 1}, Column {e.problem_mark.column + 1}")
242
return None
243
244
except yaml.ParserError as e:
245
print(f"YAML structure error in {filename}:")
246
print(f" Problem: {e.problem}")
247
if e.problem_mark:
248
print(f" Line {e.problem_mark.line + 1}, Column {e.problem_mark.column + 1}")
249
return None
250
251
except yaml.ConstructorError as e:
252
print(f"YAML construction error in {filename}:")
253
print(f" Problem: {e.problem}")
254
if e.problem_mark:
255
print(f" Line {e.problem_mark.line + 1}, Column {e.problem_mark.column + 1}")
256
return None
257
258
except yaml.YAMLError as e:
259
print(f"Other YAML error in {filename}: {e}")
260
return None
261
262
# Usage
263
config = load_config_file('app.yaml')
264
if config is not None:
265
print("Configuration loaded successfully")
266
```
267
268
### Custom Error Handling in Constructors
269
270
```python
271
import yaml
272
from datetime import datetime
273
274
def strict_datetime_constructor(loader, node):
275
"""Strict datetime constructor with custom error handling."""
276
try:
277
value = loader.construct_scalar(node)
278
return datetime.fromisoformat(value)
279
except ValueError as e:
280
# Convert to ConstructorError with position info
281
raise yaml.ConstructorError(
282
None, None,
283
f"Invalid datetime format: {e}",
284
node.start_mark
285
)
286
287
yaml.add_constructor('!datetime', strict_datetime_constructor)
288
289
yaml_content = """
290
created: !datetime 2023-01-01T10:00:00
291
invalid: !datetime not-a-date
292
"""
293
294
try:
295
data = yaml.load(yaml_content, yaml.Loader)
296
except yaml.ConstructorError as e:
297
print(f"Constructor error: {e.problem}")
298
if e.problem_mark:
299
print(f"At line {e.problem_mark.line + 1}, column {e.problem_mark.column + 1}")
300
```
301
302
### Validation with Error Context
303
304
```python
305
import yaml
306
307
class ValidatingLoader(yaml.SafeLoader):
308
"""Loader that validates data structure during construction."""
309
pass
310
311
def validated_mapping_constructor(loader, node):
312
"""Construct mapping with validation."""
313
# First construct normally
314
mapping = loader.construct_mapping(node, deep=True)
315
316
# Then validate
317
if 'name' not in mapping:
318
raise yaml.ConstructorError(
319
"while constructing a validated mapping", node.start_mark,
320
"missing required field 'name'", node.start_mark
321
)
322
323
if not isinstance(mapping.get('age'), int):
324
raise yaml.ConstructorError(
325
"while constructing a validated mapping", node.start_mark,
326
"field 'age' must be an integer", node.start_mark
327
)
328
329
return mapping
330
331
ValidatingLoader.add_constructor('!person', validated_mapping_constructor)
332
333
yaml_content = """
334
person1: !person
335
name: John
336
age: 30
337
338
person2: !person
339
name: Jane
340
age: "not a number" # This will cause an error
341
"""
342
343
try:
344
data = yaml.load(yaml_content, ValidatingLoader)
345
except yaml.ConstructorError as e:
346
print(f"Validation error: {e.problem}")
347
print(f"Context: {e.context}")
348
if e.problem_mark:
349
print(f"Position: Line {e.problem_mark.line + 1}")
350
```
351
352
## Error Recovery Strategies
353
354
### Partial Loading
355
356
```python
357
import yaml
358
359
def load_yaml_partially(content):
360
"""Load YAML documents, skipping invalid ones."""
361
documents = []
362
errors = []
363
364
try:
365
for doc in yaml.safe_load_all(content):
366
documents.append(doc)
367
except yaml.YAMLError as e:
368
errors.append(e)
369
370
return documents, errors
371
372
multi_doc_yaml = """
373
---
374
valid: document1
375
---
376
invalid: [unclosed
377
---
378
valid: document3
379
"""
380
381
docs, errs = load_yaml_partially(multi_doc_yaml)
382
print(f"Loaded {len(docs)} documents, {len(errs)} errors")
383
```
384
385
### Fallback Loading
386
387
```python
388
import yaml
389
390
def load_with_fallback(content):
391
"""Try different loaders in order of safety."""
392
loaders = [yaml.SafeLoader, yaml.FullLoader, yaml.Loader]
393
394
for loader_class in loaders:
395
try:
396
return yaml.load(content, Loader=loader_class), loader_class.__name__
397
except yaml.ConstructorError:
398
continue # Try next loader
399
except yaml.YAMLError:
400
break # Syntax error, don't try other loaders
401
402
return None, None
403
404
# YAML with Python-specific tag
405
yaml_content = """
406
data: !!python/tuple [1, 2, 3]
407
"""
408
409
result, loader_used = load_with_fallback(yaml_content)
410
if result:
411
print(f"Loaded successfully with {loader_used}")
412
else:
413
print("Failed to load with any loader")
414
```
415
416
## Debugging Techniques
417
418
### Verbose Error Reporting
419
420
```python
421
import yaml
422
import traceback
423
424
def debug_yaml_load(content, filename="<string>"):
425
"""Load YAML with detailed debugging information."""
426
try:
427
return yaml.safe_load(content)
428
except yaml.MarkedYAMLError as e:
429
print(f"=== YAML Error in {filename} ===")
430
print(f"Error Type: {type(e).__name__}")
431
print(f"Problem: {e.problem}")
432
433
if e.problem_mark:
434
mark = e.problem_mark
435
print(f"Position: Line {mark.line + 1}, Column {mark.column + 1}")
436
437
# Show context lines
438
lines = content.split('\n')
439
start_line = max(0, mark.line - 2)
440
end_line = min(len(lines), mark.line + 3)
441
442
print("\nContext:")
443
for i in range(start_line, end_line):
444
prefix = ">>> " if i == mark.line else " "
445
print(f"{prefix}{i + 1:3}: {lines[i]}")
446
if i == mark.line:
447
print(f" {' ' * (3 + mark.column)}^")
448
449
print(f"\nPython traceback:")
450
traceback.print_exc()
451
return None
452
453
except Exception as e:
454
print(f"Unexpected error: {e}")
455
traceback.print_exc()
456
return None
457
458
# Test with error
459
debug_yaml_load("""
460
name: John
461
items:
462
- one
463
- two
464
- invalid indentation
465
""")
466
```
467
468
### Processing Stage Identification
469
470
```python
471
import yaml
472
473
def identify_processing_stage(content):
474
"""Identify which processing stage fails."""
475
try:
476
# Test scanning
477
list(yaml.scan(content, yaml.SafeLoader))
478
print("✓ Scanning successful")
479
480
# Test parsing
481
list(yaml.parse(content, yaml.SafeLoader))
482
print("✓ Parsing successful")
483
484
# Test composition
485
yaml.compose(content, yaml.SafeLoader)
486
print("✓ Composition successful")
487
488
# Test construction
489
yaml.safe_load(content)
490
print("✓ Construction successful")
491
492
except yaml.ScannerError as e:
493
print(f"✗ Scanning failed: {e.problem}")
494
except yaml.ParserError as e:
495
print(f"✗ Parsing failed: {e.problem}")
496
except yaml.ComposerError as e:
497
print(f"✗ Composition failed: {e.problem}")
498
except yaml.ConstructorError as e:
499
print(f"✗ Construction failed: {e.problem}")
500
501
# Test problematic YAML
502
identify_processing_stage("invalid: [unclosed")
503
```
504
505
## Best Practices
506
507
### Error Handling Guidelines
508
509
1. **Always handle YAMLError** as the base exception type
510
2. **Use specific exception types** when you need different handling
511
3. **Check for position information** to provide better error messages
512
4. **Validate data after loading** rather than relying solely on YAML validation
513
5. **Provide meaningful error messages** to users
514
515
### Production Error Handling
516
517
1. **Log errors with context** for debugging
518
2. **Provide fallback behavior** for non-critical failures
519
3. **Sanitize error messages** before showing to users
520
4. **Monitor error patterns** to identify common issues
521
5. **Have recovery strategies** for partial failures
522
523
### Development and Testing
524
525
1. **Test error conditions** explicitly in unit tests
526
2. **Use debug modes** during development
527
3. **Validate error message quality** and usefulness
528
4. **Test with malformed input** to ensure robustness
529
5. **Document expected error scenarios** for API users