0
# Exception Handling
1
2
Error handling and testing utilities for comprehensive validation workflows and error reporting. Yamale provides specialized exception classes and testing utilities for robust error handling in validation scenarios.
3
4
## Capabilities
5
6
### YamaleError Exception
7
8
Main exception class raised when YAML validation fails, containing detailed error information for all failed validations.
9
10
```python { .api }
11
class YamaleError(ValueError):
12
"""
13
Exception raised when YAML validation fails.
14
Inherits from ValueError for standard exception handling.
15
"""
16
17
def __init__(self, results):
18
"""
19
Initialize YamaleError with validation results.
20
21
Parameters:
22
- results (list): List of ValidationResult objects containing errors
23
24
Attributes:
25
- message (str): Combined error message from all failed validations
26
- results (list): List of ValidationResult objects
27
"""
28
29
message: str # Formatted error message string
30
results: list # List of ValidationResult objects with detailed errors
31
```
32
33
Usage examples:
34
35
```python
36
import yamale
37
38
try:
39
yamale.validate(schema, data)
40
print("Validation successful!")
41
except yamale.YamaleError as e:
42
# Access the combined error message
43
print(f"Validation failed: {e}")
44
print(f"Error message: {e.message}")
45
46
# Process individual validation results
47
print(f"Failed validations: {len([r for r in e.results if not r.isValid()])}")
48
for result in e.results:
49
if not result.isValid():
50
print(f"\nFile: {result.data}")
51
print(f"Schema: {result.schema}")
52
for error in result.errors:
53
print(f" - {error}")
54
55
# Handle as standard ValueError
56
except ValueError as e:
57
print(f"General validation error: {e}")
58
```
59
60
### Detailed Error Information
61
62
YamaleError provides structured access to validation failures:
63
64
```python
65
try:
66
yamale.validate(schema, data)
67
except yamale.YamaleError as e:
68
# Get summary statistics
69
total_results = len(e.results)
70
failed_results = [r for r in e.results if not r.isValid()]
71
success_rate = (total_results - len(failed_results)) / total_results * 100
72
73
print(f"Validation Summary:")
74
print(f" Total files: {total_results}")
75
print(f" Failed: {len(failed_results)}")
76
print(f" Success rate: {success_rate:.1f}%")
77
78
# Group errors by type
79
error_types = {}
80
for result in failed_results:
81
for error in result.errors:
82
error_type = error.split(':')[0] if ':' in error else 'General'
83
error_types[error_type] = error_types.get(error_type, 0) + 1
84
85
print(f"\nError breakdown:")
86
for error_type, count in error_types.items():
87
print(f" {error_type}: {count}")
88
```
89
90
### YamaleTestCase Class
91
92
Unittest.TestCase subclass for easy integration of YAML validation into unit testing frameworks.
93
94
```python { .api }
95
class YamaleTestCase(TestCase):
96
"""
97
TestCase subclass for validating YAML files in unit tests.
98
Inherits from unittest.TestCase.
99
"""
100
101
schema = None # String path to schema file (required)
102
yaml = None # String path or list of paths to YAML files (required)
103
base_dir = None # String path to prepend to all other paths (optional)
104
105
def validate(self, validators=None):
106
"""
107
Validate configured YAML files against schema.
108
109
Parameters:
110
- validators (dict, optional): Custom validator dictionary
111
112
Returns:
113
bool: True if all validations pass
114
115
Raises:
116
ValueError: If validation fails or configuration is invalid
117
"""
118
```
119
120
Usage examples:
121
122
```python
123
import os
124
import yamale
125
from yamale import YamaleTestCase
126
127
class TestUserConfigs(YamaleTestCase):
128
# Required configuration
129
schema = 'schemas/user.yaml'
130
yaml = 'data/users/*.yaml' # Supports glob patterns
131
base_dir = os.path.dirname(os.path.realpath(__file__))
132
133
def runTest(self):
134
# Validate all matching files
135
self.assertTrue(self.validate())
136
137
class TestMultipleFiles(YamaleTestCase):
138
schema = 'config-schema.yaml'
139
yaml = ['config.yaml', 'config-prod.yaml', 'config-dev.yaml']
140
141
def runTest(self):
142
self.assertTrue(self.validate())
143
144
class TestWithCustomValidators(YamaleTestCase):
145
schema = 'custom-schema.yaml'
146
yaml = 'custom-data.yaml'
147
148
def runTest(self):
149
# Use custom validators
150
custom_validators = yamale.validators.DefaultValidators.copy()
151
custom_validators['email'] = EmailValidator
152
self.assertTrue(self.validate(validators=custom_validators))
153
154
# Run tests
155
if __name__ == '__main__':
156
import unittest
157
unittest.main()
158
```
159
160
### Advanced Testing Patterns
161
162
#### Test Suite Organization
163
164
```python
165
import unittest
166
import os
167
from yamale import YamaleTestCase
168
169
class BaseYamaleTest(YamaleTestCase):
170
"""Base class with common configuration."""
171
base_dir = os.path.join(os.path.dirname(__file__), 'fixtures')
172
173
class TestAPISchemas(BaseYamaleTest):
174
schema = 'api-schema.yaml'
175
yaml = 'api-examples/*.yaml'
176
177
def runTest(self):
178
self.assertTrue(self.validate())
179
180
class TestConfigSchemas(BaseYamaleTest):
181
schema = 'config-schema.yaml'
182
yaml = ['config-*.yaml']
183
184
def runTest(self):
185
self.assertTrue(self.validate())
186
187
# Create test suite
188
def create_yamale_suite():
189
suite = unittest.TestSuite()
190
suite.addTest(TestAPISchemas())
191
suite.addTest(TestConfigSchemas())
192
return suite
193
```
194
195
#### Error Handling in Tests
196
197
```python
198
class TestWithErrorHandling(YamaleTestCase):
199
schema = 'strict-schema.yaml'
200
yaml = 'test-data.yaml'
201
202
def runTest(self):
203
try:
204
result = self.validate()
205
self.assertTrue(result)
206
except ValueError as e:
207
# Log detailed error information for debugging
208
self.fail(f"Validation failed: {e}")
209
210
def test_expected_failure(self):
211
"""Test that intentionally invalid data fails validation."""
212
# Temporarily change to invalid data
213
original_yaml = self.yaml
214
self.yaml = 'invalid-data.yaml'
215
216
with self.assertRaises(ValueError):
217
self.validate()
218
219
# Restore original configuration
220
self.yaml = original_yaml
221
```
222
223
### Integration with Testing Frameworks
224
225
#### pytest Integration
226
227
```python
228
import pytest
229
import yamale
230
231
def test_yaml_validation():
232
"""Test YAML validation using pytest."""
233
schema = yamale.make_schema('./schema.yaml')
234
data = yamale.make_data('./data.yaml')
235
236
try:
237
yamale.validate(schema, data)
238
except yamale.YamaleError as e:
239
pytest.fail(f"YAML validation failed: {e}")
240
241
@pytest.mark.parametrize("data_file", [
242
"config-dev.yaml",
243
"config-prod.yaml",
244
"config-test.yaml"
245
])
246
def test_multiple_configs(data_file):
247
"""Test multiple configuration files."""
248
schema = yamale.make_schema('./config-schema.yaml')
249
data = yamale.make_data(f'./configs/{data_file}')
250
251
yamale.validate(schema, data) # Will raise on failure
252
253
def test_validation_with_custom_message():
254
"""Test with custom error handling."""
255
schema = yamale.make_schema('./user-schema.yaml')
256
data = yamale.make_data('./invalid-user.yaml')
257
258
with pytest.raises(yamale.YamaleError) as exc_info:
259
yamale.validate(schema, data)
260
261
# Assert specific error conditions
262
error_msg = str(exc_info.value)
263
assert "age" in error_msg
264
assert "required" in error_msg
265
```
266
267
## Error Handling Best Practices
268
269
### Production Error Handling
270
271
```python
272
import logging
273
import yamale
274
275
def validate_config_file(config_path, schema_path):
276
"""
277
Validate configuration file with comprehensive error handling.
278
"""
279
logger = logging.getLogger(__name__)
280
281
try:
282
# Create schema
283
schema = yamale.make_schema(schema_path)
284
logger.info(f"Loaded schema from {schema_path}")
285
286
# Load data
287
data = yamale.make_data(config_path)
288
logger.info(f"Loaded data from {config_path}")
289
290
# Validate
291
yamale.validate(schema, data)
292
logger.info("Validation successful")
293
return True
294
295
except FileNotFoundError as e:
296
logger.error(f"File not found: {e}")
297
return False
298
except yaml.YAMLError as e:
299
logger.error(f"YAML parsing error: {e}")
300
return False
301
except yamale.YamaleError as e:
302
logger.error(f"Validation failed: {e}")
303
# Log detailed errors for debugging
304
for result in e.results:
305
if not result.isValid():
306
logger.debug(f"Failed file: {result.data}")
307
for error in result.errors:
308
logger.debug(f" Error: {error}")
309
return False
310
except Exception as e:
311
logger.error(f"Unexpected error: {e}")
312
return False
313
314
# Usage
315
if validate_config_file('config.yaml', 'schema.yaml'):
316
print("Configuration is valid")
317
else:
318
print("Configuration validation failed")
319
sys.exit(1)
320
```
321
322
### Batch Validation with Error Aggregation
323
324
```python
325
def validate_multiple_files(file_paths, schema_path):
326
"""
327
Validate multiple files and aggregate errors.
328
"""
329
schema = yamale.make_schema(schema_path)
330
all_results = []
331
332
for file_path in file_paths:
333
try:
334
data = yamale.make_data(file_path)
335
results = yamale.validate(schema, data, _raise_error=False)
336
all_results.extend(results)
337
except Exception as e:
338
# Create error result for files that couldn't be loaded
339
error_result = yamale.schema.validationresults.Result([str(e)])
340
error_result.data = file_path
341
error_result.schema = schema_path
342
all_results.append(error_result)
343
344
# Analyze results
345
valid_count = sum(1 for r in all_results if r.isValid())
346
total_count = len(all_results)
347
348
print(f"Validation Summary: {valid_count}/{total_count} files valid")
349
350
# Report failures
351
for result in all_results:
352
if not result.isValid():
353
print(f"FAILED: {result.data}")
354
for error in result.errors:
355
print(f" {error}")
356
357
return valid_count == total_count
358
```