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

[RFC] Add automated summaries #135

Closed
wants to merge 2 commits into from
Closed
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
67 changes: 63 additions & 4 deletions orangewidget/utils/signals.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import copy
import itertools
from functools import singledispatch

from orangecanvas.registry.description import (
InputSignal, OutputSignal, Single, Multiple, Default, NonDefault,
Expand All @@ -14,6 +15,11 @@
_counter = itertools.count()


@singledispatch
def summarize(_):
return None, None


class _Signal:
@staticmethod
def get_flags(multiple, default, explicit, dynamic):
Expand Down Expand Up @@ -76,23 +82,40 @@ def set_train_data(self, data):
explicit (bool, optional):
if set, this signal is only used when it is the only option or when
explicitly connected in the dialog (default: `False`)
auto_summary (bool, optional):
decides whether this signal is used for auto_summary; only one signal
should set this to True. If left at default (`None`), the value is set
by `set_default_auto_summary`, which is called from the meta-class.
"""
def __init__(self, name, type, id=None, doc=None, replaces=None, *,
multiple=False, default=False, explicit=False):
multiple=False, default=False, explicit=False,
auto_summary=None):
flags = self.get_flags(multiple, default, explicit, False)
super().__init__(name, type, "", flags, id, doc, replaces or [])
self.auto_summary = auto_summary
self._seq_id = next(_counter)

def __call__(self, method):
"""
Decorator that stores decorated method's name in the signal's
`handler` attribute. The method is returned unchanged.
"""
def set_summary(widget, value, *args, **kwargs):
if self.auto_summary:
info = widget.info
if value is None:
info.set_input_summary(info.NoInput)
else:
summary, details = summarize(value)
if summary is not None:
info.set_input_summary(summary, details)
return method(widget, value, *args, **kwargs)

if self.handler:
raise ValueError("Input {} is already bound to method {}".
format(self.name, self.handler))
self.handler = method.__name__
return method
return set_summary


class Output(OutputSignal, _Signal):
Expand Down Expand Up @@ -133,12 +156,18 @@ class Outputs:
of the declared type and that the output can be connected to any input
signal which can accept a subtype of the declared output type
(default: `True`)
auto_summary (bool, optional):
decides whether this signal is used for auto_summary; only one signal
should set this to True. If left at default (`None`), the value is set
by `set_default_auto_summary`, which is called from the meta-class.
"""
def __init__(self, name, type, id=None, doc=None, replaces=None, *,
default=False, explicit=False, dynamic=True):
default=False, explicit=False, dynamic=True,
auto_summary=None):
flags = self.get_flags(False, default, explicit, dynamic)
self.auto_summary = auto_summary
super().__init__(name, type, flags, id, doc, replaces or [])
self.widget = None
self.widget = None #: OWBaseWidget
self._seq_id = next(_counter)

def send(self, value, id=None):
Expand All @@ -147,6 +176,14 @@ def send(self, value, id=None):
signal_manager = self.widget.signalManager
if signal_manager is not None:
signal_manager.send(self.widget, self.name, value, id)
info = self.widget.info
if self.auto_summary:
if value is None:
info.set_output_summary(info.NoOutput)
else:
summary, details = summarize(value)
if summary is not None:
info.set_output_summary(summary, details)

def invalidate(self):
"""Invalidate the current output value on the signal"""
Expand Down Expand Up @@ -257,6 +294,28 @@ def get_signals(cls, direction, ignore_old_style=False):
signals = [signal for _, signal in getmembers(signal_class, _Signal)]
return list(sorted(signals, key=lambda s: s._seq_id))

@classmethod
def set_default_auto_summary(cls):
"""
Set the default auto_summary; called from meta class.

If
- there is only a single input/output, or there is a default signal,
- and that signal has auto_summary left at default (None),
- and no other signal has auto_summary set to True,
set that signal's auto_summary to True, so that it's value will be
shown in the status.
"""
for signal_cls, signal_type in ((cls.Inputs, Input),
(cls.Outputs, Output)):
signals = getmembers(signal_cls, signal_type)
if any(signal.auto_summary for _, signal in signals):
continue
for _, signal in signals:
if signal.auto_summary is None:
signal.auto_summary = \
bool(signal.flags & Default) or len(signals) == 1


class AttributeList(list):
"""Signal type for lists of attributes (variables)"""
1 change: 1 addition & 0 deletions orangewidget/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def __new__(mcs, name, bases, namespace, openclass=False, **kwargs):
if not cls.name: # not a widget
return cls
cls.convert_signals()
cls.set_default_auto_summary()
cls.settingsHandler = \
SettingsHandler.create(cls, template=cls.settingsHandler)
return cls
Expand Down