lamindb 0.75.1__py3-none-any.whl → 0.76.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lamindb/__init__.py +6 -8
- lamindb/_artifact.py +2 -1
- lamindb/_collection.py +1 -0
- lamindb/_curate.py +4 -4
- lamindb/_feature.py +1 -1
- lamindb/_finish.py +9 -52
- lamindb/_from_values.py +28 -12
- lamindb/_query_manager.py +2 -2
- lamindb/_query_set.py +5 -30
- lamindb/_record.py +73 -15
- lamindb/_run.py +1 -1
- lamindb/_transform.py +1 -1
- lamindb/core/__init__.py +9 -3
- lamindb/core/_context.py +513 -0
- lamindb/core/_data.py +8 -6
- lamindb/core/_label_manager.py +4 -3
- lamindb/core/_mapped_collection.py +82 -26
- lamindb/core/_settings.py +4 -8
- lamindb/core/datasets/_core.py +30 -23
- lamindb/core/exceptions.py +22 -5
- lamindb/core/storage/_valid_suffixes.py +2 -0
- lamindb/core/versioning.py +13 -1
- {lamindb-0.75.1.dist-info → lamindb-0.76.1.dist-info}/METADATA +5 -5
- {lamindb-0.75.1.dist-info → lamindb-0.76.1.dist-info}/RECORD +26 -26
- lamindb/core/_run_context.py +0 -514
- {lamindb-0.75.1.dist-info → lamindb-0.76.1.dist-info}/LICENSE +0 -0
- {lamindb-0.75.1.dist-info → lamindb-0.76.1.dist-info}/WHEEL +0 -0
lamindb/core/_run_context.py
DELETED
@@ -1,514 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import builtins
|
4
|
-
import hashlib
|
5
|
-
import os
|
6
|
-
from datetime import datetime, timezone
|
7
|
-
from pathlib import Path, PurePath
|
8
|
-
from typing import TYPE_CHECKING
|
9
|
-
|
10
|
-
from lamin_utils import logger
|
11
|
-
from lamindb_setup.core.hashing import hash_file
|
12
|
-
from lnschema_core import Run, Transform, ids
|
13
|
-
from lnschema_core.models import Param, ParamValue, RunParamValue
|
14
|
-
from lnschema_core.users import current_user_id
|
15
|
-
|
16
|
-
from ._settings import settings
|
17
|
-
from ._sync_git import get_transform_reference_from_git_repo
|
18
|
-
from .exceptions import (
|
19
|
-
MissingTransformSettings,
|
20
|
-
NotebookNotSavedError,
|
21
|
-
NoTitleError,
|
22
|
-
UpdateTransformSettings,
|
23
|
-
)
|
24
|
-
from .subsettings._transform_settings import transform_settings
|
25
|
-
from .versioning import bump_version as bump_version_function
|
26
|
-
|
27
|
-
if TYPE_CHECKING:
|
28
|
-
from lamindb_setup.core.types import UPathStr
|
29
|
-
from lnschema_core.types import TransformType
|
30
|
-
|
31
|
-
is_run_from_ipython = getattr(builtins, "__IPYTHON__", False)
|
32
|
-
|
33
|
-
msg_path_failed = (
|
34
|
-
"failed to infer notebook path.\nfix: either track manually via"
|
35
|
-
" `ln.track(transform=ln.Transform(name='My notebook'))` or pass"
|
36
|
-
" `path` to ln.track()"
|
37
|
-
)
|
38
|
-
|
39
|
-
|
40
|
-
def get_uid_ext(version: str) -> str:
|
41
|
-
from lamin_utils._base62 import encodebytes
|
42
|
-
|
43
|
-
# merely zero-padding the nbproject version such that the base62 encoding is
|
44
|
-
# at least 4 characters long doesn't yields sufficiently diverse hashes and
|
45
|
-
# leads to collisions; it'd be nice because the uid_ext would be ordered
|
46
|
-
return encodebytes(hashlib.md5(version.encode()).digest())[:4] # noqa: S324
|
47
|
-
|
48
|
-
|
49
|
-
def update_stem_uid_or_version(
|
50
|
-
stem_uid: str,
|
51
|
-
version: str,
|
52
|
-
bump_version: bool = False,
|
53
|
-
) -> (bool, str, str): # type:ignore
|
54
|
-
get_uid_ext(version)
|
55
|
-
updated = False
|
56
|
-
if bump_version:
|
57
|
-
response = "bump"
|
58
|
-
else:
|
59
|
-
# ask for generating a new stem uid
|
60
|
-
# it simply looks better here to not use the logger because we won't have an
|
61
|
-
# emoji also for the subsequent input question
|
62
|
-
if os.getenv("LAMIN_TESTING") is None:
|
63
|
-
response = input(
|
64
|
-
"To create a new stem uid, type 'new'. To bump the version, type 'bump'"
|
65
|
-
" or a custom version: "
|
66
|
-
)
|
67
|
-
else:
|
68
|
-
response = "new"
|
69
|
-
if response == "new":
|
70
|
-
new_stem_uid = ids.base62_12()
|
71
|
-
updated = True
|
72
|
-
else:
|
73
|
-
bump_version = True
|
74
|
-
new_version = version
|
75
|
-
if bump_version:
|
76
|
-
new_stem_uid = stem_uid
|
77
|
-
if response == "bump":
|
78
|
-
new_version = bump_version_function(version, behavior="prompt")
|
79
|
-
else:
|
80
|
-
new_version = response
|
81
|
-
updated = new_version != version
|
82
|
-
if updated:
|
83
|
-
new_metadata = (
|
84
|
-
f'ln.settings.transform.stem_uid = "{new_stem_uid}"\nln.settings.transform.version ='
|
85
|
-
f' "{new_version}"\n'
|
86
|
-
)
|
87
|
-
raise UpdateTransformSettings(
|
88
|
-
f"Please update your transform settings as follows:\n{new_metadata}"
|
89
|
-
)
|
90
|
-
return updated, new_stem_uid, new_version
|
91
|
-
|
92
|
-
|
93
|
-
def get_notebook_path():
|
94
|
-
from nbproject.dev._jupyter_communicate import (
|
95
|
-
notebook_path as get_notebook_path,
|
96
|
-
)
|
97
|
-
|
98
|
-
path = None
|
99
|
-
try:
|
100
|
-
path = get_notebook_path()
|
101
|
-
except Exception:
|
102
|
-
raise RuntimeError(msg_path_failed) from None
|
103
|
-
if path is None:
|
104
|
-
raise RuntimeError(msg_path_failed) from None
|
105
|
-
return path
|
106
|
-
|
107
|
-
|
108
|
-
# from https://stackoverflow.com/questions/61901628
|
109
|
-
def get_notebook_name_colab() -> str:
|
110
|
-
from socket import gethostbyname, gethostname # type: ignore
|
111
|
-
|
112
|
-
from requests import get # type: ignore
|
113
|
-
|
114
|
-
ip = gethostbyname(gethostname()) # 172.28.0.12
|
115
|
-
try:
|
116
|
-
name = get(f"http://{ip}:9000/api/sessions").json()[0]["name"] # noqa: S113
|
117
|
-
except Exception:
|
118
|
-
logger.warning(
|
119
|
-
"could not get notebook name from Google Colab, using: notebook.ipynb"
|
120
|
-
)
|
121
|
-
name = "notebook.ipynb"
|
122
|
-
return name.rstrip(".ipynb")
|
123
|
-
|
124
|
-
|
125
|
-
MESSAGE = """To track this {transform_type}, set
|
126
|
-
|
127
|
-
ln.settings.transform.stem_uid = "{stem_uid}"
|
128
|
-
ln.settings.transform.version = "{version}"
|
129
|
-
"""
|
130
|
-
|
131
|
-
MESSAGE_UPDATE = """You updated your {transform_type}.
|
132
|
-
|
133
|
-
If this is a minor update, bump your version from {old_version} to:
|
134
|
-
|
135
|
-
ln.settings.transform.version = "{new_version_minor_bump}"
|
136
|
-
|
137
|
-
If this is a major update, bump it to:
|
138
|
-
|
139
|
-
ln.settings.transform.version = "{new_version_major_bump}"
|
140
|
-
|
141
|
-
If this is a new {transform_type}, set:
|
142
|
-
|
143
|
-
ln.settings.transform.stem_uid = "{new_stem_uid}"
|
144
|
-
ln.settings.transform.version = "1"
|
145
|
-
|
146
|
-
"""
|
147
|
-
|
148
|
-
|
149
|
-
def raise_transform_settings_error_needs_update(old_version: str) -> None:
|
150
|
-
from lnschema_core.ids import base62_12
|
151
|
-
|
152
|
-
transform_type = "notebook" if is_run_from_ipython else "script"
|
153
|
-
new_stem_uid = base62_12()
|
154
|
-
|
155
|
-
raise UpdateTransformSettings(
|
156
|
-
MESSAGE_UPDATE.format(
|
157
|
-
transform_type=transform_type,
|
158
|
-
new_stem_uid=new_stem_uid,
|
159
|
-
old_version=old_version,
|
160
|
-
new_version_major_bump=bump_version_function(
|
161
|
-
old_version, bump_type="major", behavior="ignore"
|
162
|
-
),
|
163
|
-
new_version_minor_bump=bump_version_function(
|
164
|
-
old_version, bump_type="minor", behavior="ignore"
|
165
|
-
),
|
166
|
-
)
|
167
|
-
)
|
168
|
-
|
169
|
-
|
170
|
-
def raise_transform_settings_error() -> None:
|
171
|
-
from lnschema_core.ids import base62_12
|
172
|
-
|
173
|
-
transform_type = "notebook" if is_run_from_ipython else "script"
|
174
|
-
stem_uid = base62_12()
|
175
|
-
version = "1"
|
176
|
-
|
177
|
-
# backward compat: use the nbproject_id
|
178
|
-
if is_run_from_ipython:
|
179
|
-
from nbproject.dev import (
|
180
|
-
MetaContainer,
|
181
|
-
MetaStore,
|
182
|
-
read_notebook,
|
183
|
-
)
|
184
|
-
from nbproject.dev._jupyter_communicate import (
|
185
|
-
notebook_path as get_notebook_path,
|
186
|
-
)
|
187
|
-
|
188
|
-
filepath = get_notebook_path()
|
189
|
-
nb = read_notebook(filepath) # type: ignore
|
190
|
-
nb_meta = nb.metadata
|
191
|
-
if "nbproject" in nb_meta:
|
192
|
-
meta_container = MetaContainer(**nb_meta["nbproject"])
|
193
|
-
meta_store = MetaStore(meta_container, filepath)
|
194
|
-
stem_uid, version = meta_store.id, meta_store.version
|
195
|
-
raise MissingTransformSettings(
|
196
|
-
MESSAGE.format(
|
197
|
-
transform_type=transform_type, stem_uid=stem_uid, version=version
|
198
|
-
)
|
199
|
-
)
|
200
|
-
|
201
|
-
|
202
|
-
def pretty_pypackages(dependencies: dict) -> str:
|
203
|
-
deps_list = []
|
204
|
-
for pkg, ver in dependencies.items():
|
205
|
-
if ver != "":
|
206
|
-
deps_list.append(pkg + f"=={ver}")
|
207
|
-
else:
|
208
|
-
deps_list.append(pkg)
|
209
|
-
deps_list.sort()
|
210
|
-
return " ".join(deps_list)
|
211
|
-
|
212
|
-
|
213
|
-
class run_context:
|
214
|
-
"""Global run context."""
|
215
|
-
|
216
|
-
transform: Transform | None = None
|
217
|
-
"""Current transform."""
|
218
|
-
run: Run | None = None
|
219
|
-
"""Current run."""
|
220
|
-
path: Path | None = None
|
221
|
-
"""A local path to the script that's running."""
|
222
|
-
|
223
|
-
@classmethod
|
224
|
-
def _track(
|
225
|
-
cls,
|
226
|
-
*,
|
227
|
-
params: dict | None = None,
|
228
|
-
transform: Transform | None = None,
|
229
|
-
new_run: bool | None = None,
|
230
|
-
path: str | None = None,
|
231
|
-
) -> Run:
|
232
|
-
"""Track notebook or script run.
|
233
|
-
|
234
|
-
Creates or loads a global :class:`~lamindb.Run` that enables data
|
235
|
-
lineage tracking. You can find it in :class:`~lamindb.core.run_context`.
|
236
|
-
|
237
|
-
Saves source code and compute environment.
|
238
|
-
|
239
|
-
If :attr:`~lamindb.core.Settings.sync_git_repo` is set, will first check
|
240
|
-
whether the script exists in the git repository and add a link.
|
241
|
-
|
242
|
-
Args:
|
243
|
-
params: A dictionary of parameters to track for the run.
|
244
|
-
transform: Can be of type `"pipeline"` or `"notebook"`
|
245
|
-
(:class:`~lamindb.core.types.TransformType`).
|
246
|
-
new_run: If `False`, loads latest run of transform
|
247
|
-
(default notebook), if `True`, creates new run (default pipeline).
|
248
|
-
path: Filepath of notebook or script. Only needed if it can't be
|
249
|
-
automatically detected.
|
250
|
-
|
251
|
-
Examples:
|
252
|
-
|
253
|
-
To track a notebook or script, call:
|
254
|
-
|
255
|
-
>>> import lamindb as ln
|
256
|
-
>>> ln.track()
|
257
|
-
|
258
|
-
If you'd like to track an abstract pipeline run, pass a
|
259
|
-
:class:`~lamindb.Transform` object of ``type`` ``"pipeline"``:
|
260
|
-
|
261
|
-
>>> ln.Transform(name="Cell Ranger", version="2", type="pipeline").save()
|
262
|
-
>>> transform = ln.Transform.filter(name="Cell Ranger", version="2").one()
|
263
|
-
>>> ln.track(transform=transform)
|
264
|
-
"""
|
265
|
-
cls.path = None
|
266
|
-
if transform is None:
|
267
|
-
is_tracked = False
|
268
|
-
transform_settings_are_set = (
|
269
|
-
transform_settings.stem_uid is not None
|
270
|
-
and transform_settings.version is not None
|
271
|
-
)
|
272
|
-
if transform_settings_are_set:
|
273
|
-
stem_uid, version = (
|
274
|
-
transform_settings.stem_uid,
|
275
|
-
transform_settings.version,
|
276
|
-
)
|
277
|
-
transform = Transform.filter(
|
278
|
-
uid__startswith=stem_uid, version=version
|
279
|
-
).one_or_none()
|
280
|
-
if is_run_from_ipython:
|
281
|
-
key, name = cls._track_notebook(path=path)
|
282
|
-
transform_type = "notebook"
|
283
|
-
transform_ref = None
|
284
|
-
transform_ref_type = None
|
285
|
-
else:
|
286
|
-
(name, key, transform_ref, transform_ref_type) = cls._track_script(
|
287
|
-
path=path
|
288
|
-
)
|
289
|
-
transform_type = "script"
|
290
|
-
# overwrite whatever is auto-detected in the notebook or script
|
291
|
-
if transform_settings.name is not None:
|
292
|
-
name = transform_settings.name
|
293
|
-
cls._create_or_load_transform(
|
294
|
-
stem_uid=stem_uid,
|
295
|
-
version=version,
|
296
|
-
name=name,
|
297
|
-
transform_ref=transform_ref,
|
298
|
-
transform_ref_type=transform_ref_type,
|
299
|
-
transform_type=transform_type,
|
300
|
-
key=key,
|
301
|
-
transform=transform,
|
302
|
-
)
|
303
|
-
# if no error is raised, the transform is tracked
|
304
|
-
is_tracked = True
|
305
|
-
if not is_tracked:
|
306
|
-
raise_transform_settings_error()
|
307
|
-
else:
|
308
|
-
if transform.type in {"notebook", "script"}:
|
309
|
-
raise ValueError(
|
310
|
-
"Use ln.track() without passing transform in a notebook or script"
|
311
|
-
" - metadata is automatically parsed"
|
312
|
-
)
|
313
|
-
transform_exists = None
|
314
|
-
if transform.id is not None:
|
315
|
-
# transform has an id but unclear whether already saved
|
316
|
-
transform_exists = Transform.filter(id=transform.id).first()
|
317
|
-
if transform_exists is None:
|
318
|
-
transform.save()
|
319
|
-
logger.important(f"saved: {transform}")
|
320
|
-
transform_exists = transform
|
321
|
-
else:
|
322
|
-
logger.important(f"loaded: {transform}")
|
323
|
-
cls.transform = transform_exists
|
324
|
-
|
325
|
-
if new_run is None: # for notebooks, default to loading latest runs
|
326
|
-
new_run = False if cls.transform.type == "notebook" else True # type: ignore
|
327
|
-
|
328
|
-
run = None
|
329
|
-
from lamindb._run import Run
|
330
|
-
|
331
|
-
if not new_run: # try loading latest run by same user
|
332
|
-
run = (
|
333
|
-
Run.filter(transform=cls.transform, created_by_id=current_user_id())
|
334
|
-
.order_by("-created_at")
|
335
|
-
.first()
|
336
|
-
)
|
337
|
-
if run is not None: # loaded latest run
|
338
|
-
run.started_at = datetime.now(timezone.utc) # update run time
|
339
|
-
logger.important(f"loaded: {run}")
|
340
|
-
|
341
|
-
if run is None: # create new run
|
342
|
-
run = Run(
|
343
|
-
transform=cls.transform,
|
344
|
-
params=params,
|
345
|
-
)
|
346
|
-
logger.important(f"saved: {run}")
|
347
|
-
# can only determine at ln.finish() if run was consecutive in
|
348
|
-
# interactive session, otherwise, is consecutive
|
349
|
-
run.is_consecutive = True if is_run_from_ipython else None
|
350
|
-
# need to save in all cases
|
351
|
-
run.save()
|
352
|
-
if params is not None:
|
353
|
-
run.params.add_values(params)
|
354
|
-
cls.run = run
|
355
|
-
|
356
|
-
from ._track_environment import track_environment
|
357
|
-
|
358
|
-
track_environment(run)
|
359
|
-
return run
|
360
|
-
|
361
|
-
@classmethod
|
362
|
-
def _track_script(
|
363
|
-
cls,
|
364
|
-
*,
|
365
|
-
path: UPathStr | None,
|
366
|
-
) -> tuple[str, str, str, str]:
|
367
|
-
if path is None:
|
368
|
-
import inspect
|
369
|
-
|
370
|
-
frame = inspect.stack()[2]
|
371
|
-
module = inspect.getmodule(frame[0])
|
372
|
-
cls.path = Path(module.__file__)
|
373
|
-
else:
|
374
|
-
cls.path = Path(path)
|
375
|
-
name = cls.path.name
|
376
|
-
key = name
|
377
|
-
reference = None
|
378
|
-
reference_type = None
|
379
|
-
if settings.sync_git_repo is not None:
|
380
|
-
reference = get_transform_reference_from_git_repo(cls.path)
|
381
|
-
reference_type = "url"
|
382
|
-
return name, key, reference, reference_type
|
383
|
-
|
384
|
-
@classmethod
|
385
|
-
def _track_notebook(
|
386
|
-
cls,
|
387
|
-
*,
|
388
|
-
path: str | None,
|
389
|
-
):
|
390
|
-
if path is None:
|
391
|
-
path = get_notebook_path()
|
392
|
-
key = Path(path).stem
|
393
|
-
if isinstance(path, (Path, PurePath)):
|
394
|
-
path_str = path.as_posix() # type: ignore
|
395
|
-
else:
|
396
|
-
path_str = str(path)
|
397
|
-
if path_str.endswith("Untitled.ipynb"):
|
398
|
-
raise RuntimeError("Please rename your notebook before tracking it")
|
399
|
-
if path_str.startswith("/fileId="):
|
400
|
-
key = get_notebook_name_colab()
|
401
|
-
name = key
|
402
|
-
else:
|
403
|
-
import nbproject
|
404
|
-
|
405
|
-
try:
|
406
|
-
nbproject_title = nbproject.meta.live.title
|
407
|
-
except IndexError:
|
408
|
-
raise NotebookNotSavedError(
|
409
|
-
"The notebook is not saved, please save the notebook and"
|
410
|
-
" rerun `ln.track()`"
|
411
|
-
) from None
|
412
|
-
if nbproject_title is None:
|
413
|
-
raise NoTitleError(
|
414
|
-
"Please add a title to your notebook in a markdown cell: # Title"
|
415
|
-
) from None
|
416
|
-
name = nbproject_title
|
417
|
-
# log imported python packages
|
418
|
-
if not path_str.startswith("/fileId="):
|
419
|
-
try:
|
420
|
-
from nbproject.dev._pypackage import infer_pypackages
|
421
|
-
|
422
|
-
nb = nbproject.dev.read_notebook(path_str)
|
423
|
-
logger.important(
|
424
|
-
"notebook imports:"
|
425
|
-
f" {pretty_pypackages(infer_pypackages(nb, pin_versions=True))}"
|
426
|
-
)
|
427
|
-
except Exception:
|
428
|
-
logger.debug("inferring imported packages failed")
|
429
|
-
pass
|
430
|
-
cls.path = Path(path_str)
|
431
|
-
return key, name
|
432
|
-
|
433
|
-
@classmethod
|
434
|
-
def _create_or_load_transform(
|
435
|
-
cls,
|
436
|
-
*,
|
437
|
-
stem_uid: str,
|
438
|
-
version: str | None,
|
439
|
-
name: str,
|
440
|
-
transform_ref: str | None = None,
|
441
|
-
transform_ref_type: str | None = None,
|
442
|
-
key: str | None = None,
|
443
|
-
transform_type: TransformType = None,
|
444
|
-
transform: Transform | None = None,
|
445
|
-
):
|
446
|
-
# make a new transform record
|
447
|
-
if transform is None:
|
448
|
-
uid = f"{stem_uid}{get_uid_ext(version)}"
|
449
|
-
transform = Transform(
|
450
|
-
uid=uid,
|
451
|
-
version=version,
|
452
|
-
name=name,
|
453
|
-
key=key,
|
454
|
-
reference=transform_ref,
|
455
|
-
reference_type=transform_ref_type,
|
456
|
-
type=transform_type,
|
457
|
-
)
|
458
|
-
transform.save()
|
459
|
-
logger.important(f"saved: {transform}")
|
460
|
-
else:
|
461
|
-
# check whether there was an update to the transform, like
|
462
|
-
# renaming the file or updating the title
|
463
|
-
if transform.name != name or transform.key != key:
|
464
|
-
if os.getenv("LAMIN_TESTING") is None:
|
465
|
-
response = input(
|
466
|
-
"Updated transform filename and/or title: Do you want to assign a"
|
467
|
-
" new stem_uid or version? (y/n)"
|
468
|
-
)
|
469
|
-
else:
|
470
|
-
response = "y"
|
471
|
-
if response == "y":
|
472
|
-
# will raise SystemExit
|
473
|
-
update_stem_uid_or_version(stem_uid, version)
|
474
|
-
else:
|
475
|
-
transform.name = name
|
476
|
-
transform.key = key
|
477
|
-
transform.save()
|
478
|
-
logger.important(f"updated: {transform}")
|
479
|
-
# check whether transform source code was already saved
|
480
|
-
if transform._source_code_artifact_id is not None:
|
481
|
-
response = None
|
482
|
-
if is_run_from_ipython:
|
483
|
-
if os.getenv("LAMIN_TESTING") is None:
|
484
|
-
response = input(
|
485
|
-
"You already saved source code for this notebook."
|
486
|
-
" Auto-bump the version before a new run? (y/n)"
|
487
|
-
)
|
488
|
-
else:
|
489
|
-
response = "y"
|
490
|
-
else:
|
491
|
-
hash, _ = hash_file(cls.path) # ignore hash_type for now
|
492
|
-
if hash != transform._source_code_artifact.hash:
|
493
|
-
# only if hashes don't match, we need user input
|
494
|
-
if os.getenv("LAMIN_TESTING") is None:
|
495
|
-
response = input(
|
496
|
-
"You already saved source code for this script and meanwhile modified it without bumping a version."
|
497
|
-
" Auto-bump the version before a new run? (y/n)"
|
498
|
-
)
|
499
|
-
else:
|
500
|
-
response = "y"
|
501
|
-
else:
|
502
|
-
logger.important(f"loaded: {transform}")
|
503
|
-
if response is not None:
|
504
|
-
# if a script is re-run and hashes match, we don't need user input
|
505
|
-
if response == "y":
|
506
|
-
update_stem_uid_or_version(stem_uid, version, bump_version=True)
|
507
|
-
else:
|
508
|
-
# the user didn't agree to auto-bump, hence treat manually
|
509
|
-
raise_transform_settings_error_needs_update(
|
510
|
-
old_version=transform.version
|
511
|
-
)
|
512
|
-
else:
|
513
|
-
logger.important(f"loaded: {transform}")
|
514
|
-
cls.transform = transform
|
File without changes
|
File without changes
|