lamindb 0.49.3__py3-none-any.whl → 0.50.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 +55 -15
- lamindb/_context.py +25 -25
- lamindb/_delete.py +8 -8
- lamindb/_feature.py +15 -11
- lamindb/_feature_set.py +70 -39
- lamindb/_file.py +80 -56
- lamindb/_filter.py +5 -5
- lamindb/_from_values.py +55 -92
- lamindb/{_manager.py → _query_manager.py} +8 -5
- lamindb/{_queryset.py → _query_set.py} +31 -28
- lamindb/{_orm.py → _registry.py} +53 -294
- lamindb/_save.py +14 -13
- lamindb/_synonym.py +203 -0
- lamindb/_validate.py +134 -0
- lamindb/_view.py +15 -9
- lamindb/dev/__init__.py +13 -6
- lamindb/dev/_data.py +195 -0
- lamindb/dev/_feature_manager.py +102 -0
- lamindb/dev/_settings.py +10 -9
- lamindb/dev/_view_parents.py +36 -17
- lamindb/dev/datasets/__init__.py +5 -3
- lamindb/dev/datasets/_core.py +35 -17
- lamindb/dev/exc.py +4 -0
- lamindb/dev/storage/_backed_access.py +53 -17
- lamindb/dev/storage/file.py +44 -15
- {lamindb-0.49.3.dist-info → lamindb-0.50.1.dist-info}/METADATA +34 -36
- lamindb-0.50.1.dist-info/RECORD +47 -0
- lamindb/_feature_manager.py +0 -237
- lamindb-0.49.3.dist-info/RECORD +0 -43
- {lamindb-0.49.3.dist-info → lamindb-0.50.1.dist-info}/LICENSE +0 -0
- {lamindb-0.49.3.dist-info → lamindb-0.50.1.dist-info}/WHEEL +0 -0
- {lamindb-0.49.3.dist-info → lamindb-0.50.1.dist-info}/entry_points.txt +0 -0
lamindb/{_orm.py → _registry.py}
RENAMED
@@ -1,5 +1,5 @@
|
|
1
1
|
import builtins
|
2
|
-
from typing import Dict, Iterable, List,
|
2
|
+
from typing import Dict, Iterable, List, NamedTuple, Optional, Union
|
3
3
|
|
4
4
|
import pandas as pd
|
5
5
|
from django.core.exceptions import FieldDoesNotExist
|
@@ -9,16 +9,17 @@ from lamin_utils import colors, logger
|
|
9
9
|
from lamin_utils._lookup import Lookup
|
10
10
|
from lamin_utils._search import search as base_search
|
11
11
|
from lamindb_setup.dev._docs import doc_args
|
12
|
-
from lnschema_core import
|
13
|
-
from lnschema_core.models import
|
12
|
+
from lnschema_core import Registry
|
13
|
+
from lnschema_core.models import format_field_value
|
14
14
|
from lnschema_core.types import ListLike, StrField
|
15
15
|
|
16
16
|
from lamindb.dev.utils import attach_func_to_class_method
|
17
17
|
|
18
18
|
from . import _TESTING
|
19
|
-
from .
|
20
|
-
from .
|
19
|
+
from ._from_values import get_or_create_records
|
20
|
+
from .dev._feature_manager import create_features_df
|
21
21
|
from .dev._settings import settings
|
22
|
+
from .dev._view_parents import _transform_emoji
|
22
23
|
|
23
24
|
IPYTHON = getattr(builtins, "__IPYTHON__", False)
|
24
25
|
|
@@ -27,7 +28,7 @@ class ValidationError(Exception):
|
|
27
28
|
pass
|
28
29
|
|
29
30
|
|
30
|
-
def init_self_from_db(self:
|
31
|
+
def init_self_from_db(self: Registry, existing_record: Registry):
|
31
32
|
new_args = [
|
32
33
|
getattr(existing_record, field.attname) for field in self._meta.concrete_fields
|
33
34
|
]
|
@@ -36,7 +37,7 @@ def init_self_from_db(self: ORM, existing_record: ORM):
|
|
36
37
|
self._state.db = "default"
|
37
38
|
|
38
39
|
|
39
|
-
def validate_required_fields(orm:
|
40
|
+
def validate_required_fields(orm: Registry, kwargs):
|
40
41
|
required_fields = {
|
41
42
|
k.name for k in orm._meta.fields if not k.null and k.default is None
|
42
43
|
}
|
@@ -49,7 +50,7 @@ def validate_required_fields(orm: ORM, kwargs):
|
|
49
50
|
raise TypeError(f"{missing_fields} are required.")
|
50
51
|
|
51
52
|
|
52
|
-
def suggest_objects_with_same_name(orm:
|
53
|
+
def suggest_objects_with_same_name(orm: Registry, kwargs) -> Optional[str]:
|
53
54
|
if kwargs.get("name") is None:
|
54
55
|
return None
|
55
56
|
else:
|
@@ -65,9 +66,10 @@ def suggest_objects_with_same_name(orm: ORM, kwargs) -> Optional[str]:
|
|
65
66
|
if results.index[0] == kwargs["name"]:
|
66
67
|
return "object-with-same-name-exists"
|
67
68
|
else:
|
69
|
+
s = "" if results.shape[0] == 1 else "s"
|
70
|
+
it = "it" if results.shape[0] == 1 else "one of them"
|
68
71
|
msg = (
|
69
|
-
"
|
70
|
-
" them?"
|
72
|
+
f"record{s} with similar name{s} exist! did you mean to load {it}?"
|
71
73
|
)
|
72
74
|
if IPYTHON:
|
73
75
|
from IPython.display import display
|
@@ -79,7 +81,7 @@ def suggest_objects_with_same_name(orm: ORM, kwargs) -> Optional[str]:
|
|
79
81
|
return None
|
80
82
|
|
81
83
|
|
82
|
-
def __init__(orm:
|
84
|
+
def __init__(orm: Registry, *args, **kwargs):
|
83
85
|
if not args:
|
84
86
|
validate_required_fields(orm, kwargs)
|
85
87
|
from .dev._settings import settings
|
@@ -97,23 +99,23 @@ def __init__(orm: ORM, *args, **kwargs):
|
|
97
99
|
existing_record = orm.filter(name=kwargs["name"]).one()
|
98
100
|
if existing_record is not None:
|
99
101
|
logger.success(
|
100
|
-
f"
|
102
|
+
f"loaded record with exact same name{version_comment}"
|
101
103
|
)
|
102
104
|
init_self_from_db(orm, existing_record)
|
103
105
|
return None
|
104
|
-
super(
|
106
|
+
super(Registry, orm).__init__(**kwargs)
|
105
107
|
elif len(args) != len(orm._meta.concrete_fields):
|
106
|
-
raise ValueError("
|
108
|
+
raise ValueError("please provide keyword arguments, not plain arguments")
|
107
109
|
else:
|
108
110
|
# object is loaded from DB (**kwargs could be omitted below, I believe)
|
109
|
-
super(
|
111
|
+
super(Registry, orm).__init__(*args, **kwargs)
|
110
112
|
|
111
113
|
|
112
114
|
def view_parents(
|
113
115
|
self,
|
114
116
|
field: Optional[StrField] = None,
|
115
117
|
with_children: bool = False,
|
116
|
-
distance: int =
|
118
|
+
distance: int = 5,
|
117
119
|
):
|
118
120
|
from lamindb.dev._view_parents import view_parents as _view_parents
|
119
121
|
|
@@ -128,14 +130,14 @@ def view_parents(
|
|
128
130
|
|
129
131
|
|
130
132
|
@classmethod # type:ignore
|
131
|
-
@doc_args(
|
132
|
-
def from_values(cls, values: ListLike, field: StrField, **kwargs) -> List["
|
133
|
+
@doc_args(Registry.from_values.__doc__)
|
134
|
+
def from_values(cls, values: ListLike, field: StrField, **kwargs) -> List["Registry"]:
|
133
135
|
"""{}"""
|
134
136
|
if isinstance(field, str):
|
135
137
|
field = getattr(cls, field)
|
136
138
|
if not isinstance(field, Field): # field is DeferredAttribute
|
137
139
|
raise TypeError(
|
138
|
-
"field must be a string or an
|
140
|
+
"field must be a string or an Registry field, e.g., `CellType.name`!"
|
139
141
|
)
|
140
142
|
from_bionty = True if cls.__module__.startswith("lnschema_bionty.") else False
|
141
143
|
return get_or_create_records(
|
@@ -144,7 +146,7 @@ def from_values(cls, values: ListLike, field: StrField, **kwargs) -> List["ORM"]
|
|
144
146
|
|
145
147
|
|
146
148
|
# From: https://stackoverflow.com/a/37648265
|
147
|
-
def
|
149
|
+
def _order_query_set_by_ids(queryset: QuerySet, ids: Iterable):
|
148
150
|
from django.db.models import Case, When
|
149
151
|
|
150
152
|
preserved = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(ids)])
|
@@ -228,13 +230,13 @@ def _search(
|
|
228
230
|
result["__ratio__"] = result.pop("__ratio__")
|
229
231
|
|
230
232
|
if return_queryset:
|
231
|
-
return
|
233
|
+
return _order_query_set_by_ids(query_set, result.reset_index()["id"])
|
232
234
|
else:
|
233
235
|
return result.fillna("")
|
234
236
|
|
235
237
|
|
236
238
|
@classmethod # type: ignore
|
237
|
-
@doc_args(
|
239
|
+
@doc_args(Registry.search.__doc__)
|
238
240
|
def search(
|
239
241
|
cls,
|
240
242
|
string: str,
|
@@ -260,7 +262,10 @@ def search(
|
|
260
262
|
def _lookup(cls, field: Optional[StrField] = None) -> NamedTuple:
|
261
263
|
"""{}"""
|
262
264
|
if field is None:
|
263
|
-
|
265
|
+
if cls._meta.model.__name__ == "User":
|
266
|
+
field = cls._meta.get_field("handle").name
|
267
|
+
else:
|
268
|
+
field = get_default_str_field(cls)
|
264
269
|
if not isinstance(field, str):
|
265
270
|
field = field.field.name
|
266
271
|
|
@@ -276,131 +281,12 @@ def _lookup(cls, field: Optional[StrField] = None) -> NamedTuple:
|
|
276
281
|
|
277
282
|
|
278
283
|
@classmethod # type: ignore
|
279
|
-
@doc_args(
|
284
|
+
@doc_args(Registry.lookup.__doc__)
|
280
285
|
def lookup(cls, field: Optional[StrField] = None) -> NamedTuple:
|
281
286
|
"""{}"""
|
282
287
|
return _lookup(cls=cls, field=field)
|
283
288
|
|
284
289
|
|
285
|
-
def _inspect(
|
286
|
-
cls,
|
287
|
-
identifiers: ListLike,
|
288
|
-
field: StrField,
|
289
|
-
*,
|
290
|
-
case_sensitive: bool = False,
|
291
|
-
inspect_synonyms: bool = True,
|
292
|
-
return_df: bool = False,
|
293
|
-
logging: bool = True,
|
294
|
-
**kwargs,
|
295
|
-
) -> Union["pd.DataFrame", Dict[str, List[str]]]:
|
296
|
-
"""{}"""
|
297
|
-
from lamin_utils._inspect import inspect
|
298
|
-
|
299
|
-
if not isinstance(field, str):
|
300
|
-
field = field.field.name
|
301
|
-
|
302
|
-
cls = cls.model if isinstance(cls, QuerySet) else cls
|
303
|
-
|
304
|
-
return inspect(
|
305
|
-
df=_filter_df_based_on_species(orm=cls, species=kwargs.get("species")),
|
306
|
-
identifiers=identifiers,
|
307
|
-
field=str(field),
|
308
|
-
case_sensitive=case_sensitive,
|
309
|
-
inspect_synonyms=inspect_synonyms,
|
310
|
-
return_df=return_df,
|
311
|
-
logging=logging,
|
312
|
-
)
|
313
|
-
|
314
|
-
|
315
|
-
@classmethod # type: ignore
|
316
|
-
@doc_args(ORM.inspect.__doc__)
|
317
|
-
def inspect(
|
318
|
-
cls,
|
319
|
-
identifiers: ListLike,
|
320
|
-
field: StrField,
|
321
|
-
*,
|
322
|
-
case_sensitive: bool = False,
|
323
|
-
inspect_synonyms: bool = True,
|
324
|
-
return_df: bool = False,
|
325
|
-
logging: bool = True,
|
326
|
-
**kwargs,
|
327
|
-
) -> Union["pd.DataFrame", Dict[str, List[str]]]:
|
328
|
-
"""{}"""
|
329
|
-
return _inspect(
|
330
|
-
cls=cls,
|
331
|
-
identifiers=identifiers,
|
332
|
-
field=field,
|
333
|
-
case_sensitive=case_sensitive,
|
334
|
-
inspect_synonyms=inspect_synonyms,
|
335
|
-
return_df=return_df,
|
336
|
-
logging=logging,
|
337
|
-
**kwargs,
|
338
|
-
)
|
339
|
-
|
340
|
-
|
341
|
-
def _map_synonyms(
|
342
|
-
cls,
|
343
|
-
synonyms: Iterable,
|
344
|
-
*,
|
345
|
-
return_mapper: bool = False,
|
346
|
-
case_sensitive: bool = False,
|
347
|
-
keep: Literal["first", "last", False] = "first",
|
348
|
-
synonyms_field: str = "synonyms",
|
349
|
-
field: Optional[str] = None,
|
350
|
-
**kwargs,
|
351
|
-
) -> Union[List[str], Dict[str, str]]:
|
352
|
-
"""{}"""
|
353
|
-
from lamin_utils._map_synonyms import map_synonyms
|
354
|
-
|
355
|
-
if field is None:
|
356
|
-
field = get_default_str_field(cls)
|
357
|
-
if not isinstance(field, str):
|
358
|
-
field = field.field.name
|
359
|
-
|
360
|
-
cls = cls.model if isinstance(cls, QuerySet) else cls
|
361
|
-
|
362
|
-
try:
|
363
|
-
cls._meta.get_field(synonyms_field)
|
364
|
-
df = _filter_df_based_on_species(orm=cls, species=kwargs.get("species"))
|
365
|
-
except FieldDoesNotExist:
|
366
|
-
df = pd.DataFrame()
|
367
|
-
return map_synonyms(
|
368
|
-
df=df,
|
369
|
-
identifiers=synonyms,
|
370
|
-
field=field,
|
371
|
-
return_mapper=return_mapper,
|
372
|
-
case_sensitive=case_sensitive,
|
373
|
-
keep=keep,
|
374
|
-
synonyms_field=synonyms_field,
|
375
|
-
)
|
376
|
-
|
377
|
-
|
378
|
-
@classmethod # type: ignore
|
379
|
-
@doc_args(ORM.map_synonyms.__doc__)
|
380
|
-
def map_synonyms(
|
381
|
-
cls,
|
382
|
-
synonyms: Iterable,
|
383
|
-
*,
|
384
|
-
return_mapper: bool = False,
|
385
|
-
case_sensitive: bool = False,
|
386
|
-
keep: Literal["first", "last", False] = "first",
|
387
|
-
synonyms_field: str = "synonyms",
|
388
|
-
field: Optional[str] = None,
|
389
|
-
**kwargs,
|
390
|
-
) -> Union[List[str], Dict[str, str]]:
|
391
|
-
"""{}"""
|
392
|
-
return _map_synonyms(
|
393
|
-
cls=cls,
|
394
|
-
synonyms=synonyms,
|
395
|
-
return_mapper=return_mapper,
|
396
|
-
case_sensitive=case_sensitive,
|
397
|
-
keep=keep,
|
398
|
-
synonyms_field=synonyms_field,
|
399
|
-
field=field,
|
400
|
-
**kwargs,
|
401
|
-
)
|
402
|
-
|
403
|
-
|
404
290
|
def describe(self):
|
405
291
|
model_name = colors.green(self.__class__.__name__)
|
406
292
|
msg = ""
|
@@ -433,7 +319,12 @@ def describe(self):
|
|
433
319
|
|
434
320
|
# Display Provenance
|
435
321
|
# display line by line the foreign key fields
|
436
|
-
emojis = {
|
322
|
+
emojis = {
|
323
|
+
"storage": "🗃️",
|
324
|
+
"created_by": "👤",
|
325
|
+
"transform": _transform_emoji(self.transform),
|
326
|
+
"run": "🚗",
|
327
|
+
}
|
437
328
|
if len(foreign_key_fields) > 0:
|
438
329
|
record_msg = f"{model_name}({''.join([f'{i}={self.__getattribute__(i)}, ' for i in direct_fields])})" # noqa
|
439
330
|
msg += f"{record_msg.rstrip(', )')})\n\n"
|
@@ -448,7 +339,7 @@ def describe(self):
|
|
448
339
|
msg += related_msg
|
449
340
|
# input of
|
450
341
|
if self.input_of.exists():
|
451
|
-
values = [
|
342
|
+
values = [format_field_value(i.run_at) for i in self.input_of.all()]
|
452
343
|
msg += f"⬇️ input_of ({colors.italic('core.Run')}): {values}\n "
|
453
344
|
msg = msg.rstrip(" ")
|
454
345
|
|
@@ -462,21 +353,19 @@ def describe(self):
|
|
462
353
|
# Display Features by slot
|
463
354
|
msg += f"{colors.green('Features')}:\n"
|
464
355
|
# var
|
465
|
-
feature_sets = self.feature_sets.exclude(
|
356
|
+
feature_sets = self.feature_sets.exclude(registry="core.Feature")
|
466
357
|
if feature_sets.exists():
|
467
358
|
for feature_set in feature_sets.all():
|
468
|
-
key_split = feature_set.
|
469
|
-
if len(key_split)
|
359
|
+
key_split = feature_set.registry.split(".")
|
360
|
+
if len(key_split) == 3:
|
470
361
|
logger.warning(
|
471
|
-
"
|
472
|
-
" 'bionty.Gene
|
362
|
+
"you have a legacy entry in feature_set.field, should be just"
|
363
|
+
" 'bionty.Gene'"
|
473
364
|
)
|
474
365
|
orm_name_with_schema = f"{key_split[0]}.{key_split[1]}"
|
475
|
-
field_name =
|
366
|
+
field_name = "id"
|
476
367
|
related_name = feature_sets_related_models.get(orm_name_with_schema)
|
477
|
-
values = (
|
478
|
-
feature_set.__getattribute__(related_name).all()[:5].list(field_name)
|
479
|
-
)
|
368
|
+
values = feature_set.__getattribute__(related_name).all()[:5].list("id")
|
480
369
|
slots = self.feature_sets.through.objects.filter(
|
481
370
|
file=self, feature_set=feature_set
|
482
371
|
).list("slot")
|
@@ -491,7 +380,7 @@ def describe(self):
|
|
491
380
|
|
492
381
|
# obs
|
493
382
|
# Feature, combine all features into one dataframe
|
494
|
-
feature_sets = self.feature_sets.filter(
|
383
|
+
feature_sets = self.feature_sets.filter(registry="core.Feature").all()
|
495
384
|
if feature_sets.exists():
|
496
385
|
features_df = create_features_df(
|
497
386
|
file=self, feature_sets=feature_sets.all(), exclude=True
|
@@ -502,7 +391,7 @@ def describe(self):
|
|
502
391
|
slot += " (metadata)"
|
503
392
|
msg += f" 🗺️ {colors.bold(slot)}:\n"
|
504
393
|
for _, row in df_slot.iterrows():
|
505
|
-
labels = self.
|
394
|
+
labels = self.get_labels(row["name"], mute=True)
|
506
395
|
indent = ""
|
507
396
|
if isinstance(labels, dict):
|
508
397
|
msg += f" 🔗 {row['name']} ({row.registries})\n"
|
@@ -523,39 +412,12 @@ def describe(self):
|
|
523
412
|
msg = msg.rstrip("\n")
|
524
413
|
msg = msg.rstrip("Features:")
|
525
414
|
verbosity = settings.verbosity
|
526
|
-
settings.verbosity =
|
415
|
+
settings.verbosity = 3
|
527
416
|
logger.info(msg)
|
528
417
|
settings.verbosity = verbosity
|
529
418
|
|
530
419
|
|
531
|
-
def
|
532
|
-
try:
|
533
|
-
self.add_synonym(value, save=False)
|
534
|
-
except NotImplementedError:
|
535
|
-
pass
|
536
|
-
self.abbr = value
|
537
|
-
if not self._state.adding:
|
538
|
-
self.save()
|
539
|
-
|
540
|
-
|
541
|
-
def _filter_df_based_on_species(
|
542
|
-
orm: Union[ORM, QuerySet], species: Optional[Union[str, ORM]] = None
|
543
|
-
):
|
544
|
-
import pandas as pd
|
545
|
-
|
546
|
-
records = orm.all() if isinstance(orm, QuerySet) else orm.objects.all()
|
547
|
-
if _has_species_field(orm):
|
548
|
-
# here, we can safely import lnschema_bionty
|
549
|
-
from lnschema_bionty._bionty import create_or_get_species_record
|
550
|
-
|
551
|
-
species_record = create_or_get_species_record(species=species, orm=orm)
|
552
|
-
if species_record is not None:
|
553
|
-
records = records.filter(species__name=species_record.name)
|
554
|
-
|
555
|
-
return pd.DataFrame.from_records(records.values())
|
556
|
-
|
557
|
-
|
558
|
-
def get_default_str_field(orm: Union[ORM, QuerySet, Manager]) -> str:
|
420
|
+
def get_default_str_field(orm: Union[Registry, QuerySet, Manager]) -> str:
|
559
421
|
"""Get the 1st char or text field from the orm."""
|
560
422
|
if isinstance(orm, (QuerySet, Manager)):
|
561
423
|
orm = orm.model
|
@@ -583,115 +445,12 @@ def get_default_str_field(orm: Union[ORM, QuerySet, Manager]) -> str:
|
|
583
445
|
return field.name
|
584
446
|
|
585
447
|
|
586
|
-
def _add_or_remove_synonyms(
|
587
|
-
synonym: Union[str, Iterable],
|
588
|
-
record: ORM,
|
589
|
-
action: Literal["add", "remove"],
|
590
|
-
force: bool = False,
|
591
|
-
save: Optional[bool] = None,
|
592
|
-
):
|
593
|
-
"""Add or remove synonyms."""
|
594
|
-
|
595
|
-
def check_synonyms_in_all_records(synonyms: Set[str], record: ORM):
|
596
|
-
"""Errors if input synonym is associated with other records in the DB."""
|
597
|
-
import pandas as pd
|
598
|
-
from IPython.display import display
|
599
|
-
|
600
|
-
syns_all = (
|
601
|
-
record.__class__.objects.exclude(synonyms="").exclude(synonyms=None).all()
|
602
|
-
)
|
603
|
-
if len(syns_all) == 0:
|
604
|
-
return
|
605
|
-
df = pd.DataFrame(syns_all.values())
|
606
|
-
df["synonyms"] = df["synonyms"].str.split("|")
|
607
|
-
df = df.explode("synonyms")
|
608
|
-
matches_df = df[(df["synonyms"].isin(synonyms)) & (df["id"] != record.id)]
|
609
|
-
if matches_df.shape[0] > 0:
|
610
|
-
records_df = pd.DataFrame(syns_all.filter(id__in=matches_df["id"]).values())
|
611
|
-
logger.error(
|
612
|
-
f"Input synonyms {matches_df['synonyms'].unique()} already associated"
|
613
|
-
" with the following records:\n(Pass `force=True` to ignore this error)"
|
614
|
-
)
|
615
|
-
display(records_df)
|
616
|
-
raise SystemExit(AssertionError)
|
617
|
-
|
618
|
-
# passed synonyms
|
619
|
-
if isinstance(synonym, str):
|
620
|
-
syn_new_set = set([synonym])
|
621
|
-
else:
|
622
|
-
syn_new_set = set(synonym)
|
623
|
-
# nothing happens when passing an empty string or list
|
624
|
-
if len(syn_new_set) == 0:
|
625
|
-
return
|
626
|
-
# because we use | as the separator
|
627
|
-
if any(["|" in i for i in syn_new_set]):
|
628
|
-
raise AssertionError("A synonym can't contain '|'!")
|
629
|
-
|
630
|
-
# existing synonyms
|
631
|
-
syns_exist = record.synonyms
|
632
|
-
if syns_exist is None or len(syns_exist) == 0:
|
633
|
-
syns_exist_set = set()
|
634
|
-
else:
|
635
|
-
syns_exist_set = set(syns_exist.split("|"))
|
636
|
-
|
637
|
-
if action == "add":
|
638
|
-
if not force:
|
639
|
-
check_synonyms_in_all_records(syn_new_set, record)
|
640
|
-
syns_exist_set.update(syn_new_set)
|
641
|
-
elif action == "remove":
|
642
|
-
syns_exist_set = syns_exist_set.difference(syn_new_set)
|
643
|
-
|
644
|
-
if len(syns_exist_set) == 0:
|
645
|
-
syns_str = None
|
646
|
-
else:
|
647
|
-
syns_str = "|".join(syns_exist_set)
|
648
|
-
|
649
|
-
record.synonyms = syns_str
|
650
|
-
|
651
|
-
if save is None:
|
652
|
-
# if record is already in DB, save the changes to DB
|
653
|
-
save = not record._state.adding
|
654
|
-
if save:
|
655
|
-
record.save()
|
656
|
-
|
657
|
-
|
658
|
-
def _check_synonyms_field_exist(record: ORM):
|
659
|
-
try:
|
660
|
-
record.__getattribute__("synonyms")
|
661
|
-
except AttributeError:
|
662
|
-
raise NotImplementedError(
|
663
|
-
f"No synonyms field found in table {record.__class__.__name__}!"
|
664
|
-
)
|
665
|
-
|
666
|
-
|
667
|
-
def add_synonym(
|
668
|
-
self,
|
669
|
-
synonym: Union[str, ListLike],
|
670
|
-
force: bool = False,
|
671
|
-
save: Optional[bool] = None,
|
672
|
-
):
|
673
|
-
_check_synonyms_field_exist(self)
|
674
|
-
_add_or_remove_synonyms(
|
675
|
-
synonym=synonym, record=self, force=force, action="add", save=save
|
676
|
-
)
|
677
|
-
|
678
|
-
|
679
|
-
def remove_synonym(self, synonym: Union[str, ListLike]):
|
680
|
-
_check_synonyms_field_exist(self)
|
681
|
-
_add_or_remove_synonyms(synonym=synonym, record=self, action="remove")
|
682
|
-
|
683
|
-
|
684
448
|
METHOD_NAMES = [
|
685
449
|
"__init__",
|
686
450
|
"search",
|
687
451
|
"lookup",
|
688
|
-
"map_synonyms",
|
689
|
-
"inspect",
|
690
|
-
"add_synonym",
|
691
|
-
"remove_synonym",
|
692
452
|
"from_values",
|
693
453
|
"describe",
|
694
|
-
"set_abbr",
|
695
454
|
"view_parents",
|
696
455
|
]
|
697
456
|
|
@@ -699,13 +458,13 @@ if _TESTING: # type: ignore
|
|
699
458
|
from inspect import signature
|
700
459
|
|
701
460
|
SIGS = {
|
702
|
-
name: signature(getattr(
|
461
|
+
name: signature(getattr(Registry, name))
|
703
462
|
for name in METHOD_NAMES
|
704
463
|
if not name.startswith("__")
|
705
464
|
}
|
706
465
|
|
707
466
|
for name in METHOD_NAMES:
|
708
|
-
attach_func_to_class_method(name,
|
467
|
+
attach_func_to_class_method(name, Registry, globals())
|
709
468
|
|
710
469
|
|
711
470
|
@classmethod # type: ignore
|
@@ -722,7 +481,7 @@ def __get_name_with_schema__(cls) -> str:
|
|
722
481
|
|
723
482
|
|
724
483
|
def select_backward(cls, **expressions):
|
725
|
-
logger.warning("select() is deprecated!
|
484
|
+
logger.warning("select() is deprecated! please use: Registry.filter()")
|
726
485
|
return cls.filter(**expressions)
|
727
486
|
|
728
487
|
|
@@ -731,6 +490,6 @@ def select(cls, **expressions):
|
|
731
490
|
return select_backward(cls, **expressions)
|
732
491
|
|
733
492
|
|
734
|
-
setattr(
|
735
|
-
setattr(
|
736
|
-
setattr(
|
493
|
+
setattr(Registry, "__get_schema_name__", __get_schema_name__)
|
494
|
+
setattr(Registry, "__get_name_with_schema__", __get_name_with_schema__)
|
495
|
+
setattr(Registry, "select", select) # backward compat
|
lamindb/_save.py
CHANGED
@@ -9,7 +9,7 @@ from typing import Iterable, List, Optional, Tuple, Union, overload # noqa
|
|
9
9
|
import lamindb_setup
|
10
10
|
from django.db import transaction
|
11
11
|
from lamin_utils import logger
|
12
|
-
from lnschema_core.models import
|
12
|
+
from lnschema_core.models import File, Registry
|
13
13
|
|
14
14
|
from lamindb.dev.storage import store_object
|
15
15
|
from lamindb.dev.storage.file import (
|
@@ -26,8 +26,8 @@ except ImportError:
|
|
26
26
|
raise ImportError("Please install zarr: pip install zarr")
|
27
27
|
|
28
28
|
|
29
|
-
def save(records: Iterable[
|
30
|
-
"""Bulk save to
|
29
|
+
def save(records: Iterable[Registry], **kwargs) -> None: # type: ignore
|
30
|
+
"""Bulk save to registries & storage.
|
31
31
|
|
32
32
|
Note:
|
33
33
|
|
@@ -36,10 +36,10 @@ def save(records: Iterable[ORM], **kwargs) -> None: # type: ignore
|
|
36
36
|
Warning:
|
37
37
|
|
38
38
|
It neither automatically creates related records nor updates existing records!
|
39
|
-
Use ``
|
39
|
+
Use ``Registry.save()`` for these use cases.
|
40
40
|
|
41
41
|
Args:
|
42
|
-
records: One or multiple ``
|
42
|
+
records: One or multiple ``Registry`` objects.
|
43
43
|
|
44
44
|
Examples:
|
45
45
|
|
@@ -63,7 +63,7 @@ def save(records: Iterable[ORM], **kwargs) -> None: # type: ignore
|
|
63
63
|
"""
|
64
64
|
if isinstance(records, Iterable):
|
65
65
|
records = set(records)
|
66
|
-
elif isinstance(records,
|
66
|
+
elif isinstance(records, Registry):
|
67
67
|
records = {records}
|
68
68
|
|
69
69
|
# we're distinguishing between files and non-files
|
@@ -86,11 +86,11 @@ def save(records: Iterable[ORM], **kwargs) -> None: # type: ignore
|
|
86
86
|
):
|
87
87
|
# save the record with parents one by one
|
88
88
|
logger.warning(
|
89
|
-
"
|
89
|
+
"now recursing through parents: "
|
90
90
|
"this only happens once, but is much slower than bulk saving"
|
91
91
|
)
|
92
92
|
logger.hint(
|
93
|
-
"
|
93
|
+
"you can switch this off via: lb.settings.auto_save_parents = False"
|
94
94
|
)
|
95
95
|
for record in non_files_with_parents:
|
96
96
|
record._save_ontology_parents()
|
@@ -107,7 +107,7 @@ def save(records: Iterable[ORM], **kwargs) -> None: # type: ignore
|
|
107
107
|
return None
|
108
108
|
|
109
109
|
|
110
|
-
def bulk_create(records: Iterable[
|
110
|
+
def bulk_create(records: Iterable[Registry]):
|
111
111
|
records_by_orm = defaultdict(list)
|
112
112
|
for record in records:
|
113
113
|
records_by_orm[record.__class__].append(record)
|
@@ -123,7 +123,7 @@ def check_and_attempt_upload(file: File) -> Optional[Exception]:
|
|
123
123
|
try:
|
124
124
|
upload_data_object(file)
|
125
125
|
except Exception as exception:
|
126
|
-
logger.warning(f"
|
126
|
+
logger.warning(f"could not upload file: {file}")
|
127
127
|
return exception
|
128
128
|
# copies (if ob-disk) or moves the temporary file (if in-memory) to the cache
|
129
129
|
copy_or_move_to_cache(file)
|
@@ -173,7 +173,7 @@ def check_and_attempt_clearing(file: File) -> Optional[Exception]:
|
|
173
173
|
if file._clear_storagekey is not None:
|
174
174
|
delete_storage_using_key(file, file._clear_storagekey)
|
175
175
|
logger.success(
|
176
|
-
f"
|
176
|
+
f"deleted stale object at storage key {file._clear_storagekey}"
|
177
177
|
)
|
178
178
|
file._clear_storagekey = None
|
179
179
|
except Exception as exception:
|
@@ -238,15 +238,16 @@ def upload_data_object(file) -> None:
|
|
238
238
|
"""Store and add file and its linked entries."""
|
239
239
|
# do NOT hand-craft the storage key!
|
240
240
|
file_storage_key = auto_storage_key_from_file(file)
|
241
|
+
msg = f"storing file '{file.id}' with key '{file_storage_key}'"
|
241
242
|
if hasattr(file, "_to_store") and file._to_store and file.suffix != ".zarr":
|
242
|
-
logger.
|
243
|
+
logger.save(msg)
|
243
244
|
store_object(file._local_filepath, file_storage_key)
|
244
245
|
elif (
|
245
246
|
file.suffix in {".zarr", ".zrad"}
|
246
247
|
and hasattr(file, "_memory_rep")
|
247
248
|
and file._memory_rep is not None
|
248
249
|
):
|
249
|
-
logger.
|
250
|
+
logger.save(msg)
|
250
251
|
storagepath = lamindb_setup.settings.storage.key_to_filepath(file_storage_key)
|
251
252
|
print_progress = partial(
|
252
253
|
print_hook, filepath=file_storage_key, action="uploading"
|