0
# Type System
1
2
Cerberus provides an extensible type system that allows defining custom types for validation. This enables domain-specific validation logic and type constraints beyond the built-in types.
3
4
```python
5
# Import types and utilities for custom type definitions
6
from cerberus import TypeDefinition, validator_factory
7
from cerberus.utils import readonly_classproperty
8
9
# Platform-specific types used in built-in definitions
10
from cerberus.platform import Container, Mapping, Sequence, _str_type, _int_types
11
```
12
13
## Capabilities
14
15
### TypeDefinition
16
17
Named tuple for defining custom types that can be used in validator type mappings.
18
19
```python { .api }
20
TypeDefinition = namedtuple('TypeDefinition', 'name,included_types,excluded_types')
21
"""
22
Defines a custom type for validation.
23
24
Fields:
25
- name: Descriptive type name (str)
26
- included_types: Tuple of allowed Python types
27
- excluded_types: Tuple of excluded Python types
28
29
A value is valid for this type if it's an instance of any type in
30
included_types and not an instance of any type in excluded_types.
31
"""
32
```
33
34
### Validator Factory
35
36
Factory function for creating custom validator classes with mixins.
37
38
```python { .api }
39
def validator_factory(name, bases=None, namespace={}):
40
"""
41
Dynamically create a Validator subclass with mixin capabilities.
42
43
Parameters:
44
- name: Name of the new validator class (str)
45
- bases: Base/mixin classes to include (tuple, class, or None)
46
- namespace: Additional class attributes (dict)
47
48
Returns:
49
type: New Validator subclass with combined functionality
50
51
Note: Docstrings from mixin classes are automatically combined
52
if __doc__ is not specified in namespace.
53
"""
54
```
55
56
### Utility Classes
57
58
Utility classes for advanced property management.
59
60
```python { .api }
61
class readonly_classproperty(property):
62
"""
63
Creates read-only class properties that raise errors on modification attempts.
64
65
Usage:
66
class MyValidator(Validator):
67
@readonly_classproperty
68
def my_property(cls):
69
return "read-only value"
70
"""
71
72
def __get__(self, instance, owner): ...
73
def __set__(self, instance, value): ... # Raises RuntimeError
74
def __delete__(self, instance): ... # Raises RuntimeError
75
```
76
77
### Built-in Type Definitions
78
79
Cerberus provides a comprehensive set of built-in type definitions that map common Python types to validation constraints.
80
81
```python { .api }
82
# Built-in types_mapping in Validator class
83
types_mapping = {
84
'binary': TypeDefinition('binary', (bytes, bytearray), ()),
85
'boolean': TypeDefinition('boolean', (bool,), ()),
86
'container': TypeDefinition('container', (Container,), (_str_type,)),
87
'date': TypeDefinition('date', (date,), ()),
88
'datetime': TypeDefinition('datetime', (datetime,), ()),
89
'dict': TypeDefinition('dict', (Mapping,), ()),
90
'float': TypeDefinition('float', (float, _int_types), ()),
91
'integer': TypeDefinition('integer', (_int_types,), ()),
92
'list': TypeDefinition('list', (Sequence,), (_str_type,)),
93
'number': TypeDefinition('number', (_int_types, float), (bool,)),
94
'set': TypeDefinition('set', (set,), ()),
95
'string': TypeDefinition('string', (_str_type,), ()),
96
}
97
```
98
99
**Type Details:**
100
101
- **binary**: Accepts bytes and bytearray objects for binary data
102
- **boolean**: Accepts bool values (True/False)
103
- **container**: Accepts any Container type except strings (_str_type)
104
- **date**: Accepts datetime.date objects
105
- **datetime**: Accepts datetime.datetime objects
106
- **dict**: Accepts any Mapping type (dict, OrderedDict, etc.)
107
- **float**: Accepts float and integer types (_int_types can be coerced to floats)
108
- **integer**: Accepts integer types (_int_types) only
109
- **list**: Accepts any Sequence type except strings (_str_type)
110
- **number**: Accepts integer types (_int_types) and float but excludes bool (even though bool is subclass of int)
111
- **set**: Accepts set objects
112
- **string**: Accepts string types (_str_type)
113
114
## Usage Examples
115
116
### Defining Custom Types
117
118
```python
119
from cerberus import Validator, TypeDefinition
120
121
# Define a custom type for positive numbers
122
positive_number = TypeDefinition(
123
'positive_number',
124
(int, float), # Include integers and floats
125
() # No excluded types
126
)
127
128
# Add to validator's type mapping
129
class CustomValidator(Validator):
130
def __init__(self, *args, **kwargs):
131
super().__init__(*args, **kwargs)
132
self.types_mapping['positive_number'] = positive_number
133
134
# Define custom validation method for positive constraint
135
def _validate_positive(self, positive, field, value):
136
"""Test if value is positive"""
137
if positive and value <= 0:
138
self._error(field, "must be positive")
139
140
# Add the method to our custom validator
141
CustomValidator._validate_positive = _validate_positive
142
143
# Use the custom type and validator
144
schema = {
145
'count': {'type': 'positive_number', 'positive': True}
146
}
147
148
v = CustomValidator(schema)
149
print(v.validate({'count': 5})) # True
150
print(v.validate({'count': -1})) # False
151
print(v.validate({'count': '5'})) # False (not int or float)
152
```
153
154
### Complex Type Definitions
155
156
```python
157
from cerberus import TypeDefinition
158
159
# Define a type that accepts strings but excludes empty strings
160
non_empty_string = TypeDefinition(
161
'non_empty_string',
162
(str,),
163
() # We'll handle empty string logic in custom validator
164
)
165
166
# Define a type for numeric types excluding booleans
167
numeric_no_bool = TypeDefinition(
168
'numeric_no_bool',
169
(int, float, complex),
170
(bool,) # Exclude booleans even though bool is subclass of int
171
)
172
173
# Use in schema
174
schema = {
175
'name': {'type': 'non_empty_string'},
176
'value': {'type': 'numeric_no_bool'}
177
}
178
```
179
180
### Using validator_factory
181
182
```python
183
from cerberus import validator_factory
184
from cerberus.validator import Validator
185
186
# Define mixin classes
187
class EmailValidatorMixin:
188
"""Adds email validation capabilities"""
189
190
def _validate_email_domain(self, domain, field, value):
191
"""Validate email domain"""
192
if '@' in value and not value.split('@')[1].endswith(domain):
193
self._error(field, f"must be from {domain} domain")
194
195
class NumericValidatorMixin:
196
"""Adds numeric validation capabilities"""
197
198
def _validate_even(self, even, field, value):
199
"""Validate if number is even"""
200
if even and value % 2 != 0:
201
self._error(field, "must be even")
202
203
# Create custom validator class with mixins
204
CustomValidator = validator_factory(
205
'CustomValidator',
206
(EmailValidatorMixin, NumericValidatorMixin),
207
{
208
'custom_attribute': 'custom_value'
209
}
210
)
211
212
# Use the custom validator
213
schema = {
214
'email': {'type': 'string', 'email_domain': '.com'},
215
'count': {'type': 'integer', 'even': True}
216
}
217
218
v = CustomValidator(schema)
219
print(v.validate({
220
'email': 'user@example.com',
221
'count': 4
222
})) # True
223
224
print(v.validate({
225
'email': 'user@example.org', # Wrong domain
226
'count': 3 # Not even
227
})) # False
228
```
229
230
### Custom Types with Built-in Types
231
232
```python
233
from cerberus import Validator, TypeDefinition
234
235
# Create validator with custom types
236
class ExtendedValidator(Validator):
237
def __init__(self, *args, **kwargs):
238
super().__init__(*args, **kwargs)
239
240
# Add custom types to existing mapping
241
self.types_mapping.update({
242
'phone_number': TypeDefinition('phone_number', (str,), ()),
243
'uuid': TypeDefinition('uuid', (str,), ()),
244
'positive_int': TypeDefinition('positive_int', (int,), (bool,))
245
})
246
247
# Define validation methods for custom types
248
def _validate_phone_format(self, phone_format, field, value):
249
"""Validate phone number format"""
250
import re
251
if phone_format and not re.match(r'^\+?1?\d{9,15}$', value):
252
self._error(field, "invalid phone number format")
253
254
def _validate_uuid_format(self, uuid_format, field, value):
255
"""Validate UUID format"""
256
import re
257
if uuid_format and not re.match(r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', value.lower()):
258
self._error(field, "invalid UUID format")
259
260
def _validate_positive(self, positive, field, value):
261
"""Validate positive number"""
262
if positive and value <= 0:
263
self._error(field, "must be positive")
264
265
# Add methods to validator
266
ExtendedValidator._validate_phone_format = _validate_phone_format
267
ExtendedValidator._validate_uuid_format = _validate_uuid_format
268
ExtendedValidator._validate_positive = _validate_positive
269
270
# Use custom types in schema
271
schema = {
272
'id': {'type': 'uuid', 'uuid_format': True},
273
'phone': {'type': 'phone_number', 'phone_format': True},
274
'count': {'type': 'positive_int', 'positive': True}
275
}
276
277
v = ExtendedValidator(schema)
278
```
279
280
### Multiple Inheritance with validator_factory
281
282
```python
283
from cerberus import validator_factory
284
285
class LoggingMixin:
286
"""Adds logging to validation"""
287
288
def validate(self, *args, **kwargs):
289
print(f"Starting validation with schema: {list(self.schema.keys())}")
290
result = super().validate(*args, **kwargs)
291
print(f"Validation result: {result}")
292
return result
293
294
class CachingMixin:
295
"""Adds result caching"""
296
297
def __init__(self, *args, **kwargs):
298
super().__init__(*args, **kwargs)
299
self._cache = {}
300
301
def validate(self, document, *args, **kwargs):
302
doc_hash = hash(str(sorted(document.items())))
303
if doc_hash in self._cache:
304
return self._cache[doc_hash]
305
306
result = super().validate(document, *args, **kwargs)
307
self._cache[doc_hash] = result
308
return result
309
310
# Create validator with multiple mixins
311
EnhancedValidator = validator_factory(
312
'EnhancedValidator',
313
(LoggingMixin, CachingMixin)
314
)
315
316
v = EnhancedValidator({'name': {'type': 'string'}})
317
v.validate({'name': 'test'}) # Logs and caches result
318
v.validate({'name': 'test'}) # Uses cached result
319
```
320
321
### Read-only Class Properties
322
323
```python
324
from cerberus import Validator
325
from cerberus.utils import readonly_classproperty
326
327
class ConfiguredValidator(Validator):
328
@readonly_classproperty
329
def default_schema(cls):
330
return {
331
'id': {'type': 'integer', 'required': True},
332
'name': {'type': 'string', 'required': True}
333
}
334
335
@readonly_classproperty
336
def version(cls):
337
return "1.0.0"
338
339
# Access read-only properties
340
print(ConfiguredValidator.default_schema) # Works
341
print(ConfiguredValidator.version) # Works
342
343
# Attempting to modify raises RuntimeError
344
try:
345
ConfiguredValidator.version = "2.0.0"
346
except RuntimeError as e:
347
print(f"Error: {e}") # "This is a readonly class property."
348
```
349
350
### Type System Integration
351
352
```python
353
from cerberus import Validator, TypeDefinition
354
355
class BusinessValidator(Validator):
356
def __init__(self, *args, **kwargs):
357
super().__init__(*args, **kwargs)
358
359
# Define business-specific types
360
self.types_mapping.update({
361
'currency': TypeDefinition('currency', (int, float), (bool,)),
362
'percentage': TypeDefinition('percentage', (int, float), (bool,)),
363
'business_id': TypeDefinition('business_id', (str,), ())
364
})
365
366
# Add business rule validations
367
def _validate_currency_positive(self, currency_positive, field, value):
368
if currency_positive and value < 0:
369
self._error(field, "currency amounts must be positive")
370
371
def _validate_percentage_range(self, percentage_range, field, value):
372
if percentage_range and not (0 <= value <= 100):
373
self._error(field, "percentage must be between 0 and 100")
374
375
def _validate_business_id_format(self, business_id_format, field, value):
376
if business_id_format and not value.startswith('BIZ'):
377
self._error(field, "business ID must start with 'BIZ'")
378
379
BusinessValidator._validate_currency_positive = _validate_currency_positive
380
BusinessValidator._validate_percentage_range = _validate_percentage_range
381
BusinessValidator._validate_business_id_format = _validate_business_id_format
382
383
# Use in business domain schemas
384
product_schema = {
385
'id': {'type': 'business_id', 'business_id_format': True},
386
'price': {'type': 'currency', 'currency_positive': True},
387
'discount': {'type': 'percentage', 'percentage_range': True}
388
}
389
390
v = BusinessValidator(product_schema)
391
result = v.validate({
392
'id': 'BIZ123',
393
'price': 29.99,
394
'discount': 15
395
})
396
print(result) # True
397
```