Skip to content

Commit 65d3a22

Browse files
committed
Preserve declared base class order for DataItem collection in multiple inheritance
collect_items_in_bases_order now ensures DataItems are collected according to the order bases are listed in class declarations, matching developer expectations in multiple inheritance scenarios. Fix #92
1 parent c52ff53 commit 65d3a22

File tree

3 files changed

+77
-13
lines changed

3 files changed

+77
-13
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog #
22

3+
## Version 3.12.1 ##
4+
5+
🛠️ Bug fixes:
6+
7+
* [Issue #92](https://github.com/PlotPyStack/guidata/issues/92) - Changing base class order in DataSet inheritance does not affect item order as expected
8+
* When using multiple inheritance with DataSet classes, changing the order of the base classes in the child class definition did not affect the order of the items in the resulting dataset.
9+
* One would expect the items to appear in the order in which the base classes are listed, and thanks to this fix, this is now the case.
10+
311
## Version 3.12.0 ##
412

513
💥 New features:

guidata/dataset/datatypes.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,31 @@ def label(self) -> str:
10181018
return self.item.get_prop("display", "label")
10191019

10201020

1021+
def collect_items_in_bases_order(
1022+
bases: tuple[type], seen: set | None = None
1023+
) -> list[DataItem]:
1024+
"""Collect items in bases order
1025+
1026+
Args:
1027+
bases: tuple of base classes
1028+
seen: set of already seen items to avoid duplicates
1029+
1030+
Returns:
1031+
List of data items
1032+
"""
1033+
if seen is None:
1034+
seen = set()
1035+
items = []
1036+
for base in bases:
1037+
items.extend(collect_items_in_bases_order(base.__bases__, seen))
1038+
base_dict = getattr(base, "__dict__", {})
1039+
for name, obj in base_dict.items():
1040+
if isinstance(obj, DataItem) and name not in seen:
1041+
items.append(obj)
1042+
seen.add(name)
1043+
return items
1044+
1045+
10211046
class DataSetMeta(type):
10221047
"""
10231048
DataSet metaclass
@@ -1027,12 +1052,7 @@ class DataSetMeta(type):
10271052
"""
10281053

10291054
def __new__(cls: type, name: str, bases: Any, dct: dict[str, Any]) -> type:
1030-
items = {}
1031-
for base in bases:
1032-
if getattr(base, "__metaclass__", None) is DataSetMeta:
1033-
for item in base._items:
1034-
items[item._name] = item
1035-
1055+
items = {item._name: item for item in collect_items_in_bases_order(bases)}
10361056
for attrname, value in list(dct.items()):
10371057
if isinstance(value, DataItem):
10381058
value.set_name(attrname)

guidata/tests/dataset/test_inheritance.py

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,15 @@
1717
from guidata.qthelpers import qt_app_context
1818

1919

20-
class OriginalDataset(gds.DataSet):
20+
class OriginalDataset1(gds.DataSet):
2121
"""Original dataset
2222
This is the original dataset"""
2323

24-
bool = gds.BoolItem("Boolean")
25-
string = gds.StringItem("String")
26-
text = gds.TextItem("Text")
27-
float = gds.FloatItem("Float", default=0.5, min=0, max=1, step=0.01, slider=True)
24+
text1 = gds.TextItem("Text 1")
25+
int1 = gds.IntItem("Integer 1", default=111)
2826

2927

30-
class DerivedDataset(OriginalDataset):
28+
class DerivedDataset(OriginalDataset1):
3129
"""Derived dataset
3230
This is the derived dataset"""
3331

@@ -40,7 +38,7 @@ class DerivedDataset(OriginalDataset):
4038
def test_inheritance():
4139
"""Test DataSet inheritance"""
4240
with qt_app_context():
43-
e = OriginalDataset()
41+
e = OriginalDataset1()
4442
e.edit()
4543
execenv.print(e)
4644

@@ -50,5 +48,43 @@ def test_inheritance():
5048
execenv.print("OK")
5149

5250

51+
class OriginalDataset2(gds.DataSet):
52+
"""Original dataset 2
53+
This is another original dataset"""
54+
55+
text2 = gds.TextItem("Text 2")
56+
int2 = gds.IntItem("Integer 2", default=222)
57+
58+
59+
class DoubleInheritedDataset1(OriginalDataset1, OriginalDataset2):
60+
"""Double inherited dataset
61+
This is a dataset that inherits from two original datasets"""
62+
63+
text3 = gds.TextItem("Text 3")
64+
int3 = gds.IntItem("Integer 3", default=333)
65+
66+
67+
class DoubleInheritedDataset2(OriginalDataset2, OriginalDataset1):
68+
"""Double inherited dataset 2
69+
This is a dataset that inherits from two original datasets in reverse order"""
70+
71+
text4 = gds.TextItem("Text 4")
72+
int4 = gds.IntItem("Integer 4", default=444)
73+
74+
75+
def test_double_inheritance():
76+
"""Test DataSet double inheritance"""
77+
with qt_app_context():
78+
e = DoubleInheritedDataset1()
79+
e.edit()
80+
execenv.print(e)
81+
82+
e = DoubleInheritedDataset2()
83+
e.edit()
84+
execenv.print(e)
85+
execenv.print("OK")
86+
87+
5388
if __name__ == "__main__":
89+
test_double_inheritance()
5490
test_inheritance()

0 commit comments

Comments
 (0)