lamindb 1.3.2__py3-none-any.whl → 1.5.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 (59) hide show
  1. lamindb/__init__.py +52 -36
  2. lamindb/_finish.py +17 -10
  3. lamindb/_tracked.py +1 -1
  4. lamindb/base/__init__.py +3 -1
  5. lamindb/base/fields.py +40 -22
  6. lamindb/base/ids.py +1 -94
  7. lamindb/base/types.py +2 -0
  8. lamindb/base/uids.py +117 -0
  9. lamindb/core/_context.py +216 -133
  10. lamindb/core/_settings.py +38 -25
  11. lamindb/core/datasets/__init__.py +11 -4
  12. lamindb/core/datasets/_core.py +5 -5
  13. lamindb/core/datasets/_small.py +0 -93
  14. lamindb/core/datasets/mini_immuno.py +172 -0
  15. lamindb/core/loaders.py +1 -1
  16. lamindb/core/storage/_backed_access.py +100 -6
  17. lamindb/core/storage/_polars_lazy_df.py +51 -0
  18. lamindb/core/storage/_pyarrow_dataset.py +15 -30
  19. lamindb/core/storage/objects.py +6 -0
  20. lamindb/core/subsettings/__init__.py +2 -0
  21. lamindb/core/subsettings/_annotation_settings.py +11 -0
  22. lamindb/curators/__init__.py +7 -3559
  23. lamindb/curators/_legacy.py +2056 -0
  24. lamindb/curators/core.py +1546 -0
  25. lamindb/errors.py +11 -0
  26. lamindb/examples/__init__.py +27 -0
  27. lamindb/examples/schemas/__init__.py +12 -0
  28. lamindb/examples/schemas/_anndata.py +25 -0
  29. lamindb/examples/schemas/_simple.py +19 -0
  30. lamindb/integrations/_vitessce.py +8 -5
  31. lamindb/migrations/0091_alter_featurevalue_options_alter_space_options_and_more.py +24 -0
  32. lamindb/migrations/0092_alter_artifactfeaturevalue_artifact_and_more.py +75 -0
  33. lamindb/models/__init__.py +12 -2
  34. lamindb/models/_describe.py +21 -4
  35. lamindb/models/_feature_manager.py +384 -301
  36. lamindb/models/_from_values.py +1 -1
  37. lamindb/models/_is_versioned.py +5 -15
  38. lamindb/models/_label_manager.py +8 -2
  39. lamindb/models/artifact.py +354 -177
  40. lamindb/models/artifact_set.py +122 -0
  41. lamindb/models/can_curate.py +4 -1
  42. lamindb/models/collection.py +79 -56
  43. lamindb/models/core.py +1 -1
  44. lamindb/models/feature.py +78 -47
  45. lamindb/models/has_parents.py +24 -9
  46. lamindb/models/project.py +3 -3
  47. lamindb/models/query_manager.py +221 -22
  48. lamindb/models/query_set.py +251 -206
  49. lamindb/models/record.py +211 -344
  50. lamindb/models/run.py +59 -5
  51. lamindb/models/save.py +9 -5
  52. lamindb/models/schema.py +673 -196
  53. lamindb/models/transform.py +5 -14
  54. lamindb/models/ulabel.py +8 -5
  55. {lamindb-1.3.2.dist-info → lamindb-1.5.0.dist-info}/METADATA +8 -7
  56. lamindb-1.5.0.dist-info/RECORD +108 -0
  57. lamindb-1.3.2.dist-info/RECORD +0 -95
  58. {lamindb-1.3.2.dist-info → lamindb-1.5.0.dist-info}/LICENSE +0 -0
  59. {lamindb-1.3.2.dist-info → lamindb-1.5.0.dist-info}/WHEEL +0 -0
lamindb/models/run.py CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING, Any, overload
4
4
 
5
+ import numpy as np
5
6
  from django.db import models
6
7
  from django.db.models import (
7
8
  CASCADE,
@@ -19,7 +20,7 @@ from lamindb.base.fields import (
19
20
  ForeignKey,
20
21
  )
21
22
  from lamindb.base.users import current_user_id
22
- from lamindb.errors import ValidationError
23
+ from lamindb.errors import InvalidArgument, ValidationError
23
24
 
24
25
  from ..base.ids import base62_20
25
26
  from .can_curate import CanCurate
@@ -33,6 +34,7 @@ if TYPE_CHECKING:
33
34
  from .artifact import Artifact
34
35
  from .collection import Collection
35
36
  from .project import Project
37
+ from .query_set import QuerySet
36
38
  from .schema import Schema
37
39
  from .transform import Transform
38
40
  from .ulabel import ULabel
@@ -345,7 +347,7 @@ class ParamValue(Record):
345
347
 
346
348
 
347
349
  class Run(Record):
348
- """Runs.
350
+ """Runs of transforms such as the execution of a script.
349
351
 
350
352
  A registry to store runs of transforms, such as an executation of a script.
351
353
 
@@ -538,6 +540,56 @@ class Run(Record):
538
540
  delete_run_artifacts(self)
539
541
  super().delete()
540
542
 
543
+ @classmethod
544
+ def filter(
545
+ cls,
546
+ *queries,
547
+ **expressions,
548
+ ) -> QuerySet:
549
+ """Query a set of artifacts.
550
+
551
+ Args:
552
+ *queries: `Q` expressions.
553
+ **expressions: Params, fields, and values passed via the Django query syntax.
554
+
555
+ See Also:
556
+ - Guide: :doc:`docs:registries`
557
+
558
+ Examples:
559
+
560
+ Query by fields::
561
+
562
+ ln.Run.filter(key="examples/my_file.parquet")
563
+
564
+ Query by params::
565
+
566
+ ln.Run.filter(hyperparam_x=100)
567
+ """
568
+ from ._feature_manager import filter_base
569
+ from .query_set import QuerySet
570
+
571
+ if expressions:
572
+ keys_normalized = [key.split("__")[0] for key in expressions]
573
+ field_or_feature_or_param = keys_normalized[0].split("__")[0]
574
+ if field_or_feature_or_param in Run.__get_available_fields__():
575
+ return QuerySet(model=cls).filter(*queries, **expressions)
576
+ elif all(
577
+ params_validated := Param.validate(
578
+ keys_normalized, field="name", mute=True
579
+ )
580
+ ):
581
+ return filter_base(ParamManagerRun, **expressions)
582
+ else:
583
+ params = ", ".join(sorted(np.array(keys_normalized)[~params_validated]))
584
+ message = f"param names: {params}"
585
+ fields = ", ".join(sorted(cls.__get_available_fields__()))
586
+ raise InvalidArgument(
587
+ f"You can query either by available fields: {fields}\n"
588
+ f"Or fix invalid {message}"
589
+ )
590
+ else:
591
+ return QuerySet(model=cls).filter(*queries, **expressions)
592
+
541
593
 
542
594
  def delete_run_artifacts(run: Run) -> None:
543
595
  environment = None
@@ -555,14 +607,16 @@ def delete_run_artifacts(run: Run) -> None:
555
607
  if environment._environment_of.count() == 0:
556
608
  environment.delete(permanent=True)
557
609
  if report is not None:
558
- report.delete(permanent=True)
610
+ # only delete if there are no other runs attached to this environment
611
+ if report._report_of.count() == 0:
612
+ report.delete(permanent=True)
559
613
 
560
614
 
561
615
  class RunParamValue(BasicRecord, LinkORM):
562
616
  id: int = models.BigAutoField(primary_key=True)
563
- run: Run = ForeignKey(Run, CASCADE, related_name="+")
617
+ run: Run = ForeignKey(Run, CASCADE, related_name="links_paramvalue")
564
618
  # we follow the lower() case convention rather than snake case for link models
565
- paramvalue: ParamValue = ForeignKey(ParamValue, PROTECT, related_name="+")
619
+ paramvalue: ParamValue = ForeignKey(ParamValue, PROTECT, related_name="links_run")
566
620
  created_at: datetime = DateTimeField(
567
621
  editable=False, db_default=models.functions.Now(), db_index=True
568
622
  )
lamindb/models/save.py CHANGED
@@ -30,7 +30,7 @@ if TYPE_CHECKING:
30
30
 
31
31
 
32
32
  def save(records: Iterable[Record], ignore_conflicts: bool | None = False) -> None:
33
- """Bulk save to registries & storage.
33
+ """Bulk save records.
34
34
 
35
35
  Note:
36
36
 
@@ -157,7 +157,13 @@ def check_and_attempt_upload(
157
157
  return exception
158
158
  # copies (if on-disk) or moves the temporary file (if in-memory) to the cache
159
159
  if os.getenv("LAMINDB_MULTI_INSTANCE") is None:
160
- copy_or_move_to_cache(artifact, storage_path, cache_path)
160
+ # this happens only after the actual upload was performed
161
+ # we avoid failing here in case any problems happen in copy_or_move_to_cache
162
+ # because the cache copying or cleanup is not absolutely necessary
163
+ try:
164
+ copy_or_move_to_cache(artifact, storage_path, cache_path)
165
+ except Exception as e:
166
+ logger.warning(f"A problem with cache on saving: {e}")
161
167
  # after successful upload, we should remove the attribute so that another call
162
168
  # call to save won't upload again, the user should call replace() then
163
169
  del artifact._local_filepath
@@ -192,9 +198,7 @@ def copy_or_move_to_cache(
192
198
  # non-local storage_path further
193
199
  if local_path != cache_path:
194
200
  if cache_path.exists():
195
- logger.warning(
196
- f"The cache path {cache_path.as_posix()} already exists, replacing it."
197
- )
201
+ logger.warning(f"replacing the existing cache path {cache_path.as_posix()}")
198
202
  if cache_path.is_dir():
199
203
  shutil.rmtree(cache_path)
200
204
  else: