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.
Files changed (52) hide show
  1. lamindb/__init__.py +89 -49
  2. lamindb/_finish.py +14 -12
  3. lamindb/_tracked.py +2 -4
  4. lamindb/_view.py +1 -1
  5. lamindb/base/__init__.py +2 -1
  6. lamindb/base/dtypes.py +76 -0
  7. lamindb/core/_settings.py +45 -2
  8. lamindb/core/storage/_anndata_accessor.py +118 -26
  9. lamindb/core/storage/_backed_access.py +10 -7
  10. lamindb/core/storage/_spatialdata_accessor.py +15 -4
  11. lamindb/core/storage/_zarr.py +3 -0
  12. lamindb/curators/_legacy.py +16 -3
  13. lamindb/curators/core.py +439 -191
  14. lamindb/examples/cellxgene/__init__.py +8 -3
  15. lamindb/examples/cellxgene/_cellxgene.py +127 -13
  16. lamindb/examples/cellxgene/{cxg_schema_versions.csv → cellxgene_schema_versions.csv} +11 -0
  17. lamindb/examples/croissant/__init__.py +12 -2
  18. lamindb/examples/datasets/__init__.py +2 -2
  19. lamindb/examples/datasets/_core.py +1 -1
  20. lamindb/examples/datasets/_small.py +66 -22
  21. lamindb/examples/datasets/mini_immuno.py +1 -0
  22. lamindb/migrations/0118_alter_recordproject_value_projectrecord.py +99 -0
  23. lamindb/migrations/0119_rename_records_project_linked_in_records.py +26 -0
  24. lamindb/migrations/{0117_squashed.py → 0119_squashed.py} +92 -5
  25. lamindb/migrations/0120_add_record_fk_constraint.py +64 -0
  26. lamindb/migrations/0121_recorduser.py +53 -0
  27. lamindb/models/__init__.py +3 -1
  28. lamindb/models/_describe.py +2 -2
  29. lamindb/models/_feature_manager.py +53 -53
  30. lamindb/models/_from_values.py +2 -2
  31. lamindb/models/_is_versioned.py +4 -4
  32. lamindb/models/_label_manager.py +4 -4
  33. lamindb/models/artifact.py +336 -136
  34. lamindb/models/artifact_set.py +36 -1
  35. lamindb/models/can_curate.py +1 -2
  36. lamindb/models/collection.py +3 -34
  37. lamindb/models/feature.py +111 -7
  38. lamindb/models/has_parents.py +11 -11
  39. lamindb/models/project.py +42 -2
  40. lamindb/models/query_manager.py +16 -7
  41. lamindb/models/query_set.py +59 -34
  42. lamindb/models/record.py +25 -4
  43. lamindb/models/run.py +8 -6
  44. lamindb/models/schema.py +54 -26
  45. lamindb/models/sqlrecord.py +123 -25
  46. lamindb/models/storage.py +59 -14
  47. lamindb/models/transform.py +17 -17
  48. lamindb/models/ulabel.py +6 -1
  49. {lamindb-1.10.1.dist-info → lamindb-1.11a1.dist-info}/METADATA +3 -3
  50. {lamindb-1.10.1.dist-info → lamindb-1.11a1.dist-info}/RECORD +52 -47
  51. {lamindb-1.10.1.dist-info → lamindb-1.11a1.dist-info}/LICENSE +0 -0
  52. {lamindb-1.10.1.dist-info → lamindb-1.11a1.dist-info}/WHEEL +0 -0
@@ -1,4 +1,4 @@
1
- # Generated by Django 5.2 on 2025-07-26 18:58
1
+ # Generated by Django 5.2 on 2025-08-09 13:33
2
2
 
3
3
  import django.core.validators
4
4
  import django.db.models.deletion
@@ -139,6 +139,8 @@ class Migration(migrations.Migration):
139
139
  ("lamindb", "0115_alter_space_uid"),
140
140
  ("lamindb", "0116_remove_artifact_unique_artifact_storage_key_hash_and_more"),
141
141
  ("lamindb", "0117_fix_artifact_storage_hash_unique_constraints"),
142
+ ("lamindb", "0118_alter_recordproject_value_projectrecord"),
143
+ ("lamindb", "0119_rename_records_project_linked_in_records"),
142
144
  ]
143
145
 
144
146
  dependencies = [] # type: ignore
@@ -217,9 +219,8 @@ class Migration(migrations.Migration):
217
219
  "uid",
218
220
  lamindb.base.fields.CharField(
219
221
  blank=True,
220
- db_default="aaaaaaaaaaaa",
221
222
  db_index=True,
222
- default="aaaaaaaaaaaaa",
223
+ default=lamindb.base.uids.base62_12,
223
224
  editable=False,
224
225
  max_length=12,
225
226
  unique=True,
@@ -1522,6 +1523,60 @@ class Migration(migrations.Migration):
1522
1523
  },
1523
1524
  bases=(lamindb.models.can_curate.CanCurate, models.Model),
1524
1525
  ),
1526
+ migrations.CreateModel(
1527
+ name="ProjectRecord",
1528
+ fields=[
1529
+ (
1530
+ "created_at",
1531
+ lamindb.base.fields.DateTimeField(
1532
+ blank=True,
1533
+ db_default=django.db.models.functions.datetime.Now(),
1534
+ db_index=True,
1535
+ editable=False,
1536
+ ),
1537
+ ),
1538
+ ("id", models.BigAutoField(primary_key=True, serialize=False)),
1539
+ (
1540
+ "feature",
1541
+ lamindb.base.fields.ForeignKey(
1542
+ blank=True,
1543
+ default=None,
1544
+ null=True,
1545
+ on_delete=django.db.models.deletion.PROTECT,
1546
+ related_name="links_projectrecord",
1547
+ to="lamindb.feature",
1548
+ ),
1549
+ ),
1550
+ (
1551
+ "project",
1552
+ lamindb.base.fields.ForeignKey(
1553
+ blank=True,
1554
+ on_delete=django.db.models.deletion.PROTECT,
1555
+ related_name="links_record",
1556
+ to="lamindb.project",
1557
+ ),
1558
+ ),
1559
+ (
1560
+ "record",
1561
+ lamindb.base.fields.ForeignKey(
1562
+ blank=True,
1563
+ on_delete=django.db.models.deletion.CASCADE,
1564
+ related_name="links_project",
1565
+ to="lamindb.record",
1566
+ ),
1567
+ ),
1568
+ ],
1569
+ bases=(lamindb.models.sqlrecord.IsLink, models.Model),
1570
+ ),
1571
+ migrations.AddField(
1572
+ model_name="project",
1573
+ name="records",
1574
+ field=models.ManyToManyField(
1575
+ related_name="projects",
1576
+ through="lamindb.ProjectRecord",
1577
+ to="lamindb.record",
1578
+ ),
1579
+ ),
1525
1580
  migrations.AddField(
1526
1581
  model_name="artifactrecord",
1527
1582
  name="record",
@@ -1651,7 +1706,7 @@ class Migration(migrations.Migration):
1651
1706
  lamindb.base.fields.ForeignKey(
1652
1707
  blank=True,
1653
1708
  on_delete=django.db.models.deletion.PROTECT,
1654
- related_name="links_record",
1709
+ related_name="links_in_record",
1655
1710
  to="lamindb.project",
1656
1711
  ),
1657
1712
  ),
@@ -1663,7 +1718,7 @@ class Migration(migrations.Migration):
1663
1718
  ),
1664
1719
  migrations.AddField(
1665
1720
  model_name="project",
1666
- name="records",
1721
+ name="linked_in_records",
1667
1722
  field=models.ManyToManyField(
1668
1723
  related_name="linked_projects",
1669
1724
  through="lamindb.RecordProject",
@@ -2198,6 +2253,18 @@ class Migration(migrations.Migration):
2198
2253
  to="lamindb.run",
2199
2254
  ),
2200
2255
  ),
2256
+ migrations.AddField(
2257
+ model_name="projectrecord",
2258
+ name="run",
2259
+ field=lamindb.base.fields.ForeignKey(
2260
+ blank=True,
2261
+ default=lamindb.models.run.current_run,
2262
+ null=True,
2263
+ on_delete=django.db.models.deletion.PROTECT,
2264
+ related_name="+",
2265
+ to="lamindb.run",
2266
+ ),
2267
+ ),
2201
2268
  migrations.AddField(
2202
2269
  model_name="project",
2203
2270
  name="run",
@@ -4174,6 +4241,18 @@ class Migration(migrations.Migration):
4174
4241
  to="lamindb.user",
4175
4242
  ),
4176
4243
  ),
4244
+ migrations.AddField(
4245
+ model_name="projectrecord",
4246
+ name="created_by",
4247
+ field=lamindb.base.fields.ForeignKey(
4248
+ blank=True,
4249
+ default=lamindb.base.users.current_user_id,
4250
+ editable=False,
4251
+ on_delete=django.db.models.deletion.PROTECT,
4252
+ related_name="+",
4253
+ to="lamindb.user",
4254
+ ),
4255
+ ),
4177
4256
  migrations.AddField(
4178
4257
  model_name="project",
4179
4258
  name="created_by",
@@ -4427,6 +4506,10 @@ class Migration(migrations.Migration):
4427
4506
  name="runfeaturevalue",
4428
4507
  unique_together={("run", "featurevalue")},
4429
4508
  ),
4509
+ migrations.AlterUniqueTogether(
4510
+ name="projectrecord",
4511
+ unique_together={("record", "project", "feature")},
4512
+ ),
4430
4513
  migrations.AlterUniqueTogether(
4431
4514
  name="personproject",
4432
4515
  unique_together={("person", "project")},
@@ -4498,4 +4581,8 @@ class Migration(migrations.Migration):
4498
4581
  name="unique_artifact_storage_hash_null_key",
4499
4582
  ),
4500
4583
  ),
4584
+ migrations.AlterModelOptions(
4585
+ name="user",
4586
+ options={},
4587
+ ),
4501
4588
  ]
@@ -0,0 +1,64 @@
1
+ # Generated by Django 5.2 on 2025-08-07 18:52
2
+
3
+ from django.db import migrations
4
+
5
+ CREATE_FUNCTION_SQL = """
6
+ CREATE OR REPLACE FUNCTION is_valid_record_type(record_type_id INTEGER, record_is_type BOOLEAN)
7
+ RETURNS BOOLEAN AS $$
8
+ BEGIN
9
+ -- Record with no type is valid
10
+ IF record_type_id IS NULL THEN
11
+ RETURN TRUE;
12
+ END IF;
13
+
14
+ -- If current record is a type, it can only reference schema-less types
15
+ IF record_is_type THEN
16
+ RETURN EXISTS (
17
+ SELECT 1 FROM lamindb_record r
18
+ WHERE r.id = record_type_id AND r.is_type AND r.schema_id IS NULL
19
+ );
20
+ END IF;
21
+
22
+ -- Regular records can reference any type
23
+ RETURN EXISTS (
24
+ SELECT 1 FROM lamindb_record r
25
+ WHERE r.id = record_type_id AND r.is_type
26
+ );
27
+ END;
28
+ $$ LANGUAGE plpgsql;
29
+ """
30
+
31
+ ADD_CONSTRAINT_SQL = """
32
+ ALTER TABLE lamindb_record
33
+ ADD CONSTRAINT record_type_is_valid_fk
34
+ CHECK (is_valid_record_type(type_id, is_type));
35
+ """
36
+
37
+ DROP_CONSTRAINT_SQL = (
38
+ "ALTER TABLE lamindb_record DROP CONSTRAINT IF EXISTS record_type_is_valid_fk;"
39
+ )
40
+ DROP_FUNCTION_SQL = "DROP FUNCTION IF EXISTS is_valid_record_type(INTEGER, BOOLEAN);"
41
+
42
+
43
+ def apply_postgres_constraint(apps, schema_editor):
44
+ if schema_editor.connection.vendor == "postgresql":
45
+ schema_editor.execute(CREATE_FUNCTION_SQL)
46
+ schema_editor.execute(ADD_CONSTRAINT_SQL)
47
+
48
+
49
+ def revert_postgres_constraint(apps, schema_editor):
50
+ if schema_editor.connection.vendor == "postgresql":
51
+ schema_editor.execute(DROP_CONSTRAINT_SQL)
52
+ schema_editor.execute(DROP_FUNCTION_SQL)
53
+
54
+
55
+ class Migration(migrations.Migration):
56
+ dependencies = [
57
+ ("lamindb", "0119_squashed"),
58
+ ]
59
+
60
+ operations = [
61
+ migrations.RunPython(
62
+ apply_postgres_constraint, reverse_code=revert_postgres_constraint
63
+ ),
64
+ ]
@@ -0,0 +1,53 @@
1
+ # Generated by Django 5.2 on 2025-09-05 12:25
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+ import lamindb.base.fields
7
+ import lamindb.models.sqlrecord
8
+
9
+
10
+ class Migration(migrations.Migration):
11
+ dependencies = [
12
+ ("lamindb", "0120_add_record_fk_constraint"),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.CreateModel(
17
+ name="RecordUser",
18
+ fields=[
19
+ ("id", models.BigAutoField(primary_key=True, serialize=False)),
20
+ (
21
+ "feature",
22
+ lamindb.base.fields.ForeignKey(
23
+ blank=True,
24
+ on_delete=django.db.models.deletion.PROTECT,
25
+ related_name="links_recorduser",
26
+ to="lamindb.feature",
27
+ ),
28
+ ),
29
+ (
30
+ "record",
31
+ lamindb.base.fields.ForeignKey(
32
+ blank=True,
33
+ on_delete=django.db.models.deletion.CASCADE,
34
+ related_name="values_user",
35
+ to="lamindb.record",
36
+ ),
37
+ ),
38
+ (
39
+ "value",
40
+ lamindb.base.fields.ForeignKey(
41
+ blank=True,
42
+ on_delete=django.db.models.deletion.PROTECT,
43
+ related_name="links_record",
44
+ to="lamindb.user",
45
+ ),
46
+ ),
47
+ ],
48
+ options={
49
+ "unique_together": {("record", "feature", "value")},
50
+ },
51
+ bases=(models.Model, lamindb.models.sqlrecord.IsLink),
52
+ ),
53
+ ]
@@ -9,6 +9,7 @@
9
9
  BasicQuerySet
10
10
  QuerySet
11
11
  ArtifactSet
12
+ LazyArtifact
12
13
  QueryManager
13
14
  SQLRecordList
14
15
  FeatureManager
@@ -49,7 +50,7 @@ from .schema import Schema
49
50
  from .ulabel import ULabel
50
51
 
51
52
  # should come last as it needs everything else
52
- from .artifact import Artifact
53
+ from .artifact import Artifact, LazyArtifact
53
54
  from ._feature_manager import FeatureManager
54
55
  from ._label_manager import LabelManager
55
56
  from .collection import Collection, CollectionArtifact
@@ -78,6 +79,7 @@ from .project import (
78
79
  PersonProject,
79
80
  RecordPerson,
80
81
  RecordReference,
82
+ ProjectRecord,
81
83
  )
82
84
  from .run import RunFeatureValue
83
85
  from .schema import (
@@ -8,8 +8,6 @@ from lamin_utils import logger
8
8
  from rich.text import Text
9
9
  from rich.tree import Tree
10
10
 
11
- from ..core._context import is_run_from_ipython
12
-
13
11
  if TYPE_CHECKING:
14
12
  from lamindb.models import Artifact, Collection, Run
15
13
 
@@ -41,6 +39,8 @@ def format_rich_tree(
41
39
  ) -> str | None:
42
40
  from rich.console import Console
43
41
 
42
+ from ..core._context import is_run_from_ipython
43
+
44
44
  # If tree has no children, return fallback
45
45
  if not tree.children:
46
46
  return fallback
@@ -496,21 +496,11 @@ def describe_features(
496
496
  return tree
497
497
 
498
498
 
499
- def is_valid_datetime_str(date_string: str) -> bool | str:
500
- try:
501
- dt = datetime.fromisoformat(date_string)
502
- return dt.isoformat()
503
- except ValueError:
504
- return False
505
-
506
-
507
- def is_iterable_of_sqlrecord(value: Any):
508
- return isinstance(value, Iterable) and isinstance(next(iter(value)), SQLRecord)
509
-
510
-
511
499
  def infer_feature_type_convert_json(
512
- key: str, value: Any, mute: bool = False, str_as_ulabel: bool = True
500
+ key: str, value: Any, mute: bool = False
513
501
  ) -> tuple[str, Any, str]:
502
+ from lamindb.base.dtypes import is_valid_datetime_str
503
+
514
504
  message = ""
515
505
  if isinstance(value, bool):
516
506
  return "bool", value, message
@@ -719,15 +709,15 @@ def parse_staged_feature_sets_from_anndata(
719
709
  data_parse = backed_access(filepath, using_key=using_key)
720
710
  else:
721
711
  data_parse = ad.read_h5ad(filepath, backed="r")
722
- type = "float"
712
+ dtype = "float"
723
713
  else:
724
- type = "float" if adata.X is None else serialize_pandas_dtype(adata.X.dtype)
714
+ dtype = "float" if adata.X is None else serialize_pandas_dtype(adata.X.dtype)
725
715
  feature_sets = {}
726
716
  if var_field is not None:
727
717
  schema_var = Schema.from_values(
728
718
  data_parse.var.index,
729
719
  var_field,
730
- type=type,
720
+ dtype=dtype,
731
721
  mute=mute,
732
722
  organism=organism,
733
723
  raise_validation_error=False,
@@ -735,7 +725,7 @@ def parse_staged_feature_sets_from_anndata(
735
725
  if schema_var is not None:
736
726
  feature_sets["var"] = schema_var
737
727
  if obs_field is not None and len(data_parse.obs.columns) > 0:
738
- schema_obs = Schema.from_df(
728
+ schema_obs = Schema.from_dataframe(
739
729
  df=data_parse.obs,
740
730
  field=obs_field,
741
731
  mute=mute,
@@ -851,16 +841,17 @@ class FeatureManager:
851
841
  self,
852
842
  values: dict[str, str | int | float | bool],
853
843
  feature_field: FieldAttr = Feature.name,
854
- str_as_ulabel: bool = True,
844
+ schema: Schema = None,
855
845
  ) -> None:
856
846
  """Curate artifact with features & values.
857
847
 
858
848
  Args:
859
849
  values: A dictionary of keys (features) & values (labels, numbers, booleans).
860
- feature_field: The field of a reference registry to map keys of the
861
- dictionary.
862
- str_as_ulabel: Whether to interpret string values as ulabels.
850
+ feature_field: The field of a reference registry to map keys of the dictionary.
851
+ schema: Schema to validate against.
863
852
  """
853
+ from lamindb.base.dtypes import is_iterable_of_sqlrecord
854
+
864
855
  from .._tracked import get_current_tracked_run
865
856
 
866
857
  # rename to distinguish from the values inside the dict
@@ -870,39 +861,48 @@ class FeatureManager:
870
861
  keys = list(keys) # type: ignore
871
862
  # deal with other cases later
872
863
  assert all(isinstance(key, str) for key in keys) # noqa: S101
864
+
873
865
  registry = feature_field.field.model
874
866
  value_model = FeatureValue
875
867
  model_name = "Feature"
876
- records = registry.from_values(keys, field=feature_field, mute=True)
877
- if len(records) != len(keys):
878
- not_validated_keys = [
879
- key for key in keys if key not in records.list("name")
880
- ]
881
- not_validated_keys_dtype_message = [
882
- (key, infer_feature_type_convert_json(key, dictionary[key]))
883
- for key in not_validated_keys
884
- ]
885
- run = get_current_tracked_run()
886
- if run is not None:
887
- name = f"{run.transform.type}[{run.transform.key}]"
888
- type_hint = f""" {model_name.lower()}_type = ln.{model_name}(name='{name}', is_type=True).save()"""
889
- elements = [type_hint]
890
- type_kwarg = f", type={model_name.lower()}_type"
891
- else:
892
- elements = []
893
- type_kwarg = ""
894
- elements += [
895
- f" ln.{model_name}(name='{key}', dtype='{dtype}'{type_kwarg}).save(){message}"
896
- for key, (dtype, _, message) in not_validated_keys_dtype_message
897
- ]
898
- hint = "\n".join(elements)
899
- msg = (
900
- f"These keys could not be validated: {not_validated_keys}\n"
901
- f"Here is how to create a {model_name.lower()}:\n\n{hint}"
902
- )
903
- raise ValidationError(msg)
904
868
 
905
- # figure out which of the values go where
869
+ if schema is not None:
870
+ from lamindb.curators import DataFrameCurator
871
+
872
+ temp_df = pd.DataFrame([values])
873
+ curator = DataFrameCurator(temp_df, schema)
874
+ curator.validate()
875
+ records = schema.members.filter(name__in=keys)
876
+ else:
877
+ records = registry.from_values(keys, field=feature_field, mute=True)
878
+ if len(records) != len(keys):
879
+ not_validated_keys = [
880
+ key for key in keys if key not in records.to_list("name")
881
+ ]
882
+ not_validated_keys_dtype_message = [
883
+ (key, infer_feature_type_convert_json(key, dictionary[key]))
884
+ for key in not_validated_keys
885
+ ]
886
+ run = get_current_tracked_run()
887
+ if run is not None:
888
+ name = f"{run.transform.type}[{run.transform.key}]"
889
+ type_hint = f""" {model_name.lower()}_type = ln.{model_name}(name='{name}', is_type=True).save()"""
890
+ elements = [type_hint]
891
+ type_kwarg = f", type={model_name.lower()}_type"
892
+ else:
893
+ elements = []
894
+ type_kwarg = ""
895
+ elements += [
896
+ f" ln.{model_name}(name='{key}', dtype='{dtype}'{type_kwarg}).save(){message}"
897
+ for key, (dtype, _, message) in not_validated_keys_dtype_message
898
+ ]
899
+ hint = "\n".join(elements)
900
+ msg = (
901
+ f"These keys could not be validated: {not_validated_keys}\n"
902
+ f"Here is how to create a {model_name.lower()}:\n\n{hint}"
903
+ )
904
+ raise ValidationError(msg)
905
+
906
906
  features_labels = defaultdict(list)
907
907
  _feature_values = []
908
908
  not_validated_values: dict[str, list[str]] = defaultdict(list)
@@ -912,7 +912,6 @@ class FeatureManager:
912
912
  feature.name,
913
913
  value,
914
914
  mute=True,
915
- str_as_ulabel=str_as_ulabel,
916
915
  )
917
916
  if feature.dtype == "num":
918
917
  if inferred_type not in {"int", "float"}:
@@ -994,6 +993,7 @@ class FeatureManager:
994
993
  f"Here is how to create records for them:\n\n{hint}"
995
994
  )
996
995
  raise ValidationError(msg)
996
+
997
997
  if features_labels:
998
998
  self._add_label_feature_links(features_labels)
999
999
  if _feature_values:
@@ -1039,7 +1039,7 @@ class FeatureManager:
1039
1039
  feature: str | Feature,
1040
1040
  *,
1041
1041
  value: Any | None = None,
1042
- ):
1042
+ ) -> None:
1043
1043
  """Remove value annotations for a given feature.
1044
1044
 
1045
1045
  Args:
@@ -1262,7 +1262,7 @@ class FeatureManager:
1262
1262
  """Add feature set corresponding to column names of DataFrame."""
1263
1263
  assert self._host.otype == "DataFrame" # noqa: S101
1264
1264
  df = self._host.load(is_run_input=False)
1265
- schema = Schema.from_df(
1265
+ schema = Schema.from_dataframe(
1266
1266
  df=df,
1267
1267
  field=field,
1268
1268
  mute=mute,
@@ -121,7 +121,7 @@ def get_existing_records(
121
121
  # ]
122
122
  # )
123
123
  # order by causes a factor 10 in runtime
124
- # records = query_set.order_by(preserved).list()
124
+ # records = query_set.order_by(preserved).to_list()
125
125
 
126
126
  # log validated terms
127
127
  is_validated = model.validate(
@@ -165,7 +165,7 @@ def get_existing_records(
165
165
  query = {f"{field.field.name}__in": iterable_idx.values} # type: ignore
166
166
  if organism is not None:
167
167
  query["organism"] = organism
168
- records = model.filter(**query).list()
168
+ records = model.filter(**query).to_list()
169
169
 
170
170
  if len(validated) == len(iterable_idx):
171
171
  return records, pd.Index([]), msg
@@ -108,12 +108,12 @@ def bump_version(
108
108
  ) -> str:
109
109
  """Bumps the version number by major or minor depending on the bump_type flag.
110
110
 
111
- Parameters:
112
- version (str): The current version in "MAJOR" or "MAJOR.MINOR" format.
113
- bump_type (str): The type of version bump, either 'major' or 'minor'.
111
+ Args:
112
+ version: The current version in "MAJOR" or "MAJOR.MINOR" format.
113
+ bump_type: The type of version bump, either 'major' or 'minor'.
114
114
 
115
115
  Returns:
116
- str: The new version string.
116
+ The new version string.
117
117
  """
118
118
  try:
119
119
  # Split the version into major and minor parts if possible
@@ -268,7 +268,7 @@ class LabelManager:
268
268
  for link in links:
269
269
  if link.feature is not None:
270
270
  features.add(link.feature)
271
- key = link.feature.name
271
+ key = link.feature.uid
272
272
  else:
273
273
  key = None
274
274
  keys.append(key)
@@ -299,9 +299,9 @@ class LabelManager:
299
299
  )
300
300
  save(new_features) # type: ignore
301
301
  if hasattr(self._host, related_name):
302
- for feature_name, feature_labels in labels_by_features.items():
303
- if feature_name is not None:
304
- feature_id = Feature.get(name=feature_name).id
302
+ for feature_uid, feature_labels in labels_by_features.items():
303
+ if feature_uid is not None:
304
+ feature_id = Feature.get(feature_uid).id
305
305
  else:
306
306
  feature_id = None
307
307
  getattr(self._host, related_name).add(