Skip to content

Schema Patches

Historical schemas are derived from the current model with declarative patches. Patches target declared schema versions, not software versions.

Defaults

Use field_default() when a field still exists but had a different default in a historical schema:

@schema_version("1", patches=[field_default("timeout", 5.0)])
class AppConfig(BaseModel):
    timeout: float = 10.0

Default factories are supported:

@schema_version("1", patches=[field_default("plugins", default_factory=list)])
class AppConfig(BaseModel):
    plugins: list[str]

Removed fields

Use field_removed() when a field did not exist in an older schema:

@schema_version("1", patches=[field_removed("new_feature")])
class AppConfig(BaseModel):
    new_feature: bool = False

When rendering version 1, new_feature is omitted. When validating version 1, the generated historical model does not accept or require that field.

Renamed fields

Use field_renamed() when an older schema used a different field name:

@schema_version("1", patches=[field_renamed("retries", "attempts")])
class AppConfig(BaseModel):
    retries: int = 3

Version 1 accepts and renders attempts. Upgrade validation maps it back to retries before current-model validation.

Applying patches to multiple versions

Use schema_versions() to avoid repeated decorators when several explicit versions share the same patches:

@versioned_schema(name="app_config", versions=["1.0", "1.1", "2.0"], current="2.0")
@schema_versions(["1.0", "1.1"], patches=[field_default("timeout", 5.0)])
class AppConfig(BaseModel):
    timeout: float = 10.0

Only explicitly listed versions are patched. Pattern or regex matching is not enabled in the first release because schema versions are arbitrary ordered strings, not necessarily semantic versions.