Skip to main content

Handling Defaults and Custom Errors

Handling default values and customizing error responses are core tasks when defining schemas. This guide demonstrates how to use WithDefaultSchema to manage missing or invalid data and CustomErrorSchema to provide domain-specific error feedback.

Providing Default Values

Use WithDefaultSchema to specify a value to use when a field is missing or when validation fails.

Static Default Values

The most common use case is providing a static value for a missing field.

from pydantic_core import SchemaValidator, core_schema

v = SchemaValidator(
core_schema.typed_dict_schema(
fields={
'x': core_schema.typed_dict_field(schema=core_schema.str_schema()),
'y': core_schema.typed_dict_field(
schema=core_schema.with_default_schema(
schema=core_schema.str_schema(),
default='[default]'
)
),
}
)
)

# 'y' is missing, so the default is used
assert v.validate_python({'x': 'x'}) == {'x': 'x', 'y': '[default]'}

Dynamic Defaults with Factories

For values that should be generated at runtime (like timestamps or new list instances), use default_factory.

v = SchemaValidator(
core_schema.with_default_schema(
schema=core_schema.int_schema(),
default_factory=lambda: 17
)
)

assert v.validate_python(None) == 17

If your factory needs access to the rest of the data being validated, set default_factory_takes_data=True.

Validating Default Values

By default, the default or the result of default_factory is not validated. Set validate_default=True to ensure the default value also conforms to the inner schema.

from pydantic_core import ValidationError
import pytest

v = SchemaValidator(
core_schema.with_default_schema(
core_schema.int_schema(),
default='not-an-int',
validate_default=True
)
)

# Validation fails because the default value 'not-an-int' is not a valid integer
with pytest.raises(ValidationError):
v.validate_python(None)

Handling Validation Errors

The on_error parameter in WithDefaultSchema determines how the validator behaves when the input data is present but invalid.

Using Defaults on Failure

Set on_error='default' to gracefully fall back to the default value if validation fails.

v = SchemaValidator(
core_schema.with_default_schema(
schema=core_schema.int_schema(),
default=2,
on_error='default'
)
)

# 'wrong' is not an int, so it returns the default value 2
assert v.validate_python('wrong') == 2

Omitting Invalid Items

Set on_error='omit' to remove the item entirely if it fails validation. This is particularly useful for filtering collections.

v = SchemaValidator(
core_schema.list_schema(
items_schema=core_schema.with_default_schema(
schema=core_schema.int_schema(),
on_error='omit'
)
)
)

# 'wrong' is omitted from the resulting list
assert v.validate_python([1, 'wrong', 3]) == [1, 3]

Customizing Error Messages

Use CustomErrorSchema to override the standard error type and message generated by an inner schema.

v = SchemaValidator(
core_schema.custom_error_schema(
core_schema.int_schema(),
custom_error_type='my_custom_error',
custom_error_message='Please provide a valid number'
)
)

with pytest.raises(ValidationError) as exc_info:
v.validate_python('not-a-number')

assert exc_info.value.errors(include_url=False) == [
{
'type': 'my_custom_error',
'loc': (),
'msg': 'Please provide a valid number',
'input': 'not-a-number'
}
]

Using Known Error Types

If you use a custom_error_type that matches one of Pydantic's built-in error types, you should not provide a custom_error_message. The validator will automatically use the standard message for that type.

# This uses the standard 'recursion_loop' message
v = SchemaValidator(
core_schema.custom_error_schema(
core_schema.int_schema(),
'recursion_loop'
)
)

Troubleshooting

Mutable Defaults

Pydantic-core automatically deep-copies mutable default values (like lists or dictionaries) to prevent shared state between different validation results.

stored_empty_list = []
v = SchemaValidator(
core_schema.with_default_schema(
schema=core_schema.list_schema(items_schema=core_schema.int_schema()),
default=stored_empty_list,
)
)

m1 = v.validate_python(None)
assert m1 is not stored_empty_list # The default was copied

InvalidSchema Errors

The InvalidSchema class is a placeholder used for internal signaling. If you attempt to build a SchemaValidator using core_schema.invalid_schema(), it will raise a SchemaError.

from pydantic_core import SchemaError

# This will raise: SchemaError: Cannot construct schema with `InvalidSchema` member.
with pytest.raises(SchemaError):
SchemaValidator(core_schema.invalid_schema())

Configuration Conflicts

  • You cannot provide both default and default_factory in the same WithDefaultSchema.
  • on_error='default' requires either a default or default_factory to be present.
  • on_error='omit' cannot be used within an arguments_schema.