0
# Validation Composers
1
2
Logical composition of multiple validators using boolean-like operations, union types with discriminant functions, and flexible validation count requirements. These composable validators enable complex validation logic through simple combinations.
3
4
## Capabilities
5
6
### Any (Or) Validator
7
8
Value must pass at least one of the provided validators. Uses the first validator that succeeds.
9
10
```python { .api }
11
class Any:
12
def __init__(self, *validators, msg=None, required=False, discriminant=None):
13
"""
14
Value must pass at least one validator.
15
16
Parameters:
17
- validators: Validator functions, types, or values to try in order
18
- msg: Custom error message if all validators fail
19
- required: Whether this validator is required
20
- discriminant: Function to filter which validators to try based on input value
21
22
Returns:
23
Result from first successful validator
24
25
Raises:
26
AnyInvalid: If no validators pass
27
"""
28
29
# Alias for Any
30
Or = Any
31
```
32
33
**Usage Examples:**
34
35
```python
36
from voluptuous import Schema, Any, Coerce
37
38
# Accept multiple types
39
flexible_schema = Schema({
40
'id': Any(int, str), # Can be integer or string
41
'active': Any(bool, Coerce(bool)), # Boolean or coercible to boolean
42
})
43
44
# Multiple validation options
45
email_or_phone = Any(Email(), Match(r'^\+?[\d\s-()]+$'))
46
47
# With custom message
48
age_schema = Schema(Any(
49
int,
50
Coerce(int),
51
msg="Age must be an integer or convertible to integer"
52
))
53
```
54
55
### All (And) Validator
56
57
Value must pass all validators in sequence. Output of each validator feeds to the next.
58
59
```python { .api }
60
class All:
61
def __init__(self, *validators, msg=None, required=False, discriminant=None):
62
"""
63
Value must pass all validators in sequence.
64
65
Parameters:
66
- validators: Validator functions, types, or values to apply in order
67
- msg: Custom error message if any validator fails
68
- required: Whether this validator is required
69
- discriminant: Function to filter which validators to apply
70
71
Returns:
72
Result from final validator in chain
73
74
Raises:
75
AllInvalid: If any validator fails
76
"""
77
78
# Alias for All
79
And = All
80
```
81
82
**Usage Examples:**
83
84
```python
85
from voluptuous import Schema, All, Range, Length, Coerce
86
87
# Chain multiple validations
88
username_schema = Schema(All(
89
str, # Must be string
90
Length(min=3, max=20), # Must be 3-20 characters
91
Match(r'^[a-zA-Z0-9_]+$'), # Must be alphanumeric + underscore
92
))
93
94
# Type coercion then validation
95
price_schema = Schema(All(
96
Coerce(float), # Convert to float
97
Range(min=0.01), # Must be positive
98
))
99
100
# Complex data transformation
101
normalized_email = Schema(All(
102
str,
103
Strip, # Remove whitespace
104
Lower, # Convert to lowercase
105
Email(), # Validate email format
106
))
107
```
108
109
### Union (Switch) Validator
110
111
Like Any but with a discriminant function to select which validators to try based on the input value.
112
113
```python { .api }
114
class Union:
115
def __init__(self, *validators, msg=None, discriminant=None):
116
"""
117
Select validators to try based on discriminant function.
118
119
Parameters:
120
- validators: Validator functions, types, or values
121
- msg: Custom error message
122
- discriminant: Function that takes input value and returns which validators to try
123
124
Returns:
125
Result from successful validator
126
127
Raises:
128
AnyInvalid: If no selected validators pass
129
"""
130
131
# Alias for Union
132
Switch = Union
133
```
134
135
**Usage Examples:**
136
137
```python
138
from voluptuous import Schema, Union, Required
139
140
def api_discriminant(value):
141
"""Select validators based on API version."""
142
if isinstance(value, dict) and value.get('version') == 'v1':
143
return [v1_validator]
144
elif isinstance(value, dict) and value.get('version') == 'v2':
145
return [v2_validator]
146
return [v1_validator, v2_validator] # Try both
147
148
v1_validator = Schema({Required('name'): str})
149
v2_validator = Schema({Required('full_name'): str, Required('email'): str})
150
151
api_schema = Schema(Union(
152
v1_validator,
153
v2_validator,
154
discriminant=api_discriminant
155
))
156
157
# Uses v1 validator
158
api_schema({'version': 'v1', 'name': 'John'})
159
160
# Uses v2 validator
161
api_schema({'version': 'v2', 'full_name': 'John Doe', 'email': 'john@example.com'})
162
```
163
164
### SomeOf Validator
165
166
Value must pass between a minimum and maximum number of validators from the provided set.
167
168
```python { .api }
169
class SomeOf:
170
def __init__(self, validators, min_valid=None, max_valid=None):
171
"""
172
Value must pass between min and max validators.
173
174
Parameters:
175
- validators: List of validators to try
176
- min_valid: Minimum number of validators that must pass (default: 1)
177
- max_valid: Maximum number of validators that can pass (default: unlimited)
178
179
Returns:
180
Original input value if validation count requirements are met
181
182
Raises:
183
NotEnoughValid: If fewer than min_valid validators pass
184
TooManyValid: If more than max_valid validators pass
185
"""
186
```
187
188
**Usage Examples:**
189
190
```python
191
from voluptuous import Schema, SomeOf, Length, Match
192
193
# Password must satisfy at least 2 of 3 complexity rules
194
password_complexity = SomeOf([
195
Length(min=8), # At least 8 characters
196
Match(r'[A-Z]'), # Contains uppercase
197
Match(r'[0-9]'), # Contains numbers
198
], min_valid=2)
199
200
password_schema = Schema(password_complexity)
201
202
# Passes: has length and uppercase
203
password_schema('MyPassword')
204
205
# Passes: has length and numbers
206
password_schema('password123')
207
208
# Fails: only satisfies length requirement
209
# password_schema('password') # Raises NotEnoughValid
210
211
# Exactly one authentication method
212
auth_method = SomeOf([
213
Match(r'^user:'), # Username-based
214
Match(r'^token:'), # Token-based
215
Match(r'^key:'), # API key-based
216
], min_valid=1, max_valid=1)
217
218
# Security policy: exactly 2 of 3 factors
219
two_factor = SomeOf([
220
lambda x: 'password' in x, # Something you know
221
lambda x: 'device_id' in x, # Something you have
222
lambda x: 'biometric' in x, # Something you are
223
], min_valid=2, max_valid=2)
224
```
225
226
### Maybe Validator
227
228
Convenience validator that allows None or validates with the given validator. Equivalent to `Any(None, validator)`.
229
230
```python { .api }
231
def Maybe(validator, msg=None):
232
"""
233
Allow None or validate with given validator.
234
235
Parameters:
236
- validator: Validator to apply if value is not None
237
- msg: Custom error message
238
239
Returns:
240
None if input is None, otherwise result of validator
241
"""
242
```
243
244
**Usage Examples:**
245
246
```python
247
from voluptuous import Schema, Maybe, Email, Required
248
249
# Optional email field
250
user_schema = Schema({
251
Required('name'): str,
252
Required('email'): Maybe(Email()), # None or valid email
253
Required('website'): Maybe(Url()), # None or valid URL
254
})
255
256
# All valid:
257
user_schema({'name': 'John', 'email': None, 'website': None})
258
user_schema({'name': 'John', 'email': 'john@example.com', 'website': None})
259
user_schema({'name': 'John', 'email': None, 'website': 'https://example.com'})
260
```
261
262
### Composition Patterns
263
264
Common patterns for combining validators effectively.
265
266
**Sequential Processing:**
267
268
```python
269
from voluptuous import Schema, All, Strip, Lower, Length, Match
270
271
# Clean and validate user input
272
clean_username = All(
273
str, # Ensure string
274
Strip, # Remove whitespace
275
Lower, # Convert to lowercase
276
Length(min=3, max=20), # Validate length
277
Match(r'^[a-z][a-z0-9_]*$'), # Validate format
278
)
279
```
280
281
**Flexible Type Handling:**
282
283
```python
284
from voluptuous import Schema, Any, All, Coerce, Range
285
286
# Accept various numeric representations
287
flexible_number = Any(
288
int, # Already an integer
289
All(str, Coerce(int)), # String that converts to int
290
All(float, lambda x: int(x) if x.is_integer() else None), # Whole number float
291
)
292
293
# Flexible price validation
294
price_validator = Any(
295
All(int, Range(min=0)), # Integer price (cents)
296
All(float, Range(min=0.0)), # Float price (dollars)
297
All(str, Coerce(float), Range(min=0.0)), # String price convertible to float
298
)
299
```
300
301
**Conditional Validation:**
302
303
```python
304
from voluptuous import Schema, Any, Required, Optional
305
306
def conditional_schema(data):
307
"""Different validation based on user type."""
308
if data.get('user_type') == 'admin':
309
return Schema({
310
Required('username'): str,
311
Required('permissions'): [str],
312
Optional('department'): str,
313
})
314
else:
315
return Schema({
316
Required('username'): str,
317
Required('email'): Email(),
318
})
319
320
# Usage in a validator
321
user_schema = Schema(lambda data: conditional_schema(data)(data))
322
```
323
324
**Error Aggregation:**
325
326
```python
327
from voluptuous import Schema, All, MultipleInvalid
328
329
def validate_all_fields(data):
330
"""Validate multiple fields and collect all errors."""
331
errors = []
332
333
field_schemas = {
334
'name': All(str, Length(min=1)),
335
'email': Email(),
336
'age': All(int, Range(min=0, max=150)),
337
}
338
339
validated = {}
340
for field, schema in field_schemas.items():
341
try:
342
if field in data:
343
validated[field] = schema(data[field])
344
except Invalid as e:
345
e.prepend([field])
346
errors.append(e)
347
348
if errors:
349
raise MultipleInvalid(errors)
350
351
return validated
352
```