0
# Type Validation
1
2
Comprehensive template system for validating and converting configuration values with built-in templates for common types and support for custom validation logic. Templates provide type safety and automatic conversion between YAML representations and Python objects.
3
4
## Capabilities
5
6
### Base Template Class
7
8
The foundation template class that all validation templates inherit from, providing the core validation and conversion interface.
9
10
```python { .api }
11
class Template:
12
def __init__(self, default=REQUIRED):
13
"""
14
Create a template with optional default value.
15
16
Parameters:
17
- default: Default value to return when configuration value is missing.
18
Use REQUIRED (default) to raise exception for missing values.
19
"""
20
21
def value(self, view, template=None):
22
"""
23
Get validated value from a ConfigView.
24
25
Parameters:
26
- view (ConfigView): Configuration view to validate
27
- template (Template, optional): Parent template context
28
29
Returns:
30
Validated and converted value
31
32
Raises:
33
- NotFoundError: If value is missing and required
34
- ConfigValueError: If value is invalid
35
"""
36
37
def convert(self, value, view):
38
"""
39
Convert raw configuration value to target type.
40
41
Parameters:
42
- value: Raw value from configuration source
43
- view (ConfigView): Configuration view context
44
45
Returns:
46
Converted value
47
"""
48
49
def get_default_value(self, key_name='default'):
50
"""
51
Get the default value for this template.
52
53
Returns:
54
Default value or raises NotFoundError if required
55
"""
56
```
57
58
### Basic Type Templates
59
60
Templates for primitive types with automatic conversion and validation.
61
62
```python { .api }
63
class Integer(Template):
64
"""Template for integer values with float rounding."""
65
66
class Number(Template):
67
"""Template for numeric values (int or float)."""
68
69
class String(Template):
70
def __init__(self, default=REQUIRED, pattern=None, expand_vars=False):
71
"""
72
Template for string values with optional pattern matching and variable expansion.
73
74
Parameters:
75
- default: Default string value
76
- pattern (str, optional): Regular expression pattern to validate against
77
- expand_vars (bool): Whether to expand environment variables ($VAR, ${VAR})
78
"""
79
80
class TypeTemplate(Template):
81
def __init__(self, typ, default=REQUIRED):
82
"""
83
Template for validating Python type instances.
84
85
Parameters:
86
- typ (type): Python type to validate against
87
- default: Default value of the specified type
88
"""
89
```
90
91
### Choice and Option Templates
92
93
Templates for validating values against predefined choices or multiple possible templates.
94
95
```python { .api }
96
class Choice(Template):
97
def __init__(self, choices, default=REQUIRED):
98
"""
99
Template for values from predefined choices.
100
101
Parameters:
102
- choices: Sequence of valid values, dict mapping, or Enum class
103
- default: Default choice value
104
"""
105
106
class OneOf(Template):
107
def __init__(self, allowed, default=REQUIRED):
108
"""
109
Template accepting values matching any of multiple sub-templates.
110
111
Parameters:
112
- allowed: List of Template objects or convertible values
113
- default: Default value
114
"""
115
116
class Optional(Template):
117
def __init__(self, subtemplate, default=None, allow_missing=True):
118
"""
119
Template making sub-templates optional with null/missing value handling.
120
121
Parameters:
122
- subtemplate: Template to apply when value is present and not null
123
- default: Value to return for null/missing values
124
- allow_missing (bool): Whether to allow missing values
125
"""
126
```
127
128
### Container Templates
129
130
Templates for validating collections and nested data structures.
131
132
```python { .api }
133
class MappingTemplate(Template):
134
def __init__(self, mapping):
135
"""
136
Template for dictionary validation with typed values.
137
138
Parameters:
139
- mapping (dict): Dictionary specifying value templates for each key
140
"""
141
142
class Sequence(Template):
143
def __init__(self, subtemplate):
144
"""
145
Template for list validation with uniform item types.
146
147
Parameters:
148
- subtemplate: Template to apply to each list item
149
"""
150
151
class MappingValues(Template):
152
def __init__(self, subtemplate):
153
"""
154
Template for mappings with variable keys but typed values.
155
156
Parameters:
157
- subtemplate: Template to apply to all mapping values
158
"""
159
160
class StrSeq(Template):
161
def __init__(self, split=True, default=REQUIRED):
162
"""
163
Template for lists of strings with optional whitespace splitting.
164
165
Parameters:
166
- split (bool): Whether to split single strings on whitespace
167
- default: Default list value
168
"""
169
170
class Pairs(StrSeq):
171
def __init__(self, default_value=None):
172
"""
173
Template for ordered key-value pairs from various input formats.
174
175
Parameters:
176
- default_value: Default value for keys without explicit values
177
"""
178
```
179
180
### File Path Templates
181
182
Templates for validating and resolving file paths with platform-aware handling.
183
184
```python { .api }
185
class Filename(Template):
186
def __init__(self, default=REQUIRED, cwd=None, relative_to=None,
187
in_app_dir=False, in_source_dir=False):
188
"""
189
Template for filename validation with path resolution.
190
191
Parameters:
192
- default: Default filename
193
- cwd (str, optional): Working directory for relative paths
194
- relative_to (str, optional): Key name for sibling path resolution
195
- in_app_dir (bool): Resolve relative to application config directory
196
- in_source_dir (bool): Resolve relative to configuration file directory
197
"""
198
199
class Path(Filename):
200
"""Template for pathlib.Path objects with same resolution as Filename."""
201
```
202
203
### Utility Functions and Classes
204
205
Helper functions and classes for working with templates and validation.
206
207
```python { .api }
208
def as_template(value):
209
"""
210
Convert shorthand Python values to Template objects.
211
212
Parameters:
213
- value: Python type, value, or existing Template
214
215
Returns:
216
Template: Corresponding Template object
217
"""
218
219
class AttrDict(dict):
220
"""
221
Dictionary subclass with attribute-style access.
222
223
Enables dot notation: config.database.host instead of config['database']['host']
224
"""
225
def __getattr__(self, key): ...
226
def __setattr__(self, key, value): ...
227
```
228
229
## Usage Examples
230
231
### Basic Type Validation
232
233
```python
234
import confuse
235
236
config = confuse.Configuration('myapp')
237
238
# Simple type templates
239
port = config['port'].get(confuse.Integer())
240
timeout = config['timeout'].get(confuse.Number(30.0)) # Default: 30.0
241
name = config['app_name'].get(confuse.String())
242
243
# Type shorthand (automatically converted to templates)
244
port = config['port'].get(int) # Same as Integer()
245
timeout = config['timeout'].get(30.0) # Same as Number(30.0)
246
name = config['app_name'].get(str) # Same as String()
247
```
248
249
### String Templates with Patterns
250
251
```python
252
import confuse
253
254
config = confuse.Configuration('myapp')
255
256
# String with regex pattern validation
257
email_template = confuse.String(pattern=r'^[^@]+@[^@]+\.[^@]+$')
258
email = config['admin_email'].get(email_template)
259
260
# String with environment variable expansion
261
path_template = confuse.String(expand_vars=True)
262
data_path = config['data_path'].get(path_template) # Expands $HOME/data
263
```
264
265
### Choice Templates
266
267
```python
268
import confuse
269
from enum import Enum
270
271
config = confuse.Configuration('myapp')
272
273
# Choice from list
274
log_level = config['log_level'].get(confuse.Choice(['DEBUG', 'INFO', 'WARNING', 'ERROR']))
275
276
# Choice from dict (maps keys to values)
277
env_mapping = {'dev': 'development', 'prod': 'production'}
278
environment = config['env'].get(confuse.Choice(env_mapping))
279
280
# Choice from Enum
281
class LogLevel(Enum):
282
DEBUG = 'debug'
283
INFO = 'info'
284
WARNING = 'warning'
285
ERROR = 'error'
286
287
log_level = config['log_level'].get(confuse.Choice(LogLevel))
288
```
289
290
### Complex Data Structure Validation
291
292
```python
293
import confuse
294
295
config = confuse.Configuration('myapp')
296
297
# Dictionary template with typed values
298
database_template = {
299
'host': str,
300
'port': confuse.Integer(5432),
301
'username': str,
302
'password': str,
303
'ssl': bool,
304
'timeout': confuse.Number(30.0),
305
'options': confuse.StrSeq(),
306
}
307
308
# Get validated configuration as AttrDict
309
db_config = config['database'].get(database_template)
310
print(f"Connecting to {db_config.host}:{db_config.port}")
311
print(f"SSL enabled: {db_config.ssl}")
312
print(f"Options: {db_config.options}")
313
314
# List of dictionaries
315
server_template = confuse.Sequence({
316
'hostname': str,
317
'port': confuse.Integer(),
318
'enabled': bool,
319
'tags': confuse.StrSeq(),
320
})
321
322
servers = config['servers'].get(server_template)
323
for server in servers:
324
print(f"Server: {server.hostname}:{server.port} (enabled: {server.enabled})")
325
```
326
327
### File Path Validation
328
329
```python
330
import confuse
331
332
config = confuse.Configuration('myapp')
333
334
# Basic filename template
335
config_file = config['config_file'].get(confuse.Filename())
336
337
# Filename relative to application config directory
338
log_file = config['log_file'].get(confuse.Filename(in_app_dir=True))
339
340
# Filename relative to configuration source file
341
data_file = config['data_file'].get(confuse.Filename(in_source_dir=True))
342
343
# Filename with custom working directory
344
output_file = config['output'].get(confuse.Filename(cwd='/tmp'))
345
346
# Filename relative to another configuration value
347
template = {
348
'base_dir': confuse.Filename(),
349
'data_file': confuse.Filename(relative_to='base_dir'),
350
}
351
paths = config.get(template)
352
print(f"Data file: {paths.data_file}") # Resolved relative to base_dir
353
```
354
355
### Optional and Default Values
356
357
```python
358
import confuse
359
360
config = confuse.Configuration('myapp')
361
362
# Optional template with default
363
debug_mode = config['debug'].get(confuse.Optional(bool, default=False))
364
365
# Template with REQUIRED (raises exception if missing)
366
api_key = config['api_key'].get(confuse.String()) # Must be present
367
368
# Template with default value
369
max_workers = config['max_workers'].get(confuse.Integer(4))
370
371
# Optional nested configuration
372
cache_template = confuse.Optional({
373
'enabled': bool,
374
'ttl': confuse.Integer(3600),
375
'backend': confuse.Choice(['memory', 'redis']),
376
})
377
cache_config = config['cache'].get(cache_template)
378
if cache_config:
379
print(f"Cache enabled: {cache_config.enabled}")
380
```
381
382
### Multiple Template Options
383
384
```python
385
import confuse
386
387
config = confuse.Configuration('myapp')
388
389
# Value can be boolean or string
390
import_mode = config['import_mode'].get(confuse.OneOf([bool, 'ask', 'skip']))
391
392
# Value can be filename or URL
393
source_template = confuse.OneOf([
394
confuse.Filename(),
395
confuse.String(pattern=r'^https?://'),
396
])
397
data_source = config['data_source'].get(source_template)
398
399
# Multiple validation options with different defaults
400
retry_template = confuse.OneOf([
401
confuse.Integer(), # Number of retries
402
bool, # True/False for default retry count
403
confuse.String(pattern=r'^(always|never)$'), # Special string values
404
])
405
retry_config = config['retry'].get(retry_template)
406
```
407
408
### String Sequences and Pairs
409
410
```python
411
import confuse
412
413
config = confuse.Configuration('myapp')
414
415
# List of strings (splits single string on whitespace)
416
plugins = config['plugins'].get(confuse.StrSeq())
417
# YAML: plugins: "plugin1 plugin2 plugin3" -> ['plugin1', 'plugin2', 'plugin3']
418
# YAML: plugins: [plugin1, plugin2, plugin3] -> ['plugin1', 'plugin2', 'plugin3']
419
420
# List of strings without splitting
421
tags = config['tags'].get(confuse.StrSeq(split=False))
422
# YAML: tags: "development staging" -> ['development staging']
423
424
# Key-value pairs
425
env_vars = config['environment'].get(confuse.Pairs())
426
# YAML:
427
# environment:
428
# - DATABASE_URL: postgres://localhost/db
429
# - [API_KEY, secret123]
430
# - DEBUG # Uses default_value
431
# Result: [('DATABASE_URL', 'postgres://localhost/db'), ('API_KEY', 'secret123'), ('DEBUG', None)]
432
433
# Pairs with default value
434
flags = config['flags'].get(confuse.Pairs(default_value=True))
435
# Result: [('DEBUG', True)] for YAML: flags: [DEBUG]
436
```
437
438
### Custom Template Creation
439
440
```python
441
import confuse
442
443
class EmailTemplate(confuse.Template):
444
def convert(self, value, view):
445
if not isinstance(value, str):
446
self.fail('must be a string', view, True)
447
448
if '@' not in value:
449
self.fail('must be a valid email address', view)
450
451
return value.lower() # Normalize to lowercase
452
453
config = confuse.Configuration('myapp')
454
admin_email = config['admin_email'].get(EmailTemplate())
455
```