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.
Files changed (54) hide show
  1. lamindb/__init__.py +8 -14
  2. lamindb/_tracked.py +2 -0
  3. lamindb/base/types.py +1 -3
  4. lamindb/core/_context.py +16 -31
  5. lamindb/core/_mapped_collection.py +2 -2
  6. lamindb/core/storage/paths.py +5 -3
  7. lamindb/curators/core.py +15 -4
  8. lamindb/examples/__init__.py +3 -1
  9. lamindb/examples/croissant/__init__.py +3 -1
  10. lamindb/examples/mlflow/__init__.py +38 -0
  11. lamindb/examples/wandb/__init__.py +40 -0
  12. lamindb/integrations/__init__.py +26 -0
  13. lamindb/integrations/_lightning.py +87 -0
  14. lamindb/migrations/0120_add_record_fk_constraint.py +1 -1
  15. lamindb/migrations/0122_remove_personproject_person_and_more.py +219 -0
  16. lamindb/migrations/0123_alter_artifact_description_alter_branch_description_and_more.py +82 -0
  17. lamindb/migrations/0124_page_artifact_page_collection_page_feature_page_and_more.py +15 -0
  18. lamindb/migrations/0125_artifact_is_locked_collection_is_locked_and_more.py +79 -0
  19. lamindb/migrations/0126_alter_artifact_is_locked_alter_collection_is_locked_and_more.py +105 -0
  20. lamindb/migrations/0127_alter_run_status_code_feature_dtype.py +31 -0
  21. lamindb/migrations/0128_artifact__real_key.py +21 -0
  22. lamindb/migrations/0129_remove_feature_page_remove_project_page_and_more.py +779 -0
  23. lamindb/migrations/0130_branch_space_alter_artifactblock_artifact_and_more.py +170 -0
  24. lamindb/migrations/0131_record_unique_name_type_space.py +18 -0
  25. lamindb/migrations/0132_record_parents_record_reference_and_more.py +61 -0
  26. lamindb/migrations/0133_artifactuser_artifact_users.py +108 -0
  27. lamindb/migrations/{0119_squashed.py → 0133_squashed.py} +1211 -322
  28. lamindb/models/__init__.py +14 -4
  29. lamindb/models/_django.py +1 -2
  30. lamindb/models/_feature_manager.py +1 -0
  31. lamindb/models/_is_versioned.py +14 -16
  32. lamindb/models/_relations.py +7 -0
  33. lamindb/models/artifact.py +99 -56
  34. lamindb/models/artifact_set.py +20 -3
  35. lamindb/models/block.py +174 -0
  36. lamindb/models/can_curate.py +7 -9
  37. lamindb/models/collection.py +9 -9
  38. lamindb/models/feature.py +38 -38
  39. lamindb/models/has_parents.py +15 -6
  40. lamindb/models/project.py +44 -99
  41. lamindb/models/query_manager.py +1 -1
  42. lamindb/models/query_set.py +36 -8
  43. lamindb/models/record.py +169 -46
  44. lamindb/models/run.py +44 -10
  45. lamindb/models/save.py +7 -7
  46. lamindb/models/schema.py +9 -2
  47. lamindb/models/sqlrecord.py +87 -35
  48. lamindb/models/storage.py +13 -3
  49. lamindb/models/transform.py +7 -2
  50. lamindb/models/ulabel.py +6 -23
  51. {lamindb-1.11.3.dist-info → lamindb-1.12.1.dist-info}/METADATA +18 -21
  52. {lamindb-1.11.3.dist-info → lamindb-1.12.1.dist-info}/RECORD +54 -38
  53. {lamindb-1.11.3.dist-info → lamindb-1.12.1.dist-info}/LICENSE +0 -0
  54. {lamindb-1.11.3.dist-info → lamindb-1.12.1.dist-info}/WHEEL +0 -0
@@ -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 Person, Project, Reference
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
- # {<class 'Ulabel'>: 'ulabels', <class 'CellType'>: 'cell_types'}
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
@@ -76,6 +76,7 @@ def get_accessor_by_registry_(host: Artifact | Collection) -> dict:
76
76
  }
77
77
  dictionary["Feature"] = "features"
78
78
  dictionary["ULabel"] = "ulabels"
79
+ dictionary["Record"] = "records"
79
80
  return dictionary
80
81
 
81
82
 
@@ -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 self.__class__.__name__ == "Artifact" and self._key_is_virtual:
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
- new_path = get_new_path_from_uid(
94
- old_path=old_path, old_uid=old_uid, new_uid=new_uid
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 increment the previous version: '{revises.version}'"
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,
@@ -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
@@ -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
- else:
429
- if not key == inferred_key:
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 == "ULabel":
869
+ if registry in {"ULabel", "Record"}:
869
870
  links_to_labels = get_label_links(self, registry, feature)
870
- label_ids = [link.ulabel_id for link in links_to_labels]
871
- qs_by_registry[registry] = ULabel.objects.using(self._state.db).filter(
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.ULabel(name='{records[0]}')" # type: ignore
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.from_df`
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
- key: str | None = CharField(db_index=True, null=True)
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
- description: str | None = CharField(db_index=True, null=True)
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
- # see corresponding property `overwrite_versions`
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
- """Annotating projects."""
1484
+ """The projects annotating this artifact."""
1469
1485
  references: Reference
1470
- """Annotating references."""
1486
+ """The references annotating this artifact."""
1471
1487
  records: Record
1472
- """Annotating records."""
1488
+ """The records annotating this artifact."""
1473
1489
  linked_in_records: Record
1474
- """Linked in records."""
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
- if self.key is not None and not self._key_is_virtual:
2463
- key_path = PurePosixPath(self.key)
2464
- new_filename = f"{key_path.stem}{kwargs['suffix']}"
2465
- # the following will only be true if the suffix changes!
2466
- if key_path.name != new_filename:
2467
- self._clear_storagekey = self.key
2468
- self.key = str(key_path.with_name(new_filename))
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 = self.key
2502
+ self._old_key = new_key
2471
2503
  logger.warning(
2472
- f"replacing the file will replace key '{key_path}' with '{self.key}'"
2473
- f" and delete '{key_path}' upon `save()`"
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
- else:
2476
- old_storage = auto_storage_key_from_artifact(self)
2477
- is_dir = self.n_files is not None
2478
- new_storage = auto_storage_key_from_artifact_uid(
2479
- self.uid, kwargs["suffix"], is_dir
2480
- )
2481
- if old_storage != new_storage:
2482
- self._clear_storagekey = old_storage
2483
- if self.key is not None:
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]
@@ -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=Concat("storage__root", Value("/"), "key", output_field=TextField())
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
@@ -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")