lamindb 1.9.1__py3-none-any.whl → 1.10.0__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/__init__.py +2 -2
- lamindb/core/storage/__init__.py +2 -1
- lamindb/core/storage/_anndata_accessor.py +10 -1
- lamindb/core/storage/_backed_access.py +4 -0
- lamindb/core/storage/_spatialdata_accessor.py +52 -0
- lamindb/examples/__init__.py +3 -18
- lamindb/examples/cellxgene/_cellxgene.py +11 -3
- lamindb/examples/croissant/__init__.py +44 -0
- lamindb/examples/croissant/mini_immuno.anndata.zarr_metadata.json +73 -0
- lamindb/{core → examples}/datasets/__init__.py +1 -1
- lamindb/{core → examples}/datasets/mini_immuno.py +19 -8
- lamindb/examples/schemas/_anndata.py +25 -15
- lamindb/examples/schemas/_simple.py +23 -9
- lamindb/integrations/__init__.py +2 -0
- lamindb/integrations/_croissant.py +122 -0
- lamindb/integrations/_vitessce.py +14 -12
- lamindb/migrations/0116_remove_artifact_unique_artifact_storage_key_hash_and_more.py +51 -0
- lamindb/migrations/0117_fix_artifact_storage_hash_unique_constraints.py +32 -0
- lamindb/migrations/{0115_squashed.py → 0117_squashed.py} +29 -6
- lamindb/models/_describe.py +107 -1
- lamindb/models/_django.py +63 -6
- lamindb/models/_feature_manager.py +0 -1
- lamindb/models/artifact.py +41 -11
- lamindb/models/collection.py +4 -9
- lamindb/models/project.py +2 -2
- lamindb/models/record.py +1 -1
- lamindb/models/run.py +1 -1
- lamindb/models/sqlrecord.py +3 -0
- {lamindb-1.9.1.dist-info → lamindb-1.10.0.dist-info}/METADATA +3 -3
- {lamindb-1.9.1.dist-info → lamindb-1.10.0.dist-info}/RECORD +36 -30
- /lamindb/{core → examples}/datasets/_core.py +0 -0
- /lamindb/{core → examples}/datasets/_fake.py +0 -0
- /lamindb/{core → examples}/datasets/_small.py +0 -0
- {lamindb-1.9.1.dist-info → lamindb-1.10.0.dist-info}/LICENSE +0 -0
- {lamindb-1.9.1.dist-info → lamindb-1.10.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
# Generated by Django 5.2 on 2025-07-26 15:55
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
import lamindb.base.fields
|
6
|
+
|
7
|
+
|
8
|
+
class Migration(migrations.Migration):
|
9
|
+
dependencies = [
|
10
|
+
("lamindb", "0115_alter_space_uid"),
|
11
|
+
]
|
12
|
+
|
13
|
+
operations = [
|
14
|
+
migrations.RemoveConstraint(
|
15
|
+
model_name="artifact",
|
16
|
+
name="unique_artifact_storage_key_hash",
|
17
|
+
),
|
18
|
+
migrations.AlterField(
|
19
|
+
model_name="record",
|
20
|
+
name="description",
|
21
|
+
field=lamindb.base.fields.CharField(
|
22
|
+
blank=True, db_index=True, default=None, max_length=255, null=True
|
23
|
+
),
|
24
|
+
),
|
25
|
+
migrations.AlterField(
|
26
|
+
model_name="reference",
|
27
|
+
name="text",
|
28
|
+
field=lamindb.base.fields.TextField(
|
29
|
+
blank=True, db_index=True, default=None, null=True
|
30
|
+
),
|
31
|
+
),
|
32
|
+
migrations.AlterField(
|
33
|
+
model_name="reference",
|
34
|
+
name="url",
|
35
|
+
field=lamindb.base.fields.URLField(blank=True, db_index=True, null=True),
|
36
|
+
),
|
37
|
+
migrations.AlterField(
|
38
|
+
model_name="run",
|
39
|
+
name="name",
|
40
|
+
field=lamindb.base.fields.CharField(
|
41
|
+
blank=True, db_index=True, default=None, max_length=150, null=True
|
42
|
+
),
|
43
|
+
),
|
44
|
+
migrations.AddConstraint(
|
45
|
+
model_name="artifact",
|
46
|
+
constraint=models.UniqueConstraint(
|
47
|
+
fields=("storage", "key", "hash"),
|
48
|
+
name="unique_artifact_storage_key_hash",
|
49
|
+
),
|
50
|
+
),
|
51
|
+
]
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Generated by Django 5.2 on 2025-07-26 18:50
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
dependencies = [
|
8
|
+
("lamindb", "0116_remove_artifact_unique_artifact_storage_key_hash_and_more"),
|
9
|
+
]
|
10
|
+
|
11
|
+
operations = [
|
12
|
+
migrations.RemoveConstraint(
|
13
|
+
model_name="artifact",
|
14
|
+
name="unique_artifact_storage_key_hash",
|
15
|
+
),
|
16
|
+
migrations.AddConstraint(
|
17
|
+
model_name="artifact",
|
18
|
+
constraint=models.UniqueConstraint(
|
19
|
+
condition=models.Q(("key__isnull", False)),
|
20
|
+
fields=("storage", "key", "hash"),
|
21
|
+
name="unique_artifact_storage_key_hash_not_null",
|
22
|
+
),
|
23
|
+
),
|
24
|
+
migrations.AddConstraint(
|
25
|
+
model_name="artifact",
|
26
|
+
constraint=models.UniqueConstraint(
|
27
|
+
condition=models.Q(("key__isnull", True)),
|
28
|
+
fields=("storage", "hash"),
|
29
|
+
name="unique_artifact_storage_hash_null_key",
|
30
|
+
),
|
31
|
+
),
|
32
|
+
]
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Generated by Django 5.2 on 2025-07-
|
1
|
+
# Generated by Django 5.2 on 2025-07-26 18:58
|
2
2
|
|
3
3
|
import django.core.validators
|
4
4
|
import django.db.models.deletion
|
@@ -137,6 +137,8 @@ class Migration(migrations.Migration):
|
|
137
137
|
("lamindb", "0113_lower_case_branch_and_space_names"),
|
138
138
|
("lamindb", "0114_alter_run__status_code"),
|
139
139
|
("lamindb", "0115_alter_space_uid"),
|
140
|
+
("lamindb", "0116_remove_artifact_unique_artifact_storage_key_hash_and_more"),
|
141
|
+
("lamindb", "0117_fix_artifact_storage_hash_unique_constraints"),
|
140
142
|
]
|
141
143
|
|
142
144
|
dependencies = [] # type: ignore
|
@@ -1477,7 +1479,11 @@ class Migration(migrations.Migration):
|
|
1477
1479
|
(
|
1478
1480
|
"description",
|
1479
1481
|
lamindb.base.fields.CharField(
|
1480
|
-
blank=True,
|
1482
|
+
blank=True,
|
1483
|
+
db_index=True,
|
1484
|
+
default=None,
|
1485
|
+
max_length=255,
|
1486
|
+
null=True,
|
1481
1487
|
),
|
1482
1488
|
),
|
1483
1489
|
(
|
@@ -1825,7 +1831,10 @@ class Migration(migrations.Migration):
|
|
1825
1831
|
null=True,
|
1826
1832
|
),
|
1827
1833
|
),
|
1828
|
-
(
|
1834
|
+
(
|
1835
|
+
"url",
|
1836
|
+
lamindb.base.fields.URLField(blank=True, db_index=True, null=True),
|
1837
|
+
),
|
1829
1838
|
(
|
1830
1839
|
"pubmed_id",
|
1831
1840
|
lamindb.base.fields.BigIntegerField(
|
@@ -1860,7 +1869,9 @@ class Migration(migrations.Migration):
|
|
1860
1869
|
),
|
1861
1870
|
(
|
1862
1871
|
"text",
|
1863
|
-
lamindb.base.fields.TextField(
|
1872
|
+
lamindb.base.fields.TextField(
|
1873
|
+
blank=True, db_index=True, default=None, null=True
|
1874
|
+
),
|
1864
1875
|
),
|
1865
1876
|
(
|
1866
1877
|
"date",
|
@@ -1989,7 +2000,11 @@ class Migration(migrations.Migration):
|
|
1989
2000
|
(
|
1990
2001
|
"name",
|
1991
2002
|
lamindb.base.fields.CharField(
|
1992
|
-
blank=True,
|
2003
|
+
blank=True,
|
2004
|
+
db_index=True,
|
2005
|
+
default=None,
|
2006
|
+
max_length=150,
|
2007
|
+
null=True,
|
1993
2008
|
),
|
1994
2009
|
),
|
1995
2010
|
(
|
@@ -4472,7 +4487,15 @@ class Migration(migrations.Migration):
|
|
4472
4487
|
constraint=models.UniqueConstraint(
|
4473
4488
|
condition=models.Q(("key__isnull", False)),
|
4474
4489
|
fields=("storage", "key", "hash"),
|
4475
|
-
name="
|
4490
|
+
name="unique_artifact_storage_key_hash_not_null",
|
4491
|
+
),
|
4492
|
+
),
|
4493
|
+
migrations.AddConstraint(
|
4494
|
+
model_name="artifact",
|
4495
|
+
constraint=models.UniqueConstraint(
|
4496
|
+
condition=models.Q(("key__isnull", True)),
|
4497
|
+
fields=("storage", "hash"),
|
4498
|
+
name="unique_artifact_storage_hash_null_key",
|
4476
4499
|
),
|
4477
4500
|
),
|
4478
4501
|
]
|
lamindb/models/_describe.py
CHANGED
@@ -179,7 +179,7 @@ def describe_artifact_general(
|
|
179
179
|
two_column_items.append(Text.assemble(("branch: ", "dim"), branch_name))
|
180
180
|
# actually not name field here, but handle
|
181
181
|
created_by_handle = (
|
182
|
-
foreign_key_data["
|
182
|
+
foreign_key_data["created_by"]["name"]
|
183
183
|
if foreign_key_data
|
184
184
|
else self.created_by.handle
|
185
185
|
)
|
@@ -234,3 +234,109 @@ def describe_artifact_general(
|
|
234
234
|
)
|
235
235
|
)
|
236
236
|
return tree
|
237
|
+
|
238
|
+
|
239
|
+
def describe_collection_general(
|
240
|
+
self: Collection,
|
241
|
+
tree: Tree | None = None,
|
242
|
+
foreign_key_data: dict[str, dict[str, int | str]] | None = None,
|
243
|
+
) -> Tree:
|
244
|
+
if tree is None:
|
245
|
+
tree = describe_header(self)
|
246
|
+
|
247
|
+
# add general information (order is the same as in API docs)
|
248
|
+
general = tree.add(Text("General", style="bold bright_cyan"))
|
249
|
+
|
250
|
+
if self.key:
|
251
|
+
general.add(Text.assemble(("key: ", "dim"), (f"{self.key}", "cyan3")))
|
252
|
+
if self.description:
|
253
|
+
general.add(
|
254
|
+
Text.assemble(
|
255
|
+
("description: ", "dim"),
|
256
|
+
f"{self.description}",
|
257
|
+
)
|
258
|
+
)
|
259
|
+
|
260
|
+
# Two column items (short content)
|
261
|
+
two_column_items = []
|
262
|
+
|
263
|
+
two_column_items.append(Text.assemble(("uid: ", "dim"), f"{self.uid}"))
|
264
|
+
|
265
|
+
transform_name = (
|
266
|
+
foreign_key_data["transform"]["name"]
|
267
|
+
if foreign_key_data and "transform" in foreign_key_data
|
268
|
+
else self.transform.name
|
269
|
+
if self.transform
|
270
|
+
else None
|
271
|
+
)
|
272
|
+
if transform_name:
|
273
|
+
two_column_items.append(
|
274
|
+
Text.assemble(
|
275
|
+
("transform: ", "dim"),
|
276
|
+
(f"{transform_name}", "cyan3"),
|
277
|
+
)
|
278
|
+
)
|
279
|
+
|
280
|
+
space_name = (
|
281
|
+
foreign_key_data["space"]["name"]
|
282
|
+
if foreign_key_data and "space" in foreign_key_data
|
283
|
+
else self.space.name
|
284
|
+
if self.space
|
285
|
+
else None
|
286
|
+
)
|
287
|
+
if space_name:
|
288
|
+
two_column_items.append(Text.assemble(("space: ", "dim"), space_name))
|
289
|
+
|
290
|
+
branch_name = (
|
291
|
+
foreign_key_data["branch"]["name"]
|
292
|
+
if foreign_key_data and "branch" in foreign_key_data
|
293
|
+
else self.branch.name
|
294
|
+
if self.branch
|
295
|
+
else None
|
296
|
+
)
|
297
|
+
if branch_name:
|
298
|
+
two_column_items.append(Text.assemble(("branch: ", "dim"), branch_name))
|
299
|
+
|
300
|
+
created_by_handle = (
|
301
|
+
foreign_key_data["created_by"]["name"]
|
302
|
+
if foreign_key_data and "created_by" in foreign_key_data
|
303
|
+
else self.created_by.handle
|
304
|
+
if self.created_by
|
305
|
+
else None
|
306
|
+
)
|
307
|
+
if created_by_handle:
|
308
|
+
two_column_items.append(
|
309
|
+
Text.assemble(
|
310
|
+
("created_by: ", "dim"),
|
311
|
+
(created_by_handle),
|
312
|
+
)
|
313
|
+
)
|
314
|
+
|
315
|
+
if self.created_at:
|
316
|
+
two_column_items.append(
|
317
|
+
Text.assemble(("created_at: ", "dim"), highlight_time(str(self.created_at)))
|
318
|
+
)
|
319
|
+
|
320
|
+
if self.version:
|
321
|
+
two_column_items.append(Text.assemble(("version: ", "dim"), f"{self.version}"))
|
322
|
+
|
323
|
+
# Add two-column items in pairs
|
324
|
+
for i in range(0, len(two_column_items), 2):
|
325
|
+
if i + 1 < len(two_column_items):
|
326
|
+
# Two items side by side
|
327
|
+
left_item = two_column_items[i]
|
328
|
+
right_item = two_column_items[i + 1]
|
329
|
+
|
330
|
+
# Create padded version by calculating the plain text length
|
331
|
+
left_plain_text = (
|
332
|
+
left_item.plain if hasattr(left_item, "plain") else str(left_item)
|
333
|
+
)
|
334
|
+
padding_needed = max(0, 45 - len(left_plain_text))
|
335
|
+
padding = " " * padding_needed
|
336
|
+
|
337
|
+
general.add(Text.assemble(left_item, padding, right_item))
|
338
|
+
else:
|
339
|
+
# Single item (odd number)
|
340
|
+
general.add(two_column_items[i])
|
341
|
+
|
342
|
+
return tree
|
lamindb/models/_django.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from typing import TYPE_CHECKING
|
3
|
+
from typing import TYPE_CHECKING, Any
|
4
4
|
|
5
5
|
from django.contrib.postgres.aggregates import ArrayAgg
|
6
6
|
from django.db import connection
|
@@ -13,8 +13,7 @@ from ._relations import dict_related_model_to_related_name, get_schema_modules
|
|
13
13
|
from .schema import Schema
|
14
14
|
|
15
15
|
if TYPE_CHECKING:
|
16
|
-
from .artifact import Artifact
|
17
|
-
from .sqlrecord import SQLRecord
|
16
|
+
from .artifact import Artifact, Collection
|
18
17
|
|
19
18
|
|
20
19
|
def patch_many_to_many_descriptor() -> None:
|
@@ -32,7 +31,8 @@ def patch_many_to_many_descriptor() -> None:
|
|
32
31
|
def patched_get(self, instance, cls=None):
|
33
32
|
if instance is not None and instance.pk is None:
|
34
33
|
raise ValueError(
|
35
|
-
f"You are trying to access the many-to-many relationships of an unsaved {instance.__class__.__name__} object.
|
34
|
+
f"You are trying to access the many-to-many relationships of an unsaved {instance.__class__.__name__} object. "
|
35
|
+
f"Please save it first using '.save()'."
|
36
36
|
)
|
37
37
|
|
38
38
|
manager = original_get(self, instance, cls)
|
@@ -77,12 +77,12 @@ def get_related_model(model, field_name):
|
|
77
77
|
|
78
78
|
|
79
79
|
def get_artifact_with_related(
|
80
|
-
artifact:
|
80
|
+
artifact: Artifact,
|
81
81
|
include_fk: bool = False,
|
82
82
|
include_m2m: bool = False,
|
83
83
|
include_feature_link: bool = False,
|
84
84
|
include_schema: bool = False,
|
85
|
-
) -> dict:
|
85
|
+
) -> dict[str, Any]:
|
86
86
|
"""Fetch an artifact with its related data."""
|
87
87
|
from ._label_manager import EXCLUDE_LABELS
|
88
88
|
from .can_curate import get_name_field
|
@@ -234,6 +234,63 @@ def get_artifact_with_related(
|
|
234
234
|
}
|
235
235
|
|
236
236
|
|
237
|
+
def get_collection_with_related(
|
238
|
+
collection: Collection,
|
239
|
+
include_fk: bool = False,
|
240
|
+
) -> dict[str, Any]:
|
241
|
+
"""Fetch a collection with its related data."""
|
242
|
+
from .can_curate import get_name_field
|
243
|
+
|
244
|
+
model = collection.__class__
|
245
|
+
schema_modules = get_schema_modules(collection._state.db)
|
246
|
+
|
247
|
+
foreign_key_fields = [
|
248
|
+
f.name
|
249
|
+
for f in model._meta.fields
|
250
|
+
if f.is_relation and f.related_model.__get_module_name__() in schema_modules
|
251
|
+
]
|
252
|
+
|
253
|
+
# Clear previous queries
|
254
|
+
connection.queries_log.clear()
|
255
|
+
|
256
|
+
annotations = {}
|
257
|
+
|
258
|
+
if include_fk:
|
259
|
+
for fk in foreign_key_fields:
|
260
|
+
name_field = get_name_field(get_related_model(model, fk))
|
261
|
+
if fk == "run":
|
262
|
+
annotations[f"fkfield_{fk}"] = JSONObject(
|
263
|
+
id=F(f"{fk}__id"),
|
264
|
+
name=F(f"{fk}__{name_field}"),
|
265
|
+
transform_key=F(f"{fk}__transform__key"),
|
266
|
+
)
|
267
|
+
else:
|
268
|
+
annotations[f"fkfield_{fk}"] = JSONObject(
|
269
|
+
id=F(f"{fk}__id"), name=F(f"{fk}__{name_field}")
|
270
|
+
)
|
271
|
+
|
272
|
+
collection_meta = (
|
273
|
+
model.objects.using(collection._state.db)
|
274
|
+
.filter(uid=collection.uid)
|
275
|
+
.annotate(**annotations)
|
276
|
+
.values(*["id", "uid"], *annotations.keys())
|
277
|
+
.first()
|
278
|
+
)
|
279
|
+
|
280
|
+
if not collection_meta:
|
281
|
+
return None
|
282
|
+
|
283
|
+
related_data: dict = {"fk": {}}
|
284
|
+
for k, v in collection_meta.items():
|
285
|
+
if k.startswith("fkfield_") and v is not None:
|
286
|
+
related_data["fk"][k[8:]] = v
|
287
|
+
|
288
|
+
return {
|
289
|
+
**{name: collection_meta[name] for name in ["id", "uid"]},
|
290
|
+
"related_data": related_data,
|
291
|
+
}
|
292
|
+
|
293
|
+
|
237
294
|
def get_schema_m2m_relations(artifact: Artifact, slot_schema: dict, limit: int = 20):
|
238
295
|
"""Fetch all many-to-many relationships for given feature sets."""
|
239
296
|
from .can_curate import get_name_field
|
lamindb/models/artifact.py
CHANGED
@@ -67,7 +67,7 @@ from ..errors import IntegrityError, InvalidArgument, ValidationError
|
|
67
67
|
from ..models._is_versioned import (
|
68
68
|
create_uid,
|
69
69
|
)
|
70
|
-
from ._django import get_artifact_with_related
|
70
|
+
from ._django import get_artifact_with_related, get_collection_with_related
|
71
71
|
from ._feature_manager import (
|
72
72
|
FeatureManager,
|
73
73
|
filter_base,
|
@@ -117,7 +117,11 @@ if TYPE_CHECKING:
|
|
117
117
|
from tiledbsoma import Measurement as SOMAMeasurement
|
118
118
|
|
119
119
|
from lamindb.base.types import StrField
|
120
|
-
from lamindb.core.storage._backed_access import
|
120
|
+
from lamindb.core.storage._backed_access import (
|
121
|
+
AnnDataAccessor,
|
122
|
+
BackedAccessor,
|
123
|
+
SpatialDataAccessor,
|
124
|
+
)
|
121
125
|
from lamindb.core.types import ScverseDataStructures
|
122
126
|
|
123
127
|
from ..base.types import (
|
@@ -709,7 +713,11 @@ def save_schema_links(self: Artifact) -> None:
|
|
709
713
|
|
710
714
|
|
711
715
|
def _describe_postgres(self): # for Artifact & Collection
|
712
|
-
from ._describe import
|
716
|
+
from ._describe import (
|
717
|
+
describe_artifact_general,
|
718
|
+
describe_collection_general,
|
719
|
+
describe_header,
|
720
|
+
)
|
713
721
|
from ._feature_manager import describe_features
|
714
722
|
|
715
723
|
model_name = self.__class__.__name__
|
@@ -737,13 +745,23 @@ def _describe_postgres(self): # for Artifact & Collection
|
|
737
745
|
related_data=related_data,
|
738
746
|
with_labels=True,
|
739
747
|
)
|
748
|
+
elif model_name == "Collection":
|
749
|
+
result = get_collection_with_related(self, include_fk=True)
|
750
|
+
tree = describe_collection_general(
|
751
|
+
self, foreign_key_data=related_data.get("fk", {})
|
752
|
+
)
|
753
|
+
return tree
|
740
754
|
else:
|
741
755
|
tree = describe_header(self)
|
742
756
|
return tree
|
743
757
|
|
744
758
|
|
745
759
|
def _describe_sqlite(self, print_types: bool = False): # for artifact & collection
|
746
|
-
from ._describe import
|
760
|
+
from ._describe import (
|
761
|
+
describe_artifact_general,
|
762
|
+
describe_collection_general,
|
763
|
+
describe_header,
|
764
|
+
)
|
747
765
|
from ._feature_manager import describe_features
|
748
766
|
from .collection import Collection
|
749
767
|
|
@@ -786,6 +804,9 @@ def _describe_sqlite(self, print_types: bool = False): # for artifact & collect
|
|
786
804
|
tree=tree,
|
787
805
|
with_labels=True,
|
788
806
|
)
|
807
|
+
elif model_name == "Collection":
|
808
|
+
tree = describe_collection_general(self)
|
809
|
+
return tree
|
789
810
|
else:
|
790
811
|
tree = describe_header(self)
|
791
812
|
return tree
|
@@ -983,7 +1004,7 @@ class Artifact(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
983
1004
|
|
984
1005
|
Args:
|
985
1006
|
data: `UPathStr` A path to a local or remote folder or file.
|
986
|
-
kind: `Literal["dataset", "model"] | None = None` Distinguish models from datasets from other files & folders.
|
1007
|
+
kind: `Literal["dataset", "model"] | str | None = None` Distinguish models from datasets from other files & folders.
|
987
1008
|
key: `str | None = None` A path-like key to reference artifact in default storage, e.g., `"myfolder/myfile.fcs"`. Artifacts with the same key form a version family.
|
988
1009
|
description: `str | None = None` A description.
|
989
1010
|
revises: `Artifact | None = None` Previous version of the artifact. Is an alternative way to passing `key` to trigger a new version.
|
@@ -1095,11 +1116,19 @@ class Artifact(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
1095
1116
|
# folders
|
1096
1117
|
# the conditional composite constraint allows duplicating files in different parts of the
|
1097
1118
|
# file hierarchy, but errors if the same file is to be registered with the same key
|
1098
|
-
#
|
1119
|
+
# In SQL, NULL values are treated specially in unique constraints.
|
1120
|
+
# Multiple NULL values are not considered equal to each other for uniqueness purposes.
|
1121
|
+
# For non-NULL keys
|
1099
1122
|
models.UniqueConstraint(
|
1100
1123
|
fields=["storage", "key", "hash"],
|
1101
|
-
|
1102
|
-
|
1124
|
+
condition=models.Q(key__isnull=False),
|
1125
|
+
name="unique_artifact_storage_key_hash_not_null",
|
1126
|
+
),
|
1127
|
+
# For NULL keys (only storage + hash need to be unique)
|
1128
|
+
models.UniqueConstraint(
|
1129
|
+
fields=["storage", "hash"],
|
1130
|
+
condition=models.Q(key__isnull=True),
|
1131
|
+
name="unique_artifact_storage_hash_null_key",
|
1103
1132
|
),
|
1104
1133
|
]
|
1105
1134
|
|
@@ -1209,12 +1238,12 @@ class Artifact(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
1209
1238
|
|
1210
1239
|
This is either a file suffix (`".csv"`, `".h5ad"`, etc.) or the empty string "".
|
1211
1240
|
"""
|
1212
|
-
kind: ArtifactKind | None = CharField(
|
1241
|
+
kind: ArtifactKind | str | None = CharField(
|
1213
1242
|
max_length=20,
|
1214
1243
|
db_index=True,
|
1215
1244
|
null=True,
|
1216
1245
|
)
|
1217
|
-
""":class:`~lamindb.base.types.ArtifactKind` (default `None`)."""
|
1246
|
+
""":class:`~lamindb.base.types.ArtifactKind` or custom `str` value (default `None`)."""
|
1218
1247
|
otype: str | None = CharField(
|
1219
1248
|
max_length=64, db_index=True, null=True, editable=False
|
1220
1249
|
)
|
@@ -1327,7 +1356,7 @@ class Artifact(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
1327
1356
|
# here; and we might refactor this but we might also keep that internal
|
1328
1357
|
# usage
|
1329
1358
|
data: UPathStr,
|
1330
|
-
kind: ArtifactKind | None = None,
|
1359
|
+
kind: ArtifactKind | str | None = None,
|
1331
1360
|
key: str | None = None,
|
1332
1361
|
description: str | None = None,
|
1333
1362
|
revises: Artifact | None = None,
|
@@ -2246,6 +2275,7 @@ class Artifact(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
2246
2275
|
**kwargs,
|
2247
2276
|
) -> (
|
2248
2277
|
AnnDataAccessor
|
2278
|
+
| SpatialDataAccessor
|
2249
2279
|
| BackedAccessor
|
2250
2280
|
| SOMACollection
|
2251
2281
|
| SOMAExperiment
|
lamindb/models/collection.py
CHANGED
@@ -1,12 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import warnings
|
4
|
-
from typing import
|
5
|
-
TYPE_CHECKING,
|
6
|
-
Any,
|
7
|
-
Literal,
|
8
|
-
overload,
|
9
|
-
)
|
4
|
+
from typing import TYPE_CHECKING, Any, Literal, overload
|
10
5
|
|
11
6
|
import anndata as ad
|
12
7
|
import pandas as pd
|
@@ -132,7 +127,7 @@ class Collection(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
132
127
|
"""Versioned collections of artifacts.
|
133
128
|
|
134
129
|
Args:
|
135
|
-
artifacts: `list[Artifact]`
|
130
|
+
artifacts: `Artifact | list[Artifact]` One or several artifacts.
|
136
131
|
key: `str` A file-path like key, analogous to the `key` parameter of `Artifact` and `Transform`.
|
137
132
|
description: `str | None = None` A description.
|
138
133
|
revises: `Collection | None = None` An old version of the collection.
|
@@ -232,7 +227,7 @@ class Collection(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
232
227
|
@overload
|
233
228
|
def __init__(
|
234
229
|
self,
|
235
|
-
artifacts: list[Artifact],
|
230
|
+
artifacts: Artifact | list[Artifact],
|
236
231
|
key: str,
|
237
232
|
description: str | None = None,
|
238
233
|
meta: Any | None = None,
|
@@ -259,7 +254,7 @@ class Collection(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
259
254
|
# now we proceed with the user-facing constructor
|
260
255
|
if len(args) > 1:
|
261
256
|
raise ValueError("Only one non-keyword arg allowed: artifacts")
|
262
|
-
artifacts: Artifact |
|
257
|
+
artifacts: Artifact | list[Artifact] = (
|
263
258
|
kwargs.pop("artifacts") if len(args) == 0 else args[0]
|
264
259
|
)
|
265
260
|
meta_artifact: Artifact | None = kwargs.pop("meta_artifact", None)
|
lamindb/models/project.py
CHANGED
@@ -133,7 +133,7 @@ class Reference(SQLRecord, CanCurate, TracksRun, TracksUpdates, ValidateFields):
|
|
133
133
|
null=True,
|
134
134
|
)
|
135
135
|
"""An abbreviation for the reference."""
|
136
|
-
url: str | None = URLField(null=True)
|
136
|
+
url: str | None = URLField(null=True, db_index=True)
|
137
137
|
"""URL linking to the reference."""
|
138
138
|
pubmed_id: int | None = BigIntegerField(null=True, db_index=True)
|
139
139
|
"""A PudMmed ID."""
|
@@ -150,7 +150,7 @@ class Reference(SQLRecord, CanCurate, TracksRun, TracksUpdates, ValidateFields):
|
|
150
150
|
"""Digital Object Identifier (DOI) for the reference."""
|
151
151
|
description: str | None = CharField(null=True, db_index=True)
|
152
152
|
"""Description of the reference."""
|
153
|
-
text: str | None = TextField(null=True)
|
153
|
+
text: str | None = TextField(null=True, db_index=True)
|
154
154
|
"""Abstract or full text of the reference to make it searchable."""
|
155
155
|
date: DateType | None = DateField(null=True, default=None)
|
156
156
|
"""Date of creation or publication of the reference."""
|
lamindb/models/record.py
CHANGED
@@ -93,7 +93,7 @@ class Record(SQLRecord, CanCurate, TracksRun, TracksUpdates):
|
|
93
93
|
"""Record-like components of this record."""
|
94
94
|
composites: Record
|
95
95
|
"""Record-like composites of this record."""
|
96
|
-
description: str | None = CharField(null=True)
|
96
|
+
description: str | None = CharField(null=True, db_index=True)
|
97
97
|
"""A description (optional)."""
|
98
98
|
linked_artifacts: Artifact = models.ManyToManyField(
|
99
99
|
Artifact, through="RecordArtifact", related_name="linked_in_records"
|
lamindb/models/run.py
CHANGED
@@ -232,7 +232,7 @@ class Run(SQLRecord):
|
|
232
232
|
editable=False, unique=True, db_index=True, max_length=20, default=base62_16
|
233
233
|
)
|
234
234
|
"""Universal id, valid across DB instances."""
|
235
|
-
name: str | None = CharField(max_length=150, null=True)
|
235
|
+
name: str | None = CharField(max_length=150, null=True, db_index=True)
|
236
236
|
"""A name."""
|
237
237
|
transform: Transform = ForeignKey("Transform", CASCADE, related_name="runs")
|
238
238
|
"""The transform :class:`~lamindb.Transform` that is being run."""
|
lamindb/models/sqlrecord.py
CHANGED
@@ -1764,6 +1764,9 @@ def record_repr(
|
|
1764
1764
|
field_names.insert(0, "uid")
|
1765
1765
|
fields_str = {}
|
1766
1766
|
for k in field_names:
|
1767
|
+
if k == "n" and getattr(self, k) < 0:
|
1768
|
+
# only needed for Schema
|
1769
|
+
continue
|
1767
1770
|
if not k.startswith("_") and hasattr(self, k):
|
1768
1771
|
value = getattr(self, k)
|
1769
1772
|
# Force strip the time component of the version
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: lamindb
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.10.0
|
4
4
|
Summary: A data framework for biology.
|
5
5
|
Author-email: Lamin Labs <open-source@lamin.ai>
|
6
6
|
Requires-Python: >=3.10,<3.14
|
@@ -10,8 +10,8 @@ Classifier: Programming Language :: Python :: 3.11
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.12
|
11
11
|
Classifier: Programming Language :: Python :: 3.13
|
12
12
|
Requires-Dist: lamin_utils==0.15.0
|
13
|
-
Requires-Dist: lamin_cli==1.6.
|
14
|
-
Requires-Dist: lamindb_setup[aws]==1.
|
13
|
+
Requires-Dist: lamin_cli==1.6.1
|
14
|
+
Requires-Dist: lamindb_setup[aws]==1.9.0
|
15
15
|
Requires-Dist: pyyaml
|
16
16
|
Requires-Dist: pyarrow
|
17
17
|
Requires-Dist: pandera>=0.24.0
|