lamindb 0.48a2__py3-none-any.whl → 0.48.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/_manager.py CHANGED
@@ -8,38 +8,45 @@ class Manager(models.Manager):
8
8
 
9
9
  See Also:
10
10
 
11
+ :class:`lamindb.dev.QuerySet`
11
12
  `django Manager <https://docs.djangoproject.com/en/4.2/topics/db/managers/>`__
12
13
 
13
14
  Examples:
14
15
 
15
- >>> ln.save(ln.Tag.from_values(["Tag1", "Tag2", "Tag3"], field="name"))
16
- >>> tags = ln.Tag.select(name__icontains = "tag").all()
17
- >>> ln.Project(name="Project1").save()
18
- >>> project = ln.Project.select(name="Project1").one()
19
- >>> project.tags.set(tags)
20
- >>> manager = project.tags
16
+ >>> ln.save(ln.Label.from_values(["Label1", "Label2", "Label3"], field="name"))
17
+ >>> labels = ln.Label.select(name__icontains = "label").all()
18
+ >>> ln.Label(name="Label1").save()
19
+ >>> label = ln.Label.select(name="Label1").one()
20
+ >>> label.parents.set(labels)
21
+ >>> manager = label.parents
22
+ >>> manager.df()
21
23
  """
22
24
 
23
25
  def list(self, field: Optional[str] = None):
24
26
  """Populate a list with the results.
25
27
 
26
28
  Examples:
27
- >>> ln.save(ln.Tag.from_values(["Tag1", "Tag2", "Tag3"], field="name"))
28
- >>> tags = ln.Tag.select(name__icontains = "tag").all()
29
- >>> ln.Project(name="Project1").save()
30
- >>> project = ln.Project.select(name="Project1").one()
31
- >>> project.tags.set(tags)
32
- >>> project.tags.list()
33
- [Tag(id=sFMcPepC, name=Tag1, updated_at=2023-07-19 19:45:17, created_by_id=DzTjkKse), # noqa
34
- Tag(id=2SscQvsM, name=Tag2, updated_at=2023-07-19 19:45:17, created_by_id=DzTjkKse), # noqa
35
- Tag(id=lecV87vi, name=Tag3, updated_at=2023-07-19 19:45:17, created_by_id=DzTjkKse)] # noqa
36
- >>> project.tags.list("name")
37
- ['Tag1', 'Tag2', 'Tag3']
29
+ >>> ln.save(ln.Label.from_values(["Label1", "Label2", "Label3"], field="name"))
30
+ >>> labels = ln.Label.select(name__icontains = "label").all()
31
+ >>> ln.Label(name="Label1").save()
32
+ >>> label = ln.Label.select(name="Label1").one()
33
+ >>> label.parents.set(labels)
34
+ >>> label.parents.list()
35
+ [Label(id=sFMcPepC, name=Label1, updated_at=2023-07-19 19:45:17, created_by_id=DzTjkKse), # noqa
36
+ Label(id=2SscQvsM, name=Label2, updated_at=2023-07-19 19:45:17, created_by_id=DzTjkKse), # noqa
37
+ Label(id=lecV87vi, name=Label3, updated_at=2023-07-19 19:45:17, created_by_id=DzTjkKse)] # noqa
38
+ >>> label.parents.list("name")
39
+ ['Label1', 'Label2', 'Label3']
38
40
  """
39
41
  if field is None:
40
42
  return [item for item in self.all()]
41
43
  else:
42
44
  return [item for item in self.values_list(field, flat=True)]
43
45
 
46
+ def df(self, **kwargs):
47
+ """Convert to DataFrame."""
48
+ return self.all().df(**kwargs)
49
+
44
50
 
45
51
  setattr(models.Manager, "list", Manager.list)
52
+ setattr(models.Manager, "df", Manager.df)
lamindb/_orm.py CHANGED
@@ -5,9 +5,9 @@ import pandas as pd
5
5
  from django.core.exceptions import FieldDoesNotExist
6
6
  from django.db.models import Manager, QuerySet
7
7
  from django.db.models.query_utils import DeferredAttribute as Field
8
- from lamin_logger import logger
9
- from lamin_logger._lookup import Lookup
10
- from lamin_logger._search import search as base_search
8
+ from lamin_utils import colors, logger
9
+ from lamin_utils._lookup import Lookup
10
+ from lamin_utils._search import search as base_search
11
11
  from lamindb_setup.dev._docs import doc_args
12
12
  from lnschema_core import ORM
13
13
  from lnschema_core.models import format_datetime
@@ -16,7 +16,9 @@ from lnschema_core.types import ListLike, StrField
16
16
  from lamindb.dev.utils import attach_func_to_class_method
17
17
 
18
18
  from . import _TESTING
19
+ from ._feature_manager import create_features_df
19
20
  from ._from_values import _has_species_field, get_or_create_records
21
+ from .dev._settings import settings
20
22
 
21
23
  IPYTHON = getattr(builtins, "__IPYTHON__", False)
22
24
 
@@ -105,7 +107,12 @@ def __init__(orm: ORM, *args, **kwargs):
105
107
  super(ORM, orm).__init__(*args, **kwargs)
106
108
 
107
109
 
108
- def view_parents(self, field: Optional[StrField] = None, distance: int = 100):
110
+ def view_parents(
111
+ self,
112
+ field: Optional[StrField] = None,
113
+ with_children: bool = False,
114
+ distance: int = 100,
115
+ ):
109
116
  from lamindb.dev._view_parents import view_parents as _view_parents
110
117
 
111
118
  if field is None:
@@ -113,7 +120,9 @@ def view_parents(self, field: Optional[StrField] = None, distance: int = 100):
113
120
  if not isinstance(field, str):
114
121
  field = field.field.name
115
122
 
116
- return _view_parents(record=self, field=field, distance=distance)
123
+ return _view_parents(
124
+ record=self, field=field, with_children=with_children, distance=distance
125
+ )
117
126
 
118
127
 
119
128
  @classmethod # type:ignore
@@ -281,7 +290,7 @@ def _inspect(
281
290
  **kwargs,
282
291
  ) -> Union["pd.DataFrame", Dict[str, List[str]]]:
283
292
  """{}"""
284
- from lamin_logger._inspect import inspect
293
+ from lamin_utils._inspect import inspect
285
294
 
286
295
  if not isinstance(field, str):
287
296
  field = field.field.name
@@ -337,7 +346,7 @@ def _map_synonyms(
337
346
  **kwargs,
338
347
  ) -> Union[List[str], Dict[str, str]]:
339
348
  """{}"""
340
- from lamin_logger._map_synonyms import map_synonyms
349
+ from lamin_utils._map_synonyms import map_synonyms
341
350
 
342
351
  if field is None:
343
352
  field = get_default_str_field(cls)
@@ -388,9 +397,40 @@ def map_synonyms(
388
397
  )
389
398
 
390
399
 
400
+ def _labels_with_feature_names(labels: Union[QuerySet, Manager]) -> Dict:
401
+ from django.db.models import F
402
+
403
+ df = labels.annotate(feature_name=F("feature__name")).df()
404
+ return df.groupby("feature_name")["name"].apply(list).to_dict()
405
+
406
+
391
407
  def describe(self):
392
- model_name = self.__class__.__name__
408
+ model_name = colors.green(self.__class__.__name__)
393
409
  msg = ""
410
+
411
+ def dict_related_model_to_related_name(orm):
412
+ d: Dict = {
413
+ f"{i.related_model.__get_schema_name__()}.{i.related_model.__name__}": (
414
+ i.related_name
415
+ )
416
+ for i in orm._meta.related_objects
417
+ if i.related_name is not None
418
+ }
419
+ d.update(
420
+ {
421
+ f"{i.related_model.__get_schema_name__()}.{i.related_model.__name__}": (
422
+ i.name
423
+ )
424
+ for i in orm._meta.many_to_many
425
+ if i.name is not None
426
+ }
427
+ )
428
+
429
+ return d
430
+
431
+ file_related_models = dict_related_model_to_related_name(self)
432
+
433
+ # Display the file record
394
434
  fields = self._meta.fields
395
435
  direct_fields = []
396
436
  foreign_key_fields = []
@@ -399,42 +439,126 @@ def describe(self):
399
439
  foreign_key_fields.append(f.name)
400
440
  else:
401
441
  direct_fields.append(f.name)
442
+
443
+ # Display Provenance
402
444
  # display line by line the foreign key fields
445
+ emojis = {"storage": "💾", "created_by": "👤", "transform": "💫", "run": "🚗"}
403
446
  if len(foreign_key_fields) > 0:
404
447
  record_msg = f"{model_name}({''.join([f'{i}={self.__getattribute__(i)}, ' for i in direct_fields])})" # noqa
405
448
  msg += f"{record_msg.rstrip(', )')})\n\n"
406
449
 
407
- msg += "One/Many-to-One:\n "
450
+ msg += f"{colors.green('Provenance')}:\n "
408
451
  related_msg = "".join(
409
- [f"🔗 {i}: {self.__getattribute__(i)}\n " for i in foreign_key_fields]
452
+ [
453
+ f"{emojis.get(i, '📎')} {i}: {self.__getattribute__(i)}\n "
454
+ for i in foreign_key_fields
455
+ ]
410
456
  )
411
457
  msg += related_msg
458
+ # input of
459
+ if self.input_of.exists():
460
+ values = [format_datetime(i.run_at) for i in self.input_of.all()]
461
+ msg += f"⬇️ input_of ({colors.italic('core.Run')}): {values}\n "
412
462
  msg = msg.rstrip(" ")
413
463
 
414
- # display many-to-many relationship objects
415
- # fields in the model definition
416
- related_names = [i.name for i in self._meta.many_to_many]
417
- # fields back linked
418
- related_names += [i.related_name for i in self._meta.related_objects]
419
- msg += "Many-to-Many:\n"
420
- for related_name in related_names:
421
- related_objects = self.__getattribute__(related_name)
422
- count = related_objects.count()
423
- if count > 0:
424
- try:
425
- field = get_default_str_field(related_objects)
426
- except ValueError:
427
- field = "id"
428
- objects_list = list(related_objects.values_list(field, flat=True)[:10])
429
- if field == "created_at":
430
- objects_list = [format_datetime(i) for i in objects_list]
431
- msg_objects = f" 🔗 {related_name} ({count}): {objects_list}\n"
432
- if count > 10:
433
- msg_objects = msg_objects.replace("]", " ... ]")
434
- msg += msg_objects
464
+ if not self.feature_sets.exists():
465
+ print(msg)
466
+ return
467
+ else:
468
+ feature_sets_related_models = dict_related_model_to_related_name(
469
+ self.feature_sets.first()
470
+ )
471
+ # Display Features by slot
472
+ msg += f"{colors.green('Features')}:\n"
473
+ # var
474
+ feature_sets = self.feature_sets.exclude(ref_orm="Feature")
475
+ if feature_sets.exists():
476
+ for feature_set in feature_sets.all():
477
+ key = f"{feature_set.ref_schema}.{feature_set.ref_orm}"
478
+ related_name = feature_sets_related_models.get(key)
479
+ values = (
480
+ feature_set.__getattribute__(related_name)
481
+ .all()[:5]
482
+ .list(feature_set.ref_field)
483
+ )
484
+ slots = self.feature_sets.through.objects.filter(
485
+ file=self, feature_set=feature_set
486
+ ).list("slot")
487
+ for slot in slots:
488
+ if slot == "var":
489
+ slot += " (X)"
490
+ msg += f" 🗺️ {colors.bold(slot)}:\n"
491
+ ref = colors.italic(f"{key}.{feature_set.ref_field}")
492
+ msg += f" 🔗 index ({feature_set.n}, {ref}): {values}\n".replace(
493
+ "]", "...]"
494
+ )
495
+
496
+ # obs
497
+ # ref_orm=Feature, combine all features into one dataframe
498
+ feature_sets = self.feature_sets.filter(ref_orm="Feature").all()
499
+ if feature_sets.exists():
500
+ features_df = create_features_df(
501
+ file=self, feature_sets=feature_sets.all(), exclude=True
502
+ )
503
+ for slot in features_df["slot"].unique():
504
+ df_slot = features_df[features_df.slot == slot]
505
+ if slot == "obs":
506
+ slot += " (metadata)"
507
+ msg += f" 🗺️ {colors.bold(slot)}:\n"
508
+ df_label_index = df_slot[
509
+ (df_slot["labels_orm"] == "Label")
510
+ & (df_slot["labels_schema"] == "core")
511
+ ].index
512
+
513
+ # for labels
514
+ if len(df_label_index) > 0:
515
+ labels_schema = "core"
516
+ labels_orm = "Label"
517
+ key = f"{labels_schema}.{labels_orm}"
518
+ related_name = file_related_models.get(key)
519
+ related_objects = self.__getattribute__(related_name)
520
+ labels = _labels_with_feature_names(related_objects)
521
+ msg_objects = ""
522
+ for k, v in labels.items():
523
+ msg_objects_k = (
524
+ f" 🔗 {k} ({len(v)}, {colors.italic(key)}): {v[:5]}\n"
525
+ )
526
+ if len(v) > 5:
527
+ msg_objects_k = msg_objects_k.replace("]", " ... ]")
528
+ msg_objects += msg_objects_k
529
+ msg += msg_objects
530
+
531
+ # for non-labels
532
+ nonlabel_index = df_slot.index.difference(df_label_index)
533
+ if len(nonlabel_index) == 0:
534
+ continue
535
+ df_nonlabels = df_slot.loc[nonlabel_index]
536
+ df_nonlabels = (
537
+ df_nonlabels.groupby(["labels_schema", "labels_orm"], group_keys=False)[
538
+ "name"
539
+ ]
540
+ .apply(lambda x: "|".join(x))
541
+ .reset_index()
542
+ )
543
+ for _, row in df_nonlabels.iterrows():
544
+ key = f"{row.labels_schema}.{row.labels_orm}"
545
+ related_name = file_related_models.get(key)
546
+ related_objects = self.__getattribute__(related_name)
547
+ count = related_objects.count()
548
+ count_str = f"{count}, {colors.italic(f'{key}')}"
549
+ try:
550
+ field = get_default_str_field(related_objects)
551
+ except ValueError:
552
+ field = "id"
553
+ values = list(related_objects.values_list(field, flat=True)[:5])
554
+ msg_objects = f" 🔗 {row['name']} ({count_str}): {values}\n"
555
+ msg += msg_objects
435
556
  msg = msg.rstrip("\n")
436
- msg = msg.rstrip("Many-to-Many:")
437
- print(msg)
557
+ msg = msg.rstrip("Features:")
558
+ verbosity = settings.verbosity
559
+ settings.verbosity = 2
560
+ logger.info(msg)
561
+ settings.verbosity = verbosity
438
562
 
439
563
 
440
564
  def set_abbr(self, value: str):
lamindb/_queryset.py CHANGED
@@ -36,10 +36,10 @@ class QuerySet(models.QuerySet):
36
36
 
37
37
  Examples:
38
38
 
39
- >>> ln.Tag(name="my tag").save()
40
- >>> queryset = ln.Tag.select(name="my tag")
39
+ >>> ln.Label(name="my label").save()
40
+ >>> queryset = ln.Label.select(name="my label")
41
41
  >>> queryset
42
- <QuerySet [Tag(id=MIeZISeF, name=my tag, updated_at=2023-07-19 19:53:34, created_by_id=DzTjkKse)]> # noqa
42
+ <QuerySet [Label(id=MIeZISeF, name=my label, updated_at=2023-07-19 19:53:34, created_by_id=DzTjkKse)]> # noqa
43
43
  """
44
44
 
45
45
  def df(self, include: Optional[List[str]] = None):
@@ -52,27 +52,27 @@ class QuerySet(models.QuerySet):
52
52
 
53
53
  Args:
54
54
  include: ``Optional[List[str]] = None`` Additional (many-to-many)
55
- fields to include. Takes expressions like ``"tags__name"``
55
+ fields to include. Takes expressions like ``"labels__name"``
56
56
  ``"cell_types__name"``.
57
57
 
58
58
  Examples:
59
59
 
60
- >>> ln.save(ln.Project.from_values(["Project1", "Project2", "Project3"], field="name")) # noqa
61
- >>> ln.Project.select().df()
60
+ >>> ln.save(ln.Label.from_values(["Label1", "Label2", "Label3"], field="name")) # noqa
61
+ >>> ln.Label.select().df()
62
62
  name external_id updated_at created_by_id
63
63
  id
64
- wsCyIq2Z Project1 None 2023-07-19 19:14:08 DzTjkKse
65
- MvpDP8Y3 Project2 None 2023-07-19 19:14:08 DzTjkKse
66
- zKFFabCu Project3 None 2023-07-19 19:14:08 DzTjkKse
67
- >>> project = ln.Project.select(name="Project1").one()
68
- >>> tag = ln.Tag.select(name="benchmark").one()
69
- >>> project.tags.add(tag)
70
- >>> ln.Project.select().df(include=["tags__name", "tags__created_by_id"])
71
- tags__name tags__created_by_id name external_id updated_at created_by_id # noqa
64
+ wsCyIq2Z Label1 None 2023-07-19 19:14:08 DzTjkKse
65
+ MvpDP8Y3 Label2 None 2023-07-19 19:14:08 DzTjkKse
66
+ zKFFabCu Label3 None 2023-07-19 19:14:08 DzTjkKse
67
+ >>> label = ln.Label.select(name="Label1").one()
68
+ >>> label = ln.Label.select(name="benchmark").one()
69
+ >>> label.parents.add(label)
70
+ >>> ln.Label.select().df(include=["labels__name", "labels__created_by_id"])
71
+ labels__name labels__created_by_id name external_id updated_at created_by_id # noqa
72
72
  id
73
- wsCyIq2Z [benchmark] [DzTjkKse] Project1 None 2023-07-19 19:14:08 DzTjkKse # noqa
74
- MvpDP8Y3 None None Project2 None 2023-07-19 19:14:08 DzTjkKse # noqa
75
- zKFFabCu None None Project3 None 2023-07-19 19:14:08 DzTjkKse # noqa
73
+ wsCyIq2Z [benchmark] [DzTjkKse] Label1 None 2023-07-19 19:14:08 DzTjkKse # noqa
74
+ MvpDP8Y3 None None Label2 None 2023-07-19 19:14:08 DzTjkKse # noqa
75
+ zKFFabCu None None Label3 None 2023-07-19 19:14:08 DzTjkKse # noqa
76
76
  """
77
77
  data = self.values()
78
78
  if len(data) > 0:
@@ -100,6 +100,8 @@ class QuerySet(models.QuerySet):
100
100
  df.run_at = format_and_convert_to_local_time(df.run_at)
101
101
  if "id" in df.columns:
102
102
  df = df.set_index("id")
103
+ if len(df) == 0:
104
+ return df
103
105
  if include is not None:
104
106
  if isinstance(include, str):
105
107
  include = [include]
@@ -121,7 +123,7 @@ class QuerySet(models.QuerySet):
121
123
  if field.field.model != ORM
122
124
  else field.field.related_model
123
125
  )
124
- if field.field.model == related_ORM:
126
+ if ORM == related_ORM:
125
127
  left_side_link_model = f"from_{ORM.__name__.lower()}"
126
128
  values_expression = f"to_{ORM.__name__.lower()}__{lookup_str}"
127
129
  else:
@@ -135,7 +137,7 @@ class QuerySet(models.QuerySet):
135
137
  link_groupby = link_df.groupby(left_side_link_model)[
136
138
  values_expression
137
139
  ].apply(list)
138
- df = pd.concat((link_groupby, df), axis=1)
140
+ df = pd.concat((link_groupby, df), axis=1, join="inner")
139
141
  df.rename(columns={values_expression: expression}, inplace=True)
140
142
  return df
141
143
 
@@ -144,14 +146,14 @@ class QuerySet(models.QuerySet):
144
146
 
145
147
  Examples:
146
148
 
147
- >>> ln.save(ln.Project.from_values(["Project1", "Project2", "Project3"], field="name")) # noqa
148
- >>> queryset = ln.Project.select(name__icontains = "project")
149
+ >>> ln.save(ln.Label.from_values(["Label1", "Label2", "Label3"], field="name")) # noqa
150
+ >>> queryset = ln.Label.select(name__icontains = "project")
149
151
  >>> queryset.list()
150
- [Project(id=NAgTZxoo, name=Project1, updated_at=2023-07-19 19:25:48, created_by_id=DzTjkKse), # noqa
151
- Project(id=bnsAgKRC, name=Project2, updated_at=2023-07-19 19:25:48, created_by_id=DzTjkKse), # noqa
152
- Project(id=R8xhAJNE, name=Project3, updated_at=2023-07-19 19:25:48, created_by_id=DzTjkKse)] # noqa
152
+ [Label(id=NAgTZxoo, name=Label1, updated_at=2023-07-19 19:25:48, created_by_id=DzTjkKse), # noqa
153
+ Label(id=bnsAgKRC, name=Label2, updated_at=2023-07-19 19:25:48, created_by_id=DzTjkKse), # noqa
154
+ Label(id=R8xhAJNE, name=Label3, updated_at=2023-07-19 19:25:48, created_by_id=DzTjkKse)] # noqa
153
155
  >>> queryset.list("name")
154
- ['Project1', 'Project2', 'Project3']
156
+ ['Label1', 'Label2', 'Label3']
155
157
  """
156
158
  if field is None:
157
159
  return [item for item in self]
@@ -162,10 +164,10 @@ class QuerySet(models.QuerySet):
162
164
  """If non-empty, the first result in the query set, otherwise None.
163
165
 
164
166
  Examples:
165
- >>> ln.save(ln.Project.from_values(["Project1", "Project2", "Project3"], field="name")) # noqa
166
- >>> queryset = ln.Project.select(name__icontains = "project")
167
+ >>> ln.save(ln.Label.from_values(["Label1", "Label2", "Label3"], field="name")) # noqa
168
+ >>> queryset = ln.Label.select(name__icontains = "project")
167
169
  >>> queryset.first()
168
- Project(id=NAgTZxoo, name=Project1, updated_at=2023-07-19 19:25:48, created_by_id=DzTjkKse) # noqa
170
+ Label(id=NAgTZxoo, name=Label1, updated_at=2023-07-19 19:25:48, created_by_id=DzTjkKse) # noqa
169
171
  """
170
172
  if len(self) == 0:
171
173
  return None
@@ -175,9 +177,9 @@ class QuerySet(models.QuerySet):
175
177
  """Exactly one result. Throws error if there are more or none.
176
178
 
177
179
  Examples:
178
- >>> ln.Tag(name="benchmark").save()
179
- >>> ln.Tag.select(name="benchmark").one()
180
- Tag(id=gznl0GZk, name=benchmark, updated_at=2023-07-19 19:39:01, created_by_id=DzTjkKse) # noqa
180
+ >>> ln.Label(name="benchmark").save()
181
+ >>> ln.Label.select(name="benchmark").one()
182
+ Label(id=gznl0GZk, name=benchmark, updated_at=2023-07-19 19:39:01, created_by_id=DzTjkKse) # noqa
181
183
  """
182
184
  if len(self) == 0:
183
185
  raise NoResultFound
@@ -190,10 +192,10 @@ class QuerySet(models.QuerySet):
190
192
  """At most one result. Returns it if there is one, otherwise returns None.
191
193
 
192
194
  Examples:
193
- >>> ln.Tag(name="benchmark").save()
194
- >>> ln.Tag.select(name="benchmark").one_or_none()
195
- Tag(id=gznl0GZk, name=benchmark, updated_at=2023-07-19 19:39:01, created_by_id=DzTjkKse) # noqa
196
- >>> ln.Tag.select(name="non existing tag").one_or_none()
195
+ >>> ln.Label(name="benchmark").save()
196
+ >>> ln.Label.select(name="benchmark").one_or_none()
197
+ Label(id=gznl0GZk, name=benchmark, updated_at=2023-07-19 19:39:01, created_by_id=DzTjkKse) # noqa
198
+ >>> ln.Label.select(name="non existing label").one_or_none()
197
199
  None
198
200
  """
199
201
  if len(self) == 0:
lamindb/_save.py CHANGED
@@ -7,7 +7,7 @@ from typing import Iterable, List, Optional, Tuple, Union, overload # noqa
7
7
 
8
8
  import lamindb_setup
9
9
  from django.db import transaction
10
- from lamin_logger import logger
10
+ from lamin_utils import logger
11
11
  from lnschema_core.models import ORM, File
12
12
 
13
13
  from lamindb.dev.storage import store_object
@@ -45,7 +45,7 @@ def save(records: Iterable[ORM], **kwargs) -> None: # type: ignore
45
45
  Save a collection of records in one transaction, which is much faster
46
46
  than writing a loop over calls ``projects.save()``:
47
47
 
48
- >>> projects = [ln.Project(f"Project {i}") for i in range(10)]
48
+ >>> labels = [ln.Label(f"Label {i}") for i in range(10)]
49
49
  >>> ln.save(projects)
50
50
 
51
51
  For a single record, use ``.save()``:
@@ -76,13 +76,23 @@ def save(records: Iterable[ORM], **kwargs) -> None: # type: ignore
76
76
  non_files_with_parents = {r for r in non_files if hasattr(r, "_parents")}
77
77
 
78
78
  if len(non_files_with_parents) > 0 and kwargs.get("parents") is not False:
79
- # save the record with parents one by one
80
- logger.warning(
81
- "Now recursing through parents: "
82
- "this only happens once, but is much slower than bulk saving"
83
- )
84
- for record in non_files_with_parents:
85
- record._save_ontology_parents()
79
+ # this can only happen within lnschema_bionty right now!!
80
+ # we might extend to core lamindb later
81
+ import lnschema_bionty as lb
82
+
83
+ if kwargs.get("parents") or (
84
+ kwargs.get("parents") is None and lb.settings.auto_save_parents
85
+ ):
86
+ # save the record with parents one by one
87
+ logger.warning(
88
+ "Now recursing through parents: "
89
+ "this only happens once, but is much slower than bulk saving"
90
+ )
91
+ logger.hint(
92
+ "You can switch this off via: lb.settings.auto_save_parents = False"
93
+ )
94
+ for record in non_files_with_parents:
95
+ record._save_ontology_parents()
86
96
 
87
97
  if files:
88
98
  with transaction.atomic():
lamindb/_transform.py CHANGED
@@ -1,19 +1,28 @@
1
+ import hashlib
2
+
1
3
  from lnschema_core.ids import base62
2
4
  from lnschema_core.models import Transform
3
5
 
6
+ from .dev.hashing import to_b64_str
7
+
4
8
 
5
9
  def __init__(transform, *args, **kwargs):
6
10
  if len(args) > 0: # initialize with all fields from db as args
7
11
  super(Transform, transform).__init__(*args, **kwargs)
8
12
  return None
9
13
  else: # user-facing calling signature
14
+ if "version" not in kwargs:
15
+ kwargs["version"] = "0"
16
+ elif not isinstance(kwargs["version"], str):
17
+ raise ValueError("version must be str, e.g., '0', '1', etc.")
18
+ id_ext = to_b64_str(hashlib.md5(kwargs["version"].encode()).digest())[:2]
10
19
  # set default ids
11
20
  if "id" not in kwargs and "stem_id" not in kwargs:
12
- kwargs["id"] = base62(14)
13
- kwargs["stem_id"] = kwargs["id"][:12]
21
+ kwargs["stem_id"] = base62(12)
22
+ kwargs["id"] = kwargs["stem_id"] + id_ext
14
23
  elif "stem_id" in kwargs:
15
24
  assert isinstance(kwargs["stem_id"], str) and len(kwargs["stem_id"]) == 12
16
- kwargs["id"] = kwargs["stem_id"] + base62(2)
25
+ kwargs["id"] = kwargs["stem_id"] + id_ext
17
26
  elif "id" in kwargs:
18
27
  assert isinstance(kwargs["id"], str) and len(kwargs["id"]) == 14
19
28
  kwargs["stem_id"] = kwargs["id"][:12]
lamindb/_view.py CHANGED
@@ -3,7 +3,7 @@ import inspect
3
3
  from typing import List, Optional
4
4
 
5
5
  from IPython.display import display
6
- from lamin_logger import colors
6
+ from lamin_utils import colors
7
7
  from lamindb_setup import settings
8
8
  from lamindb_setup.dev._setup_schema import get_schema_module_name
9
9
  from lnschema_core import ORM
lamindb/dev/__init__.py CHANGED
@@ -6,16 +6,20 @@
6
6
  ORM
7
7
  QuerySet
8
8
  Manager
9
+ FeatureManager
9
10
  datasets
10
11
  hashing
11
12
  storage
12
13
  Settings
14
+ run_context
13
15
  """
14
16
 
15
17
  from lnschema_core.models import ORM
16
18
 
19
+ from lamindb._feature_manager import FeatureManager
17
20
  from lamindb._manager import Manager
18
21
  from lamindb._queryset import QuerySet
19
22
 
23
+ from .._context import run_context
20
24
  from . import datasets # noqa
21
25
  from ._settings import Settings
lamindb/dev/_settings.py CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
2
2
  from typing import Literal, Mapping, Tuple, Union
3
3
 
4
4
  import lamindb_setup as ln_setup
5
- from lamin_logger import logger
5
+ from lamin_utils import logger
6
6
  from upath import UPath
7
7
 
8
8