lamindb 1.10.1__py3-none-any.whl → 1.11a1__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 +89 -49
- lamindb/_finish.py +14 -12
- lamindb/_tracked.py +2 -4
- lamindb/_view.py +1 -1
- lamindb/base/__init__.py +2 -1
- lamindb/base/dtypes.py +76 -0
- lamindb/core/_settings.py +45 -2
- lamindb/core/storage/_anndata_accessor.py +118 -26
- lamindb/core/storage/_backed_access.py +10 -7
- lamindb/core/storage/_spatialdata_accessor.py +15 -4
- lamindb/core/storage/_zarr.py +3 -0
- lamindb/curators/_legacy.py +16 -3
- lamindb/curators/core.py +439 -191
- lamindb/examples/cellxgene/__init__.py +8 -3
- lamindb/examples/cellxgene/_cellxgene.py +127 -13
- lamindb/examples/cellxgene/{cxg_schema_versions.csv → cellxgene_schema_versions.csv} +11 -0
- lamindb/examples/croissant/__init__.py +12 -2
- lamindb/examples/datasets/__init__.py +2 -2
- lamindb/examples/datasets/_core.py +1 -1
- lamindb/examples/datasets/_small.py +66 -22
- lamindb/examples/datasets/mini_immuno.py +1 -0
- lamindb/migrations/0118_alter_recordproject_value_projectrecord.py +99 -0
- lamindb/migrations/0119_rename_records_project_linked_in_records.py +26 -0
- lamindb/migrations/{0117_squashed.py → 0119_squashed.py} +92 -5
- lamindb/migrations/0120_add_record_fk_constraint.py +64 -0
- lamindb/migrations/0121_recorduser.py +53 -0
- lamindb/models/__init__.py +3 -1
- lamindb/models/_describe.py +2 -2
- lamindb/models/_feature_manager.py +53 -53
- lamindb/models/_from_values.py +2 -2
- lamindb/models/_is_versioned.py +4 -4
- lamindb/models/_label_manager.py +4 -4
- lamindb/models/artifact.py +336 -136
- lamindb/models/artifact_set.py +36 -1
- lamindb/models/can_curate.py +1 -2
- lamindb/models/collection.py +3 -34
- lamindb/models/feature.py +111 -7
- lamindb/models/has_parents.py +11 -11
- lamindb/models/project.py +42 -2
- lamindb/models/query_manager.py +16 -7
- lamindb/models/query_set.py +59 -34
- lamindb/models/record.py +25 -4
- lamindb/models/run.py +8 -6
- lamindb/models/schema.py +54 -26
- lamindb/models/sqlrecord.py +123 -25
- lamindb/models/storage.py +59 -14
- lamindb/models/transform.py +17 -17
- lamindb/models/ulabel.py +6 -1
- {lamindb-1.10.1.dist-info → lamindb-1.11a1.dist-info}/METADATA +3 -3
- {lamindb-1.10.1.dist-info → lamindb-1.11a1.dist-info}/RECORD +52 -47
- {lamindb-1.10.1.dist-info → lamindb-1.11a1.dist-info}/LICENSE +0 -0
- {lamindb-1.10.1.dist-info → lamindb-1.11a1.dist-info}/WHEEL +0 -0
lamindb/models/sqlrecord.py
CHANGED
@@ -334,7 +334,7 @@ RECORD_REGISTRY_EXAMPLE = """Example::
|
|
334
334
|
experiment.save()
|
335
335
|
|
336
336
|
# `Experiment` refers to the registry, which you can query
|
337
|
-
df = Experiment.filter(name__startswith="my ").
|
337
|
+
df = Experiment.filter(name__startswith="my ").to_dataframe()
|
338
338
|
"""
|
339
339
|
|
340
340
|
|
@@ -425,7 +425,7 @@ class Registry(ModelBase):
|
|
425
425
|
|
426
426
|
Examples:
|
427
427
|
>>> ln.ULabel(name="my label").save()
|
428
|
-
>>> ln.ULabel.filter(name__startswith="my").
|
428
|
+
>>> ln.ULabel.filter(name__startswith="my").to_dataframe()
|
429
429
|
"""
|
430
430
|
from .query_set import QuerySet
|
431
431
|
|
@@ -464,7 +464,7 @@ class Registry(ModelBase):
|
|
464
464
|
|
465
465
|
return QuerySet(model=cls).get(idlike, **expressions)
|
466
466
|
|
467
|
-
def
|
467
|
+
def to_dataframe(
|
468
468
|
cls,
|
469
469
|
include: str | list[str] | None = None,
|
470
470
|
features: bool | list[str] | str = False,
|
@@ -492,21 +492,30 @@ class Registry(ModelBase):
|
|
492
492
|
|
493
493
|
Include the name of the creator in the `DataFrame`:
|
494
494
|
|
495
|
-
>>> ln.ULabel.
|
495
|
+
>>> ln.ULabel.to_dataframe(include="created_by__name"])
|
496
496
|
|
497
497
|
Include display of features for `Artifact`:
|
498
498
|
|
499
|
-
>>> df = ln.Artifact.
|
499
|
+
>>> df = ln.Artifact.to_dataframe(features=True)
|
500
500
|
>>> ln.view(df) # visualize with type annotations
|
501
501
|
|
502
502
|
Only include select features:
|
503
503
|
|
504
|
-
>>> df = ln.Artifact.
|
504
|
+
>>> df = ln.Artifact.to_dataframe(features=["cell_type_by_expert", "cell_type_by_model"])
|
505
505
|
"""
|
506
506
|
query_set = cls.filter()
|
507
507
|
if hasattr(cls, "updated_at"):
|
508
508
|
query_set = query_set.order_by("-updated_at")
|
509
|
-
return query_set[:limit].
|
509
|
+
return query_set[:limit].to_dataframe(include=include, features=features)
|
510
|
+
|
511
|
+
@deprecated(new_name="to_dataframe")
|
512
|
+
def df(
|
513
|
+
cls,
|
514
|
+
include: str | list[str] | None = None,
|
515
|
+
features: bool | list[str] | str = False,
|
516
|
+
limit: int = 100,
|
517
|
+
) -> pd.DataFrame:
|
518
|
+
return cls.to_dataframe(include, features, limit)
|
510
519
|
|
511
520
|
@doc_args(_search.__doc__)
|
512
521
|
def search(
|
@@ -795,7 +804,7 @@ class BaseSQLRecord(models.Model, metaclass=Registry):
|
|
795
804
|
artifacts: list = []
|
796
805
|
if self.__class__.__name__ == "Collection" and self.id is not None:
|
797
806
|
# when creating a new collection without being able to access artifacts
|
798
|
-
artifacts = self.ordered_artifacts.
|
807
|
+
artifacts = self.ordered_artifacts.to_list()
|
799
808
|
pre_existing_record = None
|
800
809
|
# consider records that are being transferred from other databases
|
801
810
|
transfer_logs: dict[str, list[str]] = {
|
@@ -920,13 +929,15 @@ class BaseSQLRecord(models.Model, metaclass=Registry):
|
|
920
929
|
|
921
930
|
def delete(self) -> None:
|
922
931
|
"""Delete."""
|
923
|
-
#
|
924
|
-
#
|
925
|
-
#
|
926
|
-
# of a
|
927
|
-
|
928
|
-
|
929
|
-
|
932
|
+
# deal with versioned records
|
933
|
+
# _overwrite_versions is set to True for folder artifacts
|
934
|
+
# no need to set the new latest version becase all versions are deleted
|
935
|
+
# when deleting the latest version of a folder artifact
|
936
|
+
if (
|
937
|
+
isinstance(self, IsVersioned)
|
938
|
+
and self.is_latest
|
939
|
+
and not getattr(self, "_overwrite_versions", False)
|
940
|
+
):
|
930
941
|
new_latest = (
|
931
942
|
self.__class__.objects.using(self._state.db)
|
932
943
|
.filter(is_latest=False, uid__startswith=self.stem_uid)
|
@@ -938,7 +949,7 @@ class BaseSQLRecord(models.Model, metaclass=Registry):
|
|
938
949
|
with transaction.atomic():
|
939
950
|
new_latest.save()
|
940
951
|
super().delete() # type: ignore
|
941
|
-
logger.warning(f"new latest version is {new_latest}")
|
952
|
+
logger.warning(f"new latest version is: {new_latest}")
|
942
953
|
return None
|
943
954
|
super().delete()
|
944
955
|
|
@@ -952,6 +963,7 @@ class Space(BaseSQLRecord):
|
|
952
963
|
"""
|
953
964
|
|
954
965
|
class Meta:
|
966
|
+
app_label = "lamindb"
|
955
967
|
constraints = [
|
956
968
|
models.UniqueConstraint(Lower("name"), name="unique_space_name_lower")
|
957
969
|
]
|
@@ -964,8 +976,7 @@ class Space(BaseSQLRecord):
|
|
964
976
|
editable=False,
|
965
977
|
unique=True,
|
966
978
|
max_length=12,
|
967
|
-
default=
|
968
|
-
db_default="aaaaaaaaaaaa",
|
979
|
+
default=base62_12,
|
969
980
|
db_index=True,
|
970
981
|
)
|
971
982
|
"""Universal id."""
|
@@ -998,6 +1009,21 @@ class Space(BaseSQLRecord):
|
|
998
1009
|
*args,
|
999
1010
|
**kwargs,
|
1000
1011
|
):
|
1012
|
+
if "uid" not in kwargs:
|
1013
|
+
warn = False
|
1014
|
+
msg = ""
|
1015
|
+
isettings = setup_settings.instance
|
1016
|
+
if (dialect := isettings.dialect) != "postgresql":
|
1017
|
+
warn = True
|
1018
|
+
msg = f"on {dialect} databases"
|
1019
|
+
elif not isettings.is_on_hub:
|
1020
|
+
warn = True
|
1021
|
+
msg = "on local instances"
|
1022
|
+
if warn:
|
1023
|
+
logger.warning(
|
1024
|
+
f"creating spaces manually {msg} is possible for demo purposes, "
|
1025
|
+
"but does *not* affect access permissions"
|
1026
|
+
)
|
1001
1027
|
super().__init__(*args, **kwargs)
|
1002
1028
|
|
1003
1029
|
|
@@ -1007,6 +1033,12 @@ class Branch(BaseSQLRecord):
|
|
1007
1033
|
Every `SQLRecord` has a `branch` field, which dictates where a record appears in queries & searches.
|
1008
1034
|
"""
|
1009
1035
|
|
1036
|
+
class Meta:
|
1037
|
+
app_label = "lamindb"
|
1038
|
+
constraints = [
|
1039
|
+
models.UniqueConstraint(Lower("name"), name="unique_branch_name_lower")
|
1040
|
+
]
|
1041
|
+
|
1010
1042
|
# below isn't fully implemented but a roadmap
|
1011
1043
|
# - 3: template (hidden in queries & searches)
|
1012
1044
|
# - 2: locked (same as default, but locked for edits except for space admins)
|
@@ -1018,11 +1050,6 @@ class Branch(BaseSQLRecord):
|
|
1018
1050
|
# that can be merged onto the main branch in an experience akin to a Pull Request. The mapping
|
1019
1051
|
# onto a semantic branch name is handled through LaminHub.
|
1020
1052
|
|
1021
|
-
class Meta:
|
1022
|
-
constraints = [
|
1023
|
-
models.UniqueConstraint(Lower("name"), name="unique_branch_name_lower")
|
1024
|
-
]
|
1025
|
-
|
1026
1053
|
id: int = models.AutoField(primary_key=True)
|
1027
1054
|
"""An integer id that's synchronized for a family of coupled database instances.
|
1028
1055
|
|
@@ -1119,6 +1146,76 @@ class SQLRecord(BaseSQLRecord, metaclass=Registry):
|
|
1119
1146
|
def _branch_code(self, value: int):
|
1120
1147
|
self.branch_id = value
|
1121
1148
|
|
1149
|
+
def delete(self, permanent: bool | None = None, **kwargs) -> None:
|
1150
|
+
"""Delete record.
|
1151
|
+
|
1152
|
+
Args:
|
1153
|
+
permanent: Whether to permanently delete the record (skips trash).
|
1154
|
+
|
1155
|
+
Examples:
|
1156
|
+
|
1157
|
+
For any `SQLRecord` object `record`, call:
|
1158
|
+
|
1159
|
+
>>> record.delete()
|
1160
|
+
"""
|
1161
|
+
if self._state.adding:
|
1162
|
+
logger.warning("record is not yet saved, delete has no effect")
|
1163
|
+
return
|
1164
|
+
name_with_module = self.__class__.__get_name_with_module__()
|
1165
|
+
|
1166
|
+
if name_with_module == "Artifact":
|
1167
|
+
# this first check means an invalid delete fails fast rather than cascading through
|
1168
|
+
# database and storage permission errors
|
1169
|
+
isettings = setup_settings.instance
|
1170
|
+
if self.storage.instance_uid != isettings.uid and (
|
1171
|
+
kwargs["storage"] or kwargs["storage"] is None
|
1172
|
+
):
|
1173
|
+
from ..errors import IntegrityError
|
1174
|
+
from .storage import Storage
|
1175
|
+
|
1176
|
+
raise IntegrityError(
|
1177
|
+
"Cannot simply delete artifacts outside of this instance's managed storage locations."
|
1178
|
+
"\n(1) If you only want to delete the metadata record in this instance, pass `storage=False`"
|
1179
|
+
f"\n(2) If you want to delete the artifact in storage, please load the managing lamindb instance (uid={self.storage.instance_uid})."
|
1180
|
+
f"\nThese are all managed storage locations of this instance:\n{Storage.filter(instance_uid=isettings.uid).to_dataframe()}"
|
1181
|
+
)
|
1182
|
+
|
1183
|
+
# change branch_id to trash
|
1184
|
+
trash_branch_id = -1
|
1185
|
+
if self.branch_id > trash_branch_id and permanent is not True:
|
1186
|
+
self.branch_id = trash_branch_id
|
1187
|
+
self.save()
|
1188
|
+
logger.warning(f"moved record to trash (`branch_id = -1`): {self}")
|
1189
|
+
return
|
1190
|
+
|
1191
|
+
# permanent delete
|
1192
|
+
if permanent is None:
|
1193
|
+
response = input(
|
1194
|
+
f"Record {self.uid} is already in trash! Are you sure you want to delete it from your"
|
1195
|
+
" database? You can't undo this action. (y/n) "
|
1196
|
+
)
|
1197
|
+
delete_record = response == "y"
|
1198
|
+
else:
|
1199
|
+
delete_record = permanent
|
1200
|
+
|
1201
|
+
if delete_record:
|
1202
|
+
if name_with_module == "Run":
|
1203
|
+
from .run import delete_run_artifacts
|
1204
|
+
|
1205
|
+
delete_run_artifacts(self)
|
1206
|
+
elif name_with_module == "Transform":
|
1207
|
+
from .transform import delete_transform_relations
|
1208
|
+
|
1209
|
+
delete_transform_relations(self)
|
1210
|
+
elif name_with_module == "Artifact":
|
1211
|
+
from .artifact import delete_permanently
|
1212
|
+
|
1213
|
+
delete_permanently(
|
1214
|
+
self, storage=kwargs["storage"], using_key=kwargs["using_key"]
|
1215
|
+
)
|
1216
|
+
if name_with_module != "Artifact":
|
1217
|
+
super().delete()
|
1218
|
+
|
1122
1219
|
|
1123
1220
|
def _format_django_validation_error(record: SQLRecord, e: DjangoValidationError):
|
1124
1221
|
"""Pretty print Django validation errors."""
|
@@ -1464,7 +1561,7 @@ def check_name_change(record: SQLRecord):
|
|
1464
1561
|
.exclude(feature_id=None) # must have a feature
|
1465
1562
|
.distinct()
|
1466
1563
|
)
|
1467
|
-
artifact_ids = linked_records.
|
1564
|
+
artifact_ids = linked_records.to_list("artifact__uid")
|
1468
1565
|
n = len(artifact_ids)
|
1469
1566
|
if n > 0:
|
1470
1567
|
s = "s" if n > 1 else ""
|
@@ -1482,7 +1579,7 @@ def check_name_change(record: SQLRecord):
|
|
1482
1579
|
# when a feature is renamed
|
1483
1580
|
elif isinstance(record, Feature):
|
1484
1581
|
# only internal features are associated with schemas
|
1485
|
-
linked_artifacts = Artifact.filter(feature_sets__features=record).
|
1582
|
+
linked_artifacts = Artifact.filter(feature_sets__features=record).to_list(
|
1486
1583
|
"uid"
|
1487
1584
|
)
|
1488
1585
|
n = len(linked_artifacts)
|
@@ -1806,6 +1903,7 @@ class Migration(BaseSQLRecord):
|
|
1806
1903
|
|
1807
1904
|
class Meta:
|
1808
1905
|
db_table = "django_migrations"
|
1906
|
+
app_label = "lamindb"
|
1809
1907
|
managed = False
|
1810
1908
|
|
1811
1909
|
|
lamindb/models/storage.py
CHANGED
@@ -4,6 +4,7 @@ from typing import (
|
|
4
4
|
TYPE_CHECKING,
|
5
5
|
overload,
|
6
6
|
)
|
7
|
+
from uuid import UUID
|
7
8
|
|
8
9
|
from django.db import models
|
9
10
|
from lamin_utils import logger
|
@@ -11,6 +12,8 @@ from lamindb_setup import settings as setup_settings
|
|
11
12
|
from lamindb_setup.core._hub_core import (
|
12
13
|
delete_storage_record,
|
13
14
|
get_storage_records_for_instance,
|
15
|
+
select_space,
|
16
|
+
update_storage_with_space,
|
14
17
|
)
|
15
18
|
from lamindb_setup.core._settings_storage import (
|
16
19
|
StorageSettings,
|
@@ -25,7 +28,7 @@ from lamindb.base.fields import (
|
|
25
28
|
|
26
29
|
from ..base.ids import base62_12
|
27
30
|
from .run import TracksRun, TracksUpdates
|
28
|
-
from .sqlrecord import SQLRecord
|
31
|
+
from .sqlrecord import Space, SQLRecord
|
29
32
|
|
30
33
|
if TYPE_CHECKING:
|
31
34
|
from pathlib import Path
|
@@ -61,22 +64,31 @@ class Storage(SQLRecord, TracksRun, TracksUpdates):
|
|
61
64
|
|
62
65
|
.. dropdown:: Managing access to storage locations across instances
|
63
66
|
|
64
|
-
You can manage access through
|
65
|
-
|
67
|
+
You can manage access through LaminHub's fine-grained access management or
|
68
|
+
through AWS policies that you attach to your S3 bucket.
|
66
69
|
|
67
|
-
|
68
|
-
By clicking the green button that says "Connect S3 bucket",
|
69
|
-
|
70
|
+
To enable access management via LaminHub, head over to `https://lamin.ai/{account}/infrastructure`.
|
71
|
+
By clicking the green button that says "Connect S3 bucket", LaminDB will start connecting through federated S3 tokens
|
72
|
+
so that your collaborators access data based on their permissions in LaminHub.
|
70
73
|
:doc:`docs:access` has more details.
|
71
74
|
|
72
75
|
.. image:: https://lamin-site-assets.s3.amazonaws.com/.lamindb/ze8hkgVxVptSSZEU0000.png
|
73
76
|
:width: 800px
|
74
77
|
|
78
|
+
By default, access permissions to a storage location are governed by the access permissions of its managing instance. If you
|
79
|
+
want to further restrict access to a storage location, you can move it into a space::
|
80
|
+
|
81
|
+
space = ln.Space.get(name="my-space")
|
82
|
+
storage_loc = ln.Storage.get(root="s3://my-storace-location")
|
83
|
+
storage_loc.space = space
|
84
|
+
storage_loc.save()
|
85
|
+
|
75
86
|
If you don't want to store data in the cloud, you can use local storage locations: :doc:`faq/keep-artifacts-local`.
|
76
87
|
|
77
88
|
Args:
|
78
89
|
root: `str` The root path of the storage location, e.g., `"./mydir"`, `"s3://my-bucket"`, `"s3://my-bucket/myfolder"`, `"gs://my-bucket/myfolder"`, `"/nfs/shared/datasets/genomics"`, `"/weka/shared/models/"`, ...
|
79
90
|
description: `str | None = None` An optional description.
|
91
|
+
space: `Space | None = None` A space to restrict access permissions to the storage location.
|
80
92
|
host: `str | None = None` For local storage locations, pass a globally unique host identifier, e.g. `"my-institute-cluster-1"`, `"my-server-abcd"`, ...
|
81
93
|
|
82
94
|
See Also:
|
@@ -107,18 +119,17 @@ class Storage(SQLRecord, TracksRun, TracksUpdates):
|
|
107
119
|
|
108
120
|
ln.Storage(root="/dir/our-shared-dir", host="our-server-123").save()
|
109
121
|
|
110
|
-
|
122
|
+
Globally switch to another storage location::
|
111
123
|
|
112
124
|
ln.settings.storage = "/dir/our-shared-dir" # or "s3://our-bucket/our-folder", "gs://our-bucket/our-folder", ...
|
113
125
|
|
114
|
-
|
126
|
+
Or if you're operating in `keep-artifacts-local` mode (:doc:`faq/keep-artifacts-local`)::
|
115
127
|
|
116
|
-
ln.
|
117
|
-
ln.settings.local_storage = "/dir/our-other-shared-dir" # switch
|
128
|
+
ln.settings.local_storage = "/dir/our-other-shared-dir"
|
118
129
|
|
119
130
|
View all storage locations used in your LaminDB instance::
|
120
131
|
|
121
|
-
ln.Storage.
|
132
|
+
ln.Storage.to_dataframe()
|
122
133
|
|
123
134
|
Notes:
|
124
135
|
|
@@ -146,6 +157,7 @@ class Storage(SQLRecord, TracksRun, TracksUpdates):
|
|
146
157
|
|
147
158
|
class Meta(SQLRecord.Meta, TracksRun.Meta, TracksUpdates.Meta):
|
148
159
|
abstract = False
|
160
|
+
app_label = "lamindb"
|
149
161
|
|
150
162
|
_name_field: str = "root"
|
151
163
|
|
@@ -174,6 +186,7 @@ class Storage(SQLRecord, TracksRun, TracksUpdates):
|
|
174
186
|
root: str,
|
175
187
|
*,
|
176
188
|
description: str | None = None,
|
189
|
+
space: Space | None = None,
|
177
190
|
host: str | None = None,
|
178
191
|
): ...
|
179
192
|
|
@@ -190,6 +203,8 @@ class Storage(SQLRecord, TracksRun, TracksUpdates):
|
|
190
203
|
):
|
191
204
|
if len(args) == len(self._meta.concrete_fields):
|
192
205
|
super().__init__(*args)
|
206
|
+
self._old_space = self.space
|
207
|
+
self._old_space_id = self.space_id
|
193
208
|
return None
|
194
209
|
if args:
|
195
210
|
assert len(args) == 1, ( # noqa: S101
|
@@ -213,17 +228,30 @@ class Storage(SQLRecord, TracksRun, TracksUpdates):
|
|
213
228
|
).one_or_none()
|
214
229
|
else:
|
215
230
|
storage_record = Storage.filter(root=kwargs["root"]).one_or_none()
|
231
|
+
space = kwargs.get("space", None)
|
216
232
|
if storage_record is not None:
|
217
233
|
from .sqlrecord import init_self_from_db
|
218
234
|
|
219
235
|
init_self_from_db(self, storage_record)
|
236
|
+
self._old_space = self.space
|
237
|
+
self._old_space_id = self.space_id
|
220
238
|
return None
|
221
239
|
|
222
240
|
skip_preparation = kwargs.pop("_skip_preparation", False)
|
223
241
|
if skip_preparation:
|
242
|
+
assert space is None, "`space` must not be set if _skip_preparation is True" # noqa: S101
|
224
243
|
super().__init__(*args, **kwargs)
|
225
244
|
return None
|
226
245
|
|
246
|
+
space_uuid = None
|
247
|
+
if space is not None:
|
248
|
+
hub_space_record = select_space(space.uid)
|
249
|
+
if hub_space_record is None:
|
250
|
+
raise ValueError(
|
251
|
+
"Please first create a space on the hub: https://docs.lamin.ai/access"
|
252
|
+
)
|
253
|
+
space_uuid = UUID(hub_space_record["id"])
|
254
|
+
|
227
255
|
# instance_id won't take effect if
|
228
256
|
# - there is no write access
|
229
257
|
# - the storage location is already managed by another instance
|
@@ -232,8 +260,8 @@ class Storage(SQLRecord, TracksRun, TracksUpdates):
|
|
232
260
|
instance_id=setup_settings.instance._id,
|
233
261
|
instance_slug=setup_settings.instance.slug,
|
234
262
|
register_hub=setup_settings.instance.is_on_hub,
|
235
|
-
prevent_register_hub=not setup_settings.instance.is_on_hub,
|
236
263
|
region=kwargs.get("region", None), # host was renamed to region already
|
264
|
+
space_uuid=space_uuid,
|
237
265
|
)
|
238
266
|
# ssettings performed validation and normalization of the root path
|
239
267
|
kwargs["root"] = ssettings.root_as_str # noqa: S101
|
@@ -274,6 +302,8 @@ class Storage(SQLRecord, TracksRun, TracksUpdates):
|
|
274
302
|
f"{managed_message} storage location at {kwargs['root']}{is_managed_by_instance}{hub_message}"
|
275
303
|
)
|
276
304
|
super().__init__(**kwargs)
|
305
|
+
self._old_space = self.space
|
306
|
+
self._old_space_id = self.space_id
|
277
307
|
|
278
308
|
@property
|
279
309
|
def host(self) -> str | None:
|
@@ -296,10 +326,25 @@ class Storage(SQLRecord, TracksRun, TracksUpdates):
|
|
296
326
|
access_token = self._access_token if hasattr(self, "_access_token") else None
|
297
327
|
return create_path(self.root, access_token=access_token)
|
298
328
|
|
299
|
-
def
|
329
|
+
def save(self, *args, **kwargs):
|
330
|
+
"""Save the storage record."""
|
331
|
+
if hasattr(self, "_old_space") and hasattr(self, "_old_space_id"):
|
332
|
+
if (
|
333
|
+
self._old_space != self.space or self._old_space_id != self.space_id
|
334
|
+
): # space_id is automatically handled by field tracker according to Claude
|
335
|
+
update_storage_with_space(
|
336
|
+
storage_lnid=self.uid, space_lnid=self.space.uid
|
337
|
+
)
|
338
|
+
super().save(*args, **kwargs)
|
339
|
+
return self
|
340
|
+
|
341
|
+
def delete(self) -> None: # type: ignore
|
342
|
+
# type ignore is there because we don't use a trash here unlike everywhere else
|
300
343
|
"""Delete the storage location.
|
301
344
|
|
302
345
|
This errors in case the storage location is not empty.
|
346
|
+
|
347
|
+
Unlike other `SQLRecord`-based registries, this does *not* move the storage record into the trash.
|
303
348
|
"""
|
304
349
|
from .. import settings
|
305
350
|
|
@@ -324,4 +369,4 @@ class Storage(SQLRecord, TracksRun, TracksUpdates):
|
|
324
369
|
ssettings._mark_storage_root.unlink(
|
325
370
|
missing_ok=True # this is totally weird, but needed on Py3.11
|
326
371
|
)
|
327
|
-
super().delete()
|
372
|
+
super(SQLRecord, self).delete()
|
lamindb/models/transform.py
CHANGED
@@ -30,6 +30,22 @@ if TYPE_CHECKING:
|
|
30
30
|
from .ulabel import ULabel
|
31
31
|
|
32
32
|
|
33
|
+
def delete_transform_relations(transform: Transform):
|
34
|
+
from .project import TransformProject
|
35
|
+
|
36
|
+
# query all runs and delete their associated report and env artifacts
|
37
|
+
runs = Run.filter(transform=transform)
|
38
|
+
for run in runs:
|
39
|
+
delete_run_artifacts(run)
|
40
|
+
# CASCADE doesn't do the job below because run_id might be protected through run__transform=self
|
41
|
+
# hence, proactively delete the label links
|
42
|
+
qs = TransformProject.filter(transform=transform)
|
43
|
+
if qs.exists():
|
44
|
+
qs.delete()
|
45
|
+
# at this point, all artifacts have been taken care of
|
46
|
+
# and one can now leverage CASCADE delete
|
47
|
+
|
48
|
+
|
33
49
|
# does not inherit from TracksRun because the Transform
|
34
50
|
# is needed to define a run
|
35
51
|
class Transform(SQLRecord, IsVersioned):
|
@@ -95,6 +111,7 @@ class Transform(SQLRecord, IsVersioned):
|
|
95
111
|
|
96
112
|
class Meta(SQLRecord.Meta, IsVersioned.Meta):
|
97
113
|
abstract = False
|
114
|
+
app_label = "lamindb"
|
98
115
|
unique_together = ("key", "hash")
|
99
116
|
|
100
117
|
_len_stem_uid: int = 12
|
@@ -315,23 +332,6 @@ class Transform(SQLRecord, IsVersioned):
|
|
315
332
|
"""The latest run of this transform."""
|
316
333
|
return self.runs.order_by("-started_at").first()
|
317
334
|
|
318
|
-
def delete(self) -> None:
|
319
|
-
"""Delete."""
|
320
|
-
from .project import TransformProject
|
321
|
-
|
322
|
-
# query all runs and delete their artifacts
|
323
|
-
runs = Run.filter(transform=self)
|
324
|
-
for run in runs:
|
325
|
-
delete_run_artifacts(run)
|
326
|
-
# CASCADE doesn't do the job below because run_id might be protected through run__transform=self
|
327
|
-
# hence, proactively delete the labels
|
328
|
-
qs = TransformProject.filter(transform=self)
|
329
|
-
if qs.exists():
|
330
|
-
qs.delete()
|
331
|
-
# at this point, all artifacts have been taken care of
|
332
|
-
# we can now leverage CASCADE delete
|
333
|
-
super().delete()
|
334
|
-
|
335
335
|
def view_lineage(self, with_successors: bool = False, distance: int = 5):
|
336
336
|
"""View lineage of transforms.
|
337
337
|
|
lamindb/models/ulabel.py
CHANGED
@@ -83,11 +83,12 @@ class ULabel(SQLRecord, HasParents, CanCurate, TracksRun, TracksUpdates):
|
|
83
83
|
|
84
84
|
Query an artifact by ulabel:
|
85
85
|
|
86
|
-
>>> ln.Artifact.filter(ulabels=train_split).
|
86
|
+
>>> ln.Artifact.filter(ulabels=train_split).to_dataframe()
|
87
87
|
"""
|
88
88
|
|
89
89
|
class Meta(SQLRecord.Meta, TracksRun.Meta, TracksUpdates.Meta):
|
90
90
|
abstract = False
|
91
|
+
app_label = "lamindb"
|
91
92
|
|
92
93
|
_name_field: str = "name"
|
93
94
|
|
@@ -221,6 +222,7 @@ class ArtifactULabel(BaseSQLRecord, IsLink, TracksRun):
|
|
221
222
|
class Meta:
|
222
223
|
# can have the same label linked to the same artifact if the feature is
|
223
224
|
# different
|
225
|
+
app_label = "lamindb"
|
224
226
|
unique_together = ("artifact", "ulabel", "feature")
|
225
227
|
|
226
228
|
|
@@ -230,6 +232,7 @@ class TransformULabel(BaseSQLRecord, IsLink, TracksRun):
|
|
230
232
|
ulabel: ULabel = ForeignKey(ULabel, PROTECT, related_name="links_transform")
|
231
233
|
|
232
234
|
class Meta:
|
235
|
+
app_label = "lamindb"
|
233
236
|
unique_together = ("transform", "ulabel")
|
234
237
|
|
235
238
|
|
@@ -247,6 +250,7 @@ class RunULabel(BaseSQLRecord, IsLink):
|
|
247
250
|
"""Creator of record."""
|
248
251
|
|
249
252
|
class Meta:
|
253
|
+
app_label = "lamindb"
|
250
254
|
unique_together = ("run", "ulabel")
|
251
255
|
|
252
256
|
|
@@ -263,4 +267,5 @@ class CollectionULabel(BaseSQLRecord, IsLink, TracksRun):
|
|
263
267
|
feature_ref_is_name: bool | None = BooleanField(null=True)
|
264
268
|
|
265
269
|
class Meta:
|
270
|
+
app_label = "lamindb"
|
266
271
|
unique_together = ("collection", "ulabel")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: lamindb
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.11a1
|
4
4
|
Summary: A data framework for biology.
|
5
5
|
Author-email: Lamin Labs <open-source@lamin.ai>
|
6
6
|
Requires-Python: >=3.10,<3.14
|
@@ -10,8 +10,8 @@ Classifier: Programming Language :: Python :: 3.11
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.12
|
11
11
|
Classifier: Programming Language :: Python :: 3.13
|
12
12
|
Requires-Dist: lamin_utils==0.15.0
|
13
|
-
Requires-Dist: lamin_cli==1.
|
14
|
-
Requires-Dist: lamindb_setup[aws]==1.
|
13
|
+
Requires-Dist: lamin_cli==1.7.0
|
14
|
+
Requires-Dist: lamindb_setup[aws]==1.10.0
|
15
15
|
Requires-Dist: pyyaml
|
16
16
|
Requires-Dist: pyarrow
|
17
17
|
Requires-Dist: pandera>=0.24.0
|