Use this guide when a stored .fmu resource needs a new schema version.
The supported resources and their migration function registries are:
ProjectConfig:project_config/UserConfig:user_config/InternalMappings:mappings/
Change the schema version when existing stored data needs a migration before it can be used by the current model.
Renaming a stored field needs a migration. For example, changing
cache_max_revisions to max_cache_revisions changes the JSON object from:
{
"schema_version": 1,
"cache_max_revisions": 5
}to:
{
"schema_version": 2,
"max_cache_revisions": 5
}Without a migration, the current model cannot recover the value stored under the old field name.
Other changes that need a migration include:
- Removing a stored field when its value must be moved or preserved elsewhere.
- Making an optional field required and calculating its value from stored data from an older schema.
- Changing a field from one type to another, such as a string to a list of strings.
- Moving flat fields into a nested model.
- Changing the meaning of a value when stored data from an older schema must be converted to keep its original meaning.
Adding an optional field with a safe default does not normally need a migration. For example:
class ProjectConfig(ResettableBaseModel):
schema_version: Literal[1] = 1
description: str | None = NoneAn existing version 1 file without description still validates. Pydantic supplies
None, so the schema version can remain 1.
Other changes that do not normally need a migration include:
- Making validation more permissive.
- Changing a model method that does not change stored data.
- Changing documentation or field descriptions.
- Adding a computed property that is not stored.
Each migratable model declares one positive integer schema version. The literal and default values must match:
schema_version: Literal[2] = 2Migration functions are forward-only. Each function increments schema_version by
exactly one:
1 -> 2 -> 3
Do not skip a version. Data without a schema_version field is treated as schema
version 1.
The following example changes the current ProjectConfig from schema version 1 to
2 and renames cache_max_revisions to max_cache_revisions.
Edit the existing model in models/project_config.py. Do not create a second
ProjectConfig class. Some existing fields and default values are not shown in the
shortened example. The comments show where they belong. These fields include version,
created_at, created_by, masterdata, and rms. Only the schema version and the
renamed field change in this example:
class ProjectConfig(ResettableBaseModel):
"""The configuration file in a .fmu directory."""
schema_version: Literal[2] = 2
# Existing fields before this field are not shown in this example.
max_cache_revisions: int = Field(default=10, ge=5)
# Existing fields after this field are not shown in this example.
@classmethod
def reset(cls: type[Self]) -> Self:
"""Reset the configuration to its defaults."""
return cls(
# Existing default values before this one are not shown.
max_cache_revisions=10,
# Existing default values after this one are not shown.
)Search the source code and tests for the old field name. Update attribute access
such as config.cache_max_revisions, string keys such as
set_config_value("cache_max_revisions", ...), API models, and test input
dictionaries. Any code that reads or writes this field must use the new name.
Create project_config/v1_to_v2.py:
from typing import Any
def migrate_v1_to_v2(data: dict[str, Any]) -> dict[str, Any]:
"""Migrate project config data from schema version 1 to 2."""
data["max_cache_revisions"] = data.pop("cache_max_revisions", 5)
data["schema_version"] = 2
return dataThe migration manager gives each migration function a deep copy of the loaded data. A migration function can therefore modify its input without changing the original data.
The returned data must:
- Preserve all relevant stored values.
- Set
schema_versionto the next version. - Be valid input for the next migration function or the current model.
Update project_config/__init__.py:
from fmu.settings._migrations.manager import MigrationFunction
from .v1_to_v2 import migrate_v1_to_v2
PROJECT_CONFIG_MIGRATIONS: dict[int, MigrationFunction] = {
1: migrate_v1_to_v2,
}The registry key is the source schema version. Key 1 registers the migration
function from version 1 to version 2.
Keep every migration function when later versions are added:
PROJECT_CONFIG_MIGRATIONS: dict[int, MigrationFunction] = {
1: migrate_v1_to_v2,
2: migrate_v2_to_v3,
}When you add a migration:
- Keep
test_migration_manager.pyand the generic resource tests unchanged unless the framework behavior changes. - In
test_resource_migration.py, update the affected resource tests that assert its version, migration function registry, or previous version data. - Add resource-specific tests under
tests/test_migrations/. For example, aProjectConfigmigration can usetest_project_config_migration.py.
The resource-specific tests must cover migration, load, save, cache restore, and
invalid data. Update the complete current version fixture used by
tests/test_resources/test_migratable_models_up_to_date.py. Keep the previous
version input with the resource-specific migration tests.
Run the relevant checks:
uv run pytest tests/test_migrations
uv run pytest tests/test_resources/test_migratable_models_up_to_date.py
uv run ruff check
uv run ruff format --check
uv run mypy src tests
Migration is automatic during normal use:
- The resource manager reads stored data from an older schema.
- The migration manager migrates it in memory.
- The resource manager returns the current validated model.
- The stored file remains unchanged until a save occurs.
On the first save:
- The write lock is checked.
- If the stored data needs migration, the resource manager tries to save a copy of
the original JSON under
.fmu/migration-backups/. This backup is separate from the cache, is not removed automatically, and a failure to write it does not stop the save. The original JSON is also added to a cache revision before the migrated data is written. - The current model is written with the new schema version.
- The newly written data is also added to a cache revision.
Loading a resource does not create a changelog entry. A later user update or restore uses the existing changelog behavior.
When an old cache revision is restored by the current release, it is migrated before it is written. The resource file therefore uses the current schema after the restore.
Migrations are forward-only. After current-schema data is saved, an older
fmu-settings release can reject it as newer than its supported schema. If the
pre-migration revision is retained, restore that revision with the older release to
return the resource file to the older schema. Migration backups are not read or
restored by the library. If the cache revision is no longer available, we should help
users copy the appropriate migration backup back to the resource file before running
the older release.
Use this checklist when releasing an fmu-settings package with an updated
schema version in one of the migratable models. First prepare and publish
fmu-settings. Then update the downstream applications so that users receive
the new package.
- Confirm that the schema change needs a migration.
- Increase the model schema version by one.
- Add and register the migration from the previous version.
- Update
reset()if it constructs or supplies a default for the changed field. - Update test fixture dictionaries so that current-model fixtures use the new field and current schema version.
- Update Python attribute access, dot-notation string keys, API code, and other callers that use the changed field.
- Test loading, saving, caching, and restoring previous-version data.
- Run the full
fmu-settingschecks.
Publish a new GitHub release for fmu-settings. The publish workflow builds the
package and uploads it to PyPI.
- Set the minimum
fmu-settingsdependency infmu-settings-apito the newly released version. - Update the API lockfile and run the API tests.
- Publish a new
fmu-settings-apirelease.
If the schema change affects fields exposed by the API, update and verify the OpenAPI schema. Then regenerate and release the GUI client.
- Set the minimum
fmu-settings-apidependency infmu-settings-clito the newly released API version. - Also set the CLI's direct
fmu-settingsdependency to the new version. - Update the CLI lockfile and run the CLI tests.
- Publish a new
fmu-settings-clirelease.
This release order keeps the schema implementation, API runtime, and CLI distribution on compatible versions.