Migrating from Upref v1

Upref v2 stores only raw configuration values. Upref v1 could instead store field descriptors containing keys such as label, description, type, and value. Explicit migration extracts the values, writes a v2 file, and leaves the legacy source untouched.

Importing one file

Create the destination store and pass the historical configuration name without the .conf extension:

from upref import ConfigStore, UprefError

store = ConfigStore("my-application")

try:
    config = store.import_legacy("my_personnal_data")
except UprefError as error:
    print(f"Migration not performed: {error}")
else:
    print(f"Migrated to {store.path}")

By default, Upref looks in the platform-specific v1 user-data directory used for the .upref application. The legacy name is validated as a simple component and .conf is appended.

For tests, portable installations, or a manually located source, pass an absolute legacy_directory:

from pathlib import Path
from upref import ConfigStore

store = ConfigStore("my-application")
legacy_directory = Path.home() / "legacy-upref"
config = store.import_legacy(
    "my_personnal_data",
    legacy_directory=legacy_directory,
)

Migration behavior

import_legacy has these guarantees:

  • a missing source raises MigrationError;

  • a target found during the preflight check is not replaced unless overwrite=True;

  • using the legacy source itself as the v2 target is always rejected;

  • the source file is never modified or deleted;

  • mappings not recognized as descriptors retain their shape;

  • recognized descriptor mappings are converted to raw values and metadata entries and descriptors without a value member are omitted;

  • the destination uses the same validation and atomic save as every v2 store;

  • the returned mapping is the mapping written to the destination.

Descriptor detection is necessarily structural because v1 files carry no format version. Upref treats a document as descriptors when every non-metadata top-level value is a mapping containing at least one of label, description, type, or value, and at least one field has a value member. A raw configuration with that same shape is ambiguous and may be converted. For such data, load and transform it explicitly before calling save().

Use overwrite=True only after deciding that the legacy file is the source of truth:

config = store.import_legacy("my_personnal_data", overwrite=True)

The existence check and save are separate operations and Upref does not lock them. Another writer can create or replace the target between those steps. Applications that can migrate concurrently must hold an external lock around the complete operation.

Migration is intentionally not automatic during load(). An application can therefore control when old data is discovered, how the user is informed, and whether an existing v2 file takes precedence.

Deprecated compatibility API

The v1 functions remain available during the v2 series but emit DeprecationWarning. New application code should use these replacements:

v1 function

v2 replacement

load_data / current_upref

ConfigStore.load

save_data

ConfigStore.save

remove_pref

ConfigStore.delete

get_pref

upref.collect(), followed by an explicit save

set_pref

ConfigStore.update

upref_filename

ConfigStore.path

load_conf / save_conf

A ConfigStore with an explicit directory and filename

The compatibility layer preserves v1 locations and descriptor shape; it is not the recommended way to create a new v2 file. See Legacy v1 API reference for its complete reference.