Skip to content

Commit

Permalink
Merge pull request #6301 from PrimozGodec/rank-pyqt6-compat
Browse files Browse the repository at this point in the history
[FIX] Rank - make sorting setting PyQt6 compatible
  • Loading branch information
markotoplak authored Feb 9, 2023
2 parents cfc39dc + db73b53 commit a7d215e
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 9 deletions.
19 changes: 14 additions & 5 deletions Orange/widgets/data/owrank.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
ContextSetting, DomainContextHandler, Setting
)
from Orange.widgets.unsupervised.owdistances import InterruptException
from Orange.widgets.utils import enum2int
from Orange.widgets.utils.concurrent import ConcurrentWidgetMixin, TaskState
from Orange.widgets.utils.sql import check_sql_input
from Orange.widgets.utils.itemmodels import VariableListModel
Expand Down Expand Up @@ -226,10 +227,10 @@ class Outputs:
nSelected = ContextSetting(5)
auto_apply = Setting(True)

sorting = Setting((0, Qt.DescendingOrder))
sorting = Setting((0, enum2int(Qt.DescendingOrder)))
selected_methods = Setting(set())

settings_version = 3
settings_version = 4
settingsHandler = DomainContextHandler()
selected_attrs = ContextSetting([], schema_only=True)
selectionMethod = ContextSetting(SelectNBest)
Expand Down Expand Up @@ -496,9 +497,11 @@ def on_done(self, result: Results) -> None:
sort_column, sort_order = self.sorting
if sort_column < len(labels):
# adds 2 to skip the first two columns
self.ranksModel.sort(sort_column + 2, sort_order)
# Qt.SortOrder is Enum in PyQt6 and int-like object in PyQt5
# in both cases Qt.SortOrder transforms int sort_order to required type
self.ranksModel.sort(sort_column + 2, Qt.SortOrder(sort_order))
self.ranksView.horizontalHeader().setSortIndicator(
sort_column + 2, sort_order
sort_column + 2, Qt.SortOrder(sort_order)
)
except ValueError:
pass
Expand Down Expand Up @@ -561,7 +564,7 @@ def headerClick(self, index):
self.autoSelection()

# Store the header states
sort_order = self.ranksModel.sortOrder()
sort_order = enum2int(self.ranksModel.sortOrder())
sort_column = self.ranksModel.sortColumn() - 2 # -2 for name and '#' columns
self.sorting = (sort_column, sort_order)

Expand Down Expand Up @@ -640,6 +643,12 @@ def migrate_settings(cls, settings, version):
column, order = hview.sortIndicatorSection() - 1, hview.sortIndicatorOrder()
settings["sorting"] = (column, order)

# before we saved sort order as Qt.SortOrder object, now it is integer
# help users with SortOrder as setting migrate to int setting
if "sorting" in settings:
column, order = settings["sorting"]
settings["sorting"] = (column, enum2int(order))

@classmethod
def migrate_context(cls, context, version):
if version is None or version < 3:
Expand Down
39 changes: 37 additions & 2 deletions Orange/widgets/data/tests/test_owrank.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import time
import warnings
import unittest
from enum import Enum
from itertools import count
from unittest.mock import patch

Expand Down Expand Up @@ -374,6 +375,41 @@ def test_scores_sorting(self):
order2 = self.widget.ranksModel.mapToSourceRows(...).tolist()
self.assertNotEqual(order1, order2)

def test_score_sorting_int(self):
"""
Order setting was previously set to Qt.SortOrder which is in PyQt5
int-like PyQt object. Since in PyQt6 it is Enum (non int) object, and it
is not nice to have objects in settings we changed it to int. This test
cover current case and also case with int-like object before.
"""
self.widget.sorting = (1, 1) # Gini col, descending order
self.send_signal(self.widget.Inputs.data, self.iris)
self.wait_until_finished()
order = self.widget.ranksModel.mapToSourceRows(...).tolist()
self.assertListEqual([2, 3, 0, 1], order)

self.widget.sorting = (1, 0) # Gini col, descending order
self.send_signal(self.widget.Inputs.data, self.iris)
self.wait_until_finished()
order = self.widget.ranksModel.mapToSourceRows(...).tolist()
self.assertListEqual([1, 0, 3, 2], order)

# change old setting to int
# since test can run in both pyqt5 or 6 we create SortOrder like object
class SortOrderE(Enum): # pyqt6 like
ASCENDING = 0

settings = {"sorting": (1, SortOrderE.ASCENDING), "__version__": 2}
w = self.create_widget(OWRank, stored_settings=settings)
self.assertEqual(0, w.sorting[1])

class SortOrderI: # pyqt5 like
ASCENDING = 0

settings = {"sorting": (1, SortOrderI.ASCENDING), "__version__": 2}
w = self.create_widget(OWRank, stored_settings=settings)
self.assertEqual(0, w.sorting[1])

def test_scores_nan_sorting(self):
"""Check NaNs are sorted last"""
data = self.iris.copy()
Expand All @@ -383,8 +419,7 @@ def test_scores_nan_sorting(self):
self.wait_until_finished()

# Assert last row is all nan
for order in (Qt.AscendingOrder,
Qt.DescendingOrder):
for order in (Qt.AscendingOrder, Qt.DescendingOrder):
self.widget.ranksView.horizontalHeader().setSortIndicator(2, order)
last_row = self.widget.ranksModel[self.widget.ranksModel.mapToSourceRows(...)[-1]]
np.testing.assert_array_equal(last_row[1:], np.repeat(np.nan, 3))
Expand Down
22 changes: 20 additions & 2 deletions Orange/widgets/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import enum
import inspect
import sys
from collections import deque
from enum import Enum, IntEnum
from typing import (
TypeVar, Callable, Any, Iterable, Optional, Hashable, Type, Union, Tuple
)
Expand Down Expand Up @@ -91,7 +91,7 @@ def qname(type_: type) -> str:


_T1 = TypeVar("_T1") # pylint: disable=invalid-name
_E = TypeVar("_E", bound=enum.Enum) # pylint: disable=invalid-name
_E = TypeVar("_E", bound=Enum) # pylint: disable=invalid-name
_A = TypeVar("_A") # pylint: disable=invalid-name
_B = TypeVar("_B") # pylint: disable=invalid-name

Expand Down Expand Up @@ -176,3 +176,21 @@ def show_part(_point_data, singular, plural, max_shown, _vars):
("Meta", "Metas", 4, domain.metas),
("Feature", "Features", 10, domain.attributes))
return "<br/>".join(show_part(row, *columns) for columns in parts)


def enum2int(enum: Union[Enum, IntEnum]) -> int:
"""
PyQt5 uses IntEnum like object for settings, for example SortOrder while
PyQt6 uses Enum. PyQt5's IntEnum also does not support value attribute.
This function transform both settings objects to int.
Parameters
----------
enum
IntEnum like object or Enum object with Qt's settings
Returns
-------
Settings transformed to int
"""
return int(enum) if isinstance(enum, int) else enum.value

0 comments on commit a7d215e

Please sign in to comment.