or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

csrf.mdfields.mdforms.mdi18n.mdindex.mdvalidation.mdwidgets.md

validation.mddocs/

0

# Validation

1

2

Robust validation framework with built-in validators for common use cases and support for custom validation logic. The validation system provides data requirements, length constraints, format validation, cross-field comparisons, and extensible custom validation patterns.

3

4

## Capabilities

5

6

### Validation Exceptions

7

8

Core exception classes for validation error handling.

9

10

```python { .api }

11

class ValidationError(ValueError):

12

"""

13

Exception raised when validation fails.

14

15

Parameters:

16

- message: Error message string

17

"""

18

def __init__(self, message=""): ...

19

20

class StopValidation(Exception):

21

"""

22

Exception that stops the validation chain.

23

Used by validators like Optional to halt further validation.

24

25

Parameters:

26

- message: Optional error message

27

"""

28

def __init__(self, message=""): ...

29

```

30

31

### Data Requirement Validators

32

33

Validators that check for presence and validity of input data.

34

35

```python { .api }

36

class DataRequired:

37

"""

38

Validates that field contains truthy data.

39

Fails on empty strings, empty lists, None, etc.

40

41

Parameters:

42

- message: Custom error message

43

"""

44

def __init__(self, message=None): ...

45

def __call__(self, form, field): ...

46

47

class InputRequired:

48

"""

49

Validates that form input was provided (even if empty).

50

Unlike DataRequired, accepts empty strings as valid input.

51

Sets 'required' flag on field.

52

53

Parameters:

54

- message: Custom error message

55

"""

56

def __init__(self, message=None): ...

57

def __call__(self, form, field): ...

58

59

class Optional:

60

"""

61

Allows empty input and stops validation chain.

62

If field is empty, no further validators are called.

63

Sets 'optional' flag on field.

64

65

Parameters:

66

- strip_whitespace: Whether to strip whitespace before checking (default: True)

67

"""

68

def __init__(self, strip_whitespace=True): ...

69

def __call__(self, form, field): ...

70

```

71

72

### Length and Range Validators

73

74

Validators for constraining input length and numeric ranges.

75

76

```python { .api }

77

class Length:

78

"""

79

Validates string length within specified bounds.

80

Sets 'minlength' and 'maxlength' flags on field.

81

82

Parameters:

83

- min: Minimum length (-1 for no minimum, default: -1)

84

- max: Maximum length (-1 for no maximum, default: -1)

85

- message: Custom error message

86

"""

87

def __init__(self, min=-1, max=-1, message=None): ...

88

def __call__(self, form, field): ...

89

90

class NumberRange:

91

"""

92

Validates numeric value within specified range.

93

Sets 'min' and 'max' flags on field.

94

95

Parameters:

96

- min: Minimum value (None for no minimum)

97

- max: Maximum value (None for no maximum)

98

- message: Custom error message

99

"""

100

def __init__(self, min=None, max=None, message=None): ...

101

def __call__(self, form, field): ...

102

```

103

104

### Comparison and Equality Validators

105

106

Validators for comparing field values.

107

108

```python { .api }

109

class EqualTo:

110

"""

111

Compares field value to another field in the same form.

112

Commonly used for password confirmation fields.

113

114

Parameters:

115

- fieldname: Name of field to compare against

116

- message: Custom error message

117

"""

118

def __init__(self, fieldname, message=None): ...

119

def __call__(self, form, field): ...

120

```

121

122

### Pattern and Format Validators

123

124

Validators for specific data formats and patterns.

125

126

```python { .api }

127

class Regexp:

128

"""

129

Validates field data against regular expression pattern.

130

131

Parameters:

132

- regex: Regular expression pattern (string or compiled regex)

133

- flags: Regex flags (default: 0)

134

- message: Custom error message

135

"""

136

def __init__(self, regex, flags=0, message=None): ...

137

def __call__(self, form, field): ...

138

139

class Email:

140

"""

141

Validates email address format.

142

Requires 'email_validator' package for full validation.

143

144

Parameters:

145

- message: Custom error message

146

- granular_message: Whether to show specific validation errors

147

- check_deliverability: Whether to check if domain accepts email

148

- allow_smtputf8: Whether to allow international domains

149

- allow_empty_local: Whether to allow empty local part

150

"""

151

def __init__(self, message=None, granular_message=False,

152

check_deliverability=True, allow_smtputf8=True,

153

allow_empty_local=False): ...

154

def __call__(self, form, field): ...

155

156

class URL:

157

"""

158

Validates URL format.

159

160

Parameters:

161

- require_tld: Whether to require top-level domain (default: True)

162

- message: Custom error message

163

"""

164

def __init__(self, require_tld=True, message=None): ...

165

def __call__(self, form, field): ...

166

167

class IPAddress:

168

"""

169

Validates IP address format.

170

171

Parameters:

172

- ipv4: Whether to allow IPv4 addresses (default: True)

173

- ipv6: Whether to allow IPv6 addresses (default: False)

174

- message: Custom error message

175

"""

176

def __init__(self, ipv4=True, ipv6=False, message=None): ...

177

def __call__(self, form, field): ...

178

179

class MacAddress:

180

"""

181

Validates MAC address format.

182

183

Parameters:

184

- message: Custom error message

185

"""

186

def __init__(self, message=None): ...

187

def __call__(self, form, field): ...

188

189

class UUID:

190

"""

191

Validates UUID format.

192

193

Parameters:

194

- message: Custom error message

195

"""

196

def __init__(self, message=None): ...

197

def __call__(self, form, field): ...

198

```

199

200

### Choice Validators

201

202

Validators for constraining values to specific choices.

203

204

```python { .api }

205

class AnyOf:

206

"""

207

Validates that field value is in allowed list.

208

209

Parameters:

210

- values: Iterable of allowed values

211

- message: Custom error message

212

- values_formatter: Function to format values in error message

213

"""

214

def __init__(self, values, message=None, values_formatter=None): ...

215

def __call__(self, form, field): ...

216

217

class NoneOf:

218

"""

219

Validates that field value is NOT in disallowed list.

220

221

Parameters:

222

- values: Iterable of disallowed values

223

- message: Custom error message

224

- values_formatter: Function to format values in error message

225

"""

226

def __init__(self, values, message=None, values_formatter=None): ...

227

def __call__(self, form, field): ...

228

```

229

230

### State Validators

231

232

Validators that set field state or behavior.

233

234

```python { .api }

235

class ReadOnly:

236

"""

237

Marks field as read-only for display purposes.

238

Sets 'readonly' flag on field.

239

240

Parameters:

241

- message: Custom error message if field is modified

242

"""

243

def __init__(self, message=None): ...

244

def __call__(self, form, field): ...

245

246

class Disabled:

247

"""

248

Marks field as disabled.

249

Sets 'disabled' flag on field.

250

251

Parameters:

252

- message: Custom error message if field is modified

253

"""

254

def __init__(self, message=None): ...

255

def __call__(self, form, field): ...

256

```

257

258

### Validator Function Aliases

259

260

WTForms provides lowercase function aliases for all validator classes for convenience.

261

262

```python { .api }

263

# Lowercase aliases for validator classes

264

data_required = DataRequired

265

input_required = InputRequired

266

optional = Optional

267

length = Length

268

number_range = NumberRange

269

equal_to = EqualTo

270

regexp = Regexp

271

email = Email

272

url = URL

273

ip_address = IPAddress

274

mac_address = MacAddress

275

any_of = AnyOf

276

none_of = NoneOf

277

readonly = ReadOnly

278

disabled = Disabled

279

```

280

281

## Validation Usage Examples

282

283

### Basic Validation

284

285

```python

286

from wtforms import Form, StringField, IntegerField, validators

287

288

class UserForm(Form):

289

username = StringField('Username', [

290

validators.DataRequired(message="Username is required"),

291

validators.Length(min=4, max=25, message="Username must be 4-25 characters")

292

])

293

294

email = StringField('Email', [

295

validators.DataRequired(),

296

validators.Email(message="Invalid email address")

297

])

298

299

age = IntegerField('Age', [

300

validators.NumberRange(min=13, max=120, message="Age must be 13-120")

301

])

302

303

bio = StringField('Bio', [

304

validators.Optional(), # Field is optional

305

validators.Length(max=500) # But if provided, max 500 chars

306

])

307

308

# Validation

309

form = UserForm(formdata=request.form)

310

if form.validate():

311

# All fields passed validation

312

process_user_data(form.data)

313

else:

314

# Display errors

315

for field, errors in form.errors.items():

316

for error in errors:

317

print(f"{field}: {error}")

318

```

319

320

### Password Confirmation

321

322

```python

323

class RegistrationForm(Form):

324

password = StringField('Password', [

325

validators.DataRequired(),

326

validators.Length(min=8, message="Password must be at least 8 characters")

327

])

328

329

confirm_password = StringField('Confirm Password', [

330

validators.DataRequired(),

331

validators.EqualTo('password', message='Passwords must match')

332

])

333

```

334

335

### Email Validation with Options

336

337

```python

338

class ContactForm(Form):

339

# Basic email validation

340

email = StringField('Email', [validators.Email()])

341

342

# Email with custom message and options

343

business_email = StringField('Business Email', [

344

validators.Email(

345

message="Please enter a valid business email",

346

check_deliverability=True, # Verify domain accepts email

347

granular_message=True # Show specific validation errors

348

)

349

])

350

```

351

352

### Pattern Validation

353

354

```python

355

class ProductForm(Form):

356

# SKU must be format: ABC-1234

357

sku = StringField('SKU', [

358

validators.Regexp(

359

r'^[A-Z]{3}-\d{4}$',

360

message="SKU must be format ABC-1234"

361

)

362

])

363

364

# Phone number validation

365

phone = StringField('Phone', [

366

validators.Regexp(

367

r'^\+?1?-?\(?(\d{3})\)?-?(\d{3})-?(\d{4})$',

368

message="Invalid phone number format"

369

)

370

])

371

```

372

373

### Choice Validation

374

375

```python

376

class SurveyForm(Form):

377

rating = IntegerField('Rating', [

378

validators.AnyOf([1, 2, 3, 4, 5], message="Rating must be 1-5")

379

])

380

381

language = StringField('Language', [

382

validators.AnyOf(['en', 'es', 'fr'], message="Unsupported language")

383

])

384

385

# Forbidden words

386

comment = StringField('Comment', [

387

validators.NoneOf(['spam', 'test'], message="Comment contains forbidden words")

388

])

389

```

390

391

### Network Address Validation

392

393

```python

394

class ServerForm(Form):

395

# IPv4 only

396

ipv4_address = StringField('IPv4 Address', [

397

validators.IPAddress(ipv4=True, ipv6=False)

398

])

399

400

# IPv6 only

401

ipv6_address = StringField('IPv6 Address', [

402

validators.IPAddress(ipv4=False, ipv6=True)

403

])

404

405

# Either IPv4 or IPv6

406

ip_address = StringField('IP Address', [

407

validators.IPAddress(ipv4=True, ipv6=True)

408

])

409

410

mac_address = StringField('MAC Address', [

411

validators.MacAddress()

412

])

413

414

server_id = StringField('Server ID', [

415

validators.UUID(message="Must be valid UUID")

416

])

417

```

418

419

### Custom Validators

420

421

```python

422

def validate_username_available(form, field):

423

"""Custom validator function."""

424

if User.query.filter_by(username=field.data).first():

425

raise ValidationError('Username already taken.')

426

427

class UsernameAvailable:

428

"""Custom validator class."""

429

def __init__(self, message=None):

430

self.message = message or 'Username already taken.'

431

432

def __call__(self, form, field):

433

if User.query.filter_by(username=field.data).first():

434

raise ValidationError(self.message)

435

436

class RegistrationForm(Form):

437

username = StringField('Username', [

438

validators.DataRequired(),

439

validate_username_available, # Function validator

440

UsernameAvailable() # Class validator

441

])

442

```

443

444

### Conditional Validation

445

446

```python

447

class ShippingForm(Form):

448

same_as_billing = BooleanField('Same as billing address')

449

shipping_address = StringField('Shipping Address')

450

451

def validate_shipping_address(self, field):

452

"""Custom validation method - only required if different from billing."""

453

if not self.same_as_billing.data and not field.data:

454

raise ValidationError('Shipping address is required.')

455

```

456

457

### Validation with Filters

458

459

```python

460

from wtforms.filters import strip_filter

461

462

def uppercase_filter(data):

463

"""Custom filter to uppercase data."""

464

return data.upper() if data else data

465

466

class ProductForm(Form):

467

name = StringField('Product Name',

468

filters=[strip_filter], # Remove leading/trailing whitespace

469

validators=[validators.DataRequired()]

470

)

471

472

code = StringField('Product Code',

473

filters=[strip_filter, uppercase_filter], # Strip and uppercase

474

validators=[validators.DataRequired()]

475

)

476

477

# Usage

478

form = ProductForm(formdata={'name': ' Widget ', 'code': ' abc123 '})

479

form.process()

480

print(form.name.data) # "Widget" (stripped)

481

print(form.code.data) # "ABC123" (stripped and uppercased)

482

```

483

484

### Validation Error Handling

485

486

```python

487

form = MyForm(formdata=request.form)

488

489

try:

490

if form.validate():

491

# Process valid form

492

save_form_data(form.data)

493

else:

494

# Handle validation errors

495

for field_name, errors in form.errors.items():

496

for error in errors:

497

flash(f"Error in {field_name}: {error}", 'error')

498

499

except ValidationError as e:

500

# Handle specific validation exception

501

flash(f"Validation failed: {e}", 'error')

502

503

# Access individual field errors

504

if form.email.errors:

505

email_errors = form.email.errors

506

print(f"Email validation failed: {', '.join(email_errors)}")

507

```

508

509

### Dynamic Validation

510

511

```python

512

class DynamicForm(Form):

513

email = StringField('Email')

514

515

def __init__(self, require_email=False, *args, **kwargs):

516

super().__init__(*args, **kwargs)

517

518

# Add validators dynamically

519

if require_email:

520

self.email.validators = [

521

validators.DataRequired(),

522

validators.Email()

523

]

524

else:

525

self.email.validators = [validators.Optional()]

526

527

# Usage

528

form = DynamicForm(require_email=True, formdata=request.form)

529

```

530

531

### Validation with Extra Validators

532

533

```python

534

def validate_during_business_hours(form, field):

535

"""Validator that only applies during business hours."""

536

if datetime.now().hour < 9 or datetime.now().hour > 17:

537

raise ValidationError('This form can only be submitted during business hours.')

538

539

class ContactForm(Form):

540

message = StringField('Message', [validators.DataRequired()])

541

542

# Add extra validation at runtime

543

form = ContactForm(formdata=request.form)

544

extra_validators = {

545

'message': [validate_during_business_hours]

546

}

547

548

if form.validate(extra_validators=extra_validators):

549

# Process form

550

pass

551

```