or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mdcore-validation.mderror-handling.mdindex.mdschema-management.mdtype-system.md

type-system.mddocs/

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

```