holoviz / panel
1
"""
2
Defines the Param pane which converts Parameterized classes into a
3
set of widgets.
4
"""
5 2
from __future__ import absolute_import, division, unicode_literals
6

7 2
import os
8 2
import sys
9 2
import json
10 2
import types
11 2
import inspect
12 2
import itertools
13

14 2
from collections import OrderedDict, defaultdict, namedtuple
15 2
from six import string_types
16

17 2
import param
18

19 2
from bokeh.io import curdoc as _curdoc
20 2
from param.parameterized import classlist
21

22 2
from .io import state
23 2
from .layout import Row, Panel, Tabs, Column
24 2
from .pane.base import PaneBase, ReplacementPane
25 2
from .util import (
26
    abbreviated_repr, full_groupby, get_method_owner, is_parameterized,
27
    param_name, recursive_parameterized
28
)
29 2
from .viewable import Layoutable
30 2
from .widgets import (
31
    Button, Checkbox, ColorPicker, DataFrame, DatePicker, DatetimeInput,
32
    DateRangeSlider, FileSelector, FloatSlider, IntSlider, LiteralInput,
33
    MultiSelect, RangeSlider, Select, Spinner, StaticText, TextInput,
34
    Toggle, Widget
35
)
36 2
from .widgets.button import _ButtonBase
37

38

39 2
def SingleFileSelector(pobj):
40
    """
41
    Determines whether to use a TextInput or Select widget for FileSelector
42
    """
43 0
    if pobj.path:
44 0
        return Select
45
    else:
46 0
        return TextInput
47

48

49 2
def LiteralInputTyped(pobj):
50 2
    if isinstance(pobj, param.Tuple):
51 0
        return type(str('TupleInput'), (LiteralInput,), {'type': tuple})
52 2
    elif isinstance(pobj, param.Number):
53 0
        return type(str('NumberInput'), (LiteralInput,), {'type': (int, float)})
54 2
    elif isinstance(pobj, param.Dict):
55 0
        return type(str('DictInput'), (LiteralInput,), {'type': dict})
56 2
    elif isinstance(pobj, param.List):
57 2
        return type(str('ListInput'), (LiteralInput,), {'type': list})
58 2
    return LiteralInput
59

60

61 2
class Param(PaneBase):
62
    """
63
    Param panes render a Parameterized class to a set of widgets which
64
    are linked to the parameter values on the class.
65
    """
66

67 2
    display_threshold = param.Number(default=0, precedence=-10, doc="""
68
        Parameters with precedence below this value are not displayed.""")
69

70 2
    default_layout = param.ClassSelector(default=Column, class_=Panel,
71
                                         is_instance=False)
72

73 2
    default_precedence = param.Number(default=1e-8, precedence=-10, doc="""
74
        Precedence value to use for parameters with no declared
75
        precedence.  By default, zero predecence is available for
76
        forcing some parameters to the top of the list, and other
77
        values above the default_precedence values can be used to sort
78
        or group parameters arbitrarily.""")
79

80 2
    expand = param.Boolean(default=False, doc="""
81
        Whether parameterized subobjects are expanded or collapsed on
82
        instantiation.""")
83

84 2
    expand_button = param.Boolean(default=None, doc="""
85
        Whether to add buttons to expand and collapse sub-objects.""")
86

87 2
    expand_layout = param.Parameter(default=Column, doc="""
88
        Layout to expand sub-objects into.""")
89

90 2
    height = param.Integer(default=None, bounds=(0, None), doc="""
91
        Height of widgetbox the parameter widgets are displayed in.""")
92

93 2
    initializer = param.Callable(default=None, doc="""
94
        User-supplied function that will be called on initialization,
95
        usually to update the default Parameter values of the
96
        underlying parameterized object.""")
97

98 2
    name = param.String(default='', doc="""
99
        Title of the pane.""")
100

101 2
    parameters = param.List(default=[], allow_None=True, doc="""
102
        If set this serves as a whitelist of parameters to display on
103
        the supplied Parameterized object.""")
104

105 2
    show_labels = param.Boolean(default=True, doc="""
106
        Whether to show labels for each widget""")
107

108 2
    show_name = param.Boolean(default=True, doc="""
109
        Whether to show the parameterized object's name""")
110

111 2
    width = param.Integer(default=300, allow_None=True, bounds=(0, None), doc="""
112
        Width of widgetbox the parameter widgets are displayed in.""")
113

114 2
    widgets = param.Dict(doc="""
115
        Dictionary of widget overrides, mapping from parameter name
116
        to widget class.""")
117

118 2
    priority = 0.1
119

120 2
    _unpack = True
121

122 2
    _mapping = {
123
        param.Action:            Button,
124
        param.Boolean:           Checkbox,
125
        param.CalendarDate:      DatePicker,
126
        param.Color:             ColorPicker,
127
        param.Date:              DatetimeInput,
128
        param.DateRange:         DateRangeSlider,
129
        param.CalendarDateRange: DateRangeSlider,
130
        param.DataFrame:         DataFrame,
131
        param.Dict:              LiteralInputTyped,
132
        param.FileSelector:      SingleFileSelector,
133
        param.Filename:          TextInput,
134
        param.Foldername:        TextInput,
135
        param.Integer:           IntSlider,
136
        param.List:              LiteralInputTyped,
137
        param.MultiFileSelector: FileSelector,
138
        param.ListSelector:      MultiSelect,
139
        param.Number:            FloatSlider,
140
        param.ObjectSelector:    Select,
141
        param.Parameter:         LiteralInputTyped,
142
        param.Range:             RangeSlider,
143
        param.Selector:          Select,
144
        param.String:            TextInput,
145
    }
146

147 2
    _rerender_params = []
148

149 2
    def __init__(self, object=None, **params):
150 2
        if isinstance(object, param.Parameter):
151 2
            if not 'show_name' in params:
152 2
                params['show_name'] = False
153 2
            params['parameters'] = [object.name]
154 2
            object = object.owner
155 2
        if isinstance(object, param.parameterized.Parameters):
156 2
            object = object.cls if object.self is None else object.self
157

158 2
        if 'parameters' not in params and object is not None:
159 2
            params['parameters'] = [p for p in object.param if p != 'name']
160 2
            self._explicit_parameters = False
161
        else:
162 2
            self._explicit_parameters = object is not None
163

164 2
        if object and 'name' not in params:
165 2
            params['name'] = param_name(object.name)
166 2
        super(Param, self).__init__(object, **params)
167 2
        self._updating = []
168

169
        # Construct Layout
170 2
        kwargs = {p: v for p, v in self.param.get_param_values()
171
                  if p in Layoutable.param and v is not None}
172 2
        self._widget_box = self.default_layout(**kwargs)
173

174 2
        layout = self.expand_layout
175 2
        if isinstance(layout, Panel):
176 2
            self._expand_layout = layout
177 2
            self.layout = self._widget_box
178 2
        elif isinstance(self._widget_box, layout):
179 2
            self.layout = self._expand_layout = self._widget_box
180 2
        elif isinstance(layout, type) and issubclass(layout, Panel):
181 2
            self.layout = self._expand_layout = layout(self._widget_box, **kwargs)
182
        else:
183 0
            raise ValueError('expand_layout expected to be a panel.layout.Panel'
184
                             'type or instance, found %s type.' %
185
                             type(layout).__name__)
186 2
        self.param.watch(self._update_widgets, [
187
            'object', 'parameters', 'name', 'display_threshold', 'expand_button',
188
            'expand', 'expand_layout', 'widgets', 'show_labels', 'show_name'])
189 2
        self._update_widgets()
190

191 2
    def __repr__(self, depth=0):
192 2
        cls = type(self).__name__
193 2
        obj_cls = type(self.object).__name__
194 2
        params = [] if self.object is None else list(self.object.param)
195 2
        parameters = [k for k in params if k != 'name']
196 2
        params = []
197 2
        for p, v in sorted(self.param.get_param_values()):
198 2
            if v is self.param[p].default: continue
199 2
            elif v is None: continue
200 2
            elif isinstance(v, string_types) and v == '': continue
201 2
            elif p == 'object' or (p == 'name' and (v.startswith(obj_cls) or v.startswith(cls))): continue
202 2
            elif p == 'parameters' and v == parameters: continue
203 2
            try:
204 2
                params.append('%s=%s' % (p, abbreviated_repr(v)))
205 0
            except RuntimeError:
206 0
                params.append('%s=%s' % (p, '...'))
207 2
        obj = 'None' if self.object is None else '%s' % type(self.object).__name__
208 2
        template = '{cls}({obj}, {params})' if params else '{cls}({obj})'
209 2
        return template.format(cls=cls, params=', '.join(params), obj=obj)
210

211
    #----------------------------------------------------------------
212
    # Callback API
213
    #----------------------------------------------------------------
214

215 2
    def _synced_params(self):
216 2
        ignored_params = ['default_layout']
217 2
        return [p for p in Layoutable.param if p not in ignored_params]
218

219 2
    def _update_widgets(self, *events):
220 2
        parameters = []
221 2
        for event in sorted(events, key=lambda x: x.name):
222 2
            if event.name == 'object':
223 2
                if isinstance(event.new, param.parameterized.Parameters):
224
                    # Setting object will trigger this method a second time
225 2
                    self.object = event.new.cls if event.new.self is None else event.new.self
226 2
                    return
227
                
228 2
                if self._explicit_parameters:
229 2
                    parameters = self.parameters
230 2
                elif event.new is None:
231 0
                    parameters = []
232
                else:
233 2
                    parameters = [p for p in event.new.param if p != 'name']
234 2
                    self.name = param_name(event.new.name)
235 2
            if event.name == 'parameters':
236 2
                if event.new is None:
237 0
                    self._explicit_parameters = False
238 0
                    if self.object is not None:
239 0
                        parameters = [p for p in self.object.param if p != 'name']
240
                else:
241 2
                    self._explicit_parameters = True
242 2
                    parameters = [] if event.new == [] else event.new
243

244 2
        if parameters != [] and parameters != self.parameters:
245
            # Setting parameters will trigger this method a second time
246 2
            self.parameters = parameters
247 2
            return
248

249 2
        for cb in list(self._callbacks):
250 2
            if cb.inst in self._widget_box.objects:
251 2
                cb.inst.param.unwatch(cb)
252 2
                self._callbacks.remove(cb)
253

254
        # Construct widgets
255 2
        if self.object is None:
256 2
            self._widgets = {}
257
        else:
258 2
            self._widgets = self._get_widgets()
259

260 2
        alias = {'_title': 'name'}
261 2
        widgets = [widget for p, widget in self._widgets.items()
262
                   if (self.object.param[alias.get(p, p)].precedence is None)
263
                   or (self.object.param[alias.get(p, p)].precedence >= self.display_threshold)]
264 2
        self._widget_box.objects = widgets
265 2
        if not (self.expand_button == False and not self.expand):
266 2
            self._link_subobjects()
267

268 2
    def _link_subobjects(self):
269 2
        for pname, widget in self._widgets.items():
270 2
            widgets = [widget] if isinstance(widget, Widget) else widget
271 2
            if not any(is_parameterized(getattr(w, 'value', None)) or
272
                       any(is_parameterized(o) for o in getattr(w, 'options', []))
273
                       for w in widgets):
274 2
                continue
275 2
            if (isinstance(widgets, Row) and isinstance(widgets[1], Toggle)):
276 2
                selector, toggle = (widgets[0], widgets[1])
277
            else:
278 2
                selector, toggle = (widget, None)
279

280 2
            def toggle_pane(change, parameter=pname):
281
                "Adds or removes subpanel from layout"
282 2
                parameterized = getattr(self.object, parameter)
283 2
                existing = [p for p in self._expand_layout.objects
284
                            if isinstance(p, Param) and
285
                            p.object in recursive_parameterized(parameterized)]
286 2
                if not change.new:
287 2
                    self._expand_layout[:] = [
288
                        e for e in self._expand_layout.objects
289
                        if e not in existing
290
                    ]
291 2
                elif change.new:
292 2
                    kwargs = {k: v for k, v in self.param.get_param_values()
293
                              if k not in ['name', 'object', 'parameters']}
294 2
                    pane = Param(parameterized, name=parameterized.name,
295
                                 **kwargs)
296 2
                    if isinstance(self._expand_layout, Tabs):
297 2
                        title = self.object.param[pname].label
298 2
                        pane = (title, pane)
299 2
                    self._expand_layout.append(pane)
300

301 2
            def update_pane(change, parameter=pname):
302
                "Adds or removes subpanel from layout"
303 2
                layout = self._expand_layout
304 2
                existing = [p for p in layout.objects if isinstance(p, Param)
305
                            and p.object is change.old]
306

307 2
                if toggle:
308 2
                    toggle.disabled = not is_parameterized(change.new)
309 2
                if not existing:
310 0
                    return
311 2
                elif is_parameterized(change.new):
312 2
                    parameterized = change.new
313 2
                    kwargs = {k: v for k, v in self.param.get_param_values()
314
                              if k not in ['name', 'object', 'parameters']}
315 2
                    pane = Param(parameterized, name=parameterized.name,
316
                                 **kwargs)
317 2
                    layout[layout.objects.index(existing[0])] = pane
318
                else:
319 0
                    layout.pop(existing[0])
320

321 2
            watchers = [selector.param.watch(update_pane, 'value')]
322 2
            if toggle:
323 2
                watchers.append(toggle.param.watch(toggle_pane, 'value'))
324 2
            self._callbacks += watchers
325

326 2
            if self.expand:
327 2
                if self.expand_button:
328 2
                    toggle.value = True
329
                else:
330 2
                    toggle_pane(namedtuple('Change', 'new')(True))
331

332 2
    def widget(self, p_name):
333
        """Get widget for param_name"""
334 2
        p_obj = self.object.param[p_name]
335 2
        kw_widget = {}
336

337 2
        widget_class_overridden = True
338 2
        if self.widgets is None or p_name not in self.widgets:
339 2
            widget_class_overridden = False
340 2
            widget_class = self.widget_type(p_obj)
341 2
        elif isinstance(self.widgets[p_name], dict):
342 2
            if 'type' in self.widgets[p_name]:
343 2
                widget_class = self.widgets[p_name].pop('type')
344
            else:
345 2
                widget_class_overridden = False
346 2
                widget_class = self.widget_type(p_obj)
347 2
            kw_widget = self.widgets[p_name]
348
        else:
349 2
            widget_class = self.widgets[p_name]
350

351 2
        if not self.show_labels and not issubclass(widget_class, _ButtonBase):
352 2
            label = ''
353
        else:
354 2
            label = p_obj.label
355 2
        kw = dict(disabled=p_obj.constant, name=label)
356

357 2
        value = getattr(self.object, p_name)
358 2
        if value is not None:
359 2
            kw['value'] = value
360

361
        # Update kwargs
362 2
        kw.update(kw_widget)
363

364 2
        if hasattr(p_obj, 'get_range'):
365 2
            options = p_obj.get_range()
366 2
            if not options and value is not None:
367 0
                options = [value]
368 2
            kw['options'] = options
369 2
        if hasattr(p_obj, 'get_soft_bounds'):
370 2
            bounds = p_obj.get_soft_bounds()
371 2
            if bounds[0] is not None:
372 2
                kw['start'] = bounds[0]
373 2
            if bounds[1] is not None:
374 2
                kw['end'] = bounds[1]
375 2
            if ('start' not in kw or 'end' not in kw):
376
                # Do not change widget class if _mapping was overridden
377 2
                if not widget_class_overridden:
378 2
                    if (isinstance(p_obj, param.Number) and
379
                        not isinstance(p_obj, (param.Date, param.CalendarDate))):
380 2
                        widget_class = Spinner
381 2
                        if isinstance(p_obj, param.Integer):
382 2
                            kw['step'] = 1
383 0
                    elif not issubclass(widget_class, LiteralInput):
384 0
                        widget_class = LiteralInput
385 2
            if hasattr(widget_class, 'step') and getattr(p_obj, 'step', None):
386 2
                kw['step'] = p_obj.step
387

388 2
        kwargs = {k: v for k, v in kw.items() if k in widget_class.param}
389

390 2
        if isinstance(widget_class, Widget):
391 2
            widget = widget_class
392
        else:
393 2
            widget = widget_class(**kwargs)
394 2
        widget._param_pane = self
395

396 2
        watchers = self._callbacks
397

398 2
        def link_widget(change):
399 2
            if p_name in self._updating:
400 2
                return
401 2
            try:
402 2
                self._updating.append(p_name)
403 2
                self.object.param.set_param(**{p_name: change.new})
404
            finally:
405 2
                self._updating.remove(p_name)
406

407 2
        if isinstance(p_obj, param.Action):
408 2
            def action(change):
409 2
                value(self.object)
410 2
            watcher = widget.param.watch(action, 'clicks')
411
        else:
412 2
            watcher = widget.param.watch(link_widget, 'value')
413 2
        watchers.append(watcher)
414

415 2
        def link(change, watchers=[watcher]):
416 2
            updates = {}
417 2
            if change.what == 'constant':
418 2
                updates['disabled'] = change.new
419 2
            elif change.what == 'precedence':
420 2
                if (change.new < self.display_threshold and
421
                    widget in self._widget_box.objects):
422 2
                    self._widget_box.pop(widget)
423 2
                elif change.new >= self.display_threshold:
424 2
                    precedence = lambda k: self.object.param['name' if k == '_title' else k].precedence
425 2
                    params = self._ordered_params
426 2
                    if self.show_name:
427 2
                        params.insert(0, '_title')
428 2
                    widgets = []
429 2
                    for k in params:
430 2
                        if precedence(k) is None or precedence(k) >= self.display_threshold:
431 2
                            widgets.append(self._widgets[k])
432 2
                    self._widget_box.objects = widgets
433 2
                return
434 2
            elif change.what == 'objects':
435 2
                updates['options'] = p_obj.get_range()
436 2
            elif change.what == 'bounds':
437 2
                start, end = p_obj.get_soft_bounds()
438 2
                updates['start'] = start
439 2
                updates['end'] = end
440 2
            elif change.what == 'step':
441 2
                updates['step'] = p_obj.step
442 2
            elif change.what == 'label':
443 2
                updates['name'] = p_obj.label
444 2
            elif p_name in self._updating:
445 2
                return
446 2
            elif isinstance(p_obj, param.Action):
447 2
                prev_watcher = watchers[0]
448 2
                widget.param.unwatch(prev_watcher)
449 2
                def action(event):
450 0
                    change.new(self.object)
451 2
                watchers[0] = widget.param.watch(action, 'clicks')
452 2
                idx = self._callbacks.index(prev_watcher)
453 2
                self._callbacks[idx] = watchers[0]
454 2
                return
455
            else:
456 2
                updates['value'] = change.new
457

458 2
            try:
459 2
                self._updating.append(p_name)
460 2
                widget.param.set_param(**updates)
461
            finally:
462 2
                self._updating.remove(p_name)
463

464
        # Set up links to parameterized object
465 2
        watchers.append(self.object.param.watch(link, p_name, 'constant'))
466 2
        watchers.append(self.object.param.watch(link, p_name, 'precedence'))
467 2
        watchers.append(self.object.param.watch(link, p_name, 'label'))
468 2
        if hasattr(p_obj, 'get_range'):
469 2
            watchers.append(self.object.param.watch(link, p_name, 'objects'))
470 2
        if hasattr(p_obj, 'get_soft_bounds'):
471 2
            watchers.append(self.object.param.watch(link, p_name, 'bounds'))
472 2
        if 'step' in kw:
473 2
            watchers.append(self.object.param.watch(link, p_name, 'step'))
474 2
        watchers.append(self.object.param.watch(link, p_name))
475

476 2
        options = kwargs.get('options', [])
477 2
        if isinstance(options, dict):
478 2
            options = options.values()
479 2
        if ((is_parameterized(value) or any(is_parameterized(o) for o in options))
480
            and (self.expand_button or (self.expand_button is None and not self.expand))):
481 2
            widget.margin = (5, 0, 5, 10)
482 2
            toggle = Toggle(name='\u22EE', button_type='primary',
483
                            disabled=not is_parameterized(value), max_height=30,
484
                            max_width=20, height_policy='fit', align='end',
485
                            margin=(0, 0, 5, 10))
486 2
            widget.width = self._widget_box.width-60
487 2
            return Row(widget, toggle, width_policy='max', margin=0)
488
        else:
489 2
            return widget
490

491 2
    @property
492
    def _ordered_params(self):
493 2
        params = [(p, pobj) for p, pobj in self.object.param.objects('existing').items()
494
                  if p in self.parameters or p == 'name']
495 2
        key_fn = lambda x: x[1].precedence if x[1].precedence is not None else self.default_precedence
496 2
        sorted_precedence = sorted(params, key=key_fn)
497 2
        filtered = [(k, p) for k, p in sorted_precedence]
498 2
        groups = itertools.groupby(filtered, key=key_fn)
499
        # Params preserve definition order in Python 3.6+
500 2
        dict_ordered_py3 = (sys.version_info.major == 3 and sys.version_info.minor >= 6)
501 2
        dict_ordered = dict_ordered_py3 or (sys.version_info.major > 3)
502 2
        ordered_groups = [list(grp) if dict_ordered else sorted(grp) for (_, grp) in groups]
503 2
        ordered_params = [el[0] for group in ordered_groups for el in group
504
                          if (el[0] != 'name' or el[0] in self.parameters)]
505 2
        return ordered_params
506

507
    #----------------------------------------------------------------
508
    # Model API
509
    #----------------------------------------------------------------
510

511 2
    def _get_widgets(self):
512
        """Return name,widget boxes for all parameters (i.e., a property sheet)"""
513
        # Format name specially
514 2
        if self.expand_layout is Tabs:
515 2
            widgets = []
516 2
        elif self.show_name:
517 2
            widgets = [('_title', StaticText(value='<b>{0}</b>'.format(self.name)))]
518
        else:
519 2
            widgets = []
520 2
        widgets += [(pname, self.widget(pname)) for pname in self._ordered_params]
521 2
        return OrderedDict(widgets)
522

523 2
    def _get_model(self, doc, root=None, parent=None, comm=None):
524 2
        model = self.layout._get_model(doc, root, parent, comm)
525 2
        self._models[root.ref['id']] = (model, parent)
526 2
        return model
527

528 2
    def _cleanup(self, root):
529 2
        self.layout._cleanup(root)
530 2
        super(Param, self)._cleanup(root)
531

532
    #----------------------------------------------------------------
533
    # Public API
534
    #----------------------------------------------------------------
535

536 2
    @classmethod
537
    def applies(cls, obj):
538 2
        return (is_parameterized(obj) or
539
                isinstance(obj, param.parameterized.Parameters) or
540
                (isinstance(obj, param.Parameter) and obj.owner is not None))
541

542 2
    @classmethod
543
    def widget_type(cls, pobj):
544 2
        ptype = type(pobj)
545 2
        for t in classlist(ptype)[::-1]:
546 2
            if t in cls._mapping:
547 2
                if isinstance(cls._mapping[t], types.FunctionType):
548 2
                    return cls._mapping[t](pobj)
549 2
                return cls._mapping[t]
550

551 2
    def select(self, selector=None):
552
        """
553
        Iterates over the Viewable and any potential children in the
554
        applying the Selector.
555

556
        Arguments
557
        ---------
558
        selector: type or callable or None
559
          The selector allows selecting a subset of Viewables by
560
          declaring a type or callable function to filter by.
561

562
        Returns
563
        -------
564
        viewables: list(Viewable)
565
        """
566 2
        return super().select(selector) + self.layout.select(selector)
567

568 2
    def get_root(self, doc=None, comm=None):
569
        """
570
        Returns the root model and applies pre-processing hooks
571

572
        Arguments
573
        ---------
574
        doc: bokeh.Document
575
          Bokeh document the bokeh model will be attached to.
576
        comm: pyviz_comms.Comm
577
          Optional pyviz_comms when working in notebook
578

579
        Returns
580
        -------
581
        Returns the bokeh model corresponding to this panel object
582
        """
583 2
        doc = doc or _curdoc()
584 2
        root = self.layout.get_root(doc, comm)
585 2
        ref = root.ref['id']
586 2
        self._models[ref] = (root, None)
587 2
        state._views[ref] = (self, root, doc, comm)
588 2
        return root
589

590

591 2
class ParamMethod(ReplacementPane):
592
    """
593
    ParamMethod panes wrap methods on parameterized classes and
594
    rerenders the plot when any of the method's parameters change. By
595
    default ParamMethod will watch all parameters on the class owning
596
    the method or can be restricted to certain parameters by annotating
597
    the method using the param.depends decorator. The method may
598
    return any object which itself can be rendered as a Pane.
599
    """
600

601 2
    def __init__(self, object=None, **params):
602 2
        super(ParamMethod, self).__init__(object, **params)
603 2
        self._link_object_params()
604 2
        if object is not None:
605 2
            self._validate_object()
606 2
            self._update_inner(self.eval(object))
607

608 2
    @param.depends('object', watch=True)
609
    def _validate_object(self):
610 2
        dependencies = getattr(self.object, '_dinfo', None)
611 2
        if not dependencies or not dependencies.get('watch'):
612 2
            return
613 0
        fn_type = 'method' if type(self) is ParamMethod else 'function'
614 0
        self.param.warning(f"The {fn_type} supplied for Panel to display "
615
                           "was declared with `watch=True`, which will "
616
                           f"cause the {fn_type} to be called twice for "
617
                           "any change in a dependent Parameter. "
618
                           "`watch` should be False when Panel is "
619
                           "responsible for displaying the result "
620
                           f"of the {fn_type} call, while `watch=True` "
621
                           f"should be reserved for {fn_type}s that work "
622
                           "via side-effects, e.g. by modifying internal  "
623
                           "state of a class or global state in an "
624
                           "application's namespace.")
625

626
    #----------------------------------------------------------------
627
    # Callback API
628
    #----------------------------------------------------------------
629

630 2
    @classmethod
631
    def eval(self, function):
632 2
        args, kwargs = (), {}
633 2
        if hasattr(function, '_dinfo'):
634 2
            arg_deps = function._dinfo['dependencies']
635 2
            kw_deps = function._dinfo.get('kw', {})
636 2
            if kw_deps or any(isinstance(d, param.Parameter) for d in arg_deps):
637 2
                args = (getattr(dep.owner, dep.name) for dep in arg_deps)
638 2
                kwargs = {n: getattr(dep.owner, dep.name) for n, dep in kw_deps.items()}
639 2
        return function(*args, **kwargs)
640

641 2
    def _update_pane(self, *events):
642 0
        callbacks = []
643 0
        for watcher in self._callbacks:
644 0
            obj = watcher.inst if watcher.inst is None else watcher.cls
645 0
            if obj is self:
646 0
                callbacks.append(watcher)
647 0
                continue
648 0
            obj.param.unwatch(watcher)
649 0
        self._callbacks = callbacks
650 0
        self._link_object_params()
651 0
        if object is not None:
652 0
            self._update_inner(self.eval(self.object))
653

654 2
    def _link_object_params(self):
655 2
        parameterized = get_method_owner(self.object)
656 2
        params = parameterized.param.params_depended_on(self.object.__name__)
657 2
        deps = params
658

659 2
        def update_pane(*events):
660
            # Update nested dependencies if parameterized object events
661 2
            if any(is_parameterized(event.new) for event in events):
662 2
                new_deps = parameterized.param.params_depended_on(self.object.__name__)
663 2
                for p in list(deps):
664 2
                    if p in new_deps: continue
665 2
                    watchers = self._callbacks
666 2
                    for w in list(watchers):
667 2
                        if (w.inst is p.inst and w.cls is p.cls and
668
                            p.name in w.parameter_names):
669 2
                            obj = p.cls if p.inst is None else p.inst
670 2
                            obj.param.unwatch(w)
671 2
                            watchers.pop(watchers.index(w))
672 2
                    deps.pop(deps.index(p))
673

674 2
                new_deps = [dep for dep in new_deps if dep not in deps]
675 2
                for _, params in full_groupby(new_deps, lambda x: (x.inst or x.cls, x.what)):
676 2
                    p = params[0]
677 2
                    pobj = p.cls if p.inst is None else p.inst
678 2
                    ps = [_p.name for _p in params]
679 2
                    watcher = pobj.param.watch(update_pane, ps, p.what)
680 2
                    self._callbacks.append(watcher)
681 2
                    for p in params:
682 2
                        deps.append(p)
683 2
            new_object = self.eval(self.object)
684 2
            self._update_inner(new_object)
685

686 2
        for _, params in full_groupby(params, lambda x: (x.inst or x.cls, x.what)):
687 2
            p = params[0]
688 2
            pobj = (p.inst or p.cls)
689 2
            ps = [_p.name for _p in params]
690 2
            watcher = pobj.param.watch(update_pane, ps, p.what)
691 2
            self._callbacks.append(watcher)
692

693
    #----------------------------------------------------------------
694
    # Public API
695
    #----------------------------------------------------------------
696

697 2
    @classmethod
698
    def applies(cls, obj):
699 2
        return inspect.ismethod(obj) and isinstance(get_method_owner(obj), param.Parameterized)
700

701

702

703 2
class ParamFunction(ParamMethod):
704
    """
705
    ParamFunction panes wrap functions decorated with the param.depends
706
    decorator and rerenders the output when any of the function's
707
    dependencies change. This allows building reactive components into
708
    a Panel which depend on other parameters, e.g. tying the value of
709
    a widget to some other output.
710
    """
711

712 2
    priority = 0.6
713

714 2
    def _replace_pane(self, *args):
715 2
        new_object = self.eval(self.object)
716 2
        self._update_inner(new_object)
717

718 2
    def _link_object_params(self):
719 2
        deps = self.object._dinfo
720 2
        dep_params = list(deps['dependencies']) + list(deps.get('kw', {}).values())
721 2
        grouped = defaultdict(list)
722 2
        for dep in dep_params:
723 2
            grouped[id(dep.owner)].append(dep)
724 2
        for group in grouped.values():
725 2
            watcher = group[0].owner.param.watch(self._replace_pane, [dep.name for dep in group])
726 2
            self._callbacks.append(watcher)
727

728
    #----------------------------------------------------------------
729
    # Public API
730
    #----------------------------------------------------------------
731

732 2
    @classmethod
733
    def applies(cls, obj):
734 2
        return isinstance(obj, types.FunctionType) and hasattr(obj, '_dinfo')
735

736

737 2
class JSONInit(param.Parameterized):
738
    """
739
    Callable that can be passed to Widgets.initializer to set Parameter
740
    values using JSON. There are three approaches that may be used:
741
    1. If the json_file argument is specified, this takes precedence.
742
    2. The JSON file path can be specified via an environment variable.
743
    3. The JSON can be read directly from an environment variable.
744
    Here is an easy example of setting such an environment variable on
745
    the commandline:
746
    PARAM_JSON_INIT='{"p1":5}' jupyter notebook
747
    This addresses any JSONInit instances that are inspecting the
748
    default environment variable called PARAM_JSON_INIT, instructing it to set
749
    the 'p1' parameter to 5.
750
    """
751

752 2
    varname = param.String(default='PARAM_JSON_INIT', doc="""
753
        The name of the environment variable containing the JSON
754
        specification.""")
755

756 2
    target = param.String(default=None, doc="""
757
        Optional key in the JSON specification dictionary containing the
758
        desired parameter values.""")
759

760 2
    json_file = param.String(default=None, doc="""
761
        Optional path to a JSON file containing the parameter settings.""")
762

763 2
    def __call__(self, parameterized):
764 2
        warnobj = param.main if isinstance(parameterized, type) else parameterized
765 2
        param_class = (parameterized if isinstance(parameterized, type)
766
                       else parameterized.__class__)
767

768 2
        target = self.target if self.target is not None else param_class.__name__
769

770 2
        env_var = os.environ.get(self.varname, None)
771 2
        if env_var is None and self.json_file is None: return
772

773 2
        if self.json_file or env_var.endswith('.json'):
774 0
            try:
775 0
                fname = self.json_file if self.json_file else env_var
776 0
                spec = json.load(open(os.path.abspath(fname), 'r'))
777 0
            except Exception:
778 0
                warnobj.warning('Could not load JSON file %r' % spec)
779
        else:
780 2
            spec = json.loads(env_var)
781

782 2
        if not isinstance(spec, dict):
783 0
            warnobj.warning('JSON parameter specification must be a dictionary.')
784 0
            return
785

786 2
        if target in spec:
787 0
            params = spec[target]
788
        else:
789 2
            params = spec
790

791 2
        for name, value in params.items():
792 2
            try:
793 2
                parameterized.param.set_param(**{name:value})
794 0
            except ValueError as e:
795 0
                warnobj.warning(str(e))

Read our documentation on viewing source code .

Loading