lamindb 0.74.1__py3-none-any.whl → 0.74.2__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 +8 -5
- lamindb/_artifact.py +31 -41
- lamindb/_can_validate.py +24 -22
- lamindb/_collection.py +5 -6
- lamindb/{_annotate.py → _curate.py} +62 -40
- lamindb/_feature.py +7 -9
- lamindb/_feature_set.py +17 -18
- lamindb/_filter.py +5 -5
- lamindb/_finish.py +18 -6
- lamindb/_from_values.py +12 -12
- lamindb/_is_versioned.py +2 -2
- lamindb/_parents.py +7 -7
- lamindb/_query_manager.py +7 -7
- lamindb/_query_set.py +27 -27
- lamindb/{_registry.py → _record.py} +89 -48
- lamindb/_save.py +6 -6
- lamindb/_storage.py +1 -1
- lamindb/_view.py +4 -4
- lamindb/core/__init__.py +16 -12
- lamindb/core/_data.py +10 -10
- lamindb/core/_feature_manager.py +48 -31
- lamindb/core/_label_manager.py +5 -5
- lamindb/core/_mapped_collection.py +4 -1
- lamindb/core/_run_context.py +2 -2
- lamindb/core/_settings.py +2 -1
- lamindb/core/_sync_git.py +22 -12
- lamindb/core/_track_environment.py +5 -1
- lamindb/core/fields.py +1 -1
- lamindb/core/schema.py +6 -6
- lamindb/core/storage/_backed_access.py +56 -12
- lamindb/core/storage/paths.py +1 -1
- lamindb/core/versioning.py +1 -1
- lamindb/integrations/_vitessce.py +4 -3
- {lamindb-0.74.1.dist-info → lamindb-0.74.2.dist-info}/METADATA +5 -7
- lamindb-0.74.2.dist-info/RECORD +57 -0
- lamindb-0.74.1.dist-info/RECORD +0 -57
- {lamindb-0.74.1.dist-info → lamindb-0.74.2.dist-info}/LICENSE +0 -0
- {lamindb-0.74.1.dist-info → lamindb-0.74.2.dist-info}/WHEEL +0 -0
lamindb/_parents.py
CHANGED
@@ -5,12 +5,12 @@ from typing import TYPE_CHECKING
|
|
5
5
|
|
6
6
|
import lamindb_setup as ln_setup
|
7
7
|
from lamin_utils import logger
|
8
|
-
from lnschema_core import Artifact, Collection,
|
8
|
+
from lnschema_core import Artifact, Collection, Record, Run, Transform
|
9
9
|
from lnschema_core.models import HasParents, format_field_value
|
10
10
|
|
11
11
|
from lamindb._utils import attach_func_to_class_method
|
12
12
|
|
13
|
-
from .
|
13
|
+
from ._record import get_default_str_field
|
14
14
|
|
15
15
|
if TYPE_CHECKING:
|
16
16
|
from lnschema_core.types import StrField
|
@@ -137,7 +137,7 @@ def view_lineage(data: Artifact | Collection, with_children: bool = True) -> Non
|
|
137
137
|
|
138
138
|
|
139
139
|
def _view_parents(
|
140
|
-
record:
|
140
|
+
record: Record, field: str, with_children: bool = False, distance: int = 100
|
141
141
|
):
|
142
142
|
"""Graph of parents."""
|
143
143
|
if not hasattr(record, "parents"):
|
@@ -197,7 +197,7 @@ def _view_parents(
|
|
197
197
|
_view(u)
|
198
198
|
|
199
199
|
|
200
|
-
def _get_parents(record:
|
200
|
+
def _get_parents(record: Record, field: str, distance: int, children: bool = False):
|
201
201
|
"""Recursively get parent records within a distance."""
|
202
202
|
if children:
|
203
203
|
key = "parents"
|
@@ -228,7 +228,7 @@ def _get_parents(record: Registry, field: str, distance: int, children: bool = F
|
|
228
228
|
|
229
229
|
|
230
230
|
def _df_edges_from_parents(
|
231
|
-
record:
|
231
|
+
record: Record, field: str, distance: int, children: bool = False
|
232
232
|
):
|
233
233
|
"""Construct a DataFrame of edges as the input of graphviz.Digraph."""
|
234
234
|
key = "children" if children else "parents"
|
@@ -267,7 +267,7 @@ def _df_edges_from_parents(
|
|
267
267
|
return df_edges
|
268
268
|
|
269
269
|
|
270
|
-
def _record_label(record:
|
270
|
+
def _record_label(record: Record, field: str | None = None):
|
271
271
|
if isinstance(record, Artifact):
|
272
272
|
if record.description is None:
|
273
273
|
name = record.key
|
@@ -311,7 +311,7 @@ def _record_label(record: Registry, field: str | None = None):
|
|
311
311
|
)
|
312
312
|
|
313
313
|
|
314
|
-
def _add_emoji(record:
|
314
|
+
def _add_emoji(record: Record, label: str):
|
315
315
|
if record.__class__.__name__ == "Transform":
|
316
316
|
emoji = TRANSFORM_EMOJIS.get(record.type, "💫")
|
317
317
|
elif record.__class__.__name__ == "Run":
|
lamindb/_query_manager.py
CHANGED
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, NamedTuple
|
|
5
5
|
from django.db import models
|
6
6
|
from lamin_utils import logger
|
7
7
|
from lamindb_setup.core._docs import doc_args
|
8
|
-
from lnschema_core.models import
|
8
|
+
from lnschema_core.models import Record
|
9
9
|
|
10
10
|
from lamindb.core._settings import settings
|
11
11
|
|
@@ -84,17 +84,17 @@ class QueryManager(models.Manager):
|
|
84
84
|
self._track_run_input_manager()
|
85
85
|
return self._all_base_class()
|
86
86
|
|
87
|
-
@doc_args(
|
87
|
+
@doc_args(Record.search.__doc__)
|
88
88
|
def search(self, string: str, **kwargs):
|
89
|
-
"""{}
|
90
|
-
from .
|
89
|
+
"""{}""" # noqa: D415
|
90
|
+
from ._record import _search
|
91
91
|
|
92
92
|
return _search(cls=self.all(), string=string, **kwargs)
|
93
93
|
|
94
|
-
@doc_args(
|
94
|
+
@doc_args(Record.lookup.__doc__)
|
95
95
|
def lookup(self, field: StrField | None = None, **kwargs) -> NamedTuple:
|
96
|
-
"""{}
|
97
|
-
from .
|
96
|
+
"""{}""" # noqa: D415
|
97
|
+
from ._record import _lookup
|
98
98
|
|
99
99
|
return _lookup(cls=self.all(), field=field, **kwargs)
|
100
100
|
|
lamindb/_query_set.py
CHANGED
@@ -12,7 +12,7 @@ from lnschema_core.models import (
|
|
12
12
|
CanValidate,
|
13
13
|
Collection,
|
14
14
|
IsVersioned,
|
15
|
-
|
15
|
+
Record,
|
16
16
|
Run,
|
17
17
|
Transform,
|
18
18
|
)
|
@@ -35,7 +35,7 @@ class MultipleResultsFound(Exception):
|
|
35
35
|
# return (series + timedelta).dt.strftime("%Y-%m-%d %H:%M:%S %Z")
|
36
36
|
|
37
37
|
|
38
|
-
def get_keys_from_df(data: list, registry:
|
38
|
+
def get_keys_from_df(data: list, registry: Record) -> list[str]:
|
39
39
|
if len(data) > 0:
|
40
40
|
if isinstance(data[0], dict):
|
41
41
|
keys = list(data[0].keys())
|
@@ -69,7 +69,7 @@ def one_helper(self):
|
|
69
69
|
class RecordsList(UserList):
|
70
70
|
"""Is ordered, can't be queried, but has `.df()`."""
|
71
71
|
|
72
|
-
def __init__(self, records: Iterable[
|
72
|
+
def __init__(self, records: Iterable[Record]):
|
73
73
|
super().__init__(record for record in records)
|
74
74
|
|
75
75
|
def df(self) -> pd.DataFrame:
|
@@ -77,7 +77,7 @@ class RecordsList(UserList):
|
|
77
77
|
values = [record.__dict__ for record in self.data]
|
78
78
|
return pd.DataFrame(values, columns=keys)
|
79
79
|
|
80
|
-
def one(self) ->
|
80
|
+
def one(self) -> Record:
|
81
81
|
"""Exactly one result. Throws error if there are more or none."""
|
82
82
|
return one_helper(self)
|
83
83
|
|
@@ -96,11 +96,11 @@ class QuerySet(models.QuerySet, CanValidate):
|
|
96
96
|
>>> queryset
|
97
97
|
"""
|
98
98
|
|
99
|
-
@doc_args(
|
99
|
+
@doc_args(Record.df.__doc__)
|
100
100
|
def df(
|
101
101
|
self, include: str | list[str] | None = None, join: str = "inner"
|
102
102
|
) -> pd.DataFrame:
|
103
|
-
"""{}
|
103
|
+
"""{}""" # noqa: D415
|
104
104
|
# re-order the columns
|
105
105
|
exclude_field_names = ["created_at"]
|
106
106
|
field_names = [
|
@@ -147,21 +147,21 @@ class QuerySet(models.QuerySet, CanValidate):
|
|
147
147
|
lookup_str = "__".join(split[1:])
|
148
148
|
else:
|
149
149
|
lookup_str = "id"
|
150
|
-
|
151
|
-
field = getattr(
|
150
|
+
Record = self.model
|
151
|
+
field = getattr(Record, field_name)
|
152
152
|
if isinstance(field.field, models.ManyToManyField):
|
153
153
|
related_ORM = (
|
154
154
|
field.field.model
|
155
|
-
if field.field.model !=
|
155
|
+
if field.field.model != Record
|
156
156
|
else field.field.related_model
|
157
157
|
)
|
158
|
-
if
|
159
|
-
left_side_link_model = f"from_{
|
158
|
+
if Record == related_ORM:
|
159
|
+
left_side_link_model = f"from_{Record.__name__.lower()}"
|
160
160
|
values_expression = (
|
161
|
-
f"to_{
|
161
|
+
f"to_{Record.__name__.lower()}__{lookup_str}"
|
162
162
|
)
|
163
163
|
else:
|
164
|
-
left_side_link_model = f"{
|
164
|
+
left_side_link_model = f"{Record.__name__.lower()}"
|
165
165
|
values_expression = (
|
166
166
|
f"{related_ORM.__name__.lower()}__{lookup_str}"
|
167
167
|
)
|
@@ -199,7 +199,7 @@ class QuerySet(models.QuerySet, CanValidate):
|
|
199
199
|
else:
|
200
200
|
self._delete_base_class(*args, **kwargs)
|
201
201
|
|
202
|
-
def list(self, field: str | None = None) -> list[
|
202
|
+
def list(self, field: str | None = None) -> list[Record]:
|
203
203
|
"""Populate a list with the results.
|
204
204
|
|
205
205
|
Examples:
|
@@ -211,7 +211,7 @@ class QuerySet(models.QuerySet, CanValidate):
|
|
211
211
|
else:
|
212
212
|
return list(self.values_list(field, flat=True))
|
213
213
|
|
214
|
-
def first(self) ->
|
214
|
+
def first(self) -> Record | None:
|
215
215
|
"""If non-empty, the first result in the query set, otherwise ``None``.
|
216
216
|
|
217
217
|
Examples:
|
@@ -221,7 +221,7 @@ class QuerySet(models.QuerySet, CanValidate):
|
|
221
221
|
return None
|
222
222
|
return self[0]
|
223
223
|
|
224
|
-
def one(self) ->
|
224
|
+
def one(self) -> Record:
|
225
225
|
"""Exactly one result. Raises error if there are more or none.
|
226
226
|
|
227
227
|
Examples:
|
@@ -229,7 +229,7 @@ class QuerySet(models.QuerySet, CanValidate):
|
|
229
229
|
"""
|
230
230
|
return one_helper(self)
|
231
231
|
|
232
|
-
def one_or_none(self) ->
|
232
|
+
def one_or_none(self) -> Record | None:
|
233
233
|
"""At most one result. Returns it if there is one, otherwise returns ``None``.
|
234
234
|
|
235
235
|
Examples:
|
@@ -248,32 +248,32 @@ class QuerySet(models.QuerySet, CanValidate):
|
|
248
248
|
if issubclass(self.model, IsVersioned):
|
249
249
|
return filter_query_set_by_latest_version(self)
|
250
250
|
else:
|
251
|
-
raise ValueError("
|
251
|
+
raise ValueError("Record isn't subclass of `lamindb.core.IsVersioned`")
|
252
252
|
|
253
|
-
@doc_args(
|
253
|
+
@doc_args(Record.search.__doc__)
|
254
254
|
def search(self, string: str, **kwargs):
|
255
|
-
"""{}
|
256
|
-
from .
|
255
|
+
"""{}""" # noqa: D415
|
256
|
+
from ._record import _search
|
257
257
|
|
258
258
|
return _search(cls=self, string=string, **kwargs)
|
259
259
|
|
260
|
-
@doc_args(
|
260
|
+
@doc_args(Record.lookup.__doc__)
|
261
261
|
def lookup(self, field: StrField | None = None, **kwargs) -> NamedTuple:
|
262
|
-
"""{}
|
263
|
-
from .
|
262
|
+
"""{}""" # noqa: D415
|
263
|
+
from ._record import _lookup
|
264
264
|
|
265
265
|
return _lookup(cls=self, field=field, **kwargs)
|
266
266
|
|
267
267
|
@doc_args(CanValidate.validate.__doc__)
|
268
268
|
def validate(self, values: ListLike, field: str | StrField | None = None, **kwargs):
|
269
|
-
"""{}
|
269
|
+
"""{}""" # noqa: D415
|
270
270
|
from ._can_validate import _validate
|
271
271
|
|
272
272
|
return _validate(cls=self, values=values, field=field, **kwargs)
|
273
273
|
|
274
274
|
@doc_args(CanValidate.inspect.__doc__)
|
275
275
|
def inspect(self, values: ListLike, field: str | StrField | None = None, **kwargs):
|
276
|
-
"""{}
|
276
|
+
"""{}""" # noqa: D415
|
277
277
|
from ._can_validate import _inspect
|
278
278
|
|
279
279
|
return _inspect(cls=self, values=values, field=field, **kwargs)
|
@@ -282,7 +282,7 @@ class QuerySet(models.QuerySet, CanValidate):
|
|
282
282
|
def standardize(
|
283
283
|
self, values: Iterable, field: str | StrField | None = None, **kwargs
|
284
284
|
):
|
285
|
-
"""{}
|
285
|
+
"""{}""" # noqa: D415
|
286
286
|
from ._can_validate import _standardize
|
287
287
|
|
288
288
|
return _standardize(cls=self, values=values, field=field, **kwargs)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import builtins
|
4
|
-
from typing import TYPE_CHECKING,
|
4
|
+
from typing import TYPE_CHECKING, List, NamedTuple
|
5
5
|
|
6
6
|
import dj_database_url
|
7
7
|
import lamindb_setup as ln_setup
|
@@ -12,7 +12,7 @@ from lamin_utils._lookup import Lookup
|
|
12
12
|
from lamindb_setup._connect_instance import get_owner_name_from_identifier
|
13
13
|
from lamindb_setup.core._docs import doc_args
|
14
14
|
from lamindb_setup.core._hub_core import connect_instance
|
15
|
-
from lnschema_core import
|
15
|
+
from lnschema_core.models import IsVersioned, Record
|
16
16
|
|
17
17
|
from lamindb._utils import attach_func_to_class_method
|
18
18
|
from lamindb.core._settings import settings
|
@@ -20,12 +20,14 @@ from lamindb.core._settings import settings
|
|
20
20
|
from ._from_values import get_or_create_records
|
21
21
|
|
22
22
|
if TYPE_CHECKING:
|
23
|
+
import pandas as pd
|
23
24
|
from lnschema_core.types import ListLike, StrField
|
24
25
|
|
26
|
+
|
25
27
|
IPYTHON = getattr(builtins, "__IPYTHON__", False)
|
26
28
|
|
27
29
|
|
28
|
-
def init_self_from_db(self:
|
30
|
+
def init_self_from_db(self: Record, existing_record: Record):
|
29
31
|
new_args = [
|
30
32
|
getattr(existing_record, field.attname) for field in self._meta.concrete_fields
|
31
33
|
]
|
@@ -34,7 +36,7 @@ def init_self_from_db(self: Registry, existing_record: Registry):
|
|
34
36
|
self._state.db = "default"
|
35
37
|
|
36
38
|
|
37
|
-
def validate_required_fields(orm:
|
39
|
+
def validate_required_fields(orm: Record, kwargs):
|
38
40
|
required_fields = {
|
39
41
|
k.name for k in orm._meta.fields if not k.null and k.default is None
|
40
42
|
}
|
@@ -47,14 +49,16 @@ def validate_required_fields(orm: Registry, kwargs):
|
|
47
49
|
raise TypeError(f"{missing_fields} are required.")
|
48
50
|
|
49
51
|
|
50
|
-
def suggest_records_with_similar_names(record:
|
52
|
+
def suggest_records_with_similar_names(record: Record, kwargs) -> bool:
|
51
53
|
"""Returns True if found exact match, otherwise False.
|
52
54
|
|
53
55
|
Logs similar matches if found.
|
54
56
|
"""
|
55
57
|
if kwargs.get("name") is None:
|
56
58
|
return False
|
57
|
-
queryset = _search(
|
59
|
+
queryset = _search(
|
60
|
+
record.__class__, kwargs["name"], field="name", truncate_words=True, limit=20
|
61
|
+
)
|
58
62
|
if not queryset.exists(): # empty queryset
|
59
63
|
return False
|
60
64
|
for alternative_record in queryset:
|
@@ -73,7 +77,7 @@ def suggest_records_with_similar_names(record: Registry, kwargs) -> bool:
|
|
73
77
|
return False
|
74
78
|
|
75
79
|
|
76
|
-
def __init__(orm:
|
80
|
+
def __init__(orm: Record, *args, **kwargs):
|
77
81
|
if not args:
|
78
82
|
validate_required_fields(orm, kwargs)
|
79
83
|
|
@@ -87,12 +91,12 @@ def __init__(orm: Registry, *args, **kwargs):
|
|
87
91
|
if match:
|
88
92
|
if "version" in kwargs:
|
89
93
|
version_comment = " and version"
|
90
|
-
existing_record = orm.filter(
|
94
|
+
existing_record = orm.__class__.filter(
|
91
95
|
name=kwargs["name"], version=kwargs["version"]
|
92
96
|
).one_or_none()
|
93
97
|
else:
|
94
98
|
version_comment = ""
|
95
|
-
existing_record = orm.filter(name=kwargs["name"]).one()
|
99
|
+
existing_record = orm.__class__.filter(name=kwargs["name"]).one()
|
96
100
|
if existing_record is not None:
|
97
101
|
logger.important(
|
98
102
|
f"returning existing {orm.__class__.__name__} record with same"
|
@@ -100,27 +104,66 @@ def __init__(orm: Registry, *args, **kwargs):
|
|
100
104
|
)
|
101
105
|
init_self_from_db(orm, existing_record)
|
102
106
|
return None
|
103
|
-
super(
|
107
|
+
super(Record, orm).__init__(**kwargs)
|
104
108
|
elif len(args) != len(orm._meta.concrete_fields):
|
105
109
|
raise ValueError("please provide keyword arguments, not plain arguments")
|
106
110
|
else:
|
107
111
|
# object is loaded from DB (**kwargs could be omitted below, I believe)
|
108
|
-
super(
|
112
|
+
super(Record, orm).__init__(*args, **kwargs)
|
113
|
+
|
114
|
+
|
115
|
+
@classmethod # type:ignore
|
116
|
+
@doc_args(Record.filter.__doc__)
|
117
|
+
def filter(cls, **expressions) -> QuerySet:
|
118
|
+
"""{}""" # noqa: D415
|
119
|
+
from lamindb._filter import filter
|
120
|
+
|
121
|
+
return filter(cls, **expressions)
|
122
|
+
|
123
|
+
|
124
|
+
@classmethod # type:ignore
|
125
|
+
@doc_args(Record.get.__doc__)
|
126
|
+
def get(cls, idlike: int | str) -> Record:
|
127
|
+
"""{}""" # noqa: D415
|
128
|
+
from lamindb._filter import filter
|
129
|
+
|
130
|
+
if isinstance(idlike, int):
|
131
|
+
return filter(cls, id=idlike).one()
|
132
|
+
else:
|
133
|
+
qs = filter(cls, uid__startswith=idlike)
|
134
|
+
if issubclass(cls, IsVersioned):
|
135
|
+
return qs.latest_version().one()
|
136
|
+
else:
|
137
|
+
return qs.one()
|
138
|
+
|
139
|
+
|
140
|
+
@classmethod # type:ignore
|
141
|
+
@doc_args(Record.df.__doc__)
|
142
|
+
def df(
|
143
|
+
cls, include: str | list[str] | None = None, join: str = "inner"
|
144
|
+
) -> pd.DataFrame:
|
145
|
+
"""{}""" # noqa: D415
|
146
|
+
from lamindb._filter import filter
|
147
|
+
|
148
|
+
query_set = filter(cls)
|
149
|
+
if hasattr(cls, "updated_at"):
|
150
|
+
query_set = query_set.order_by("-updated_at")
|
151
|
+
return query_set.df(include=include, join=join)
|
109
152
|
|
110
153
|
|
111
154
|
# from_values doesn't apply for QuerySet or Manager
|
112
155
|
@classmethod # type:ignore
|
113
|
-
@doc_args(
|
156
|
+
@doc_args(Record.from_values.__doc__)
|
114
157
|
def from_values(
|
115
158
|
cls,
|
116
159
|
values: ListLike,
|
117
160
|
field: StrField | None = None,
|
118
161
|
create: bool = False,
|
119
|
-
organism:
|
120
|
-
public_source:
|
162
|
+
organism: Record | str | None = None,
|
163
|
+
public_source: Record | None = None,
|
121
164
|
mute: bool = False,
|
122
|
-
) -> list[
|
123
|
-
"""{}
|
165
|
+
) -> list[Record]:
|
166
|
+
"""{}""" # noqa: D415
|
124
167
|
from_public = True if cls.__module__.startswith("lnschema_bionty.") else False
|
125
168
|
field_str = get_default_str_field(cls, field=field)
|
126
169
|
return get_or_create_records(
|
@@ -134,14 +177,6 @@ def from_values(
|
|
134
177
|
)
|
135
178
|
|
136
179
|
|
137
|
-
# From: https://stackoverflow.com/a/37648265
|
138
|
-
def _order_queryset_by_ids(queryset: QuerySet, ids: Iterable):
|
139
|
-
from django.db.models import Case, When
|
140
|
-
|
141
|
-
preserved = Case(*[When(uid=pk, then=pos) for pos, pk in enumerate(ids)])
|
142
|
-
return queryset.filter(uid__in=ids).order_by(preserved)
|
143
|
-
|
144
|
-
|
145
180
|
def _search(
|
146
181
|
cls,
|
147
182
|
string: str,
|
@@ -172,7 +207,7 @@ def _search(
|
|
172
207
|
fields.append(field.field.name)
|
173
208
|
except AttributeError as error:
|
174
209
|
raise TypeError(
|
175
|
-
"Please pass a
|
210
|
+
"Please pass a Record string field, e.g., `CellType.name`!"
|
176
211
|
) from error
|
177
212
|
else:
|
178
213
|
fields.append(field)
|
@@ -187,7 +222,7 @@ def _search(
|
|
187
222
|
else:
|
188
223
|
return word
|
189
224
|
|
190
|
-
decomposed_string = string.split()
|
225
|
+
decomposed_string = str(string).split()
|
191
226
|
# add the entire string back
|
192
227
|
decomposed_string += [string]
|
193
228
|
for word in decomposed_string:
|
@@ -222,7 +257,7 @@ def _search(
|
|
222
257
|
|
223
258
|
|
224
259
|
@classmethod # type: ignore
|
225
|
-
@doc_args(
|
260
|
+
@doc_args(Record.search.__doc__)
|
226
261
|
def search(
|
227
262
|
cls,
|
228
263
|
string: str,
|
@@ -231,7 +266,7 @@ def search(
|
|
231
266
|
limit: int | None = 20,
|
232
267
|
case_sensitive: bool = False,
|
233
268
|
) -> QuerySet:
|
234
|
-
"""{}
|
269
|
+
"""{}""" # noqa: D415
|
235
270
|
return _search(
|
236
271
|
cls=cls,
|
237
272
|
string=string,
|
@@ -247,7 +282,7 @@ def _lookup(
|
|
247
282
|
return_field: StrField | None = None,
|
248
283
|
using_key: str | None = None,
|
249
284
|
) -> NamedTuple:
|
250
|
-
"""{}
|
285
|
+
"""{}""" # noqa: D415
|
251
286
|
queryset = _queryset(cls, using_key=using_key)
|
252
287
|
field = get_default_str_field(orm=queryset.model, field=field)
|
253
288
|
|
@@ -266,18 +301,18 @@ def _lookup(
|
|
266
301
|
|
267
302
|
|
268
303
|
@classmethod # type: ignore
|
269
|
-
@doc_args(
|
304
|
+
@doc_args(Record.lookup.__doc__)
|
270
305
|
def lookup(
|
271
306
|
cls,
|
272
307
|
field: StrField | None = None,
|
273
308
|
return_field: StrField | None = None,
|
274
309
|
) -> NamedTuple:
|
275
|
-
"""{}
|
310
|
+
"""{}""" # noqa: D415
|
276
311
|
return _lookup(cls=cls, field=field, return_field=return_field)
|
277
312
|
|
278
313
|
|
279
314
|
def get_default_str_field(
|
280
|
-
orm:
|
315
|
+
orm: Record | QuerySet | Manager,
|
281
316
|
*,
|
282
317
|
field: str | StrField | None = None,
|
283
318
|
) -> str:
|
@@ -307,7 +342,7 @@ def get_default_str_field(
|
|
307
342
|
# no default field can be found
|
308
343
|
if field is None:
|
309
344
|
raise ValueError(
|
310
|
-
"please pass a
|
345
|
+
"please pass a Record string field, e.g., `CellType.name`!"
|
311
346
|
)
|
312
347
|
else:
|
313
348
|
field = field.name # type:ignore
|
@@ -316,17 +351,20 @@ def get_default_str_field(
|
|
316
351
|
field = field.field.name
|
317
352
|
except AttributeError:
|
318
353
|
raise TypeError(
|
319
|
-
"please pass a
|
354
|
+
"please pass a Record string field, e.g., `CellType.name`!"
|
320
355
|
) from None
|
321
356
|
|
322
357
|
return field
|
323
358
|
|
324
359
|
|
325
|
-
def _queryset(cls:
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
360
|
+
def _queryset(cls: Record | QuerySet | Manager, using_key: str) -> QuerySet:
|
361
|
+
if isinstance(cls, (QuerySet, Manager)):
|
362
|
+
return cls.all()
|
363
|
+
elif using_key is None:
|
364
|
+
return cls.objects.all()
|
365
|
+
else:
|
366
|
+
# using must be called on cls, otherwise the connection isn't found
|
367
|
+
return cls.using(using_key).all()
|
330
368
|
|
331
369
|
|
332
370
|
def add_db_connection(db: str, using: str):
|
@@ -340,12 +378,12 @@ def add_db_connection(db: str, using: str):
|
|
340
378
|
|
341
379
|
|
342
380
|
@classmethod # type: ignore
|
343
|
-
@doc_args(
|
381
|
+
@doc_args(Record.using.__doc__)
|
344
382
|
def using(
|
345
383
|
cls,
|
346
384
|
instance: str,
|
347
385
|
) -> QuerySet:
|
348
|
-
"""{}
|
386
|
+
"""{}""" # noqa: D415
|
349
387
|
from lamindb_setup._connect_instance import (
|
350
388
|
load_instance_settings,
|
351
389
|
update_db_using_local,
|
@@ -378,7 +416,7 @@ REGISTRY_UNIQUE_FIELD = {
|
|
378
416
|
|
379
417
|
|
380
418
|
def update_fk_to_default_db(
|
381
|
-
records:
|
419
|
+
records: Record | list[Record] | QuerySet,
|
382
420
|
fk: str,
|
383
421
|
using_key: str | None,
|
384
422
|
):
|
@@ -418,12 +456,12 @@ def transfer_fk_to_default_db_bulk(records: list | QuerySet, using_key: str | No
|
|
418
456
|
|
419
457
|
|
420
458
|
def transfer_to_default_db(
|
421
|
-
record:
|
459
|
+
record: Record,
|
422
460
|
using_key: str | None,
|
423
461
|
save: bool = False,
|
424
462
|
mute: bool = False,
|
425
463
|
transfer_fk: bool = True,
|
426
|
-
) ->
|
464
|
+
) -> Record | None:
|
427
465
|
db = record._state.db
|
428
466
|
if db is not None and db != "default" and using_key is None:
|
429
467
|
registry = record.__class__
|
@@ -476,7 +514,7 @@ def transfer_to_default_db(
|
|
476
514
|
|
477
515
|
|
478
516
|
# docstring handled through attach_func_to_class_method
|
479
|
-
def save(self, *args, **kwargs) ->
|
517
|
+
def save(self, *args, **kwargs) -> Record:
|
480
518
|
using_key = None
|
481
519
|
if "using" in kwargs:
|
482
520
|
using_key = kwargs["using"]
|
@@ -496,7 +534,7 @@ def save(self, *args, **kwargs) -> Registry:
|
|
496
534
|
save_kwargs = kwargs.copy()
|
497
535
|
if "parents" in save_kwargs:
|
498
536
|
save_kwargs.pop("parents")
|
499
|
-
super(
|
537
|
+
super(Record, self).save(*args, **save_kwargs)
|
500
538
|
# perform transfer of many-to-many fields
|
501
539
|
# only supported for Artifact and Collection records
|
502
540
|
if db is not None and db != "default" and using_key is None:
|
@@ -531,6 +569,9 @@ def save(self, *args, **kwargs) -> Registry:
|
|
531
569
|
|
532
570
|
METHOD_NAMES = [
|
533
571
|
"__init__",
|
572
|
+
"filter",
|
573
|
+
"get",
|
574
|
+
"df",
|
534
575
|
"search",
|
535
576
|
"lookup",
|
536
577
|
"save",
|
@@ -542,10 +583,10 @@ if ln_setup._TESTING: # type: ignore
|
|
542
583
|
from inspect import signature
|
543
584
|
|
544
585
|
SIGS = {
|
545
|
-
name: signature(getattr(
|
586
|
+
name: signature(getattr(Record, name))
|
546
587
|
for name in METHOD_NAMES
|
547
588
|
if not name.startswith("__")
|
548
589
|
}
|
549
590
|
|
550
591
|
for name in METHOD_NAMES:
|
551
|
-
attach_func_to_class_method(name,
|
592
|
+
attach_func_to_class_method(name, Record, globals())
|
lamindb/_save.py
CHANGED
@@ -13,7 +13,7 @@ from django.db import IntegrityError, transaction
|
|
13
13
|
from django.utils.functional import partition
|
14
14
|
from lamin_utils import logger
|
15
15
|
from lamindb_setup.core.upath import LocalPathClasses
|
16
|
-
from lnschema_core.models import Artifact,
|
16
|
+
from lnschema_core.models import Artifact, Record
|
17
17
|
|
18
18
|
from lamindb.core._settings import settings
|
19
19
|
from lamindb.core.storage.paths import (
|
@@ -28,7 +28,7 @@ if TYPE_CHECKING:
|
|
28
28
|
|
29
29
|
|
30
30
|
def save(
|
31
|
-
records: Iterable[
|
31
|
+
records: Iterable[Record], ignore_conflicts: bool | None = False, **kwargs
|
32
32
|
) -> None:
|
33
33
|
"""Bulk save to registries & storage.
|
34
34
|
|
@@ -42,7 +42,7 @@ def save(
|
|
42
42
|
existing records! Use ``record.save()`` for these use cases.
|
43
43
|
|
44
44
|
Args:
|
45
|
-
records: Multiple :class:`~lamindb.core.
|
45
|
+
records: Multiple :class:`~lamindb.core.Record` objects.
|
46
46
|
ignore_conflicts: If ``True``, do not error if some records violate a
|
47
47
|
unique or another constraint. However, it won't inplace update the id
|
48
48
|
fields of records. If you need records with ids, you need to query
|
@@ -69,7 +69,7 @@ def save(
|
|
69
69
|
>>> transform.save()
|
70
70
|
|
71
71
|
"""
|
72
|
-
if isinstance(records,
|
72
|
+
if isinstance(records, Record):
|
73
73
|
raise ValueError("Please use record.save() if saving a single record.")
|
74
74
|
|
75
75
|
# previously, this was all set based,
|
@@ -122,7 +122,7 @@ def save(
|
|
122
122
|
return None
|
123
123
|
|
124
124
|
|
125
|
-
def bulk_create(records: Iterable[
|
125
|
+
def bulk_create(records: Iterable[Record], ignore_conflicts: bool | None = False):
|
126
126
|
records_by_orm = defaultdict(list)
|
127
127
|
for record in records:
|
128
128
|
records_by_orm[record.__class__].append(record)
|
@@ -130,7 +130,7 @@ def bulk_create(records: Iterable[Registry], ignore_conflicts: bool | None = Fal
|
|
130
130
|
orm.objects.bulk_create(records, ignore_conflicts=ignore_conflicts)
|
131
131
|
|
132
132
|
|
133
|
-
def bulk_update(records: Iterable[
|
133
|
+
def bulk_update(records: Iterable[Record], ignore_conflicts: bool | None = False):
|
134
134
|
records_by_orm = defaultdict(list)
|
135
135
|
for record in records:
|
136
136
|
records_by_orm[record.__class__].append(record)
|
lamindb/_storage.py
CHANGED
@@ -6,7 +6,7 @@ from lnschema_core import Storage
|
|
6
6
|
@property # type: ignore
|
7
7
|
@doc_args(Storage.path.__doc__)
|
8
8
|
def path(self) -> UPath:
|
9
|
-
"""{}
|
9
|
+
"""{}""" # noqa: D415
|
10
10
|
access_token = self._access_token if hasattr(self, "_access_token") else None
|
11
11
|
return create_path(self.root, access_token=access_token)
|
12
12
|
|