or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-validation.mderror-handling.mdformat-validation.mdindex.mdtype-checking.mdvalidator-creation.mdvalidators.md

validator-creation.mddocs/

0

# Validator Creation

1

2

Advanced validator creation and extension capabilities for building custom validators, extending existing ones, and registering new schema versions. These functions provide the foundation for creating specialized validation logic.

3

4

## Capabilities

5

6

### Creating Custom Validators

7

8

Build completely new validator classes with custom validation logic and meta-schemas.

9

10

```python { .api }

11

def create(meta_schema,

12

validators=(),

13

version=None,

14

type_checker=None,

15

format_checker=None,

16

id_of=None,

17

applicable_validators=None):

18

"""

19

Create a new validator class.

20

21

Parameters:

22

- meta_schema: Schema that describes valid schemas for this validator

23

- validators: Mapping of keyword names to validation functions

24

- version: Version identifier for this validator

25

- type_checker: TypeChecker instance for type validation

26

- format_checker: FormatChecker instance for format validation

27

- id_of: Function to extract schema ID from schema

28

- applicable_validators: Function to filter applicable validators

29

30

Returns:

31

- type[Validator]: New validator class

32

"""

33

```

34

35

### Extending Existing Validators

36

37

Create new validator classes by extending existing ones with additional or modified validation logic.

38

39

```python { .api }

40

def extend(validator,

41

validators=(),

42

version=None,

43

type_checker=None,

44

format_checker=None):

45

"""

46

Create a new validator class by extending an existing one.

47

48

Parameters:

49

- validator: Existing validator class to extend

50

- validators: Additional or replacement validation functions

51

- version: Version identifier for the new validator

52

- type_checker: TypeChecker to use (default: inherit from base)

53

- format_checker: FormatChecker to use (default: inherit from base)

54

55

Returns:

56

- type[Validator]: Extended validator class

57

"""

58

```

59

60

### Validator Registration

61

62

Register validators for automatic selection based on schema version.

63

64

```python { .api }

65

def validates(version):

66

"""

67

Decorator to register a validator for a schema version.

68

69

Parameters:

70

- version: Version identifier string

71

72

Returns:

73

- callable: Class decorator for validator registration

74

"""

75

```

76

77

### Validation Function Signature

78

79

All validation functions must follow this signature:

80

81

```python { .api }

82

def validation_function(validator, value, instance, schema):

83

"""

84

Custom validation function.

85

86

Parameters:

87

- validator: The validator instance

88

- value: The schema value for this keyword

89

- instance: The instance being validated

90

- schema: The complete schema being validated against

91

92

Yields:

93

- ValidationError: Each validation error found

94

"""

95

```

96

97

## Usage Examples

98

99

### Creating a Simple Custom Validator

100

101

```python

102

from jsonschema import create, ValidationError, Draft202012Validator

103

from jsonschema._types import draft202012_type_checker

104

from jsonschema._format import draft202012_format_checker

105

106

def even_number(validator, value, instance, schema):

107

"""Validate that a number is even."""

108

if not validator.is_type(instance, "number"):

109

return

110

111

if value and instance % 2 != 0:

112

yield ValidationError(f"{instance} is not an even number")

113

114

def minimum_length(validator, value, instance, schema):

115

"""Validate minimum string length."""

116

if not validator.is_type(instance, "string"):

117

return

118

119

if len(instance) < value:

120

yield ValidationError(f"String too short: {len(instance)} < {value}")

121

122

# Create custom validator

123

CustomValidator = create(

124

meta_schema=Draft202012Validator.META_SCHEMA,

125

validators={

126

"evenNumber": even_number,

127

"minimumLength": minimum_length,

128

# Include standard validators

129

**Draft202012Validator.VALIDATORS

130

},

131

type_checker=draft202012_type_checker,

132

format_checker=draft202012_format_checker,

133

version="custom-v1"

134

)

135

136

# Use custom validator

137

schema = {

138

"type": "object",

139

"properties": {

140

"count": {"type": "number", "evenNumber": True},

141

"name": {"type": "string", "minimumLength": 3}

142

}

143

}

144

145

validator = CustomValidator(schema)

146

147

# Test validation

148

valid_data = {"count": 4, "name": "Alice"}

149

validator.validate(valid_data) # Passes

150

151

invalid_data = {"count": 3, "name": "Al"} # Odd number, short name

152

errors = list(validator.iter_errors(invalid_data))

153

for error in errors:

154

print(f"Custom validation error: {error.message}")

155

```

156

157

### Extending Existing Validators

158

159

```python

160

from jsonschema import extend, Draft202012Validator, ValidationError

161

162

def divisible_by(validator, value, instance, schema):

163

"""Validate that a number is divisible by the given value."""

164

if not validator.is_type(instance, "number"):

165

return

166

167

if instance % value != 0:

168

yield ValidationError(f"{instance} is not divisible by {value}")

169

170

def contains_word(validator, value, instance, schema):

171

"""Validate that a string contains a specific word."""

172

if not validator.is_type(instance, "string"):

173

return

174

175

if value not in instance:

176

yield ValidationError(f"String does not contain required word: {value}")

177

178

# Extend Draft 2020-12 validator

179

ExtendedValidator = extend(

180

Draft202012Validator,

181

validators={

182

"divisibleBy": divisible_by,

183

"containsWord": contains_word

184

},

185

version="extended-draft2020-12"

186

)

187

188

# Use extended validator

189

schema = {

190

"type": "object",

191

"properties": {

192

"score": {"type": "number", "divisibleBy": 5},

193

"description": {"type": "string", "containsWord": "python"}

194

}

195

}

196

197

validator = ExtendedValidator(schema)

198

199

valid_data = {"score": 85, "description": "I love python programming"}

200

validator.validate(valid_data) # Passes

201

202

invalid_data = {"score": 87, "description": "I love javascript"}

203

errors = list(validator.iter_errors(invalid_data))

204

for error in errors:

205

print(f"Extended validation error: {error.message}")

206

```

207

208

### Registering Custom Validators

209

210

```python

211

from jsonschema import validates, create, validator_for

212

from jsonschema._types import draft202012_type_checker

213

from jsonschema._format import draft202012_format_checker

214

215

# Custom meta-schema

216

CUSTOM_META_SCHEMA = {

217

"$schema": "https://json-schema.org/draft/2020-12/schema",

218

"$id": "https://example.com/custom-schema",

219

"type": "object",

220

"properties": {

221

"type": {"type": "string"},

222

"customValidation": {"type": "boolean"}

223

}

224

}

225

226

def custom_validation(validator, value, instance, schema):

227

"""Custom validation logic."""

228

if value:

229

# Perform custom validation

230

if not isinstance(instance, str) or len(instance) < 5:

231

yield ValidationError("Custom validation failed")

232

233

@validates("custom-v1")

234

class CustomValidator:

235

META_SCHEMA = CUSTOM_META_SCHEMA

236

VALIDATORS = {"customValidation": custom_validation}

237

TYPE_CHECKER = draft202012_type_checker

238

FORMAT_CHECKER = draft202012_format_checker

239

240

def __init__(self, schema, **kwargs):

241

# Implementation similar to standard validators

242

pass

243

244

# Now validator_for will automatically select CustomValidator

245

schema_with_custom_version = {

246

"$schema": "https://example.com/custom-schema",

247

"type": "string",

248

"customValidation": True

249

}

250

251

ValidatorClass = validator_for(schema_with_custom_version)

252

print(ValidatorClass.__name__) # CustomValidator

253

```

254

255

### Complex Validation Functions

256

257

```python

258

from jsonschema import ValidationError

259

260

def unique_properties(validator, value, instance, schema):

261

"""

262

Validate that all property values in an object are unique.

263

"""

264

if not validator.is_type(instance, "object"):

265

return

266

267

if not value: # Skip if not enabled

268

return

269

270

values = list(instance.values())

271

seen = set()

272

duplicates = set()

273

274

for val in values:

275

# Only check hashable values

276

try:

277

if val in seen:

278

duplicates.add(val)

279

else:

280

seen.add(val)

281

except TypeError:

282

# Skip unhashable values

283

continue

284

285

if duplicates:

286

yield ValidationError(

287

f"Object has duplicate values: {duplicates}",

288

validator="uniqueProperties",

289

validator_value=value,

290

instance=instance,

291

schema=schema

292

)

293

294

def conditional_required(validator, value, instance, schema):

295

"""

296

Conditionally require properties based on other property values.

297

298

Example: {"ifProperty": "type", "equals": "user", "thenRequired": ["email"]}

299

"""

300

if not validator.is_type(instance, "object"):

301

return

302

303

if_prop = value.get("ifProperty")

304

equals = value.get("equals")

305

then_required = value.get("thenRequired", [])

306

307

if if_prop in instance and instance[if_prop] == equals:

308

for required_prop in then_required:

309

if required_prop not in instance:

310

yield ValidationError(

311

f"Property '{required_prop}' is required when '{if_prop}' equals '{equals}'",

312

validator="conditionalRequired",

313

validator_value=value,

314

instance=instance,

315

schema=schema,

316

path=[required_prop]

317

)

318

319

# Use complex validators

320

AdvancedValidator = extend(

321

Draft202012Validator,

322

validators={

323

"uniqueProperties": unique_properties,

324

"conditionalRequired": conditional_required

325

}

326

)

327

328

schema = {

329

"type": "object",

330

"uniqueProperties": True,

331

"conditionalRequired": {

332

"ifProperty": "type",

333

"equals": "user",

334

"thenRequired": ["email", "username"]

335

},

336

"properties": {

337

"type": {"type": "string"},

338

"email": {"type": "string"},

339

"username": {"type": "string"},

340

"name": {"type": "string"},

341

"age": {"type": "number"}

342

}

343

}

344

345

validator = AdvancedValidator(schema)

346

347

# Valid data

348

valid_data = {

349

"type": "user",

350

"email": "user@example.com",

351

"username": "john_doe",

352

"name": "John Doe",

353

"age": 30

354

}

355

validator.validate(valid_data) # Passes

356

357

# Invalid - duplicate values

358

invalid_duplicate = {

359

"name": "John",

360

"username": "John", # Duplicate value

361

"type": "admin"

362

}

363

364

# Invalid - missing required when type=user

365

invalid_missing = {

366

"type": "user",

367

"name": "John" # Missing email and username

368

}

369

370

for data in [invalid_duplicate, invalid_missing]:

371

errors = list(validator.iter_errors(data))

372

for error in errors:

373

print(f"Advanced validation error: {error.message}")

374

```

375

376

### Custom Meta-Schema Validation

377

378

```python

379

from jsonschema import create, ValidationError, SchemaError

380

381

# Define custom meta-schema

382

CUSTOM_META_SCHEMA = {

383

"$schema": "https://json-schema.org/draft/2020-12/schema",

384

"$id": "https://example.com/custom-meta-schema",

385

"type": "object",

386

"properties": {

387

"type": {"type": "string"},

388

"customKeyword": {"type": "string", "enum": ["strict", "loose"]}

389

},

390

"additionalProperties": False

391

}

392

393

def custom_keyword_validator(validator, value, instance, schema):

394

"""Custom keyword validation."""

395

if value == "strict" and isinstance(instance, str) and len(instance) < 10:

396

yield ValidationError("Strict mode requires strings of at least 10 characters")

397

398

CustomValidator = create(

399

meta_schema=CUSTOM_META_SCHEMA,

400

validators={"customKeyword": custom_keyword_validator}

401

)

402

403

# Valid schema according to custom meta-schema

404

valid_schema = {

405

"type": "string",

406

"customKeyword": "strict"

407

}

408

409

# Invalid schema - violates meta-schema

410

invalid_schema = {

411

"type": "string",

412

"customKeyword": "invalid_value", # Not in enum

413

"additionalProperty": "not_allowed" # Not allowed by meta-schema

414

}

415

416

try:

417

CustomValidator.check_schema(valid_schema)

418

print("Schema is valid")

419

except SchemaError as e:

420

print(f"Schema error: {e.message}")

421

422

try:

423

CustomValidator.check_schema(invalid_schema)

424

except SchemaError as e:

425

print(f"Schema validation failed: {e.message}")

426

```