0
# Custom Validators
1
2
Data validation beyond basic type checking using composable validator factories. The `beartype.vale` module provides a hierarchy of subscriptable classes for creating custom validation logic that integrates seamlessly with beartype's type checking system.
3
4
## Capabilities
5
6
### Generic Validator Factory
7
8
Create validators from arbitrary callable predicates.
9
10
```python { .api }
11
class Is:
12
"""
13
Generic validator factory for callable predicates.
14
15
Creates validators from functions that return boolean values.
16
Used with typing.Annotated to add validation logic to type hints.
17
"""
18
19
def __class_getitem__(cls, predicate):
20
"""
21
Create validator from predicate function.
22
23
Parameters:
24
- predicate: callable - Function that takes object and returns bool
25
26
Returns:
27
BeartypeValidator - Validator object for use with Annotated
28
"""
29
```
30
31
Usage examples:
32
33
```python
34
from beartype import beartype
35
from beartype.vale import Is
36
from typing import Annotated
37
38
# Positive integer validator
39
PositiveInt = Annotated[int, Is[lambda x: x > 0]]
40
41
@beartype
42
def deposit(amount: PositiveInt) -> str:
43
return f"Deposited ${amount}"
44
45
deposit(100) # ✓ Valid
46
# deposit(-50) # ✗ Raises validation error
47
48
# String length validator
49
NonEmptyStr = Annotated[str, Is[lambda s: len(s) > 0]]
50
51
@beartype
52
def greet(name: NonEmptyStr) -> str:
53
return f"Hello, {name}!"
54
55
greet("Alice") # ✓ Valid
56
# greet("") # ✗ Raises validation error
57
58
# Complex validation logic
59
def is_valid_email(email: str) -> bool:
60
return "@" in email and "." in email.split("@")[-1]
61
62
Email = Annotated[str, Is[is_valid_email]]
63
64
@beartype
65
def send_email(address: Email, message: str) -> bool:
66
print(f"Sending to {address}: {message}")
67
return True
68
```
69
70
### Attribute-Based Validator Factory
71
72
Create validators based on object attributes.
73
74
```python { .api }
75
class IsAttr:
76
"""
77
Attribute-based validator factory.
78
79
Creates validators that check object attributes against predicates.
80
"""
81
82
def __class_getitem__(cls, params):
83
"""
84
Create attribute validator.
85
86
Parameters:
87
- params: str or tuple - Attribute name or (attr_name, predicate)
88
89
Returns:
90
BeartypeValidator - Validator for object attributes
91
"""
92
```
93
94
Usage examples:
95
96
```python
97
from beartype.vale import IsAttr
98
from typing import Annotated
99
from dataclasses import dataclass
100
101
# Check for attribute existence
102
HasName = Annotated[object, IsAttr["name"]]
103
104
# Check attribute value
105
HasPositiveLength = Annotated[object, IsAttr[("__len__", lambda x: x > 0)]]
106
107
@dataclass
108
class Person:
109
name: str
110
age: int
111
112
@beartype
113
def process_person(person: HasName) -> str:
114
return f"Processing {person.name}"
115
116
person = Person("Alice", 30)
117
process_person(person) # ✓ Valid
118
119
# List length validation
120
@beartype
121
def process_items(items: HasPositiveLength) -> int:
122
return len(items)
123
124
process_items([1, 2, 3]) # ✓ Valid
125
# process_items([]) # ✗ Raises validation error
126
```
127
128
### Equality-Based Validator Factory
129
130
Create validators based on equality comparisons.
131
132
```python { .api }
133
class IsEqual:
134
"""
135
Equality-based validator factory.
136
137
Creates validators that check equality against specific values.
138
"""
139
140
def __class_getitem__(cls, value):
141
"""
142
Create equality validator.
143
144
Parameters:
145
- value: Any - Value to compare against
146
147
Returns:
148
BeartypeValidator - Validator for equality checking
149
"""
150
```
151
152
Usage examples:
153
154
```python
155
from beartype.vale import IsEqual
156
from typing import Annotated, Literal
157
158
# Exact value validation
159
StatusOK = Annotated[int, IsEqual[200]]
160
StatusError = Annotated[int, IsEqual[404]]
161
ConfigMode = Annotated[str, IsEqual["production"]]
162
163
@beartype
164
def handle_response(status: StatusOK) -> str:
165
return "Request successful"
166
167
@beartype
168
def set_config(mode: ConfigMode) -> None:
169
print(f"Setting config to {mode}")
170
171
handle_response(200) # ✓ Valid
172
# handle_response(404) # ✗ Raises validation error
173
174
set_config("production") # ✓ Valid
175
# set_config("development") # ✗ Raises validation error
176
```
177
178
### Instance Type Validator Factory
179
180
Create validators based on isinstance() checks.
181
182
```python { .api }
183
class IsInstance:
184
"""
185
Instance type validator factory.
186
187
Creates validators using isinstance() checks against type or tuple of types.
188
"""
189
190
def __class_getitem__(cls, types):
191
"""
192
Create isinstance validator.
193
194
Parameters:
195
- types: type or tuple - Type(s) for isinstance check
196
197
Returns:
198
BeartypeValidator - Validator using isinstance()
199
"""
200
```
201
202
Usage examples:
203
204
```python
205
from beartype.vale import IsInstance
206
from typing import Annotated, Union
207
import datetime
208
209
# Single type validation
210
NumericValue = Annotated[Union[int, float], IsInstance[int, float]]
211
212
# Multiple type validation
213
DateTimeValue = Annotated[object, IsInstance[(datetime.date, datetime.datetime)]]
214
215
@beartype
216
def calculate(value: NumericValue) -> float:
217
return float(value) * 2.0
218
219
@beartype
220
def format_date(dt: DateTimeValue) -> str:
221
return dt.strftime("%Y-%m-%d")
222
223
calculate(42) # ✓ Valid (int)
224
calculate(3.14) # ✓ Valid (float)
225
# calculate("42") # ✗ Raises validation error
226
227
format_date(datetime.date.today()) # ✓ Valid
228
format_date(datetime.datetime.now()) # ✓ Valid
229
# format_date("2023-01-01") # ✗ Raises validation error
230
```
231
232
### Subclass Validator Factory
233
234
Create validators based on issubclass() checks.
235
236
```python { .api }
237
class IsSubclass:
238
"""
239
Subclass validator factory.
240
241
Creates validators using issubclass() checks against type or tuple of types.
242
"""
243
244
def __class_getitem__(cls, types):
245
"""
246
Create issubclass validator.
247
248
Parameters:
249
- types: type or tuple - Type(s) for issubclass check
250
251
Returns:
252
BeartypeValidator - Validator using issubclass()
253
"""
254
```
255
256
Usage examples:
257
258
```python
259
from beartype.vale import IsSubclass
260
from typing import Annotated
261
from abc import ABC, abstractmethod
262
263
# Abstract base class
264
class Drawable(ABC):
265
@abstractmethod
266
def draw(self): pass
267
268
class Shape(Drawable):
269
def draw(self): return "Drawing shape"
270
271
class Circle(Shape):
272
def draw(self): return "Drawing circle"
273
274
# Validator for subclasses
275
DrawableClass = Annotated[type, IsSubclass[Drawable]]
276
ShapeClass = Annotated[type, IsSubclass[Shape]]
277
278
@beartype
279
def create_drawable(cls: DrawableClass) -> Drawable:
280
return cls()
281
282
@beartype
283
def create_shape(cls: ShapeClass) -> Shape:
284
return cls()
285
286
create_drawable(Circle) # ✓ Valid
287
create_shape(Circle) # ✓ Valid
288
# create_drawable(str) # ✗ Raises validation error
289
```
290
291
### Combining Validators
292
293
Multiple validators can be combined using multiple annotations:
294
295
```python
296
from beartype.vale import Is, IsAttr
297
from typing import Annotated
298
299
# Multiple validation constraints
300
ValidUser = Annotated[
301
object,
302
IsAttr["name"], # Must have name attribute
303
IsAttr[("age", lambda x: x >= 18)], # Age must be >= 18
304
Is[lambda obj: len(obj.name) > 0] # Name must be non-empty
305
]
306
307
@dataclass
308
class User:
309
name: str
310
age: int
311
312
@beartype
313
def register_user(user: ValidUser) -> str:
314
return f"Registered {user.name}, age {user.age}"
315
316
valid_user = User("Alice", 25)
317
register_user(valid_user) # ✓ Valid
318
319
invalid_user = User("", 16)
320
# register_user(invalid_user) # ✗ Raises validation error
321
```
322
323
### Advanced Patterns
324
325
#### Conditional Validation
326
327
```python
328
from beartype.vale import Is
329
from typing import Annotated, Union
330
331
def validate_id(obj) -> bool:
332
if isinstance(obj, str):
333
return obj.isalnum() and len(obj) >= 3
334
elif isinstance(obj, int):
335
return obj > 0
336
return False
337
338
FlexibleID = Annotated[Union[str, int], Is[validate_id]]
339
340
@beartype
341
def lookup_item(item_id: FlexibleID) -> str:
342
return f"Item {item_id}"
343
344
lookup_item("abc123") # ✓ Valid string ID
345
lookup_item(42) # ✓ Valid numeric ID
346
# lookup_item("ab") # ✗ Too short
347
# lookup_item(-1) # ✗ Negative number
348
```
349
350
#### Custom Exception Messages
351
352
```python
353
from beartype.vale import Is
354
from typing import Annotated
355
356
def positive_with_message(x: int) -> bool:
357
if x <= 0:
358
raise ValueError(f"Expected positive integer, got {x}")
359
return True
360
361
PositiveInt = Annotated[int, Is[positive_with_message]]
362
363
@beartype
364
def process_positive(value: PositiveInt) -> int:
365
return value * 2
366
367
try:
368
process_positive(-5)
369
except Exception as e:
370
print(f"Validation failed: {e}")
371
```