0
# Configuration and Error Handling
1
2
Configuration options and comprehensive exception hierarchy for error handling in attrs applications. attrs provides global configuration settings and specific exceptions for different error conditions.
3
4
## Capabilities
5
6
### Configuration
7
8
#### Validator Configuration
9
10
Global control over validator execution.
11
12
```python { .api }
13
def set_run_validators(run):
14
"""
15
Globally enable or disable validator execution (deprecated).
16
17
This function is deprecated. Use attrs.validators.set_disabled() instead.
18
19
Parameters:
20
- run (bool): True to enable validators, False to disable
21
"""
22
23
def get_run_validators():
24
"""
25
Check if validators are globally enabled (deprecated).
26
27
This function is deprecated. Use attrs.validators.get_disabled() instead.
28
29
Returns:
30
bool: True if validators are enabled, False if disabled
31
"""
32
```
33
34
Modern validator control (preferred):
35
```python
36
# Using validators module
37
attrs.validators.set_disabled(False) # Enable validators
38
is_disabled = attrs.validators.get_disabled()
39
40
# Temporarily disable validators
41
with attrs.validators.disabled():
42
# Create instances without validation
43
user = User(name="", age=-5) # Would normally fail validation
44
```
45
46
### Exception Hierarchy
47
48
attrs provides a comprehensive exception hierarchy for different error conditions.
49
50
#### Base Frozen Errors
51
52
Exceptions related to immutable instance or attribute modification.
53
54
```python { .api }
55
class FrozenError(AttributeError):
56
"""
57
Base exception for frozen/immutable modification attempts.
58
59
Mirrors namedtuple behavior by subclassing AttributeError.
60
61
Attributes:
62
- msg (str): Error message "can't set attribute"
63
"""
64
65
class FrozenInstanceError(FrozenError):
66
"""
67
Raised when attempting to modify a frozen attrs instance.
68
69
Occurs when trying to set attributes on classes decorated with frozen=True
70
or @attrs.frozen.
71
"""
72
73
class FrozenAttributeError(FrozenError):
74
"""
75
Raised when attempting to modify a frozen attribute.
76
77
Occurs when trying to set attributes with on_setattr=attrs.setters.frozen.
78
"""
79
```
80
81
Usage examples:
82
```python
83
@attrs.frozen
84
class ImmutablePoint:
85
x: float
86
y: float
87
88
point = ImmutablePoint(1.0, 2.0)
89
try:
90
point.x = 5.0 # Raises FrozenInstanceError
91
except attrs.FrozenInstanceError as e:
92
print(f"Cannot modify frozen instance: {e}")
93
94
@attrs.define
95
class PartiallyImmutable:
96
mutable_field: str
97
immutable_field: str = attrs.field(on_setattr=attrs.setters.frozen)
98
99
obj = PartiallyImmutable("can change", "cannot change")
100
obj.mutable_field = "changed" # OK
101
try:
102
obj.immutable_field = "new value" # Raises FrozenAttributeError
103
except attrs.FrozenAttributeError as e:
104
print(f"Cannot modify frozen attribute: {e}")
105
```
106
107
#### Attrs Lookup Errors
108
109
Exceptions for attrs-specific operations and lookups.
110
111
```python { .api }
112
class AttrsAttributeNotFoundError(ValueError):
113
"""
114
Raised when an attrs function can't find a requested attribute.
115
116
Occurs in functions like evolve() when specifying non-existent field names.
117
"""
118
119
class NotAnAttrsClassError(ValueError):
120
"""
121
Raised when a non-attrs class is passed to an attrs function.
122
123
Occurs when calling attrs functions like fields(), asdict(), etc.
124
on regular classes.
125
"""
126
```
127
128
Usage examples:
129
```python
130
@attrs.define
131
class Person:
132
name: str
133
age: int
134
135
person = Person("Alice", 30)
136
137
try:
138
# Typo in field name
139
updated = attrs.evolve(person, namee="Bob")
140
except attrs.AttrsAttributeNotFoundError as e:
141
print(f"Field not found: {e}")
142
143
class RegularClass:
144
def __init__(self, value):
145
self.value = value
146
147
regular = RegularClass(42)
148
try:
149
attrs.fields(RegularClass) # Raises NotAnAttrsClassError
150
except attrs.NotAnAttrsClassError as e:
151
print(f"Not an attrs class: {e}")
152
```
153
154
#### Definition Errors
155
156
Exceptions related to incorrect attrs class or field definitions.
157
158
```python { .api }
159
class DefaultAlreadySetError(RuntimeError):
160
"""
161
Raised when attempting to set a default value multiple times.
162
163
Occurs when both default and factory are specified for the same field,
164
or when default is set in conflicting ways.
165
"""
166
167
class UnannotatedAttributeError(RuntimeError):
168
"""
169
Raised when type annotation is missing with auto_attribs=True.
170
171
Occurs when using @attrs.define or auto_attribs=True without proper
172
type annotations on all fields.
173
"""
174
175
class PythonTooOldError(RuntimeError):
176
"""
177
Raised when a feature requires a newer Python version.
178
179
Occurs when using attrs features that require Python versions
180
newer than the current runtime.
181
"""
182
```
183
184
Usage examples:
185
```python
186
try:
187
@attrs.define
188
class BadClass:
189
# Missing type annotation with auto_attribs (implicit with @define)
190
name = "default" # Raises UnannotatedAttributeError
191
except attrs.UnannotatedAttributeError as e:
192
print(f"Missing type annotation: {e}")
193
194
try:
195
@attrs.define
196
class ConflictingDefaults:
197
# Cannot specify both default and factory
198
value: int = attrs.field(default=0, factory=int) # Raises DefaultAlreadySetError
199
except attrs.DefaultAlreadySetError as e:
200
print(f"Conflicting defaults: {e}")
201
```
202
203
#### Validation Errors
204
205
Exceptions related to validation failures.
206
207
```python { .api }
208
class NotCallableError(TypeError):
209
"""
210
Raised when a non-callable is passed where callable is required.
211
212
Occurs when using attrs.validators.is_callable() validator
213
or when passing non-callable validators or converters.
214
"""
215
```
216
217
Usage example:
218
```python
219
@attrs.define
220
class Config:
221
callback: callable = attrs.field(validator=attrs.validators.is_callable())
222
223
try:
224
config = Config(callback="not a function") # Raises NotCallableError
225
except attrs.NotCallableError as e:
226
print(f"Not callable: {e}")
227
```
228
229
### Error Handling Patterns
230
231
#### Graceful Validation Handling
232
233
Handle validation errors gracefully in applications.
234
235
```python
236
def create_user_safely(name, age, email):
237
"""Create user with error handling."""
238
try:
239
return User(name=name, age=age, email=email)
240
except (TypeError, ValueError) as e:
241
# Handle validation errors
242
print(f"Invalid user data: {e}")
243
return None
244
245
def validate_data_batch(data_list):
246
"""Validate batch of data with individual error handling."""
247
results = []
248
errors = []
249
250
for i, data in enumerate(data_list):
251
try:
252
user = User(**data)
253
attrs.validate(user) # Explicit validation
254
results.append(user)
255
except Exception as e:
256
errors.append((i, str(e)))
257
258
return results, errors
259
```
260
261
#### Configuration Validation
262
263
Validate attrs configuration at class definition time.
264
265
```python
266
def validate_attrs_class(cls):
267
"""Validate attrs class configuration."""
268
if not attrs.has(cls):
269
raise attrs.NotAnAttrsClassError(f"{cls} is not an attrs class")
270
271
field_names = set()
272
for field in attrs.fields(cls):
273
if field.name in field_names:
274
raise ValueError(f"Duplicate field name: {field.name}")
275
field_names.add(field.name)
276
277
# Check for conflicting configurations
278
if field.default is not attrs.NOTHING and field.factory is not None:
279
raise attrs.DefaultAlreadySetError(f"Field {field.name} has both default and factory")
280
281
return True
282
```
283
284
#### Migration and Compatibility
285
286
Handle attrs version compatibility and migration issues.
287
288
```python
289
def safe_evolve(instance, **changes):
290
"""Safely evolve instance with error handling."""
291
try:
292
return attrs.evolve(instance, **changes)
293
except attrs.AttrsAttributeNotFoundError as e:
294
# Handle field name changes or typos
295
available_fields = [f.name for f in attrs.fields(instance.__class__)]
296
print(f"Available fields: {available_fields}")
297
raise ValueError(f"Cannot evolve: {e}") from e
298
299
def check_attrs_compatibility(cls):
300
"""Check if class is compatible with current attrs version."""
301
try:
302
# Try to access modern features
303
attrs.fields_dict(cls)
304
return True
305
except AttributeError:
306
# Older attrs version
307
return False
308
```
309
310
## Common Patterns
311
312
### Defensive Programming
313
```python
314
def safe_attrs_operation(obj, operation):
315
"""Safely perform attrs operations with comprehensive error handling."""
316
if not attrs.has(obj.__class__):
317
raise ValueError("Object is not an attrs instance")
318
319
try:
320
return operation(obj)
321
except attrs.FrozenInstanceError:
322
print("Cannot modify frozen instance")
323
return obj # Return unchanged
324
except attrs.AttrsAttributeNotFoundError as e:
325
print(f"Attribute not found: {e}")
326
raise
327
except Exception as e:
328
print(f"Unexpected error: {e}")
329
raise
330
```
331
332
### Configuration Management
333
```python
334
class AttrsConfig:
335
"""Centralized attrs configuration management."""
336
337
@staticmethod
338
def disable_validators():
339
"""Disable validators for performance."""
340
attrs.validators.set_disabled(True)
341
342
@staticmethod
343
def enable_validators():
344
"""Enable validators for safety."""
345
attrs.validators.set_disabled(False)
346
347
@contextmanager
348
def temporary_config(self, disable_validators=False):
349
"""Temporarily change attrs configuration."""
350
old_disabled = attrs.validators.get_disabled()
351
352
try:
353
if disable_validators:
354
attrs.validators.set_disabled(True)
355
yield
356
finally:
357
attrs.validators.set_disabled(old_disabled)
358
```
359
360
### Error Context Enhancement
361
```python
362
def enhanced_evolve(instance, **changes):
363
"""Evolve with enhanced error messages."""
364
try:
365
return attrs.evolve(instance, **changes)
366
except attrs.AttrsAttributeNotFoundError as e:
367
# Provide helpful suggestions
368
available = [f.name for f in attrs.fields(instance.__class__)]
369
suggestions = []
370
371
for change_name in changes:
372
# Simple fuzzy matching for typos
373
for field_name in available:
374
if abs(len(change_name) - len(field_name)) <= 2:
375
# Simple edit distance check
376
if sum(c1 != c2 for c1, c2 in zip(change_name, field_name)) <= 2:
377
suggestions.append(field_name)
378
379
error_msg = str(e)
380
if suggestions:
381
error_msg += f". Did you mean: {', '.join(suggestions)}?"
382
383
raise attrs.AttrsAttributeNotFoundError(error_msg) from e
384
```
385
386
### Testing Utilities
387
```python
388
def assert_attrs_equal(obj1, obj2, ignore_fields=None):
389
"""Assert two attrs objects are equal, optionally ignoring fields."""
390
if not attrs.has(obj1.__class__) or not attrs.has(obj2.__class__):
391
raise ValueError("Both objects must be attrs instances")
392
393
if obj1.__class__ != obj2.__class__:
394
raise ValueError("Objects must be of the same class")
395
396
ignore_fields = ignore_fields or []
397
398
for field in attrs.fields(obj1.__class__):
399
if field.name in ignore_fields:
400
continue
401
402
val1 = getattr(obj1, field.name)
403
val2 = getattr(obj2, field.name)
404
405
if val1 != val2:
406
raise AssertionError(f"Field {field.name} differs: {val1} != {val2}")
407
408
def create_test_instance(cls, **overrides):
409
"""Create test instance with safe defaults."""
410
try:
411
# Try to create with minimal required fields
412
required_fields = {}
413
for field in attrs.fields(cls):
414
if field.default is attrs.NOTHING and field.factory is None:
415
# Required field, provide a test default
416
if field.type == str:
417
required_fields[field.name] = "test"
418
elif field.type == int:
419
required_fields[field.name] = 0
420
# Add more type defaults as needed
421
422
required_fields.update(overrides)
423
return cls(**required_fields)
424
425
except Exception as e:
426
raise ValueError(f"Could not create test instance of {cls}: {e}")
427
```