lamindb 1.11.3__py3-none-any.whl → 1.12.1__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 -14
- lamindb/_tracked.py +2 -0
- lamindb/base/types.py +1 -3
- lamindb/core/_context.py +16 -31
- lamindb/core/_mapped_collection.py +2 -2
- lamindb/core/storage/paths.py +5 -3
- lamindb/curators/core.py +15 -4
- lamindb/examples/__init__.py +3 -1
- lamindb/examples/croissant/__init__.py +3 -1
- lamindb/examples/mlflow/__init__.py +38 -0
- lamindb/examples/wandb/__init__.py +40 -0
- lamindb/integrations/__init__.py +26 -0
- lamindb/integrations/_lightning.py +87 -0
- lamindb/migrations/0120_add_record_fk_constraint.py +1 -1
- lamindb/migrations/0122_remove_personproject_person_and_more.py +219 -0
- lamindb/migrations/0123_alter_artifact_description_alter_branch_description_and_more.py +82 -0
- lamindb/migrations/0124_page_artifact_page_collection_page_feature_page_and_more.py +15 -0
- lamindb/migrations/0125_artifact_is_locked_collection_is_locked_and_more.py +79 -0
- lamindb/migrations/0126_alter_artifact_is_locked_alter_collection_is_locked_and_more.py +105 -0
- lamindb/migrations/0127_alter_run_status_code_feature_dtype.py +31 -0
- lamindb/migrations/0128_artifact__real_key.py +21 -0
- lamindb/migrations/0129_remove_feature_page_remove_project_page_and_more.py +779 -0
- lamindb/migrations/0130_branch_space_alter_artifactblock_artifact_and_more.py +170 -0
- lamindb/migrations/0131_record_unique_name_type_space.py +18 -0
- lamindb/migrations/0132_record_parents_record_reference_and_more.py +61 -0
- lamindb/migrations/0133_artifactuser_artifact_users.py +108 -0
- lamindb/migrations/{0119_squashed.py → 0133_squashed.py} +1211 -322
- lamindb/models/__init__.py +14 -4
- lamindb/models/_django.py +1 -2
- lamindb/models/_feature_manager.py +1 -0
- lamindb/models/_is_versioned.py +14 -16
- lamindb/models/_relations.py +7 -0
- lamindb/models/artifact.py +99 -56
- lamindb/models/artifact_set.py +20 -3
- lamindb/models/block.py +174 -0
- lamindb/models/can_curate.py +7 -9
- lamindb/models/collection.py +9 -9
- lamindb/models/feature.py +38 -38
- lamindb/models/has_parents.py +15 -6
- lamindb/models/project.py +44 -99
- lamindb/models/query_manager.py +1 -1
- lamindb/models/query_set.py +36 -8
- lamindb/models/record.py +169 -46
- lamindb/models/run.py +44 -10
- lamindb/models/save.py +7 -7
- lamindb/models/schema.py +9 -2
- lamindb/models/sqlrecord.py +87 -35
- lamindb/models/storage.py +13 -3
- lamindb/models/transform.py +7 -2
- lamindb/models/ulabel.py +6 -23
- {lamindb-1.11.3.dist-info → lamindb-1.12.1.dist-info}/METADATA +18 -21
- {lamindb-1.11.3.dist-info → lamindb-1.12.1.dist-info}/RECORD +54 -38
- {lamindb-1.11.3.dist-info → lamindb-1.12.1.dist-info}/LICENSE +0 -0
- {lamindb-1.11.3.dist-info → lamindb-1.12.1.dist-info}/WHEEL +0 -0
lamindb/models/__init__.py
CHANGED
@@ -54,7 +54,7 @@ from .artifact import Artifact, LazyArtifact
|
|
54
54
|
from ._feature_manager import FeatureManager
|
55
55
|
from ._label_manager import LabelManager
|
56
56
|
from .collection import Collection, CollectionArtifact
|
57
|
-
from .project import
|
57
|
+
from .project import Project, Reference
|
58
58
|
from .query_manager import QueryManager
|
59
59
|
from .query_set import BasicQuerySet, QuerySet, SQLRecordList
|
60
60
|
from .artifact_set import ArtifactSet
|
@@ -76,9 +76,8 @@ from .project import (
|
|
76
76
|
CollectionReference,
|
77
77
|
RunProject,
|
78
78
|
RecordProject,
|
79
|
-
PersonProject,
|
80
|
-
RecordPerson,
|
81
79
|
RecordReference,
|
80
|
+
ReferenceRecord,
|
82
81
|
ProjectRecord,
|
83
82
|
)
|
84
83
|
from .run import RunFeatureValue
|
@@ -100,7 +99,18 @@ from .record import (
|
|
100
99
|
RecordArtifact,
|
101
100
|
ArtifactRecord,
|
102
101
|
)
|
103
|
-
|
102
|
+
from .block import (
|
103
|
+
RootBlock,
|
104
|
+
ArtifactBlock,
|
105
|
+
TransformBlock,
|
106
|
+
RecordBlock,
|
107
|
+
CollectionBlock,
|
108
|
+
RunBlock,
|
109
|
+
SchemaBlock,
|
110
|
+
ProjectBlock,
|
111
|
+
BranchBlock,
|
112
|
+
SpaceBlock,
|
113
|
+
)
|
104
114
|
|
105
115
|
LinkORM = IsLink # backward compat
|
106
116
|
ParamValue = FeatureValue # backward compat
|
lamindb/models/_django.py
CHANGED
@@ -98,7 +98,7 @@ def get_artifact_with_related(
|
|
98
98
|
|
99
99
|
# Create the map that the conversion function will need.
|
100
100
|
# It maps the target model class to the m2m field name, e.g.,
|
101
|
-
# {
|
101
|
+
# {'Ulabel': 'ulabels', 'CellType': 'cell_types'}
|
102
102
|
m2m_model_to_field_map = {}
|
103
103
|
if include_m2m:
|
104
104
|
full_map = dict_related_model_to_related_name(
|
@@ -109,7 +109,6 @@ def get_artifact_with_related(
|
|
109
109
|
for model_cls, field_name in full_map.items()
|
110
110
|
if not field_name.startswith("_") and field_name not in EXCLUDE_LABELS
|
111
111
|
}
|
112
|
-
list(m2m_model_to_field_map.values())
|
113
112
|
link_tables = (
|
114
113
|
[]
|
115
114
|
if not include_feature_link
|
lamindb/models/_is_versioned.py
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
from pathlib import PurePosixPath
|
3
4
|
from typing import TYPE_CHECKING, Literal, overload
|
4
5
|
|
5
6
|
from django.db import models
|
6
7
|
from lamin_utils import logger
|
7
8
|
from lamin_utils._base62 import increment_base62
|
8
|
-
from lamindb_setup.core.upath import LocalPathClasses, UPath
|
9
9
|
|
10
10
|
from lamindb.base import ids
|
11
11
|
from lamindb.base.fields import (
|
@@ -88,12 +88,20 @@ class IsVersioned(models.Model):
|
|
88
88
|
"""
|
89
89
|
old_uid = self.uid # type: ignore
|
90
90
|
new_uid, revises = create_uid(revises=revises, version=version)
|
91
|
-
if
|
91
|
+
if (
|
92
|
+
self.__class__.__name__ == "Artifact"
|
93
|
+
and self._real_key is None
|
94
|
+
and (self._key_is_virtual or self.key is None)
|
95
|
+
):
|
96
|
+
from lamindb.core.storage.paths import auto_storage_key_from_artifact_uid
|
97
|
+
|
92
98
|
old_path = self.path
|
93
|
-
|
94
|
-
|
99
|
+
new_storage_key = auto_storage_key_from_artifact_uid(
|
100
|
+
new_uid, self.suffix, self._overwrite_versions
|
101
|
+
)
|
102
|
+
new_path = old_path.rename(
|
103
|
+
old_path.with_name(PurePosixPath(new_storage_key).name)
|
95
104
|
)
|
96
|
-
new_path = UPath(old_path).rename(new_path)
|
97
105
|
logger.success(f"updated path from {old_path} to {new_path}!")
|
98
106
|
self.uid = new_uid
|
99
107
|
self.version = version
|
@@ -193,21 +201,11 @@ def create_uid(
|
|
193
201
|
if revises is not None:
|
194
202
|
if version == revises.version:
|
195
203
|
raise ValueError(
|
196
|
-
f"Please
|
204
|
+
f"Please change the version tag or leave it `None`, '{revises.version}' is already taken"
|
197
205
|
)
|
198
206
|
return suid + vuid, revises
|
199
207
|
|
200
208
|
|
201
|
-
def get_new_path_from_uid(old_path: UPath, old_uid: str, new_uid: str):
|
202
|
-
if isinstance(old_path, LocalPathClasses):
|
203
|
-
# for local path, the rename target must be full path
|
204
|
-
new_path = old_path.as_posix().replace(old_uid, new_uid)
|
205
|
-
else:
|
206
|
-
# for cloud path, the rename target must be the last part of the path
|
207
|
-
new_path = old_path.name.replace(old_uid, new_uid)
|
208
|
-
return new_path
|
209
|
-
|
210
|
-
|
211
209
|
def process_revises(
|
212
210
|
revises: IsVersioned | None,
|
213
211
|
version: str | None,
|
lamindb/models/_relations.py
CHANGED
@@ -83,6 +83,13 @@ def dict_related_model_to_related_name(
|
|
83
83
|
record.name is not None
|
84
84
|
and include(record.related_model)
|
85
85
|
and record.related_model.__get_module_name__() in schema_modules
|
86
|
+
and not (
|
87
|
+
(
|
88
|
+
record.related_name
|
89
|
+
if not isinstance(record, ManyToManyField)
|
90
|
+
else record.name
|
91
|
+
).startswith("linked_in_")
|
92
|
+
)
|
86
93
|
)
|
87
94
|
}
|
88
95
|
return d
|
lamindb/models/artifact.py
CHANGED
@@ -31,6 +31,7 @@ from lamindb.base.fields import (
|
|
31
31
|
BooleanField,
|
32
32
|
CharField,
|
33
33
|
ForeignKey,
|
34
|
+
TextField,
|
34
35
|
)
|
35
36
|
from lamindb.errors import FieldValidationError, NoWriteAccess, UnknownStorageLocation
|
36
37
|
from lamindb.models.query_set import QuerySet
|
@@ -125,6 +126,7 @@ if TYPE_CHECKING:
|
|
125
126
|
ArtifactKind,
|
126
127
|
)
|
127
128
|
from ._label_manager import LabelManager
|
129
|
+
from .block import ArtifactBlock
|
128
130
|
from .collection import Collection
|
129
131
|
from .project import Project, Reference
|
130
132
|
from .record import Record
|
@@ -419,20 +421,15 @@ def get_artifact_kwargs_from_data(
|
|
419
421
|
)
|
420
422
|
|
421
423
|
check_path_in_storage = False
|
424
|
+
real_key = None
|
422
425
|
if use_existing_storage_key:
|
423
426
|
inferred_key = get_relative_path_to_directory(
|
424
427
|
path=path, directory=UPath(storage.root)
|
425
428
|
).as_posix()
|
426
429
|
if key is None:
|
427
430
|
key = inferred_key
|
428
|
-
|
429
|
-
|
430
|
-
raise InvalidArgument(
|
431
|
-
f"The path '{data}' is already in registered storage"
|
432
|
-
f" '{storage.root}' with key '{inferred_key}'\nYou passed"
|
433
|
-
f" conflicting key '{key}': please move the file before"
|
434
|
-
" registering it."
|
435
|
-
)
|
431
|
+
elif key != inferred_key:
|
432
|
+
real_key = inferred_key
|
436
433
|
check_path_in_storage = True
|
437
434
|
else:
|
438
435
|
storage = storage
|
@@ -466,15 +463,16 @@ def get_artifact_kwargs_from_data(
|
|
466
463
|
is_dir=n_files is not None,
|
467
464
|
)
|
468
465
|
|
469
|
-
# do we use a virtual or an actual storage key?
|
470
|
-
key_is_virtual = settings.creation._artifact_use_virtual_keys
|
471
|
-
|
472
|
-
# if the file is already in storage, independent of the default
|
473
|
-
# we use an actual storage key
|
474
|
-
if check_path_in_storage:
|
475
|
-
key_is_virtual = False
|
476
466
|
if overwrite_versions is None:
|
477
467
|
overwrite_versions = n_files is not None
|
468
|
+
|
469
|
+
if check_path_in_storage:
|
470
|
+
# we use an actual storage key if key is not provided explicitly
|
471
|
+
key_is_virtual = real_key is not None
|
472
|
+
else:
|
473
|
+
# do we use a virtual or an actual storage key?
|
474
|
+
key_is_virtual = settings.creation._artifact_use_virtual_keys
|
475
|
+
|
478
476
|
kwargs = {
|
479
477
|
"uid": provisional_uid,
|
480
478
|
"suffix": suffix,
|
@@ -493,6 +491,7 @@ def get_artifact_kwargs_from_data(
|
|
493
491
|
"run": run,
|
494
492
|
"_key_is_virtual": key_is_virtual,
|
495
493
|
"revises": revises,
|
494
|
+
"_real_key": real_key,
|
496
495
|
}
|
497
496
|
if not isinstance(path, LocalPathClasses):
|
498
497
|
local_filepath = None
|
@@ -851,6 +850,8 @@ def get_labels(
|
|
851
850
|
flat_names: bool = False,
|
852
851
|
) -> QuerySet | dict[str, QuerySet] | list:
|
853
852
|
"""{}""" # noqa: D415
|
853
|
+
from .record import Record
|
854
|
+
|
854
855
|
if not isinstance(feature, Feature):
|
855
856
|
raise TypeError("feature has to be of type Feature")
|
856
857
|
if feature.dtype is None or not feature.dtype.startswith("cat["):
|
@@ -865,10 +866,14 @@ def get_labels(
|
|
865
866
|
for registry in registries_to_check:
|
866
867
|
# currently need to distinguish between ULabel and non-ULabel, because
|
867
868
|
# we only have the feature information for Label
|
868
|
-
if registry
|
869
|
+
if registry in {"ULabel", "Record"}:
|
869
870
|
links_to_labels = get_label_links(self, registry, feature)
|
870
|
-
label_ids = [
|
871
|
-
|
871
|
+
label_ids = [
|
872
|
+
(link.ulabel_id if registry == "ULabel" else link.record_id)
|
873
|
+
for link in links_to_labels
|
874
|
+
]
|
875
|
+
model = ULabel if registry == "ULabel" else Record
|
876
|
+
qs_by_registry[registry] = model.objects.using(self._state.db).filter(
|
872
877
|
id__in=label_ids
|
873
878
|
)
|
874
879
|
elif registry in self.features._accessor_by_registry:
|
@@ -932,7 +937,7 @@ def add_labels(
|
|
932
937
|
raise ValueError(
|
933
938
|
"Please pass a record (a `SQLRecord` object), not a string, e.g., via:"
|
934
939
|
" label"
|
935
|
-
f" = ln.
|
940
|
+
f" = ln.Record(name='{records[0]}')" # type: ignore
|
936
941
|
)
|
937
942
|
records = records_validated
|
938
943
|
|
@@ -1224,7 +1229,7 @@ class Artifact(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
1224
1229
|
Storage locations for artifacts.
|
1225
1230
|
:class:`~lamindb.Collection`
|
1226
1231
|
Collections of artifacts.
|
1227
|
-
:meth:`~lamindb.Artifact.
|
1232
|
+
:meth:`~lamindb.Artifact.from_dataframe`
|
1228
1233
|
Create an artifact from a `DataFrame`.
|
1229
1234
|
:meth:`~lamindb.Artifact.from_anndata`
|
1230
1235
|
Create an artifact from an `AnnData`.
|
@@ -1343,7 +1348,8 @@ class Artifact(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
1343
1348
|
editable=False, unique=True, db_index=True, max_length=_len_full_uid
|
1344
1349
|
)
|
1345
1350
|
"""A universal random id."""
|
1346
|
-
|
1351
|
+
# the max length of 1024 equals the max length of a S3 key
|
1352
|
+
key: str | None = CharField(db_index=True, null=True, max_length=1024)
|
1347
1353
|
"""A (virtual) relative file path within the artifact's storage location.
|
1348
1354
|
|
1349
1355
|
Setting a `key` is useful to automatically group artifacts into a version family.
|
@@ -1353,7 +1359,10 @@ class Artifact(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
1353
1359
|
If you register existing files in a storage location, the `key` equals the
|
1354
1360
|
actual filepath on the underyling filesytem or object store.
|
1355
1361
|
"""
|
1356
|
-
|
1362
|
+
_real_key: str | None = CharField(db_index=True, null=True, max_length=1024)
|
1363
|
+
"""An optional real storage key."""
|
1364
|
+
# db_index on description because sometimes we query for equality in the case of artifacts
|
1365
|
+
description: str | None = TextField(null=True, db_index=True)
|
1357
1366
|
"""A description."""
|
1358
1367
|
storage: Storage = ForeignKey(
|
1359
1368
|
Storage, PROTECT, related_name="artifacts", editable=False
|
@@ -1408,10 +1417,6 @@ class Artifact(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
1408
1417
|
max_length=30, db_index=True, null=True, editable=False
|
1409
1418
|
)
|
1410
1419
|
"""Type of hash."""
|
1411
|
-
ulabels: ULabel = models.ManyToManyField(
|
1412
|
-
ULabel, through="ArtifactULabel", related_name="artifacts"
|
1413
|
-
)
|
1414
|
-
"""The ulabels measured in the artifact (:class:`~lamindb.ULabel`)."""
|
1415
1420
|
run: Run | None = ForeignKey(
|
1416
1421
|
Run,
|
1417
1422
|
PROTECT,
|
@@ -1463,15 +1468,28 @@ class Artifact(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
1463
1468
|
)
|
1464
1469
|
"""Creator of record."""
|
1465
1470
|
_overwrite_versions: bool = BooleanField(default=None)
|
1466
|
-
|
1471
|
+
"""See corresponding property `overwrite_versions`."""
|
1472
|
+
ulabels: ULabel = models.ManyToManyField(
|
1473
|
+
ULabel, through="ArtifactULabel", related_name="artifacts"
|
1474
|
+
)
|
1475
|
+
"""The ulabels annotating this artifact."""
|
1476
|
+
users: User = models.ManyToManyField(
|
1477
|
+
User,
|
1478
|
+
through="ArtifactUser",
|
1479
|
+
through_fields=("artifact", "user"),
|
1480
|
+
related_name="+",
|
1481
|
+
)
|
1482
|
+
"""The users annotating this artifact."""
|
1467
1483
|
projects: Project
|
1468
|
-
"""
|
1484
|
+
"""The projects annotating this artifact."""
|
1469
1485
|
references: Reference
|
1470
|
-
"""
|
1486
|
+
"""The references annotating this artifact."""
|
1471
1487
|
records: Record
|
1472
|
-
"""
|
1488
|
+
"""The records annotating this artifact."""
|
1473
1489
|
linked_in_records: Record
|
1474
|
-
"""
|
1490
|
+
"""This artifact is linked in these records as a value."""
|
1491
|
+
blocks: ArtifactBlock
|
1492
|
+
"""The blocks that annotate this artifact."""
|
1475
1493
|
|
1476
1494
|
@overload
|
1477
1495
|
def __init__(
|
@@ -2459,34 +2477,42 @@ class Artifact(SQLRecord, IsVersioned, TracksRun, TracksUpdates):
|
|
2459
2477
|
err_msg += " with " + ("a folder." if _overwrite_versions else "a file.")
|
2460
2478
|
raise ValueError(err_msg)
|
2461
2479
|
|
2462
|
-
|
2463
|
-
|
2464
|
-
|
2465
|
-
|
2466
|
-
if
|
2467
|
-
|
2468
|
-
|
2480
|
+
new_suffix = kwargs["suffix"]
|
2481
|
+
if new_suffix != self.suffix:
|
2482
|
+
key = self.key
|
2483
|
+
real_key = self._real_key
|
2484
|
+
if key is not None:
|
2485
|
+
new_key = PurePosixPath(key).with_suffix(new_suffix).as_posix()
|
2486
|
+
else:
|
2487
|
+
new_key = None
|
2488
|
+
if (key is not None and not self._key_is_virtual) or real_key is not None:
|
2489
|
+
# real_key is not None implies key is not None
|
2490
|
+
assert key is not None # noqa: S101
|
2491
|
+
if real_key is not None:
|
2492
|
+
self._clear_storagekey = real_key
|
2493
|
+
self._real_key = (
|
2494
|
+
PurePosixPath(real_key).with_suffix(new_suffix).as_posix()
|
2495
|
+
)
|
2496
|
+
warn_msg = f", _real_key '{real_key}' with '{self._real_key}'"
|
2497
|
+
else:
|
2498
|
+
self._clear_storagekey = key
|
2499
|
+
warn_msg = ""
|
2500
|
+
self.key = new_key
|
2469
2501
|
# update old key with the new one so that checks in record pass
|
2470
|
-
self._old_key =
|
2502
|
+
self._old_key = new_key
|
2471
2503
|
logger.warning(
|
2472
|
-
f"replacing the file will replace key '{
|
2473
|
-
f" and delete '{
|
2504
|
+
f"replacing the file will replace key '{key}' with '{new_key}'{warn_msg}"
|
2505
|
+
f" and delete '{self._clear_storagekey}' upon `save()`"
|
2474
2506
|
)
|
2475
|
-
|
2476
|
-
|
2477
|
-
|
2478
|
-
|
2479
|
-
self.
|
2480
|
-
|
2481
|
-
|
2482
|
-
|
2483
|
-
|
2484
|
-
new_key_path = PurePosixPath(self.key).with_suffix(kwargs["suffix"])
|
2485
|
-
self.key = str(new_key_path)
|
2486
|
-
# update old key with the new one so that checks in record pass
|
2487
|
-
self._old_key = self.key
|
2488
|
-
|
2489
|
-
self.suffix = kwargs["suffix"]
|
2507
|
+
else:
|
2508
|
+
# purely virtual key case
|
2509
|
+
self._clear_storagekey = auto_storage_key_from_artifact(self)
|
2510
|
+
# might replace None with None, not a big deal
|
2511
|
+
self.key = new_key
|
2512
|
+
# update the old key with the new one so that checks in record pass
|
2513
|
+
self._old_key = new_key
|
2514
|
+
|
2515
|
+
self.suffix = new_suffix
|
2490
2516
|
self.size = kwargs["size"]
|
2491
2517
|
self.hash = kwargs["hash"]
|
2492
2518
|
self._hash_type = kwargs["_hash_type"]
|
@@ -3025,6 +3051,23 @@ class ArtifactFeatureValue(BaseSQLRecord, IsLink, TracksRun):
|
|
3025
3051
|
unique_together = ("artifact", "featurevalue")
|
3026
3052
|
|
3027
3053
|
|
3054
|
+
class ArtifactUser(BaseSQLRecord, IsLink, TracksRun):
|
3055
|
+
id: int = models.BigAutoField(primary_key=True)
|
3056
|
+
artifact: Artifact = ForeignKey("Artifact", CASCADE, related_name="links_user")
|
3057
|
+
user: User = ForeignKey(User, PROTECT, related_name="links_artifact")
|
3058
|
+
feature: Feature | None = ForeignKey(
|
3059
|
+
Feature, PROTECT, null=True, related_name="links_artifactuser", default=None
|
3060
|
+
)
|
3061
|
+
label_ref_is_name: bool | None = BooleanField(null=True)
|
3062
|
+
feature_ref_is_name: bool | None = BooleanField(null=True)
|
3063
|
+
|
3064
|
+
class Meta:
|
3065
|
+
# can have the same label linked to the same artifact if the feature is
|
3066
|
+
# different
|
3067
|
+
app_label = "lamindb"
|
3068
|
+
unique_together = ("artifact", "user", "feature")
|
3069
|
+
|
3070
|
+
|
3028
3071
|
def _track_run_input(
|
3029
3072
|
data: (
|
3030
3073
|
Artifact | Iterable[Artifact]
|
lamindb/models/artifact_set.py
CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
3
3
|
from collections.abc import Iterable, Iterator
|
4
4
|
from typing import TYPE_CHECKING, Literal
|
5
5
|
|
6
|
-
from django.db.models import Q, TextField, Value
|
6
|
+
from django.db.models import Case, Q, TextField, Value, When
|
7
7
|
from django.db.models.functions import Concat
|
8
8
|
from lamin_utils import logger
|
9
9
|
from lamindb_setup.core._docs import doc_args
|
@@ -143,11 +143,13 @@ def artifacts_from_path(artifacts: ArtifactSet, path: UPathStr) -> ArtifactSet:
|
|
143
143
|
if stem_len == 16:
|
144
144
|
qs = artifacts.filter(
|
145
145
|
Q(_key_is_virtual=True) | Q(key__isnull=True),
|
146
|
+
_real_key__isnull=True,
|
146
147
|
uid__startswith=stem,
|
147
148
|
)
|
148
149
|
elif stem_len == 20:
|
149
150
|
qs = artifacts.filter(
|
150
151
|
Q(_key_is_virtual=True) | Q(key__isnull=True),
|
152
|
+
_real_key__isnull=True,
|
151
153
|
uid=stem,
|
152
154
|
)
|
153
155
|
else:
|
@@ -157,10 +159,25 @@ def artifacts_from_path(artifacts: ArtifactSet, path: UPathStr) -> ArtifactSet:
|
|
157
159
|
return qs
|
158
160
|
|
159
161
|
qs = (
|
160
|
-
artifacts.filter(_key_is_virtual=False)
|
162
|
+
artifacts.filter(Q(_key_is_virtual=False) | Q(_real_key__isnull=False))
|
161
163
|
.alias(
|
162
|
-
db_path=
|
164
|
+
db_path=Case(
|
165
|
+
When(
|
166
|
+
_real_key__isnull=False,
|
167
|
+
then=Concat(
|
168
|
+
"storage__root",
|
169
|
+
Value("/"),
|
170
|
+
"_real_key",
|
171
|
+
output_field=TextField(),
|
172
|
+
),
|
173
|
+
),
|
174
|
+
default=Concat(
|
175
|
+
"storage__root", Value("/"), "key", output_field=TextField()
|
176
|
+
),
|
177
|
+
output_field=TextField(),
|
178
|
+
)
|
163
179
|
)
|
164
180
|
.filter(db_path=path_str)
|
165
181
|
)
|
182
|
+
|
166
183
|
return qs
|
lamindb/models/block.py
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
|
3
|
+
from django.db import models
|
4
|
+
from django.db.models import CASCADE, CharField, DateTimeField, ForeignKey, TextField
|
5
|
+
|
6
|
+
from .artifact import Artifact
|
7
|
+
from .collection import Collection
|
8
|
+
from .feature import Feature
|
9
|
+
from .project import Project
|
10
|
+
from .record import Record
|
11
|
+
from .run import Run, User
|
12
|
+
from .schema import Schema
|
13
|
+
from .sqlrecord import BaseSQLRecord, Branch, IsVersioned, Space
|
14
|
+
from .transform import Transform
|
15
|
+
|
16
|
+
|
17
|
+
class BlockMixin(IsVersioned):
|
18
|
+
class Meta:
|
19
|
+
abstract = True
|
20
|
+
|
21
|
+
_len_full_uid: int = 20
|
22
|
+
_len_stem_uid: int = 16
|
23
|
+
|
24
|
+
id = models.BigAutoField(primary_key=True)
|
25
|
+
"""Internal id, valid only in one DB instance."""
|
26
|
+
uid: str = CharField(
|
27
|
+
editable=False, unique=True, db_index=True, max_length=_len_full_uid
|
28
|
+
)
|
29
|
+
"""Universal id."""
|
30
|
+
content: str = TextField()
|
31
|
+
"""Content of the block."""
|
32
|
+
hash: str = CharField(max_length=22, db_index=True, null=True)
|
33
|
+
"""Content hash of the block."""
|
34
|
+
type: str = CharField(
|
35
|
+
max_length=22, db_index=True, default="mdpage", db_default="mdpage"
|
36
|
+
)
|
37
|
+
"""Type of the block.
|
38
|
+
|
39
|
+
Only current option is: "mdpage" (markdown page).
|
40
|
+
"""
|
41
|
+
vertical_pos: float = models.FloatField(default=0, db_default=0, db_index=True)
|
42
|
+
"""The vertical position of the block in the GUI as a float.
|
43
|
+
|
44
|
+
It can be used to act as a percentage to define absolute positioning or for ordering.
|
45
|
+
"""
|
46
|
+
created_at: datetime = DateTimeField(
|
47
|
+
editable=False, db_default=models.functions.Now(), db_index=True
|
48
|
+
)
|
49
|
+
"""Time of creation of record."""
|
50
|
+
created_by: User = ForeignKey(
|
51
|
+
"User", CASCADE, default=None, related_name="+", null=True
|
52
|
+
)
|
53
|
+
"""Creator of block."""
|
54
|
+
|
55
|
+
|
56
|
+
class RootBlock(BlockMixin, BaseSQLRecord):
|
57
|
+
"""A root block for every registry that can appear at the top of the registry root block in the GUI."""
|
58
|
+
|
59
|
+
class Meta:
|
60
|
+
app_label = "lamindb"
|
61
|
+
|
62
|
+
name: str = CharField(max_length=255, db_index=True)
|
63
|
+
"""The entity for which we want to create a block.
|
64
|
+
|
65
|
+
Conventions are mostly to take the SQL table name:
|
66
|
+
name = "instance" means instance
|
67
|
+
name = "lamindb_artifact" means artifact
|
68
|
+
name = "lamindb_transform" means transform
|
69
|
+
name = "bionty_celltype" means bionty cell type
|
70
|
+
"""
|
71
|
+
|
72
|
+
|
73
|
+
class RecordBlock(BlockMixin, BaseSQLRecord):
|
74
|
+
"""An unstructured notes block that can be attached to an artifact."""
|
75
|
+
|
76
|
+
class Meta:
|
77
|
+
app_label = "lamindb"
|
78
|
+
|
79
|
+
record: Record = ForeignKey(Record, CASCADE, related_name="blocks")
|
80
|
+
"""The record to which the block is attached."""
|
81
|
+
|
82
|
+
|
83
|
+
class ArtifactBlock(BlockMixin, BaseSQLRecord):
|
84
|
+
"""An unstructured notes block that can be attached to an artifact."""
|
85
|
+
|
86
|
+
class Meta:
|
87
|
+
app_label = "lamindb"
|
88
|
+
|
89
|
+
artifact: Artifact = ForeignKey(Artifact, CASCADE, related_name="blocks")
|
90
|
+
"""The artifact to which the block is attached."""
|
91
|
+
|
92
|
+
|
93
|
+
class TransformBlock(BlockMixin, BaseSQLRecord):
|
94
|
+
"""An unstructured notes block that can be attached to an artifact."""
|
95
|
+
|
96
|
+
class Meta:
|
97
|
+
app_label = "lamindb"
|
98
|
+
|
99
|
+
transform: Transform = ForeignKey(
|
100
|
+
Transform, CASCADE, related_name="blocks", null=True
|
101
|
+
)
|
102
|
+
"""The transform to which the block is attached."""
|
103
|
+
line_number: int | None = models.IntegerField(null=True)
|
104
|
+
"""The line number in the source code to which the block belongs."""
|
105
|
+
|
106
|
+
|
107
|
+
class RunBlock(BlockMixin, BaseSQLRecord):
|
108
|
+
"""An unstructured notes block that can be attached to an artifact."""
|
109
|
+
|
110
|
+
class Meta:
|
111
|
+
app_label = "lamindb"
|
112
|
+
|
113
|
+
run: Run = ForeignKey(Run, CASCADE, related_name="blocks")
|
114
|
+
"""The run to which the block is attached."""
|
115
|
+
|
116
|
+
|
117
|
+
class CollectionBlock(BlockMixin, BaseSQLRecord):
|
118
|
+
"""An unstructured notes block that can be attached to an artifact."""
|
119
|
+
|
120
|
+
class Meta:
|
121
|
+
app_label = "lamindb"
|
122
|
+
|
123
|
+
collection: Collection = ForeignKey(
|
124
|
+
Collection, CASCADE, related_name="blocks", null=True
|
125
|
+
)
|
126
|
+
"""The collection to which the block is attached."""
|
127
|
+
|
128
|
+
|
129
|
+
class SchemaBlock(BlockMixin, BaseSQLRecord):
|
130
|
+
"""An unstructured notes block that can be attached to an artifact."""
|
131
|
+
|
132
|
+
class Meta:
|
133
|
+
app_label = "lamindb"
|
134
|
+
|
135
|
+
schema: Schema = ForeignKey(Schema, CASCADE, related_name="blocks")
|
136
|
+
"""The schema to which the block is attached."""
|
137
|
+
|
138
|
+
|
139
|
+
class FeatureBlock(BlockMixin, BaseSQLRecord):
|
140
|
+
"""An unstructured notes block that can be attached to an artifact."""
|
141
|
+
|
142
|
+
class Meta:
|
143
|
+
app_label = "lamindb"
|
144
|
+
|
145
|
+
feature: Feature = ForeignKey(Feature, CASCADE, related_name="blocks")
|
146
|
+
"""The feature to which the block is attached."""
|
147
|
+
|
148
|
+
|
149
|
+
class ProjectBlock(BlockMixin, BaseSQLRecord):
|
150
|
+
"""An unstructured notes block that can be attached to an artifact."""
|
151
|
+
|
152
|
+
class Meta:
|
153
|
+
app_label = "lamindb"
|
154
|
+
|
155
|
+
project: Project = ForeignKey(Project, CASCADE, related_name="blocks")
|
156
|
+
|
157
|
+
|
158
|
+
class SpaceBlock(BlockMixin, BaseSQLRecord):
|
159
|
+
"""An unstructured notes block that can be attached to an artifact."""
|
160
|
+
|
161
|
+
class Meta:
|
162
|
+
app_label = "lamindb"
|
163
|
+
|
164
|
+
space: Space = ForeignKey(Space, CASCADE, related_name="blocks")
|
165
|
+
"""The space to which the block is attached."""
|
166
|
+
|
167
|
+
|
168
|
+
class BranchBlock(BlockMixin, BaseSQLRecord):
|
169
|
+
"""An unstructured notes block that can be attached to an artifact."""
|
170
|
+
|
171
|
+
class Meta:
|
172
|
+
app_label = "lamindb"
|
173
|
+
|
174
|
+
branch: Branch = ForeignKey(Branch, CASCADE, related_name="blocks")
|