or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

class-definition.mdconfiguration-errors.mdfield-configuration.mdindex.mdutilities.mdvalidation-conversion.md

validation-conversion.mddocs/

0

# Validation and Conversion

1

2

Comprehensive validation and conversion system with built-in validators, converters, and combinators for complex data processing. attrs provides both simple validation/conversion functions and powerful combinators for complex scenarios.

3

4

## Capabilities

5

6

### Validators

7

8

#### Type Validation

9

10

Validate that attribute values are instances of specific types.

11

12

```python { .api }

13

def instance_of(*types):

14

"""

15

Validate that value is an instance of any of the given types.

16

17

Parameters:

18

- *types: One or more types to check against

19

20

Returns:

21

Validator function that raises TypeError if value is not an instance

22

"""

23

24

def is_callable():

25

"""

26

Validate that value is callable.

27

28

Returns:

29

Validator function that raises NotCallableError if value is not callable

30

"""

31

```

32

33

Usage examples:

34

```python

35

@attrs.define

36

class Person:

37

name: str = attrs.field(validator=attrs.validators.instance_of(str))

38

age: int = attrs.field(validator=attrs.validators.instance_of(int))

39

callback: callable = attrs.field(validator=attrs.validators.is_callable())

40

```

41

42

#### Value Validation

43

44

Validate attribute values against specific criteria.

45

46

```python { .api }

47

def in_(collection):

48

"""

49

Validate that value is in the given collection.

50

51

Parameters:

52

- collection: Collection to check membership in

53

54

Returns:

55

Validator function that raises ValueError if value not in collection

56

"""

57

58

def matches_re(regex, flags=0, func=None):

59

"""

60

Validate that string value matches regular expression pattern.

61

62

Parameters:

63

- regex (str or Pattern): Regular expression pattern

64

- flags (int): Regex flags (default: 0)

65

- func (callable, optional): Function to extract string from value

66

67

Returns:

68

Validator function that raises ValueError if value doesn't match

69

"""

70

```

71

72

Usage examples:

73

```python

74

@attrs.define

75

class Config:

76

level: str = attrs.field(validator=attrs.validators.in_(["debug", "info", "warning", "error"]))

77

email: str = attrs.field(validator=attrs.validators.matches_re(r'^[^@]+@[^@]+\.[^@]+$'))

78

```

79

80

#### Numeric Validation

81

82

Validate numeric values against comparison criteria.

83

84

```python { .api }

85

def lt(val):

86

"""Validate that value is less than val."""

87

88

def le(val):

89

"""Validate that value is less than or equal to val."""

90

91

def ge(val):

92

"""Validate that value is greater than or equal to val."""

93

94

def gt(val):

95

"""Validate that value is greater than val."""

96

97

def max_len(length):

98

"""

99

Validate that value has maximum length.

100

101

Parameters:

102

- length (int): Maximum allowed length

103

"""

104

105

def min_len(length):

106

"""

107

Validate that value has minimum length.

108

109

Parameters:

110

- length (int): Minimum required length

111

"""

112

```

113

114

Usage examples:

115

```python

116

@attrs.define

117

class Product:

118

price: float = attrs.field(validator=[attrs.validators.ge(0), attrs.validators.lt(10000)])

119

name: str = attrs.field(validator=[attrs.validators.min_len(1), attrs.validators.max_len(100)])

120

rating: int = attrs.field(validator=[attrs.validators.ge(1), attrs.validators.le(5)])

121

```

122

123

#### Deep Validation

124

125

Validate nested data structures recursively.

126

127

```python { .api }

128

def deep_iterable(member_validator, iterable_validator=None):

129

"""

130

Validate iterable and its members.

131

132

Parameters:

133

- member_validator: Validator for each member

134

- iterable_validator (optional): Validator for the iterable itself

135

136

Returns:

137

Validator function for iterables with validated members

138

"""

139

140

def deep_mapping(key_validator, value_validator, mapping_validator=None):

141

"""

142

Validate mapping and its keys/values.

143

144

Parameters:

145

- key_validator: Validator for each key

146

- value_validator: Validator for each value

147

- mapping_validator (optional): Validator for the mapping itself

148

149

Returns:

150

Validator function for mappings with validated keys and values

151

"""

152

```

153

154

Usage examples:

155

```python

156

@attrs.define

157

class Database:

158

# List of strings

159

tables: list = attrs.field(

160

validator=attrs.validators.deep_iterable(

161

member_validator=attrs.validators.instance_of(str),

162

iterable_validator=attrs.validators.instance_of(list)

163

)

164

)

165

166

# Dict with string keys and int values

167

counts: dict = attrs.field(

168

validator=attrs.validators.deep_mapping(

169

key_validator=attrs.validators.instance_of(str),

170

value_validator=attrs.validators.instance_of(int)

171

)

172

)

173

```

174

175

### Validator Combinators

176

177

#### Optional Validation

178

179

Make validators optional (allow None values).

180

181

```python { .api }

182

def optional(validator):

183

"""

184

Make a validator optional by allowing None values.

185

186

Parameters:

187

- validator: Validator to make optional

188

189

Returns:

190

Validator that passes None values and validates others

191

"""

192

```

193

194

Usage example:

195

```python

196

@attrs.define

197

class User:

198

name: str = attrs.field(validator=attrs.validators.instance_of(str))

199

nickname: str = attrs.field(

200

default=None,

201

validator=attrs.validators.optional(attrs.validators.instance_of(str))

202

)

203

```

204

205

#### Logical Combinators

206

207

Combine validators with logical operations.

208

209

```python { .api }

210

def and_(*validators):

211

"""

212

Combine validators with AND logic - all must pass.

213

214

Parameters:

215

- *validators: Validators to combine

216

217

Returns:

218

Validator that requires all validators to pass

219

"""

220

221

def or_(*validators):

222

"""

223

Combine validators with OR logic - at least one must pass.

224

225

Parameters:

226

- *validators: Validators to combine

227

228

Returns:

229

Validator that requires at least one validator to pass

230

"""

231

232

def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)):

233

"""

234

Negate a validator - pass if validator fails.

235

236

Parameters:

237

- validator: Validator to negate

238

- msg (str, optional): Custom error message

239

- exc_types (tuple): Exception types to catch and negate

240

241

Returns:

242

Validator that passes when the given validator fails

243

"""

244

```

245

246

Usage examples:

247

```python

248

@attrs.define

249

class Configuration:

250

# Must be string and not empty

251

name: str = attrs.field(

252

validator=attrs.validators.and_(

253

attrs.validators.instance_of(str),

254

attrs.validators.min_len(1)

255

)

256

)

257

258

# Must be int or float

259

value: Union[int, float] = attrs.field(

260

validator=attrs.validators.or_(

261

attrs.validators.instance_of(int),

262

attrs.validators.instance_of(float)

263

)

264

)

265

```

266

267

### Validator Control

268

269

#### Global Validator Control

270

271

Control validator execution globally.

272

273

```python { .api }

274

def set_disabled(disabled):

275

"""

276

Globally disable or enable validator execution.

277

278

Parameters:

279

- disabled (bool): True to disable validators, False to enable

280

"""

281

282

def get_disabled():

283

"""

284

Check if validators are globally disabled.

285

286

Returns:

287

bool: True if validators are disabled, False if enabled

288

"""

289

290

@contextmanager

291

def disabled():

292

"""

293

Context manager to temporarily disable validators.

294

295

Returns:

296

Context manager that disables validators within its scope

297

"""

298

```

299

300

Usage examples:

301

```python

302

# Globally disable validators (not recommended for production)

303

attrs.validators.set_disabled(True)

304

305

# Temporarily disable for bulk operations

306

with attrs.validators.disabled():

307

# Create objects without validation

308

users = [User(name=name, age=age) for name, age in raw_data]

309

```

310

311

### Converters

312

313

#### Basic Converters

314

315

Convert values to different types or formats.

316

317

```python { .api }

318

def optional(converter):

319

"""

320

Make a converter optional by allowing None values to pass through.

321

322

Parameters:

323

- converter: Converter to make optional

324

325

Returns:

326

Converter that passes None values and converts others

327

"""

328

329

def default_if_none(default=NOTHING, factory=None):

330

"""

331

Replace None values with default or factory result.

332

333

Parameters:

334

- default: Default value to use (mutually exclusive with factory)

335

- factory (callable, optional): Function to generate default value

336

337

Returns:

338

Converter that replaces None with default/factory result

339

"""

340

341

def to_bool(val):

342

"""

343

Convert value to boolean using intelligent rules.

344

345

Parameters:

346

- val: Value to convert ("true", "false", 1, 0, etc.)

347

348

Returns:

349

bool: Converted boolean value

350

"""

351

```

352

353

Usage examples:

354

```python

355

@attrs.define

356

class Settings:

357

# Convert string to int, but allow None

358

timeout: Optional[int] = attrs.field(

359

default=None,

360

converter=attrs.converters.optional(int)

361

)

362

363

# Replace None with empty list

364

features: list = attrs.field(

365

converter=attrs.converters.default_if_none(factory=list)

366

)

367

368

# Convert various formats to boolean

369

debug: bool = attrs.field(

370

default="false",

371

converter=attrs.converters.to_bool

372

)

373

```

374

375

#### Converter Combinators

376

377

Chain multiple converters together.

378

379

```python { .api }

380

def pipe(*converters):

381

"""

382

Chain converters - output of one becomes input of next.

383

384

Parameters:

385

- *converters: Converters to chain in order

386

387

Returns:

388

Converter that applies all converters in sequence

389

"""

390

```

391

392

Usage example:

393

```python

394

@attrs.define

395

class Document:

396

# Strip whitespace, then convert to Path

397

filename: Path = attrs.field(

398

converter=attrs.converters.pipe(str.strip, Path)

399

)

400

401

# Convert to string, strip, then to uppercase

402

category: str = attrs.field(

403

converter=attrs.converters.pipe(str, str.strip, str.upper)

404

)

405

```

406

407

### Setters

408

409

Control how attributes are set after initialization.

410

411

```python { .api }

412

def pipe(*setters):

413

"""

414

Chain multiple setters together.

415

416

Parameters:

417

- *setters: Setters to chain in order

418

419

Returns:

420

Setter that applies all setters in sequence

421

"""

422

423

def frozen(instance, attribute, new_value):

424

"""

425

Prevent attribute modification by raising FrozenAttributeError.

426

427

Use this to make specific attributes immutable even in mutable classes.

428

"""

429

430

def validate(instance, attribute, new_value):

431

"""

432

Run attribute's validator on new value during setattr.

433

434

Parameters:

435

- instance: Object instance

436

- attribute: Attribute descriptor

437

- new_value: Value being set

438

439

Returns:

440

Validated value (or raises validation error)

441

"""

442

443

def convert(instance, attribute, new_value):

444

"""

445

Run attribute's converter on new value during setattr.

446

447

Parameters:

448

- instance: Object instance

449

- attribute: Attribute descriptor

450

- new_value: Value being set

451

452

Returns:

453

Converted value

454

"""

455

456

NO_OP: object # Sentinel to disable on_setattr for specific attributes

457

```

458

459

Usage examples:

460

```python

461

@attrs.define(on_setattr=attrs.setters.validate)

462

class ValidatedClass:

463

name: str = attrs.field(validator=attrs.validators.instance_of(str))

464

# Changes to name after initialization will be validated

465

466

@attrs.define

467

class MixedClass:

468

# Always validate and convert on changes

469

value: int = attrs.field(

470

converter=int,

471

validator=attrs.validators.instance_of(int),

472

on_setattr=attrs.setters.pipe(attrs.setters.convert, attrs.setters.validate)

473

)

474

475

# Frozen after initialization

476

id: str = attrs.field(on_setattr=attrs.setters.frozen)

477

478

# No special setattr behavior

479

temp: str = attrs.field(on_setattr=attrs.setters.NO_OP)

480

```

481

482

## Common Patterns

483

484

### Comprehensive Validation

485

```python

486

@attrs.define

487

class User:

488

username: str = attrs.field(

489

validator=[

490

attrs.validators.instance_of(str),

491

attrs.validators.min_len(3),

492

attrs.validators.max_len(20),

493

attrs.validators.matches_re(r'^[a-zA-Z0-9_]+$')

494

]

495

)

496

497

age: int = attrs.field(

498

validator=[

499

attrs.validators.instance_of(int),

500

attrs.validators.ge(0),

501

attrs.validators.le(150)

502

]

503

)

504

```

505

506

### Data Cleaning with Converters

507

```python

508

@attrs.define

509

class Contact:

510

# Clean and normalize email

511

email: str = attrs.field(

512

converter=attrs.converters.pipe(str.strip, str.lower),

513

validator=attrs.validators.matches_re(r'^[^@]+@[^@]+\.[^@]+$')

514

)

515

516

# Parse phone number

517

phone: str = attrs.field(

518

converter=lambda x: ''.join(filter(str.isdigit, str(x))),

519

validator=attrs.validators.min_len(10)

520

)

521

```

522

523

### Optional Fields with Defaults

524

```python

525

@attrs.define

526

class APIResponse:

527

status: int = attrs.field(validator=attrs.validators.in_([200, 400, 404, 500]))

528

529

# Optional message with default

530

message: str = attrs.field(

531

converter=attrs.converters.default_if_none("OK"),

532

validator=attrs.validators.optional(attrs.validators.instance_of(str))

533

)

534

535

# Optional data that gets empty dict if None

536

data: dict = attrs.field(

537

converter=attrs.converters.default_if_none(factory=dict)

538

)

539

```