lamindb 1.12.1__py3-none-any.whl → 1.13.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 +2 -2
- lamindb/_finish.py +1 -1
- lamindb/_tracked.py +3 -15
- lamindb/core/_context.py +45 -19
- lamindb/curators/_legacy.py +1 -1
- lamindb/curators/core.py +51 -21
- lamindb/errors.py +6 -0
- lamindb/examples/datasets/_core.py +1 -1
- lamindb/integrations/__init__.py +0 -18
- lamindb/integrations/{_lightning.py → lightning.py} +13 -10
- lamindb/migrations/0134_run_params.py +17 -0
- lamindb/migrations/{0133_squashed.py → 0134_squashed.py} +93 -90
- lamindb/models/_feature_manager.py +30 -20
- lamindb/models/_label_manager.py +3 -5
- lamindb/models/artifact.py +250 -291
- lamindb/models/artifact_set.py +4 -4
- lamindb/models/block.py +11 -9
- lamindb/models/can_curate.py +1 -1
- lamindb/models/collection.py +16 -17
- lamindb/models/has_parents.py +1 -3
- lamindb/models/query_manager.py +7 -7
- lamindb/models/query_set.py +38 -12
- lamindb/models/run.py +53 -49
- lamindb/models/schema.py +79 -65
- lamindb/models/sqlrecord.py +32 -17
- lamindb/models/transform.py +6 -3
- {lamindb-1.12.1.dist-info → lamindb-1.13.0.dist-info}/METADATA +26 -22
- {lamindb-1.12.1.dist-info → lamindb-1.13.0.dist-info}/RECORD +30 -29
- {lamindb-1.12.1.dist-info → lamindb-1.13.0.dist-info}/LICENSE +0 -0
- {lamindb-1.12.1.dist-info → lamindb-1.13.0.dist-info}/WHEEL +0 -0
lamindb/models/artifact_set.py
CHANGED
@@ -11,7 +11,7 @@ from upath import UPath
|
|
11
11
|
|
12
12
|
from ..core._mapped_collection import MappedCollection
|
13
13
|
from ..core.storage._backed_access import _open_dataframe
|
14
|
-
from .artifact import Artifact,
|
14
|
+
from .artifact import Artifact, track_run_input
|
15
15
|
from .collection import Collection, _load_concat_artifacts
|
16
16
|
|
17
17
|
if TYPE_CHECKING:
|
@@ -55,7 +55,7 @@ class ArtifactSet(Iterable):
|
|
55
55
|
artifacts: list[Artifact] = list(self)
|
56
56
|
concat_object = _load_concat_artifacts(artifacts, join, **kwargs)
|
57
57
|
# track only if successful
|
58
|
-
|
58
|
+
track_run_input(artifacts, is_run_input)
|
59
59
|
return concat_object
|
60
60
|
|
61
61
|
@doc_args(Collection.open.__doc__)
|
@@ -74,7 +74,7 @@ class ArtifactSet(Iterable):
|
|
74
74
|
|
75
75
|
dataframe = _open_dataframe(paths, engine=engine, **kwargs)
|
76
76
|
# track only if successful
|
77
|
-
|
77
|
+
track_run_input(artifacts, is_run_input)
|
78
78
|
return dataframe
|
79
79
|
|
80
80
|
@doc_args(Collection.mapped.__doc__)
|
@@ -122,7 +122,7 @@ class ArtifactSet(Iterable):
|
|
122
122
|
dtype,
|
123
123
|
)
|
124
124
|
# track only if successful
|
125
|
-
|
125
|
+
track_run_input(artifacts, is_run_input)
|
126
126
|
return ds
|
127
127
|
|
128
128
|
|
lamindb/models/block.py
CHANGED
@@ -71,7 +71,7 @@ class RootBlock(BlockMixin, BaseSQLRecord):
|
|
71
71
|
|
72
72
|
|
73
73
|
class RecordBlock(BlockMixin, BaseSQLRecord):
|
74
|
-
"""An unstructured notes block that can be attached to
|
74
|
+
"""An unstructured notes block that can be attached to a record."""
|
75
75
|
|
76
76
|
class Meta:
|
77
77
|
app_label = "lamindb"
|
@@ -91,7 +91,7 @@ class ArtifactBlock(BlockMixin, BaseSQLRecord):
|
|
91
91
|
|
92
92
|
|
93
93
|
class TransformBlock(BlockMixin, BaseSQLRecord):
|
94
|
-
"""An unstructured notes block that can be attached to
|
94
|
+
"""An unstructured notes block that can be attached to a transform."""
|
95
95
|
|
96
96
|
class Meta:
|
97
97
|
app_label = "lamindb"
|
@@ -105,7 +105,7 @@ class TransformBlock(BlockMixin, BaseSQLRecord):
|
|
105
105
|
|
106
106
|
|
107
107
|
class RunBlock(BlockMixin, BaseSQLRecord):
|
108
|
-
"""An unstructured notes block that can be attached to
|
108
|
+
"""An unstructured notes block that can be attached to a run."""
|
109
109
|
|
110
110
|
class Meta:
|
111
111
|
app_label = "lamindb"
|
@@ -115,7 +115,7 @@ class RunBlock(BlockMixin, BaseSQLRecord):
|
|
115
115
|
|
116
116
|
|
117
117
|
class CollectionBlock(BlockMixin, BaseSQLRecord):
|
118
|
-
"""An unstructured notes block that can be attached to
|
118
|
+
"""An unstructured notes block that can be attached to a collection."""
|
119
119
|
|
120
120
|
class Meta:
|
121
121
|
app_label = "lamindb"
|
@@ -127,7 +127,7 @@ class CollectionBlock(BlockMixin, BaseSQLRecord):
|
|
127
127
|
|
128
128
|
|
129
129
|
class SchemaBlock(BlockMixin, BaseSQLRecord):
|
130
|
-
"""An unstructured notes block that can be attached to
|
130
|
+
"""An unstructured notes block that can be attached to a schema."""
|
131
131
|
|
132
132
|
class Meta:
|
133
133
|
app_label = "lamindb"
|
@@ -137,7 +137,7 @@ class SchemaBlock(BlockMixin, BaseSQLRecord):
|
|
137
137
|
|
138
138
|
|
139
139
|
class FeatureBlock(BlockMixin, BaseSQLRecord):
|
140
|
-
"""An unstructured notes block that can be attached to
|
140
|
+
"""An unstructured notes block that can be attached to a feature."""
|
141
141
|
|
142
142
|
class Meta:
|
143
143
|
app_label = "lamindb"
|
@@ -147,16 +147,17 @@ class FeatureBlock(BlockMixin, BaseSQLRecord):
|
|
147
147
|
|
148
148
|
|
149
149
|
class ProjectBlock(BlockMixin, BaseSQLRecord):
|
150
|
-
"""An unstructured notes block that can be attached to
|
150
|
+
"""An unstructured notes block that can be attached to a project."""
|
151
151
|
|
152
152
|
class Meta:
|
153
153
|
app_label = "lamindb"
|
154
154
|
|
155
155
|
project: Project = ForeignKey(Project, CASCADE, related_name="blocks")
|
156
|
+
"""The project to which the block is attached."""
|
156
157
|
|
157
158
|
|
158
159
|
class SpaceBlock(BlockMixin, BaseSQLRecord):
|
159
|
-
"""An unstructured notes block that can be attached to
|
160
|
+
"""An unstructured notes block that can be attached to a space."""
|
160
161
|
|
161
162
|
class Meta:
|
162
163
|
app_label = "lamindb"
|
@@ -166,9 +167,10 @@ class SpaceBlock(BlockMixin, BaseSQLRecord):
|
|
166
167
|
|
167
168
|
|
168
169
|
class BranchBlock(BlockMixin, BaseSQLRecord):
|
169
|
-
"""An unstructured notes block that can be attached to
|
170
|
+
"""An unstructured notes block that can be attached to a branch."""
|
170
171
|
|
171
172
|
class Meta:
|
172
173
|
app_label = "lamindb"
|
173
174
|
|
174
175
|
branch: Branch = ForeignKey(Branch, CASCADE, related_name="blocks")
|
176
|
+
"""The branch to which the block is attached."""
|
lamindb/models/can_curate.py
CHANGED
@@ -356,7 +356,7 @@ def _add_or_remove_synonyms(
|
|
356
356
|
from IPython.display import display
|
357
357
|
|
358
358
|
syns_all = (
|
359
|
-
record.__class__.filter().exclude(synonyms="").exclude(synonyms=None)
|
359
|
+
record.__class__.filter().exclude(synonyms="").exclude(synonyms=None) # type: ignore
|
360
360
|
)
|
361
361
|
if len(syns_all) == 0:
|
362
362
|
return
|
lamindb/models/collection.py
CHANGED
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Any, Literal, overload
|
|
6
6
|
import anndata as ad
|
7
7
|
import pandas as pd
|
8
8
|
from django.db import models
|
9
|
-
from django.db.models import CASCADE, PROTECT
|
9
|
+
from django.db.models import CASCADE, PROTECT, Q
|
10
10
|
from lamin_utils import logger
|
11
11
|
from lamindb_setup.core.hashing import HASH_LENGTH, hash_set
|
12
12
|
|
@@ -25,11 +25,11 @@ from ..models._is_versioned import process_revises
|
|
25
25
|
from ._is_versioned import IsVersioned
|
26
26
|
from .artifact import (
|
27
27
|
Artifact,
|
28
|
-
_populate_subsequent_runs_,
|
29
|
-
_track_run_input,
|
30
28
|
describe_artifact_collection,
|
31
29
|
get_run,
|
30
|
+
populate_subsequent_run,
|
32
31
|
save_schema_links,
|
32
|
+
track_run_input,
|
33
33
|
)
|
34
34
|
from .has_parents import view_lineage
|
35
35
|
from .run import Run, TracksRun, TracksUpdates
|
@@ -317,17 +317,19 @@ class Collection(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
317
317
|
)
|
318
318
|
# we ignore collections in trash containing the same hash
|
319
319
|
if hash is not None:
|
320
|
-
existing_collection = Collection.filter(
|
320
|
+
existing_collection = Collection.objects.filter(
|
321
|
+
~Q(branch_id=-1),
|
322
|
+
hash=hash,
|
323
|
+
).one_or_none()
|
321
324
|
else:
|
322
325
|
existing_collection = None
|
323
326
|
if existing_collection is not None:
|
324
327
|
logger.warning(
|
325
328
|
f"returning existing collection with same hash: {existing_collection}; if you intended to query to track this collection as an input, use: ln.Collection.get()"
|
326
329
|
)
|
327
|
-
if run is not None:
|
328
|
-
existing_collection._populate_subsequent_runs(run)
|
329
330
|
init_self_from_db(self, existing_collection)
|
330
331
|
update_attributes(self, {"description": description, "key": key})
|
332
|
+
populate_subsequent_run(self, run)
|
331
333
|
else:
|
332
334
|
_skip_validation = revises is not None and key == revises.key
|
333
335
|
super().__init__( # type: ignore
|
@@ -348,9 +350,9 @@ class Collection(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
348
350
|
_skip_validation=_skip_validation,
|
349
351
|
)
|
350
352
|
self._artifacts = artifacts
|
351
|
-
if revises is not None:
|
352
|
-
|
353
|
-
|
353
|
+
if revises is not None and revises.uid != self.uid:
|
354
|
+
track_run_input(revises, run=run)
|
355
|
+
track_run_input(artifacts, run=run)
|
354
356
|
|
355
357
|
@classmethod
|
356
358
|
def get(
|
@@ -440,7 +442,7 @@ class Collection(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
440
442
|
|
441
443
|
dataframe = _open_dataframe(paths, engine=engine, **kwargs)
|
442
444
|
# track only if successful
|
443
|
-
|
445
|
+
track_run_input(self, is_run_input)
|
444
446
|
return dataframe
|
445
447
|
|
446
448
|
def mapped(
|
@@ -540,7 +542,7 @@ class Collection(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
540
542
|
dtype,
|
541
543
|
)
|
542
544
|
# track only if successful
|
543
|
-
|
545
|
+
track_run_input(self, is_run_input)
|
544
546
|
return ds
|
545
547
|
|
546
548
|
def cache(self, is_run_input: bool | None = None) -> list[UPath]:
|
@@ -557,7 +559,7 @@ class Collection(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
557
559
|
for artifact in self.ordered_artifacts.all():
|
558
560
|
# do not want to track data lineage on the artifact level
|
559
561
|
path_list.append(artifact.cache(is_run_input=False))
|
560
|
-
|
562
|
+
track_run_input(self, is_run_input)
|
561
563
|
return path_list
|
562
564
|
|
563
565
|
def load(
|
@@ -570,11 +572,11 @@ class Collection(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
570
572
|
|
571
573
|
Returns an in-memory concatenated `DataFrame` or `AnnData` object.
|
572
574
|
"""
|
573
|
-
# cannot call
|
575
|
+
# cannot call track_run_input here, see comment further down
|
574
576
|
artifacts = self.ordered_artifacts.all()
|
575
577
|
concat_object = _load_concat_artifacts(artifacts, join, **kwargs)
|
576
578
|
# only call it here because there might be errors during load or concat
|
577
|
-
|
579
|
+
track_run_input(self, is_run_input)
|
578
580
|
return concat_object
|
579
581
|
|
580
582
|
def save(self, using: str | None = None) -> Collection:
|
@@ -664,9 +666,6 @@ class Collection(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
664
666
|
"""
|
665
667
|
return describe_artifact_collection(self)
|
666
668
|
|
667
|
-
def _populate_subsequent_runs(self, run: Run) -> None:
|
668
|
-
_populate_subsequent_runs_(self, run)
|
669
|
-
|
670
669
|
|
671
670
|
# internal function, not exposed to user
|
672
671
|
def from_artifacts(artifacts: Iterable[Artifact]) -> tuple[str, dict[str, str]]:
|
lamindb/models/has_parents.py
CHANGED
@@ -550,9 +550,7 @@ def _get_all_child_runs(data: Artifact | Collection) -> list:
|
|
550
550
|
runs.update(
|
551
551
|
{
|
552
552
|
f.run
|
553
|
-
for f in data.run.output_collections.all()
|
554
|
-
.filter(branch_id__in=[0, 1])
|
555
|
-
.all()
|
553
|
+
for f in data.run.output_collections.all().filter(branch_id__in=[0, 1])
|
556
554
|
}
|
557
555
|
)
|
558
556
|
while runs.difference(all_runs):
|
lamindb/models/query_manager.py
CHANGED
@@ -237,7 +237,7 @@ class QueryManager(Manager):
|
|
237
237
|
Examples:
|
238
238
|
|
239
239
|
>>> ln.save(ln.ULabel.from_values(["ULabel1", "ULabel2", "ULabel3"], field="name")) # noqa
|
240
|
-
>>> labels = ln.ULabel.filter(name__icontains = "label")
|
240
|
+
>>> labels = ln.ULabel.filter(name__icontains = "label")
|
241
241
|
>>> ln.ULabel(name="ULabel1").save()
|
242
242
|
>>> label = ln.ULabel.get(name="ULabel1")
|
243
243
|
>>> label.parents.set(labels)
|
@@ -245,7 +245,7 @@ class QueryManager(Manager):
|
|
245
245
|
>>> manager.to_dataframe()
|
246
246
|
"""
|
247
247
|
|
248
|
-
def
|
248
|
+
def track_run_input_manager(self):
|
249
249
|
if hasattr(self, "source_field_name") and hasattr(self, "target_field_name"):
|
250
250
|
if (
|
251
251
|
self.source_field_name == "collection"
|
@@ -255,7 +255,7 @@ class QueryManager(Manager):
|
|
255
255
|
from lamindb.core._context import context
|
256
256
|
from lamindb.models.artifact import (
|
257
257
|
WARNING_RUN_TRANSFORM,
|
258
|
-
|
258
|
+
track_run_input,
|
259
259
|
)
|
260
260
|
|
261
261
|
if (
|
@@ -263,14 +263,14 @@ class QueryManager(Manager):
|
|
263
263
|
and not settings.creation.artifact_silence_missing_run_warning
|
264
264
|
):
|
265
265
|
logger.warning(WARNING_RUN_TRANSFORM)
|
266
|
-
|
266
|
+
track_run_input(self.instance)
|
267
267
|
|
268
268
|
def to_list(self, field: str | None = None):
|
269
269
|
"""Populate a list with the results.
|
270
270
|
|
271
271
|
Examples:
|
272
272
|
>>> ln.save(ln.ULabel.from_values(["ULabel1", "ULabel2", "ULabel3"], field="name"))
|
273
|
-
>>> labels = ln.ULabel.filter(name__icontains="label")
|
273
|
+
>>> labels = ln.ULabel.filter(name__icontains="label")
|
274
274
|
>>> ln.ULabel(name="ULabel1").save()
|
275
275
|
>>> label = ln.ULabel.get(name="ULabel1")
|
276
276
|
>>> label.parents.set(labels)
|
@@ -281,7 +281,7 @@ class QueryManager(Manager):
|
|
281
281
|
if field is None:
|
282
282
|
return list(self.all())
|
283
283
|
else:
|
284
|
-
self.
|
284
|
+
self.track_run_input_manager()
|
285
285
|
return list(self.values_list(field, flat=True))
|
286
286
|
|
287
287
|
@deprecated(new_name="to_list")
|
@@ -304,7 +304,7 @@ class QueryManager(Manager):
|
|
304
304
|
|
305
305
|
For `**kwargs`, see :meth:`lamindb.models.QuerySet.to_dataframe`.
|
306
306
|
"""
|
307
|
-
self.
|
307
|
+
self.track_run_input_manager()
|
308
308
|
return super().all()
|
309
309
|
|
310
310
|
@doc_args(_search.__doc__)
|
lamindb/models/query_set.py
CHANGED
@@ -26,6 +26,8 @@ from .sqlrecord import Registry, SQLRecord
|
|
26
26
|
if TYPE_CHECKING:
|
27
27
|
from lamindb.base.types import ListLike, StrField
|
28
28
|
|
29
|
+
from .sqlrecord import Branch
|
30
|
+
|
29
31
|
T = TypeVar("T")
|
30
32
|
|
31
33
|
|
@@ -60,7 +62,7 @@ def get_keys_from_df(data: list, registry: SQLRecord) -> list[str]:
|
|
60
62
|
return keys
|
61
63
|
|
62
64
|
|
63
|
-
def get_default_branch_ids() -> list[int]:
|
65
|
+
def get_default_branch_ids(branch: Branch | None = None) -> list[int]:
|
64
66
|
"""Return branch IDs to include in default queries.
|
65
67
|
|
66
68
|
By default, queries include records on the main branch (branch_id=1) but exclude trashed (branch_id=-1)
|
@@ -72,7 +74,10 @@ def get_default_branch_ids() -> list[int]:
|
|
72
74
|
Returns:
|
73
75
|
List containing the default branch and current branch if different.
|
74
76
|
"""
|
75
|
-
|
77
|
+
if branch is None:
|
78
|
+
branch_id = setup_settings.branch.id
|
79
|
+
else:
|
80
|
+
branch_id = branch.id
|
76
81
|
branch_ids = [branch_id]
|
77
82
|
if branch_id != 1: # add the main branch by default
|
78
83
|
branch_ids.append(1)
|
@@ -142,7 +147,7 @@ def get_backward_compat_filter_kwargs(queryset, expressions):
|
|
142
147
|
return list(mapped.keys()) if was_list else mapped
|
143
148
|
|
144
149
|
|
145
|
-
def process_expressions(queryset: QuerySet, expressions: dict) -> dict:
|
150
|
+
def process_expressions(queryset: QuerySet, queries: tuple, expressions: dict) -> dict:
|
146
151
|
def _map_databases(value: Any, key: str, target_db: str) -> tuple[str, Any]:
|
147
152
|
if isinstance(value, SQLRecord):
|
148
153
|
if value._state.db != target_db:
|
@@ -170,6 +175,28 @@ def process_expressions(queryset: QuerySet, expressions: dict) -> dict:
|
|
170
175
|
|
171
176
|
return key, value
|
172
177
|
|
178
|
+
branch_fields = {"branch", "branch_id"}
|
179
|
+
branch_prefixes = ("branch__", "branch_id__")
|
180
|
+
|
181
|
+
def queries_contain_branch(queries: tuple) -> bool:
|
182
|
+
"""Check if any Q object in queries references branch or branch_id."""
|
183
|
+
|
184
|
+
def check_q_object(q: Q) -> bool:
|
185
|
+
# Q objects store their conditions in q.children
|
186
|
+
for child in q.children:
|
187
|
+
if isinstance(child, tuple) and len(child) == 2:
|
188
|
+
# Normal condition: (key, value)
|
189
|
+
key = child[0]
|
190
|
+
if key in branch_fields or key.startswith(branch_prefixes):
|
191
|
+
return True
|
192
|
+
elif isinstance(child, Q):
|
193
|
+
# Nested Q object
|
194
|
+
if check_q_object(child):
|
195
|
+
return True
|
196
|
+
return False
|
197
|
+
|
198
|
+
return any(check_q_object(q) for q in queries if isinstance(q, Q))
|
199
|
+
|
173
200
|
expressions = get_backward_compat_filter_kwargs(
|
174
201
|
queryset,
|
175
202
|
expressions,
|
@@ -179,15 +206,13 @@ def process_expressions(queryset: QuerySet, expressions: dict) -> dict:
|
|
179
206
|
id_uid_hash = {"id", "uid", "hash", "id__in", "uid__in", "hash__in"}
|
180
207
|
if not any(expression in id_uid_hash for expression in expressions):
|
181
208
|
expressions_have_branch = False
|
182
|
-
branch_branch_id = {"branch", "branch_id"}
|
183
|
-
branch_branch_id__ = ("branch__", "branch_id__")
|
184
209
|
for expression in expressions:
|
185
|
-
if expression in
|
186
|
-
|
210
|
+
if expression in branch_fields or expression.startswith(
|
211
|
+
branch_prefixes
|
187
212
|
):
|
188
213
|
expressions_have_branch = True
|
189
214
|
break
|
190
|
-
if not expressions_have_branch:
|
215
|
+
if not expressions_have_branch and not queries_contain_branch(queries):
|
191
216
|
expressions["branch_id__in"] = get_default_branch_ids()
|
192
217
|
else:
|
193
218
|
# if branch_id is None, do not apply a filter
|
@@ -197,6 +222,7 @@ def process_expressions(queryset: QuerySet, expressions: dict) -> dict:
|
|
197
222
|
expressions.pop("branch_id")
|
198
223
|
if "branch" in expressions and expressions["branch"] is None:
|
199
224
|
expressions.pop("branch")
|
225
|
+
|
200
226
|
if queryset._db is not None:
|
201
227
|
# only check for database mismatch if there is a defined database on the
|
202
228
|
# queryset
|
@@ -254,7 +280,7 @@ def get(
|
|
254
280
|
return one_helper(qs, DOESNOTEXIST_MSG)
|
255
281
|
else:
|
256
282
|
assert idlike is None # noqa: S101
|
257
|
-
expressions = process_expressions(qs, expressions)
|
283
|
+
expressions = process_expressions(qs, [], expressions)
|
258
284
|
# inject is_latest for consistency with idlike
|
259
285
|
is_latest_was_not_in_expressions = "is_latest" not in expressions
|
260
286
|
if issubclass(registry, IsVersioned) and is_latest_was_not_in_expressions:
|
@@ -1022,11 +1048,11 @@ class QuerySet(BasicQuerySet):
|
|
1022
1048
|
raise # pragma: no cover
|
1023
1049
|
|
1024
1050
|
if is_run_input is not False: # might be None or True or Run
|
1025
|
-
from .artifact import Artifact,
|
1051
|
+
from .artifact import Artifact, track_run_input
|
1026
1052
|
from .collection import Collection
|
1027
1053
|
|
1028
1054
|
if isinstance(record, (Artifact, Collection)):
|
1029
|
-
|
1055
|
+
track_run_input(record, is_run_input)
|
1030
1056
|
|
1031
1057
|
return record
|
1032
1058
|
|
@@ -1059,7 +1085,7 @@ class QuerySet(BasicQuerySet):
|
|
1059
1085
|
f"Invalid lookup '{value}' for {field}. Did you mean {field}__name?"
|
1060
1086
|
)
|
1061
1087
|
|
1062
|
-
expressions = process_expressions(self, expressions)
|
1088
|
+
expressions = process_expressions(self, queries, expressions)
|
1063
1089
|
# need to run a query if queries or expressions are not empty
|
1064
1090
|
if queries or expressions:
|
1065
1091
|
try:
|
lamindb/models/run.py
CHANGED
@@ -9,7 +9,6 @@ from django.db.models import (
|
|
9
9
|
)
|
10
10
|
from lamindb_setup import _check_instance_setup
|
11
11
|
|
12
|
-
from lamindb.base import deprecated
|
13
12
|
from lamindb.base.fields import (
|
14
13
|
BooleanField,
|
15
14
|
CharField,
|
@@ -198,31 +197,57 @@ class Run(SQLRecord):
|
|
198
197
|
|
199
198
|
Args:
|
200
199
|
transform: `Transform` A :class:`~lamindb.Transform` record.
|
201
|
-
|
200
|
+
name: `str | None = None` An optional name.
|
201
|
+
params: `dict | None = None` A dictionary of parameters.
|
202
202
|
reference: `str | None = None` For instance, an external ID or a download URL.
|
203
203
|
reference_type: `str | None = None` For instance, `redun_id`, `nextflow_id` or `url`.
|
204
|
+
initiated_by_run: `Run | None = None` The run that triggers this run.
|
204
205
|
|
205
206
|
See Also:
|
206
207
|
:func:`~lamindb.track`
|
207
208
|
Globally track a script or notebook run.
|
209
|
+
:func:`~lamindb.tracked`
|
210
|
+
Track a function with this decorator.
|
208
211
|
|
209
212
|
Examples:
|
210
213
|
|
211
|
-
Create a run record
|
214
|
+
Create a run record::
|
215
|
+
|
216
|
+
ln.Transform(key="Cell Ranger", version="7.2.0", type="pipeline").save()
|
217
|
+
transform = ln.Transform.get(key="Cell Ranger", version="7.2.0")
|
218
|
+
run = ln.Run(transform)
|
219
|
+
|
220
|
+
Create a global run context for a custom transform::
|
221
|
+
|
222
|
+
ln.track(transform=transform)
|
223
|
+
ln.context.run # global run
|
212
224
|
|
213
|
-
|
214
|
-
>>> transform = ln.Transform.get(key="Cell Ranger", version="7.2.0")
|
215
|
-
>>> run = ln.Run(transform)
|
225
|
+
Track a global run context for a notebook or script::
|
216
226
|
|
217
|
-
|
227
|
+
ln.track()
|
228
|
+
ln.context.run # global run
|
229
|
+
|
230
|
+
You can pass parameters to `Run(transform, params=params)` or add them later::
|
231
|
+
|
232
|
+
run.params = {
|
233
|
+
"learning_rate": 0.01,
|
234
|
+
"input_dir": "s3://my-bucket/mydataset",
|
235
|
+
"downsample": True,
|
236
|
+
"preprocess_params": {
|
237
|
+
"normalization_type": "cool",
|
238
|
+
"subset_highlyvariable": True,
|
239
|
+
},
|
240
|
+
}
|
241
|
+
run.save()
|
218
242
|
|
219
|
-
|
220
|
-
|
243
|
+
In contrast to `.params`, features are indexed in the `Feature` registry and can reference relational categorical values.
|
244
|
+
If you want to link feature values, use::
|
221
245
|
|
222
|
-
|
246
|
+
run.features.add_values({
|
247
|
+
"experiment": "My experiment 1",
|
248
|
+
})
|
223
249
|
|
224
|
-
|
225
|
-
>>> ln.context.run
|
250
|
+
Guide: :ref:`track-run-parameters`
|
226
251
|
"""
|
227
252
|
|
228
253
|
class Meta:
|
@@ -282,7 +307,8 @@ class Run(SQLRecord):
|
|
282
307
|
"""The collections serving as input for this run."""
|
283
308
|
output_records: Record
|
284
309
|
"""The collections generated by this run."""
|
285
|
-
|
310
|
+
params: dict = models.JSONField(null=True)
|
311
|
+
"""JSON-like parameters."""
|
286
312
|
_feature_values: FeatureValue = models.ManyToManyField(
|
287
313
|
"FeatureValue", through="RunFeatureValue", related_name="runs"
|
288
314
|
)
|
@@ -331,6 +357,8 @@ class Run(SQLRecord):
|
|
331
357
|
def __init__(
|
332
358
|
self,
|
333
359
|
transform: Transform,
|
360
|
+
name: str | None = None,
|
361
|
+
params: dict | None = None,
|
334
362
|
reference: str | None = None,
|
335
363
|
reference_type: str | None = None,
|
336
364
|
initiated_by_run: Run | None = None,
|
@@ -356,6 +384,8 @@ class Run(SQLRecord):
|
|
356
384
|
transform: Transform = None
|
357
385
|
if "transform" in kwargs or len(args) == 1:
|
358
386
|
transform = kwargs.pop("transform") if len(args) == 0 else args[0]
|
387
|
+
name: str | None = kwargs.pop("name", None)
|
388
|
+
params: dict | None = kwargs.pop("params", None)
|
359
389
|
reference: str | None = kwargs.pop("reference", None)
|
360
390
|
reference_type: str | None = kwargs.pop("reference_type", None)
|
361
391
|
initiated_by_run: Run | None = kwargs.pop("initiated_by_run", None)
|
@@ -363,12 +393,17 @@ class Run(SQLRecord):
|
|
363
393
|
raise TypeError("Pass transform parameter")
|
364
394
|
if transform._state.adding:
|
365
395
|
raise ValueError("Please save transform record before creating a run")
|
366
|
-
|
396
|
+
if not len(kwargs) == 0:
|
397
|
+
raise ValueError(
|
398
|
+
f"Only transform, name, params, reference, reference_type, initiated_by_run can be passed, but you passed: {kwargs}"
|
399
|
+
)
|
367
400
|
super().__init__( # type: ignore
|
368
401
|
transform=transform,
|
402
|
+
name=name,
|
403
|
+
params=params,
|
369
404
|
reference=reference,
|
370
|
-
initiated_by_run=initiated_by_run,
|
371
405
|
reference_type=reference_type,
|
406
|
+
initiated_by_run=initiated_by_run,
|
372
407
|
)
|
373
408
|
|
374
409
|
@property
|
@@ -377,22 +412,13 @@ class Run(SQLRecord):
|
|
377
412
|
|
378
413
|
Returns the status as a string, one of: `scheduled`, `re-started`, `started`, `completed`, `errored`, or `aborted`.
|
379
414
|
|
380
|
-
The string maps to an integer field `_status_code` of the run registry, with mapping:
|
381
|
-
- -3: `scheduled`
|
382
|
-
- -2: `re-started`
|
383
|
-
- -1: `started`
|
384
|
-
- 0: `completed`
|
385
|
-
- 1: `errored`
|
386
|
-
- 2: `aborted`
|
387
|
-
|
388
|
-
You can use this private integer field for queries.
|
389
|
-
|
390
415
|
Examples:
|
391
416
|
|
392
|
-
::
|
417
|
+
See the status of a run::
|
393
418
|
|
394
419
|
run.status
|
395
420
|
#> 'completed'
|
421
|
+
|
396
422
|
"""
|
397
423
|
if self._status_code is None:
|
398
424
|
return "unknown"
|
@@ -406,31 +432,9 @@ class Run(SQLRecord):
|
|
406
432
|
}
|
407
433
|
return status_dict.get(self._status_code, "unknown")
|
408
434
|
|
409
|
-
@property
|
410
|
-
@deprecated("features")
|
411
|
-
def params(self) -> FeatureManager:
|
412
|
-
return self.features
|
413
|
-
|
414
435
|
@property
|
415
436
|
def features(self) -> FeatureManager:
|
416
|
-
"""
|
417
|
-
|
418
|
-
Run parameters are tracked via the `Feature` registry, just like all other variables.
|
419
|
-
|
420
|
-
Guide: :ref:`track-run-parameters`
|
421
|
-
|
422
|
-
Example::
|
423
|
-
|
424
|
-
run.features.add_values({
|
425
|
-
"learning_rate": 0.01,
|
426
|
-
"input_dir": "s3://my-bucket/mydataset",
|
427
|
-
"downsample": True,
|
428
|
-
"preprocess_params": {
|
429
|
-
"normalization_type": "cool",
|
430
|
-
"subset_highlyvariable": True,
|
431
|
-
},
|
432
|
-
})
|
433
|
-
"""
|
437
|
+
"""Manage annotations with features."""
|
434
438
|
from ._feature_manager import FeatureManager
|
435
439
|
|
436
440
|
return FeatureManager(self)
|