lamindb 0.76.9__py3-none-any.whl → 0.76.10__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 +15 -14
- lamindb/_artifact.py +2 -1
- lamindb/_can_validate.py +46 -4
- lamindb/_collection.py +2 -1
- lamindb/_curate.py +3 -1
- lamindb/_feature_set.py +3 -1
- lamindb/_finish.py +11 -17
- lamindb/_from_values.py +110 -89
- lamindb/_query_set.py +3 -1
- lamindb/_record.py +81 -62
- lamindb/_run.py +3 -0
- lamindb/_save.py +3 -1
- lamindb/_transform.py +9 -6
- lamindb/core/_context.py +94 -78
- lamindb/core/_data.py +113 -41
- lamindb/core/_django.py +209 -0
- lamindb/core/_feature_manager.py +140 -13
- lamindb/core/_label_manager.py +59 -23
- lamindb/core/_mapped_collection.py +1 -1
- lamindb/core/_settings.py +2 -1
- lamindb/core/exceptions.py +9 -9
- lamindb/core/storage/_anndata_accessor.py +2 -1
- lamindb/core/versioning.py +2 -14
- {lamindb-0.76.9.dist-info → lamindb-0.76.10.dist-info}/METADATA +8 -8
- {lamindb-0.76.9.dist-info → lamindb-0.76.10.dist-info}/RECORD +27 -26
- {lamindb-0.76.9.dist-info → lamindb-0.76.10.dist-info}/LICENSE +0 -0
- {lamindb-0.76.9.dist-info → lamindb-0.76.10.dist-info}/WHEEL +0 -0
lamindb/_record.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import builtins
|
4
|
-
from typing import TYPE_CHECKING,
|
4
|
+
from typing import TYPE_CHECKING, NamedTuple
|
5
5
|
|
6
6
|
import dj_database_url
|
7
7
|
import lamindb_setup as ln_setup
|
@@ -9,16 +9,19 @@ from django.db import connections, transaction
|
|
9
9
|
from django.db.models import IntegerField, Manager, Q, QuerySet, Value
|
10
10
|
from lamin_utils import logger
|
11
11
|
from lamin_utils._lookup import Lookup
|
12
|
-
from lamindb_setup._connect_instance import
|
12
|
+
from lamindb_setup._connect_instance import (
|
13
|
+
get_owner_name_from_identifier,
|
14
|
+
load_instance_settings,
|
15
|
+
update_db_using_local,
|
16
|
+
)
|
13
17
|
from lamindb_setup.core._docs import doc_args
|
14
18
|
from lamindb_setup.core._hub_core import connect_instance
|
15
|
-
from
|
19
|
+
from lamindb_setup.core._settings_store import instance_settings_file
|
20
|
+
from lnschema_core.models import IsVersioned, Record, Run, Transform
|
16
21
|
|
17
22
|
from lamindb._utils import attach_func_to_class_method
|
18
23
|
from lamindb.core._settings import settings
|
19
24
|
|
20
|
-
from ._from_values import get_or_create_records
|
21
|
-
|
22
25
|
if TYPE_CHECKING:
|
23
26
|
import pandas as pd
|
24
27
|
from lnschema_core.types import ListLike, StrField
|
@@ -169,33 +172,6 @@ def df(
|
|
169
172
|
return query_set[:limit].df(include=include, join=join)
|
170
173
|
|
171
174
|
|
172
|
-
# from_values doesn't apply for QuerySet or Manager
|
173
|
-
@classmethod # type:ignore
|
174
|
-
@doc_args(Record.from_values.__doc__)
|
175
|
-
def from_values(
|
176
|
-
cls,
|
177
|
-
values: ListLike,
|
178
|
-
field: StrField | None = None,
|
179
|
-
create: bool = False,
|
180
|
-
organism: Record | str | None = None,
|
181
|
-
source: Record | None = None,
|
182
|
-
mute: bool = False,
|
183
|
-
) -> list[Record]:
|
184
|
-
"""{}""" # noqa: D415
|
185
|
-
from_source = True if cls.__module__.startswith("bionty.") else False
|
186
|
-
|
187
|
-
field_str = get_name_field(cls, field=field)
|
188
|
-
return get_or_create_records(
|
189
|
-
iterable=values,
|
190
|
-
field=getattr(cls, field_str),
|
191
|
-
create=create,
|
192
|
-
from_source=from_source,
|
193
|
-
organism=organism,
|
194
|
-
source=source,
|
195
|
-
mute=mute,
|
196
|
-
)
|
197
|
-
|
198
|
-
|
199
175
|
def _search(
|
200
176
|
cls,
|
201
177
|
string: str,
|
@@ -402,26 +378,34 @@ def using(
|
|
402
378
|
"""{}""" # noqa: D415
|
403
379
|
if instance is None:
|
404
380
|
return QuerySet(model=cls, using=None)
|
405
|
-
from lamindb_setup._connect_instance import (
|
406
|
-
load_instance_settings,
|
407
|
-
update_db_using_local,
|
408
|
-
)
|
409
|
-
from lamindb_setup.core._settings_store import instance_settings_file
|
410
|
-
|
411
381
|
owner, name = get_owner_name_from_identifier(instance)
|
412
382
|
settings_file = instance_settings_file(name, owner)
|
383
|
+
cache_filepath = (
|
384
|
+
ln_setup.settings.storage.cache_dir / f"instance--{owner}--{name}--uid.txt"
|
385
|
+
)
|
413
386
|
if not settings_file.exists():
|
414
|
-
|
415
|
-
if isinstance(
|
387
|
+
result = connect_instance(owner=owner, name=name)
|
388
|
+
if isinstance(result, str):
|
416
389
|
raise RuntimeError(
|
417
|
-
f"Failed to load instance {instance}, please check your
|
390
|
+
f"Failed to load instance {instance}, please check your permissions!"
|
391
|
+
)
|
392
|
+
iresult, _ = result
|
393
|
+
source_schema = {
|
394
|
+
schema for schema in iresult["schema_str"].split(",") if schema != ""
|
395
|
+
} # type: ignore
|
396
|
+
target_schema = ln_setup.settings.instance.schema
|
397
|
+
if not source_schema.issubset(target_schema):
|
398
|
+
missing_members = source_schema - target_schema
|
399
|
+
logger.warning(
|
400
|
+
f"source schema has additional modules: {missing_members}\nconsider mounting these schema modules to not encounter errors"
|
418
401
|
)
|
419
|
-
|
402
|
+
cache_filepath.write_text(iresult["lnid"]) # type: ignore
|
420
403
|
settings_file = instance_settings_file(name, owner)
|
421
|
-
db = update_db_using_local(
|
404
|
+
db = update_db_using_local(iresult, settings_file)
|
422
405
|
else:
|
423
406
|
isettings = load_instance_settings(settings_file)
|
424
407
|
db = isettings.db
|
408
|
+
cache_filepath.write_text(isettings.uid)
|
425
409
|
add_db_connection(db, instance)
|
426
410
|
return QuerySet(model=cls, using=instance)
|
427
411
|
|
@@ -439,7 +423,7 @@ def update_fk_to_default_db(
|
|
439
423
|
using_key: str | None,
|
440
424
|
transfer_logs: dict,
|
441
425
|
):
|
442
|
-
record = records[0] if isinstance(records, (
|
426
|
+
record = records[0] if isinstance(records, (list, QuerySet)) else records
|
443
427
|
if hasattr(record, f"{fk}_id") and getattr(record, f"{fk}_id") is not None:
|
444
428
|
fk_record = getattr(record, fk)
|
445
429
|
field = REGISTRY_UNIQUE_FIELD.get(fk, "uid")
|
@@ -453,7 +437,7 @@ def update_fk_to_default_db(
|
|
453
437
|
transfer_to_default_db(
|
454
438
|
fk_record_default, using_key, save=True, transfer_logs=transfer_logs
|
455
439
|
)
|
456
|
-
if isinstance(records, (
|
440
|
+
if isinstance(records, (list, QuerySet)):
|
457
441
|
for r in records:
|
458
442
|
setattr(r, f"{fk}", None)
|
459
443
|
setattr(r, f"{fk}_id", fk_record_default.id)
|
@@ -477,6 +461,45 @@ def transfer_fk_to_default_db_bulk(
|
|
477
461
|
update_fk_to_default_db(records, fk, using_key, transfer_logs=transfer_logs)
|
478
462
|
|
479
463
|
|
464
|
+
def get_transfer_run(record) -> Run:
|
465
|
+
from lamindb_setup import settings as setup_settings
|
466
|
+
|
467
|
+
from lamindb.core._context import context
|
468
|
+
from lamindb.core._data import WARNING_RUN_TRANSFORM
|
469
|
+
|
470
|
+
slug = record._state.db
|
471
|
+
owner, name = get_owner_name_from_identifier(slug)
|
472
|
+
cache_filepath = (
|
473
|
+
ln_setup.settings.storage.cache_dir / f"instance--{owner}--{name}--uid.txt"
|
474
|
+
)
|
475
|
+
if not cache_filepath.exists():
|
476
|
+
raise SystemExit("Need to call .using() before")
|
477
|
+
instance_uid = cache_filepath.read_text()
|
478
|
+
key = f"transfers/{instance_uid}"
|
479
|
+
uid = instance_uid + "0000"
|
480
|
+
transform = Transform.filter(uid=uid).one_or_none()
|
481
|
+
if transform is None:
|
482
|
+
search_names = settings.creation.search_names
|
483
|
+
settings.creation.search_names = False
|
484
|
+
transform = Transform(
|
485
|
+
uid=uid, name=f"Transfer from `{slug}`", key=key, type="function"
|
486
|
+
).save()
|
487
|
+
settings.creation.search_names = search_names
|
488
|
+
# use the global run context to get the parent run id
|
489
|
+
if context.run is not None:
|
490
|
+
parent = context.run
|
491
|
+
else:
|
492
|
+
if not settings.creation.artifact_silence_missing_run_warning:
|
493
|
+
logger.warning(WARNING_RUN_TRANSFORM)
|
494
|
+
parent = None
|
495
|
+
# it doesn't seem to make sense to create new runs for every transfer
|
496
|
+
run = Run.filter(transform=transform, parent=parent).one_or_none()
|
497
|
+
if run is None:
|
498
|
+
run = Run(transform=transform, parent=parent).save()
|
499
|
+
run.parent = parent # so that it's available in memory
|
500
|
+
return run
|
501
|
+
|
502
|
+
|
480
503
|
def transfer_to_default_db(
|
481
504
|
record: Record,
|
482
505
|
using_key: str | None,
|
@@ -485,12 +508,13 @@ def transfer_to_default_db(
|
|
485
508
|
save: bool = False,
|
486
509
|
transfer_fk: bool = True,
|
487
510
|
) -> Record | None:
|
488
|
-
|
489
|
-
|
490
|
-
|
511
|
+
if record._state.db is None or record._state.db == "default":
|
512
|
+
return None
|
491
513
|
registry = record.__class__
|
492
514
|
record_on_default = registry.objects.filter(uid=record.uid).one_or_none()
|
493
515
|
record_str = f"{record.__class__.__name__}(uid='{record.uid}')"
|
516
|
+
if transfer_logs["run"] is None:
|
517
|
+
transfer_logs["run"] = get_transfer_run(record)
|
494
518
|
if record_on_default is not None:
|
495
519
|
transfer_logs["mapped"].append(record_str)
|
496
520
|
return record_on_default
|
@@ -500,20 +524,15 @@ def transfer_to_default_db(
|
|
500
524
|
if hasattr(record, "created_by_id"):
|
501
525
|
record.created_by = None
|
502
526
|
record.created_by_id = ln_setup.settings.user.id
|
527
|
+
# run & transform
|
528
|
+
run = transfer_logs["run"]
|
503
529
|
if hasattr(record, "run_id"):
|
504
530
|
record.run = None
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
if not settings.creation.artifact_silence_missing_run_warning:
|
509
|
-
logger.warning(WARNING_RUN_TRANSFORM)
|
510
|
-
record.run_id = None
|
511
|
-
if hasattr(record, "transform_id") and record._meta.model_name != "run":
|
531
|
+
record.run_id = run.id
|
532
|
+
# deal with denormalized transform FK on artifact and collection
|
533
|
+
if hasattr(record, "transform_id"):
|
512
534
|
record.transform = None
|
513
|
-
|
514
|
-
record.transform_id = context.run.transform_id
|
515
|
-
else:
|
516
|
-
record.transform_id = None
|
535
|
+
record.transform_id = run.transform_id
|
517
536
|
# transfer other foreign key fields
|
518
537
|
fk_fields = [
|
519
538
|
i.name
|
@@ -546,7 +565,7 @@ def save(self, *args, **kwargs) -> Record:
|
|
546
565
|
artifacts = self.ordered_artifacts.list()
|
547
566
|
pre_existing_record = None
|
548
567
|
# consider records that are being transferred from other databases
|
549
|
-
transfer_logs: dict[str, list[str]] = {"mapped": [], "transferred": []}
|
568
|
+
transfer_logs: dict[str, list[str]] = {"mapped": [], "transferred": [], "run": None}
|
550
569
|
if db is not None and db != "default" and using_key is None:
|
551
570
|
if isinstance(self, IsVersioned):
|
552
571
|
if not self.is_latest:
|
@@ -594,7 +613,8 @@ def save(self, *args, **kwargs) -> Record:
|
|
594
613
|
self.features._add_from(self_on_db, transfer_logs=transfer_logs)
|
595
614
|
self.labels.add_from(self_on_db, transfer_logs=transfer_logs)
|
596
615
|
for k, v in transfer_logs.items():
|
597
|
-
|
616
|
+
if k != "run":
|
617
|
+
logger.important(f"{k} records: {', '.join(v)}")
|
598
618
|
return self
|
599
619
|
|
600
620
|
|
@@ -632,7 +652,6 @@ METHOD_NAMES = [
|
|
632
652
|
"lookup",
|
633
653
|
"save",
|
634
654
|
"delete",
|
635
|
-
"from_values",
|
636
655
|
"using",
|
637
656
|
]
|
638
657
|
|
lamindb/_run.py
CHANGED
@@ -18,13 +18,16 @@ def __init__(run: Run, *args, **kwargs):
|
|
18
18
|
reference_type: str | None = (
|
19
19
|
kwargs.pop("reference_type") if "reference_type" in kwargs else None
|
20
20
|
)
|
21
|
+
parent: Run | None = kwargs.pop("parent", None)
|
21
22
|
if transform is None:
|
22
23
|
raise TypeError("Pass transform parameter")
|
23
24
|
if transform._state.adding:
|
24
25
|
raise ValueError("Please save transform record before creating a run")
|
26
|
+
|
25
27
|
super(Run, run).__init__(
|
26
28
|
transform=transform,
|
27
29
|
reference=reference,
|
30
|
+
parent=parent,
|
28
31
|
reference_type=reference_type,
|
29
32
|
)
|
30
33
|
|
lamindb/_save.py
CHANGED
@@ -6,7 +6,7 @@ import traceback
|
|
6
6
|
from collections import defaultdict
|
7
7
|
from datetime import datetime
|
8
8
|
from functools import partial
|
9
|
-
from typing import TYPE_CHECKING,
|
9
|
+
from typing import TYPE_CHECKING, overload
|
10
10
|
|
11
11
|
import lamindb_setup
|
12
12
|
from django.db import IntegrityError, transaction
|
@@ -25,6 +25,8 @@ from lamindb.core.storage.paths import (
|
|
25
25
|
)
|
26
26
|
|
27
27
|
if TYPE_CHECKING:
|
28
|
+
from collections.abc import Iterable
|
29
|
+
|
28
30
|
from lamindb_setup.core.upath import UPath
|
29
31
|
|
30
32
|
|
lamindb/_transform.py
CHANGED
@@ -6,6 +6,8 @@ from lamin_utils import logger
|
|
6
6
|
from lamindb_setup.core._docs import doc_args
|
7
7
|
from lnschema_core.models import Run, Transform
|
8
8
|
|
9
|
+
from lamindb.core.exceptions import InconsistentKey
|
10
|
+
|
9
11
|
from ._parents import _view_parents
|
10
12
|
from ._run import delete_run_artifacts
|
11
13
|
from .core.versioning import message_update_key_in_version_family, process_revises
|
@@ -38,15 +40,16 @@ def __init__(transform: Transform, *args, **kwargs):
|
|
38
40
|
f"reference_type can be passed, but you passed: {kwargs}"
|
39
41
|
)
|
40
42
|
if revises is None:
|
41
|
-
|
43
|
+
# need to check uid before checking key
|
44
|
+
if uid is not None:
|
42
45
|
revises = (
|
43
|
-
Transform.filter(
|
46
|
+
Transform.filter(uid__startswith=uid[:-4], is_latest=True)
|
44
47
|
.order_by("-created_at")
|
45
48
|
.first()
|
46
49
|
)
|
47
|
-
elif
|
50
|
+
elif key is not None:
|
48
51
|
revises = (
|
49
|
-
Transform.filter(
|
52
|
+
Transform.filter(key=key, is_latest=True)
|
50
53
|
.order_by("-created_at")
|
51
54
|
.first()
|
52
55
|
)
|
@@ -63,8 +66,8 @@ def __init__(transform: Transform, *args, **kwargs):
|
|
63
66
|
new_key=key,
|
64
67
|
registry="Artifact",
|
65
68
|
)
|
66
|
-
raise
|
67
|
-
f"`key` is {key}, but `revises.key` is '{revises.key}'\n\
|
69
|
+
raise InconsistentKey(
|
70
|
+
f"`key` is {key}, but `revises.key` is '{revises.key}'\n\nEither do *not* pass `key`.\n\n{note}"
|
68
71
|
)
|
69
72
|
new_uid, version, name, revises = process_revises(revises, version, name, Transform)
|
70
73
|
# this is only because the user-facing constructor allows passing a uid
|