Skip to content

Commit

Permalink
first working prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
markotoplak committed Feb 19, 2024
1 parent 7bdc8e4 commit 5896dd8
Showing 1 changed file with 75 additions and 18 deletions.
93 changes: 75 additions & 18 deletions Orange/widgets/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@
import warnings

from orangewidget.settings import (
Setting, SettingProvider, SettingsHandler, ContextSetting,
Setting, SettingProvider, SettingsHandler,
ContextHandler, Context, IncompatibleContext, SettingsPrinter,
rename_setting, widget_settings_dir
)
from orangewidget.settings import ContextSetting as WBContextSetting
from orangewidget.settings import _apply_setting

from Orange.data import Domain, Variable
Expand All @@ -62,6 +63,19 @@
]


class ContextSetting(WBContextSetting):
"""Description of a context dependent setting"""

def __init__(self, default, *, required=2,
exclude_attributes=False, exclude_class_vars=False, exclude_metas=False,
**data):
super().__init__(default, required=required, **data)
self.exclude_attributes = exclude_attributes
self.exclude_class_vars = exclude_class_vars
self.exclude_metas = exclude_metas
self.required = required


class DomainContextHandler(ContextHandler):
"""Context handler for widgets with settings that depend on
the input dataset. Suitable settings are selected based on the
Expand Down Expand Up @@ -124,18 +138,18 @@ def open_context(self, widget, domain):
domain = domain.domain
super().open_context(widget, domain, *self.encode_domain(domain))

def filter_value(self, setting, data, domain, attrs, metas):
def filter_value(self, setting, data, domain, *args):
value = data.get(setting.name, None)
if isinstance(value, list):
new_value = [item for item in value
if self.is_valid_item(setting, item, attrs, metas)]
if self.is_valid_item(setting, item, *args)]
data[setting.name] = new_value
elif isinstance(value, dict):
new_value = {item: val for item, val in value.items()
if self.is_valid_item(setting, item, attrs, metas)}
if self.is_valid_item(setting, item, *args)}
data[setting.name] = new_value
elif self.is_encoded_var(value) \
and not self._var_exists(setting, value, attrs, metas):
and not self._var_exists(setting, value, *args):
del data[setting.name]

@staticmethod
Expand All @@ -159,7 +173,7 @@ def encode_setting(cls, context, setting, value):
-4)

if isinstance(value, Variable):
if isinstance(setting, ContextSetting):
if isinstance(setting, WBContextSetting):
return cls.encode_variable(value)
else:
raise ValueError("Variables must be stored as ContextSettings; "
Expand Down Expand Up @@ -205,28 +219,30 @@ def _var_exists(cls, setting, value, attributes, metas):
def match(self, context, domain, attrs, metas):
if context.attributes == attrs and context.metas == metas:
return self.PERFECT_MATCH
return self._match(context, domain, attrs, metas)

def _match(self, context, domain, *args):
matches = []
try:
for setting, data, _ in \
self.provider.traverse_settings(data=context.values):
if not isinstance(setting, ContextSetting):
if not isinstance(setting, WBContextSetting):
continue
value = data.get(setting.name, None)

if isinstance(value, list):
matches.append(
self.match_list(setting, value, context, attrs, metas))
self.match_list(setting, value, context, *args))
# type check is a (not foolproof) check in case of a pair that
# would, by conincidence, have -3 or -4 as the second element
elif isinstance(value, tuple) and len(value) == 2 \
and (value[1] == -3 and isinstance(value[0], list)
or (value[1] == -4 and isinstance(value[0], dict))):
matches.append(self.match_list(setting, value[0], context,
attrs, metas))
*args))
elif value is not None:
matches.append(
self.match_value(setting, value, attrs, metas))
self.match_value(setting, value, *args))
except IncompatibleContext:
return self.NO_MATCH

Expand All @@ -237,39 +253,39 @@ def match(self, context, domain, attrs, metas):
matched, available = [sum(m) for m in zip(*matches)]
return matched / available if available else 0.1

def match_list(self, setting, value, context, attrs, metas):
def match_list(self, setting, value, context, *args):
"""Match a list of values with the given context.
returns a tuple containing number of matched and all values.
"""
matched = 0
for item in value:
if self.is_valid_item(setting, item, attrs, metas):
if self.is_valid_item(setting, item, *args):
matched += 1
elif setting.required == ContextSetting.REQUIRED:
elif setting.required == WBContextSetting.REQUIRED:
raise IncompatibleContext()
return matched, len(value)

def match_value(self, setting, value, attrs, metas):
def match_value(self, setting, value, *args):
"""Match a single value """
if value[1] < 0:
return 0, 0

if self._var_exists(setting, value, attrs, metas):
if self._var_exists(setting, value, *args):
return 1, 1
elif setting.required == setting.OPTIONAL:
return 0, 1
else:
raise IncompatibleContext()

def is_valid_item(self, setting, item, attrs, metas):
def is_valid_item(self, setting, item, *args):
"""Return True if given item can be used with attrs and metas
Subclasses can override this method to checks data in alternative
representations.
"""
if not isinstance(item, tuple):
return True
return self._var_exists(setting, item, attrs, metas)
return self._var_exists(setting, item, *args)

@staticmethod
def is_encoded_var(value):
Expand All @@ -278,6 +294,47 @@ def is_encoded_var(value):
and isinstance(value[0], str) and isinstance(value[1], int) \
and value[1] >= 0


class DomainContextHandlerPosition(DomainContextHandler):

def __init__(self, *, match_values=0, first_match=True):
super().__init__(match_values=match_values, first_match=first_match)

def encode_domain(self, domain):
match = self.match_values
encode = self.encode_variables
attributes = encode(domain.attributes, match == self.MATCH_VALUES_ALL)
class_vars = encode(domain.class_vars,
match in (self.MATCH_VALUES_ALL, self.MATCH_VALUES_CLASS))
metas = encode(domain.metas, match == self.MATCH_VALUES_ALL)

return attributes, class_vars, metas

new_context = ContextHandler.new_context

@classmethod
def _var_exists(cls, setting, value, attributes, class_vars, metas):
assert isinstance(setting, ContextSetting)

if not cls.is_encoded_var(value):
return False

attr_name, attr_type = value
# attr_type used to be either 1-4 for variables stored as string
# settings, and 101-104 for variables stored as variables. The former is
# no longer supported, but we play it safe and still handle both here.
attr_type %= 100
return (not setting.exclude_attributes and
attributes.get(attr_name, -1) == attr_type or
not setting.exclude_class_vars and
class_vars.get(attr_name, -1) == attr_type or
not setting.exclude_metas and
metas.get(attr_name, -1) == attr_type)

def match(self, context, domain, attrs, class_vars, metas):
return self._match(context, domain, attrs, class_vars, metas)


class ClassValuesContextHandler(ContextHandler):
"""Context handler used for widgets that work with
a single discrete variable"""
Expand Down Expand Up @@ -358,7 +415,7 @@ def encode_setting(self, context, setting, value):
"""Same as is domain context handler, but handles separately stored
class_vars."""

if isinstance(setting, ContextSetting) and isinstance(value, str):
if isinstance(setting, WBContextSetting) and isinstance(value, str):

def _candidate_variables():
if not setting.exclude_attributes:
Expand Down

0 comments on commit 5896dd8

Please sign in to comment.