diff --git a/Orange/widgets/unsupervised/owhierarchicalclustering.py b/Orange/widgets/unsupervised/owhierarchicalclustering.py
index 62b742cd0a8..f71cc12ba72 100644
--- a/Orange/widgets/unsupervised/owhierarchicalclustering.py
+++ b/Orange/widgets/unsupervised/owhierarchicalclustering.py
@@ -19,11 +19,12 @@
from Orange.widgets.utils.localization import pl
from orangewidget.utils.itemmodels import PyListModel
+from orangewidget.utils.signals import LazyValue
import Orange.data
from Orange.data.domain import filter_visible
from Orange.data import Domain, DiscreteVariable, ContinuousVariable, \
- StringVariable
+ StringVariable, Table
import Orange.misc
from Orange.clustering.hierarchical import \
postorder, preorder, Tree, tree_from_linkage, dist_matrix_linkage, \
@@ -32,8 +33,11 @@
from Orange.widgets import widget, gui, settings
from Orange.widgets.utils import itemmodels, combobox
-from Orange.widgets.utils.annotated_data import (create_annotated_table,
- ANNOTATED_DATA_SIGNAL_NAME)
+from Orange.widgets.utils.annotated_data import (lazy_annotated_table,
+ ANNOTATED_DATA_SIGNAL_NAME,
+ domain_with_annotation_column,
+ add_columns,
+ create_annotated_table)
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.visualize.utils.plotutils import AxisItem
from Orange.widgets.widget import Input, Output, Msg
@@ -776,71 +780,73 @@ def commit(self):
for node in selection]
selected_indices = list(chain(*maps))
- unselected_indices = sorted(set(range(self.root.value.last)) -
- set(selected_indices))
if not selected_indices:
self.Outputs.selected_data.send(None)
- annotated_data = create_annotated_table(items, []) \
+ annotated_data = lazy_annotated_table(items, []) \
if self.selection_method == 0 and self.matrix.axis else None
self.Outputs.annotated_data.send(annotated_data)
return
- selected_data = None
+ selected_data = annotated_data = None
if isinstance(items, Orange.data.Table) and self.matrix.axis == 1:
# Select rows
- c = np.zeros(self.matrix.shape[0])
+ data, domain = items, items.domain
+ c = np.full(self.matrix.shape[0], len(maps))
for i, indices in enumerate(maps):
c[indices] = i
- c[unselected_indices] = len(maps)
-
- mask = c != len(maps)
-
- data, domain = items, items.domain
- attrs = domain.attributes
- classes = domain.class_vars
- metas = domain.metas
- var_name = get_unique_names(domain, "Cluster")
+ clust_name = get_unique_names(domain, "Cluster")
values = [f"C{i + 1}" for i in range(len(maps))]
- clust_var = Orange.data.DiscreteVariable(
- var_name, values=values + ["Other"])
- domain = Orange.data.Domain(attrs, classes, metas + (clust_var,))
- data = items.transform(domain)
- with data.unlocked(data.metas):
- data.set_column(clust_var, c)
-
- if selected_indices:
- selected_data = data[mask]
- clust_var = Orange.data.DiscreteVariable(
- var_name, values=values)
- selected_data.domain = Domain(
- attrs, classes, metas + (clust_var, ))
-
- annotated_data = create_annotated_table(data, selected_indices)
+ sel_clust_var = Orange.data.DiscreteVariable(
+ name=clust_name, values=values)
+ sel_domain = add_columns(domain, metas=(sel_clust_var,))
+ selected_data = LazyValue[Table](
+ lambda: items.add_column(
+ sel_clust_var, c, to_metas=True)[c != len(maps)],
+ domain=sel_domain, length=len(selected_indices))
+
+ ann_clust_var = Orange.data.DiscreteVariable(
+ name=clust_name, values=values + ["Other"]
+ )
+ ann_domain = add_columns(
+ domain_with_annotation_column(data)[0], metas=(ann_clust_var, ))
+ annotated_data = LazyValue[Table](
+ lambda: create_annotated_table(
+ data=items.add_column(ann_clust_var, c, to_metas=True),
+ selected_indices=selected_indices),
+ domain=ann_domain, length=len(items)
+ )
elif isinstance(items, Orange.data.Table) and self.matrix.axis == 0:
# Select columns
attrs = []
+ unselected_indices = sorted(set(range(self.root.value.last)) -
+ set(selected_indices))
for clust, indices in chain(enumerate(maps, start=1),
[(0, unselected_indices)]):
for i in indices:
attr = items.domain[i].copy()
attr.attributes["cluster"] = clust
attrs.append(attr)
- domain = Orange.data.Domain(
+ all_domain = Orange.data.Domain(
# len(unselected_indices) can be 0
attrs[:len(attrs) - len(unselected_indices)],
items.domain.class_vars, items.domain.metas)
- selected_data = items.from_table(domain, items)
- domain = Orange.data.Domain(
+ selected_data = LazyValue[Table](
+ lambda: items.from_table(all_domain, items),
+ domain=all_domain, length=len(items))
+
+ sel_domain = Orange.data.Domain(
attrs,
items.domain.class_vars, items.domain.metas)
- annotated_data = items.from_table(domain, items)
+ annotated_data = LazyValue[Table](
+ lambda: items.from_table(sel_domain, items),
+ domain=sel_domain, length=len(items))
self.Outputs.selected_data.send(selected_data)
self.Outputs.annotated_data.send(annotated_data)
diff --git a/Orange/widgets/utils/annotated_data.py b/Orange/widgets/utils/annotated_data.py
index 46149c15291..401520d3201 100644
--- a/Orange/widgets/utils/annotated_data.py
+++ b/Orange/widgets/utils/annotated_data.py
@@ -1,5 +1,10 @@
+from typing import Union
+
import numpy as np
-from Orange.data import Domain, DiscreteVariable
+
+from orangewidget.utils.signals import LazyValue
+
+from Orange.data import Domain, DiscreteVariable, Table
from Orange.data.util import get_unique_names
ANNOTATED_DATA_SIGNAL_NAME = "Data"
@@ -30,16 +35,26 @@ def add_columns(domain, attributes=(), class_vars=(), metas=()):
return Domain(attributes, class_vars, metas)
+def domain_with_annotation_column(
+ data: Union[Table, Domain],
+ values=("No", "Yes"),
+ var_name=ANNOTATED_DATA_FEATURE_NAME):
+ domain = data if isinstance(data, Domain) else data.domain
+ var = DiscreteVariable(get_unique_names(domain, var_name), values)
+ class_vars, metas = domain.class_vars, domain.metas
+ if not domain.class_vars:
+ class_vars += (var, )
+ else:
+ metas += (var, )
+ return Domain(domain.attributes, class_vars, metas), var
+
+
def _table_with_annotation_column(data, values, column_data, var_name):
- var = DiscreteVariable(get_unique_names(data.domain, var_name), values)
- class_vars, metas = data.domain.class_vars, data.domain.metas
+ domain, var = domain_with_annotation_column(data, values, var_name)
if not data.domain.class_vars:
- class_vars += (var, )
column_data = column_data.reshape((len(data), ))
else:
- metas += (var, )
column_data = column_data.reshape((len(data), 1))
- domain = Domain(data.domain.attributes, class_vars, metas)
table = data.transform(domain)
with table.unlocked(table.Y if not data.domain.class_vars else table.metas):
table[:, var] = column_data
@@ -65,17 +80,20 @@ def create_annotated_table(data, selected_indices):
data, ("No", "Yes"), annotated, ANNOTATED_DATA_FEATURE_NAME)
+def lazy_annotated_table(data, selected_indices):
+ domain, _ = domain_with_annotation_column(data)
+ return LazyValue[Table](
+ lambda: create_annotated_table(data, selected_indices),
+ length=len(data), domain=domain)
+
+
def create_groups_table(data, selection,
include_unselected=True,
var_name=ANNOTATED_DATA_FEATURE_NAME,
values=None):
if data is None:
return None
- max_sel = np.max(selection)
- if values is None:
- values = ["G{}".format(i + 1) for i in range(max_sel)]
- if include_unselected:
- values.append("Unselected")
+ values, max_sel = group_values(selection, include_unselected, values)
if include_unselected:
# Place Unselected instances in the "last group", so that the group
# colors and scatter diagram marker colors will match
@@ -88,3 +106,24 @@ def create_groups_table(data, selection,
data = data[mask]
selection = selection[mask] - 1
return _table_with_annotation_column(data, values, selection, var_name)
+
+
+def lazy_groups_table(data, selection, include_unselected=True,
+ var_name=ANNOTATED_DATA_FEATURE_NAME, values=None):
+ length = len(data) if include_unselected else np.sum(selection != 0)
+ values, _ = group_values(selection, include_unselected, values)
+ domain, _ = domain_with_annotation_column(data, values, var_name)
+ return LazyValue[Table](
+ lambda: create_groups_table(data, selection, include_unselected,
+ var_name, values),
+ length=length, domain=domain
+ )
+
+
+def group_values(selection, include_unselected, values):
+ max_sel = np.max(selection)
+ if values is None:
+ values = ["G{}".format(i + 1) for i in range(max_sel)]
+ if include_unselected:
+ values.append("Unselected")
+ return values, max_sel
diff --git a/Orange/widgets/utils/state_summary.py b/Orange/widgets/utils/state_summary.py
index d43037dcf3b..964b48eaba6 100644
--- a/Orange/widgets/utils/state_summary.py
+++ b/Orange/widgets/utils/state_summary.py
@@ -1,10 +1,11 @@
from datetime import date
from html import escape
+from typing import Union
from AnyQt.QtCore import Qt
from Orange.widgets.utils.localization import pl
-from orangewidget.utils.signals import summarize, PartialSummary
+from orangewidget.utils.signals import summarize, PartialSummary, LazyValue
from Orange.widgets.utils.itemmodels import TableModel
from Orange.widgets.utils.tableview import TableView
from Orange.widgets.utils.distmatrixmodel import \
@@ -12,7 +13,7 @@
from Orange.data import (
StringVariable, DiscreteVariable, ContinuousVariable, TimeVariable,
- Table
+ Table, Domain
)
from Orange.evaluation import Results
@@ -62,64 +63,65 @@ def format_variables_string(variables):
# `format` is a good name for the argument, pylint: disable=redefined-builtin
-def format_summary_details(data, format=Qt.PlainText):
+def format_summary_details(data: Union[Table, Domain],
+ format=Qt.PlainText, missing=None):
"""
A function that forms the entire descriptive part of the input/output
summary.
:param data: A dataset
- :type data: Orange.data.Table
+ :type data: Orange.data.Table or Orange.data.Domain
:return: A formatted string
"""
if data is None:
return ""
- if format == Qt.PlainText:
- def b(s):
- return s
- else:
- def b(s):
- return f"{s}"
-
- features_missing = ""
- if len(data) * len(data.domain.attributes) < COMPUTE_NANS_LIMIT:
- features_missing = missing_values(data.get_nan_frequency_attribute())
- n_features = len(data.domain.variables) + len(data.domain.metas)
- name = getattr(data, "name", None)
- if name == "untitled":
+ features_missing = "" if missing is None else missing_values(missing)
+ if isinstance(data, Domain):
+ domain = data
name = None
-
- basic = f'{len(data):n} {pl(len(data), "instance")}, ' \
- f'{n_features} {pl(n_features, "variable")}'
-
- features = format_variables_string(data.domain.attributes)
- features = f'Features: {features} {features_missing}'
-
- targets = format_variables_string(data.domain.class_vars)
+ basic = ""
+ else:
+ assert isinstance(data, Table)
+ domain = data.domain
+ if not features_missing and \
+ len(data) * len(domain.attributes) < COMPUTE_NANS_LIMIT:
+ features_missing \
+ = missing_values(data.get_nan_frequency_attribute())
+ name = getattr(data, "name", None)
+ if name == "untitled":
+ name = None
+ basic = f'{len(data):n} {pl(len(data), "instance")}, '
+
+ n_features = len(domain.variables) + len(domain.metas)
+ basic += f'{n_features} {pl(n_features, "variable")}'
+
+ features = format_variables_string(domain.attributes)
+ features = f'Features: {features}{features_missing}'
+
+ targets = format_variables_string(domain.class_vars)
targets = f'Target: {targets}'
- metas = format_variables_string(data.domain.metas)
+ metas = format_variables_string(domain.metas)
metas = f'Metas: {metas}'
if format == Qt.PlainText:
- details = ""
- if name:
- details += f"{name}: "
+ details = f"{name}: " if name else "Table with "
details += f"{basic}\n{features}\n{targets}"
- if data.domain.metas:
+ if domain.metas:
details += f"\n{metas}"
else:
descs = []
if name:
descs.append(_nobr(f"{escape(name)}: {basic}"))
else:
- descs.append(_nobr(basic))
+ descs.append(_nobr(f"Table with {basic}"))
- if data.domain.variables:
+ if domain.variables:
descs.append(_nobr(features))
- if data.domain.class_vars:
+ if domain.class_vars:
descs.append(_nobr(targets))
- if data.domain.metas:
+ if domain.metas:
descs.append(_nobr(metas))
details = '
'.join(descs)
@@ -129,11 +131,11 @@ def b(s):
def missing_values(value):
if value:
- return f'({value*100:.1f}% missing values)'
+ return f' ({value*100:.1f}% missing values)'
elif value is None:
return ''
else:
- return '(no missing values)'
+ return ' (no missing values)'
def format_multiple_summaries(data_list, type_io='input'):
@@ -176,15 +178,31 @@ def _nobr(s):
@summarize.register
def summarize_table(data: Table): # pylint: disable=function-redefined
- def previewer():
- view = TableView(selectionMode=TableView.NoSelection)
- view.setModel(TableModel(data))
- return view
-
return PartialSummary(
data.approx_len(),
format_summary_details(data, format=Qt.RichText),
- previewer)
+ lambda: _table_previewer(data))
+
+
+@summarize.register
+def summarize_table(data: LazyValue[Table]):
+ if data.is_cached:
+ return summarize(data.get_value())
+
+ length = getattr(data, "length", "?")
+ details = format_summary_details(data.domain, format=Qt.RichText,
+ missing=getattr(data, "missing", None)) \
+ if hasattr(data, "domain") else "data available, but not prepared yet"
+ return PartialSummary(
+ length,
+ details,
+ lambda: _table_previewer(data.get_value()))
+
+
+def _table_previewer(data):
+ view = TableView(selectionMode=TableView.NoSelection)
+ view.setModel(TableModel(data))
+ return view
@summarize.register
diff --git a/Orange/widgets/utils/tests/test_annotated_data.py b/Orange/widgets/utils/tests/test_annotated_data.py
index a2b5647d3e6..47bdda3910a 100644
--- a/Orange/widgets/utils/tests/test_annotated_data.py
+++ b/Orange/widgets/utils/tests/test_annotated_data.py
@@ -1,12 +1,16 @@
+from unittest.mock import patch
+
import random
import unittest
import numpy as np
-from Orange.data import Table, Domain, StringVariable, DiscreteVariable
+from Orange.data import Table, Domain, StringVariable, DiscreteVariable, \
+ ContinuousVariable
from Orange.data.filter import SameValue
from Orange.widgets.utils.annotated_data import (
- create_annotated_table, create_groups_table, ANNOTATED_DATA_FEATURE_NAME
+ create_annotated_table, create_groups_table, ANNOTATED_DATA_FEATURE_NAME,
+ lazy_annotated_table, lazy_groups_table, domain_with_annotation_column
)
@@ -15,6 +19,42 @@ def setUp(self):
random.seed(42)
self.zoo = Table("zoo")
+ def test_domain_with_annotation_column(self):
+ a, b, c = (ContinuousVariable(x) for x in "abc")
+
+ x = [[1, 2, 3], [4, 5, 6]]
+
+ for data in (dabc := Domain([a, b, c]), Table.from_list(dabc, x)):
+ dom, var = domain_with_annotation_column(data)
+ self.assertEqual(dom.attributes, (a, b, c))
+ self.assertIs(dom.class_var, var)
+ self.assertEqual(var.name, ANNOTATED_DATA_FEATURE_NAME)
+ self.assertEqual(var.values, ("No", "Yes"))
+
+ dom, var = domain_with_annotation_column(
+ data, values=tuple("xyz"), var_name="d")
+ self.assertEqual(dom.attributes, (a, b, c))
+ self.assertIs(dom.class_var, var)
+ self.assertEqual(var.name, "d")
+ self.assertEqual(var.values, tuple("xyz"))
+
+ for data in (dabc := Domain([a, b], c), Table.from_list(dabc, x)):
+ dom, var = domain_with_annotation_column(
+ data, values=tuple("xyz"), var_name="d")
+ self.assertEqual(dom.attributes, (a, b))
+ self.assertIs(dom.class_var, c)
+ self.assertEqual(dom.metas, (var, ))
+ self.assertEqual(var.name, "d")
+ self.assertEqual(var.values, tuple("xyz"))
+
+ dom, var = domain_with_annotation_column(
+ data, values=tuple("xyz"), var_name="c")
+ self.assertEqual(dom.attributes, (a, b))
+ self.assertIs(dom.class_var, c)
+ self.assertEqual(dom.metas, (var, ))
+ self.assertEqual(var.name, "c (1)")
+ self.assertEqual(var.values, tuple("xyz"))
+
def test_create_annotated_table(self):
annotated = create_annotated_table(self.zoo, list(range(10)))
@@ -129,3 +169,52 @@ def test_create_groups_table_set_values(self):
values = ("this", "that", "rest")
table = create_groups_table(self.zoo, selection, values=values)
self.assertEqual(tuple(table.domain["Selected"].values), values)
+
+ @patch("Orange.widgets.utils.annotated_data.create_annotated_table")
+ def test_lazy_annotated_table(self, creator):
+ selected_indices = np.array([1, 2, 3])
+ lazy_table = lazy_annotated_table(self.zoo, selected_indices)
+ self.assertEqual(lazy_table.length, len(self.zoo))
+ self.assertEqual(lazy_table.domain.attributes, self.zoo.domain.attributes)
+ self.assertEqual(lazy_table.domain.class_var, self.zoo.domain.class_var)
+ self.assertEqual(len(lazy_table.domain.metas), 2)
+ var = lazy_table.domain.metas[1]
+ self.assertIsInstance(var, DiscreteVariable)
+ self.assertEqual(var.name, ANNOTATED_DATA_FEATURE_NAME)
+ creator.assert_not_called()
+ self.assertIs(lazy_table.get_value(), creator.return_value)
+
+ @patch("Orange.widgets.utils.annotated_data.create_groups_table")
+ def test_lazy_groups_table(self, creator):
+ group_indices = np.zeros(len(self.zoo), dtype=int)
+ group_indices[10:15] = 1
+
+ lazy_table = lazy_groups_table(self.zoo, group_indices)
+ self.assertEqual(lazy_table.length, len(self.zoo))
+ self.assertEqual(lazy_table.domain.attributes, self.zoo.domain.attributes)
+ self.assertEqual(lazy_table.domain.class_var, self.zoo.domain.class_var)
+ self.assertEqual(len(lazy_table.domain.metas), 2)
+ var = lazy_table.domain.metas[1]
+ self.assertIsInstance(var, DiscreteVariable)
+ self.assertEqual(var.name, ANNOTATED_DATA_FEATURE_NAME)
+ creator.assert_not_called()
+ self.assertIs(lazy_table.get_value(), creator.return_value)
+ creator.reset_mock()
+
+ lazy_table = lazy_groups_table(
+ self.zoo, group_indices, include_unselected=False, var_name="foo",
+ values=("bar", "baz"))
+ self.assertEqual(lazy_table.length, 5)
+ self.assertEqual(lazy_table.domain.attributes, self.zoo.domain.attributes)
+ self.assertEqual(lazy_table.domain.class_var, self.zoo.domain.class_var)
+ self.assertEqual(len(lazy_table.domain.metas), 2)
+ var = lazy_table.domain.metas[1]
+ self.assertIsInstance(var, DiscreteVariable)
+ self.assertEqual(var.name, "foo")
+ self.assertEqual(var.values, ("bar", "baz"))
+ creator.assert_not_called()
+ self.assertIs(lazy_table.get_value(), creator.return_value)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Orange/widgets/utils/tests/test_state_summary.py b/Orange/widgets/utils/tests/test_state_summary.py
index 3d09b49e131..fe5ea27bab8 100644
--- a/Orange/widgets/utils/tests/test_state_summary.py
+++ b/Orange/widgets/utils/tests/test_state_summary.py
@@ -1,10 +1,15 @@
import unittest
-from unittest.mock import patch
+from unittest.mock import patch, Mock
import datetime
from collections import namedtuple
import numpy as np
+from AnyQt.QtCore import Qt
+
+from orangecanvas.scheme.signalmanager import LazyValue
+from orangewidget.utils.signals import summarize
+
from Orange.data import Table, Domain, StringVariable, ContinuousVariable, \
DiscreteVariable, TimeVariable
from Orange.widgets.tests.base import WidgetTest
@@ -116,6 +121,12 @@ def test_details(self):
f'Metas: string'
self.assertEqual(details, format_summary_details(data))
+ details = f'Table with {n_features} variables\n' \
+ f'Features: {len(data.domain.attributes)} categorical\n' \
+ f'Target: categorical\n' \
+ f'Metas: string'
+ self.assertEqual(details, format_summary_details(data.domain))
+
data = Table('housing')
n_features = len(data.domain.variables) + len(data.domain.metas)
details = f'housing: {len(data)} instances, ' \
@@ -139,7 +150,7 @@ def test_details(self):
target=[rgb_full, rgb_missing], metas=[ints_full, ints_missing]
)
n_features = len(data.domain.variables) + len(data.domain.metas)
- details = f'{len(data)} instances, ' \
+ details = f'Table with {len(data)} instances, ' \
f'{n_features} variables\n' \
f'Features: {len(data.domain.attributes)} numeric ' \
f'(10.0% missing values)\n' \
@@ -153,7 +164,7 @@ def test_details(self):
metas=[string_full, string_missing]
)
n_features = len(data.domain.variables) + len(data.domain.metas)
- details = f'{len(data)} instances, ' \
+ details = f'Table with {len(data)} instances, ' \
f'{n_features} variables\n' \
f'Features: {len(data.domain.attributes)} ' \
f'(2 categorical, 1 numeric, 1 time) (5.0% missing values)\n' \
@@ -164,7 +175,7 @@ def test_details(self):
data = make_table([time_full, time_missing], target=[ints_missing],
metas=None)
- details = f'{len(data)} instances, ' \
+ details = f'Table with {len(data)} instances, ' \
f'{len(data.domain.variables)} variables\n' \
f'Features: {len(data.domain.attributes)} time ' \
f'(10.0% missing values)\n' \
@@ -172,7 +183,7 @@ def test_details(self):
self.assertEqual(details, format_summary_details(data))
data = make_table([rgb_full, ints_full], target=None, metas=None)
- details = f'{len(data)} instances, ' \
+ details = f'Table with {len(data)} instances, ' \
f'{len(data.domain.variables)} variables\n' \
f'Features: {len(data.domain.variables)} categorical ' \
f'(no missing values)\n' \
@@ -180,16 +191,16 @@ def test_details(self):
self.assertEqual(details, format_summary_details(data))
data = make_table([rgb_full], target=None, metas=None)
- details = f'{len(data)} instances, ' \
+ details = f'Table with {len(data)} instances, ' \
f'{len(data.domain.variables)} variable\n' \
f'Features: categorical (no missing values)\n' \
f'Target: —'
self.assertEqual(details, format_summary_details(data))
data = Table.from_numpy(domain=None, X=np.random.random((10000, 1000)))
- details = f'{len(data):n} instances, ' \
+ details = f'Table with {len(data):n} instances, ' \
f'{len(data.domain.variables)} variables\n' \
- f'Features: {len(data.domain.variables)} numeric \n' \
+ f'Features: {len(data.domain.variables)} numeric\n' \
f'Target: —'
with patch.object(Table, "get_nan_frequency_attribute") as mock:
self.assertEqual(details, format_summary_details(data))
@@ -248,5 +259,61 @@ def test_multiple_summaries(self):
format_multiple_summaries(outputs, type_io='output'))
+class TestSummarize(unittest.TestCase):
+ @patch("Orange.widgets.utils.state_summary._table_previewer")
+ def test_summarize_table(self, previewer):
+ data = Table('zoo')
+ summary = summarize(data)
+ self.assertEqual(summary.summary, len(data))
+ self.assertEqual(summary.details,
+ format_summary_details(data, format=Qt.RichText))
+ previewer.assert_not_called()
+ summary.preview_func()
+ previewer.assert_called_with(data)
+
+ @patch("Orange.widgets.utils.state_summary._table_previewer")
+ def test_summarize_lazy_table(self, previewer):
+ data = Table('zoo')
+
+ # lazy_data of unknown length and domain
+ lazy_data = LazyValue[Table](lambda: data)
+ lazy_data.get_value = Mock(return_value=data)
+ summary = summarize(lazy_data)
+ self.assertEqual(summary.summary, "?")
+ self.assertIsInstance(summary.details, str)
+ lazy_data.get_value.assert_not_called()
+ previewer.assert_not_called()
+ summary.preview_func()
+ lazy_data.get_value.assert_called()
+ previewer.assert_called_with(data)
+ previewer.reset_mock()
+
+ # lazy_data with length and domain hint
+ lazy_data = LazyValue[Table](
+ lambda: data, length=123, domain=data.domain)
+ lazy_data.get_value = Mock(return_value=data)
+ summary = summarize(lazy_data)
+ self.assertEqual(summary.summary, 123)
+ self.assertEqual(summary.details,
+ format_summary_details(data.domain, format=Qt.RichText))
+ lazy_data.get_value.assert_not_called()
+ previewer.assert_not_called()
+ summary.preview_func()
+ lazy_data.get_value.assert_called()
+ previewer.assert_called_with(data)
+ previewer.reset_mock()
+
+ # lazy_data that is already cached: complete summary even without hints
+ lazy_data = LazyValue[Table](lambda: data)
+ lazy_data.get_value()
+ summary = summarize(lazy_data)
+ self.assertEqual(summary.summary, len(data))
+ self.assertEqual(summary.details,
+ format_summary_details(data, format=Qt.RichText))
+ previewer.assert_not_called()
+ summary.preview_func()
+ previewer.assert_called_with(data)
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml
index f33d4c8a7b8..7a7ba552598 100644
--- a/conda-recipe/meta.yaml
+++ b/conda-recipe/meta.yaml
@@ -61,7 +61,7 @@ requirements:
- pandas >=1.4.0,!=1.5.0,!=2.0.0
- pyyaml
- orange-canvas-core >=0.1.30,<0.2a
- - orange-widget-base >=4.20.0
+ - orange-widget-base >=4.21.0
- openpyxl
- httpx >=0.21
- baycomp >=1.0.2
diff --git a/requirements-gui.txt b/requirements-gui.txt
index 52392deb7b8..12466671d8e 100644
--- a/requirements-gui.txt
+++ b/requirements-gui.txt
@@ -1,5 +1,5 @@
orange-canvas-core>=0.1.30,<0.2a
-orange-widget-base>=4.20.0
+orange-widget-base>=4.21.0
AnyQt>=0.2.0
diff --git a/tox.ini b/tox.ini
index 376f8f9a4a7..21f5e88f041 100644
--- a/tox.ini
+++ b/tox.ini
@@ -39,7 +39,7 @@ deps =
latest: https://github.com/biolab/orange-canvas-core/archive/refs/heads/master.zip#egg=orange-canvas-core
latest: https://github.com/biolab/orange-widget-base/archive/refs/heads/master.zip#egg=orange-widget-base
oldest: orange-canvas-core==0.1.30
- oldest: orange-widget-base==4.20.0
+ oldest: orange-widget-base==4.21.0
oldest: AnyQt==0.2.0
oldest: pyqtgraph>=0.13.1
oldest: matplotlib==3.2.0