class DOMWidget(Widget): visible = Bool(True, allow_none=True, help="Whether the widget is visible. False collapses the empty space, while None preserves the empty space.", sync=True) _css = Tuple(sync=True, help="CSS property list: (selector, key, value)") _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.") width = CUnicode(sync=True) height = CUnicode(sync=True) # A default padding of 2.5 px makes the widgets look nice when displayed inline. padding = CUnicode("2.5px", sync=True) margin = CUnicode(sync=True) color = Unicode(sync=True) background_color = Unicode(sync=True) border_color = Unicode(sync=True) border_width = CUnicode(sync=True) border_radius = CUnicode(sync=True) border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp 'none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset', 'initial', 'inherit', ''], default_value='', sync=True) font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp 'normal', 'italic', 'oblique', 'initial', 'inherit', ''], default_value='', sync=True) font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp 'normal', 'bold', 'bolder', 'lighter', 'initial', 'inherit', ''] + [str(100 * (i+1)) for i in range(9)], default_value='', sync=True) font_size = CUnicode(sync=True) font_family = Unicode(sync=True) def __init__(self, *pargs, **kwargs): super(DOMWidget, self).__init__(*pargs, **kwargs) def _validate_border(name, old, new): if new is not None and new != '': if name != 'border_width' and not self.border_width: self.border_width = 1 if name != 'border_style' and self.border_style == '': self.border_style = 'solid' self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
class ContainerWidget(DOMWidget): _view_name = Unicode('ContainerView', sync=True) # Child widgets in the container. # Using a tuple here to force reassignment to update the list. # When a proper notifying-list trait exists, that is what should be used here. children = Tuple() _children = Tuple(sync=True) def _children_changed(self, name, old, new): """Validate children list. Makes sure only one instance of any given model can exist in the children list. An excellent post on uniqifiers is available at http://www.peterbe.com/plog/uniqifiers-benchmark which provides the inspiration for using this implementation. Below I've implemented the `f5` algorithm using Python comprehensions.""" if new is not None: seen = {} def add_item(i): seen[i.model_id] = True return i self._children = [ add_item(i) for i in new if not i.model_id in seen ]
class _MultipleSelection(_Selection): """Base class for MultipleSelection widgets. As with ``_Selection``, ``options`` can be specified as a list or dict. If given as a list, it will be transformed to a dict of the form ``{str(value): value}``. Despite their names, ``value`` (and ``selected_label``) will be tuples, even if only a single option is selected. """ value = Tuple(help="Selected values") selected_labels = Tuple(help="The labels of the selected options", sync=True) @property def selected_label(self): raise AttributeError( "Does not support selected_label, use selected_labels") def _value_in_options(self): # ensure that the chosen value is one of the choices if self.options: old_value = self.value or [] new_value = [] for value in old_value: if value in self._options_dict.values(): new_value.append(value) if new_value: self.value = new_value else: self.value = [next(iter(self._options_dict.values()))] def _value_changed(self, name, old, new): """Called when value has been changed""" if self.value_lock.acquire(False): try: self.selected_labels = [ self._options_labels[self._options_values.index(v)] for v in new ] except: self.value = old raise KeyError(new) finally: self.value_lock.release() def _selected_labels_changed(self, name, old, new): """Called when the selected label has been changed (typically by the frontend).""" if self.value_lock.acquire(False): try: self.value = [self._options_dict[name] for name in new] finally: self.value_lock.release()
class Horizons(InstallerMixin, GraphMixin): _view_name = Unicode('HorizonsView', sync=True) _view_module = Unicode('/nbextensions/nbmonty/js/widget-horizons.js', sync=True) nodes = Tuple([], sync=True) title = Unicode("Horizons", sync=True) x_label = Unicode("Time", sync=True) x_scale = Tuple([ datetime.datetime.today().isoformat(), ( datetime.datetime.today() + datetime.timedelta(weeks=DEFAULT_MAX_HORIZON) ).isoformat() ], sync=True) y_label = Unicode("Value", sync=True) y_scale = Tuple([0, 100], sync=True) context = Dict({ "rdfs": str(RDFS), "aog": str(AOG), "xsd": str(XSD), "name": "rdfs:label", "x": { "@id": "aog:timeOfAttention", "@type": "xsd:date" }, "y": "aog:growthInValue" }) def __init__(self, *args, **kwargs): super(Horizons, self).__init__(*args, **kwargs) self.width = self.width or "100%" self.height = self.height or 500 def _ld_changed(self, name, old, new): super(Horizons, self)._ld_changed(name, old, new) try: node_ld = self.graph.serialize( format="json-ld", indent=2, context=self.context ).decode('utf-8') self.nodes = json.loads(node_ld)["@graph"] except Exception as err: print(err)
class Box(DOMWidget): """Displays multiple widgets in a group.""" _view_name = Unicode('BoxView', sync=True) # Child widgets in the container. # Using a tuple here to force reassignment to update the list. # When a proper notifying-list trait exists, that is what should be used here. children = Tuple(sync=True, allow_none=False) _overflow_values = ['visible', 'hidden', 'scroll', 'auto', 'initial', 'inherit', ''] overflow_x = CaselessStrEnum( values=_overflow_values, default_value='', allow_none=False, sync=True, help="""Specifies what happens to content that is too large for the rendered region.""") overflow_y = CaselessStrEnum( values=_overflow_values, default_value='', allow_none=False, sync=True, help="""Specifies what happens to content that is too large for the rendered region.""") box_style = CaselessStrEnum( values=['success', 'info', 'warning', 'danger', ''], default_value='', allow_none=True, sync=True, help="""Use a predefined styling for the box.""") def __init__(self, children = (), **kwargs): kwargs['children'] = children super(Box, self).__init__(**kwargs) self.on_displayed(Box._fire_children_displayed) def _fire_children_displayed(self): for child in self.children: child._handle_displayed()
class _IntRange(_Int): value = Tuple(CInt, CInt, default_value=(0, 1), help="Tuple of (lower, upper) bounds", sync=True) lower = CInt(0, help="Lower bound", sync=False) upper = CInt(1, help="Upper bound", sync=False) def __init__(self, *pargs, **kwargs): value_given = 'value' in kwargs lower_given = 'lower' in kwargs upper_given = 'upper' in kwargs if value_given and (lower_given or upper_given): raise ValueError("Cannot specify both 'value' and 'lower'/'upper' for range widget") if lower_given != upper_given: raise ValueError("Must specify both 'lower' and 'upper' for range widget") super(_IntRange, self).__init__(*pargs, **kwargs) # ensure the traits match, preferring whichever (if any) was given in kwargs if value_given: self.lower, self.upper = self.value else: self.value = (self.lower, self.upper) self.on_trait_change(self._validate, ['value', 'upper', 'lower']) def _validate(self, name, old, new): if name == 'value': self.lower, self.upper = min(new), max(new) elif name == 'lower': self.value = (new, self.value[1]) elif name == 'upper': self.value = (self.value[0], new)
class Link(Widget): """Link Widget""" _model_name = Unicode('LinkModel', sync=True) widgets = Tuple(sync=True, allow_none=False) def __init__(self, widgets=(), **kwargs): kwargs['widgets'] = widgets super(Link, self).__init__(**kwargs) # for compatibility with traitlet links def unlink(self): self.close()
class ContainerWidget(DOMWidget): _view_name = Unicode('ContainerView', sync=True) # Child widgets in the container. # Using a tuple here to force reassignment to update the list. # When a proper notifying-list trait exists, that is what should be used here. children = Tuple(sync=True) def __init__(self, **kwargs): super(ContainerWidget, self).__init__(**kwargs) self.on_displayed(ContainerWidget._fire_children_displayed) def _fire_children_displayed(self): for child in self.children: child._handle_displayed()
class DirectionalLink(Widget): """Directional Link Widget""" _model_name = Unicode('DirectionalLinkModel', sync=True) targets = Any(sync=True) source = Tuple(sync=True) # Does not quite behave like other widgets but reproduces # the behavior of IPython.utils.traitlets.directional_link def __init__(self, source, targets=(), **kwargs): kwargs['source'] = source kwargs['targets'] = targets super(DirectionalLink, self).__init__(**kwargs) # for compatibility with traitlet links def unlink(self): self.close()
class Box(DOMWidget): """Displays multiple widgets in a group.""" _view_name = Unicode('BoxView', sync=True) # Child widgets in the container. # Using a tuple here to force reassignment to update the list. # When a proper notifying-list trait exists, that is what should be used here. children = Tuple(sync=True, allow_none=False) def __init__(self, children=(), **kwargs): kwargs['children'] = children super(Box, self).__init__(**kwargs) self.on_displayed(Box._fire_children_displayed) def _fire_children_displayed(self): for child in self.children: child._handle_displayed()
class FloatRangeWidget(DOMWidget): _view_name = Unicode('FloatRangeWidget', sync=True) value = Tuple(CFloat, CFloat, default_value=(0.0, 1.0), help="Tuple of (lower, upper) bounds", sync=True) min = CFloat(sync=True) max = CFloat(sync=True) step = CFloat(sync=True) value_min = CFloat(sync=True) value_max = CFloat(sync=True) description = Unicode(sync=True) def __init__(self, min=0.0, max=1.0, step=0.1, value_min=0.0, value_max=1.0): super(FloatRangeWidget, self).__init__() self.min = min self.max = max self.step = step self.value_min = value_min self.value_max = value_max self.value = (self.value_min, self.value_max) self.on_trait_change(self.on_value_max_change, "value_max") self.on_trait_change(self.on_value_min_change, "value_min") def on_value_max_change(self, name, old, new): self.value = (self.value_min, self.value_max) def on_value_min_change(self, name, old, new): self.value = (self.value_min, self.value_max)
def tangle(*args, **kwargs): """ Shortcut to create a new, custom Tangle model. Use instead of directly subclassing `Tangle`. A new, custom Widget class is created, with each of `kwargs` as a traitlet. Returns an instance of the new class with default values. `kwargs` options - primitive types (int, bool, float) will be created as casting versions (`CInt`, `CBool`, `CFloat`) - a `list` will be created as an `Enum` - a `Widget` instance will create a link to that widget's `value` - a `tuple` `(widget_instance, "traitlet")` will create a `link` - functions will be `inspect`ed to find their argument names subscribed for update... this uses `inspect`, won't work with `*` magic - a `tuple` `(function, default)` will be created as the type (as above) """ class_attrs = {"_links": [], "_dlinks": [], "_derived": {}} for value in args: if isinstance(value, function): # we'll just go ahead and assume this was made by `interact` if hasattr(value, "widget") and hasattr(value.widget, "children"): for child in value.widget.children: _link_widget(child.description, child, class_attrs) for key, value in kwargs.items(): traitlet_cls = _get_primitive(value) traitlet_args = [value] traitlet_kwargs = {"sync": True} handled = False if traitlet_cls is not None: pass elif isinstance(value, list): traitlet_cls = Any traitlet_args = [value[0]] class_attrs["{}_options".format(key)] = Tuple(value, sync=True) elif isinstance(value, Widget): _link_widget(key, value, class_attrs) handled = True elif isinstance(value, tuple): if isinstance(value[0], Widget): widget, traitlet = value widget_cls = widget.__class__ traitlet_args = [] traitlet_cls = getattr(widget_cls, traitlet).__class__ class_attrs["_links"].append((key, value)) elif hasattr(value[1], "__call__"): example, fn = value traitlet_args = [example] traitlet_cls = _get_primitive(example) subscribed = inspect.getargspec(fn).args class_attrs["_derived"][key] = (fn, subscribed) if not handled: if traitlet_cls is None: raise ValueError("Didn't understand {}: {}".format(key, value)) class_attrs[key] = traitlet_cls(*traitlet_args, **traitlet_kwargs) new_class = type('DynamicAutoTangle{}'.format(id(class_attrs)), (AutoTangle, ), class_attrs) inst = new_class() return inst._refresh()
class HubFactory(RegistrationFactory): """The Configurable for setting up a Hub.""" # port-pairs for monitoredqueues: hb = Tuple(Integer,Integer,config=True, help="""XREQ/SUB Port pair for Engine heartbeats""") def _hb_default(self): return tuple(util.select_random_ports(2)) mux = Tuple(Integer,Integer,config=True, help="""Engine/Client Port pair for MUX queue""") def _mux_default(self): return tuple(util.select_random_ports(2)) task = Tuple(Integer,Integer,config=True, help="""Engine/Client Port pair for Task queue""") def _task_default(self): return tuple(util.select_random_ports(2)) control = Tuple(Integer,Integer,config=True, help="""Engine/Client Port pair for Control queue""") def _control_default(self): return tuple(util.select_random_ports(2)) iopub = Tuple(Integer,Integer,config=True, help="""Engine/Client Port pair for IOPub relay""") def _iopub_default(self): return tuple(util.select_random_ports(2)) # single ports: mon_port = Integer(config=True, help="""Monitor (SUB) port for queue traffic""") def _mon_port_default(self): return util.select_random_ports(1)[0] notifier_port = Integer(config=True, help="""PUB port for sending engine status notifications""") def _notifier_port_default(self): return util.select_random_ports(1)[0] engine_ip = Unicode('127.0.0.1', config=True, help="IP on which to listen for engine connections. [default: loopback]") engine_transport = Unicode('tcp', config=True, help="0MQ transport for engine connections. [default: tcp]") client_ip = Unicode('127.0.0.1', config=True, help="IP on which to listen for client connections. [default: loopback]") client_transport = Unicode('tcp', config=True, help="0MQ transport for client connections. [default : tcp]") monitor_ip = Unicode('127.0.0.1', config=True, help="IP on which to listen for monitor messages. [default: loopback]") monitor_transport = Unicode('tcp', config=True, help="0MQ transport for monitor messages. [default : tcp]") monitor_url = Unicode('') db_class = DottedObjectName('IPython.parallel.controller.dictdb.DictDB', config=True, help="""The class to use for the DB backend""") # not configurable db = Instance('IPython.parallel.controller.dictdb.BaseDB') heartmonitor = Instance('IPython.parallel.controller.heartmonitor.HeartMonitor') def _ip_changed(self, name, old, new): self.engine_ip = new self.client_ip = new self.monitor_ip = new self._update_monitor_url() def _update_monitor_url(self): self.monitor_url = "%s://%s:%i" % (self.monitor_transport, self.monitor_ip, self.mon_port) def _transport_changed(self, name, old, new): self.engine_transport = new self.client_transport = new self.monitor_transport = new self._update_monitor_url() def __init__(self, **kwargs): super(HubFactory, self).__init__(**kwargs) self._update_monitor_url() def construct(self): self.init_hub() def start(self): self.heartmonitor.start() self.log.info("Heartmonitor started") def init_hub(self): """construct""" client_iface = "%s://%s:" % (self.client_transport, self.client_ip) + "%i" engine_iface = "%s://%s:" % (self.engine_transport, self.engine_ip) + "%i" ctx = self.context loop = self.loop # Registrar socket q = ZMQStream(ctx.socket(zmq.ROUTER), loop) q.bind(client_iface % self.regport) self.log.info("Hub listening on %s for registration.", client_iface % self.regport) if self.client_ip != self.engine_ip: q.bind(engine_iface % self.regport) self.log.info("Hub listening on %s for registration.", engine_iface % self.regport) ### Engine connections ### # heartbeat hpub = ctx.socket(zmq.PUB) hpub.bind(engine_iface % self.hb[0]) hrep = ctx.socket(zmq.ROUTER) hrep.bind(engine_iface % self.hb[1]) self.heartmonitor = HeartMonitor(loop=loop, config=self.config, log=self.log, pingstream=ZMQStream(hpub,loop), pongstream=ZMQStream(hrep,loop) ) ### Client connections ### # Notifier socket n = ZMQStream(ctx.socket(zmq.PUB), loop) n.bind(client_iface%self.notifier_port) ### build and launch the queues ### # monitor socket sub = ctx.socket(zmq.SUB) sub.setsockopt(zmq.SUBSCRIBE, b"") sub.bind(self.monitor_url) sub.bind('inproc://monitor') sub = ZMQStream(sub, loop) # connect the db self.log.info('Hub using DB backend: %r'%(self.db_class.split()[-1])) # cdir = self.config.Global.cluster_dir self.db = import_item(str(self.db_class))(session=self.session.session, config=self.config, log=self.log) time.sleep(.25) try: scheme = self.config.TaskScheduler.scheme_name except AttributeError: from .scheduler import TaskScheduler scheme = TaskScheduler.scheme_name.get_default_value() # build connection dicts self.engine_info = { 'control' : engine_iface%self.control[1], 'mux': engine_iface%self.mux[1], 'heartbeat': (engine_iface%self.hb[0], engine_iface%self.hb[1]), 'task' : engine_iface%self.task[1], 'iopub' : engine_iface%self.iopub[1], # 'monitor' : engine_iface%self.mon_port, } self.client_info = { 'control' : client_iface%self.control[0], 'mux': client_iface%self.mux[0], 'task' : (scheme, client_iface%self.task[0]), 'iopub' : client_iface%self.iopub[0], 'notification': client_iface%self.notifier_port } self.log.debug("Hub engine addrs: %s", self.engine_info) self.log.debug("Hub client addrs: %s", self.client_info) # resubmit stream r = ZMQStream(ctx.socket(zmq.DEALER), loop) url = util.disambiguate_url(self.client_info['task'][-1]) r.setsockopt(zmq.IDENTITY, self.session.bsession) r.connect(url) self.hub = Hub(loop=loop, session=self.session, monitor=sub, heartmonitor=self.heartmonitor, query=q, notifier=n, resubmit=r, db=self.db, engine_info=self.engine_info, client_info=self.client_info, log=self.log)
class Widget(LoggingConfigurable): #------------------------------------------------------------------------- # Class attributes #------------------------------------------------------------------------- _widget_construction_callback = None widgets = {} @staticmethod def on_widget_constructed(callback): """Registers a callback to be called when a widget is constructed. The callback must have the following signature: callback(widget)""" Widget._widget_construction_callback = callback @staticmethod def _call_widget_constructed(widget): """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) #------------------------------------------------------------------------- # Traits #------------------------------------------------------------------------- model_name = Unicode('WidgetModel', help="""Name of the backbone model registered in the front-end to create and sync this widget with.""") _view_name = Unicode(help="""Default view registered in the front-end to use to represent the widget.""", sync=True) _comm = Instance('IPython.kernel.comm.Comm') 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, ()) #------------------------------------------------------------------------- # (Con/de)structor #------------------------------------------------------------------------- def __init__(self, **kwargs): """Public constructor""" super(Widget, self).__init__(**kwargs) self.on_trait_change(self._handle_property_changed, self.keys) Widget._call_widget_constructed(self) def __del__(self): """Object disposal""" self.close() #------------------------------------------------------------------------- # Properties #------------------------------------------------------------------------- @property def comm(self): """Gets the Comm associated with this widget. If a Comm doesn't exist yet, a Comm will be created automagically.""" if self._comm is None: # Create a comm. self._comm = Comm(target_name=self.model_name) self._comm.on_msg(self._handle_msg) self._comm.on_close(self._close) Widget.widgets[self.model_id] = self # first update self.send_state() return self._comm @property def model_id(self): """Gets the model id of this widget. If a Comm doesn't exist yet, a Comm will be created automagically.""" return self.comm.comm_id #------------------------------------------------------------------------- # Methods #------------------------------------------------------------------------- def _close(self): """Private close - cleanup objects, registry entries""" del Widget.widgets[self.model_id] self._comm = None self.closed = True def close(self): """Close method. 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: self._comm.close() self._close() def send_state(self, key=None): """Sends the widget state, or a piece of it, to the front-end. Parameters ---------- key : unicode (optional) A single property's name to sync with the front-end. """ self._send({"method": "update", "state": self.get_state()}) def get_state(self, key=None): """Gets the widget state, or a piece of it. Parameters ---------- key : unicode (optional) A single property's name to get. """ keys = self.keys if key is None else [key] return {k: self._pack_widgets(getattr(self, k)) for k in keys} 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. """ self._send({"method": "custom", "content": content}) def on_msg(self, callback, remove=False): """(Un)Register a custom msg receive callback. Parameters ---------- callback: callable callback will be passed two arguments when a message arrives:: callback(widget, content) remove: bool True if the callback should be unregistered.""" self._msg_callbacks.register_callback(callback, remove=remove) def on_displayed(self, callback, remove=False): """(Un)Register a widget displayed callback. Parameters ---------- callback: method handler Must have a signature of:: callback(widget, **kwargs) kwargs from display are passed through without modification. remove: bool True if the callback should be unregistered.""" self._display_callbacks.register_callback(callback, remove=remove) #------------------------------------------------------------------------- # Support methods #------------------------------------------------------------------------- @contextmanager def _lock_property(self, key, value): """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': if 'content' in data: self._handle_custom_msg(data['content']) 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]) with self._lock_property(name, value): setattr(self, name, value) def _handle_custom_msg(self, content): """Called when a custom msg is received.""" self._msg_callbacks(self, content) 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""" self._display_callbacks(self, **kwargs) def _pack_widgets(self, x): """Recursively converts all widget instances to model id strings. Children widgets will be stored and transmitted to the front-end by their model ids. Return value must be JSON-able.""" 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 else: return x # Value must be JSON-able def _unpack_widgets(self, x): """Recursively converts all model id strings to widget instances. Children widgets will be stored and transmitted to the front-end by their model ids.""" 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] else: return x def _ipython_display_(self, **kwargs): """Called when `IPython.display.display` is called on the widget.""" # Show view. By sending a display message, the comm is opened and the # initial state is sent. self._send({"method": "display"}) self._handle_displayed(**kwargs) def _send(self, msg): """Sends a message to the model in the front-end.""" self.comm.send(msg)
class Widget(LoggingConfigurable): #------------------------------------------------------------------------- # Class attributes #------------------------------------------------------------------------- _widget_construction_callback = None widgets = {} widget_types = {} @staticmethod def on_widget_constructed(callback): """Registers a callback to be called when a widget is constructed. The callback must have the following signature: callback(widget)""" Widget._widget_construction_callback = callback @staticmethod def _call_widget_constructed(widget): """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) @staticmethod def handle_comm_opened(comm, msg): """Static method, called when a widget is constructed.""" widget_class = import_item(msg['content']['data']['widget_class']) widget = widget_class(comm=comm) #------------------------------------------------------------------------- # Traits #------------------------------------------------------------------------- _model_module = Unicode(None, allow_none=True, help="""A requirejs module name in which to find _model_name. If empty, look in the global registry.""") _model_name = Unicode('WidgetModel', help="""Name of the backbone model registered in the front-end to create and sync this widget with.""") _view_module = Unicode(help="""A requirejs module in which to find _view_name. If empty, look in the global registry.""", sync=True) _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end to use to represent the widget.""", sync=True) comm = Instance('IPython.kernel.comm.Comm') msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the front-end can send before receiving an idle msg from the back-end.""") version = Int(0, sync=True, help="""Widget's version""") keys = List() def _keys_default(self): return [name for name in self.traits(sync=True)] _property_lock = Tuple((None, None)) _send_state_lock = Int(0) _states_to_send = Set(allow_none=False) _display_callbacks = Instance(CallbackDispatcher, ()) _msg_callbacks = Instance(CallbackDispatcher, ()) #------------------------------------------------------------------------- # (Con/de)structor #------------------------------------------------------------------------- def __init__(self, **kwargs): """Public constructor""" self._model_id = kwargs.pop('model_id', None) super(Widget, self).__init__(**kwargs) Widget._call_widget_constructed(self) self.open() def __del__(self): """Object disposal""" self.close() #------------------------------------------------------------------------- # Properties #------------------------------------------------------------------------- def open(self): """Open a comm to the frontend if one isn't already open.""" if self.comm is None: args = dict(target_name='ipython.widget', data={'model_name': self._model_name, 'model_module': self._model_module}) if self._model_id is not None: args['comm_id'] = self._model_id self.comm = Comm(**args) def _comm_changed(self, name, new): """Called when the comm is changed.""" if new is None: return self._model_id = self.model_id self.comm.on_msg(self._handle_msg) Widget.widgets[self.model_id] = self # first update self.send_state() @property def model_id(self): """Gets the model id of this widget. If a Comm doesn't exist yet, a Comm will be created automagically.""" return self.comm.comm_id #------------------------------------------------------------------------- # Methods #------------------------------------------------------------------------- def close(self): """Close method. Closes the underlying comm. When the comm is closed, all of the widget views are automatically removed from the front-end.""" if self.comm is not None: Widget.widgets.pop(self.model_id, None) self.comm.close() self.comm = None def send_state(self, key=None): """Sends the widget state, or a piece of it, to the front-end. Parameters ---------- key : unicode, or iterable (optional) A single property's name or iterable of property names to sync with the front-end. """ self._send({ "method" : "update", "state" : self.get_state(key=key) }) def get_state(self, key=None): """Gets the widget state, or a piece of it. Parameters ---------- key : unicode or iterable (optional) A single property's name or iterable of property names to get. """ if key is None: keys = self.keys elif isinstance(key, string_types): keys = [key] elif isinstance(key, collections.Iterable): keys = key else: raise ValueError("key must be a string, an iterable of keys, or None") state = {} for k in keys: f = self.trait_metadata(k, 'to_json', self._trait_to_json) value = getattr(self, k) state[k] = f(value) return state def set_state(self, sync_data): """Called when a state is received from the front-end.""" for name in self.keys: if name in sync_data: json_value = sync_data[name] from_json = self.trait_metadata(name, 'from_json', self._trait_from_json) with self._lock_property(name, json_value): setattr(self, name, from_json(json_value)) 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. """ self._send({"method": "custom", "content": content}) def on_msg(self, callback, remove=False): """(Un)Register a custom msg receive callback. Parameters ---------- callback: callable callback will be passed two arguments when a message arrives:: callback(widget, content) remove: bool True if the callback should be unregistered.""" self._msg_callbacks.register_callback(callback, remove=remove) def on_displayed(self, callback, remove=False): """(Un)Register a widget displayed callback. Parameters ---------- callback: method handler Must have a signature of:: callback(widget, **kwargs) kwargs from display are passed through without modification. remove: bool True if the callback should be unregistered.""" self._display_callbacks.register_callback(callback, remove=remove) #------------------------------------------------------------------------- # Support methods #------------------------------------------------------------------------- @contextmanager def _lock_property(self, key, value): """Lock a property-value pair. The value should be the JSON state of the property. 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) @contextmanager def hold_sync(self): """Hold syncing any state until the context manager is released""" # We increment a value so that this can be nested. Syncing will happen when # all levels have been released. self._send_state_lock += 1 try: yield finally: self._send_state_lock -=1 if self._send_state_lock == 0: self.send_state(self._states_to_send) self._states_to_send.clear() def _should_send_property(self, key, value): """Check the property lock (property_lock)""" to_json = self.trait_metadata(key, 'to_json', self._trait_to_json) if (key == self._property_lock[0] and to_json(value) == self._property_lock[1]): return False elif self._send_state_lock > 0: self._states_to_send.add(key) return False else: return True # Event handlers @_show_traceback def _handle_msg(self, msg): """Called when a msg is received from the front-end""" data = msg['content']['data'] method = data['method'] # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one. if method == 'backbone': if 'sync_data' in data: sync_data = data['sync_data'] self.set_state(sync_data) # handles all methods # Handle a state request. elif method == 'request_state': self.send_state() # Handle a custom msg from the front-end. elif method == 'custom': if 'content' in data: self._handle_custom_msg(data['content']) # Catch remainder. else: self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method) def _handle_custom_msg(self, content): """Called when a custom msg is received.""" self._msg_callbacks(self, content) def _notify_trait(self, name, old_value, new_value): """Called when a property has been changed.""" # Trigger default traitlet callback machinery. This allows any user # registered validation to be processed prior to allowing the widget # machinery to handle the state. LoggingConfigurable._notify_trait(self, name, old_value, new_value) # Send the state after the user registered callbacks for trait changes # have all fired (allows for user to validate values). if self.comm is not None and name in self.keys: # Make sure this isn't information that the front-end just sent us. if self._should_send_property(name, new_value): # 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""" self._display_callbacks(self, **kwargs) def _trait_to_json(self, x): """Convert a trait value to json Traverse lists/tuples and dicts and serialize their values as well. Replace any widgets with their model_id """ if isinstance(x, dict): return {k: self._trait_to_json(v) for k, v in x.items()} elif isinstance(x, (list, tuple)): return [self._trait_to_json(v) for v in x] elif isinstance(x, Widget): return "IPY_MODEL_" + x.model_id else: return x # Value must be JSON-able def _trait_from_json(self, x): """Convert json values to objects Replace any strings representing valid model id values to Widget references. """ if isinstance(x, dict): return {k: self._trait_from_json(v) for k, v in x.items()} elif isinstance(x, (list, tuple)): return [self._trait_from_json(v) for v in x] elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets: # we want to support having child widgets at any level in a hierarchy # trusting that a widget UUID will not appear out in the wild return Widget.widgets[x[10:]] else: return x def _ipython_display_(self, **kwargs): """Called when `IPython.display.display` is called on the widget.""" # Show view. if self._view_name is not None: self._send({"method": "display"}) self._handle_displayed(**kwargs) def _send(self, msg): """Sends a message to the model in the front-end.""" self.comm.send(msg)
class CyGraphWidget(widgets.DOMWidget): _view_module = Unicode('nbextensions/ivy/js/widget_cy_graph', sync=True) _view_name = Unicode('CyGraphView', sync=True) _cy_elements = Tuple(sync=True) # cy_elements defined below cy_style = Tuple(sync=True) cy_layout = Any(None, sync=True) selected = Tuple((), sync=True) # see elements for format info_area = Any(sync=True) def __init__(self, **kwargs): """Constructor""" super(CyGraphWidget, self).__init__(**kwargs) self.background_color = 'rgb(192,192,255)' self.on_msg(self._handle_cy_msg) self._obj_to_key = dict() self._key_to_obj = dict() cy_elements = property(lambda self: self._cy_elements) @cy_elements.setter def cy_elements(self, value): assert type(value) is CyElements elements = value.elements value.elements = None # prevents future use of this graph if self._cy_elements != elements: # clear the dictionaries to free memory # TODO: figure out why this sometime gives errors. in the # meantime this is commented out and we have a memory # leak. # self._obj_to_key = dict() # self._key_to_obj = dict() self.selected = [] # clear selection self._cy_elements = elements def _ele_to_tuple(self, ele): if ele['group'] == 'nodes': return (ele['data']['obj'],) else: return (ele['data']['obj'], ele['data']['source_obj'], ele['data']['target_obj']) @property def elements(self): """ All graph elements as a list of tuples. Nodes are represented by (obj, ) and edges by (obj, source_obj, target_obj) """ return [self._ele_to_tuple(ele) for ele in self.cy_elements] def _handle_cy_msg(self, _, content): content = self._trait_from_json(content) if content['type'] == 'callback': content['callback'](*content['args']) def execute_new_cell(self, code): """ Causes a new code cell to appear in the notebook and get exectued """ self.send({ "method": "execute_new_cell", "code": code, }) # maintain references to python functions and objects of user # defined classes def _trait_to_json(self, x): x = super(CyGraphWidget, self)._trait_to_json(x) if callable(x) or _is_user_object(x): if x not in self._obj_to_key: k = _object_key(x) self._obj_to_key[x] = k self._key_to_obj[k] = x else: assert x == self._key_to_obj[self._obj_to_key[x]] return _object_prefix + self._obj_to_key[x] else: return x # Value must be JSON-able def _trait_from_json(self, x): x = super(CyGraphWidget, self)._trait_from_json(x) if isinstance(x, string_types) and x.startswith(_object_prefix): # we support object references at any level in a hierarchy # trusting that a string 'CY_OBJECT_XXXX' will not appear # out in the wild return self._key_to_obj[x[len(_object_prefix):]] else: return x
class _Selection(DOMWidget): """Base class for Selection widgets ``values`` can be specified as a list or dict. If given as a list, it will be transformed to a dict of the form ``{str(value):value}``. """ value = Any(help="Selected value") value_name = Unicode(help="The name of the selected value", sync=True) values = Any( help="""List of (key, value) tuples or dict of values that the user can select. The keys of this list are the strings that will be displayed in the UI, representing the actual Python choices. The keys of this list are also available as _value_names. """) _values_dict = Dict() _value_names = Tuple(sync=True) _value_values = Tuple() disabled = Bool(False, help="Enable or disable user changes", sync=True) description = Unicode( help="Description of the value this widget represents", sync=True) def __init__(self, *args, **kwargs): self.value_lock = Lock() self.values_lock = Lock() self.on_trait_change( self._values_readonly_changed, ['_values_dict', '_value_names', '_value_values', '_values']) if 'values' in kwargs: self.values = kwargs.pop('values') DOMWidget.__init__(self, *args, **kwargs) self._value_in_values() def _make_values(self, x): # If x is a dict, convert it to list format. if isinstance(x, (OrderedDict, dict)): return [(k, v) for k, v in x.items()] # Make sure x is a list or tuple. if not isinstance(x, (list, tuple)): raise ValueError('x') # If x is an ordinary list, use the values as names. for y in x: if not isinstance(y, (list, tuple)) or len(y) < 2: return [(i, i) for i in x] # Value is already in the correct format. return x def _values_changed(self, name, old, new): """Handles when the values tuple has been changed. Setting values implies setting value names from the keys of the dict. """ if self.values_lock.acquire(False): try: self.values = new values = self._make_values(new) self._values_dict = {i[0]: i[1] for i in values} self._value_names = [i[0] for i in values] self._value_values = [i[1] for i in values] self._value_in_values() finally: self.values_lock.release() def _value_in_values(self): # ensure that the chosen value is one of the choices if self._value_values: if self.value not in self._value_values: self.value = next(iter(self._value_values)) def _values_readonly_changed(self, name, old, new): if not self.values_lock.locked(): raise TraitError( "`.%s` is a read-only trait. Use the `.values` tuple instead." % name) def _value_changed(self, name, old, new): """Called when value has been changed""" if self.value_lock.acquire(False): try: # Reverse dictionary lookup for the value name for k, v in self._values_dict.items(): if new == v: # set the selected value name self.value_name = k return # undo the change, and raise KeyError self.value = old raise KeyError(new) finally: self.value_lock.release() def _value_name_changed(self, name, old, new): """Called when the value name has been changed (typically by the frontend).""" if self.value_lock.acquire(False): try: self.value = self._values_dict[new] finally: self.value_lock.release()
class MultiTupleTrait(HasTraits): value = Tuple(Int, Bytes, default_value=[99, b'bottles'])
def test_invalid_args(self): self.assertRaises(TypeError, Tuple, 5) self.assertRaises(TypeError, Tuple, default_value='hello') t = Tuple(Int, CBytes, default_value=(1, 5))
class LooseTupleTrait(HasTraits): value = Tuple((1, 2, 3))
class TupleTrait(HasTraits): value = Tuple(Int(allow_none=True))
class TupleTrait(HasTraits): value = Tuple(Int)
class _Selection(DOMWidget): """Base class for Selection widgets ``options`` can be specified as a list or dict. If given as a list, it will be transformed to a dict of the form ``{str(value):value}``. When programmatically setting the value, a reverse lookup is performed among the options to set the value of ``selected_label`` accordingly. The reverse lookup uses the equality operator by default, but an other predicate may be provided via the ``equals`` argument. For example, when dealing with numpy arrays, one may set equals=np.array_equal. """ value = Any(help="Selected value") selected_label = Unicode(help="The label of the selected value", sync=True) options = Any( help="""List of (key, value) tuples or dict of values that the user can select. The keys of this list are the strings that will be displayed in the UI, representing the actual Python choices. The keys of this list are also available as _options_labels. """) _options_dict = Dict() _options_labels = Tuple(sync=True) _options_values = Tuple() disabled = Bool(False, help="Enable or disable user changes", sync=True) description = Unicode( help="Description of the value this widget represents", sync=True) def __init__(self, *args, **kwargs): self.value_lock = Lock() self.options_lock = Lock() self.equals = kwargs.pop('equals', lambda x, y: x == y) self.on_trait_change(self._options_readonly_changed, [ '_options_dict', '_options_labels', '_options_values', '_options' ]) if 'options' in kwargs: self.options = kwargs.pop('options') DOMWidget.__init__(self, *args, **kwargs) self._value_in_options() def _make_options(self, x): # If x is a dict, convert it to list format. if isinstance(x, (OrderedDict, dict)): return [(k, v) for k, v in x.items()] # Make sure x is a list or tuple. if not isinstance(x, (list, tuple)): raise ValueError('x') # If x is an ordinary list, use the option values as names. for y in x: if not isinstance(y, (list, tuple)) or len(y) < 2: return [(i, i) for i in x] # Value is already in the correct format. return x def _options_changed(self, name, old, new): """Handles when the options tuple has been changed. Setting options implies setting option labels from the keys of the dict. """ if self.options_lock.acquire(False): try: self.options = new options = self._make_options(new) self._options_dict = {i[0]: i[1] for i in options} self._options_labels = [i[0] for i in options] self._options_values = [i[1] for i in options] self._value_in_options() finally: self.options_lock.release() def _value_in_options(self): # ensure that the chosen value is one of the choices if self._options_values: if self.value not in self._options_values: self.value = next(iter(self._options_values)) def _options_readonly_changed(self, name, old, new): if not self.options_lock.locked(): raise TraitError( "`.%s` is a read-only trait. Use the `.options` tuple instead." % name) def _value_changed(self, name, old, new): """Called when value has been changed""" if self.value_lock.acquire(False): try: # Reverse dictionary lookup for the value name for k, v in self._options_dict.items(): if self.equals(new, v): # set the selected value name self.selected_label = k return # undo the change, and raise KeyError self.value = old raise KeyError(new) finally: self.value_lock.release() def _selected_label_changed(self, name, old, new): """Called when the value name has been changed (typically by the frontend).""" if self.value_lock.acquire(False): try: self.value = self._options_dict[new] finally: self.value_lock.release()
class TupleTrait(HasTraits): value = Tuple(Int(allow_none=True), default_value=(1, ))