or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-schema.mderror-handling.mdindex.mdrange-collection-validators.mdstring-pattern-validators.mdtype-validators.mdutility-transformers.mdvalidation-composers.md

validation-composers.mddocs/

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

```