Version Discovery¶
validate_versioned() chooses the source schema version in a fixed order:
- explicit
version=; - configured
version_fieldin mapping input; - configured
missing_version; - otherwise
MissingSchemaVersionError.
Explicit version¶
An explicit version wins over anything in the payload:
result = validate_versioned(
AppConfig,
{"schema_version": "2"},
version="1",
)
assert result.source_version == "1"
Use this when the version is known from a filename, database column, API route, or another source outside the config body.
Top-level version fields¶
By default, configs use a top-level schema_version field:
You can use a different field name:
@versioned_schema(
name="crd_config",
versions=["example.com/v1", "example.com/v2"],
current="example.com/v2",
version_field="apiVersion",
)
class ResourceConfig(BaseModel):
replicas: int = 1
This works for Kubernetes-style apiVersion values:
Nested version fields¶
Use a tuple path when version metadata lives inside a wrapper object:
@versioned_schema(
name="metadata_config",
versions=["1", "2"],
current="2",
version_field=("metadata", "schema_version"),
)
class MetadataConfig(BaseModel):
timeout: float = 10.0
This reads:
The metadata wrapper does not have to be part of the Pydantic model. The version field is removed before source-model validation, so strict models can still reject unrelated extra fields.
Legacy unversioned configs¶
missing_version is only for legacy config files that do not contain version
metadata. It means "when the version field is missing, assume this schema
version."
@versioned_schema(
name="app_config",
versions=["1", "2"],
current="2",
missing_version="1",
)
class AppConfig(BaseModel):
timeout: float = 10.0
Then this unversioned payload is treated as schema version 1:
If you do not set missing_version, unversioned input raises
MissingSchemaVersionError. That is the safer default for new config formats.