lamindb 1.1.1__py3-none-any.whl → 1.2.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 +30 -25
- lamindb/_tracked.py +1 -1
- lamindb/_view.py +2 -3
- lamindb/base/__init__.py +1 -1
- lamindb/base/ids.py +1 -10
- lamindb/core/__init__.py +7 -65
- lamindb/core/_compat.py +60 -0
- lamindb/core/_context.py +43 -20
- lamindb/core/_settings.py +6 -6
- lamindb/core/_sync_git.py +1 -1
- lamindb/core/loaders.py +30 -19
- lamindb/core/storage/_backed_access.py +4 -2
- lamindb/core/storage/_tiledbsoma.py +8 -6
- lamindb/core/storage/_zarr.py +104 -25
- lamindb/core/storage/objects.py +63 -28
- lamindb/core/storage/paths.py +4 -1
- lamindb/core/types.py +10 -0
- lamindb/curators/__init__.py +100 -85
- lamindb/errors.py +1 -1
- lamindb/integrations/_vitessce.py +4 -4
- lamindb/migrations/0089_subsequent_runs.py +159 -0
- lamindb/migrations/0090_runproject_project_runs.py +73 -0
- lamindb/migrations/{0088_squashed.py → 0090_squashed.py} +245 -177
- lamindb/models/__init__.py +79 -0
- lamindb/{core → models}/_describe.py +3 -3
- lamindb/{core → models}/_django.py +8 -5
- lamindb/{core → models}/_feature_manager.py +103 -87
- lamindb/{_from_values.py → models/_from_values.py} +5 -2
- lamindb/{core/versioning.py → models/_is_versioned.py} +94 -6
- lamindb/{core → models}/_label_manager.py +10 -17
- lamindb/{core/relations.py → models/_relations.py} +8 -1
- lamindb/models/artifact.py +2602 -0
- lamindb/{_can_curate.py → models/can_curate.py} +349 -180
- lamindb/models/collection.py +683 -0
- lamindb/models/core.py +135 -0
- lamindb/models/feature.py +643 -0
- lamindb/models/flextable.py +163 -0
- lamindb/{_parents.py → models/has_parents.py} +55 -49
- lamindb/models/project.py +384 -0
- lamindb/{_query_manager.py → models/query_manager.py} +10 -8
- lamindb/{_query_set.py → models/query_set.py} +40 -26
- lamindb/models/record.py +1762 -0
- lamindb/models/run.py +563 -0
- lamindb/{_save.py → models/save.py} +9 -7
- lamindb/models/schema.py +732 -0
- lamindb/models/transform.py +360 -0
- lamindb/models/ulabel.py +249 -0
- {lamindb-1.1.1.dist-info → lamindb-1.2.0.dist-info}/METADATA +6 -6
- {lamindb-1.1.1.dist-info → lamindb-1.2.0.dist-info}/RECORD +51 -51
- lamindb/_artifact.py +0 -1379
- lamindb/_collection.py +0 -440
- lamindb/_feature.py +0 -316
- lamindb/_is_versioned.py +0 -40
- lamindb/_record.py +0 -1064
- lamindb/_run.py +0 -60
- lamindb/_schema.py +0 -347
- lamindb/_storage.py +0 -15
- lamindb/_transform.py +0 -170
- lamindb/_ulabel.py +0 -56
- lamindb/_utils.py +0 -9
- lamindb/base/validation.py +0 -63
- lamindb/core/_data.py +0 -491
- lamindb/core/fields.py +0 -12
- lamindb/models.py +0 -4475
- {lamindb-1.1.1.dist-info → lamindb-1.2.0.dist-info}/LICENSE +0 -0
- {lamindb-1.1.1.dist-info → lamindb-1.2.0.dist-info}/WHEEL +0 -0
lamindb/_run.py
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from lamindb.models import ParamManager, Run, Transform
|
4
|
-
|
5
|
-
|
6
|
-
def __init__(run: Run, *args, **kwargs):
|
7
|
-
run.params = ParamManager(run) # type: ignore
|
8
|
-
if len(args) == len(run._meta.concrete_fields):
|
9
|
-
super(Run, run).__init__(*args, **kwargs)
|
10
|
-
return None
|
11
|
-
# now we proceed with the user-facing constructor
|
12
|
-
if len(args) > 1:
|
13
|
-
raise ValueError("Only one non-keyword arg allowed: transform")
|
14
|
-
transform: Transform = None
|
15
|
-
if "transform" in kwargs or len(args) == 1:
|
16
|
-
transform = kwargs.pop("transform") if len(args) == 0 else args[0]
|
17
|
-
reference: str | None = kwargs.pop("reference") if "reference" in kwargs else None
|
18
|
-
reference_type: str | None = (
|
19
|
-
kwargs.pop("reference_type") if "reference_type" in kwargs else None
|
20
|
-
)
|
21
|
-
initiated_by_run: Run | None = kwargs.pop("initiated_by_run", None)
|
22
|
-
if transform is None:
|
23
|
-
raise TypeError("Pass transform parameter")
|
24
|
-
if transform._state.adding:
|
25
|
-
raise ValueError("Please save transform record before creating a run")
|
26
|
-
|
27
|
-
super(Run, run).__init__( # type: ignore
|
28
|
-
transform=transform,
|
29
|
-
reference=reference,
|
30
|
-
initiated_by_run=initiated_by_run,
|
31
|
-
reference_type=reference_type,
|
32
|
-
)
|
33
|
-
|
34
|
-
|
35
|
-
def delete_run_artifacts(run: Run) -> None:
|
36
|
-
environment = None
|
37
|
-
if run.environment is not None:
|
38
|
-
environment = run.environment
|
39
|
-
run.environment = None
|
40
|
-
report = None
|
41
|
-
if run.report is not None:
|
42
|
-
report = run.report
|
43
|
-
run.report = None
|
44
|
-
if environment is not None or report is not None:
|
45
|
-
run.save()
|
46
|
-
if environment is not None:
|
47
|
-
# only delete if there are no other runs attached to this environment
|
48
|
-
if environment._environment_of.count() == 0:
|
49
|
-
environment.delete(permanent=True)
|
50
|
-
if report is not None:
|
51
|
-
report.delete(permanent=True)
|
52
|
-
|
53
|
-
|
54
|
-
def delete(self) -> None:
|
55
|
-
delete_run_artifacts(self)
|
56
|
-
super(Run, self).delete()
|
57
|
-
|
58
|
-
|
59
|
-
Run.__init__ = __init__ # type: ignore
|
60
|
-
Run.delete = delete # type: ignore
|
lamindb/_schema.py
DELETED
@@ -1,347 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from typing import TYPE_CHECKING
|
4
|
-
|
5
|
-
import lamindb_setup as ln_setup
|
6
|
-
import numpy as np
|
7
|
-
from lamin_utils import logger
|
8
|
-
from lamindb_setup.core._docs import doc_args
|
9
|
-
from lamindb_setup.core.hashing import hash_set
|
10
|
-
|
11
|
-
from lamindb.base import ids
|
12
|
-
from lamindb.base.types import FieldAttr, ListLike
|
13
|
-
from lamindb.errors import InvalidArgument
|
14
|
-
from lamindb.models import Feature, Record, Schema
|
15
|
-
|
16
|
-
from ._feature import convert_pandas_dtype_to_lamin_dtype, get_dtype_str_from_dtype
|
17
|
-
from ._record import init_self_from_db, update_attributes
|
18
|
-
from ._utils import attach_func_to_class_method
|
19
|
-
from .core.relations import (
|
20
|
-
dict_related_model_to_related_name,
|
21
|
-
get_related_name,
|
22
|
-
)
|
23
|
-
from .errors import ValidationError
|
24
|
-
|
25
|
-
if TYPE_CHECKING:
|
26
|
-
from collections.abc import Iterable
|
27
|
-
|
28
|
-
import pandas as pd
|
29
|
-
from django.db.models.query_utils import DeferredAttribute
|
30
|
-
|
31
|
-
from ._query_set import QuerySet
|
32
|
-
|
33
|
-
NUMBER_TYPE = "num"
|
34
|
-
DICT_KEYS_TYPE = type({}.keys()) # type: ignore
|
35
|
-
|
36
|
-
|
37
|
-
def validate_features(features: list[Record]) -> Record:
|
38
|
-
"""Validate and return feature type."""
|
39
|
-
try:
|
40
|
-
if len(features) == 0:
|
41
|
-
raise ValueError("Provide list of features with at least one element")
|
42
|
-
except TypeError:
|
43
|
-
raise ValueError(
|
44
|
-
"Please pass a ListLike of features, not a single feature"
|
45
|
-
) from None
|
46
|
-
if not hasattr(features, "__getitem__"):
|
47
|
-
raise TypeError("features has to be list-like")
|
48
|
-
if not isinstance(features[0], Record):
|
49
|
-
raise TypeError(
|
50
|
-
"features has to store feature records! use .from_values() otherwise"
|
51
|
-
)
|
52
|
-
feature_types = {feature.__class__ for feature in features}
|
53
|
-
if len(feature_types) > 1:
|
54
|
-
raise TypeError("schema can only contain a single type")
|
55
|
-
for feature in features:
|
56
|
-
if feature._state.adding:
|
57
|
-
raise ValueError("Can only construct feature sets from validated features")
|
58
|
-
return next(iter(feature_types)) # return value in set of cardinality 1
|
59
|
-
|
60
|
-
|
61
|
-
def __init__(self, *args, **kwargs):
|
62
|
-
if len(args) == len(self._meta.concrete_fields):
|
63
|
-
super(Schema, self).__init__(*args, **kwargs)
|
64
|
-
return None
|
65
|
-
if len(args) > 1:
|
66
|
-
raise ValueError("Only one non-keyword arg allowed: features")
|
67
|
-
|
68
|
-
features: Iterable[Record] | None = args[0] if args else kwargs.pop("features", [])
|
69
|
-
# typing here anticipates transitioning to a ManyToMany
|
70
|
-
# between composites and components similar to feature_sets
|
71
|
-
# in lamindb v2
|
72
|
-
components: dict[str, Schema] = kwargs.pop("components", {})
|
73
|
-
name: str | None = kwargs.pop("name", None)
|
74
|
-
description: str | None = kwargs.pop("description", None)
|
75
|
-
dtype: str | None = kwargs.pop("dtype", None)
|
76
|
-
itype: str | Record | DeferredAttribute | None = kwargs.pop("itype", None)
|
77
|
-
type: Feature | None = kwargs.pop("type", None)
|
78
|
-
is_type: bool = kwargs.pop("is_type", False)
|
79
|
-
otype: str | None = kwargs.pop("otype", None)
|
80
|
-
minimal_set: bool = kwargs.pop("minimal_set", True)
|
81
|
-
ordered_set: bool = kwargs.pop("ordered_set", False)
|
82
|
-
maximal_set: bool = kwargs.pop("maximal_set", False)
|
83
|
-
slot: str | None = kwargs.pop("slot", None)
|
84
|
-
coerce_dtype: bool | None = kwargs.pop("coerce_dtype", None)
|
85
|
-
|
86
|
-
if kwargs:
|
87
|
-
raise ValueError(
|
88
|
-
f"Unexpected keyword arguments: {', '.join(kwargs.keys())}\n"
|
89
|
-
"Valid arguments are: features, description, dtype, itype, type, "
|
90
|
-
"is_type, otype, minimal_set, ordered_set, maximal_set, "
|
91
|
-
"slot, validated_by, coerce_dtype"
|
92
|
-
)
|
93
|
-
|
94
|
-
if features:
|
95
|
-
features_registry = validate_features(features)
|
96
|
-
itype_compare = features_registry.__get_name_with_module__()
|
97
|
-
if itype is not None:
|
98
|
-
assert itype == itype_compare, str(itype_compare) # noqa: S101
|
99
|
-
else:
|
100
|
-
itype = itype_compare
|
101
|
-
n_features = len(features)
|
102
|
-
else:
|
103
|
-
n_features = -1
|
104
|
-
if dtype is None:
|
105
|
-
dtype = None if itype is not None and itype == "Feature" else NUMBER_TYPE
|
106
|
-
else:
|
107
|
-
dtype = get_type_str(dtype)
|
108
|
-
components: dict[str, Schema]
|
109
|
-
if components:
|
110
|
-
itype = "Composite"
|
111
|
-
if otype is None:
|
112
|
-
raise InvalidArgument("Please pass otype != None for composite schemas")
|
113
|
-
if itype is not None and not isinstance(itype, str):
|
114
|
-
itype_str = get_dtype_str_from_dtype(itype, is_itype=True)
|
115
|
-
else:
|
116
|
-
itype_str = itype
|
117
|
-
validated_kwargs = {
|
118
|
-
"name": name,
|
119
|
-
"description": description,
|
120
|
-
"type": type,
|
121
|
-
"dtype": dtype,
|
122
|
-
"is_type": is_type,
|
123
|
-
"otype": otype,
|
124
|
-
"n": n_features,
|
125
|
-
"itype": itype_str,
|
126
|
-
"minimal_set": minimal_set,
|
127
|
-
"ordered_set": ordered_set,
|
128
|
-
"maximal_set": maximal_set,
|
129
|
-
}
|
130
|
-
if coerce_dtype:
|
131
|
-
validated_kwargs["_aux"] = {"af": {"0": coerce_dtype}}
|
132
|
-
if features:
|
133
|
-
hash = hash_set({feature.uid for feature in features})
|
134
|
-
elif components:
|
135
|
-
hash = hash_set({component.hash for component in components.values()})
|
136
|
-
else:
|
137
|
-
hash = hash_set({str(value) for value in validated_kwargs.values()})
|
138
|
-
validated_kwargs["hash"] = hash
|
139
|
-
validated_kwargs["slot"] = slot
|
140
|
-
schema = Schema.filter(hash=hash).one_or_none()
|
141
|
-
if schema is not None:
|
142
|
-
logger.important(f"returning existing schema with same hash: {schema}")
|
143
|
-
init_self_from_db(self, schema)
|
144
|
-
update_attributes(self, validated_kwargs)
|
145
|
-
return None
|
146
|
-
if features:
|
147
|
-
self._features = (get_related_name(features_registry), features)
|
148
|
-
elif components:
|
149
|
-
for slot, component in components.items():
|
150
|
-
if component._state.adding:
|
151
|
-
raise InvalidArgument(
|
152
|
-
f"component {slot} {component} must be saved before use"
|
153
|
-
)
|
154
|
-
self._components = components
|
155
|
-
validated_kwargs["uid"] = ids.base62_20()
|
156
|
-
super(Schema, self).__init__(**validated_kwargs)
|
157
|
-
|
158
|
-
|
159
|
-
@doc_args(Schema.save.__doc__)
|
160
|
-
def save(self, *args, **kwargs) -> Schema:
|
161
|
-
"""{}""" # noqa: D415
|
162
|
-
from lamindb._save import bulk_create
|
163
|
-
|
164
|
-
super(Schema, self).save(*args, **kwargs)
|
165
|
-
if hasattr(self, "_components"):
|
166
|
-
# analogous to save_schema_links in core._data.py
|
167
|
-
# which is called to save feature sets in artifact.save()
|
168
|
-
links = []
|
169
|
-
for slot, component in self._components.items():
|
170
|
-
kwargs = {
|
171
|
-
"composite_id": self.id,
|
172
|
-
"component_id": component.id,
|
173
|
-
"slot": slot,
|
174
|
-
}
|
175
|
-
links.append(Schema.components.through(**kwargs))
|
176
|
-
bulk_create(links, ignore_conflicts=True)
|
177
|
-
if hasattr(self, "_features"):
|
178
|
-
assert self.n > 0 # noqa: S101
|
179
|
-
related_name, records = self._features
|
180
|
-
# only the following method preserves the order
|
181
|
-
# .set() does not preserve the order but orders by
|
182
|
-
# the feature primary key
|
183
|
-
through_model = getattr(self, related_name).through
|
184
|
-
related_model_split = self.itype.split(".")
|
185
|
-
if len(related_model_split) == 1:
|
186
|
-
related_field = related_model_split[0].lower()
|
187
|
-
else:
|
188
|
-
related_field = related_model_split[1].lower()
|
189
|
-
related_field_id = f"{related_field}_id"
|
190
|
-
links = [
|
191
|
-
through_model(**{"schema_id": self.id, related_field_id: record.id})
|
192
|
-
for record in records
|
193
|
-
]
|
194
|
-
through_model.objects.bulk_create(links, ignore_conflicts=True)
|
195
|
-
return self
|
196
|
-
|
197
|
-
|
198
|
-
def get_type_str(dtype: str | None) -> str | None:
|
199
|
-
if dtype is not None:
|
200
|
-
type_str = dtype.__name__ if not isinstance(dtype, str) else dtype # type: ignore
|
201
|
-
else:
|
202
|
-
type_str = None
|
203
|
-
return type_str
|
204
|
-
|
205
|
-
|
206
|
-
@classmethod # type:ignore
|
207
|
-
@doc_args(Schema.from_values.__doc__)
|
208
|
-
def from_values(
|
209
|
-
cls,
|
210
|
-
values: ListLike,
|
211
|
-
field: FieldAttr = Feature.name,
|
212
|
-
type: str | None = None,
|
213
|
-
name: str | None = None,
|
214
|
-
mute: bool = False,
|
215
|
-
organism: Record | str | None = None,
|
216
|
-
source: Record | None = None,
|
217
|
-
raise_validation_error: bool = True,
|
218
|
-
) -> Schema:
|
219
|
-
"""{}""" # noqa: D415
|
220
|
-
if not isinstance(field, FieldAttr):
|
221
|
-
raise TypeError("Argument `field` must be a Record field, e.g., `Feature.name`")
|
222
|
-
if len(values) == 0:
|
223
|
-
raise ValueError("Provide a list of at least one value")
|
224
|
-
if isinstance(values, DICT_KEYS_TYPE):
|
225
|
-
values = list(values)
|
226
|
-
registry = field.field.model
|
227
|
-
if registry != Feature and type is None:
|
228
|
-
type = NUMBER_TYPE
|
229
|
-
logger.debug("setting feature set to 'number'")
|
230
|
-
validated = registry.validate(values, field=field, mute=mute, organism=organism)
|
231
|
-
values_array = np.array(values)
|
232
|
-
validated_values = values_array[validated]
|
233
|
-
if validated.sum() != len(values):
|
234
|
-
not_validated_values = values_array[~validated]
|
235
|
-
msg = (
|
236
|
-
f"These values could not be validated: {not_validated_values.tolist()}\n"
|
237
|
-
f"If there are no typos, add them to their registry: {registry.__name__}"
|
238
|
-
)
|
239
|
-
if raise_validation_error:
|
240
|
-
raise ValidationError(msg)
|
241
|
-
elif len(validated_values) == 0:
|
242
|
-
return None # temporarily return None here
|
243
|
-
validated_features = registry.from_values(
|
244
|
-
validated_values,
|
245
|
-
field=field,
|
246
|
-
organism=organism,
|
247
|
-
source=source,
|
248
|
-
)
|
249
|
-
schema = Schema(
|
250
|
-
features=validated_features,
|
251
|
-
name=name,
|
252
|
-
dtype=get_type_str(type),
|
253
|
-
)
|
254
|
-
return schema
|
255
|
-
|
256
|
-
|
257
|
-
@classmethod # type:ignore
|
258
|
-
@doc_args(Schema.from_df.__doc__)
|
259
|
-
def from_df(
|
260
|
-
cls,
|
261
|
-
df: pd.DataFrame,
|
262
|
-
field: FieldAttr = Feature.name,
|
263
|
-
name: str | None = None,
|
264
|
-
mute: bool = False,
|
265
|
-
organism: Record | str | None = None,
|
266
|
-
source: Record | None = None,
|
267
|
-
) -> Schema | None:
|
268
|
-
"""{}""" # noqa: D415
|
269
|
-
registry = field.field.model
|
270
|
-
validated = registry.validate(df.columns, field=field, mute=mute, organism=organism)
|
271
|
-
if validated.sum() == 0:
|
272
|
-
if mute is True:
|
273
|
-
logger.warning("no validated features, skip creating feature set")
|
274
|
-
return None
|
275
|
-
if registry == Feature:
|
276
|
-
validated_features = Feature.from_values( # type: ignore
|
277
|
-
df.columns, field=field, organism=organism
|
278
|
-
)
|
279
|
-
schema = Schema(validated_features, name=name, dtype=None, otype="DataFrame")
|
280
|
-
else:
|
281
|
-
dtypes = [col.dtype for (_, col) in df.loc[:, validated].items()]
|
282
|
-
if len(set(dtypes)) != 1:
|
283
|
-
raise ValueError(f"data types are heterogeneous: {set(dtypes)}")
|
284
|
-
dtype = convert_pandas_dtype_to_lamin_dtype(dtypes[0])
|
285
|
-
validated_features = registry.from_values(
|
286
|
-
df.columns[validated],
|
287
|
-
field=field,
|
288
|
-
organism=organism,
|
289
|
-
source=source,
|
290
|
-
)
|
291
|
-
schema = Schema(
|
292
|
-
features=validated_features,
|
293
|
-
name=name,
|
294
|
-
dtype=get_type_str(dtype),
|
295
|
-
otype="DataFrame",
|
296
|
-
)
|
297
|
-
return schema
|
298
|
-
|
299
|
-
|
300
|
-
@property # type: ignore
|
301
|
-
@doc_args(Schema.members.__doc__)
|
302
|
-
def members(self) -> QuerySet:
|
303
|
-
"""{}""" # noqa: D415
|
304
|
-
if self._state.adding:
|
305
|
-
# this should return a queryset and not a list...
|
306
|
-
# need to fix this
|
307
|
-
return self._features[1]
|
308
|
-
related_name = self._get_related_name()
|
309
|
-
if related_name is None:
|
310
|
-
related_name = "features"
|
311
|
-
return self.__getattribute__(related_name).order_by("links_schema__id")
|
312
|
-
|
313
|
-
|
314
|
-
def _get_related_name(self: Schema) -> str:
|
315
|
-
related_models = dict_related_model_to_related_name(self, instance=self._state.db)
|
316
|
-
related_name = related_models.get(self.itype)
|
317
|
-
return related_name
|
318
|
-
|
319
|
-
|
320
|
-
METHOD_NAMES = [
|
321
|
-
"__init__",
|
322
|
-
"from_values",
|
323
|
-
"from_df",
|
324
|
-
"save",
|
325
|
-
]
|
326
|
-
|
327
|
-
if ln_setup._TESTING:
|
328
|
-
from inspect import signature
|
329
|
-
|
330
|
-
SIGS = {
|
331
|
-
name: signature(getattr(Schema, name))
|
332
|
-
for name in METHOD_NAMES
|
333
|
-
if name != "__init__"
|
334
|
-
}
|
335
|
-
|
336
|
-
for name in METHOD_NAMES:
|
337
|
-
attach_func_to_class_method(name, Schema, globals())
|
338
|
-
|
339
|
-
Schema.members = members # type: ignore
|
340
|
-
Schema._get_related_name = _get_related_name
|
341
|
-
# excluded on docs via
|
342
|
-
# https://github.com/laminlabs/lndocs/blob/8c1963de65445107ea69b3fd59354c3828e067d1/lndocs/lamin_sphinx/__init__.py#L584-L588
|
343
|
-
delattr(Schema, "validated_by") # we don't want to expose these
|
344
|
-
delattr(Schema, "validated_by_id") # we don't want to expose these
|
345
|
-
delattr(Schema, "validated_schemas") # we don't want to expose these
|
346
|
-
delattr(Schema, "composite") # we don't want to expose these
|
347
|
-
delattr(Schema, "composite_id") # we don't want to expose these
|
lamindb/_storage.py
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
from lamindb_setup.core._docs import doc_args
|
2
|
-
from lamindb_setup.core.upath import UPath, create_path
|
3
|
-
|
4
|
-
from lamindb.models import Storage
|
5
|
-
|
6
|
-
|
7
|
-
@property # type: ignore
|
8
|
-
@doc_args(Storage.path.__doc__)
|
9
|
-
def path(self) -> UPath:
|
10
|
-
"""{}""" # noqa: D415
|
11
|
-
access_token = self._access_token if hasattr(self, "_access_token") else None
|
12
|
-
return create_path(self.root, access_token=access_token)
|
13
|
-
|
14
|
-
|
15
|
-
Storage.path = path # type: ignore
|
lamindb/_transform.py
DELETED
@@ -1,170 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import warnings
|
4
|
-
from typing import TYPE_CHECKING
|
5
|
-
|
6
|
-
from lamin_utils import logger
|
7
|
-
from lamindb_setup.core._docs import doc_args
|
8
|
-
from lamindb_setup.core.hashing import hash_string
|
9
|
-
|
10
|
-
from lamindb.models import Run, Transform
|
11
|
-
|
12
|
-
from ._parents import _view_parents
|
13
|
-
from ._record import init_self_from_db, update_attributes
|
14
|
-
from ._run import delete_run_artifacts
|
15
|
-
from .core._settings import settings
|
16
|
-
from .core.versioning import message_update_key_in_version_family, process_revises
|
17
|
-
from .errors import InconsistentKey
|
18
|
-
|
19
|
-
if TYPE_CHECKING:
|
20
|
-
from lamindb.base.types import TransformType
|
21
|
-
|
22
|
-
|
23
|
-
def __init__(transform: Transform, *args, **kwargs):
|
24
|
-
if len(args) == len(transform._meta.concrete_fields):
|
25
|
-
super(Transform, transform).__init__(*args, **kwargs)
|
26
|
-
return None
|
27
|
-
key: str | None = kwargs.pop("key") if "key" in kwargs else None
|
28
|
-
description: str | None = (
|
29
|
-
kwargs.pop("description") if "description" in kwargs else None
|
30
|
-
)
|
31
|
-
revises: Transform | None = kwargs.pop("revises") if "revises" in kwargs else None
|
32
|
-
version: str | None = kwargs.pop("version") if "version" in kwargs else None
|
33
|
-
type: TransformType | None = kwargs.pop("type") if "type" in kwargs else "pipeline"
|
34
|
-
reference: str | None = kwargs.pop("reference") if "reference" in kwargs else None
|
35
|
-
reference_type: str | None = (
|
36
|
-
kwargs.pop("reference_type") if "reference_type" in kwargs else None
|
37
|
-
)
|
38
|
-
using_key = (
|
39
|
-
kwargs.pop("using_key") if "using_key" in kwargs else settings._using_key
|
40
|
-
)
|
41
|
-
if "name" in kwargs:
|
42
|
-
if key is None:
|
43
|
-
key = kwargs.pop("name")
|
44
|
-
warnings.warn(
|
45
|
-
f"`name` will be removed soon, please pass '{key}' to `key` instead",
|
46
|
-
FutureWarning,
|
47
|
-
stacklevel=2,
|
48
|
-
)
|
49
|
-
else:
|
50
|
-
# description wasn't exist, so no check necessary
|
51
|
-
description = kwargs.pop("name")
|
52
|
-
warnings.warn(
|
53
|
-
f"`name` will be removed soon, please pass '{description}' to `description` instead",
|
54
|
-
FutureWarning,
|
55
|
-
stacklevel=2,
|
56
|
-
)
|
57
|
-
# below is internal use that we'll hopefully be able to eliminate
|
58
|
-
uid: str | None = kwargs.pop("uid") if "uid" in kwargs else None
|
59
|
-
source_code: str | None = (
|
60
|
-
kwargs.pop("source_code") if "source_code" in kwargs else None
|
61
|
-
)
|
62
|
-
if not len(kwargs) == 0:
|
63
|
-
raise ValueError(
|
64
|
-
"Only key, description, version, type, revises, reference, "
|
65
|
-
f"reference_type can be passed, but you passed: {kwargs}"
|
66
|
-
)
|
67
|
-
if revises is None:
|
68
|
-
# need to check uid before checking key
|
69
|
-
if uid is not None:
|
70
|
-
revises = (
|
71
|
-
Transform.objects.using(using_key)
|
72
|
-
.filter(uid__startswith=uid[:-4], is_latest=True)
|
73
|
-
.order_by("-created_at")
|
74
|
-
.first()
|
75
|
-
)
|
76
|
-
elif key is not None:
|
77
|
-
candidate_for_revises = (
|
78
|
-
Transform.objects.using(using_key)
|
79
|
-
.filter(key=key, is_latest=True)
|
80
|
-
.order_by("-created_at")
|
81
|
-
.first()
|
82
|
-
)
|
83
|
-
if candidate_for_revises is not None:
|
84
|
-
revises = candidate_for_revises
|
85
|
-
if candidate_for_revises.source_code is None:
|
86
|
-
# no source code was yet saved, return the same transform
|
87
|
-
logger.important(
|
88
|
-
"no source code was yet saved, returning existing transform with same key"
|
89
|
-
)
|
90
|
-
uid = revises.uid
|
91
|
-
if revises is not None and uid is not None and uid == revises.uid:
|
92
|
-
if revises.key != key:
|
93
|
-
logger.warning("ignoring inconsistent key")
|
94
|
-
init_self_from_db(transform, revises)
|
95
|
-
update_attributes(transform, {"description": description})
|
96
|
-
return None
|
97
|
-
if revises is not None and key is not None and revises.key != key:
|
98
|
-
note = message_update_key_in_version_family(
|
99
|
-
suid=revises.stem_uid,
|
100
|
-
existing_key=revises.key,
|
101
|
-
new_key=key,
|
102
|
-
registry="Transform",
|
103
|
-
)
|
104
|
-
raise InconsistentKey(
|
105
|
-
f"`key` is '{key}', but `revises.key` is '{revises.key}'\n\nEither do *not* pass `key`.\n\n{note}"
|
106
|
-
)
|
107
|
-
new_uid, version, key, description, revises = process_revises(
|
108
|
-
revises, version, key, description, Transform
|
109
|
-
)
|
110
|
-
# this is only because the user-facing constructor allows passing a uid
|
111
|
-
# most others don't
|
112
|
-
if uid is None:
|
113
|
-
has_consciously_provided_uid = False
|
114
|
-
uid = new_uid
|
115
|
-
else:
|
116
|
-
has_consciously_provided_uid = True
|
117
|
-
hash = None
|
118
|
-
if source_code is not None:
|
119
|
-
hash = hash_string(source_code)
|
120
|
-
transform_candidate = Transform.filter(hash=hash, is_latest=True).one_or_none()
|
121
|
-
if transform_candidate is not None:
|
122
|
-
init_self_from_db(transform, transform_candidate)
|
123
|
-
update_attributes(transform, {"key": key, "description": description})
|
124
|
-
return None
|
125
|
-
super(Transform, transform).__init__( # type: ignore
|
126
|
-
uid=uid,
|
127
|
-
description=description,
|
128
|
-
key=key,
|
129
|
-
type=type,
|
130
|
-
version=version,
|
131
|
-
reference=reference,
|
132
|
-
reference_type=reference_type,
|
133
|
-
source_code=source_code,
|
134
|
-
hash=hash,
|
135
|
-
_has_consciously_provided_uid=has_consciously_provided_uid,
|
136
|
-
revises=revises,
|
137
|
-
)
|
138
|
-
|
139
|
-
|
140
|
-
def delete(self) -> None:
|
141
|
-
# query all runs and delete their artifacts
|
142
|
-
runs = Run.filter(transform=self)
|
143
|
-
for run in runs:
|
144
|
-
delete_run_artifacts(run)
|
145
|
-
# at this point, all artifacts have been taken care of
|
146
|
-
# we can now leverage CASCADE delete
|
147
|
-
super(Transform, self).delete()
|
148
|
-
|
149
|
-
|
150
|
-
@property # type: ignore
|
151
|
-
@doc_args(Transform.latest_run.__doc__)
|
152
|
-
def latest_run(self) -> Run:
|
153
|
-
"""{}""" # noqa: D415
|
154
|
-
return self.runs.order_by("-started_at").first()
|
155
|
-
|
156
|
-
|
157
|
-
def view_lineage(self, with_successors: bool = False, distance: int = 5):
|
158
|
-
return _view_parents(
|
159
|
-
record=self,
|
160
|
-
field="key",
|
161
|
-
with_children=with_successors,
|
162
|
-
distance=distance,
|
163
|
-
attr_name="predecessors",
|
164
|
-
)
|
165
|
-
|
166
|
-
|
167
|
-
Transform.__init__ = __init__ # type: ignore
|
168
|
-
Transform.delete = delete # type: ignore
|
169
|
-
Transform.latest_run = latest_run # type: ignore
|
170
|
-
Transform.view_lineage = view_lineage # type: ignore
|
lamindb/_ulabel.py
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import lamindb_setup as ln_setup
|
4
|
-
|
5
|
-
from lamindb._record import _get_record_kwargs
|
6
|
-
from lamindb.errors import FieldValidationError
|
7
|
-
from lamindb.models import ULabel
|
8
|
-
|
9
|
-
from ._utils import attach_func_to_class_method
|
10
|
-
|
11
|
-
|
12
|
-
def __init__(self, *args, **kwargs):
|
13
|
-
if len(args) == len(self._meta.concrete_fields):
|
14
|
-
super(ULabel, self).__init__(*args, **kwargs)
|
15
|
-
return None
|
16
|
-
# now we proceed with the user-facing constructor
|
17
|
-
if len(args) > 0:
|
18
|
-
raise ValueError("Only one non-keyword arg allowed")
|
19
|
-
name: str = kwargs.pop("name") if "name" in kwargs else None
|
20
|
-
type: str | None = kwargs.pop("type") if "type" in kwargs else None
|
21
|
-
is_type: bool = kwargs.pop("is_type") if "is_type" in kwargs else False
|
22
|
-
description: str | None = (
|
23
|
-
kwargs.pop("description") if "description" in kwargs else None
|
24
|
-
)
|
25
|
-
reference: str | None = kwargs.pop("reference") if "reference" in kwargs else None
|
26
|
-
reference_type: str | None = (
|
27
|
-
kwargs.pop("reference_type") if "reference_type" in kwargs else None
|
28
|
-
)
|
29
|
-
if len(kwargs) > 0:
|
30
|
-
valid_keywords = ", ".join([val[0] for val in _get_record_kwargs(ULabel)])
|
31
|
-
raise FieldValidationError(f"Only {valid_keywords} are valid keyword arguments")
|
32
|
-
super(ULabel, self).__init__(
|
33
|
-
name=name,
|
34
|
-
type=type,
|
35
|
-
is_type=is_type,
|
36
|
-
description=description,
|
37
|
-
reference=reference,
|
38
|
-
reference_type=reference_type,
|
39
|
-
)
|
40
|
-
|
41
|
-
|
42
|
-
METHOD_NAMES = [
|
43
|
-
"__init__",
|
44
|
-
]
|
45
|
-
|
46
|
-
if ln_setup._TESTING:
|
47
|
-
from inspect import signature
|
48
|
-
|
49
|
-
SIGS = {
|
50
|
-
name: signature(getattr(ULabel, name))
|
51
|
-
for name in METHOD_NAMES
|
52
|
-
if name != "__init__"
|
53
|
-
}
|
54
|
-
|
55
|
-
for name in METHOD_NAMES:
|
56
|
-
attach_func_to_class_method(name, ULabel, globals())
|
lamindb/_utils.py
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
def attach_func_to_class_method(func_name, cls, globals):
|
2
|
-
implementation = globals[func_name]
|
3
|
-
target = getattr(cls, func_name)
|
4
|
-
# assigning the original class definition docstring
|
5
|
-
# to the implementation only has an effect for regular methods
|
6
|
-
# not for class methods
|
7
|
-
# this is why we need @doc_args for class methods
|
8
|
-
implementation.__doc__ = target.__doc__
|
9
|
-
setattr(cls, func_name, implementation)
|