Skip to main content

Handling Nullability and Defaults

Handling nullability and default values in this project is managed through schema wrappers that modify how validation and serialization behave when data is missing or invalid.

Allowing Null Values

To allow a field to accept None in addition to its primary type, wrap the inner schema with nullable_schema.

from pydantic_core import SchemaValidator, core_schema

# Wrap an integer schema to allow None
schema = core_schema.nullable_schema(core_schema.int_schema())
v = SchemaValidator(schema)

assert v.validate_python(None) is None
assert v.validate_python(123) == 123
assert v.validate_python('456') == 456

The nullable_schema effectively creates a union between the provided schema and a None schema. If the input is None, validation succeeds immediately.

Providing Default Values

Use with_default_schema to provide a fallback value when a field is missing from the input. This is typically used within typed_dict_field or model_field.

from pydantic_core import SchemaValidator, core_schema

# Define a field with a static default value
schema = core_schema.typed_dict_schema(
fields={
'name': core_schema.typed_dict_field(
schema=core_schema.with_default_schema(
core_schema.str_schema(),
default='Anonymous'
)
)
}
)

v = SchemaValidator(schema)
assert v.validate_python({}) == {'name': 'Anonymous'}
assert v.validate_python({'name': 'John'}) == {'name': 'John'}

Dynamic Defaults with Factories

For mutable types (like lists) or dynamic values (like timestamps), use default_factory instead of default.

from pydantic_core import SchemaValidator, core_schema

# Use a factory to provide a fresh list for every missing value
schema = core_schema.typed_dict_schema(
fields={
'tags': core_schema.typed_dict_field(
schema=core_schema.with_default_schema(
core_schema.list_schema(core_schema.str_schema()),
default_factory=list
)
)
}
)

v = SchemaValidator(schema)
res1 = v.validate_python({})
res2 = v.validate_python({})
assert res1['tags'] is not res2['tags'] # Different list instances

Validating Default Values

By default, the value provided in default or returned by default_factory is not validated against the inner schema. To force validation of the default value, set validate_default=True.

from pydantic_core import SchemaValidator, core_schema, ValidationError

# This will fail because the default 'wrong' is not an integer
schema = core_schema.with_default_schema(
core_schema.int_schema(),
default='wrong',
validate_default=True
)

v = SchemaValidator(schema)
try:
v.validate_python(None) # Triggers default if input is missing/None in some contexts
except ValidationError as e:
print(e.errors())

Handling Validation Errors with Defaults

The on_error parameter in with_default_schema controls what happens when the inner schema fails to validate the input.

Using a Default on Error

Set on_error='default' to return the default value when validation fails, rather than raising a ValidationError.

from pydantic_core import SchemaValidator, core_schema

schema = core_schema.with_default_schema(
core_schema.int_schema(),
default=0,
on_error='default'
)

v = SchemaValidator(schema)
assert v.validate_python('not-an-int') == 0

Omitting Invalid Items

Set on_error='omit' to remove the field or item entirely when validation fails. This is particularly useful for filtering collections or optional fields in a TypedDict.

from pydantic_core import SchemaValidator, core_schema

# Filter out non-integer items from a list
schema = core_schema.list_schema(
items_schema=core_schema.with_default_schema(
core_schema.int_schema(),
on_error='omit'
)
)

v = SchemaValidator(schema)
assert v.validate_python([1, 'invalid', 3]) == [1, 3]

Triggering Defaults in Validators

You can manually trigger the default value of a field from within a validator function by raising the PydanticUseDefault exception.

from pydantic_core import SchemaValidator, core_schema, PydanticUseDefault

def validate_empty_to_default(v, info):
if v == "":
raise PydanticUseDefault()
return v

schema = core_schema.with_default_schema(
core_schema.no_info_after_validator_function(
validate_empty_to_default,
core_schema.str_schema()
),
default='standard-value'
)

v = SchemaValidator(schema)
assert v.validate_python("") == 'standard-value'

Excluding Fields with the MISSING Sentinel

The MISSING sentinel is used to represent a value that is truly absent. Fields assigned MISSING are automatically excluded from serialization.

from pydantic import BaseModel
from pydantic_core import MISSING

class Model(BaseModel):
optional_field: int | MISSING = MISSING
required_field: int

m = Model(required_field=1)
# optional_field is excluded because its value is MISSING
assert m.model_dump() == {'required_field': 1}

m2 = Model(required_field=1, optional_field=10)
assert m2.model_dump() == {'required_field': 1, 'optional_field': 10}

Troubleshooting

Uncaught Omit Error

If you use on_error='omit' outside of a container that supports omission (like list_schema, dict_schema, or typed_dict_schema), you will encounter a SchemaError.

# This will raise SchemaError: Uncaught Omit error
schema = core_schema.with_default_schema(core_schema.int_schema(), on_error='omit')
v = SchemaValidator(schema)
v.validate_python('invalid')

Solution: Ensure on_error='omit' is only used for items within a collection or fields within a TypedDict/Model.

Mutually Exclusive Parameters

In with_default_schema, you cannot provide both default and default_factory. Attempting to do so will result in a SchemaError during validator initialization.

Uncaught PydanticUseDefault

If PydanticUseDefault is raised but the field does not have a with_default_schema wrapper, the validation will fail with a SchemaError indicating that no default value is available.