mirror of
https://github.com/aykhans/AzSuicideDataVisualization.git
synced 2025-07-05 15:42:33 +00:00
first commit
This commit is contained in:
@ -0,0 +1,394 @@
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import pytest
|
||||
try:
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
import mock
|
||||
|
||||
from traitlets import Bool, Tuple, List, Instance, CFloat, CInt, Float, Int, TraitError, observe
|
||||
|
||||
from .utils import setup, teardown
|
||||
|
||||
import ipywidgets
|
||||
from ipywidgets import Widget
|
||||
|
||||
|
||||
@pytest.fixture(params=[True, False])
|
||||
def echo(request):
|
||||
oldvalue = ipywidgets.widgets.widget.JUPYTER_WIDGETS_ECHO
|
||||
ipywidgets.widgets.widget.JUPYTER_WIDGETS_ECHO = request.param
|
||||
yield request.param
|
||||
ipywidgets.widgets.widget.JUPYTER_WIDGETS_ECHO = oldvalue
|
||||
|
||||
#
|
||||
# First some widgets to test on:
|
||||
#
|
||||
|
||||
# A widget with simple traits (list + tuple to ensure both are handled)
|
||||
class SimpleWidget(Widget):
|
||||
a = Bool().tag(sync=True)
|
||||
b = Tuple(Bool(), Bool(), Bool(), default_value=(False, False, False)).tag(sync=True)
|
||||
c = List(Bool()).tag(sync=True)
|
||||
|
||||
|
||||
# A widget with various kinds of number traits
|
||||
class NumberWidget(Widget):
|
||||
f = Float().tag(sync=True)
|
||||
cf = CFloat().tag(sync=True)
|
||||
i = Int().tag(sync=True)
|
||||
ci = CInt().tag(sync=True)
|
||||
|
||||
|
||||
|
||||
# A widget where the data might be changed on reception:
|
||||
def transform_fromjson(data, widget):
|
||||
# Switch the two last elements when setting from json, if the first element is True
|
||||
# and always set first element to False
|
||||
if not data[0]:
|
||||
return data
|
||||
return [False] + data[1:-2] + [data[-1], data[-2]]
|
||||
|
||||
class TransformerWidget(Widget):
|
||||
d = List(Bool()).tag(sync=True, from_json=transform_fromjson)
|
||||
|
||||
|
||||
|
||||
# A widget that has a buffer:
|
||||
class DataInstance():
|
||||
def __init__(self, data=None):
|
||||
self.data = data
|
||||
|
||||
def mview_serializer(instance, widget):
|
||||
return { 'data': memoryview(instance.data) if instance.data else None }
|
||||
|
||||
def bytes_serializer(instance, widget):
|
||||
return { 'data': bytearray(memoryview(instance.data).tobytes()) if instance.data else None }
|
||||
|
||||
def deserializer(json_data, widget):
|
||||
return DataInstance( memoryview(json_data['data']).tobytes() if json_data else None )
|
||||
|
||||
class DataWidget(SimpleWidget):
|
||||
d = Instance(DataInstance).tag(sync=True, to_json=mview_serializer, from_json=deserializer)
|
||||
|
||||
# A widget that has a buffer that might be changed on reception:
|
||||
def truncate_deserializer(json_data, widget):
|
||||
return DataInstance( json_data['data'][:20].tobytes() if json_data else None )
|
||||
|
||||
class TruncateDataWidget(SimpleWidget):
|
||||
d = Instance(DataInstance).tag(sync=True, to_json=bytes_serializer, from_json=truncate_deserializer)
|
||||
|
||||
|
||||
#
|
||||
# Actual tests:
|
||||
#
|
||||
|
||||
def test_set_state_simple(echo):
|
||||
w = SimpleWidget()
|
||||
w.set_state(dict(
|
||||
a=True,
|
||||
b=[True, False, True],
|
||||
c=[False, True, False],
|
||||
))
|
||||
|
||||
assert len(w.comm.messages) == (1 if echo else 0)
|
||||
|
||||
|
||||
def test_set_state_transformer(echo):
|
||||
w = TransformerWidget()
|
||||
w.set_state(dict(
|
||||
d=[True, False, True]
|
||||
))
|
||||
# Since the deserialize step changes the state, this should send an update
|
||||
expected = []
|
||||
if echo:
|
||||
expected.append(
|
||||
((), dict(
|
||||
buffers=[],
|
||||
data=dict(
|
||||
buffer_paths=[],
|
||||
method='echo_update',
|
||||
state=dict(d=[True, False, True]),
|
||||
))))
|
||||
expected.append(
|
||||
((), dict(
|
||||
buffers=[],
|
||||
data=dict(
|
||||
buffer_paths=[],
|
||||
method='update',
|
||||
state=dict(d=[False, True, False]),
|
||||
))))
|
||||
assert w.comm.messages == expected
|
||||
|
||||
|
||||
def test_set_state_data(echo):
|
||||
w = DataWidget()
|
||||
data = memoryview(b'x'*30)
|
||||
w.set_state(dict(
|
||||
a=True,
|
||||
d={'data': data},
|
||||
))
|
||||
assert len(w.comm.messages) == (1 if echo else 0)
|
||||
|
||||
|
||||
def test_set_state_data_truncate(echo):
|
||||
w = TruncateDataWidget()
|
||||
data = memoryview(b'x'*30)
|
||||
w.set_state(dict(
|
||||
a=True,
|
||||
d={'data': data},
|
||||
))
|
||||
# Get message for checking
|
||||
assert len(w.comm.messages) == 2 if echo else 1 # ensure we didn't get more than expected
|
||||
msg = w.comm.messages[-1]
|
||||
# Assert that the data update (truncation) sends an update
|
||||
buffers = msg[1].pop('buffers')
|
||||
assert msg == ((), dict(
|
||||
data=dict(
|
||||
method='update',
|
||||
state=dict(d={}),
|
||||
buffer_paths=[['d', 'data']]
|
||||
)))
|
||||
|
||||
# Sanity:
|
||||
assert len(buffers) == 1
|
||||
assert buffers[0] == data[:20].tobytes()
|
||||
|
||||
|
||||
def test_set_state_numbers_int(echo):
|
||||
# JS does not differentiate between float/int.
|
||||
# Instead, it formats exact floats as ints in JSON (1.0 -> '1').
|
||||
|
||||
w = NumberWidget()
|
||||
# Set everything with ints
|
||||
w.set_state(dict(
|
||||
f = 1,
|
||||
cf = 2,
|
||||
i = 3,
|
||||
ci = 4,
|
||||
))
|
||||
# Ensure one update message gets produced
|
||||
assert len(w.comm.messages) == (1 if echo else 0)
|
||||
|
||||
|
||||
def test_set_state_numbers_float(echo):
|
||||
w = NumberWidget()
|
||||
# Set floats to int-like floats
|
||||
w.set_state(dict(
|
||||
f = 1.0,
|
||||
cf = 2.0,
|
||||
ci = 4.0
|
||||
))
|
||||
# Ensure one update message gets produced
|
||||
assert len(w.comm.messages) == (1 if echo else 0)
|
||||
|
||||
|
||||
def test_set_state_float_to_float(echo):
|
||||
w = NumberWidget()
|
||||
# Set floats to float
|
||||
w.set_state(dict(
|
||||
f = 1.2,
|
||||
cf = 2.6,
|
||||
))
|
||||
# Ensure one message gets produced
|
||||
assert len(w.comm.messages) == (1 if echo else 0)
|
||||
|
||||
|
||||
def test_set_state_cint_to_float(echo):
|
||||
w = NumberWidget()
|
||||
|
||||
# Set CInt to float
|
||||
w.set_state(dict(
|
||||
ci = 5.6
|
||||
))
|
||||
# Ensure an update message gets produced
|
||||
assert len(w.comm.messages) == (2 if echo else 1)
|
||||
msg = w.comm.messages[-1]
|
||||
data = msg[1]['data']
|
||||
assert data['method'] == 'update'
|
||||
assert data['state'] == {'ci': 5}
|
||||
|
||||
|
||||
# This test is disabled, meaning ipywidgets REQUIRES
|
||||
# any JSON received to format int-like numbers as ints
|
||||
def _x_test_set_state_int_to_int_like():
|
||||
# Note: Setting i to an int-like float will produce an
|
||||
# error, so if JSON producer were to always create
|
||||
# float formatted numbers, this would fail!
|
||||
|
||||
w = NumberWidget()
|
||||
# Set floats to int-like floats
|
||||
w.set_state(dict(
|
||||
i = 3.0
|
||||
))
|
||||
# Ensure no update message gets produced
|
||||
assert len(w.comm.messages) == 0
|
||||
|
||||
|
||||
def test_set_state_int_to_float(echo):
|
||||
w = NumberWidget()
|
||||
|
||||
# Set Int to float
|
||||
with pytest.raises(TraitError):
|
||||
w.set_state(dict(
|
||||
i = 3.5
|
||||
))
|
||||
|
||||
def test_property_lock(echo):
|
||||
# when this widget's value is set to 42, it sets itself to 2, and then back to 42 again (and then stops)
|
||||
class AnnoyingWidget(Widget):
|
||||
value = Float().tag(sync=True)
|
||||
stop = Bool(False)
|
||||
|
||||
@observe('value')
|
||||
def _propagate_value(self, change):
|
||||
print('_propagate_value', change.new)
|
||||
if self.stop:
|
||||
return
|
||||
if change.new == 42:
|
||||
self.value = 2
|
||||
if change.new == 2:
|
||||
self.stop = True
|
||||
self.value = 42
|
||||
|
||||
widget = AnnoyingWidget(value=1)
|
||||
assert widget.value == 1
|
||||
|
||||
widget._send = mock.MagicMock()
|
||||
# this mimics a value coming from the front end
|
||||
widget.set_state({'value': 42})
|
||||
assert widget.value == 42
|
||||
assert widget.stop is True
|
||||
|
||||
# we expect no new state to be sent
|
||||
calls = []
|
||||
widget._send.assert_has_calls(calls)
|
||||
|
||||
|
||||
def test_hold_sync(echo):
|
||||
# when this widget's value is set to 42, it sets the value to 2, and also sets a different trait value
|
||||
class AnnoyingWidget(Widget):
|
||||
value = Float().tag(sync=True)
|
||||
other = Float().tag(sync=True)
|
||||
|
||||
@observe('value')
|
||||
def _propagate_value(self, change):
|
||||
print('_propagate_value', change.new)
|
||||
if change.new == 42:
|
||||
with self.hold_sync():
|
||||
self.value = 2
|
||||
self.other = 11
|
||||
|
||||
widget = AnnoyingWidget(value=1)
|
||||
assert widget.value == 1
|
||||
|
||||
widget._send = mock.MagicMock()
|
||||
# this mimics a value coming from the front end
|
||||
widget.set_state({'value': 42})
|
||||
assert widget.value == 2
|
||||
assert widget.other == 11
|
||||
|
||||
msg = {'method': 'echo_update', 'state': {'value': 42.0}, 'buffer_paths': []}
|
||||
call42 = mock.call(msg, buffers=[])
|
||||
|
||||
msg = {'method': 'update', 'state': {'value': 2.0, 'other': 11.0}, 'buffer_paths': []}
|
||||
call2 = mock.call(msg, buffers=[])
|
||||
|
||||
calls = [call42, call2] if echo else [call2]
|
||||
widget._send.assert_has_calls(calls)
|
||||
|
||||
|
||||
def test_echo(echo):
|
||||
# we always echo values back to the frontend if configured
|
||||
class ValueWidget(Widget):
|
||||
value = Float().tag(sync=True)
|
||||
|
||||
widget = ValueWidget(value=1)
|
||||
assert widget.value == 1
|
||||
|
||||
widget._send = mock.MagicMock()
|
||||
# this mimics a value coming from the front end
|
||||
widget.set_state({'value': 42})
|
||||
assert widget.value == 42
|
||||
|
||||
# we expect this to be echoed
|
||||
msg = {'method': 'echo_update', 'state': {'value': 42.0}, 'buffer_paths': []}
|
||||
call42 = mock.call(msg, buffers=[])
|
||||
|
||||
calls = [call42] if echo else []
|
||||
widget._send.assert_has_calls(calls)
|
||||
|
||||
|
||||
|
||||
def test_echo_single(echo):
|
||||
# we always echo multiple changes back in 1 update
|
||||
class ValueWidget(Widget):
|
||||
value = Float().tag(sync=True)
|
||||
square = Float().tag(sync=True)
|
||||
@observe('value')
|
||||
def _square(self, change):
|
||||
self.square = self.value**2
|
||||
|
||||
widget = ValueWidget(value=1)
|
||||
assert widget.value == 1
|
||||
|
||||
widget._send = mock.MagicMock()
|
||||
# this mimics a value coming from the front end
|
||||
widget._handle_msg({
|
||||
'content': {
|
||||
'data': {
|
||||
'method': 'update',
|
||||
'state': {
|
||||
'value': 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
assert widget.value == 8
|
||||
assert widget.square == 64
|
||||
|
||||
# we expect this to be echoed
|
||||
# note that only value is echoed, not square
|
||||
msg = {'method': 'echo_update', 'state': {'value': 8.0}, 'buffer_paths': []}
|
||||
call = mock.call(msg, buffers=[])
|
||||
|
||||
msg = {'method': 'update', 'state': {'square': 64}, 'buffer_paths': []}
|
||||
call2 = mock.call(msg, buffers=[])
|
||||
|
||||
|
||||
calls = [call, call2] if echo else [call2]
|
||||
widget._send.assert_has_calls(calls)
|
||||
|
||||
|
||||
def test_no_echo(echo):
|
||||
# in cases where values coming from the frontend are 'heavy', we might want to opt out
|
||||
class ValueWidget(Widget):
|
||||
value = Float().tag(sync=True, echo_update=False)
|
||||
|
||||
widget = ValueWidget(value=1)
|
||||
assert widget.value == 1
|
||||
|
||||
widget._send = mock.MagicMock()
|
||||
# this mimics a value coming from the front end
|
||||
widget._handle_msg({
|
||||
'content': {
|
||||
'data': {
|
||||
'method': 'update',
|
||||
'state': {
|
||||
'value': 42,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
assert widget.value == 42
|
||||
|
||||
# widget._send.assert_not_called(calls)
|
||||
widget._send.assert_not_called()
|
||||
|
||||
# a regular set should sync to the frontend
|
||||
widget.value = 43
|
||||
widget._send.assert_has_calls([mock.call({'method': 'update', 'state': {'value': 43.0}, 'buffer_paths': []}, buffers=[])])
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user