0
# Range & Collection Validation
1
2
Numeric range validation, sequence length validation, membership testing, and complex sequence validation patterns. These validators handle constraints on numeric values, collection sizes, and collection contents.
3
4
## Capabilities
5
6
### Numeric Range Validation
7
8
Validate that numeric values fall within specified ranges with configurable boundary inclusion.
9
10
```python { .api }
11
class Range:
12
def __init__(self, min=None, max=None, min_included=True, max_included=True, msg=None):
13
"""
14
Validate numeric value is within range.
15
16
Parameters:
17
- min: Minimum allowed value (None for no minimum)
18
- max: Maximum allowed value (None for no maximum)
19
- min_included: Whether minimum value is included in valid range (default True)
20
- max_included: Whether maximum value is included in valid range (default True)
21
- msg: Custom error message
22
23
Returns:
24
Original numeric value if within range
25
26
Raises:
27
RangeInvalid: If value is outside specified range
28
"""
29
```
30
31
**Usage Examples:**
32
33
```python
34
from voluptuous import Schema, Range, All, Coerce
35
36
# Basic range validation
37
age_validator = Range(min=0, max=150)
38
percentage_validator = Range(min=0.0, max=100.0)
39
40
# Open ranges
41
positive_validator = Range(min=0) # No maximum
42
negative_validator = Range(max=0) # No minimum
43
44
# Exclusive ranges
45
exclusive_range = Range(min=0, max=10, min_included=False, max_included=False) # 0 < x < 10
46
47
# Combined with type coercion
48
flexible_range = Schema(All(
49
Coerce(float), # Convert to float first
50
Range(min=0.0, max=1.0), # Then validate range
51
))
52
53
# Schema usage
54
product_schema = Schema({
55
'price': All(Coerce(float), Range(min=0.01)), # Positive price
56
'discount': Range(min=0, max=100), # Percentage
57
'quantity': All(int, Range(min=1)), # At least 1
58
})
59
60
# Valid examples
61
product_schema({
62
'price': '19.99', # Coerced to 19.99
63
'discount': 25, # 25% discount
64
'quantity': 5, # 5 items
65
})
66
```
67
68
### Numeric Clamping
69
70
Automatically adjust numeric values to stay within specified bounds instead of rejecting them.
71
72
```python { .api }
73
class Clamp:
74
def __init__(self, min=None, max=None, msg=None):
75
"""
76
Clamp numeric value to specified range.
77
78
Parameters:
79
- min: Minimum allowed value (values below are set to min)
80
- max: Maximum allowed value (values above are set to max)
81
- msg: Custom error message (rarely used since values are adjusted)
82
83
Returns:
84
Numeric value clamped to specified range
85
86
Raises:
87
Invalid: If input is not numeric
88
"""
89
```
90
91
**Usage Examples:**
92
93
```python
94
from voluptuous import Schema, Clamp, All, Coerce
95
96
# Clamp values instead of rejecting them
97
volume_control = Clamp(min=0, max=100) # Volume: 0-100
98
opacity_clamp = Clamp(min=0.0, max=1.0) # Opacity: 0.0-1.0
99
100
# One-way clamping
101
non_negative = Clamp(min=0) # No upper limit
102
max_limit = Clamp(max=1000) # No lower limit
103
104
# Usage examples
105
settings_schema = Schema({
106
'volume': All(Coerce(int), volume_control),
107
'opacity': All(Coerce(float), opacity_clamp),
108
'timeout': All(Coerce(int), Clamp(min=1, max=3600)), # 1 second to 1 hour
109
})
110
111
# Clamping in action:
112
volume_control(150) # -> 100 (clamped to maximum)
113
volume_control(-10) # -> 0 (clamped to minimum)
114
opacity_clamp(1.5) # -> 1.0 (clamped to maximum)
115
```
116
117
### Length Validation
118
119
Validate the length of sequences, strings, and other objects with `len()`.
120
121
```python { .api }
122
class Length:
123
def __init__(self, min=None, max=None, msg=None):
124
"""
125
Validate length of sequences, strings, etc.
126
127
Parameters:
128
- min: Minimum allowed length (None for no minimum)
129
- max: Maximum allowed length (None for no maximum)
130
- msg: Custom error message
131
132
Returns:
133
Original object if length is within range
134
135
Raises:
136
LengthInvalid: If length is outside specified range
137
"""
138
```
139
140
**Usage Examples:**
141
142
```python
143
from voluptuous import Schema, Length, All
144
145
# String length validation
146
username_length = Length(min=3, max=20)
147
password_length = Length(min=8) # At least 8 characters
148
description_length = Length(max=500) # At most 500 characters
149
150
# List length validation
151
tags_length = Length(min=1, max=10) # 1-10 tags
152
items_length = Length(min=0, max=100) # 0-100 items
153
154
# Combined validation
155
user_schema = Schema({
156
'username': All(str, username_length),
157
'password': All(str, password_length),
158
'bio': All(str, description_length),
159
'tags': All([str], tags_length),
160
})
161
162
# Dictionary length (number of keys)
163
config_schema = Schema(All(
164
dict,
165
Length(min=1), # At least one configuration key
166
))
167
168
# Valid examples
169
user_schema({
170
'username': 'john_doe', # 8 characters (valid)
171
'password': 'secretpassword', # 14 characters (valid)
172
'bio': 'Software developer', # 18 characters (valid)
173
'tags': ['python', 'web'], # 2 tags (valid)
174
})
175
```
176
177
### Numeric Precision Validation
178
179
Validate numeric precision and scale for high-precision financial or scientific calculations.
180
181
```python { .api }
182
class Number:
183
def __init__(self, precision=None, scale=None, msg=None, yield_decimal=False):
184
"""
185
Validate numeric precision and scale.
186
187
Parameters:
188
- precision: Maximum total number of digits (None for no limit)
189
- scale: Maximum number of digits after decimal point (None for no limit)
190
- msg: Custom error message
191
- yield_decimal: Return Decimal object instead of original number
192
193
Returns:
194
Original number or Decimal object if yield_decimal=True
195
196
Raises:
197
Invalid: If number exceeds precision or scale limits
198
"""
199
```
200
201
**Usage Examples:**
202
203
```python
204
from voluptuous import Schema, Number, All, Coerce
205
from decimal import Decimal
206
207
# Financial precision validation
208
price_validator = Number(precision=10, scale=2) # Max 10 digits, 2 decimal places
209
currency_validator = Number(precision=15, scale=4) # High precision currency
210
211
# Scientific precision
212
measurement_validator = Number(precision=8, scale=6) # Scientific measurements
213
214
# With Decimal conversion
215
financial_schema = Schema({
216
'amount': All(
217
Coerce(Decimal), # Convert to Decimal first
218
Number(precision=12, scale=2, yield_decimal=True) # Validate and return Decimal
219
),
220
'rate': Number(precision=6, scale=4), # Interest rate with 4 decimal places
221
})
222
223
# Usage examples:
224
price_validator(123.45) # Valid: 5 digits total, 2 decimal places
225
price_validator(12345678.99) # Valid: 10 digits total, 2 decimal places
226
# price_validator(123.456) # Invalid: too many decimal places
227
228
currency_validator(1234567890.1234) # Valid: 14 digits total, 4 decimal places
229
230
# Financial calculations
231
financial_schema({
232
'amount': '1234.56', # Converted to Decimal(1234.56)
233
'rate': 0.0525, # 5.25% interest rate
234
})
235
```
236
237
### Membership Testing
238
239
Validate that values are present or absent in specified containers.
240
241
```python { .api }
242
class In:
243
def __init__(self, container, msg=None):
244
"""
245
Validate value is in specified container.
246
247
Parameters:
248
- container: Container to check membership in (list, set, dict keys, etc.)
249
- msg: Custom error message
250
251
Returns:
252
Original value if found in container
253
254
Raises:
255
InInvalid: If value not found in container
256
"""
257
258
class NotIn:
259
def __init__(self, container, msg=None):
260
"""
261
Validate value is not in specified container.
262
263
Parameters:
264
- container: Container to check membership against
265
- msg: Custom error message
266
267
Returns:
268
Original value if not found in container
269
270
Raises:
271
NotInInvalid: If value found in container
272
"""
273
```
274
275
**Usage Examples:**
276
277
```python
278
from voluptuous import Schema, In, NotIn, All
279
280
# Whitelist validation
281
status_validator = In(['active', 'inactive', 'pending', 'suspended'])
282
priority_validator = In([1, 2, 3, 4, 5])
283
country_validator = In(['US', 'CA', 'UK', 'DE', 'FR', 'JP'])
284
285
# Blacklist validation
286
forbidden_usernames = NotIn(['admin', 'root', 'system', 'administrator'])
287
forbidden_ports = NotIn([22, 23, 25, 53, 80, 110, 443])
288
289
# Combined validation
290
user_schema = Schema({
291
'username': All(str, forbidden_usernames, Length(min=3)),
292
'status': status_validator,
293
'priority': priority_validator,
294
'country': country_validator,
295
})
296
297
# Server configuration
298
server_schema = Schema({
299
'port': All(int, Range(min=1024, max=65535), forbidden_ports),
300
'protocol': In(['http', 'https', 'tcp', 'udp']),
301
})
302
303
# Dynamic containers
304
def get_valid_roles():
305
return ['user', 'moderator', 'admin'] # Could be from database
306
307
role_validator = lambda value: In(get_valid_roles())(value)
308
```
309
310
### Collection Content Validation
311
312
Validate that collections contain specific items or match expected patterns.
313
314
```python { .api }
315
class Contains:
316
def __init__(self, item, msg=None):
317
"""
318
Validate sequence contains specific item.
319
320
Parameters:
321
- item: Item that must be present in sequence
322
- msg: Custom error message
323
324
Returns:
325
Original sequence if item is found
326
327
Raises:
328
ContainsInvalid: If item not found in sequence
329
"""
330
```
331
332
**Usage Examples:**
333
334
```python
335
from voluptuous import Schema, Contains, All, Length
336
337
# Required items validation
338
required_permissions = Contains('read') # Must have 'read' permission
339
required_features = Contains('authentication') # Must include auth feature
340
341
# Multiple required items
342
permissions_schema = All(
343
[str], # List of strings
344
Contains('read'), # Must contain 'read'
345
Contains('write'), # Must contain 'write'
346
Length(min=2), # At least 2 permissions
347
)
348
349
# Configuration validation
350
app_config_schema = Schema({
351
'features': All([str], Contains('core')), # Core feature is required
352
'plugins': All([str], Length(min=0)), # Optional plugins
353
})
354
355
# Email list validation
356
email_list_schema = All(
357
[str],
358
Contains(lambda emails: any('@company.com' in email for email in emails)), # At least one company email
359
)
360
```
361
362
### Sequence Structure Validation
363
364
Validate sequences against specific structural patterns.
365
366
```python { .api }
367
class ExactSequence:
368
def __init__(self, validators, msg=None):
369
"""
370
Validate sequence elements against validators in exact order.
371
372
Parameters:
373
- validators: List of validators, one per sequence element
374
- msg: Custom error message
375
376
Returns:
377
Validated sequence with each element validated by corresponding validator
378
379
Raises:
380
ExactSequenceInvalid: If sequence length doesn't match validators or validation fails
381
"""
382
383
class Unordered:
384
def __init__(self, validators, msg=None):
385
"""
386
Validate sequence contains elements matching validators in any order.
387
388
Parameters:
389
- validators: List of validators that must all match some element
390
- msg: Custom error message
391
392
Returns:
393
Original sequence if all validators match some element
394
395
Raises:
396
Invalid: If any validator doesn't match any element
397
"""
398
399
class Unique:
400
def __init__(self, msg=None):
401
"""
402
Ensure sequence contains no duplicate elements.
403
404
Parameters:
405
- msg: Custom error message
406
407
Returns:
408
Original sequence if all elements are unique
409
410
Raises:
411
Invalid: If duplicate elements are found
412
"""
413
```
414
415
**Usage Examples:**
416
417
```python
418
from voluptuous import Schema, ExactSequence, Unordered, Unique, All, Range
419
420
# Exact sequence validation (fixed structure)
421
coordinates_validator = ExactSequence([
422
All(float, Range(min=-90, max=90)), # Latitude
423
All(float, Range(min=-180, max=180)), # Longitude
424
All(float, Range(min=0)), # Altitude (optional, positive)
425
])
426
427
rgb_color_validator = ExactSequence([
428
All(int, Range(min=0, max=255)), # Red
429
All(int, Range(min=0, max=255)), # Green
430
All(int, Range(min=0, max=255)), # Blue
431
])
432
433
# Unordered validation (flexible structure)
434
required_fields_validator = Unordered([
435
'name', # Must contain 'name'
436
'email', # Must contain 'email'
437
'age', # Must contain 'age'
438
])
439
440
# Unique elements validation
441
unique_tags = All([str], Unique())
442
unique_ids = All([int], Unique())
443
444
# Combined sequence validation
445
data_schema = Schema({
446
'coordinates': coordinates_validator, # [lat, lon, alt]
447
'color': rgb_color_validator, # [r, g, b]
448
'required_fields': required_fields_validator,
449
'tags': unique_tags, # Unique string tags
450
})
451
452
# Usage examples:
453
coordinates_validator([40.7128, -74.0060, 10.0]) # Valid coordinates
454
rgb_color_validator([255, 128, 0]) # Valid orange color
455
unique_tags(['python', 'web', 'api']) # Valid unique tags
456
```
457
458
### Exact Value Matching
459
460
Ensure value exactly matches a target value (no coercion or transformation).
461
462
```python { .api }
463
class Equal:
464
def __init__(self, target, msg=None):
465
"""
466
Ensure value exactly matches target value.
467
468
Parameters:
469
- target: Exact value that input must match
470
- msg: Custom error message
471
472
Returns:
473
Original value if it exactly matches target
474
475
Raises:
476
Invalid: If value doesn't exactly match target
477
"""
478
```
479
480
**Usage Examples:**
481
482
```python
483
from voluptuous import Schema, Equal, Any, Required
484
485
# API status validation
486
status_schema = Schema({
487
'status': Equal('OK'), # Must be exactly 'OK'
488
'version': Equal(1), # Must be exactly 1 (integer)
489
'enabled': Equal(True), # Must be exactly True (boolean)
490
})
491
492
# Configuration validation
493
config_schema = Schema({
494
'debug': Equal(False), # Must be exactly False
495
'max_retries': Equal(3), # Must be exactly 3
496
'api_version': Any(Equal('v1'), Equal('v2')), # Must be 'v1' or 'v2'
497
})
498
499
# Validate exact matches
500
status_schema({'status': 'OK', 'version': 1, 'enabled': True}) # Valid
501
config_schema({'debug': False, 'max_retries': 3, 'api_version': 'v1'}) # Valid
502
503
# These would fail:
504
# status_schema({'status': 'ok'}) # Fails: 'ok' != 'OK' (case sensitive)
505
# config_schema({'debug': 0}) # Fails: 0 != False (no coercion)
506
```
507
508
### Advanced Collection Patterns
509
510
Complex validation patterns for sophisticated collection requirements.
511
512
**Conditional Collection Validation:**
513
514
```python
515
from voluptuous import Schema, All, Any, Length, Contains
516
517
def conditional_length(data):
518
"""Different length requirements based on data type."""
519
if data.get('type') == 'premium':
520
return All([str], Length(min=5, max=20)) # Premium users get more tags
521
else:
522
return All([str], Length(min=1, max=10)) # Regular users get fewer tags
523
524
user_schema = Schema({
525
'type': In(['basic', 'premium']),
526
'tags': conditional_length,
527
})
528
```
529
530
**Nested Collection Validation:**
531
532
```python
533
from voluptuous import Schema, All, Length, Range
534
535
# Validate list of lists (matrix)
536
matrix_validator = All(
537
[[int]], # List of lists of integers
538
Length(min=1), # At least one row
539
lambda matrix: all(len(row) == len(matrix[0]) for row in matrix), # Rectangular
540
)
541
542
# Validate hierarchical data
543
tree_node = Schema({
544
'value': int,
545
'children': All([lambda x: tree_node(x)], Length(max=10)), # Recursive validation
546
})
547
```
548
549
**Collection Aggregation Validation:**
550
551
```python
552
from voluptuous import Schema, All
553
554
def sum_constraint(target_sum):
555
"""Validate that numeric list sums to target value."""
556
def validator(values):
557
if sum(values) != target_sum:
558
raise ValueError(f"Sum must equal {target_sum}, got {sum(values)}")
559
return values
560
return validator
561
562
def average_constraint(min_avg, max_avg):
563
"""Validate that numeric list average is within range."""
564
def validator(values):
565
if not values:
566
raise ValueError("Cannot calculate average of empty list")
567
avg = sum(values) / len(values)
568
if not (min_avg <= avg <= max_avg):
569
raise ValueError(f"Average {avg} not in range [{min_avg}, {max_avg}]")
570
return values
571
return validator
572
573
# Budget allocation (percentages must sum to 100)
574
budget_schema = Schema(All(
575
[All(float, Range(min=0, max=100))], # Each percentage 0-100
576
sum_constraint(100.0), # Must sum to 100%
577
Length(min=1), # At least one category
578
))
579
580
# Performance scores (average must be acceptable)
581
scores_schema = Schema(All(
582
[All(int, Range(min=0, max=100))], # Each score 0-100
583
average_constraint(70, 100), # Average must be 70-100
584
Length(min=3), # At least 3 scores
585
))
586
```