0
# Utility Transformers
1
2
String transformation utilities, default value handling, value setting, and collection conversion utilities. These transformers modify data during validation rather than just validating it.
3
4
## Capabilities
5
6
### String Transformation
7
8
Functions that transform strings while validating them.
9
10
```python { .api }
11
def Lower(v):
12
"""
13
Transform string to lowercase.
14
15
Parameters:
16
- v: String to transform
17
18
Returns:
19
Lowercase version of input string
20
21
Raises:
22
Invalid: If input cannot be converted to string
23
"""
24
25
def Upper(v):
26
"""
27
Transform string to uppercase.
28
29
Parameters:
30
- v: String to transform
31
32
Returns:
33
Uppercase version of input string
34
35
Raises:
36
Invalid: If input cannot be converted to string
37
"""
38
39
def Capitalize(v):
40
"""
41
Capitalize first letter of string.
42
43
Parameters:
44
- v: String to transform
45
46
Returns:
47
String with first letter capitalized, rest lowercase
48
49
Raises:
50
Invalid: If input cannot be converted to string
51
"""
52
53
def Title(v):
54
"""
55
Transform string to title case (each word capitalized).
56
57
Parameters:
58
- v: String to transform
59
60
Returns:
61
String in title case
62
63
Raises:
64
Invalid: If input cannot be converted to string
65
"""
66
67
def Strip(v):
68
"""
69
Remove leading and trailing whitespace from string.
70
71
Parameters:
72
- v: String to transform
73
74
Returns:
75
String with whitespace stripped
76
77
Raises:
78
Invalid: If input cannot be converted to string
79
"""
80
```
81
82
**Usage Examples:**
83
84
```python
85
from voluptuous import Schema, All, Lower, Upper, Capitalize, Title, Strip, Length
86
87
# Data normalization
88
user_schema = Schema({
89
'username': All(str, Strip, Lower), # Clean and lowercase username
90
'name': All(str, Strip, Title), # Clean and title case name
91
'email': All(str, Strip, Lower), # Clean and lowercase email
92
'country': All(str, Strip, Upper), # Clean and uppercase country code
93
})
94
95
# Text processing pipeline
96
text_processor = All(
97
str, # Ensure string
98
Strip, # Remove whitespace
99
lambda s: s if s else None, # Convert empty strings to None
100
)
101
102
# Combined with validation
103
clean_name = All(
104
str,
105
Strip, # Remove whitespace
106
Title, # Title case
107
Length(min=1, max=100), # Validate length
108
)
109
110
# Usage examples:
111
user_schema({
112
'username': ' JohnDoe ', # -> 'johndoe'
113
'name': ' john doe ', # -> 'John Doe'
114
'email': ' JOHN@EXAMPLE.COM ', # -> 'john@example.com'
115
'country': ' usa ', # -> 'USA'
116
})
117
```
118
119
### Default Value Handling
120
121
Set default values when input is None or missing.
122
123
```python { .api }
124
class DefaultTo:
125
def __init__(self, default_value, msg=None):
126
"""
127
Set default value when input is None.
128
129
Parameters:
130
- default_value: Value to use when input is None (can be callable for factory)
131
- msg: Custom error message (rarely used)
132
133
Returns:
134
Input value if not None, otherwise default_value (or result of calling it)
135
136
Note:
137
If default_value is callable, it will be called each time to generate the default
138
"""
139
```
140
141
**Usage Examples:**
142
143
```python
144
from voluptuous import Schema, DefaultTo, Required, Optional
145
146
# Simple default values
147
config_schema = Schema({
148
Required('host', default='localhost'): str, # Default host
149
Required('port', default=8080): int, # Default port
150
Required('debug', default=False): bool, # Default debug mode
151
})
152
153
# Factory functions for dynamic defaults
154
import datetime
155
156
session_schema = Schema({
157
'user_id': str,
158
'created_at': DefaultTo(datetime.datetime.now), # Current time as default
159
'expires_at': DefaultTo(lambda: datetime.datetime.now() + datetime.timedelta(hours=24)),
160
'permissions': DefaultTo(list), # Empty list as default
161
'metadata': DefaultTo(dict), # Empty dict as default
162
})
163
164
# Conditional defaults
165
def default_config():
166
return {'theme': 'light', 'language': 'en'}
167
168
user_prefs_schema = Schema({
169
'username': str,
170
'config': DefaultTo(default_config), # Function returns dict
171
})
172
173
# Usage examples:
174
config_schema({'host': None}) # -> {'host': 'localhost', 'port': 8080, 'debug': False}
175
session_schema({'user_id': '123'}) # Adds timestamps and empty collections
176
```
177
178
### Value Setting
179
180
Always set specific values regardless of input.
181
182
```python { .api }
183
class SetTo:
184
def __init__(self, value):
185
"""
186
Always set to specific value, ignoring input.
187
188
Parameters:
189
- value: Value to always return (can be callable for factory)
190
191
Returns:
192
The specified value (or result of calling it if callable)
193
194
Note:
195
Completely ignores the input value
196
"""
197
```
198
199
**Usage Examples:**
200
201
```python
202
from voluptuous import Schema, SetTo, Any, All
203
import uuid
204
import datetime
205
206
# Always set specific values
207
audit_schema = Schema({
208
'data': dict, # User data
209
'created_by': SetTo('system'), # Always 'system'
210
'created_at': SetTo(datetime.datetime.now), # Always current time
211
'id': SetTo(lambda: str(uuid.uuid4())), # Always new UUID
212
'version': SetTo(1), # Always version 1
213
})
214
215
# Conditional value setting
216
def status_based_priority(data):
217
if data.get('status') == 'urgent':
218
return SetTo(1) # High priority
219
elif data.get('status') == 'low':
220
return SetTo(5) # Low priority
221
else:
222
return SetTo(3) # Medium priority
223
224
task_schema = Schema({
225
'title': str,
226
'status': str,
227
'priority': status_based_priority, # Set based on status
228
})
229
230
# Default or override pattern
231
flexible_schema = Schema({
232
'name': str,
233
'type': Any(str, SetTo('default')), # Use provided or default to 'default'
234
})
235
236
# Usage examples:
237
audit_result = audit_schema({'data': {'key': 'value'}})
238
# Result includes system-generated fields regardless of input
239
240
task_result = task_schema({'title': 'Fix bug', 'status': 'urgent'})
241
# Result: {'title': 'Fix bug', 'status': 'urgent', 'priority': 1}
242
```
243
244
### Collection Conversion
245
246
Convert between different collection types.
247
248
```python { .api }
249
class Set:
250
def __init__(self, msg=None):
251
"""
252
Convert iterable to Python set.
253
254
Parameters:
255
- msg: Custom error message if conversion fails
256
257
Returns:
258
Set containing unique elements from input iterable
259
260
Raises:
261
TypeInvalid: If input cannot be converted to set (not iterable or contains unhashable types)
262
"""
263
```
264
265
**Usage Examples:**
266
267
```python
268
from voluptuous import Schema, Set, All, Length
269
270
# Remove duplicates by converting to set
271
unique_tags_schema = Schema({
272
'tags': All([str], Set()), # Convert list to set (removes duplicates)
273
})
274
275
# Validate set properties
276
permissions_schema = Schema(All(
277
[str], # Start with list of strings
278
Set(), # Convert to set
279
lambda s: len(s) >= 2, # Must have at least 2 unique permissions
280
))
281
282
# Combined with other validation
283
category_schema = Schema(All(
284
[str], # List of strings
285
lambda lst: [s.strip().lower() for s in lst], # Normalize
286
Set(), # Remove duplicates
287
lambda s: len(s) <= 10, # Max 10 unique categories
288
))
289
290
# Usage examples:
291
unique_tags_schema({'tags': ['python', 'web', 'python', 'api']})
292
# Result: {'tags': {'python', 'web', 'api'}}
293
294
permissions_schema(['read', 'write', 'read', 'admin'])
295
# Result: {'read', 'write', 'admin'}
296
```
297
298
### Literal Value Matching
299
300
Validate exact literal values with custom comparison.
301
302
```python { .api }
303
class Literal:
304
def __init__(self, lit):
305
"""
306
Validate exact literal value match.
307
308
Parameters:
309
- lit: Literal value that input must exactly match
310
311
Returns:
312
The literal value if input matches exactly
313
314
Raises:
315
LiteralInvalid: If input doesn't match literal value exactly
316
"""
317
```
318
319
**Usage Examples:**
320
321
```python
322
from voluptuous import Schema, Literal, Any
323
324
# API version validation
325
api_schema = Schema({
326
'version': Literal('v1'), # Must be exactly 'v1'
327
'format': Literal('json'), # Must be exactly 'json'
328
})
329
330
# Multiple literal options
331
status_schema = Schema({
332
'status': Any(
333
Literal('active'),
334
Literal('inactive'),
335
Literal('pending'),
336
),
337
})
338
339
# Configuration flags
340
feature_flags = Schema({
341
'new_ui': Any(Literal(True), Literal(False)), # Boolean literals
342
'api_version': Any(Literal(1), Literal(2)), # Integer literals
343
'mode': Any(Literal('dev'), Literal('prod')), # String literals
344
})
345
346
# Null/None validation
347
nullable_field = Schema({
348
'optional_field': Any(str, Literal(None)), # String or exactly None
349
})
350
351
# Usage examples:
352
api_schema({'version': 'v1', 'format': 'json'}) # Valid
353
# api_schema({'version': 'v2', 'format': 'json'}) # Invalid - wrong version
354
355
status_schema({'status': 'active'}) # Valid
356
feature_flags({'new_ui': True, 'api_version': 1, 'mode': 'prod'}) # Valid
357
```
358
359
### Transformation Pipelines
360
361
Complex data transformation patterns using multiple transformers.
362
363
**Text Normalization Pipeline:**
364
365
```python
366
from voluptuous import Schema, All, Strip, Lower, Replace, Length
367
import re
368
369
# Complete text normalization
370
normalize_text = All(
371
str, # Ensure string
372
Strip, # Remove leading/trailing whitespace
373
lambda s: re.sub(r'\s+', ' ', s), # Normalize internal whitespace
374
Lower, # Convert to lowercase
375
lambda s: s if s else None, # Convert empty strings to None
376
)
377
378
# URL slug generation
379
create_slug = All(
380
str,
381
Strip,
382
Lower,
383
Replace(r'[^\w\s-]', ''), # Remove special characters
384
Replace(r'\s+', '-'), # Replace spaces with hyphens
385
Replace(r'-+', '-'), # Collapse multiple hyphens
386
lambda s: s.strip('-'), # Remove leading/trailing hyphens
387
)
388
389
# Usage:
390
normalize_text(' Hello World ') # -> 'hello world'
391
create_slug('My Blog Post Title!') # -> 'my-blog-post-title'
392
```
393
394
**Data Cleaning Pipeline:**
395
396
```python
397
from voluptuous import Schema, All, DefaultTo, Strip, Coerce
398
399
# Clean and validate user input
400
clean_user_data = Schema({
401
'name': All(str, Strip, DefaultTo('Anonymous')),
402
'age': All(Any(int, str), Coerce(int), Range(min=0, max=150)),
403
'email': All(str, Strip, Lower, Any(Email(), DefaultTo(None))),
404
'tags': All(
405
Any(list, str), # Accept list or comma-separated string
406
lambda x: x.split(',') if isinstance(x, str) else x, # Split if string
407
[All(str, Strip)], # Clean each tag
408
lambda tags: [t for t in tags if t], # Remove empty tags
409
Set(), # Remove duplicates
410
),
411
})
412
```
413
414
**Configuration Processing:**
415
416
```python
417
from voluptuous import Schema, All, DefaultTo, Coerce, Any
418
419
# Process configuration with defaults and coercion
420
config_processor = Schema({
421
'database': {
422
'host': All(str, Strip, DefaultTo('localhost')),
423
'port': All(Any(int, str), Coerce(int), Range(min=1024, max=65535), DefaultTo(5432)),
424
'name': All(str, Strip),
425
'ssl': All(Any(bool, str), Boolean, DefaultTo(True)),
426
},
427
'cache': {
428
'enabled': All(Any(bool, str), Boolean, DefaultTo(False)),
429
'ttl': All(Any(int, str), Coerce(int), Range(min=1), DefaultTo(3600)),
430
},
431
'features': All(
432
Any([str], str), # List or comma-separated string
433
lambda x: x.split(',') if isinstance(x, str) else x,
434
[All(str, Strip, Lower)], # Normalize feature names
435
Set(), # Remove duplicates
436
DefaultTo(set()), # Default to empty set
437
),
438
})
439
```
440
441
**Flexible Data Transformation:**
442
443
```python
444
from voluptuous import Schema, All, Any, SetTo
445
446
def smart_transform(field_name, transforms):
447
"""Apply different transformations based on field name or value."""
448
def transformer(value):
449
for condition, transform in transforms:
450
if condition(field_name, value):
451
return transform(value)
452
return value
453
return transformer
454
455
# Dynamic field processing
456
dynamic_schema = Schema({
457
'username': smart_transform('username', [
458
(lambda f, v: isinstance(v, str), All(Strip, Lower)),
459
(lambda f, v: True, SetTo('anonymous')), # Fallback
460
]),
461
'created_at': smart_transform('created_at', [
462
(lambda f, v: v is None, SetTo(datetime.datetime.now)),
463
(lambda f, v: isinstance(v, str), lambda s: datetime.datetime.fromisoformat(s)),
464
(lambda f, v: True, lambda x: x), # Pass through
465
]),
466
})
467
```