##// END OF EJS Templates
s/model_name/_model_name
s/model_name/_model_name

File last commit:

r14896:c33b8414
r14896:c33b8414
Show More
widget.py
419 lines | 14.9 KiB | text/x-python | PythonLexer
Jonathan Frederic
Dev meeting widget review day 1
r14586 """Base Widget class. Allows user to create widgets in the back-end that render
in the IPython notebook front-end.
Jonathan Frederic
Cleaned up Python widget code.
r14283 """
#-----------------------------------------------------------------------------
# Copyright (c) 2013, the IPython Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file COPYING.txt, distributed with this software.
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
Jonathan Frederic
Implement a context manager as a property locking mechanism in Widget.
r14579 from contextlib import contextmanager
Jonathan Frederic
Added widget.py
r14223
Jonathan Frederic
Basic display logic...
r14229 from IPython.kernel.comm import Comm
from IPython.config import LoggingConfigurable
MinRK
review pass on Python-side of widgets...
r14793 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, Tuple
Jonathan Frederic
Updates to widget.py...
r14232 from IPython.utils.py3compat import string_types
Jonathan Frederic
Cleaned up Python widget code.
r14283 #-----------------------------------------------------------------------------
# Classes
#-----------------------------------------------------------------------------
Jonathan Frederic
Added new CallbackDispatcher class
r14658 class CallbackDispatcher(LoggingConfigurable):
MinRK
review pass on Python-side of widgets...
r14793 """A structure for registering and running callbacks"""
callbacks = List()
def __call__(self, *args, **kwargs):
"""Call all of the registered callbacks."""
Jonathan Frederic
Callback dispatcher return callback value....
r14721 value = None
MinRK
review pass on Python-side of widgets...
r14793 for callback in self.callbacks:
try:
local_value = callback(*args, **kwargs)
except Exception as e:
self.log.warn("Exception in callback %s: %s", callback, e)
else:
Jonathan Frederic
Callback dispatcher return callback value....
r14721 value = local_value if local_value is not None else value
return value
Jonathan Frederic
Added new CallbackDispatcher class
r14658
def register_callback(self, callback, remove=False):
"""(Un)Register a callback
Parameters
----------
callback: method handle
MinRK
review pass on Python-side of widgets...
r14793 Method to be registered or unregistered.
Jonathan Frederic
Added new CallbackDispatcher class
r14658 remove=False: bool
MinRK
review pass on Python-side of widgets...
r14793 Whether to unregister the callback."""
Jonathan Frederic
Added new CallbackDispatcher class
r14658 # (Un)Register the callback.
MinRK
review pass on Python-side of widgets...
r14793 if remove and callback in self.callbacks:
self.callbacks.remove(callback)
elif not remove and callback not in self.callbacks:
self.callbacks.append(callback)
Jonathan Frederic
Added widget.py
r14223
Jonathan Frederic
Added new CallbackDispatcher class
r14658
class Widget(LoggingConfigurable):
Jonathan Frederic
Reorganized attrs in widget.py
r14653 #-------------------------------------------------------------------------
# Class attributes
#-------------------------------------------------------------------------
MinRK
review pass on Python-side of widgets...
r14793 _widget_construction_callback = None
Jonathan Frederic
Dev meeting widget review day 1
r14586 widgets = {}
Jonathan Frederic
Added event for widget construction
r14478
MinRK
review pass on Python-side of widgets...
r14793 @staticmethod
Jonathan Frederic
Added event for widget construction
r14478 def on_widget_constructed(callback):
MinRK
review pass on Python-side of widgets...
r14793 """Registers a callback to be called when a widget is constructed.
Jonathan Frederic
More PEP8 changes
r14607
The callback must have the following signature:
Jonathan Frederic
Added event for widget construction
r14478 callback(widget)"""
MinRK
review pass on Python-side of widgets...
r14793 Widget._widget_construction_callback = callback
Jonathan Frederic
Added event for widget construction
r14478
MinRK
review pass on Python-side of widgets...
r14793 @staticmethod
Jonathan Frederic
s/_handle_widget_constructed/_call_widget_constructed
r14542 def _call_widget_constructed(widget):
MinRK
review pass on Python-side of widgets...
r14793 """Static method, called when a widget is constructed."""
if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
Widget._widget_construction_callback(widget)
Jonathan Frederic
Added event for widget construction
r14478
Jonathan Frederic
Reorganized attrs in widget.py
r14653 #-------------------------------------------------------------------------
# Traits
#-------------------------------------------------------------------------
Jonathan Frederic
s/model_name/_model_name
r14896 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
Jonathan Frederic
Dev meeting widget review day 1
r14586 registered in the front-end to create and sync this widget with.""")
Jonathan Frederic
s/view_name/_view_name
r14701 _view_name = Unicode(help="""Default view registered in the front-end
Jonathan Frederic
sync=True isntead of a keys list
r14588 to use to represent the widget.""", sync=True)
Jason Grout
Intermediate changes to javascript side of backbone widgets
r14486 _comm = Instance('IPython.kernel.comm.Comm')
MinRK
review pass on Python-side of widgets...
r14793
closed = Bool(False)
keys = List()
def _keys_default(self):
return [name for name in self.traits(sync=True)]
_property_lock = Tuple((None, None))
_display_callbacks = Instance(CallbackDispatcher, ())
_msg_callbacks = Instance(CallbackDispatcher, ())
Jonathan Frederic
Reorganized attrs in widget.py
r14653 #-------------------------------------------------------------------------
# (Con/de)structor
MinRK
review pass on Python-side of widgets...
r14793 #-------------------------------------------------------------------------
Jonathan Frederic
Allow parent to be set after construction......
r14310 def __init__(self, **kwargs):
Jonathan Frederic
More PEP8 changes
r14607 """Public constructor"""
Jonathan Frederic
s/Widget/DOMWidget s/BaseWidget/Widget
r14540 super(Widget, self).__init__(**kwargs)
Jonathan Frederic
Added event for widget construction
r14478
Jason Grout
Remove the automatic _children_attr and _children_lists_attr....
r14487 self.on_trait_change(self._handle_property_changed, self.keys)
Jonathan Frederic
s/_handle_widget_constructed/_call_widget_constructed
r14542 Widget._call_widget_constructed(self)
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485
Jonathan Frederic
Added widget.py
r14223 def __del__(self):
Jonathan Frederic
Cleaned up Python widget code.
r14283 """Object disposal"""
Jonathan Frederic
Added widget.py
r14223 self.close()
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485
Jonathan Frederic
Reorganized attrs in widget.py
r14653 #-------------------------------------------------------------------------
# Properties
MinRK
review pass on Python-side of widgets...
r14793 #-------------------------------------------------------------------------
Jonathan Frederic
Dev meeting widget review day 1
r14586
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485 @property
def comm(self):
Jonathan Frederic
Added doc strings to properties in widget.py
r14654 """Gets the Comm associated with this widget.
If a Comm doesn't exist yet, a Comm will be created automagically."""
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485 if self._comm is None:
Jonathan Frederic
Dev meeting widget review day 1
r14586 # Create a comm.
Jonathan Frederic
s/model_name/_model_name
r14896 self._comm = Comm(target_name=self._model_name)
Jonathan Frederic
Dev meeting widget review day 1
r14586 self._comm.on_msg(self._handle_msg)
self._comm.on_close(self._close)
Widget.widgets[self.model_id] = self
# first update
self.send_state()
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485 return self._comm
Jonathan Frederic
Re-decoupled comm_id from widget models
r14512
@property
def model_id(self):
Jonathan Frederic
Added doc strings to properties in widget.py
r14654 """Gets the model id of this widget.
If a Comm doesn't exist yet, a Comm will be created automagically."""
Jonathan Frederic
Fixed typo in model_id property
r14527 return self.comm.comm_id
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485
Jonathan Frederic
Reorganized attrs in widget.py
r14653 #-------------------------------------------------------------------------
# Methods
MinRK
review pass on Python-side of widgets...
r14793 #-------------------------------------------------------------------------
def _close(self):
"""Private close - cleanup objects, registry entries"""
del Widget.widgets[self.model_id]
self._comm = None
self.closed = True
Jonathan Frederic
Reorganized attrs in widget.py
r14653 def close(self):
MinRK
review pass on Python-side of widgets...
r14793 """Close method.
Jonathan Frederic
Updates to widget.py...
r14232
Jonathan Frederic
Reorganized attrs in widget.py
r14653 Closes the widget which closes the underlying comm.
When the comm is closed, all of the widget views are automatically
removed from the front-end."""
if not self.closed:
MinRK
review pass on Python-side of widgets...
r14793 self._comm.close()
Jonathan Frederic
Reorganized attrs in widget.py
r14653 self._close()
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485
Jonathan Frederic
Cleaned up Python widget code.
r14283 def send_state(self, key=None):
Jonathan Frederic
Dev meeting widget review day 1
r14586 """Sends the widget state, or a piece of it, to the front-end.
Jonathan Frederic
Cleaned up Python widget code.
r14283
Parameters
----------
key : unicode (optional)
Jonathan Frederic
Dev meeting widget review day 1
r14586 A single property's name to sync with the front-end.
Jonathan Frederic
Cleaned up Python widget code.
r14283 """
Jonathan Frederic
Halign dict colons
r14610 self._send({
"method" : "update",
"state" : self.get_state()
})
Jonathan Frederic
Cleaned up Python widget code.
r14283
Jason Grout
Intermediate changes to javascript side of backbone widgets
r14486 def get_state(self, key=None):
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485 """Gets the widget state, or a piece of it.
Jonathan Frederic
Cleaned up Python widget code.
r14283
Parameters
----------
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485 key : unicode (optional)
A single property's name to get.
Jonathan Frederic
Cleaned up Python widget code.
r14283 """
Jonathan Frederic
Dev meeting widget review day 1
r14586 keys = self.keys if key is None else [key]
return {k: self._pack_widgets(getattr(self, k)) for k in keys}
Jonathan Frederic
Cleaned up Python widget code.
r14283
Jonathan Frederic
Added support for custom widget msgs
r14387 def send(self, content):
"""Sends a custom msg to the widget model in the front-end.
Parameters
----------
content : dict
Content of the message to send.
"""
Jonathan Frederic
s/custom_content/content
r14655 self._send({"method": "custom", "content": content})
Jonathan Frederic
Added support for custom widget msgs
r14387
Jonathan Frederic
Dev meeting widget review day 1
r14586 def on_msg(self, callback, remove=False):
MinRK
review pass on Python-side of widgets...
r14793 """(Un)Register a custom msg receive callback.
Jonathan Frederic
Added support for custom widget msgs
r14387
Parameters
----------
MinRK
review pass on Python-side of widgets...
r14793 callback: callable
callback will be passed two arguments when a message arrives:
callback(widget, content)
Jonathan Frederic
Added support for custom widget msgs
r14387 remove: bool
True if the callback should be unregistered."""
Jonathan Frederic
Added new CallbackDispatcher class
r14658 self._msg_callbacks.register_callback(callback, remove=remove)
Jonathan Frederic
Added support for custom widget msgs
r14387
Jonathan Frederic
Added on_display callback
r14330 def on_displayed(self, callback, remove=False):
Jonathan Frederic
More PEP8 changes
r14607 """(Un)Register a widget displayed callback.
Jonathan Frederic
Added on_display callback
r14330
Jonathan Frederic
Fixed doc string comments, removed extra space
r14332 Parameters
----------
Jonathan Frederic
Added on_display callback
r14330 callback: method handler
MinRK
review pass on Python-side of widgets...
r14793 Must have a signature of:
callback(widget, **kwargs)
kwargs from display are passed through without modification.
Jonathan Frederic
Added on_display callback
r14330 remove: bool
True if the callback should be unregistered."""
Jonathan Frederic
Added new CallbackDispatcher class
r14658 self._display_callbacks.register_callback(callback, remove=remove)
Jonathan Frederic
Added on_display callback
r14330
Jonathan Frederic
Reorganized attrs in widget.py
r14653 #-------------------------------------------------------------------------
Jonathan Frederic
Cleaned up Python widget code.
r14283 # Support methods
Jonathan Frederic
Reorganized attrs in widget.py
r14653 #-------------------------------------------------------------------------
@contextmanager
Jonathan Frederic
Fixed name conflict with _property_lock
r14659 def _lock_property(self, key, value):
Jonathan Frederic
Reorganized attrs in widget.py
r14653 """Lock a property-value pair.
NOTE: This, in addition to the single lock for all state changes, is
flawed. In the future we may want to look into buffering state changes
back to the front-end."""
self._property_lock = (key, value)
try:
yield
finally:
self._property_lock = (None, None)
def _should_send_property(self, key, value):
"""Check the property lock (property_lock)"""
return key != self._property_lock[0] or \
value != self._property_lock[1]
# Event handlers
def _handle_msg(self, msg):
"""Called when a msg is received from the front-end"""
data = msg['content']['data']
method = data['method']
if not method in ['backbone', 'custom']:
self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
# Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
if method == 'backbone' and 'sync_data' in data:
sync_data = data['sync_data']
self._handle_receive_state(sync_data) # handles all methods
# Handle a custom msg from the front-end
elif method == 'custom':
Jonathan Frederic
s/custom_content/content
r14655 if 'content' in data:
self._handle_custom_msg(data['content'])
Jonathan Frederic
Reorganized attrs in widget.py
r14653
def _handle_receive_state(self, sync_data):
"""Called when a state is received from the front-end."""
for name in self.keys:
if name in sync_data:
value = self._unpack_widgets(sync_data[name])
Jonathan Frederic
Fixed name conflict with _property_lock
r14659 with self._lock_property(name, value):
Jonathan Frederic
Reorganized attrs in widget.py
r14653 setattr(self, name, value)
def _handle_custom_msg(self, content):
"""Called when a custom msg is received."""
MinRK
review pass on Python-side of widgets...
r14793 self._msg_callbacks(self, content)
Jonathan Frederic
Reorganized attrs in widget.py
r14653
def _handle_property_changed(self, name, old, new):
"""Called when a property has been changed."""
# Make sure this isn't information that the front-end just sent us.
if self._should_send_property(name, new):
# Send new state to front-end
self.send_state(key=name)
def _handle_displayed(self, **kwargs):
"""Called when a view has been displayed for this widget instance"""
MinRK
review pass on Python-side of widgets...
r14793 self._display_callbacks(self, **kwargs)
Jonathan Frederic
Reorganized attrs in widget.py
r14653
Jonathan Frederic
ict comprehension and list comprehension in pack/unpack widgets
r14656 def _pack_widgets(self, x):
Jonathan Frederic
Reorganized attrs in widget.py
r14653 """Recursively converts all widget instances to model id strings.
Children widgets will be stored and transmitted to the front-end by
Jonathan Frederic
Document in widget packing that vaues must be JSON-able.
r14657 their model ids. Return value must be JSON-able."""
Jonathan Frederic
ict comprehension and list comprehension in pack/unpack widgets
r14656 if isinstance(x, dict):
return {k: self._pack_widgets(v) for k, v in x.items()}
elif isinstance(x, list):
return [self._pack_widgets(v) for v in x]
elif isinstance(x, Widget):
return x.model_id
Jonathan Frederic
Reorganized attrs in widget.py
r14653 else:
Jonathan Frederic
Document in widget packing that vaues must be JSON-able.
r14657 return x # Value must be JSON-able
Jonathan Frederic
Reorganized attrs in widget.py
r14653
Jonathan Frederic
ict comprehension and list comprehension in pack/unpack widgets
r14656 def _unpack_widgets(self, x):
Jonathan Frederic
Reorganized attrs in widget.py
r14653 """Recursively converts all model id strings to widget instances.
Children widgets will be stored and transmitted to the front-end by
their model ids."""
Jonathan Frederic
ict comprehension and list comprehension in pack/unpack widgets
r14656 if isinstance(x, dict):
return {k: self._unpack_widgets(v) for k, v in x.items()}
elif isinstance(x, list):
return [self._unpack_widgets(v) for v in x]
elif isinstance(x, string_types):
return x if x not in Widget.widgets else Widget.widgets[x]
Jonathan Frederic
Reorganized attrs in widget.py
r14653 else:
Jonathan Frederic
ict comprehension and list comprehension in pack/unpack widgets
r14656 return x
Jonathan Frederic
Reorganized attrs in widget.py
r14653
Jonathan Frederic
Dev meeting widget review day 1
r14586 def _ipython_display_(self, **kwargs):
Jonathan Frederic
More PEP8 changes
r14607 """Called when `IPython.display.display` is called on the widget."""
Jonathan Frederic
Fixed _send so it can open a comm if needed....
r14548 # Show view. By sending a display message, the comm is opened and the
# initial state is sent.
Jonathan Frederic
Remove view_name from display
r14549 self._send({"method": "display"})
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485 self._handle_displayed(**kwargs)
Jonathan Frederic
Decoupled Python Widget from Comm...
r14479
def _send(self, msg):
Jonathan Frederic
More PEP8 changes
r14607 """Sends a message to the model in the front-end."""
Jonathan Frederic
Fixed _send so it can open a comm if needed....
r14548 self.comm.send(msg)
Jonathan Frederic
Moved view widget into widget.py
r14516
Jonathan Frederic
s/Widget/DOMWidget s/BaseWidget/Widget
r14540 class DOMWidget(Widget):
MinRK
review pass on Python-side of widgets...
r14793 visible = Bool(True, help="Whether the widget is visible.", sync=True)
Jonathan Frederic
Added sync= attr to DOMWidget
r14589 _css = Dict(sync=True) # Internal CSS property dict
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485
def get_css(self, key, selector=""):
Jonathan Frederic
Add a comment that explains the notion of the default element...
r14581 """Get a CSS property of the widget.
Note: This function does not actually request the CSS from the
front-end; Only properties that have been set with set_css can be read.
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485
Parameters
----------
key: unicode
CSS key
selector: unicode (optional)
JQuery selector used when the CSS key/value was set.
"""
if selector in self._css and key in self._css[selector]:
return self._css[selector][key]
else:
return None
MinRK
review pass on Python-side of widgets...
r14793 def set_css(self, dict_or_key, value=None, selector=''):
Jonathan Frederic
Add a comment that explains the notion of the default element...
r14581 """Set one or more CSS properties of the widget.
This function has two signatures:
Jonathan Frederic
Fixed comments for optional kwargs so they are redundant.
r14551 - set_css(css_dict, selector='')
- set_css(key, value, selector='')
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485
Parameters
----------
css_dict : dict
CSS key/value pairs to apply
key: unicode
CSS key
MinRK
review pass on Python-side of widgets...
r14793 value:
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485 CSS value
MinRK
review pass on Python-side of widgets...
r14793 selector: unicode (optional, kwarg only)
Jonathan Frederic
Add a comment that explains the notion of the default element...
r14581 JQuery selector to use to apply the CSS key/value. If no selector
is provided, an empty selector is used. An empty selector makes the
front-end try to apply the css to a default element. The default
element is an attribute unique to each view, which is a DOM element
of the view that should be styled with common CSS (see
`$el_to_style` in the Javascript code).
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485 """
Jonathan Frederic
Fixed *almost* all of the test-detected bugs
r14596 if not selector in self._css:
self._css[selector] = {}
MinRK
review pass on Python-side of widgets...
r14793 my_css = self._css[selector]
if value is None:
css_dict = dict_or_key
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485 else:
MinRK
review pass on Python-side of widgets...
r14793 css_dict = {dict_or_key: value}
for (key, value) in css_dict.items():
if not (key in my_css and value == my_css[key]):
my_css[key] = value
self.send_state('_css')
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485
Jonathan Frederic
add/remove_class now can accept a list of classes
r14539 def add_class(self, class_names, selector=""):
Jonathan Frederic
More PEP8 changes
r14607 """Add class[es] to a DOM element.
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485
Parameters
----------
Jonathan Frederic
add/remove_class now can accept a list of classes
r14539 class_names: unicode or list
Class name(s) to add to the DOM element(s).
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485 selector: unicode (optional)
JQuery selector to select the DOM element(s) that the class(es) will
be added to.
"""
Jonathan Frederic
add/remove_class now can accept a list of classes
r14539 class_list = class_names
Jonathan Frederic
Many checks off the todo list, test fixes
r14583 if isinstance(class_list, list):
Jonathan Frederic
add/remove_class now can accept a list of classes
r14539 class_list = ' '.join(class_list)
Jonathan Frederic
Halign dict colons
r14610 self.send({
"msg_type" : "add_class",
"class_list" : class_list,
"selector" : selector
})
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485
Jonathan Frederic
add/remove_class now can accept a list of classes
r14539 def remove_class(self, class_names, selector=""):
Jonathan Frederic
More PEP8 changes
r14607 """Remove class[es] from a DOM element.
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485
Parameters
----------
Jonathan Frederic
add/remove_class now can accept a list of classes
r14539 class_names: unicode or list
Class name(s) to remove from the DOM element(s).
Jason Grout
Separate the display from the models on the python side, creating a BaseWidget class....
r14485 selector: unicode (optional)
JQuery selector to select the DOM element(s) that the class(es) will
be removed from.
"""
Jonathan Frederic
add/remove_class now can accept a list of classes
r14539 class_list = class_names
Jonathan Frederic
Many checks off the todo list, test fixes
r14583 if isinstance(class_list, list):
Jonathan Frederic
add/remove_class now can accept a list of classes
r14539 class_list = ' '.join(class_list)
Jonathan Frederic
Halign dict colons
r14610 self.send({
"msg_type" : "remove_class",
"class_list" : class_list,
"selector" : selector,
})