Skip to content

Commit 28877f9

Browse files
committed
Raise TypeError for Mapping as selection options
1 parent 9db179b commit 28877f9

File tree

3 files changed

+41
-12
lines changed

3 files changed

+41
-12
lines changed

ipywidgets/widgets/tests/test_interaction.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,23 @@ def test_list_tuple_invalid():
182182
print(bad) # because there is no custom message in assert_raises
183183
c = interactive(f, tup=bad)
184184

185+
def test_dict():
186+
for d in [
187+
dict(a=5),
188+
dict(a=5, b='b', c=dict),
189+
]:
190+
with pytest.raises(TypeError):
191+
c = interactive(f, d=d)
192+
193+
194+
def test_ordereddict():
195+
from collections import OrderedDict
196+
items = [(3, 300), (1, 100), (2, 200)]
197+
first = items[0][1]
198+
values = OrderedDict(items)
199+
with pytest.raises(TypeError):
200+
c = interactive(f, lis=values)
201+
185202
def test_iterable():
186203
def yield_values():
187204
yield 3
@@ -549,13 +566,14 @@ def test_multiple_selection():
549566
check_widget(w, value=(1, 2))
550567

551568
# dict style
552-
w.options = {1: 1}
553-
check_widget(w, options={1:1})
569+
with pytest.raises(TypeError):
570+
w = smw(options={1: 1})
554571

555572
# updating
573+
w.options = (1,)
556574
with pytest.raises(TraitError):
557575
w.value = (2,)
558-
check_widget(w, options={1:1})
576+
check_widget(w, options=(1,))
559577

560578

561579
def test_interact_noinspect():

ipywidgets/widgets/tests/test_widget_selection.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
# Copyright (c) Jupyter Development Team.
22
# Distributed under the terms of the Modified BSD License.
33

4+
import inspect
45
from unittest import TestCase
56

7+
import pytest
8+
69
from traitlets import TraitError
710

811
from ipywidgets import Dropdown, SelectionSlider, Select
@@ -13,6 +16,10 @@ class TestDropdown(TestCase):
1316
def test_construction(self):
1417
Dropdown()
1518

19+
def test_raise_mapping_options(self):
20+
with pytest.raises(TypeError):
21+
Dropdown(options={'One': 1, 'Two': 2, 'Three': 3})
22+
1623

1724
class TestSelectionSlider(TestCase):
1825

ipywidgets/widgets/widget_selection.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,11 @@
5454
The options for the dropdown. This can either be a list of values, e.g.
5555
``['Galileo', 'Brahe', 'Hubble']`` or ``[0, 1, 2]``, a list of
5656
(label, value) pairs, e.g.
57-
``[('Galileo', 0), ('Brahe', 1), ('Hubble', 2)]``,
58-
or a dictionary mapping the labels to the values, e.g. ``{'Galileo': 0,
59-
'Brahe': 1, 'Hubble': 2}``. The labels are the strings that will be
60-
displayed in the UI, representing the actual Python choices, and should
61-
be unique. If this is a dictionary, the order in which they are
62-
displayed is not guaranteed.
57+
``[('Galileo', 0), ('Brahe', 1), ('Hubble', 2)]``.
58+
The labels are the strings that will be displayed in the UI,
59+
representing the actual Python choices, and should be unique.
60+
If this is a dictionary, the order in which they are displayed is
61+
not guaranteed.
6362
6463
index: iterable of int
6564
The indices of the options that are selected.
@@ -106,6 +105,9 @@ def _make_options(x):
106105
* an iterable of (label, value) pairs
107106
* an iterable of values, and labels will be generated
108107
"""
108+
if isinstance(x, Mapping):
109+
raise TypeError("options must be a list of values or a list of (label, value) tuples")
110+
109111
# only iterate once through the options.
110112
xlist = tuple(x)
111113

@@ -126,7 +128,7 @@ def findvalue(array, value, compare = lambda x, y: x == y):
126128
class _Selection(DescriptionWidget, ValueWidget, CoreWidget):
127129
"""Base class for Selection widgets
128130
129-
``options`` can be specified as a list of values or list of (label, value)
131+
``options`` can be specified as a list of values or a list of (label, value)
130132
tuples. The labels are the strings that will be displayed in the UI,
131133
representing the actual Python choices, and should be unique.
132134
If labels are not specified, they are generated from the values.
@@ -177,7 +179,9 @@ def __init__(self, *args, **kwargs):
177179

178180
@validate('options')
179181
def _validate_options(self, proposal):
180-
proposal.value = tuple(proposal.value)
182+
# if an iterator is provided, exhaust it
183+
if isinstance(proposal.value, Iterable) and not isinstance(proposal.value, Mapping):
184+
proposal.value = tuple(proposal.value)
181185
# throws an error if there is a problem converting to full form
182186
self._options_full = _make_options(proposal.value)
183187
return proposal.value
@@ -283,7 +287,7 @@ class _MultipleSelection(DescriptionWidget, ValueWidget, CoreWidget):
283287
index = TypedTuple(trait=Int(), help="Selected indices").tag(sync=True)
284288

285289
options = Any((),
286-
help="""Iterable of values, (label, value) pairs, or a mapping of {label: value} pairs that the user can select.
290+
help="""Iterable of values or (label, value) pairs that the user can select.
287291
288292
The labels are the strings that will be displayed in the UI, representing the
289293
actual Python choices, and should be unique.

0 commit comments

Comments
 (0)