lamindb 1.5.0__py3-none-any.whl → 1.5.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 +1 -1
- lamindb/core/_context.py +28 -15
- lamindb/core/storage/_tiledbsoma.py +29 -13
- lamindb/curators/core.py +31 -43
- lamindb/migrations/0093_alter_schemacomponent_unique_together.py +16 -0
- lamindb/models/_feature_manager.py +24 -8
- lamindb/models/artifact.py +4 -11
- lamindb/models/has_parents.py +67 -12
- lamindb/models/query_set.py +3 -2
- lamindb/models/record.py +3 -4
- lamindb/models/schema.py +23 -28
- {lamindb-1.5.0.dist-info → lamindb-1.5.1.dist-info}/METADATA +1 -1
- {lamindb-1.5.0.dist-info → lamindb-1.5.1.dist-info}/RECORD +15 -14
- {lamindb-1.5.0.dist-info → lamindb-1.5.1.dist-info}/LICENSE +0 -0
- {lamindb-1.5.0.dist-info → lamindb-1.5.1.dist-info}/WHEEL +0 -0
lamindb/__init__.py
CHANGED
lamindb/core/_context.py
CHANGED
@@ -259,8 +259,8 @@ class Context:
|
|
259
259
|
self,
|
260
260
|
transform: str | Transform | None = None,
|
261
261
|
*,
|
262
|
-
project: str | None = None,
|
263
|
-
space: str | None = None,
|
262
|
+
project: str | Project | None = None,
|
263
|
+
space: str | Space | None = None,
|
264
264
|
params: dict | None = None,
|
265
265
|
new_run: bool | None = None,
|
266
266
|
path: str | None = None,
|
@@ -273,9 +273,10 @@ class Context:
|
|
273
273
|
|
274
274
|
Args:
|
275
275
|
transform: A transform (stem) `uid` (or record). If `None`, auto-creates a `transform` with its `uid`.
|
276
|
-
project: A project `name` or `uid` for labeling entities created during the run.
|
277
|
-
space: A space `name` or `uid`
|
278
|
-
|
276
|
+
project: A project, its `name` or `uid` for labeling entities created during the run.
|
277
|
+
space: A restricted space, its `name` or `uid` for creating sensitive entities are created during the run.
|
278
|
+
The default is the common `"All"` space that every LaminDB instance has.
|
279
|
+
The `space` argument doesn't affect `Storage`, `ULabel`, `Feature`, `Schema`, `Param` and bionty entities as these provide structure that should typically be commonly accessible.
|
279
280
|
If you want to manually move entities to a different space, set the `.space` field (:doc:`docs:access`).
|
280
281
|
params: A dictionary of parameters to track for the run.
|
281
282
|
new_run: If `False`, loads the latest run of transform
|
@@ -309,20 +310,32 @@ class Context:
|
|
309
310
|
if project is None:
|
310
311
|
project = os.environ.get("LAMIN_CURRENT_PROJECT")
|
311
312
|
if project is not None:
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
if project_record is None:
|
316
|
-
raise InvalidArgument(
|
317
|
-
f"Project '{project}' not found, either create it with `ln.Project(name='...').save()` or fix typos."
|
313
|
+
if isinstance(project, Project):
|
314
|
+
assert project._state.adding is False, ( # noqa: S101
|
315
|
+
"Project must be saved before passing it to track()"
|
318
316
|
)
|
317
|
+
project_record = project
|
318
|
+
else:
|
319
|
+
project_record = Project.filter(
|
320
|
+
Q(name=project) | Q(uid=project)
|
321
|
+
).one_or_none()
|
322
|
+
if project_record is None:
|
323
|
+
raise InvalidArgument(
|
324
|
+
f"Project '{project}' not found, either create it with `ln.Project(name='...').save()` or fix typos."
|
325
|
+
)
|
319
326
|
self._project = project_record
|
320
327
|
if space is not None:
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
f"Space '{space}', please check on the hub UI whether you have the correct `uid` or `name`."
|
328
|
+
if isinstance(space, Space):
|
329
|
+
assert space._state.adding is False, ( # noqa: S101
|
330
|
+
"Space must be saved before passing it to track()"
|
325
331
|
)
|
332
|
+
space_record = space
|
333
|
+
else:
|
334
|
+
space_record = Space.filter(Q(name=space) | Q(uid=space)).one_or_none()
|
335
|
+
if space_record is None:
|
336
|
+
raise InvalidArgument(
|
337
|
+
f"Space '{space}', please check on the hub UI whether you have the correct `uid` or `name`."
|
338
|
+
)
|
326
339
|
self._space = space_record
|
327
340
|
self._logging_message_track = ""
|
328
341
|
self._logging_message_imports = ""
|
@@ -110,7 +110,7 @@ def save_tiledbsoma_experiment(
|
|
110
110
|
) -> Artifact:
|
111
111
|
"""Write `AnnData` to `tiledbsoma.Experiment`.
|
112
112
|
|
113
|
-
Reads `AnnData` objects, writes them to `tiledbsoma.Experiment`, creates & saves an
|
113
|
+
Reads `AnnData` objects, writes them to `tiledbsoma.Experiment`, creates & saves an :class:`~lamindb.Artifact`.
|
114
114
|
|
115
115
|
Populates a column `lamin_run_uid` column in `obs` with the current `run.uid`.
|
116
116
|
|
@@ -202,28 +202,44 @@ def save_tiledbsoma_experiment(
|
|
202
202
|
context=ctx,
|
203
203
|
)
|
204
204
|
|
205
|
+
prepare_experiment = False
|
205
206
|
resize_experiment = False
|
206
207
|
if registration_mapping is not None:
|
207
|
-
|
208
|
+
soma_version_parsed = version.parse(soma.__version__)
|
209
|
+
if soma_version_parsed < version.parse("1.15.0rc4"):
|
208
210
|
n_observations = len(registration_mapping.obs_axis.data)
|
209
211
|
else:
|
210
212
|
n_observations = registration_mapping.get_obs_shape()
|
211
|
-
|
213
|
+
prepare_experiment = soma_version_parsed >= version.parse("1.16.2")
|
214
|
+
resize_experiment = not prepare_experiment
|
212
215
|
else: # happens only if not appending and only one adata passed
|
213
216
|
assert len(adata_objects) == 1 # noqa: S101
|
214
217
|
n_observations = adata_objects[0].n_obs
|
215
218
|
|
216
219
|
logger.important(f"Writing the tiledbsoma store to {storepath_str}")
|
220
|
+
experiment_exists: bool | None = None
|
217
221
|
for adata_obj in adata_objects:
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
222
|
+
# do not recheck if True
|
223
|
+
if not experiment_exists and (resize_experiment or prepare_experiment):
|
224
|
+
experiment_exists = soma.Experiment.exists(storepath_str, context=ctx)
|
225
|
+
if experiment_exists:
|
226
|
+
# both can only happen if registration_mapping is not None
|
227
|
+
if resize_experiment:
|
228
|
+
soma_io.resize_experiment(
|
229
|
+
storepath_str,
|
230
|
+
nobs=n_observations,
|
231
|
+
nvars=registration_mapping.get_var_shapes(),
|
232
|
+
context=ctx,
|
233
|
+
)
|
234
|
+
resize_experiment = False
|
235
|
+
elif prepare_experiment:
|
236
|
+
registration_mapping.prepare_experiment(storepath_str, context=ctx)
|
237
|
+
prepare_experiment = False
|
238
|
+
registration_mapping_write = (
|
239
|
+
registration_mapping.subset_for_anndata(adata_obj)
|
240
|
+
if hasattr(registration_mapping, "subset_for_anndata")
|
241
|
+
else registration_mapping
|
242
|
+
)
|
227
243
|
soma_io.from_anndata(
|
228
244
|
storepath_str,
|
229
245
|
adata_obj,
|
@@ -231,7 +247,7 @@ def save_tiledbsoma_experiment(
|
|
231
247
|
context=ctx,
|
232
248
|
obs_id_name=obs_id_name,
|
233
249
|
var_id_name=var_id_name,
|
234
|
-
registration_mapping=
|
250
|
+
registration_mapping=registration_mapping_write,
|
235
251
|
**kwargs,
|
236
252
|
)
|
237
253
|
|
lamindb/curators/core.py
CHANGED
@@ -510,7 +510,7 @@ class DataFrameCurator(Curator):
|
|
510
510
|
categoricals=categoricals,
|
511
511
|
index=schema.index,
|
512
512
|
slot=slot,
|
513
|
-
|
513
|
+
maximal_set=schema.maximal_set,
|
514
514
|
)
|
515
515
|
|
516
516
|
@property
|
@@ -836,7 +836,7 @@ class SpatialDataCurator(SlotsCurator):
|
|
836
836
|
sub_slot = split_result[1]
|
837
837
|
data_object = self._dataset.attrs[split_result[1]]
|
838
838
|
data_object = pd.DataFrame([data_object])
|
839
|
-
self._slots[slot] = DataFrameCurator(data_object, slot_schema)
|
839
|
+
self._slots[slot] = DataFrameCurator(data_object, slot_schema, slot)
|
840
840
|
_assign_var_fields_categoricals_multimodal(
|
841
841
|
modality=table_key,
|
842
842
|
slot_type=sub_slot,
|
@@ -850,27 +850,20 @@ class SpatialDataCurator(SlotsCurator):
|
|
850
850
|
|
851
851
|
|
852
852
|
class CatVector:
|
853
|
-
"""
|
854
|
-
|
855
|
-
Args:
|
856
|
-
values_getter: A callable or iterable that returns the values to validate.
|
857
|
-
field: The field to validate against.
|
858
|
-
key: The name of the column to validate. Only used for logging.
|
859
|
-
values_setter: A callable that sets the values.
|
860
|
-
source: The source to validate against.
|
861
|
-
"""
|
853
|
+
"""Vector with categorical values."""
|
862
854
|
|
863
855
|
def __init__(
|
864
856
|
self,
|
865
|
-
values_getter: Callable
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
857
|
+
values_getter: Callable
|
858
|
+
| Iterable[str], # A callable or iterable that returns the values to validate.
|
859
|
+
field: FieldAttr, # The field to validate against.
|
860
|
+
key: str, # The name of the vector to validate. Only used for logging.
|
861
|
+
values_setter: Callable | None = None, # A callable that sets the values.
|
862
|
+
source: Record | None = None, # The ontology source to validate against.
|
870
863
|
feature: Feature | None = None,
|
871
864
|
cat_manager: DataFrameCatManager | None = None,
|
872
865
|
subtype_str: str = "",
|
873
|
-
maximal_set: bool =
|
866
|
+
maximal_set: bool = True, # whether unvalidated categoricals cause validation failure.
|
874
867
|
) -> None:
|
875
868
|
self._values_getter = values_getter
|
876
869
|
self._values_setter = values_setter
|
@@ -912,18 +905,20 @@ class CatVector:
|
|
912
905
|
@property
|
913
906
|
def is_validated(self) -> bool:
|
914
907
|
"""Whether the vector is validated."""
|
915
|
-
#
|
916
|
-
#
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
908
|
+
# if nothing was validated, something likely is fundamentally wrong
|
909
|
+
# should probably add a setting `at_least_one_validated`
|
910
|
+
result = True
|
911
|
+
if len(self.values) > 0 and len(self.values) == len(self._non_validated):
|
912
|
+
result = False
|
913
|
+
# len(self._non_validated) != 0
|
914
|
+
# if maximal_set is True, return False
|
915
|
+
# if maximal_set is False, return True
|
916
|
+
# len(self._non_validated) == 0
|
917
|
+
# return True
|
918
|
+
if len(self._non_validated) != 0:
|
919
|
+
if self._maximal_set:
|
920
|
+
result = False
|
921
|
+
return result
|
927
922
|
|
928
923
|
def _replace_synonyms(self) -> list[str]:
|
929
924
|
"""Replace synonyms in the vector with standardized values."""
|
@@ -1078,11 +1073,6 @@ class CatVector:
|
|
1078
1073
|
field_name = self._field.field.name
|
1079
1074
|
model_field = f"{registry.__name__}.{field_name}"
|
1080
1075
|
|
1081
|
-
def _log_mapping_info():
|
1082
|
-
logger.indent = ""
|
1083
|
-
logger.info(f'mapping "{self._key}" on {colors.italic(model_field)}')
|
1084
|
-
logger.indent = " "
|
1085
|
-
|
1086
1076
|
kwargs_current = get_current_filter_kwargs(
|
1087
1077
|
registry, {"organism": self._organism, "source": self._source}
|
1088
1078
|
)
|
@@ -1121,7 +1111,6 @@ class CatVector:
|
|
1121
1111
|
non_validated = [i for i in non_validated if i not in values_validated]
|
1122
1112
|
n_non_validated = len(non_validated)
|
1123
1113
|
if n_non_validated == 0:
|
1124
|
-
logger.indent = ""
|
1125
1114
|
logger.success(
|
1126
1115
|
f'"{self._key}" is validated against {colors.italic(model_field)}'
|
1127
1116
|
)
|
@@ -1143,14 +1132,12 @@ class CatVector:
|
|
1143
1132
|
warning_message += f" → fix typos, remove non-existent values, or save terms via: {colors.cyan(non_validated_hint_print)}"
|
1144
1133
|
if self._subtype_query_set is not None:
|
1145
1134
|
warning_message += f"\n → a valid label for subtype '{self._subtype_str}' has to be one of {self._subtype_query_set.list('name')}"
|
1146
|
-
|
1147
|
-
_log_mapping_info()
|
1135
|
+
logger.info(f'mapping "{self._key}" on {colors.italic(model_field)}')
|
1148
1136
|
logger.warning(warning_message)
|
1149
1137
|
if self._cat_manager is not None:
|
1150
1138
|
self._cat_manager._validate_category_error_messages = strip_ansi_codes(
|
1151
1139
|
warning_message
|
1152
1140
|
)
|
1153
|
-
logger.indent = ""
|
1154
1141
|
return non_validated, syn_mapper
|
1155
1142
|
|
1156
1143
|
def validate(self) -> None:
|
@@ -1218,7 +1205,7 @@ class DataFrameCatManager:
|
|
1218
1205
|
sources: dict[str, Record] | None = None,
|
1219
1206
|
index: Feature | None = None,
|
1220
1207
|
slot: str | None = None,
|
1221
|
-
|
1208
|
+
maximal_set: bool = False,
|
1222
1209
|
) -> None:
|
1223
1210
|
self._non_validated = None
|
1224
1211
|
self._index = index
|
@@ -1235,7 +1222,7 @@ class DataFrameCatManager:
|
|
1235
1222
|
self._validate_category_error_messages: str = ""
|
1236
1223
|
self._cat_vectors: dict[str, CatVector] = {}
|
1237
1224
|
self._slot = slot
|
1238
|
-
self._maximal_set =
|
1225
|
+
self._maximal_set = maximal_set
|
1239
1226
|
|
1240
1227
|
if columns_names is None:
|
1241
1228
|
columns_names = []
|
@@ -1280,7 +1267,6 @@ class DataFrameCatManager:
|
|
1280
1267
|
feature=feature,
|
1281
1268
|
cat_manager=self,
|
1282
1269
|
subtype_str=subtype_str,
|
1283
|
-
maximal_set=self._maximal_set,
|
1284
1270
|
)
|
1285
1271
|
if index is not None and index.dtype.startswith("cat"):
|
1286
1272
|
result = parse_dtype(index.dtype)[0]
|
@@ -1292,7 +1278,6 @@ class DataFrameCatManager:
|
|
1292
1278
|
key=key,
|
1293
1279
|
feature=index,
|
1294
1280
|
cat_manager=self,
|
1295
|
-
maximal_set=self._maximal_set,
|
1296
1281
|
)
|
1297
1282
|
|
1298
1283
|
@property
|
@@ -1330,7 +1315,7 @@ class DataFrameCatManager:
|
|
1330
1315
|
|
1331
1316
|
validated = True
|
1332
1317
|
for key, cat_vector in self._cat_vectors.items():
|
1333
|
-
logger.info(f"validating
|
1318
|
+
logger.info(f"validating vector {key}")
|
1334
1319
|
cat_vector.validate()
|
1335
1320
|
validated &= cat_vector.is_validated
|
1336
1321
|
self._is_validated = validated
|
@@ -1493,6 +1478,9 @@ def annotate_artifact(
|
|
1493
1478
|
else "columns"
|
1494
1479
|
)
|
1495
1480
|
features = slot_curator.cat._cat_vectors[name].records
|
1481
|
+
if features is None:
|
1482
|
+
logger.warning(f"no features found for slot {slot}")
|
1483
|
+
continue
|
1496
1484
|
itype = parse_cat_dtype(artifact.schema.slots[slot].itype, is_itype=True)[
|
1497
1485
|
"field"
|
1498
1486
|
]
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Generated by Django 5.2 on 2025-05-07 12:16
|
2
|
+
|
3
|
+
from django.db import migrations
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
dependencies = [
|
8
|
+
("lamindb", "0092_alter_artifactfeaturevalue_artifact_and_more"),
|
9
|
+
]
|
10
|
+
|
11
|
+
operations = [
|
12
|
+
migrations.AlterUniqueTogether(
|
13
|
+
name="schemacomponent",
|
14
|
+
unique_together={("composite", "slot"), ("composite", "slot", "component")},
|
15
|
+
),
|
16
|
+
]
|
@@ -49,7 +49,7 @@ from ._label_manager import _get_labels, describe_labels
|
|
49
49
|
from ._relations import (
|
50
50
|
dict_related_model_to_related_name,
|
51
51
|
)
|
52
|
-
from .feature import Feature, FeatureValue
|
52
|
+
from .feature import Feature, FeatureValue, parse_dtype
|
53
53
|
from .record import Record
|
54
54
|
from .run import Param, ParamManager, ParamManagerRun, ParamValue, Run
|
55
55
|
from .ulabel import ULabel
|
@@ -649,13 +649,22 @@ def filter_base(cls, _skip_validation: bool = True, **expression) -> QuerySet:
|
|
649
649
|
if cls == FeatureManager:
|
650
650
|
from .artifact import ArtifactFeatureValue
|
651
651
|
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
652
|
+
if value: # True
|
653
|
+
return Artifact.objects.exclude(
|
654
|
+
id__in=Subquery(
|
655
|
+
ArtifactFeatureValue.objects.filter(
|
656
|
+
featurevalue__feature=feature
|
657
|
+
).values("artifact_id")
|
658
|
+
)
|
659
|
+
)
|
660
|
+
else:
|
661
|
+
return Artifact.objects.exclude(
|
662
|
+
id__in=Subquery(
|
663
|
+
ArtifactFeatureValue.objects.filter(
|
664
|
+
featurevalue__feature=feature
|
665
|
+
).values("artifact_id")
|
666
|
+
)
|
657
667
|
)
|
658
|
-
)
|
659
668
|
if comparator in {"__startswith", "__contains"}:
|
660
669
|
logger.important(
|
661
670
|
f"currently not supporting `{comparator}`, using `__icontains` instead"
|
@@ -667,7 +676,14 @@ def filter_base(cls, _skip_validation: bool = True, **expression) -> QuerySet:
|
|
667
676
|
elif isinstance(value, (str, Record, bool)):
|
668
677
|
if comparator == "__isnull":
|
669
678
|
if cls == FeatureManager:
|
670
|
-
|
679
|
+
result = parse_dtype(feature.dtype)[0]
|
680
|
+
kwargs = {
|
681
|
+
f"links_{result['registry'].__name__.lower()}__feature": feature
|
682
|
+
}
|
683
|
+
if value: # True
|
684
|
+
return Artifact.objects.exclude(**kwargs)
|
685
|
+
else:
|
686
|
+
return Artifact.objects.filter(**kwargs)
|
671
687
|
else:
|
672
688
|
# because SQL is sensitive to whether querying with __in or not
|
673
689
|
# and might return multiple equivalent records for the latter
|
lamindb/models/artifact.py
CHANGED
@@ -17,7 +17,6 @@ from django.db.models import CASCADE, PROTECT, Q
|
|
17
17
|
from lamin_utils import colors, logger
|
18
18
|
from lamindb_setup import settings as setup_settings
|
19
19
|
from lamindb_setup._init_instance import register_storage_in_instance
|
20
|
-
from lamindb_setup.core import doc_args
|
21
20
|
from lamindb_setup.core._settings_storage import init_storage
|
22
21
|
from lamindb_setup.core.hashing import HASH_LENGTH, hash_dir, hash_file
|
23
22
|
from lamindb_setup.core.types import UPathStr
|
@@ -99,8 +98,6 @@ WARNING_RUN_TRANSFORM = "no run & transform got linked, call `ln.track()` & re-r
|
|
99
98
|
|
100
99
|
WARNING_NO_INPUT = "run input wasn't tracked, call `ln.track()` and re-run"
|
101
100
|
|
102
|
-
DEBUG_KWARGS_DOC = "**kwargs: Internal arguments for debugging."
|
103
|
-
|
104
101
|
try:
|
105
102
|
from ..core.storage._zarr import identify_zarr_type
|
106
103
|
except ImportError:
|
@@ -914,7 +911,7 @@ def add_labels(
|
|
914
911
|
for registry_name, records in records_by_registry.items():
|
915
912
|
if not from_curator and feature.name in internal_features:
|
916
913
|
raise ValidationError(
|
917
|
-
"Cannot manually annotate
|
914
|
+
"Cannot manually annotate a feature measured *within* the dataset. Please use a Curator."
|
918
915
|
)
|
919
916
|
if registry_name not in feature.dtype:
|
920
917
|
if not feature.dtype.startswith("cat"):
|
@@ -1236,7 +1233,7 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
|
|
1236
1233
|
default=None,
|
1237
1234
|
related_name="validated_artifacts",
|
1238
1235
|
)
|
1239
|
-
"""The schema that validated this artifact in a :class:`~lamindb.curators.Curator`."""
|
1236
|
+
"""The schema that validated this artifact in a :class:`~lamindb.curators.core.Curator`."""
|
1240
1237
|
feature_sets: Schema = models.ManyToManyField(
|
1241
1238
|
Schema, related_name="artifacts", through="ArtifactSchema"
|
1242
1239
|
)
|
@@ -2397,7 +2394,6 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
|
|
2397
2394
|
_track_run_input(self, is_run_input)
|
2398
2395
|
return access_memory
|
2399
2396
|
|
2400
|
-
@doc_args(DEBUG_KWARGS_DOC)
|
2401
2397
|
def cache(
|
2402
2398
|
self, *, is_run_input: bool | None = None, mute: bool = False, **kwargs
|
2403
2399
|
) -> Path:
|
@@ -2410,7 +2406,6 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
|
|
2410
2406
|
Args:
|
2411
2407
|
mute: Silence logging of caching progress.
|
2412
2408
|
is_run_input: Whether to track this artifact as run input.
|
2413
|
-
{}
|
2414
2409
|
|
2415
2410
|
Example::
|
2416
2411
|
|
@@ -2560,13 +2555,11 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
|
|
2560
2555
|
if delete_msg != "did-not-delete":
|
2561
2556
|
logger.success(f"deleted {colors.yellow(f'{path}')}")
|
2562
2557
|
|
2563
|
-
@doc_args(DEBUG_KWARGS_DOC)
|
2564
2558
|
def save(self, upload: bool | None = None, **kwargs) -> Artifact:
|
2565
2559
|
"""Save to database & storage.
|
2566
2560
|
|
2567
2561
|
Args:
|
2568
2562
|
upload: Trigger upload to cloud storage in instances with hybrid storage mode.
|
2569
|
-
{}
|
2570
2563
|
|
2571
2564
|
Example::
|
2572
2565
|
|
@@ -2763,8 +2756,8 @@ def _track_run_input(
|
|
2763
2756
|
# record is on another db
|
2764
2757
|
# we have to save the record into the current db with
|
2765
2758
|
# the run being attached to a transfer transform
|
2766
|
-
logger.
|
2767
|
-
f"completing transfer to track {data.__class__.__name__}('{data.uid[:8]}') as input"
|
2759
|
+
logger.info(
|
2760
|
+
f"completing transfer to track {data.__class__.__name__}('{data.uid[:8]}...') as input"
|
2768
2761
|
)
|
2769
2762
|
data.save()
|
2770
2763
|
is_valid = True
|
lamindb/models/has_parents.py
CHANGED
@@ -84,10 +84,44 @@ class HasParents:
|
|
84
84
|
return view_parents(
|
85
85
|
record=self, # type: ignore
|
86
86
|
field=field,
|
87
|
+
with_parents=True,
|
87
88
|
with_children=with_children,
|
88
89
|
distance=distance,
|
89
90
|
)
|
90
91
|
|
92
|
+
def view_children(
|
93
|
+
self,
|
94
|
+
field: StrField | None = None,
|
95
|
+
distance: int = 5,
|
96
|
+
):
|
97
|
+
"""View children in an ontology.
|
98
|
+
|
99
|
+
Args:
|
100
|
+
field: Field to display on graph
|
101
|
+
distance: Maximum distance still shown.
|
102
|
+
|
103
|
+
Ontological hierarchies: :class:`~lamindb.ULabel` (project & sub-project), :class:`~bionty.CellType` (cell type & subtype).
|
104
|
+
|
105
|
+
Examples:
|
106
|
+
>>> import bionty as bt
|
107
|
+
>>> bt.Tissue.from_source(name="subsegmental bronchus").save()
|
108
|
+
>>> record = bt.Tissue.get(name="respiratory tube")
|
109
|
+
>>> record.view_parents()
|
110
|
+
>>> tissue.view_parents(with_children=True)
|
111
|
+
"""
|
112
|
+
if field is None:
|
113
|
+
field = get_name_field(self)
|
114
|
+
if not isinstance(field, str):
|
115
|
+
field = field.field.name
|
116
|
+
|
117
|
+
return view_parents(
|
118
|
+
record=self, # type: ignore
|
119
|
+
field=field,
|
120
|
+
with_parents=False,
|
121
|
+
with_children=True,
|
122
|
+
distance=distance,
|
123
|
+
)
|
124
|
+
|
91
125
|
def query_parents(self) -> QuerySet:
|
92
126
|
"""Query parents in an ontology."""
|
93
127
|
return _query_relatives([self], "parents", self.__class__) # type: ignore
|
@@ -210,6 +244,7 @@ def view_lineage(
|
|
210
244
|
def view_parents(
|
211
245
|
record: Record,
|
212
246
|
field: str,
|
247
|
+
with_parents: bool = True,
|
213
248
|
with_children: bool = False,
|
214
249
|
distance: int = 100,
|
215
250
|
attr_name: Literal["parents", "predecessors"] = "parents",
|
@@ -223,11 +258,12 @@ def view_parents(
|
|
223
258
|
import pandas as pd
|
224
259
|
|
225
260
|
df_edges = None
|
226
|
-
df_edges_parents =
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
261
|
+
df_edges_parents = None
|
262
|
+
df_edges_children = None
|
263
|
+
if with_parents:
|
264
|
+
df_edges_parents = _df_edges_from_parents(
|
265
|
+
record=record, field=field, distance=distance, attr_name=attr_name
|
266
|
+
)
|
231
267
|
if with_children:
|
232
268
|
df_edges_children = _df_edges_from_parents(
|
233
269
|
record=record,
|
@@ -236,13 +272,32 @@ def view_parents(
|
|
236
272
|
children=True,
|
237
273
|
attr_name=attr_name,
|
238
274
|
)
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
275
|
+
# Rename the columns to swap source and target
|
276
|
+
df_edges_children = df_edges_children.rename(
|
277
|
+
columns={
|
278
|
+
"source": "temp_target",
|
279
|
+
"source_label": "temp_target_label",
|
280
|
+
"source_record": "temp_target_record",
|
281
|
+
"target": "source",
|
282
|
+
"target_label": "source_label",
|
283
|
+
"target_record": "source_record",
|
284
|
+
}
|
285
|
+
)
|
286
|
+
df_edges_children = df_edges_children.rename(
|
287
|
+
columns={
|
288
|
+
"temp_target": "target",
|
289
|
+
"temp_target_label": "target_label",
|
290
|
+
"temp_target_record": "target_record",
|
291
|
+
}
|
292
|
+
)
|
293
|
+
if df_edges_parents is not None and df_edges_children is not None:
|
294
|
+
df_edges = pd.concat([df_edges_parents, df_edges_children]).drop_duplicates()
|
295
|
+
elif df_edges_parents is not None:
|
296
|
+
df_edges = df_edges_parents
|
297
|
+
elif df_edges_children is not None:
|
298
|
+
df_edges = df_edges_children
|
299
|
+
else:
|
300
|
+
return None
|
246
301
|
|
247
302
|
record_label = _record_label(record, field)
|
248
303
|
|
lamindb/models/query_set.py
CHANGED
@@ -430,8 +430,9 @@ def reshape_annotate_result(
|
|
430
430
|
"""
|
431
431
|
cols_from_include = cols_from_include or {}
|
432
432
|
|
433
|
-
# initialize result with basic fields
|
434
|
-
|
433
|
+
# initialize result with basic fields, need a copy as we're modifying it
|
434
|
+
# will give us warnings otherwise
|
435
|
+
result = df[field_names].copy()
|
435
436
|
# process features if requested
|
436
437
|
if feature_names:
|
437
438
|
# handle feature_values
|
lamindb/models/record.py
CHANGED
@@ -596,9 +596,8 @@ class Registry(ModelBase):
|
|
596
596
|
|
597
597
|
target_modules = setup_settings.instance.modules
|
598
598
|
if missing_members := source_modules - target_modules:
|
599
|
-
logger.
|
600
|
-
f"source
|
601
|
-
"consider mounting these registry modules to transfer all metadata"
|
599
|
+
logger.info(
|
600
|
+
f"in transfer, source lamindb instance has additional modules: {', '.join(missing_members)}"
|
602
601
|
)
|
603
602
|
|
604
603
|
add_db_connection(db, instance)
|
@@ -839,7 +838,7 @@ class BasicRecord(models.Model, metaclass=Registry):
|
|
839
838
|
self.features._add_from(self_on_db, transfer_logs=transfer_logs)
|
840
839
|
self.labels.add_from(self_on_db, transfer_logs=transfer_logs)
|
841
840
|
for k, v in transfer_logs.items():
|
842
|
-
if k != "run":
|
841
|
+
if k != "run" and len(v) > 0:
|
843
842
|
logger.important(f"{k} records: {', '.join(v)}")
|
844
843
|
|
845
844
|
if (
|
lamindb/models/schema.py
CHANGED
@@ -439,7 +439,7 @@ class Schema(Record, CanCurate, TracksRun):
|
|
439
439
|
artifacts: Artifact
|
440
440
|
"""The artifacts that measure a feature set that matches this schema."""
|
441
441
|
validated_artifacts: Artifact
|
442
|
-
"""The artifacts that were validated against this schema with a :class:`~lamindb.curators.Curator`."""
|
442
|
+
"""The artifacts that were validated against this schema with a :class:`~lamindb.curators.core.Curator`."""
|
443
443
|
projects: Project
|
444
444
|
"""Linked projects."""
|
445
445
|
_curation: dict[str, Any] = JSONField(default=None, db_default=None, null=True)
|
@@ -457,7 +457,7 @@ class Schema(Record, CanCurate, TracksRun):
|
|
457
457
|
# For instance, the set of measured features might be a superset of the minimally required set of features.
|
458
458
|
# """
|
459
459
|
# validated_schemas: Schema
|
460
|
-
# """The schemas that were validated against this schema with a :class:`~lamindb.curators.Curator`."""
|
460
|
+
# """The schemas that were validated against this schema with a :class:`~lamindb.curators.core.Curator`."""
|
461
461
|
composite: Schema | None = ForeignKey(
|
462
462
|
"self", PROTECT, related_name="+", default=None, null=True
|
463
463
|
)
|
@@ -538,7 +538,6 @@ class Schema(Record, CanCurate, TracksRun):
|
|
538
538
|
optional_features,
|
539
539
|
features_registry,
|
540
540
|
flexible,
|
541
|
-
list_for_hashing,
|
542
541
|
) = self._validate_kwargs_calculate_hash(
|
543
542
|
features=features,
|
544
543
|
index=index,
|
@@ -562,7 +561,6 @@ class Schema(Record, CanCurate, TracksRun):
|
|
562
561
|
.filter(hash=validated_kwargs["hash"])
|
563
562
|
.one_or_none()
|
564
563
|
)
|
565
|
-
self._list_for_hashing = list_for_hashing
|
566
564
|
if schema is not None:
|
567
565
|
logger.important(f"returning existing schema with same hash: {schema}")
|
568
566
|
init_self_from_db(self, schema)
|
@@ -609,7 +607,7 @@ class Schema(Record, CanCurate, TracksRun):
|
|
609
607
|
coerce_dtype: bool,
|
610
608
|
n_features: int | None,
|
611
609
|
optional_features_manual: list[Feature] | None = None,
|
612
|
-
) -> tuple[list[Feature], dict[str, Any], list[Feature], Registry, bool
|
610
|
+
) -> tuple[list[Feature], dict[str, Any], list[Feature], Registry, bool]:
|
613
611
|
optional_features = []
|
614
612
|
features_registry: Registry = None
|
615
613
|
if itype is not None:
|
@@ -729,7 +727,6 @@ class Schema(Record, CanCurate, TracksRun):
|
|
729
727
|
optional_features,
|
730
728
|
features_registry,
|
731
729
|
flexible,
|
732
|
-
list_for_hashing,
|
733
730
|
)
|
734
731
|
|
735
732
|
@classmethod
|
@@ -865,26 +862,24 @@ class Schema(Record, CanCurate, TracksRun):
|
|
865
862
|
if hasattr(self, "_features")
|
866
863
|
else (self.members.list() if self.members.exists() else [])
|
867
864
|
)
|
868
|
-
_, validated_kwargs, _, _, _
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
optional_features_manual=self.optionals.get(),
|
887
|
-
)
|
865
|
+
_, validated_kwargs, _, _, _ = self._validate_kwargs_calculate_hash(
|
866
|
+
features=features, # type: ignore
|
867
|
+
index=None, # need to pass None here as otherwise counting double
|
868
|
+
slots=self.slots,
|
869
|
+
name=self.name,
|
870
|
+
description=self.description,
|
871
|
+
itype=self.itype,
|
872
|
+
flexible=self.flexible,
|
873
|
+
type=self.type,
|
874
|
+
is_type=self.is_type,
|
875
|
+
otype=self.otype,
|
876
|
+
dtype=self.dtype,
|
877
|
+
minimal_set=self.minimal_set,
|
878
|
+
ordered_set=self.ordered_set,
|
879
|
+
maximal_set=self.maximal_set,
|
880
|
+
coerce_dtype=self.coerce_dtype,
|
881
|
+
n_features=self.n,
|
882
|
+
optional_features_manual=self.optionals.get(),
|
888
883
|
)
|
889
884
|
if validated_kwargs["hash"] != self.hash:
|
890
885
|
from .artifact import Artifact
|
@@ -896,7 +891,6 @@ class Schema(Record, CanCurate, TracksRun):
|
|
896
891
|
)
|
897
892
|
self.hash = validated_kwargs["hash"]
|
898
893
|
self.n = validated_kwargs["n"]
|
899
|
-
self._list_for_hashing = list_for_hashing
|
900
894
|
super().save(*args, **kwargs)
|
901
895
|
if hasattr(self, "_slots"):
|
902
896
|
# analogous to save_schema_links in core._data.py
|
@@ -910,6 +904,7 @@ class Schema(Record, CanCurate, TracksRun):
|
|
910
904
|
}
|
911
905
|
links.append(Schema.components.through(**kwargs))
|
912
906
|
bulk_create(links, ignore_conflicts=True)
|
907
|
+
delattr(self, "_slots")
|
913
908
|
if hasattr(self, "_features"):
|
914
909
|
assert self.n > 0 # noqa: S101
|
915
910
|
using: bool | None = kwargs.pop("using", None)
|
@@ -1188,7 +1183,7 @@ class SchemaComponent(BasicRecord, LinkORM, TracksRun):
|
|
1188
1183
|
slot: str | None = CharField(null=True)
|
1189
1184
|
|
1190
1185
|
class Meta:
|
1191
|
-
unique_together = (("composite", "component"), ("composite", "slot"))
|
1186
|
+
unique_together = (("composite", "slot", "component"), ("composite", "slot"))
|
1192
1187
|
|
1193
1188
|
|
1194
1189
|
Schema._get_related_name = _get_related_name
|
@@ -1,4 +1,4 @@
|
|
1
|
-
lamindb/__init__.py,sha256=
|
1
|
+
lamindb/__init__.py,sha256=LgGBliPC6cKo95tRnosq_SuMBgRGjlQF0wNmEBHuo10,2676
|
2
2
|
lamindb/_finish.py,sha256=Wqb846pCErsx5ZPulAfdF5PJbWzgAdfbuYuf4FndfhY,20124
|
3
3
|
lamindb/_tracked.py,sha256=fse_H0ehc9WvU_l1572g7qya0sRdWCh22LZkq0XU4ic,4445
|
4
4
|
lamindb/_view.py,sha256=kSmG8X4ULQZEKxY7ESnthQqsUf1DEzoYGeTLYRU1I7s,4938
|
@@ -11,7 +11,7 @@ lamindb/base/uids.py,sha256=cLBi5mIlsf1ltkTb17r1FLzlOjlGmjvsCygoVJHQ-A8,2116
|
|
11
11
|
lamindb/base/users.py,sha256=8MSmAvCKoUF15YsDE6BGLBXsFWpfoEEg8iDTKZ7kD48,848
|
12
12
|
lamindb/core/__init__.py,sha256=aaBq0UVjNolMynbT1V5hB6UrJm1tK0M6WHu_r6em9_4,604
|
13
13
|
lamindb/core/_compat.py,sha256=NLnKk1qk4xdgMV-QwFDnBnbio02ujjlF86icvhpdv4c,2029
|
14
|
-
lamindb/core/_context.py,sha256=
|
14
|
+
lamindb/core/_context.py,sha256=xKj4YGpgM3Dx8H7_rNf6EI3sC5JHScSYVgHw-QYbMp4,34679
|
15
15
|
lamindb/core/_mapped_collection.py,sha256=dxyZ1ZHFn5SBl1xILqN9N6TTUJP0PptVBV-2O0EdZww,25751
|
16
16
|
lamindb/core/_settings.py,sha256=DAeEN2Qswj6VDlM7OE5YtoteMfFZ61CmMwcS056_scE,6211
|
17
17
|
lamindb/core/_sync_git.py,sha256=Z7keuyS5X7CAj285sEbZIFExZF9mtjGH8DzKwz3xhHw,5881
|
@@ -30,7 +30,7 @@ lamindb/core/storage/_anndata_sizes.py,sha256=aXO3OB--tF5MChenSsigW6Q-RuE8YJJOUT
|
|
30
30
|
lamindb/core/storage/_backed_access.py,sha256=LlpRDZ0skseZA5tBFu3-cH1wJwuXm7-NS2RgnTK7wgc,7382
|
31
31
|
lamindb/core/storage/_polars_lazy_df.py,sha256=Z0KMp0OU5S36L5g8EuJk7V_nn-spgG1lFeEFnkTOLcw,1350
|
32
32
|
lamindb/core/storage/_pyarrow_dataset.py,sha256=lRYYt7edUtwauhxd7RwFud6YPDbz2PFvYYgqLhfapfk,1398
|
33
|
-
lamindb/core/storage/_tiledbsoma.py,sha256=
|
33
|
+
lamindb/core/storage/_tiledbsoma.py,sha256=QLMOPjdxv9JFs9JR0Kqg1UTkJKNgwIDMeHAewB0-Lqg,11124
|
34
34
|
lamindb/core/storage/_valid_suffixes.py,sha256=vUSeQ4s01rdhD_vSd6wKmFBsgMJAKkBMnL_T9Y1znMg,501
|
35
35
|
lamindb/core/storage/_zarr.py,sha256=cisYXU4_QXMF_ZY2pV52Incus6365mMxRphLaHO76W0,6801
|
36
36
|
lamindb/core/storage/objects.py,sha256=n1Kj1soxF-_iLFyNnHriVFcngw6nqEAd7aVm0Hm8Tcw,3017
|
@@ -40,7 +40,7 @@ lamindb/core/subsettings/_annotation_settings.py,sha256=o-yTYw-NmjFmtehbKU8qnf7t
|
|
40
40
|
lamindb/core/subsettings/_creation_settings.py,sha256=NGHWKqCFSzVNBxAr2VnmdYguiFdW29XUK7T9wRsVshg,906
|
41
41
|
lamindb/curators/__init__.py,sha256=ZexikeaVunT24TqsR1NsSOCSBXDBigfGtFT55tBwqS8,371
|
42
42
|
lamindb/curators/_legacy.py,sha256=dTim3YFvdYyMsn6y8qSYkbCnnEI4tlaevN2-OO_qEx8,76174
|
43
|
-
lamindb/curators/core.py,sha256=
|
43
|
+
lamindb/curators/core.py,sha256=hbmVGXRwBNxKRRwpS9h9JR7AiVWZzqgI848FBdekDAQ,59818
|
44
44
|
lamindb/curators/_cellxgene_schemas/__init__.py,sha256=zqlFzMNMDGEBe6DV0gBsBMpfc9UHvNv1EpBsz_ktMoA,7502
|
45
45
|
lamindb/curators/_cellxgene_schemas/schema_versions.csv,sha256=X9rmO88TW1Fht1f5mJs0JdW-VPvyKSajpf8lHNeECj4,1680
|
46
46
|
lamindb/examples/__init__.py,sha256=DGImiuWYDvwxh78p5FCwQWClEwsE3ODLU49i_NqbW0c,533
|
@@ -74,35 +74,36 @@ lamindb/migrations/0090_runproject_project_runs.py,sha256=Ab9wyGxc6xjBfj-36cqdTl
|
|
74
74
|
lamindb/migrations/0090_squashed.py,sha256=kx_A_25BYantikxCbGhJughFpv_lqyHH86pMh5YevEE,160823
|
75
75
|
lamindb/migrations/0091_alter_featurevalue_options_alter_space_options_and_more.py,sha256=Df4EYAQlLKZ4BpFcsRRF52pGN3hDSo94laiO-V90Kn4,607
|
76
76
|
lamindb/migrations/0092_alter_artifactfeaturevalue_artifact_and_more.py,sha256=x-2Pvi0GJugkLrR--Fw9PBzV-HxqXjl0NktxRtRFJno,2459
|
77
|
+
lamindb/migrations/0093_alter_schemacomponent_unique_together.py,sha256=p6pCGU3xzOo5FuHE_COxVn6qLgRUAdfWXJjl4_euEKU,424
|
77
78
|
lamindb/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
78
79
|
lamindb/models/__init__.py,sha256=IFYoZfly3m0Me5Fr8sk6-KdYpVzkuug0lo8jvci00Kg,2080
|
79
80
|
lamindb/models/_describe.py,sha256=4PxaavWidEL8cyV2idbpC_7Zo0Jmjam3X1Bwe71IMaY,5489
|
80
81
|
lamindb/models/_django.py,sha256=2LFaTvIPtxIV8_T6Kx0cvquTetj7C3OcnKukUyC9msY,7705
|
81
|
-
lamindb/models/_feature_manager.py,sha256=
|
82
|
+
lamindb/models/_feature_manager.py,sha256=uMIcQMYrH1bHCqzovDbkDUOsBre_-mrg_7OS4RdoR8c,54666
|
82
83
|
lamindb/models/_from_values.py,sha256=-8l3_d2Nm14kzi1FjEYvBwyuucL-ZcDSjlMufIb4XoQ,13324
|
83
84
|
lamindb/models/_is_versioned.py,sha256=Th2_cBf9UWh27E6ANxg6LGmjBOumXFy7AjH0GG4FoXA,7601
|
84
85
|
lamindb/models/_label_manager.py,sha256=QOT6mz_rzPJ5p7hM1l-XzDWzyWUERpmAan2n_ma5wpI,12112
|
85
86
|
lamindb/models/_relations.py,sha256=ONjHPiWIa_Ur7zMNTa_9Uw7K-366GORyPvGoVjf4EQs,3681
|
86
|
-
lamindb/models/artifact.py,sha256=
|
87
|
+
lamindb/models/artifact.py,sha256=JDV9I5Pf47UgsyGGd4xgEvzH6sEmG37De7vJouaR4B0,110183
|
87
88
|
lamindb/models/artifact_set.py,sha256=VOZEGDo3m_9Yg_ftx3I2fwdydjHN61X_qV18N6xG4kM,4117
|
88
89
|
lamindb/models/can_curate.py,sha256=5dXHCRoJzLg2y9YDhpH7CyWexxliFHilwJ_UPjjZwRI,29188
|
89
90
|
lamindb/models/collection.py,sha256=TNXnrR86ZgsSfEvaOuAEItgZ947klTXXZspa7hpyVmw,27288
|
90
91
|
lamindb/models/core.py,sha256=A-W_Hdg4AmbBFBU38SEEVhOwSIzww5oNgYAQFnwOO7A,4018
|
91
92
|
lamindb/models/feature.py,sha256=WoT29eZ8DR6MTZgnztbRye3-zX4BRYfJ8HlhdenX2qA,28186
|
92
93
|
lamindb/models/flextable.py,sha256=ET9j0fTFYQIdXOZfwCnosXOag7nYD1DUV6_wZNqhvOs,5400
|
93
|
-
lamindb/models/has_parents.py,sha256=
|
94
|
+
lamindb/models/has_parents.py,sha256=A8OWsNotWlFrZB2pURRxp8EcHJ1kIlyV5eMnajGgkh4,20328
|
94
95
|
lamindb/models/project.py,sha256=Hm-5hLn-FffFK3J_68gt-AxVc6bo26fegwGFRw0Gp50,15225
|
95
96
|
lamindb/models/query_manager.py,sha256=mqsULCmUQf5ibpSXazca9ZYxyZwiDLuzSm8s6dPrl_M,10712
|
96
|
-
lamindb/models/query_set.py,sha256=
|
97
|
-
lamindb/models/record.py,sha256=
|
97
|
+
lamindb/models/query_set.py,sha256=xKh5QjAlHunktB1S4x9f42Fg0SP_-sK7XlyxStIRDSo,30385
|
98
|
+
lamindb/models/record.py,sha256=fomXuOcqkfiYF3zEdiUkYw9x00qP5flM7oseUUomhIo,61354
|
98
99
|
lamindb/models/run.py,sha256=FzqVQhYj4DXqlnmHvNIziOCAlx9K0wISXBLpom1Yb74,20688
|
99
100
|
lamindb/models/save.py,sha256=JTAaorKECx0ZeHaX0H9Yt4MDwOsT9F813WbSJkBIPaU,13339
|
100
|
-
lamindb/models/schema.py,sha256=
|
101
|
+
lamindb/models/schema.py,sha256=5_31iIPh19eJn0rm1OTeFk0gtD0YUPrTbiPsglKWOeo,47753
|
101
102
|
lamindb/models/transform.py,sha256=LGnTR7g_rAx3YFAFv4l4_UzabruKQlnui1Y3tlWHwXk,12731
|
102
103
|
lamindb/models/ulabel.py,sha256=yn9ttz28MqDBh6ZgwH7cty6GHCJOzLJn2IEpspYosDo,8793
|
103
104
|
lamindb/setup/__init__.py,sha256=OwZpZzPDv5lPPGXZP7-zK6UdO4FHvvuBh439yZvIp3A,410
|
104
105
|
lamindb/setup/core/__init__.py,sha256=SevlVrc2AZWL3uALbE5sopxBnIZPWZ1IB0NBDudiAL8,167
|
105
|
-
lamindb-1.5.
|
106
|
-
lamindb-1.5.
|
107
|
-
lamindb-1.5.
|
108
|
-
lamindb-1.5.
|
106
|
+
lamindb-1.5.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
107
|
+
lamindb-1.5.1.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
108
|
+
lamindb-1.5.1.dist-info/METADATA,sha256=juLd0ioSI6_wrvJOzGLsfY_8L019WlgLqEnIDj2OolA,2782
|
109
|
+
lamindb-1.5.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|