Coverage for python / lsst / scarlet / lite / io / migration.py: 50%
38 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:28 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:28 +0000
1from __future__ import annotations
3from typing import Callable, ClassVar
5__all__ = ["MigrationError", "MigrationRegistry", "migration"]
7Migrator = Callable[[dict], dict]
9PRE_SCHEMA = "0.0.0"
12class MigrationError(Exception):
13 """Custom error for migration issues."""
15 pass
18class MigrationRegistry:
19 """Manages migration of data between different schema versions."""
21 registry: ClassVar[dict[tuple[str, str], Migrator]] = {}
22 current: ClassVar[dict[str, str]] = {}
24 @staticmethod
25 def register(type_name: str, from_version: str, migrator: Migrator) -> None:
26 """Register a migration function from one version to another."""
27 MigrationRegistry.registry[(type_name, from_version)] = migrator
29 @staticmethod
30 def set_current(data_type: str, version: str) -> None:
31 """Set the current version for a given data type."""
32 MigrationRegistry.current[data_type] = version
34 @staticmethod
35 def migrate(data_type: str, data: dict) -> dict:
36 """Migrate data to the current schema version.
38 Parameters
39 ----------
40 data :
41 The data to migrate. Must contain 'type' and 'version' keys.
43 Returns
44 -------
45 result :
46 The migrated data.
47 """
48 if "version" not in data:
49 # Unversioned data is pre-schema and is considered to be
50 # version "0.0.0" for backwards compatibility.
51 data["version"] = PRE_SCHEMA
53 from_version = data["version"]
55 if data_type not in MigrationRegistry.current:
56 raise ValueError(f"No current version set for data type '{data_type}'.")
58 to_version = MigrationRegistry.current[data_type]
60 # Keep track of seen versions to avoid infinite loops
61 seen: set[tuple[str, str]] = set()
63 while from_version != to_version:
64 key = (data_type, from_version)
65 if key not in MigrationRegistry.registry or key in seen:
66 raise MigrationError(
67 f"No migration path from version '{from_version}' for type '{data_type}'."
68 )
70 migrator = MigrationRegistry.registry[key]
71 data = migrator(data)
72 from_version = data["version"]
74 return data
77def migration(type_name: str, from_version: str) -> Callable[[Migrator], Migrator]:
78 """Decorator to register a migration step.
80 Parameters
81 ----------
82 type_name :
83 The type of data being migrated.
84 from_version :
85 The version the migrator converts from.
87 Returns
88 -------
89 result :
90 The decorator that registers the migration function.
91 """
93 def decorator(func: Migrator) -> Migrator:
94 MigrationRegistry.register(type_name, from_version, func)
95 return func
97 return decorator