Skip to content
This repository was archived by the owner on Sep 13, 2023. It is now read-only.

Commit 1a1d203

Browse files
authored
Add flag to migrate metadata on load (#605)
1 parent 362bf2f commit 1a1d203

File tree

4 files changed

+41
-6
lines changed

4 files changed

+41
-6
lines changed

mlem/api/migrations.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,21 @@ def migrate(path: str, project: Optional[str] = None, recursive: bool = False):
3030
_migrate_one(loc)
3131

3232

33-
def _migrate_one(location: Location):
34-
with location.open("r") as f:
35-
payload = safe_load(f)
36-
33+
def apply_migrations(payload: dict):
3734
changed = False
3835
for migration in _migrations:
3936
migrated = migration(payload)
4037
if migrated is not None:
4138
payload = migrated
4239
changed = True
40+
return payload, changed
41+
42+
43+
def _migrate_one(location: Location):
44+
with location.open("r") as f:
45+
payload = safe_load(f)
46+
47+
payload, changed = apply_migrations(payload)
4348

4449
if changed:
4550
echo(f"Migrated MLEM Object at {location}")

mlem/core/metadata.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ def load(
138138
rev: Optional[str] = None,
139139
batch_size: Optional[int] = None,
140140
follow_links: bool = True,
141+
try_migrations: bool = False,
141142
) -> Any:
142143
"""Load python object saved by MLEM
143144
@@ -158,6 +159,7 @@ def load(
158159
rev=rev,
159160
follow_links=follow_links,
160161
load_value=batch_size is None,
162+
try_migrations=try_migrations,
161163
)
162164
if isinstance(meta, MlemData) and batch_size:
163165
return meta.read_batch(batch_size)
@@ -175,6 +177,7 @@ def load_meta(
175177
follow_links: bool = True,
176178
load_value: bool = False,
177179
fs: Optional[AbstractFileSystem] = None,
180+
try_migrations: bool = False,
178181
*,
179182
force_type: Literal[None] = None,
180183
) -> MlemObject:
@@ -189,6 +192,7 @@ def load_meta(
189192
follow_links: bool = True,
190193
load_value: bool = False,
191194
fs: Optional[AbstractFileSystem] = None,
195+
try_migrations: bool = False,
192196
*,
193197
force_type: Optional[Type[T]] = None,
194198
) -> T:
@@ -203,6 +207,7 @@ def load_meta(
203207
follow_links: bool = True,
204208
load_value: bool = False,
205209
fs: Optional[AbstractFileSystem] = None,
210+
try_migrations: bool = False,
206211
*,
207212
force_type: Optional[Type[T]] = None,
208213
) -> T:
@@ -216,6 +221,7 @@ def load_meta(
216221
actual object link points to. Defaults to True.
217222
load_value: Load actual python object incorporated in MlemObject. Defaults to False.
218223
fs: filesystem to load from. If not provided, will be inferred from path
224+
try_migrations: If loading older versions of metadata, try to apply migrations
219225
force_type: type of meta to be loaded. Defaults to MlemObject (any mlem meta)
220226
Returns:
221227
MlemObject: Saved MlemObject
@@ -232,6 +238,7 @@ def load_meta(
232238
meta = cls.read(
233239
location=find_meta_location(location),
234240
follow_links=follow_links,
241+
try_migrations=try_migrations,
235242
)
236243
log_meta_params(meta, add_object_type=True)
237244
if load_value:
@@ -274,14 +281,21 @@ def find_meta_location(location: Location) -> Location:
274281

275282

276283
def list_objects(
277-
path: str = ".", fs: Optional[AbstractFileSystem] = None, recursive=True
284+
path: str = ".",
285+
fs: Optional[AbstractFileSystem] = None,
286+
recursive=True,
287+
try_migrations=False,
278288
) -> Dict[Type[MlemObject], List[MlemObject]]:
279289
loc = Location.resolve(path, fs=fs)
280290
result = defaultdict(list)
281291
postfix = f"/**{MLEM_EXT}" if recursive else f"/*{MLEM_EXT}"
282292
for filepath in loc.fs.glob(loc.fullpath + postfix, detail=False):
283293
meta = load_meta(
284-
filepath, fs=loc.fs, load_value=False, follow_links=False
294+
filepath,
295+
fs=loc.fs,
296+
load_value=False,
297+
follow_links=False,
298+
try_migrations=try_migrations,
285299
)
286300
type_ = meta.__class__
287301
if isinstance(meta, MlemLink):

mlem/core/objects.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ def read(
150150
cls: Type[T],
151151
location: Location,
152152
follow_links: bool = True,
153+
try_migrations: bool = False,
153154
) -> T:
154155
"""
155156
Read object in (path, fs)
@@ -170,6 +171,10 @@ def read(
170171
)
171172
with location.open() as f:
172173
payload = safe_load(f)
174+
if try_migrations:
175+
from mlem.api.migrations import apply_migrations
176+
177+
payload, _ = apply_migrations(payload)
173178
res = parse_obj_as(MlemObject, payload).bind(location)
174179
if follow_links and isinstance(res, MlemLink):
175180
link = res.load_link()

tests/api/test_migrations.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,14 @@ def test_directory(tmpdir, old_data, new_data, recursive):
6161
assert load_meta(subdir_path) != new_data
6262
except ValidationError:
6363
pass
64+
65+
66+
@pytest.mark.parametrize("old_data,new_data", [model_03])
67+
def test_load_with_migration(tmpdir, old_data, new_data):
68+
path = tmpdir / "model.mlem"
69+
path.write_text(safe_dump(old_data), encoding="utf8")
70+
71+
meta = load_meta(path, try_migrations=True)
72+
73+
assert isinstance(meta, MlemObject)
74+
assert meta == new_data

0 commit comments

Comments
 (0)