lamindb 0.77.2__py3-none-any.whl → 1.0rc1__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 (89) hide show
  1. lamindb/__init__.py +39 -32
  2. lamindb/_artifact.py +95 -64
  3. lamindb/_can_curate.py +19 -10
  4. lamindb/_collection.py +51 -49
  5. lamindb/_feature.py +9 -9
  6. lamindb/_finish.py +99 -86
  7. lamindb/_from_values.py +20 -17
  8. lamindb/_is_versioned.py +2 -1
  9. lamindb/_parents.py +23 -16
  10. lamindb/_query_manager.py +3 -3
  11. lamindb/_query_set.py +85 -18
  12. lamindb/_record.py +121 -46
  13. lamindb/_run.py +3 -3
  14. lamindb/_save.py +14 -8
  15. lamindb/{_feature_set.py → _schema.py} +34 -31
  16. lamindb/_storage.py +2 -1
  17. lamindb/_transform.py +51 -23
  18. lamindb/_ulabel.py +17 -8
  19. lamindb/_view.py +15 -14
  20. lamindb/base/__init__.py +24 -0
  21. lamindb/base/fields.py +281 -0
  22. lamindb/base/ids.py +103 -0
  23. lamindb/base/types.py +51 -0
  24. lamindb/base/users.py +30 -0
  25. lamindb/base/validation.py +67 -0
  26. lamindb/core/__init__.py +19 -14
  27. lamindb/core/_context.py +297 -228
  28. lamindb/core/_data.py +44 -49
  29. lamindb/core/_describe.py +41 -31
  30. lamindb/core/_django.py +59 -44
  31. lamindb/core/_feature_manager.py +192 -168
  32. lamindb/core/_label_manager.py +22 -22
  33. lamindb/core/_mapped_collection.py +17 -14
  34. lamindb/core/_settings.py +1 -12
  35. lamindb/core/_sync_git.py +56 -9
  36. lamindb/core/_track_environment.py +1 -1
  37. lamindb/core/datasets/_core.py +5 -6
  38. lamindb/core/exceptions.py +0 -7
  39. lamindb/core/fields.py +1 -1
  40. lamindb/core/loaders.py +18 -2
  41. lamindb/core/{schema.py → relations.py} +22 -19
  42. lamindb/core/storage/_anndata_accessor.py +1 -2
  43. lamindb/core/storage/_backed_access.py +2 -1
  44. lamindb/core/storage/_tiledbsoma.py +40 -13
  45. lamindb/core/storage/objects.py +1 -1
  46. lamindb/core/storage/paths.py +13 -8
  47. lamindb/core/subsettings/__init__.py +0 -2
  48. lamindb/core/types.py +2 -23
  49. lamindb/core/versioning.py +11 -7
  50. lamindb/{_curate.py → curators/__init__.py} +700 -57
  51. lamindb/curators/_spatial.py +528 -0
  52. lamindb/integrations/_vitessce.py +1 -3
  53. lamindb/migrations/0052_squashed.py +1261 -0
  54. lamindb/migrations/0053_alter_featureset_hash_alter_paramvalue_created_by_and_more.py +57 -0
  55. lamindb/migrations/0054_alter_feature_previous_runs_and_more.py +35 -0
  56. lamindb/migrations/0055_artifact_type_artifactparamvalue_and_more.py +61 -0
  57. lamindb/migrations/0056_rename_ulabel_ref_is_name_artifactulabel_label_ref_is_name_and_more.py +22 -0
  58. lamindb/migrations/0057_link_models_latest_report_and_others.py +356 -0
  59. lamindb/migrations/0058_artifact__actions_collection__actions.py +22 -0
  60. lamindb/migrations/0059_alter_artifact__accessor_alter_artifact__hash_type_and_more.py +31 -0
  61. lamindb/migrations/0060_alter_artifact__actions.py +22 -0
  62. lamindb/migrations/0061_alter_collection_meta_artifact_alter_run_environment_and_more.py +45 -0
  63. lamindb/migrations/0062_add_is_latest_field.py +32 -0
  64. lamindb/migrations/0063_populate_latest_field.py +45 -0
  65. lamindb/migrations/0064_alter_artifact_version_alter_collection_version_and_more.py +33 -0
  66. lamindb/migrations/0065_remove_collection_feature_sets_and_more.py +22 -0
  67. lamindb/migrations/0066_alter_artifact__feature_values_and_more.py +352 -0
  68. lamindb/migrations/0067_alter_featurevalue_unique_together_and_more.py +20 -0
  69. lamindb/migrations/0068_alter_artifactulabel_unique_together_and_more.py +20 -0
  70. lamindb/migrations/0069_alter_artifact__accessor_alter_artifact__hash_type_and_more.py +1294 -0
  71. lamindb/migrations/0069_squashed.py +1770 -0
  72. lamindb/migrations/0070_lamindbv1_migrate_data.py +78 -0
  73. lamindb/migrations/0071_lamindbv1_migrate_schema.py +741 -0
  74. lamindb/migrations/0072_remove_user__branch_code_remove_user_aux_and_more.py +148 -0
  75. lamindb/migrations/0073_merge_ourprojects.py +945 -0
  76. lamindb/migrations/0074_lamindbv1_part4.py +374 -0
  77. lamindb/migrations/0075_lamindbv1_part5.py +276 -0
  78. lamindb/migrations/0076_lamindbv1_part6.py +621 -0
  79. lamindb/migrations/0077_lamindbv1_part6b.py +228 -0
  80. lamindb/migrations/0078_lamindbv1_part6c.py +468 -0
  81. lamindb/migrations/0079_alter_rundata_value_json_and_more.py +36 -0
  82. lamindb/migrations/__init__.py +0 -0
  83. lamindb/models.py +4064 -0
  84. {lamindb-0.77.2.dist-info → lamindb-1.0rc1.dist-info}/METADATA +15 -20
  85. lamindb-1.0rc1.dist-info/RECORD +100 -0
  86. {lamindb-0.77.2.dist-info → lamindb-1.0rc1.dist-info}/WHEEL +1 -1
  87. lamindb/core/subsettings/_transform_settings.py +0 -21
  88. lamindb-0.77.2.dist-info/RECORD +0 -63
  89. {lamindb-0.77.2.dist-info → lamindb-1.0rc1.dist-info}/LICENSE +0 -0
lamindb/_from_values.py CHANGED
@@ -5,16 +5,16 @@ from typing import TYPE_CHECKING
5
5
  import pandas as pd
6
6
  from django.core.exceptions import FieldDoesNotExist
7
7
  from lamin_utils import colors, logger
8
- from lnschema_core.models import Record
9
8
 
10
9
  from lamindb._query_set import RecordList
10
+ from lamindb.models import Record
11
11
 
12
12
  from .core._settings import settings
13
13
 
14
14
  if TYPE_CHECKING:
15
15
  from collections.abc import Iterable
16
16
 
17
- from lnschema_core.types import ListLike, StrField
17
+ from lamindb.base.types import ListLike, StrField
18
18
 
19
19
 
20
20
  # The base function for `from_values`
@@ -52,12 +52,6 @@ def get_or_create_records(
52
52
  if from_source:
53
53
  if isinstance(source, Record):
54
54
  source_record = source
55
- elif (
56
- len(records) > 0
57
- and hasattr(records[0], "source_id")
58
- and records[0].source_id
59
- ):
60
- source_record = records[0].source
61
55
  if not source_record and hasattr(registry, "public"):
62
56
  if organism is None:
63
57
  organism = _ensembl_prefix(nonexist_values[0], field, organism)
@@ -95,7 +89,7 @@ def get_or_create_records(
95
89
  if len(msg) > 0 and not mute:
96
90
  logger.success(msg)
97
91
  s = "" if len(unmapped_values) == 1 else "s"
98
- print_values = colors.yellow(_print_values(unmapped_values))
92
+ print_values = colors.yellow(_format_values(unmapped_values))
99
93
  name = registry.__name__
100
94
  n_nonval = colors.yellow(f"{len(unmapped_values)} non-validated")
101
95
  if not mute:
@@ -103,7 +97,7 @@ def get_or_create_records(
103
97
  f"{colors.red('did not create')} {name} record{s} for "
104
98
  f"{n_nonval} {colors.italic(f'{field.field.name}{s}')}: {print_values}"
105
99
  )
106
- # if registry.__get_schema_name__() == "bionty" or registry == ULabel:
100
+ # if registry.__get_module_name__() == "bionty" or registry == ULabel:
107
101
  # if isinstance(iterable, pd.Series):
108
102
  # feature = iterable.name
109
103
  # feature_name = None
@@ -167,7 +161,7 @@ def get_existing_records(
167
161
  if not mute:
168
162
  if len(validated) > 0:
169
163
  s = "" if len(validated) == 1 else "s"
170
- print_values = colors.green(_print_values(validated))
164
+ print_values = colors.green(_format_values(validated))
171
165
  msg = (
172
166
  "loaded"
173
167
  f" {colors.green(f'{len(validated)} {model.__name__} record{s}')}"
@@ -176,7 +170,7 @@ def get_existing_records(
176
170
  if len(syn_mapper) > 0:
177
171
  s = "" if len(syn_mapper) == 1 else "s"
178
172
  names = list(syn_mapper.keys())
179
- print_values = colors.green(_print_values(names))
173
+ print_values = colors.green(_format_values(names))
180
174
  syn_msg = (
181
175
  "loaded"
182
176
  f" {colors.green(f'{len(syn_mapper)} {model.__name__} record{s}')}"
@@ -236,14 +230,23 @@ def create_records_from_source(
236
230
  bionty_df = filter_bionty_df_columns(model=model, public_ontology=public_ontology)
237
231
 
238
232
  # standardize in the bionty reference
239
- result = public_ontology.inspect(iterable_idx, field=field.field.name, mute=True)
233
+ # do not inspect synonyms if the field is not name field
234
+ inspect_synonyms = True
235
+ if hasattr(model, "_name_field") and field.field.name != model._name_field:
236
+ inspect_synonyms = False
237
+ result = public_ontology.inspect(
238
+ iterable_idx,
239
+ field=field.field.name,
240
+ mute=True,
241
+ inspect_synonyms=inspect_synonyms,
242
+ )
240
243
  syn_mapper = result.synonyms_mapper
241
244
 
242
245
  msg_syn: str = ""
243
246
  if len(syn_mapper) > 0:
244
247
  s = "" if len(syn_mapper) == 1 else "s"
245
248
  names = list(syn_mapper.keys())
246
- print_values = colors.purple(_print_values(names))
249
+ print_values = colors.purple(_format_values(names))
247
250
  msg_syn = (
248
251
  "created"
249
252
  f" {colors.purple(f'{len(syn_mapper)} {model.__name__} record{s} from Bionty')}"
@@ -277,7 +280,7 @@ def create_records_from_source(
277
280
  validated = result.validated
278
281
  if len(validated) > 0:
279
282
  s = "" if len(validated) == 1 else "s"
280
- print_values = colors.purple(_print_values(validated))
283
+ print_values = colors.purple(_format_values(validated))
281
284
  # this is the success msg for existing records in the DB
282
285
  if len(msg) > 0 and not mute:
283
286
  logger.success(msg)
@@ -307,7 +310,7 @@ def index_iterable(iterable: Iterable) -> pd.Index:
307
310
  return idx[(idx != "") & (~idx.isnull())]
308
311
 
309
312
 
310
- def _print_values(
313
+ def _format_values(
311
314
  names: Iterable, n: int = 20, quotes: bool = True, sep: str = "'"
312
315
  ) -> str:
313
316
  if isinstance(names, dict):
@@ -345,7 +348,7 @@ def _bulk_create_dicts_from_df(
345
348
  dup = df.index[df.index.duplicated()].unique().tolist()
346
349
  if len(dup) > 0:
347
350
  s = "" if len(dup) == 1 else "s"
348
- print_values = _print_values(dup)
351
+ print_values = _format_values(dup)
349
352
  multi_msg = (
350
353
  f"ambiguous validation in Bionty for {len(dup)} record{s}:"
351
354
  f" {print_values}"
lamindb/_is_versioned.py CHANGED
@@ -3,7 +3,8 @@ from __future__ import annotations
3
3
  import lamindb_setup as ln_setup
4
4
  from lamin_utils import logger
5
5
  from lamindb_setup.core.upath import UPath
6
- from lnschema_core.models import IsVersioned
6
+
7
+ from lamindb.models import IsVersioned
7
8
 
8
9
  from ._utils import attach_func_to_class_method
9
10
  from .core.versioning import create_uid, get_new_path_from_uid
lamindb/_parents.py CHANGED
@@ -5,15 +5,22 @@ from typing import TYPE_CHECKING, Literal
5
5
 
6
6
  import lamindb_setup as ln_setup
7
7
  from lamin_utils import logger
8
- from lnschema_core import Artifact, Collection, Record, Run, Transform
9
- from lnschema_core.models import HasParents, format_field_value
8
+
9
+ from lamindb.models import (
10
+ Artifact,
11
+ Collection,
12
+ HasParents,
13
+ Record,
14
+ Run,
15
+ Transform,
16
+ format_field_value,
17
+ )
10
18
 
11
19
  from ._record import get_name_field
12
20
  from ._utils import attach_func_to_class_method
13
21
 
14
22
  if TYPE_CHECKING:
15
- from lnschema_core.types import StrField
16
-
23
+ from lamindb.base.types import StrField
17
24
  from lamindb.core import QuerySet
18
25
 
19
26
  LAMIN_GREEN_LIGHTER = "#10b981"
@@ -25,7 +32,7 @@ TRANSFORM_EMOJIS = {
25
32
  "pipeline": "🧩",
26
33
  "script": "📝",
27
34
  "function": "🔧",
28
- "glue": "🧲",
35
+ "linker": "🧲",
29
36
  }
30
37
  is_run_from_ipython = getattr(builtins, "__IPYTHON__", False)
31
38
 
@@ -342,8 +349,8 @@ def _record_label(record: Record, field: str | None = None):
342
349
  rf' FACE="Monospace">uid={record.uid}<BR/>version={record.version}</FONT>>'
343
350
  )
344
351
  elif isinstance(record, Run):
345
- if record.transform.name:
346
- name = f'{record.transform.name.replace("&", "&amp;")}'
352
+ if record.transform.description:
353
+ name = f'{record.transform.description.replace("&", "&amp;")}'
347
354
  elif record.transform.key:
348
355
  name = f'{record.transform.key.replace("&", "&amp;")}'
349
356
  else:
@@ -395,22 +402,22 @@ def _get_all_parent_runs(data: Artifact | Collection) -> list:
395
402
  inputs_run = (
396
403
  r.__getattribute__(f"input_{name}s")
397
404
  .all()
398
- .filter(visibility__in=[0, 1])
405
+ .filter(_branch_code__in=[0, 1])
399
406
  .list()
400
407
  )
401
408
  if name == "artifact":
402
409
  inputs_run += (
403
- r.input_collections.all().filter(visibility__in=[0, 1]).list()
410
+ r.input_collections.all().filter(_branch_code__in=[0, 1]).list()
404
411
  )
405
412
  outputs_run = (
406
413
  r.__getattribute__(f"output_{name}s")
407
414
  .all()
408
- .filter(visibility__in=[0, 1])
415
+ .filter(_branch_code__in=[0, 1])
409
416
  .list()
410
417
  )
411
418
  if name == "artifact":
412
419
  outputs_run += (
413
- r.output_collections.all().filter(visibility__in=[0, 1]).list()
420
+ r.output_collections.all().filter(_branch_code__in=[0, 1]).list()
414
421
  )
415
422
  # if inputs are outputs artifacts are the same, will result infinite loop
416
423
  # so only show as outputs
@@ -444,7 +451,7 @@ def _get_all_child_runs(data: Artifact | Collection) -> list:
444
451
  {
445
452
  f.run
446
453
  for f in data.run.output_collections.all()
447
- .filter(visibility__in=[0, 1])
454
+ .filter(_branch_code__in=[0, 1])
448
455
  .all()
449
456
  }
450
457
  )
@@ -455,24 +462,24 @@ def _get_all_child_runs(data: Artifact | Collection) -> list:
455
462
  inputs_run = (
456
463
  r.__getattribute__(f"input_{name}s")
457
464
  .all()
458
- .filter(visibility__in=[0, 1])
465
+ .filter(_branch_code__in=[0, 1])
459
466
  .list()
460
467
  )
461
468
  if name == "artifact":
462
469
  inputs_run += (
463
- r.input_collections.all().filter(visibility__in=[0, 1]).list()
470
+ r.input_collections.all().filter(_branch_code__in=[0, 1]).list()
464
471
  )
465
472
  run_inputs_outputs += [(inputs_run, r)]
466
473
 
467
474
  outputs_run = (
468
475
  r.__getattribute__(f"output_{name}s")
469
476
  .all()
470
- .filter(visibility__in=[0, 1])
477
+ .filter(_branch_code__in=[0, 1])
471
478
  .list()
472
479
  )
473
480
  if name == "artifact":
474
481
  outputs_run += (
475
- r.output_collections.all().filter(visibility__in=[0, 1]).list()
482
+ r.output_collections.all().filter(_branch_code__in=[0, 1]).list()
476
483
  )
477
484
  run_inputs_outputs += [(r, outputs_run)]
478
485
 
lamindb/_query_manager.py CHANGED
@@ -5,13 +5,13 @@ from typing import TYPE_CHECKING, NamedTuple
5
5
  from django.db import models
6
6
  from lamin_utils import logger
7
7
  from lamindb_setup.core._docs import doc_args
8
- from lnschema_core.models import Record
9
8
 
10
- from .core._feature_manager import get_feature_set_by_slot_
9
+ from lamindb.models import Record
10
+
11
11
  from .core._settings import settings
12
12
 
13
13
  if TYPE_CHECKING:
14
- from lnschema_core.types import StrField
14
+ from lamindb.base.types import StrField
15
15
 
16
16
 
17
17
  class QueryManager(models.Manager):
lamindb/_query_set.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import re
4
+ import warnings
4
5
  from collections import UserList
5
6
  from collections.abc import Iterable
6
7
  from collections.abc import Iterable as IterableType
@@ -12,17 +13,17 @@ from django.db.models import F, ForeignKey, ManyToManyField
12
13
  from django.db.models.fields.related import ForeignObjectRel
13
14
  from lamin_utils import logger
14
15
  from lamindb_setup.core._docs import doc_args
15
- from lnschema_core.models import (
16
+
17
+ from lamindb.models import (
16
18
  Artifact,
17
19
  CanCurate,
18
20
  Collection,
19
21
  Feature,
20
22
  IsVersioned,
21
23
  Record,
22
- Registry,
23
24
  Run,
25
+ Schema,
24
26
  Transform,
25
- VisibilityChoice,
26
27
  )
27
28
 
28
29
  from .core.exceptions import DoesNotExist
@@ -32,7 +33,7 @@ T = TypeVar("T")
32
33
  if TYPE_CHECKING:
33
34
  from collections.abc import Iterable
34
35
 
35
- from lnschema_core.types import ListLike, StrField
36
+ from lamindb.base.types import ListLike, StrField
36
37
 
37
38
 
38
39
  class MultipleResultsFound(Exception):
@@ -79,6 +80,58 @@ def one_helper(self):
79
80
  return self[0]
80
81
 
81
82
 
83
+ def get_backward_compat_filter_kwargs(queryset, expressions):
84
+ if queryset.model in {Collection, Transform}:
85
+ name_mappings = {
86
+ "name": "key",
87
+ "visibility": "_branch_code", # for convenience (and backward compat <1.0)
88
+ }
89
+ elif queryset.model == Artifact:
90
+ name_mappings = {
91
+ "n_objects": "n_files",
92
+ "visibility": "_branch_code", # for convenience (and backward compat <1.0)
93
+ "transform": "run__transform", # for convenience (and backward compat <1.0)
94
+ "feature_sets": "_schemas_m2m",
95
+ "type": "kind",
96
+ "_accessor": "otype",
97
+ }
98
+ elif queryset.model == Schema:
99
+ name_mappings = {
100
+ "registry": "itype",
101
+ "artifacts": "_artifacts_m2m", # will raise warning when we start to migrate over
102
+ }
103
+ else:
104
+ return expressions
105
+ was_list = False
106
+ if isinstance(expressions, list):
107
+ # make a dummy dictionary
108
+ was_list = True
109
+ expressions = {field: True for field in expressions}
110
+ mapped = {}
111
+ for field, value in expressions.items():
112
+ parts = field.split("__")
113
+ if parts[0] in name_mappings:
114
+ if parts[0] not in {
115
+ "transform",
116
+ "visibility",
117
+ "feature_sets",
118
+ "schemas",
119
+ "artifacts",
120
+ }:
121
+ warnings.warn(
122
+ f"{name_mappings[parts[0]]} is deprecated, please query for {parts[0]} instead",
123
+ DeprecationWarning,
124
+ stacklevel=2,
125
+ )
126
+ new_field = name_mappings[parts[0]] + (
127
+ "__" + "__".join(parts[1:]) if len(parts) > 1 else ""
128
+ )
129
+ mapped[new_field] = value
130
+ else:
131
+ mapped[field] = value
132
+ return list(mapped.keys()) if was_list else mapped
133
+
134
+
82
135
  def process_expressions(queryset: QuerySet, expressions: dict) -> dict:
83
136
  def _map_databases(value: Any, key: str, target_db: str) -> tuple[str, Any]:
84
137
  if isinstance(value, Record):
@@ -105,23 +158,26 @@ def process_expressions(queryset: QuerySet, expressions: dict) -> dict:
105
158
 
106
159
  return key, value
107
160
 
108
- if queryset.model in {Artifact, Collection}:
109
- # visibility is set to 0 unless expressions contains id or uid equality
161
+ expressions = get_backward_compat_filter_kwargs(
162
+ queryset,
163
+ expressions,
164
+ )
165
+
166
+ if issubclass(queryset.model, Record):
167
+ # _branch_code is set to 0 unless expressions contains id or uid
110
168
  if not (
111
169
  "id" in expressions
112
170
  or "uid" in expressions
113
171
  or "uid__startswith" in expressions
114
172
  ):
115
- visibility = "visibility"
116
- if not any(e.startswith(visibility) for e in expressions):
117
- expressions[visibility] = (
118
- VisibilityChoice.default.value
119
- ) # default visibility
120
- # if visibility is None, do not apply a filter
173
+ _branch_code = "_branch_code"
174
+ if not any(e.startswith(_branch_code) for e in expressions):
175
+ expressions[_branch_code] = 1 # default _branch_code
176
+ # if _branch_code is None, do not apply a filter
121
177
  # otherwise, it would mean filtering for NULL values, which doesn't make
122
178
  # sense for a non-NULLABLE column
123
- elif visibility in expressions and expressions[visibility] is None:
124
- expressions.pop(visibility)
179
+ elif _branch_code in expressions and expressions[_branch_code] is None:
180
+ expressions.pop(_branch_code)
125
181
  if queryset._db is not None:
126
182
  # only check for database mismatch if there is a defined database on the
127
183
  # queryset
@@ -213,6 +269,8 @@ def get_basic_field_names(
213
269
  "created_at",
214
270
  "created_by_id",
215
271
  "updated_at",
272
+ "_aux",
273
+ "_branch_code",
216
274
  ]:
217
275
  if field_name in field_names:
218
276
  field_names.remove(field_name)
@@ -242,17 +300,17 @@ def get_feature_annotate_kwargs(show_features: bool | list[str]) -> dict[str, An
242
300
  link_models_on_models = {
243
301
  getattr(
244
302
  Artifact, obj.related_name
245
- ).through.__get_name_with_schema__(): obj.related_model.__get_name_with_schema__()
303
+ ).through.__get_name_with_module__(): obj.related_model.__get_name_with_module__()
246
304
  for obj in Artifact._meta.related_objects
247
- if obj.related_model.__get_name_with_schema__() in cat_feature_types
305
+ if obj.related_model.__get_name_with_module__() in cat_feature_types
248
306
  }
249
307
  link_models_on_models["ArtifactULabel"] = "ULabel"
250
308
  link_attributes_on_models = {
251
309
  obj.related_name: link_models_on_models[
252
- obj.related_model.__get_name_with_schema__()
310
+ obj.related_model.__get_name_with_module__()
253
311
  ]
254
312
  for obj in Artifact._meta.related_objects
255
- if obj.related_model.__get_name_with_schema__() in link_models_on_models
313
+ if obj.related_model.__get_name_with_module__() in link_models_on_models
256
314
  }
257
315
  # Prepare Django's annotate for features
258
316
  annotate_kwargs = {}
@@ -478,7 +536,9 @@ class QuerySet(models.QuerySet):
478
536
  include = []
479
537
  elif isinstance(include, str):
480
538
  include = [include]
539
+ include = get_backward_compat_filter_kwargs(self, include)
481
540
  field_names = get_basic_field_names(self, include, features)
541
+
482
542
  annotate_kwargs = {}
483
543
  if features:
484
544
  annotate_kwargs.update(get_feature_annotate_kwargs(features))
@@ -490,6 +550,7 @@ class QuerySet(models.QuerySet):
490
550
  queryset = self.annotate(**annotate_kwargs)
491
551
  else:
492
552
  queryset = self
553
+
493
554
  df = pd.DataFrame(queryset.values(*field_names, *list(annotate_kwargs.keys())))
494
555
  if len(df) == 0:
495
556
  df = pd.DataFrame({}, columns=field_names)
@@ -500,6 +561,12 @@ class QuerySet(models.QuerySet):
500
561
  pk_column_name = pk_name if pk_name in df.columns else f"{pk_name}_id"
501
562
  if pk_column_name in df_reshaped.columns:
502
563
  df_reshaped = df_reshaped.set_index(pk_column_name)
564
+
565
+ # Compatibility code
566
+ df_reshaped.columns = df_reshaped.columns.str.replace(
567
+ r"_schemas_m2m", "feature_sets", regex=True
568
+ )
569
+
503
570
  return df_reshaped
504
571
 
505
572
  def delete(self, *args, **kwargs):