Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typed settings #4721

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 56 additions & 5 deletions Orange/widgets/data/owcolor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
from itertools import chain
import json
from typing import List

import numpy as np

Expand All @@ -10,7 +11,7 @@
from AnyQt.QtWidgets import QHeaderView, QColorDialog, QTableView, QComboBox, \
QFileDialog, QMessageBox

from orangewidget.settings import IncompatibleContext
from orangewidget.settings import IncompatibleContext, TypeSupport

import Orange
from Orange.preprocess.transformation import Identity
Expand Down Expand Up @@ -232,6 +233,48 @@ def from_dict(cls, var, data):
return obj, warnings


class AttrDescTypeSupport(TypeSupport):
@classmethod
def pack_value(cls, value, _=None):
packed = {k: list(tuple(x) for x in v) if k == "new_colors" else v
for k, v in value.__dict__.items()
if k.startswith("new_") and v is not None}
packed["var"] = value.var.name
return packed

@classmethod
def unpack_value(cls, value, _, domain, *_a):
var = domain[value["var"]]
desc = cls.supported_types[0](var)
if "new_name" in value:
desc.name = value["new_name"]
return desc


class DiscAttrDescTypeSupport(AttrDescTypeSupport):
supported_types = (DiscAttrDesc, )

@classmethod
def unpack_value(cls, value, tp, domain, *_):
desc = super().unpack_value(value, tp, domain)
for i, color in enumerate(value.get("new_colors", ())):
desc.set_color(i, color)
for i, color in enumerate(value.get("new_values", ())):
desc.set_value(i, color)
return desc


class ContAttrDescTypeSupport(AttrDescTypeSupport):
supported_types = (ContAttrDesc, )

@classmethod
def unpack_value(cls, value, tp, domain, *_):
desc = super().unpack_value(value, tp, domain)
if "new_palette_name" in value:
desc.palette_name = value["new_palette_name"]
return desc


class ColorTableModel(QAbstractTableModel):
"""
Base color model for discrete and continuous variables. The model handles:
Expand Down Expand Up @@ -547,12 +590,12 @@ class Outputs:

settingsHandler = settings.PerfectDomainContextHandler(
match_values=settings.PerfectDomainContextHandler.MATCH_VALUES_ALL)
disc_descs = settings.ContextSetting([])
cont_descs = settings.ContextSetting([])
disc_descs: List[DiscAttrDesc] = settings.ContextSetting([])
cont_descs: List[ContAttrDesc] = settings.ContextSetting([])
selected_schema_index = settings.Setting(0)
auto_apply = settings.Setting(True)

settings_version = 2
settings_version = 3

want_main_area = False

Expand Down Expand Up @@ -801,9 +844,17 @@ def was(n, o):
self.report_raw(f"<table>{table}</table>")

@classmethod
def migrate_context(cls, _, version):
def migrate_context(cls, context, version):
if not version or version < 2:
raise IncompatibleContext
if version < 3:
values = context.values
values["disc_descs"] = [
DiscAttrDescTypeSupport.pack_value(desc)
for desc in values["disc_descs"]]
values["cont_descs"] = [
ContAttrDescTypeSupport.pack_value(desc)
for desc in values["cont_descs"]]


if __name__ == "__main__": # pragma: no cover
Expand Down
5 changes: 3 additions & 2 deletions Orange/widgets/data/owcorrelations.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from operator import attrgetter
from types import SimpleNamespace
from itertools import combinations, groupby, chain
from typing import List

import numpy as np
from scipy.stats import spearmanr, pearsonr
Expand Down Expand Up @@ -254,8 +255,8 @@ class Outputs:

settings_version = 3
settingsHandler = DomainContextHandler()
selection = ContextSetting([])
feature = ContextSetting(None)
selection: List[ContinuousVariable] = ContextSetting([])
feature: ContinuousVariable = ContextSetting(None)
correlation_type = Setting(0)

class Information(OWWidget.Information):
Expand Down
7 changes: 4 additions & 3 deletions Orange/widgets/data/owcreateclass.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
"""Widget for creating classes from non-numeric attribute by substrings"""
import re
from itertools import count
from typing import List, Dict

import numpy as np

from AnyQt.QtWidgets import QGridLayout, QLabel, QLineEdit, QSizePolicy, QWidget
from AnyQt.QtCore import QSize, Qt

from Orange.data import StringVariable, DiscreteVariable, Domain
from Orange.data import StringVariable, DiscreteVariable, Domain, Variable
from Orange.data.table import Table
from Orange.statistics.util import bincount
from Orange.preprocess.transformation import Transformation, Lookup
Expand Down Expand Up @@ -176,9 +177,9 @@ class Outputs:
buttons_area_orientation = Qt.Vertical

settingsHandler = DomainContextHandler()
attribute = ContextSetting(None)
attribute: Variable = ContextSetting(None)
class_name = ContextSetting("class")
rules = ContextSetting({})
rules: Dict[str, List[List[str]]] = ContextSetting({})
match_beginning = ContextSetting(False)
case_sensitive = ContextSetting(False)

Expand Down
2 changes: 1 addition & 1 deletion Orange/widgets/data/owdatasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ class Outputs:
data = Output("Data", Orange.data.Table)

#: Selected dataset id
selected_id = settings.Setting(None) # type: Optional[str]
selected_id: Optional[str] = settings.Setting(None)

#: main area splitter state
splitter_state = settings.Setting(b'') # type: bytes
Expand Down
13 changes: 7 additions & 6 deletions Orange/widgets/data/owfeaturestatistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -716,11 +716,11 @@ class Outputs:
settings_version = 2

auto_commit = Setting(True)
color_var = ContextSetting(None) # type: Optional[Variable]
color_var: Optional[Variable] = ContextSetting(None)
# filter_string = ContextSetting('')

sorting = Setting((0, Qt.DescendingOrder))
selected_vars = ContextSetting([], schema_only=True)
sorting: Tuple[int, int] = Setting((0, Qt.DescendingOrder))
selected_vars: List[Variable] = ContextSetting([], schema_only=True)

def __init__(self):
super().__init__()
Expand Down Expand Up @@ -859,8 +859,9 @@ def commit(self):

# Send a table with only selected columns to output
variables = self.selected_vars
self.info.set_output_summary(len(self.data[:, variables]),
format_summary_details(self.data[:, variables]))
reduced_data = self.data[:, variables]
self.info.set_output_summary(len(reduced_data),
format_summary_details(reduced_data))
self.Outputs.reduced_data.send(self.data[:, variables])

# Send the statistics of the selected variables to ouput
Expand Down Expand Up @@ -895,7 +896,7 @@ def migrate_context(cls, context, version):
# is no suitable conversion function, and StringVariable (3)
# was the only hidden var when settings_version < 2, so:
if tpe != 3]
selected_vars = [all_vars[i] for i in selected_rows]
selected_vars = [all_vars[i] for i in selected_rows[0]]
context.values["selected_vars"] = selected_vars, -3


Expand Down
35 changes: 15 additions & 20 deletions Orange/widgets/data/owfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging
from itertools import chain
from urllib.parse import urlparse
from typing import List
from typing import List, Dict

import numpy as np
from AnyQt.QtWidgets import \
Expand All @@ -14,22 +14,18 @@
from Orange.data.io import FileFormat, UrlReader, class_from_qualified_name
from Orange.util import log_warnings
from Orange.widgets import widget, gui
from Orange.widgets.settings import Setting, ContextSetting, \
PerfectDomainContextHandler, SettingProvider
from Orange.widgets.settings import \
Setting, PerfectDomainContextHandler, SettingProvider
from Orange.widgets.utils import vartype
from Orange.widgets.utils.domaineditor import DomainEditor
from Orange.widgets.utils.itemmodels import PyListModel
from Orange.widgets.utils.filedialogs import RecentPathsWComboMixin, \
open_filename_dialog
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.utils.state_summary import format_summary_details
from Orange.widgets.widget import Output, Msg

# Backward compatibility: class RecentPath used to be defined in this module,
# and it is used in saved (pickled) settings. It must be imported into the
# module's namespace so that old saved settings still work
from Orange.widgets.utils.filedialogs import RecentPath


log = logging.getLogger(__name__)


Expand Down Expand Up @@ -95,10 +91,11 @@ class Outputs:
LOCAL_FILE, URL = range(2)

settingsHandler = PerfectDomainContextHandler(
match_values=PerfectDomainContextHandler.MATCH_VALUES_ALL
)
match_values=PerfectDomainContextHandler.MATCH_VALUES_ALL)

# pylint seems to want declarations separated from definitions
# This must be in the widget, not the mixin - otherwise all widgets will
# share the same paths
recent_paths: List[RecentPath]
recent_urls: List[str]
variables: list
Expand All @@ -114,11 +111,9 @@ class Outputs:
])
recent_urls = Setting([])
source = Setting(LOCAL_FILE)
sheet_names = Setting({})
sheet_names: Dict[str, str] = Setting({})
url = Setting("")

variables = ContextSetting([])

domain_editor = SettingProvider(DomainEditor)

class Warning(widget.OWWidget.Warning):
Expand Down Expand Up @@ -471,13 +466,6 @@ def missing_prop(prop):
f"Last entry: {table[-1, 'Timestamp']}</p>"
return text

def storeSpecificSettings(self):
self.current_context.modified_variables = self.variables[:]

def retrieveSpecificSettings(self):
if hasattr(self.current_context, "modified_variables"):
self.variables[:] = self.current_context.modified_variables

def reset_domain_edit(self):
self.domain_editor.reset_domain()
self.apply_domain_edit()
Expand Down Expand Up @@ -578,6 +566,13 @@ def workflowEnvChanged(self, key, value, oldvalue):
"""
self.update_file_list(key, value, oldvalue)

@classmethod
def migrate_context(cls, context, _):
if hasattr(context, "modified_variables"):
delattr(context, "modified_variables")
de_vars = context.values["domain_editor"]["variables"]
de_vars[:] = [(x[0], vartype(x[1]), *x[2:]) for x in de_vars]


if __name__ == "__main__": # pragma: no cover
WidgetPreview(OWFile).run()
13 changes: 12 additions & 1 deletion Orange/widgets/data/owmergedata.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from collections import namedtuple
from itertools import chain, product
from typing import List, Tuple, Dict

import numpy as np

from AnyQt.QtCore import Qt, QModelIndex, pyqtSignal as Signal
from AnyQt.QtWidgets import (
QWidget, QLabel, QPushButton, QVBoxLayout, QHBoxLayout
)

from orangewidget.settings import Context
from orangewidget.utils.combobox import ComboBoxSearch

import Orange
Expand Down Expand Up @@ -173,12 +176,19 @@ def data(self, index, role=Qt.DisplayRole):
return super().data(index, role)


class MergeDataContext(Context):
variables1: Dict[str, int]
variables2: Dict[str, int]


class MergeDataContextHandler(ContextHandler):
# `widget` is used as an argument in most methods
# pylint: disable=redefined-outer-name
# context handlers override methods using different signatures
# pylint: disable=arguments-differ

ContextType = MergeDataContext

def new_context(self, variables1, variables2):
context = super().new_context()
context.variables1 = variables1
Expand Down Expand Up @@ -286,7 +296,8 @@ class Outputs:
"merging_types")]

settingsHandler = MergeDataContextHandler()
attr_pairs = ContextSetting(None, schema_only=True)
attr_pairs: List[Tuple[Variable, Variable]] \
= ContextSetting([], schema_only=True)
merging = Setting(LeftJoin)
auto_apply = Setting(True)
settings_version = 2
Expand Down
5 changes: 3 additions & 2 deletions Orange/widgets/data/owpaintdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import itertools
from functools import partial, singledispatch
from collections import namedtuple
from typing import List

import numpy as np

Expand Down Expand Up @@ -761,8 +762,8 @@ class Outputs:
symbol_size = Setting(10)

#: current data array (shape=(N, 3)) as presented on the output
data = Setting(None, schema_only=True)
labels = Setting(["C1", "C2"], schema_only=True)
data: List[List[float]] = Setting(None, schema_only=True)
labels: List[str] = Setting(["C1", "C2"], schema_only=True)

buttons_area_orientation = Qt.Vertical
graph_name = "plot"
Expand Down
18 changes: 9 additions & 9 deletions Orange/widgets/data/owpivot.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# pylint: disable=missing-docstring
from typing import Iterable, Set
from enum import IntEnum
from typing import Iterable, Set, Tuple
from collections import defaultdict
from itertools import product, chain

Expand All @@ -20,7 +21,6 @@
from Orange.data.filter import FilterContinuous, FilterDiscrete, Values
from Orange.statistics.util import (nanmin, nanmax, nanunique, nansum, nanvar,
nanmean, nanmedian, nanmode, bincount)
from Orange.util import Enum
from Orange.widgets import gui
from Orange.widgets.settings import (Setting, ContextSetting,
DomainContextHandler)
Expand All @@ -36,7 +36,7 @@
BorderColorRole = next(gui.OrangeUserRole)


class AggregationFunctionsEnum(Enum):
class AggregationFunctionsEnum(IntEnum):
(Count, Count_defined, Sum, Mean, Min, Max,
Mode, Median, Var, Majority) = range(10)

Expand All @@ -45,7 +45,7 @@ def __init__(self, *_, **__):
self.func = None

@property
def value(self):
def value(self): # pylint: disable=invalid-overridden-method
return self._value_

def __call__(self, *args):
Expand Down Expand Up @@ -748,11 +748,11 @@ class Warning(OWWidget.Warning):
too_many_values = Msg("Selected variable has too many values.")

settingsHandler = DomainContextHandler()
row_feature = ContextSetting(None)
col_feature = ContextSetting(None)
val_feature = ContextSetting(None)
sel_agg_functions = Setting(set([Pivot.Count]))
selection = Setting(set(), schema_only=True)
row_feature: Variable = ContextSetting(None)
col_feature: DiscreteVariable = ContextSetting(None)
val_feature: Variable = ContextSetting(None)
sel_agg_functions: Set[AggregationFunctionsEnum] = Setting({Pivot.Count})
selection: Set[Tuple[int, int]] = Setting(set(), schema_only=True)
auto_commit = Setting(True)

AGGREGATIONS = (Pivot.Count,
Expand Down
Loading