0
# Special Values and Utilities
1
2
Sentinel values, utility classes, and helper functions for advanced validation scenarios and edge case handling. These utilities provide fine-grained control over validation behavior and enable sophisticated data processing patterns.
3
4
## Capabilities
5
6
### Sentinel Values
7
8
Special values that represent different states during validation and serialization.
9
10
```python { .api }
11
PydanticUndefined: PydanticUndefinedType
12
"""
13
Sentinel value representing undefined/missing values in Pydantic Core.
14
15
This is the primary sentinel value used internally by pydantic-core
16
to indicate when a value has not been provided or is undefined.
17
"""
18
19
class PydanticUndefinedType:
20
"""
21
Type class for the PydanticUndefined sentinel.
22
23
This type ensures PydanticUndefined has a unique type that can
24
be used in type annotations and isinstance checks.
25
"""
26
27
def __repr__(self) -> str:
28
return 'PydanticUndefined'
29
30
def __bool__(self) -> bool:
31
return False
32
33
MISSING: Sentinel
34
"""
35
Sentinel indicating a field value was not provided during validation.
36
37
Can be used as a default value alternative to None when None has
38
explicit meaning. During serialization, fields with MISSING are excluded.
39
"""
40
41
UNSET: Sentinel # Alias for MISSING
42
"""
43
Alternative name for MISSING sentinel value.
44
45
Provides a more explicit name when indicating that a field
46
has not been set or provided.
47
"""
48
```
49
50
### Utility Classes
51
52
Helper classes for advanced validation and data handling scenarios.
53
54
```python { .api }
55
class Some:
56
"""
57
Wrapper for optional values that distinguishes between None and undefined.
58
59
Useful when you need to differentiate between an explicit None value
60
and a missing/undefined value in validation contexts.
61
"""
62
63
def __init__(self, value):
64
"""
65
Create a Some wrapper around a value.
66
67
Args:
68
value: The value to wrap (can be None)
69
"""
70
self.value = value
71
72
def __repr__(self) -> str:
73
return f'Some({self.value!r})'
74
75
class ArgsKwargs:
76
"""
77
Container for function arguments and keyword arguments.
78
79
Used in advanced validation scenarios where you need to capture
80
and validate both positional and keyword arguments together.
81
"""
82
83
def __init__(self, args: tuple, kwargs: dict):
84
"""
85
Create an ArgsKwargs container.
86
87
Args:
88
args: Tuple of positional arguments
89
kwargs: Dictionary of keyword arguments
90
"""
91
self.args = args
92
self.kwargs = kwargs
93
94
def __repr__(self) -> str:
95
return f'ArgsKwargs({self.args!r}, {self.kwargs!r})'
96
```
97
98
### Marker Classes
99
100
Special marker classes used to control validation and serialization behavior.
101
102
```python { .api }
103
class PydanticOmit:
104
"""
105
Marker to omit fields during serialization.
106
107
When a field value is set to PydanticOmit (or a validator returns it),
108
the field will be excluded from serialization output.
109
"""
110
111
def __repr__(self) -> str:
112
return 'PydanticOmit'
113
114
class PydanticUseDefault:
115
"""
116
Marker to use default value during validation.
117
118
When a validator returns PydanticUseDefault, the validation process
119
will use the field's default value instead of the returned marker.
120
"""
121
122
def __repr__(self) -> str:
123
return 'PydanticUseDefault'
124
```
125
126
### Time and Timezone Utilities
127
128
```python { .api }
129
class TzInfo:
130
"""
131
Timezone information handling for datetime validation.
132
133
Provides timezone-aware datetime processing and validation
134
with support for various timezone representations.
135
"""
136
137
def __repr__(self) -> str:
138
return 'TzInfo(...)'
139
```
140
141
## Usage Examples
142
143
### Working with Sentinel Values
144
145
```python
146
from pydantic_core import SchemaValidator, MISSING, UNSET, PydanticUndefined
147
from pydantic_core.core_schema import dict_schema, str_schema, default_schema
148
149
# Schema with optional fields using sentinels
150
schema = dict_schema({
151
'name': str_schema(),
152
'nickname': default_schema(str_schema(), default=MISSING),
153
'email': default_schema(str_schema(), default=UNSET),
154
'phone': default_schema(str_schema(), default=PydanticUndefined)
155
})
156
157
validator = SchemaValidator(schema)
158
159
# Test data without optional fields
160
data = {'name': 'Alice'}
161
result = validator.validate_python(data)
162
163
print(f"Result: {result}")
164
print(f"Nickname: {result.get('nickname', 'NOT_FOUND')}")
165
print(f"Email: {result.get('email', 'NOT_FOUND')}")
166
print(f"Phone: {result.get('phone', 'NOT_FOUND')}")
167
168
# Check sentinel values
169
print(f"MISSING is UNSET: {MISSING is UNSET}") # True - they're aliases
170
print(f"MISSING == PydanticUndefined: {MISSING == PydanticUndefined}") # False
171
172
# Type checking
173
print(f"Type of PydanticUndefined: {type(PydanticUndefined)}")
174
print(f"isinstance(PydanticUndefined, PydanticUndefinedType): {isinstance(PydanticUndefined, type(PydanticUndefined))}")
175
```
176
177
### Using Some for Optional Values
178
179
```python
180
from pydantic_core import SchemaValidator, Some
181
from pydantic_core.core_schema import (
182
with_info_plain_validator_function,
183
union_schema,
184
none_schema,
185
str_schema
186
)
187
188
def validate_optional_with_some(value):
189
"""Validator that distinguishes None from missing."""
190
if isinstance(value, Some):
191
# Extract the wrapped value, even if it's None
192
return value.value
193
elif value is None:
194
# Explicit None
195
return None
196
else:
197
# Regular value
198
if not isinstance(value, str):
199
raise ValueError("Expected string or Some(value)")
200
return value
201
202
# Schema using Some
203
optional_schema = with_info_plain_validator_function(validate_optional_with_some)
204
validator = SchemaValidator(optional_schema)
205
206
# Test different values
207
test_values = [
208
"regular_string",
209
None,
210
Some("wrapped_string"),
211
Some(None), # Explicit None wrapped in Some
212
]
213
214
for value in test_values:
215
try:
216
result = validator.validate_python(value)
217
print(f"Input: {value} -> Output: {result}")
218
except Exception as e:
219
print(f"Input: {value} -> Error: {e}")
220
```
221
222
### Using ArgsKwargs for Function Validation
223
224
```python
225
from pydantic_core import SchemaValidator, ArgsKwargs
226
from pydantic_core.core_schema import (
227
with_info_plain_validator_function,
228
arguments_schema,
229
str_schema,
230
int_schema
231
)
232
233
def validate_function_call(value):
234
"""Validate function arguments structure."""
235
if isinstance(value, ArgsKwargs):
236
args, kwargs = value.args, value.kwargs
237
elif isinstance(value, dict) and 'args' in value and 'kwargs' in value:
238
args, kwargs = value['args'], value['kwargs']
239
else:
240
raise ValueError("Expected ArgsKwargs or dict with 'args' and 'kwargs'")
241
242
# Validate arguments
243
if len(args) < 1:
244
raise ValueError("At least one positional argument required")
245
246
if not isinstance(args[0], str):
247
raise ValueError("First argument must be a string")
248
249
# Return validated ArgsKwargs
250
return ArgsKwargs(args, kwargs)
251
252
# Create validator
253
func_validator = SchemaValidator(
254
with_info_plain_validator_function(validate_function_call)
255
)
256
257
# Test function call validation
258
test_calls = [
259
ArgsKwargs(("hello",), {"count": 3}),
260
{"args": ("world",), "kwargs": {"repeat": True}},
261
ArgsKwargs((), {"name": "test"}), # Invalid: no args
262
]
263
264
for call in test_calls:
265
try:
266
result = func_validator.validate_python(call)
267
print(f"Valid call: {result}")
268
print(f" Args: {result.args}")
269
print(f" Kwargs: {result.kwargs}")
270
except Exception as e:
271
print(f"Invalid call: {e}")
272
```
273
274
### Marker Classes for Serialization Control
275
276
```python
277
from pydantic_core import (
278
SchemaValidator,
279
SchemaSerializer,
280
PydanticOmit,
281
PydanticUseDefault
282
)
283
from pydantic_core.core_schema import (
284
dict_schema,
285
str_schema,
286
int_schema,
287
with_info_plain_validator_function,
288
default_schema
289
)
290
291
def process_field_value(value):
292
"""Validator that may omit fields or use defaults."""
293
if isinstance(value, str):
294
if value == "OMIT":
295
return PydanticOmit # This field will be excluded
296
elif value == "DEFAULT":
297
return PydanticUseDefault # Use the field's default value
298
elif value.startswith("PROCESS:"):
299
return value[8:] # Remove "PROCESS:" prefix
300
return value
301
302
# Schema with conditional processing
303
schema = dict_schema({
304
'id': int_schema(),
305
'name': with_info_plain_validator_function(process_field_value),
306
'description': default_schema(
307
with_info_plain_validator_function(process_field_value),
308
default="Default description"
309
),
310
'status': with_info_plain_validator_function(process_field_value)
311
})
312
313
validator = SchemaValidator(schema)
314
serializer = SchemaSerializer(schema)
315
316
# Test data with markers
317
test_data = {
318
'id': 123,
319
'name': 'PROCESS:Alice', # Will become 'Alice'
320
'description': 'DEFAULT', # Will use default value
321
'status': 'OMIT' # Will be omitted from output
322
}
323
324
# Validate
325
validated = validator.validate_python(test_data)
326
print(f"Validated: {validated}")
327
328
# Serialize - status should be omitted
329
serialized = serializer.to_python(validated)
330
print(f"Serialized: {serialized}")
331
332
# Check what fields are present
333
print(f"Fields in result: {list(serialized.keys())}")
334
print(f"Status omitted: {'status' not in serialized}")
335
```
336
337
### Advanced Sentinel Usage in Models
338
339
```python
340
from pydantic_core import SchemaValidator, SchemaSerializer, MISSING
341
from pydantic_core.core_schema import (
342
model_schema,
343
str_schema,
344
int_schema,
345
default_schema,
346
nullable_schema
347
)
348
349
# User model with various optional field types
350
user_schema = model_schema(
351
'User',
352
{
353
'id': int_schema(),
354
'name': str_schema(),
355
'email': nullable_schema(str_schema()), # Can be None
356
'phone': default_schema(str_schema(), default=MISSING), # Missing by default
357
'address': default_schema(nullable_schema(str_schema()), default=None), # None by default
358
}
359
)
360
361
validator = SchemaValidator(user_schema)
362
serializer = SchemaSerializer(user_schema)
363
364
# Test different scenarios
365
test_users = [
366
# Minimal user
367
{'id': 1, 'name': 'Alice', 'email': None},
368
369
# User with phone
370
{'id': 2, 'name': 'Bob', 'email': 'bob@example.com', 'phone': '123-456-7890'},
371
372
# User with all fields
373
{'id': 3, 'name': 'Charlie', 'email': 'charlie@example.com', 'phone': '098-765-4321', 'address': '123 Main St'},
374
]
375
376
for user_data in test_users:
377
print(f"\nInput: {user_data}")
378
379
# Validate
380
validated = validator.validate_python(user_data)
381
print(f"Validated: {validated}")
382
383
# Serialize excluding unset fields
384
serialized_exclude_unset = serializer.to_python(validated, exclude_unset=True)
385
print(f"Exclude unset: {serialized_exclude_unset}")
386
387
# Serialize excluding None values
388
serialized_exclude_none = serializer.to_python(validated, exclude_none=True)
389
print(f"Exclude None: {serialized_exclude_none}")
390
```
391
392
### Creating Custom Sentinel Values
393
394
```python
395
from typing_extensions import Sentinel
396
397
# Create custom sentinel values for specific use cases
398
REDACTED = Sentinel('REDACTED')
399
COMPUTED = Sentinel('COMPUTED')
400
INHERITED = Sentinel('INHERITED')
401
402
def process_config_value(value):
403
"""Process configuration values with custom sentinels."""
404
if value is REDACTED:
405
return "[REDACTED]"
406
elif value is COMPUTED:
407
return f"computed_value_{hash('config')}"
408
elif value is INHERITED:
409
return "inherited_from_parent"
410
else:
411
return value
412
413
# Example usage in configuration processing
414
config_data = {
415
'api_key': REDACTED,
416
'cache_size': COMPUTED,
417
'log_level': INHERITED,
418
'service_name': 'my-service'
419
}
420
421
processed_config = {
422
key: process_config_value(value)
423
for key, value in config_data.items()
424
}
425
426
print("Original config:")
427
for key, value in config_data.items():
428
print(f" {key}: {value}")
429
430
print("\nProcessed config:")
431
for key, value in processed_config.items():
432
print(f" {key}: {value}")
433
434
# Sentinel comparison
435
print(f"\nSentinel comparisons:")
436
print(f"REDACTED is REDACTED: {REDACTED is REDACTED}")
437
print(f"REDACTED == REDACTED: {REDACTED == REDACTED}")
438
print(f"REDACTED is COMPUTED: {REDACTED is COMPUTED}")
439
```