lamindb 0.76.11__py3-none-any.whl → 0.76.13__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 CHANGED
@@ -43,7 +43,7 @@ Modules and settings.
43
43
  """
44
44
 
45
45
  # denote a release candidate for 0.1.0 with 0.1rc1, 0.1a1, 0.1b1, etc.
46
- __version__ = "0.76.11"
46
+ __version__ = "0.76.13"
47
47
 
48
48
  import os as _os
49
49
 
lamindb/_artifact.py CHANGED
@@ -10,7 +10,7 @@ import fsspec
10
10
  import lamindb_setup as ln_setup
11
11
  import pandas as pd
12
12
  from anndata import AnnData
13
- from django.db.models import Q, QuerySet
13
+ from django.db.models import Q
14
14
  from lamin_utils import colors, logger
15
15
  from lamindb_setup import settings as setup_settings
16
16
  from lamindb_setup._init_instance import register_storage_in_instance
@@ -180,7 +180,7 @@ def process_data(
180
180
  f" be '{suffix}'."
181
181
  )
182
182
  cache_name = f"{provisional_uid}{suffix}"
183
- path = settings.storage.cache_dir / cache_name
183
+ path = settings.cache_dir / cache_name
184
184
  # Alex: I don't understand the line below
185
185
  if path.suffixes == []:
186
186
  path = path.with_suffix(suffix)
@@ -331,9 +331,10 @@ def get_artifact_kwargs_from_data(
331
331
  artifact = stat_or_artifact
332
332
  # update the run of the existing artifact
333
333
  if run is not None:
334
- # save the information that this artifact was previously
335
- # produced by another run
336
- if artifact.run is not None:
334
+ # save the information that this artifact was previously produced by
335
+ # another run
336
+ # note: same logic exists for _output_collections_with_later_updates
337
+ if artifact.run is not None and artifact.run != run:
337
338
  artifact.run._output_artifacts_with_later_updates.add(artifact)
338
339
  # update the run of the artifact with the latest run
339
340
  stat_or_artifact.run = run
@@ -344,7 +345,7 @@ def get_artifact_kwargs_from_data(
344
345
 
345
346
  if revises is not None: # update provisional_uid
346
347
  provisional_uid, revises = create_uid(revises=revises, version=version)
347
- if settings.storage.cache_dir in path.parents:
348
+ if settings.cache_dir in path.parents:
348
349
  path = path.rename(path.with_name(f"{provisional_uid}{suffix}"))
349
350
 
350
351
  check_path_in_storage = False
@@ -749,10 +750,6 @@ def from_dir(
749
750
  run: Run | None = None,
750
751
  ) -> list[Artifact]:
751
752
  """{}""" # noqa: D415
752
- logger.warning(
753
- "this creates one artifact per file in the directory - consider"
754
- " ln.Artifact(dir_path) to get one artifact for the entire directory"
755
- )
756
753
  folderpath: UPath = create_path(path) # returns Path for local
757
754
  default_storage = settings.storage.record
758
755
  using_key = settings._using_key
@@ -1125,7 +1122,7 @@ def save(self, upload: bool | None = None, **kwargs) -> Artifact:
1125
1122
  raise RuntimeError(exception)
1126
1123
  if local_path is not None and not state_was_adding:
1127
1124
  # only move the local artifact to cache if it was not newly created
1128
- local_path_cache = ln_setup.settings.storage.cache_dir / local_path.name
1125
+ local_path_cache = ln_setup.settings.cache_dir / local_path.name
1129
1126
  # don't use Path.rename here because of cross-device link error
1130
1127
  # https://laminlabs.slack.com/archives/C04A0RMA0SC/p1710259102686969
1131
1128
  shutil.move(
lamindb/_collection.py CHANGED
@@ -145,15 +145,16 @@ def __init__(
145
145
  logger.warning(
146
146
  f"returning existing collection with same hash: {existing_collection}"
147
147
  )
148
- # update the run of the existing artifact
148
+ # update the run of the existing collection
149
149
  if run is not None:
150
- # save the information that this artifact was previously
151
- # produced by another run
152
- if existing_collection.run is not None:
150
+ # save the information that this collection was previously produced
151
+ # by another run
152
+ # note: same logic exists for _output_artifacts_with_later_updates
153
+ if existing_collection.run is not None and existing_collection.run != run:
153
154
  existing_collection.run._output_collections_with_later_updates.add(
154
155
  existing_collection
155
156
  )
156
- # update the run of the artifact with the latest run
157
+ # update the run of the collection with the latest run
157
158
  existing_collection.run = run
158
159
  existing_collection.transform = run.transform
159
160
  init_self_from_db(collection, existing_collection)
lamindb/_feature_set.py CHANGED
@@ -216,7 +216,9 @@ def members(self) -> QuerySet:
216
216
 
217
217
 
218
218
  def _get_related_name(self: FeatureSet) -> str:
219
- feature_sets_related_models = dict_related_model_to_related_name(self)
219
+ feature_sets_related_models = dict_related_model_to_related_name(
220
+ self, instance=self._state.db
221
+ )
220
222
  related_name = feature_sets_related_models.get(self.registry)
221
223
  return related_name
222
224
 
lamindb/_filter.py CHANGED
@@ -2,8 +2,6 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
- from lnschema_core import Artifact, Collection
6
-
7
5
  from ._query_set import QuerySet, process_expressions
8
6
 
9
7
  if TYPE_CHECKING:
lamindb/_finish.py CHANGED
@@ -130,12 +130,12 @@ def save_context_core(
130
130
  if response != "y":
131
131
  return "aborted-non-consecutive"
132
132
  # write the report
133
- report_path = ln_setup.settings.storage.cache_dir / filepath.name.replace(
133
+ report_path = ln_setup.settings.cache_dir / filepath.name.replace(
134
134
  ".ipynb", ".html"
135
135
  )
136
136
  notebook_to_report(filepath, report_path)
137
137
  # write the source code
138
- source_code_path = ln_setup.settings.storage.cache_dir / filepath.name.replace(
138
+ source_code_path = ln_setup.settings.cache_dir / filepath.name.replace(
139
139
  ".ipynb", ".py"
140
140
  )
141
141
  notebook_to_script(transform, filepath, source_code_path)
@@ -171,7 +171,7 @@ def save_context_core(
171
171
  transform.hash = hash
172
172
 
173
173
  # track environment
174
- env_path = ln_setup.settings.storage.cache_dir / f"run_env_pip_{run.uid}.txt"
174
+ env_path = ln_setup.settings.cache_dir / f"run_env_pip_{run.uid}.txt"
175
175
  if env_path.exists():
176
176
  overwrite_env = True
177
177
  if run.environment_id is not None and from_cli:
lamindb/_parents.py CHANGED
@@ -310,7 +310,12 @@ def _record_label(record: Record, field: str | None = None):
310
310
  rf' FACE="Monospace">uid={record.uid}<BR/>version={record.version}</FONT>>'
311
311
  )
312
312
  elif isinstance(record, Run):
313
- name = f'{record.transform.name.replace("&", "&amp;")}'
313
+ if record.transform.name:
314
+ name = f'{record.transform.name.replace("&", "&amp;")}'
315
+ elif record.transform.key:
316
+ name = f'{record.transform.key.replace("&", "&amp;")}'
317
+ else:
318
+ name = f"{record.transform.uid}"
314
319
  user_display = (
315
320
  record.created_by.handle
316
321
  if record.created_by.name is None
@@ -365,7 +370,6 @@ def _get_all_parent_runs(data: Artifact | Collection) -> list:
365
370
  inputs_run += (
366
371
  r.input_collections.all().filter(visibility__in=[0, 1]).list()
367
372
  )
368
- run_inputs_outputs += [(inputs_run, r)]
369
373
  outputs_run = (
370
374
  r.__getattribute__(f"output_{name}s")
371
375
  .all()
@@ -376,7 +380,18 @@ def _get_all_parent_runs(data: Artifact | Collection) -> list:
376
380
  outputs_run += (
377
381
  r.output_collections.all().filter(visibility__in=[0, 1]).list()
378
382
  )
379
- run_inputs_outputs += [(r, outputs_run)]
383
+ # if inputs are outputs artifacts are the same, will result infinite loop
384
+ # so only show as outputs
385
+ overlap = set(inputs_run).intersection(outputs_run)
386
+ if overlap:
387
+ logger.warning(
388
+ f"The following artifacts are both inputs and outputs of Run(uid={r.uid}): {overlap}\n → Only showing as outputs."
389
+ )
390
+ inputs_run = list(set(inputs_run) - overlap)
391
+ if len(inputs_run) > 0:
392
+ run_inputs_outputs += [(inputs_run, r)]
393
+ if len(outputs_run) > 0:
394
+ run_inputs_outputs += [(r, outputs_run)]
380
395
  inputs += inputs_run
381
396
  runs = [f.run for f in inputs if f.run is not None]
382
397
  return run_inputs_outputs
lamindb/_query_manager.py CHANGED
@@ -98,26 +98,11 @@ class QueryManager(models.Manager):
98
98
 
99
99
  return _lookup(cls=self.all(), field=field, **kwargs)
100
100
 
101
- def __getitem__(self, item: str):
102
- try:
103
- source_field_name = self.source_field_name
104
- target_field_name = self.target_field_name
105
-
106
- if (
107
- source_field_name in {"artifact", "collection"}
108
- and target_field_name == "feature_set"
109
- ):
110
- return get_feature_set_by_slot_(host=self.instance).get(item)
111
-
112
- except Exception: # pragma: no cover
113
- return
114
-
115
101
 
116
102
  models.Manager.list = QueryManager.list
117
103
  models.Manager.df = QueryManager.df
118
104
  models.Manager.search = QueryManager.search
119
105
  models.Manager.lookup = QueryManager.lookup
120
- models.Manager.__getitem__ = QueryManager.__getitem__
121
106
  models.Manager._track_run_input_manager = QueryManager._track_run_input_manager
122
107
  # the two lines below would be easy if we could actually inherit; like this,
123
108
  # they're suboptimal
lamindb/_query_set.py CHANGED
@@ -115,7 +115,7 @@ def get(
115
115
  else:
116
116
  assert idlike is None # noqa: S101
117
117
  expressions = process_expressions(registry, expressions)
118
- return registry.objects.get(**expressions)
118
+ return registry.objects.using(qs.db).get(**expressions)
119
119
 
120
120
 
121
121
  class RecordsList(UserList):
@@ -139,7 +139,7 @@ class QuerySet(models.QuerySet):
139
139
 
140
140
  See Also:
141
141
 
142
- `django QuerySet <https://docs.djangoproject.com/en/4.2/ref/models/querysets/>`__ # noqa
142
+ `django QuerySet <https://docs.djangoproject.com/en/4.2/ref/models/querysets/>`__
143
143
 
144
144
  Examples:
145
145
 
@@ -154,7 +154,7 @@ class QuerySet(models.QuerySet):
154
154
  ) -> pd.DataFrame:
155
155
  """{}""" # noqa: D415
156
156
  # re-order the columns
157
- exclude_field_names = ["created_at"]
157
+ exclude_field_names = ["updated_at"]
158
158
  field_names = [
159
159
  field.name
160
160
  for field in self.model._meta.fields
lamindb/_record.py CHANGED
@@ -15,7 +15,7 @@ from lamindb_setup._connect_instance import (
15
15
  update_db_using_local,
16
16
  )
17
17
  from lamindb_setup.core._docs import doc_args
18
- from lamindb_setup.core._hub_core import connect_instance
18
+ from lamindb_setup.core._hub_core import connect_instance_hub
19
19
  from lamindb_setup.core._settings_store import instance_settings_file
20
20
  from lnschema_core.models import IsVersioned, Record, Run, Transform
21
21
 
@@ -376,15 +376,15 @@ def using(
376
376
  instance: str | None,
377
377
  ) -> QuerySet:
378
378
  """{}""" # noqa: D415
379
+ from ._query_set import QuerySet
380
+
379
381
  if instance is None:
380
382
  return QuerySet(model=cls, using=None)
381
383
  owner, name = get_owner_name_from_identifier(instance)
382
384
  settings_file = instance_settings_file(name, owner)
383
- cache_filepath = (
384
- ln_setup.settings.storage.cache_dir / f"instance--{owner}--{name}--uid.txt"
385
- )
385
+ cache_filepath = ln_setup.settings.cache_dir / f"instance--{owner}--{name}--uid.txt"
386
386
  if not settings_file.exists():
387
- result = connect_instance(owner=owner, name=name)
387
+ result = connect_instance_hub(owner=owner, name=name)
388
388
  if isinstance(result, str):
389
389
  raise RuntimeError(
390
390
  f"Failed to load instance {instance}, please check your permissions!"
@@ -397,15 +397,15 @@ def using(
397
397
  if not source_schema.issubset(target_schema):
398
398
  missing_members = source_schema - target_schema
399
399
  logger.warning(
400
- f"source schema has additional modules: {missing_members}\nconsider mounting these schema modules to not encounter errors"
400
+ f"source schema has additional modules: {missing_members}\nconsider mounting these schema modules to transfer all metadata"
401
401
  )
402
- cache_filepath.write_text(iresult["lnid"]) # type: ignore
402
+ cache_filepath.write_text(f"{iresult['lnid']}\n{iresult['schema_str']}") # type: ignore
403
403
  settings_file = instance_settings_file(name, owner)
404
404
  db = update_db_using_local(iresult, settings_file)
405
405
  else:
406
406
  isettings = load_instance_settings(settings_file)
407
407
  db = isettings.db
408
- cache_filepath.write_text(isettings.uid)
408
+ cache_filepath.write_text(f"{isettings.uid}\n{','.join(isettings.schema)}") # type: ignore
409
409
  add_db_connection(db, instance)
410
410
  return QuerySet(model=cls, using=instance)
411
411
 
@@ -469,12 +469,10 @@ def get_transfer_run(record) -> Run:
469
469
 
470
470
  slug = record._state.db
471
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
- )
472
+ cache_filepath = ln_setup.settings.cache_dir / f"instance--{owner}--{name}--uid.txt"
475
473
  if not cache_filepath.exists():
476
474
  raise SystemExit("Need to call .using() before")
477
- instance_uid = cache_filepath.read_text()
475
+ instance_uid = cache_filepath.read_text().split("\n")[0]
478
476
  key = f"transfers/{instance_uid}"
479
477
  uid = instance_uid + "0000"
480
478
  transform = Transform.filter(uid=uid).one_or_none()
lamindb/_save.py CHANGED
@@ -168,7 +168,7 @@ def copy_or_move_to_cache(
168
168
 
169
169
  local_path = local_path.resolve()
170
170
  is_dir = local_path.is_dir()
171
- cache_dir = settings._storage_settings.cache_dir
171
+ cache_dir = settings.cache_dir
172
172
 
173
173
  # just delete from the cache dir if storage_path is local
174
174
  if cache_path is None:
lamindb/core/_context.py CHANGED
@@ -438,7 +438,7 @@ class Context:
438
438
  )
439
439
  return (
440
440
  f'Filename "{key}" clashes with the existing key "{transform.key}" for uid "{transform.uid[:-4]}...."\n\nEither init a new transform with a new uid:\n\n'
441
- f'ln.track("{ids.base62_12()}0000)"\n\n{update_key_note}'
441
+ f'ln.track("{ids.base62_12()}0000")\n\n{update_key_note}'
442
442
  )
443
443
 
444
444
  # make a new transform record
@@ -562,26 +562,32 @@ class Context:
562
562
 
563
563
  return "CMD + s" if platform.system() == "Darwin" else "CTRL + s"
564
564
 
565
- if context.run is None:
565
+ if self.run is None:
566
566
  raise TrackNotCalled("Please run `ln.track()` before `ln.finish()`")
567
- if context._path is None:
568
- if context.run.transform.type in {"script", "notebook"}:
567
+ if self._path is None:
568
+ if self.run.transform.type in {"script", "notebook"}:
569
569
  raise ValueError(
570
- f"Transform type is not allowed to be 'script' or 'notebook' but is {context.run.transform.type}."
570
+ "Transform type is not allowed to be 'script' or 'notebook' because `context._path` is `None`."
571
571
  )
572
- context.run.finished_at = datetime.now(timezone.utc)
573
- context.run.save()
572
+ self.run.finished_at = datetime.now(timezone.utc)
573
+ self.run.save()
574
574
  # nothing else to do
575
575
  return None
576
576
  if is_run_from_ipython: # notebooks
577
- if get_seconds_since_modified(context._path) > 2 and not ln_setup._TESTING:
577
+ import nbproject
578
+
579
+ # it might be that the user modifies the title just before ln.finish()
580
+ if (nbproject_title := nbproject.meta.live.title) != self.transform.name:
581
+ self.transform.name = nbproject_title
582
+ self.transform.save()
583
+ if get_seconds_since_modified(self._path) > 2 and not ln_setup._TESTING:
578
584
  raise NotebookNotSaved(
579
585
  f"Please save the notebook in your editor (shortcut `{get_shortcut()}`) right before calling `ln.finish()`"
580
586
  )
581
587
  save_context_core(
582
- run=context.run,
583
- transform=context.run.transform,
584
- filepath=context._path,
588
+ run=self.run,
589
+ transform=self.run.transform,
590
+ filepath=self._path,
585
591
  finished_at=True,
586
592
  ignore_non_consecutive=ignore_non_consecutive,
587
593
  )
lamindb/core/_django.py CHANGED
@@ -6,7 +6,7 @@ from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel
6
6
  from django.db.models.functions import JSONObject
7
7
  from lnschema_core.models import Artifact, FeatureSet, Record
8
8
 
9
- from .schema import dict_related_model_to_related_name
9
+ from .schema import dict_related_model_to_related_name, get_schemas_modules
10
10
 
11
11
 
12
12
  def get_related_model(model, field_name):
@@ -35,22 +35,36 @@ def get_artifact_with_related(
35
35
  """Fetch an artifact with its related data."""
36
36
  from lamindb._can_validate import get_name_field
37
37
 
38
+ from ._label_manager import LABELS_EXCLUDE_SET
39
+
38
40
  model = artifact.__class__
39
- foreign_key_fields = [f.name for f in model._meta.fields if f.is_relation]
41
+ schema_modules = get_schemas_modules(artifact._state.db)
42
+
43
+ foreign_key_fields = [
44
+ f.name
45
+ for f in model._meta.fields
46
+ if f.is_relation and f.related_model.__get_schema_name__() in schema_modules
47
+ ]
40
48
 
41
49
  m2m_relations = (
42
50
  []
43
51
  if not include_m2m
44
52
  else [
45
53
  v
46
- for v in dict_related_model_to_related_name(model).values()
47
- if not v.startswith("_")
54
+ for v in dict_related_model_to_related_name(
55
+ model, instance=artifact._state.db
56
+ ).values()
57
+ if not v.startswith("_") and v not in LABELS_EXCLUDE_SET
48
58
  ]
49
59
  )
50
60
  link_tables = (
51
61
  []
52
62
  if not include_feature_link
53
- else list(dict_related_model_to_related_name(model, links=True).values())
63
+ else list(
64
+ dict_related_model_to_related_name(
65
+ model, links=True, instance=artifact._state.db
66
+ ).values()
67
+ )
54
68
  )
55
69
 
56
70
  # Clear previous queries
@@ -40,7 +40,7 @@ from lamindb._record import (
40
40
  transfer_to_default_db,
41
41
  )
42
42
  from lamindb._save import save
43
- from lamindb.core.exceptions import ValidationError
43
+ from lamindb.core.exceptions import DoesNotExist, ValidationError
44
44
  from lamindb.core.storage import LocalPathClasses
45
45
 
46
46
  from ._django import get_artifact_with_related
@@ -216,9 +216,11 @@ def _print_categoricals(
216
216
  if not print_params:
217
217
  labels_msg = ""
218
218
  labels_by_feature = defaultdict(list)
219
- for _, (_, links) in get_labels_as_dict(self, links=True).items():
219
+ for _, (_, links) in get_labels_as_dict(
220
+ self, links=True, instance=self._state.db
221
+ ).items():
220
222
  for link in links:
221
- if link.feature_id is not None:
223
+ if hasattr(link, "feature_id") and link.feature_id is not None:
222
224
  link_attr = get_link_attr(link, self)
223
225
  labels_by_feature[link.feature_id].append(
224
226
  getattr(link, link_attr).name
@@ -509,18 +511,49 @@ def filter_base(cls, **expression):
509
511
  expression = {feature_param: feature, f"value{comparator}": value}
510
512
  feature_value = value_model.filter(**expression)
511
513
  new_expression[f"_{feature_param}_values__in"] = feature_value
512
- else:
514
+ elif isinstance(value, (str, Record)):
515
+ # because SQL is sensitive to whether querying with __in or not
516
+ # and might return multiple equivalent records for the latter
517
+ # we distinguish cases in which we have multiple label matches vs. one
518
+ label = None
519
+ labels = None
513
520
  if isinstance(value, str):
521
+ # we need the comparator here because users might query like so
522
+ # ln.Artifact.features.filter(experiment__contains="Experi")
514
523
  expression = {f"name{comparator}": value}
515
- label = ULabel.get(**expression)
516
- new_expression["ulabels"] = label
524
+ labels = ULabel.filter(**expression).all()
525
+ if len(labels) == 0:
526
+ raise DoesNotExist(
527
+ f"Did not find a ULabel matching `name{comparator}={value}`"
528
+ )
529
+ elif len(labels) == 1:
530
+ label = labels[0]
531
+ elif isinstance(value, Record):
532
+ label = value
533
+ label_registry = (
534
+ label.__class__ if label is not None else labels[0].__class__
535
+ )
536
+ accessor_name = (
537
+ label_registry.artifacts.through.artifact.field._related_name
538
+ )
539
+ new_expression[f"{accessor_name}__feature"] = feature
540
+ if label is not None:
541
+ # simplified query if we have exactly one label
542
+ new_expression[
543
+ f"{accessor_name}__{label_registry.__name__.lower()}"
544
+ ] = label
517
545
  else:
518
- raise NotImplementedError
546
+ new_expression[
547
+ f"{accessor_name}__{label_registry.__name__.lower()}__in"
548
+ ] = labels
549
+ else:
550
+ # if passing a list of records, we want to
551
+ # find artifacts that are annotated by all of them at the same
552
+ # time; hence, we don't want the __in construct that we use to match strings
553
+ # https://laminlabs.slack.com/archives/C04FPE8V01W/p1688328084810609
554
+ raise NotImplementedError
519
555
  if cls == FeatureManager or cls == ParamManagerArtifact:
520
556
  return Artifact.filter(**new_expression)
521
- # might renable something similar in the future
522
- # elif cls == FeatureManagerCollection:
523
- # return Collection.filter(**new_expression)
524
557
  elif cls == ParamManagerRun:
525
558
  return Run.filter(**new_expression)
526
559
 
@@ -26,32 +26,19 @@ if TYPE_CHECKING:
26
26
 
27
27
  from lamindb._query_set import QuerySet
28
28
 
29
+ LABELS_EXCLUDE_SET = {"feature_sets"}
29
30
 
30
- def get_labels_as_dict(self: Artifact | Collection, links: bool = False):
31
- exclude_set = {
32
- "feature_sets",
33
- "artifacts",
34
- "input_of_runs",
35
- "collections",
36
- "_source_code_artifact_of",
37
- "_report_of",
38
- "_environment_of",
39
- "links_collection",
40
- "links_artifact",
41
- "links_feature_set",
42
- "previous_runs",
43
- "_feature_values",
44
- "_action_targets",
45
- "_lnschema_core_collection__actions_+", # something seems off with this one
46
- "_actions",
47
- }
31
+
32
+ def get_labels_as_dict(
33
+ self: Artifact | Collection, links: bool = False, instance: str | None = None
34
+ ) -> dict:
48
35
  labels = {} # type: ignore
49
36
  if self.id is None:
50
37
  return labels
51
38
  for related_model_name, related_name in dict_related_model_to_related_name(
52
- self.__class__, links=links
39
+ self.__class__, links=links, instance=instance
53
40
  ).items():
54
- if related_name not in exclude_set:
41
+ if related_name not in LABELS_EXCLUDE_SET and not related_name.startswith("_"):
55
42
  labels[related_name] = (
56
43
  related_model_name,
57
44
  getattr(self, related_name).all(),
@@ -86,18 +73,15 @@ def print_labels(
86
73
  labels_msg = _print_labels_postgres(self, m2m_data, print_types)
87
74
  else:
88
75
  labels_msg = ""
89
- for related_name, (related_model, labels) in get_labels_as_dict(self).items():
90
- # there is a try except block here to deal with schema inconsistencies
91
- # during transfer between instances
92
- try:
93
- field = get_name_field(labels)
94
- labels_list = list(labels.values_list(field, flat=True))
95
- if len(labels_list) > 0:
96
- print_values = _print_values(labels_list, n=10)
97
- type_str = f": {related_model}" if print_types else ""
98
- labels_msg += f" .{related_name}{type_str} = {print_values}\n"
99
- except Exception: # noqa: S112
100
- continue
76
+ for related_name, (related_model, labels) in get_labels_as_dict(
77
+ self, instance=self._state.db
78
+ ).items():
79
+ field = get_name_field(labels)
80
+ labels_list = list(labels.values_list(field, flat=True))
81
+ if len(labels_list) > 0:
82
+ print_values = _print_values(labels_list, n=10)
83
+ type_str = f": {related_model}" if print_types else ""
84
+ labels_msg += f" .{related_name}{type_str} = {print_values}\n"
101
85
 
102
86
  msg = ""
103
87
  if labels_msg:
@@ -214,75 +198,68 @@ class LabelManager:
214
198
  >>> artifact1.ulabels.set(labels)
215
199
  >>> artifact2.labels.add_from(artifact1)
216
200
  """
217
- from django.db.utils import ProgrammingError
218
-
219
201
  if transfer_logs is None:
220
202
  transfer_logs = {"mapped": [], "transferred": [], "run": None}
221
203
  using_key = settings._using_key
222
- for related_name, (_, labels) in get_labels_as_dict(data).items():
204
+ for related_name, (_, labels) in get_labels_as_dict(
205
+ data, instance=self._host._state.db
206
+ ).items():
223
207
  labels = labels.all()
224
- try:
225
- if not labels.exists():
226
- continue
227
- # look for features
228
- data_name_lower = data.__class__.__name__.lower()
229
- labels_by_features = defaultdict(list)
230
- features = set()
231
- _, new_labels = validate_labels(labels)
232
- if len(new_labels) > 0:
233
- transfer_fk_to_default_db_bulk(
234
- new_labels, using_key, transfer_logs=transfer_logs
208
+ if not labels.exists():
209
+ continue
210
+ # look for features
211
+ data_name_lower = data.__class__.__name__.lower()
212
+ labels_by_features = defaultdict(list)
213
+ features = set()
214
+ _, new_labels = validate_labels(labels)
215
+ if len(new_labels) > 0:
216
+ transfer_fk_to_default_db_bulk(
217
+ new_labels, using_key, transfer_logs=transfer_logs
218
+ )
219
+ for label in labels:
220
+ # if the link table doesn't follow this convention, we'll ignore it
221
+ if not hasattr(label, f"links_{data_name_lower}"):
222
+ key = None
223
+ else:
224
+ link = getattr(label, f"links_{data_name_lower}").get(
225
+ **{f"{data_name_lower}_id": data.id}
235
226
  )
236
- for label in labels:
237
- # if the link table doesn't follow this convention, we'll ignore it
238
- if not hasattr(label, f"links_{data_name_lower}"):
239
- key = None
227
+ if link.feature is not None:
228
+ features.add(link.feature)
229
+ key = link.feature.name
240
230
  else:
241
- link = getattr(label, f"links_{data_name_lower}").get(
242
- **{f"{data_name_lower}_id": data.id}
243
- )
244
- if link.feature is not None:
245
- features.add(link.feature)
246
- key = link.feature.name
247
- else:
248
- key = None
249
- label_returned = transfer_to_default_db(
250
- label,
231
+ key = None
232
+ label_returned = transfer_to_default_db(
233
+ label,
234
+ using_key,
235
+ transfer_logs=transfer_logs,
236
+ transfer_fk=False,
237
+ save=True,
238
+ )
239
+ # TODO: refactor return value of transfer to default db
240
+ if label_returned is not None:
241
+ label = label_returned
242
+ labels_by_features[key].append(label)
243
+ # treat features
244
+ _, new_features = validate_labels(list(features))
245
+ if len(new_features) > 0:
246
+ transfer_fk_to_default_db_bulk(
247
+ new_features, using_key, transfer_logs=transfer_logs
248
+ )
249
+ for feature in new_features:
250
+ transfer_to_default_db(
251
+ feature,
251
252
  using_key,
252
253
  transfer_logs=transfer_logs,
253
254
  transfer_fk=False,
254
- save=True,
255
255
  )
256
- # TODO: refactor return value of transfer to default db
257
- if label_returned is not None:
258
- label = label_returned
259
- labels_by_features[key].append(label)
260
- # treat features
261
- _, new_features = validate_labels(list(features))
262
- if len(new_features) > 0:
263
- transfer_fk_to_default_db_bulk(
264
- new_features, using_key, transfer_logs=transfer_logs
256
+ save(new_features)
257
+ if hasattr(self._host, related_name):
258
+ for feature_name, labels in labels_by_features.items():
259
+ if feature_name is not None:
260
+ feature_id = Feature.get(name=feature_name).id
261
+ else:
262
+ feature_id = None
263
+ getattr(self._host, related_name).add(
264
+ *labels, through_defaults={"feature_id": feature_id}
265
265
  )
266
- for feature in new_features:
267
- transfer_to_default_db(
268
- feature,
269
- using_key,
270
- transfer_logs=transfer_logs,
271
- transfer_fk=False,
272
- )
273
- save(new_features)
274
- if hasattr(self._host, related_name):
275
- for feature_name, labels in labels_by_features.items():
276
- if feature_name is not None:
277
- feature_id = Feature.get(name=feature_name).id
278
- else:
279
- feature_id = None
280
- getattr(self._host, related_name).add(
281
- *labels, through_defaults={"feature_id": feature_id}
282
- )
283
- # ProgrammingError is raised when schemas don't match between source and target instances
284
- except ProgrammingError:
285
- logger.warning(
286
- f"{related_name} labels cannot be transferred because schema module does not exist in target instance: {labels}"
287
- )
288
- continue
lamindb/core/_settings.py CHANGED
@@ -143,6 +143,11 @@ class Settings:
143
143
  path, kwargs = path_kwargs, {}
144
144
  set_managed_storage(path, **kwargs)
145
145
 
146
+ @property
147
+ def cache_dir(self) -> UPath:
148
+ """Cache root, a local directory to cache cloud files."""
149
+ return ln_setup.settings.cache_dir
150
+
146
151
  @property
147
152
  def storage_local(self) -> StorageSettings:
148
153
  """An additional local default storage (a path to its root).
lamindb/core/_sync_git.py CHANGED
@@ -16,7 +16,7 @@ class BlobHashNotFound(SystemExit):
16
16
 
17
17
  def get_git_repo_from_remote() -> Path:
18
18
  repo_url = settings.sync_git_repo
19
- repo_dir = setup_settings.storage.cache_dir / repo_url.split("/")[-1]
19
+ repo_dir = setup_settings.cache_dir / repo_url.split("/")[-1]
20
20
  if repo_dir.exists():
21
21
  logger.warning(f"git repo {repo_dir} already exists locally")
22
22
  return repo_dir
@@ -26,7 +26,7 @@ def get_git_repo_from_remote() -> Path:
26
26
  result = subprocess.run(
27
27
  ["git", "clone", "--depth", "10", f"{repo_url}.git"],
28
28
  capture_output=True,
29
- cwd=setup_settings.storage.cache_dir,
29
+ cwd=setup_settings.cache_dir,
30
30
  )
31
31
  if result.returncode != 0 or not repo_dir.exists():
32
32
  raise RuntimeError(result.stderr.decode())
@@ -11,7 +11,7 @@ if TYPE_CHECKING:
11
11
 
12
12
 
13
13
  def track_environment(run: Run) -> None:
14
- filepath = ln_setup.settings.storage.cache_dir / f"run_env_pip_{run.uid}.txt"
14
+ filepath = ln_setup.settings.cache_dir / f"run_env_pip_{run.uid}.txt"
15
15
  # create a requirements.txt
16
16
  # we don't create a conda environment.yml mostly for its slowness
17
17
  try:
@@ -45,7 +45,7 @@ class ValidationError(SystemExit):
45
45
 
46
46
  # inspired by Django's DoesNotExist
47
47
  # equivalent to SQLAlchemy's NoResultFound
48
- class DoesNotExist(Exception):
48
+ class DoesNotExist(SystemExit):
49
49
  """No record found."""
50
50
 
51
51
  pass
lamindb/core/schema.py CHANGED
@@ -1,31 +1,66 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import lamindb_setup as ln_setup
3
4
  from django.db.models import ManyToManyField
5
+ from lamindb_setup._connect_instance import (
6
+ get_owner_name_from_identifier,
7
+ load_instance_settings,
8
+ )
9
+ from lamindb_setup.core._settings_store import instance_settings_file
4
10
  from lnschema_core.models import Feature, FeatureSet, LinkORM, Record
5
11
 
6
12
 
7
- def dict_schema_name_to_model_name(registry: type[Record]) -> dict[str, Record]:
13
+ def get_schemas_modules(instance: str | None) -> set[str]:
14
+ if instance is None or instance == "default":
15
+ schema_modules = set(ln_setup.settings.instance.schema)
16
+ schema_modules.add("core")
17
+ return schema_modules
18
+ owner, name = get_owner_name_from_identifier(instance)
19
+ settings_file = instance_settings_file(name, owner)
20
+ if settings_file.exists():
21
+ schema = set(load_instance_settings(settings_file).schema)
22
+ else:
23
+ cache_filepath = (
24
+ ln_setup.settings.cache_dir / f"instance--{owner}--{name}--uid.txt"
25
+ )
26
+ if cache_filepath.exists():
27
+ schema = set(cache_filepath.read_text().split("\n")[1].split(","))
28
+ else:
29
+ raise ValueError(f"Instance {instance} not found")
30
+ shared_schema_modules = set(ln_setup.settings.instance.schema).intersection(schema)
31
+ shared_schema_modules.add("core")
32
+ return shared_schema_modules
33
+
34
+
35
+ def dict_schema_name_to_model_name(
36
+ registry: type[Record], instance: str | None = None
37
+ ) -> dict[str, Record]:
38
+ schema_modules = get_schemas_modules(instance)
8
39
  d: dict = {
9
40
  i.related_model.__get_name_with_schema__(): i.related_model
10
41
  for i in registry._meta.related_objects
11
42
  if i.related_name is not None
43
+ and i.related_model.__get_schema_name__() in schema_modules
12
44
  }
13
45
  d.update(
14
46
  {
15
47
  i.related_model.__get_name_with_schema__(): i.related_model
16
48
  for i in registry._meta.many_to_many
17
49
  if i.name is not None
50
+ and i.related_model.__get_schema_name__() in schema_modules
18
51
  }
19
52
  )
20
53
  return d
21
54
 
22
55
 
23
56
  def dict_related_model_to_related_name(
24
- registry: type[Record], links: bool = False
57
+ registry: type[Record], links: bool = False, instance: str | None = None
25
58
  ) -> dict[str, str]:
26
59
  def include(model: Record):
27
60
  return not links != issubclass(model, LinkORM)
28
61
 
62
+ schema_modules = get_schemas_modules(instance)
63
+
29
64
  related_objects = registry._meta.related_objects + registry._meta.many_to_many
30
65
  d: dict = {
31
66
  record.related_model.__get_name_with_schema__(): (
@@ -34,7 +69,11 @@ def dict_related_model_to_related_name(
34
69
  else record.name
35
70
  )
36
71
  for record in related_objects
37
- if (record.name is not None and include(record.related_model))
72
+ if (
73
+ record.name is not None
74
+ and include(record.related_model)
75
+ and record.related_model.__get_schema_name__() in schema_modules
76
+ )
38
77
  }
39
78
  return d
40
79
 
@@ -8,6 +8,7 @@ import lamindb_setup as ln_setup
8
8
  from lamin_utils import logger
9
9
 
10
10
  from lamindb._artifact import Artifact
11
+ from lamindb._collection import Collection
11
12
  from lamindb._run import Run
12
13
  from lamindb._transform import Transform
13
14
 
@@ -20,14 +21,21 @@ if TYPE_CHECKING:
20
21
  def save_vitessce_config(
21
22
  vitessce_config: VitessceConfig, description: str | None = None
22
23
  ) -> Artifact:
23
- """Validates and saves a ``VitessceConfig`` object.
24
+ """Validates and saves a `VitessceConfig` object.
25
+
26
+ If the `VitessceConfig` object references multiple artifacts, automatically
27
+ creates a `Collection` and displays the "Vitessce button" next to it.
24
28
 
25
29
  Guide: :doc:`docs:vitessce`.
26
30
 
27
31
  Args:
28
- vitessce_config (``VitessceConfig``): A `VitessceConfig` object.
29
- description: A description for the `VitessceConfig` artifact.
32
+ vitessce_config: A `VitessceConfig` object.
33
+ description: A description for the `VitessceConfig` object. Is used as
34
+ `name` for a `Collection` in case the `VitessceConfig` object
35
+ references multiple artifacts.
30
36
 
37
+ .. versionchanged:: 0.76.12
38
+ Now assumes `vitessce-python >= 3.4.0`, which allows passing artifacts within `VitessceConfig`.
31
39
  .. versionchanged:: 0.75.1
32
40
  Now displays the "Vitessce button" on the hub next to the dataset. It additionally keeps displaying it next to the configuration file.
33
41
  .. versionchanged:: 0.70.2
@@ -40,68 +48,54 @@ def save_vitessce_config(
40
48
 
41
49
  assert isinstance(vitessce_config, VitessceConfig) # noqa: S101
42
50
  vc_dict = vitessce_config.to_dict()
43
- valid_composite_zarr_suffixes = [
44
- suffix for suffix in VALID_SUFFIXES.COMPOSITE if suffix.endswith(".zarr")
45
- ]
46
- # validate
47
- dataset_artifacts = []
48
- assert vc_dict["datasets"] # noqa: S101
49
- for vitessce_dataset in vc_dict["datasets"]:
50
- # didn't find good ways to violate the below, hence using plain asserts
51
- # without user feedback
52
- assert "files" in vitessce_dataset # noqa: S101
53
- assert vitessce_dataset["files"] # noqa: S101
54
- for file in vitessce_dataset["files"]:
55
- if "url" not in file:
56
- raise ValueError("Each file must have a 'url' key.")
57
- s3_path = file["url"]
58
- s3_path_last_element = s3_path.split("/")[-1]
59
- # now start with attempting to strip the composite suffix candidates
60
- for suffix in valid_composite_zarr_suffixes:
61
- s3_path_last_element = s3_path_last_element.replace(suffix, "")
62
- # in case there was no hit, strip plain ".zarr"
63
- artifact_stem_uid = s3_path_last_element.replace(".zarr", "")
64
- # if there is still a "." in string, raise an error
65
- if "." in artifact_stem_uid:
66
- raise ValueError(
67
- f"Suffix should be '.zarr' or one of {valid_composite_zarr_suffixes}. Inspect your path {s3_path}"
68
- )
69
- artifact = Artifact.filter(uid__startswith=artifact_stem_uid).one_or_none()
70
- if artifact is None:
71
- raise ValueError(
72
- f"Could not find dataset with stem uid '{artifact_stem_uid}' in lamindb: {vitessce_dataset}. Did you follow https://docs.lamin.ai/vitessce? It appears the AWS S3 path doesn't encode a lamindb uid."
73
- )
74
- else:
75
- dataset_artifacts.append(artifact)
51
+ try:
52
+ url_to_artifact_dict = vitessce_config.get_artifacts()
53
+ except AttributeError as e:
54
+ raise SystemExit(
55
+ "save_vitessce_config() requires vitessce>=3.4.0: pip install vitessce>=3.4.0"
56
+ ) from e
57
+ dataset_artifacts = list(url_to_artifact_dict.values())
58
+ message = "\n".join([artifact.__repr__() for artifact in dataset_artifacts])
59
+ logger.important(f"VitessceConfig references these artifacts:\n{message}")
60
+ assert len(dataset_artifacts) > 0 # noqa: S101
61
+
76
62
  # the below will be replaced with a `ln.tracked()` decorator soon
77
- with logger.mute():
78
- transform = Transform(
79
- uid="kup03MJBsIVa0001",
80
- name="save_vitessce_config",
81
- type="function",
82
- version="2",
83
- ).save()
63
+ transform = Transform(
64
+ uid="kup03MJBsIVa0002",
65
+ name="save_vitessce_config",
66
+ type="function",
67
+ version="3",
68
+ ).save()
84
69
  run = Run(transform=transform).save()
70
+ run.input_artifacts.set(dataset_artifacts)
71
+ collection = None
85
72
  if len(dataset_artifacts) > 1:
86
73
  # if we have more datasets, we should create a collection
87
74
  # and attach an action to the collection
88
- raise NotImplementedError
89
- run.input_artifacts.set(dataset_artifacts)
75
+ collection = Collection(dataset_artifacts, name=description).save()
76
+
90
77
  # create a JSON export
91
- config_file_local_path = (
92
- ln_setup.settings.storage.cache_dir / "config.vitessce.json"
93
- )
78
+ config_file_local_path = ln_setup.settings.cache_dir / "config.vitessce.json"
94
79
  with open(config_file_local_path, "w") as file:
95
80
  json.dump(vc_dict, file)
96
81
  vitessce_config_artifact = Artifact(
97
82
  config_file_local_path, description=description, run=run
98
83
  ).save()
99
- # we have one and only one dataset artifact, hence the following line is OK
100
- dataset_artifacts[0]._actions.add(vitessce_config_artifact)
101
84
  slug = ln_setup.settings.instance.slug
102
85
  logger.important(
103
- f"go to: https://lamin.ai/{slug}/artifact/{vitessce_config_artifact.uid}"
86
+ f"VitessceConfig: https://lamin.ai/{slug}/artifact/{vitessce_config_artifact.uid}"
104
87
  )
88
+ if collection is None:
89
+ # we have one and only one dataset artifact, hence the following line is OK
90
+ dataset_artifacts[0]._actions.add(vitessce_config_artifact)
91
+ logger.important(
92
+ f"Dataset: https://lamin.ai/{slug}/artifact/{dataset_artifacts[0].uid}"
93
+ )
94
+ else:
95
+ collection._actions.add(vitessce_config_artifact)
96
+ logger.important(
97
+ f"Collection: https://lamin.ai/{slug}/collection/{collection.uid}"
98
+ )
105
99
  run.finished_at = datetime.now(timezone.utc)
106
100
  run.save()
107
101
  return vitessce_config_artifact
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lamindb
3
- Version: 0.76.11
3
+ Version: 0.76.13
4
4
  Summary: A data framework for biology.
5
5
  Author-email: Lamin Labs <open-source@lamin.ai>
6
6
  Requires-Python: >=3.9
@@ -8,10 +8,10 @@ Description-Content-Type: text/markdown
8
8
  Classifier: Programming Language :: Python :: 3.9
9
9
  Classifier: Programming Language :: Python :: 3.10
10
10
  Classifier: Programming Language :: Python :: 3.11
11
- Requires-Dist: lnschema_core==0.74.6
12
- Requires-Dist: lamindb_setup==0.77.7
11
+ Requires-Dist: lnschema_core==0.75.0
13
12
  Requires-Dist: lamin_utils==0.13.6
14
- Requires-Dist: lamin_cli==0.17.8
13
+ Requires-Dist: lamin_cli==0.19.0
14
+ Requires-Dist: lamindb_setup
15
15
  Requires-Dist: rapidfuzz
16
16
  Requires-Dist: pyarrow
17
17
  Requires-Dist: typing_extensions!=4.6.0
@@ -23,7 +23,7 @@ Requires-Dist: pandas
23
23
  Requires-Dist: graphviz
24
24
  Requires-Dist: psycopg2-binary
25
25
  Requires-Dist: lamindb_setup[aws] ; extra == "aws"
26
- Requires-Dist: bionty==0.51.1 ; extra == "bionty"
26
+ Requires-Dist: bionty==0.51.2 ; extra == "bionty"
27
27
  Requires-Dist: line_profiler ; extra == "dev"
28
28
  Requires-Dist: pre-commit ; extra == "dev"
29
29
  Requires-Dist: nox ; extra == "dev"
@@ -1,39 +1,39 @@
1
- lamindb/__init__.py,sha256=-uf_cdV7PtdeKSV5FnhmeJrYFFMu4pTV4WgjQTXFb4I,2278
2
- lamindb/_artifact.py,sha256=kbAQp65KqskAnwx8kBBzh2Jb7HRhVBAQJD4xapj57rk,45011
1
+ lamindb/__init__.py,sha256=h_byT1s_9NrqH157lvPgG336CTBqGzIIJNlNU7MKWQE,2278
2
+ lamindb/_artifact.py,sha256=AHyKZ_D_qMGtywAklo42je8BZB-VBqQ-ZTaI9AB_Yxs,44905
3
3
  lamindb/_can_validate.py,sha256=1pUavLwZ_yPAtbVYKOGYUHaPxlJGZ246qZ0e-4ZUDSc,19552
4
- lamindb/_collection.py,sha256=TSkJ3z0axRAFrV2PvZf1YMz7NXWLX3hYoNQ48l4NoyM,14129
4
+ lamindb/_collection.py,sha256=ZQ45_9n0komz1y8l_vzZ-cRn-ghinE0Izl889Mk-TSA,14249
5
5
  lamindb/_curate.py,sha256=KpEP0-9nQUmiRn6z7fiGs6BmQdpqSg3QPlAgV-S9_wA,58839
6
6
  lamindb/_feature.py,sha256=nZhtrH0ssoNls-hV-dkwfK9sKypg2El59R9qfarxfUE,5340
7
- lamindb/_feature_set.py,sha256=lHKATvs8NagaaHwsWqi4XMTPTNKsGdM-6ZRctTHHSNc,8130
8
- lamindb/_filter.py,sha256=NMxIX67gYqN7n3lA4XL6hRgAaAFhpwnwxm3vBA5XGlU,683
9
- lamindb/_finish.py,sha256=yoVmgaUx5SBKMlA_wUKsJhNmUjtOD65l-4CZ3oXXNvk,9570
7
+ lamindb/_feature_set.py,sha256=JQSP-YLam1KW-rDzly5Dm4IYVL2A6ec7ufIf6iCc2W8,8169
8
+ lamindb/_filter.py,sha256=Pf9NHV4gm7NOC0Frtvx4W7nvwt2EowOP74DwppyXAZs,635
9
+ lamindb/_finish.py,sha256=VMAmxCUFmTKIMSCx7LEh4QAnWDeue6MeUAAzkMVEYMU,9546
10
10
  lamindb/_from_values.py,sha256=iqcR8T8i9VZpnn09W441zW-djhWMh8EZj7XFJq9jP2k,14211
11
11
  lamindb/_is_versioned.py,sha256=5lAnhTboltFkZCKVRV1uxkm0OCjJz_HKi3yQq_vEuMs,1306
12
- lamindb/_parents.py,sha256=eMavdd6IO6STOVJSlR2TzdRtx6sKYDKsMOtlR3DZlgQ,15599
13
- lamindb/_query_manager.py,sha256=Ipe85HL31DDwMbC8CN_1Svbwk48a_DUh_INGQdZL08I,4222
14
- lamindb/_query_set.py,sha256=qaOgVc9zGiB2iRyJcUplPmRbgezy4tgubNXS25REaN8,12724
15
- lamindb/_record.py,sha256=puPMZDY1GO-DY02CDbOScTHwXQrUV7BFQfrig8b21Lk,23300
12
+ lamindb/_parents.py,sha256=KMBUfCLNqjmFzOdZIXaUFqDPeEpWP28MCkHHPq887h8,16341
13
+ lamindb/_query_manager.py,sha256=pmPhJQ85-7XeAU9TFv6LPGi9F7dBgztZgZcXz33HYJM,3710
14
+ lamindb/_query_set.py,sha256=81y1c-Y-PGCmqc64WizlCiGTbsrApsajtX2xj7xrsMo,12730
15
+ lamindb/_record.py,sha256=LkKtDxqlTq43UlAiJ96_nbpjj9eQbU4N3HpfBVfImJo,23395
16
16
  lamindb/_run.py,sha256=K_5drpLn3D7y3XtZ3vtAw35rG5RCSvB4bXQZx4ESSI0,1964
17
- lamindb/_save.py,sha256=hOBby87v1_kC2Ixi8f19DgeznlKa8cihyWA7qMUZdtM,11384
17
+ lamindb/_save.py,sha256=BCaSFnANYPxTqL5gw7Hrh_9kz7SDyOxrJV2KW6rXqts,11366
18
18
  lamindb/_storage.py,sha256=GBVChv-DHVMNEBJL5l_JT6B4RDhZ6NnwgzmUICphYKk,413
19
19
  lamindb/_transform.py,sha256=wZDkY8lp4d_OsO5a7rLs1RamkDzBXZSLaWJU34zRnmA,4728
20
20
  lamindb/_ulabel.py,sha256=XDSdZBXX_ki5s1vOths3MjF2x5DPggBR_PV_KF4SGyg,1611
21
21
  lamindb/_utils.py,sha256=LGdiW4k3GClLz65vKAVRkL6Tw-Gkx9DWAdez1jyA5bE,428
22
22
  lamindb/_view.py,sha256=4Ln2ItTb3857PAI-70O8eJYqoTJ_NNFc7E_wds6OGns,2412
23
23
  lamindb/core/__init__.py,sha256=57AXQ286eOX2_o5HUeqIFJrfqN-OZ_E7FVHd3Xm5oOk,1483
24
- lamindb/core/_context.py,sha256=TD5k0F-igJRwWBjXMaw3Lh3WtgtY3GLl_a7dZ7GHUu4,22639
24
+ lamindb/core/_context.py,sha256=dI3z7fCMRPC3IMb7-EIaQYhacSZBA4HfLVFyoJtVL7I,22900
25
25
  lamindb/core/_data.py,sha256=P0vlbw_UQp9UDNMmUo9YFxqurcWtMaKPWCT12IRA2C0,19672
26
- lamindb/core/_django.py,sha256=mqZ0WhTakLDrGVp3MVI6RNfu8kZ2L5BiEnMwXnkfsG0,6760
27
- lamindb/core/_feature_manager.py,sha256=jRdBVlWE5GA9vcbiiT54WL-yix4Dptbe-YtxzayvUr0,37319
28
- lamindb/core/_label_manager.py,sha256=GoluSFk_z1c1fZw8OGKkaIURWgognEsOHCo9mzigd-A,11108
26
+ lamindb/core/_django.py,sha256=yeMPp1n9WrFmEjVRdavfpVqAolPLd24RseTQlvsK67w,7157
27
+ lamindb/core/_feature_manager.py,sha256=yH6HGF_c0NwIE6hxcMUZdZrokx75Kz-54d_3JhyCglQ,39020
28
+ lamindb/core/_label_manager.py,sha256=bhD6krOrf0SWXfCzhFsEA-7h0ZLJ59YSPxWnlq7DZiE,9991
29
29
  lamindb/core/_mapped_collection.py,sha256=M50haewVAFONeF71QQbzD09L8lVZCL1hyf0W87jKE5U,24575
30
- lamindb/core/_settings.py,sha256=f7TCIx9cYFpLH9ja7OCMLpAgrOJRZUP8Rr_xJKToMlk,5979
31
- lamindb/core/_sync_git.py,sha256=qc0yfPyKeG4uuNT_3qsv-mkIMqhLFqfXNeNVO49vV00,4547
32
- lamindb/core/_track_environment.py,sha256=STzEVUzOeUEWdX7WDJUkKH4u08k7eupRX6AXQwoVt14,828
33
- lamindb/core/exceptions.py,sha256=JowyzEXRd2DivS-LQHi46hWNKg1lXMjgcp1soNFB0eI,1635
30
+ lamindb/core/_settings.py,sha256=6jNadlQdimxCsKR2ZyUD0YJYzOdubTnKktki-MqEWqQ,6137
31
+ lamindb/core/_sync_git.py,sha256=lIgl6YfpH4rCFT1WILAp7zlemZfxog1d0zp3cX0KIZw,4531
32
+ lamindb/core/_track_environment.py,sha256=Ywzg_sJ7guI1dcsN7h5orce9VdYl8VGVE3OLITlHBXQ,820
33
+ lamindb/core/exceptions.py,sha256=0B36kOVo-Dik5KbSKvy5GPuMjUfhVb99dJwXl_J0ldo,1636
34
34
  lamindb/core/fields.py,sha256=47Jmh3efUr5ZscgimR_yckY-I3cNf8ScLutbwKCK3j4,162
35
35
  lamindb/core/loaders.py,sha256=KMTkDa73jkRVvI9uc5Fgr0t6mq22cAxBwhSlUZKUaBg,4016
36
- lamindb/core/schema.py,sha256=KiYQn_8fokSMztTNDe6qUocZzKXWxU32H-YChNJv51A,1877
36
+ lamindb/core/schema.py,sha256=Y1tGn93B236PtnYZkpgTNFgTXBcVujPM1qh1kNG6Nbs,3441
37
37
  lamindb/core/types.py,sha256=uVBqSVLoQaTkqP9nqsJhwU6yYnx8H5e6-ZxrB6vpOOw,265
38
38
  lamindb/core/versioning.py,sha256=KUpd94F5QpbDxx9eKgZwrpPyQpm9YeHVedrTEO2a_mI,4839
39
39
  lamindb/core/datasets/__init__.py,sha256=zRP98oqUAaXhqWyKMiH0s_ImVIuNeziQQ2kQ_t0f-DI,1353
@@ -52,10 +52,10 @@ lamindb/core/subsettings/__init__.py,sha256=KFHPzIE7f7Bj4RgMjGQF4CjTdHVG_VNFBrCn
52
52
  lamindb/core/subsettings/_creation_settings.py,sha256=54mfMH_osC753hpxcl7Dq1rwBD2LHnWveXtQpkLBITE,1194
53
53
  lamindb/core/subsettings/_transform_settings.py,sha256=4YbCuZtJo6zdytl6UQR4GvdDkTtT6SRBqVzofGzNOt8,583
54
54
  lamindb/integrations/__init__.py,sha256=RWGMYYIzr8zvmNPyVB4m-p4gMDhxdRbjES2Ed23OItw,215
55
- lamindb/integrations/_vitessce.py,sha256=S51wl7iF2QvQmrNcZ9yDdqTtcn_AAzuh0i5axKwQ2sM,4560
55
+ lamindb/integrations/_vitessce.py,sha256=uPl45_w4Uu9_BhpBDDVonC1nDOuAnB7DAnzi5w5bZAE,4032
56
56
  lamindb/setup/__init__.py,sha256=OwZpZzPDv5lPPGXZP7-zK6UdO4FHvvuBh439yZvIp3A,410
57
57
  lamindb/setup/core/__init__.py,sha256=SevlVrc2AZWL3uALbE5sopxBnIZPWZ1IB0NBDudiAL8,167
58
- lamindb-0.76.11.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
59
- lamindb-0.76.11.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
60
- lamindb-0.76.11.dist-info/METADATA,sha256=ZbXuWNskfvWAFEfX7FKkc5zakPnKW2IOSTx8rTZ9dag,2369
61
- lamindb-0.76.11.dist-info/RECORD,,
58
+ lamindb-0.76.13.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
59
+ lamindb-0.76.13.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
60
+ lamindb-0.76.13.dist-info/METADATA,sha256=Vo8AcZfSEBwW3NJsFndztG1b2fAVSquWw2OENmncc8M,2361
61
+ lamindb-0.76.13.dist-info/RECORD,,