Esempio n. 1
0
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'])
Esempio n. 2
0
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()
Esempio n. 4
0
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)
Esempio n. 5
0
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()
Esempio n. 6
0
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)
Esempio n. 7
0
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()
Esempio n. 8
0
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()
Esempio n. 9
0
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()
Esempio n. 10
0
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()
Esempio n. 11
0
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)
Esempio n. 12
0
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()
Esempio n. 13
0
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)
Esempio n. 14
0
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)
Esempio n. 15
0
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)
Esempio n. 16
0
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
Esempio n. 17
0
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()
Esempio n. 18
0
class MultiTupleTrait(HasTraits):

    value = Tuple(Int, Bytes, default_value=[99, b'bottles'])
Esempio n. 19
0
 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))
Esempio n. 20
0
class LooseTupleTrait(HasTraits):

    value = Tuple((1, 2, 3))
Esempio n. 21
0
class TupleTrait(HasTraits):

    value = Tuple(Int(allow_none=True))
Esempio n. 22
0
class TupleTrait(HasTraits):

    value = Tuple(Int)
Esempio n. 23
0
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()
Esempio n. 24
0
class TupleTrait(HasTraits):

    value = Tuple(Int(allow_none=True), default_value=(1, ))