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

DomainContextHandler does not handle feature "position" within the domain #6721

Open
markotoplak opened this issue Feb 1, 2024 · 1 comment · May be fixed by #6740
Open

DomainContextHandler does not handle feature "position" within the domain #6721

markotoplak opened this issue Feb 1, 2024 · 1 comment · May be fixed by #6740

Comments

@markotoplak
Copy link
Member

For remembering states of combo boxes where we choose features we tend to use contexts. We frequently use DomainContext, which works well most of the time.

It works so well that I did not notice, until now, that it is not appropriate if combo boxes within the widget are limited to a different subset of (attributes, class_vars, metas).

Here is an example:

from Orange.data import Table, Domain
from Orange.widgets.tests.base import WidgetTest
from Orange.widgets.widget import OWWidget
from Orange.widgets.gui import comboBox
from Orange.widgets.settings import \
    ContextSetting, DomainContextHandler, PerfectDomainContextHandler
from Orange.widgets.utils.itemmodels import DomainModel

from orangewidget.utils.signals import Input


class OWProblem(OWWidget):
    name = "Context Problem"

    v1 = ContextSetting(None)
    v2 = ContextSetting(None)
    settingsHandler = DomainContextHandler()

    class Inputs:
        data = Input("Data", Table, default=True)

    def __init__(self):
        super().__init__()

        m1 = DomainModel(DomainModel.SEPARATED,
                         valid_types=DomainModel.PRIMITIVE)
        comboBox(self.controlArea, self, "v1", model=m1, label="All")
        m2 = DomainModel(DomainModel.METAS | DomainModel.CLASSES,
                         valid_types=DomainModel.PRIMITIVE)
        comboBox(self.controlArea, self, "v2", model=m2, label="Metas + Classes")

        self.contextAboutToBeOpened.connect(lambda x: self.init_interface(x[0]))

    def init_interface(self, data):
        domain = data.domain if data is not None else None
        self.controls.v1.model().set_domain(domain)
        self.controls.v2.model().set_domain(domain)

    @Inputs.data
    def set_data(self, data):
        self.closeContext()
        self.openContext(data)


class TestOWProblem(WidgetTest):

    def setUp(self):
        self.widget = self.create_widget(OWProblem)

    def test_v2_invalid_after_reloading(self):
        iris = Table("iris")
        petals_in_metas = Domain(iris.domain.attributes[:2],
                                 iris.domain.class_var,
                                 metas=iris.domain.attributes[2:])
        self.send_signal(self.widget.Inputs.data,
                         iris.transform(petals_in_metas))
        self.widget.v1 = iris.domain["sepal width"]
        # v2 can have only metas and classes thus this will be invalid
        # context for original iris
        self.widget.v2 = iris.domain["petal width"]
        self.send_signal(self.widget.Inputs.data, iris)  # crashes

Running the test gives:

  File "/home/marko/dev/orange-spectroscopy/orangecontrib/spectroscopy/widgets/owproblem.py", line 42, in set_data
    self.openContext(data)
  File "/home/marko/dev/orange-widget-base/orangewidget/widget.py", line 1350, in openContext
    self.settingsHandler.open_context(self, *a)
  File "/home/marko/dev/orange3/Orange/widgets/settings.py", line 125, in open_context
    super().open_context(widget, domain, *self.encode_domain(domain))
  File "/home/marko/dev/orange-widget-base/orangewidget/settings.py", line 833, in open_context
    self.settings_to_widget(widget, *args)
  File "/home/marko/dev/orange-widget-base/orangewidget/settings.py", line 946, in settings_to_widget
    _apply_setting(setting, instance, value)
  File "/home/marko/dev/orange-widget-base/orangewidget/settings.py", line 204, in _apply_setting
    setattr(instance, setting.name, value)
  File "/home/marko/dev/orange-widget-base/orangewidget/gui.py", line 194, in __setattr__
    callback(value)
  File "/home/marko/dev/orange-widget-base/orangewidget/gui.py", line 2320, in __call__
    self.action(*args)
  File "/home/marko/dev/orange-widget-base/orangewidget/gui.py", line 2407, in action
    raise ValueError("Combo box does not contain item " + repr(value))
ValueError: Combo box does not contain item ContinuousVariable(name='petal width', number_of_decimals=1)

Is this a bug in DomainContextHandler? No, it is how it was designed; it is just that I was using it wrongly. For this kind of situations, I'll need a new type of context. PerfectDomainContextHandler does not exhibit the same issue but is too restrictive. I'll need something in between.

It is a bug that contexts that match and could not be opened effectively (for any reason) block you from using the widget with that data? Probably. :) When this happens, the only solutions is to clear the settings.

@markotoplak
Copy link
Member Author

The above case is almost solved by specifying:

v2 = ContextSetting(None, exclude_attributes=True)

The only remaining problem is that exclude_attributes effectively means exclude_attributes_or_classes. Therefore, if a class var is set as v2, then the resulting context will never be matched.

However, because exclude_* is never checked for PERFECT_MATCH, the solution with exclude_attributes=True still allows the user to properly save and load the workflow even if v2 was a class variable. It is just that that particular context will match nowhere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant