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.
Files changed (36) hide show
  1. lamindb/__init__.py +1 -1
  2. lamindb/core/__init__.py +2 -2
  3. lamindb/core/storage/__init__.py +2 -1
  4. lamindb/core/storage/_anndata_accessor.py +10 -1
  5. lamindb/core/storage/_backed_access.py +4 -0
  6. lamindb/core/storage/_spatialdata_accessor.py +52 -0
  7. lamindb/examples/__init__.py +3 -18
  8. lamindb/examples/cellxgene/_cellxgene.py +11 -3
  9. lamindb/examples/croissant/__init__.py +44 -0
  10. lamindb/examples/croissant/mini_immuno.anndata.zarr_metadata.json +73 -0
  11. lamindb/{core → examples}/datasets/__init__.py +1 -1
  12. lamindb/{core → examples}/datasets/mini_immuno.py +19 -8
  13. lamindb/examples/schemas/_anndata.py +25 -15
  14. lamindb/examples/schemas/_simple.py +23 -9
  15. lamindb/integrations/__init__.py +2 -0
  16. lamindb/integrations/_croissant.py +122 -0
  17. lamindb/integrations/_vitessce.py +14 -12
  18. lamindb/migrations/0116_remove_artifact_unique_artifact_storage_key_hash_and_more.py +51 -0
  19. lamindb/migrations/0117_fix_artifact_storage_hash_unique_constraints.py +32 -0
  20. lamindb/migrations/{0115_squashed.py → 0117_squashed.py} +29 -6
  21. lamindb/models/_describe.py +107 -1
  22. lamindb/models/_django.py +63 -6
  23. lamindb/models/_feature_manager.py +0 -1
  24. lamindb/models/artifact.py +41 -11
  25. lamindb/models/collection.py +4 -9
  26. lamindb/models/project.py +2 -2
  27. lamindb/models/record.py +1 -1
  28. lamindb/models/run.py +1 -1
  29. lamindb/models/sqlrecord.py +3 -0
  30. {lamindb-1.9.1.dist-info → lamindb-1.10.0.dist-info}/METADATA +3 -3
  31. {lamindb-1.9.1.dist-info → lamindb-1.10.0.dist-info}/RECORD +36 -30
  32. /lamindb/{core → examples}/datasets/_core.py +0 -0
  33. /lamindb/{core → examples}/datasets/_fake.py +0 -0
  34. /lamindb/{core → examples}/datasets/_small.py +0 -0
  35. {lamindb-1.9.1.dist-info → lamindb-1.10.0.dist-info}/LICENSE +0 -0
  36. {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-06 09:12
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, default=None, max_length=255, null=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
- ("url", lamindb.base.fields.URLField(blank=True, null=True)),
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(blank=True, default=None, null=True),
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, default=None, max_length=150, null=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="unique_artifact_storage_key_hash",
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
  ]
@@ -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["branch"]["name"]
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. Please save it first using '.save()'."
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: SQLRecord,
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
@@ -969,7 +969,6 @@ class FeatureManager:
969
969
  }
970
970
  else:
971
971
  result = parse_dtype(feature.dtype)[0]
972
- print(result["field"])
973
972
  validated = result["registry"].validate( # type: ignore
974
973
  values, field=result["field"], mute=True
975
974
  )
@@ -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 AnnDataAccessor, BackedAccessor
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 describe_artifact_general, describe_header
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 describe_artifact_general, describe_header
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
- # or if the key is not populated
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
- name="unique_artifact_storage_key_hash",
1102
- condition=Q(key__isnull=False),
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
@@ -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]` A list of artifacts.
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 | Iterable[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."""
@@ -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.9.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.0
14
- Requires-Dist: lamindb_setup[aws]==1.8.3
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