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

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

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

17 7
import param
18

19 7
from param.parameterized import classlist, discard_events
20

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

37

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

47

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

59

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

117 7
    priority = 0.1
118

119 7
    _unpack = True
120

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

146 7
    if hasattr(param, 'Event'):
147 7
        _mapping[param.Event] = Button
148

149 7
    _rerender_params = []
150

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

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

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

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

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

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

213
    #----------------------------------------------------------------
214
    # Callback API
215
    #----------------------------------------------------------------
216

217 7
    def _synced_params(self):
218 7
        ignored_params = ['default_layout']
219 7
        return [p for p in Layoutable.param if p not in ignored_params]
220

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

246 7
        if parameters != [] and parameters != self.parameters:
247
            # Setting parameters will trigger this method a second time
248 7
            self.parameters = parameters
249 7
            return
250

251 7
        for cb in list(self._callbacks):
252 7
            if cb.inst in self._widget_box.objects:
253 7
                cb.inst.param.unwatch(cb)
254 7
                self._callbacks.remove(cb)
255

256
        # Construct widgets
257 7
        if self.object is None:
258 7
            self._widgets = {}
259
        else:
260 7
            self._widgets = self._get_widgets()
261

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

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

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

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

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

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

328 7
            if self.expand:
329 7
                if self.expand_button:
330 7
                    toggle.value = True
331
                else:
332 7
                    toggle_pane(namedtuple('Change', 'new')(True))
333

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

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

355 7
        if not self.show_labels and not issubclass(widget_class, _ButtonBase):
356 7
            label = ''
357
        else:
358 7
            label = p_obj.label
359 7
        kw = dict(disabled=p_obj.constant, name=label)
360

361 7
        value = getattr(self.object, p_name)
362 7
        if value is not None:
363 7
            kw['value'] = value
364

365
        # Update kwargs
366 7
        kw.update(kw_widget)
367

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

392 7
        kwargs = {k: v for k, v in kw.items() if k in widget_class.param}
393

394 7
        if isinstance(widget_class, Widget):
395 7
            widget = widget_class
396
        else:
397 7
            widget = widget_class(**kwargs)
398 7
        widget._param_pane = self
399

400 7
        watchers = self._callbacks
401

402 7
        def link_widget(change):
403 7
            if p_name in self._updating:
404 7
                return
405 7
            try:
406 7
                self._updating.append(p_name)
407 7
                self.object.param.set_param(**{p_name: change.new})
408
            finally:
409 7
                self._updating.remove(p_name)
410

411 7
        if hasattr(param, 'Event') and isinstance(p_obj, param.Event):
412 0
            def event(change):
413 0
                self.object.param.trigger(p_name)
414 0
            watcher = widget.param.watch(event, 'clicks')
415 7
        elif isinstance(p_obj, param.Action):
416 7
            def action(change):
417 7
                value(self.object)
418 7
            watcher = widget.param.watch(action, 'clicks')
419
        else:
420 7
            watcher = widget.param.watch(link_widget, 'value')
421 7
        watchers.append(watcher)
422

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

468 7
            try:
469 7
                self._updating.append(p_name)
470 7
                if change.type == 'triggered':
471 7
                    with discard_events(widget):
472 7
                        widget.param.set_param(**updates)
473 7
                    widget.param.trigger(*updates)
474
                else:
475 7
                    widget.param.set_param(**updates)
476
            finally:
477 7
                self._updating.remove(p_name)
478

479
        # Set up links to parameterized object
480 7
        watchers.append(self.object.param.watch(link, p_name, 'constant'))
481 7
        watchers.append(self.object.param.watch(link, p_name, 'precedence'))
482 7
        watchers.append(self.object.param.watch(link, p_name, 'label'))
483 7
        if hasattr(p_obj, 'get_range'):
484 7
            watchers.append(self.object.param.watch(link, p_name, 'objects'))
485 7
        if hasattr(p_obj, 'get_soft_bounds'):
486 7
            watchers.append(self.object.param.watch(link, p_name, 'bounds'))
487 7
        if 'step' in kw:
488 7
            watchers.append(self.object.param.watch(link, p_name, 'step'))
489 7
        watchers.append(self.object.param.watch(link, p_name))
490

491 7
        options = kwargs.get('options', [])
492 7
        if isinstance(options, dict):
493 7
            options = options.values()
494 7
        if ((is_parameterized(value) or any(is_parameterized(o) for o in options))
495
            and (self.expand_button or (self.expand_button is None and not self.expand))):
496 7
            widget.margin = (5, 0, 5, 10)
497 7
            toggle = Toggle(name='\u22EE', button_type='primary',
498
                            disabled=not is_parameterized(value), max_height=30,
499
                            max_width=20, height_policy='fit', align='end',
500
                            margin=(0, 0, 5, 10))
501 7
            widget.width = self._widget_box.width-60
502 7
            return Row(widget, toggle, width_policy='max', margin=0)
503
        else:
504 7
            return widget
505

506 7
    @property
507 2
    def _ordered_params(self):
508 7
        params = [(p, pobj) for p, pobj in self.object.param.objects('existing').items()
509
                  if p in self.parameters or p == 'name']
510 7
        key_fn = lambda x: x[1].precedence if x[1].precedence is not None else self.default_precedence
511 7
        sorted_precedence = sorted(params, key=key_fn)
512 7
        filtered = [(k, p) for k, p in sorted_precedence]
513 7
        groups = itertools.groupby(filtered, key=key_fn)
514
        # Params preserve definition order in Python 3.6+
515 7
        dict_ordered_py3 = (sys.version_info.major == 3 and sys.version_info.minor >= 6)
516 7
        dict_ordered = dict_ordered_py3 or (sys.version_info.major > 3)
517 7
        ordered_groups = [list(grp) if dict_ordered else sorted(grp) for (_, grp) in groups]
518 7
        ordered_params = [el[0] for group in ordered_groups for el in group
519
                          if (el[0] != 'name' or el[0] in self.parameters)]
520 7
        return ordered_params
521

522
    #----------------------------------------------------------------
523
    # Model API
524
    #----------------------------------------------------------------
525

526 7
    def _get_widgets(self):
527
        """Return name,widget boxes for all parameters (i.e., a property sheet)"""
528
        # Format name specially
529 7
        if self.expand_layout is Tabs:
530 7
            widgets = []
531 7
        elif self.show_name:
532 7
            widgets = [('_title', StaticText(value='<b>{0}</b>'.format(self.name)))]
533
        else:
534 7
            widgets = []
535 7
        widgets += [(pname, self.widget(pname)) for pname in self._ordered_params]
536 7
        return OrderedDict(widgets)
537

538 7
    def _get_model(self, doc, root=None, parent=None, comm=None):
539 7
        model = self.layout._get_model(doc, root, parent, comm)
540 7
        self._models[root.ref['id']] = (model, parent)
541 7
        return model
542

543 7
    def _cleanup(self, root):
544 7
        self.layout._cleanup(root)
545 7
        super(Param, self)._cleanup(root)
546

547
    #----------------------------------------------------------------
548
    # Public API
549
    #----------------------------------------------------------------
550

551 7
    @classmethod
552 2
    def applies(cls, obj):
553 7
        return (is_parameterized(obj) or
554
                isinstance(obj, param.parameterized.Parameters) or
555
                (isinstance(obj, param.Parameter) and obj.owner is not None))
556

557 7
    @classmethod
558 2
    def widget_type(cls, pobj):
559 7
        ptype = type(pobj)
560 7
        for t in classlist(ptype)[::-1]:
561 7
            if t in cls._mapping:
562 7
                if isinstance(cls._mapping[t], types.FunctionType):
563 7
                    return cls._mapping[t](pobj)
564 7
                return cls._mapping[t]
565

566 7
    def select(self, selector=None):
567
        """
568
        Iterates over the Viewable and any potential children in the
569
        applying the Selector.
570

571
        Arguments
572
        ---------
573
        selector: type or callable or None
574
          The selector allows selecting a subset of Viewables by
575
          declaring a type or callable function to filter by.
576

577
        Returns
578
        -------
579
        viewables: list(Viewable)
580
        """
581 7
        return super().select(selector) + self.layout.select(selector)
582

583 7
    def get_root(self, doc=None, comm=None, preprocess=True):
584
        """
585
        Returns the root model and applies pre-processing hooks
586

587
        Arguments
588
        ---------
589
        doc: bokeh.Document
590
          Bokeh document the bokeh model will be attached to.
591
        comm: pyviz_comms.Comm
592
          Optional pyviz_comms when working in notebook
593
        preprocess: boolean (default=True)
594
          Whether to run preprocessing hooks
595

596
        Returns
597
        -------
598
        Returns the bokeh model corresponding to this panel object
599
        """
600 7
        doc = init_doc(doc)
601 7
        root = self.layout.get_root(doc, comm, preprocess)
602 7
        ref = root.ref['id']
603 7
        self._models[ref] = (root, None)
604 7
        state._views[ref] = (self, root, doc, comm)
605 7
        return root
606

607

608 7
class ParamMethod(ReplacementPane):
609
    """
610
    ParamMethod panes wrap methods on parameterized classes and
611
    rerenders the plot when any of the method's parameters change. By
612
    default ParamMethod will watch all parameters on the class owning
613
    the method or can be restricted to certain parameters by annotating
614
    the method using the param.depends decorator. The method may
615
    return any object which itself can be rendered as a Pane.
616
    """
617

618 7
    def __init__(self, object=None, **params):
619 7
        super(ParamMethod, self).__init__(object, **params)
620 7
        self._link_object_params()
621 7
        if object is not None:
622 7
            self._validate_object()
623 7
            self._update_inner(self.eval(object))
624

625 7
    @param.depends('object', watch=True)
626 2
    def _validate_object(self):
627 7
        dependencies = getattr(self.object, '_dinfo', None)
628 7
        if not dependencies or not dependencies.get('watch'):
629 7
            return
630 0
        fn_type = 'method' if type(self) is ParamMethod else 'function'
631 0
        self.param.warning(f"The {fn_type} supplied for Panel to display "
632
                           "was declared with `watch=True`, which will "
633
                           f"cause the {fn_type} to be called twice for "
634
                           "any change in a dependent Parameter. "
635
                           "`watch` should be False when Panel is "
636
                           "responsible for displaying the result "
637
                           f"of the {fn_type} call, while `watch=True` "
638
                           f"should be reserved for {fn_type}s that work "
639
                           "via side-effects, e.g. by modifying internal  "
640
                           "state of a class or global state in an "
641
                           "application's namespace.")
642

643
    #----------------------------------------------------------------
644
    # Callback API
645
    #----------------------------------------------------------------
646

647 7
    @classmethod
648 2
    def eval(self, function):
649 7
        args, kwargs = (), {}
650 7
        if hasattr(function, '_dinfo'):
651 7
            arg_deps = function._dinfo['dependencies']
652 7
            kw_deps = function._dinfo.get('kw', {})
653 7
            if kw_deps or any(isinstance(d, param.Parameter) for d in arg_deps):
654 7
                args = (getattr(dep.owner, dep.name) for dep in arg_deps)
655 7
                kwargs = {n: getattr(dep.owner, dep.name) for n, dep in kw_deps.items()}
656 7
        return function(*args, **kwargs)
657

658 7
    def _update_pane(self, *events):
659 0
        callbacks = []
660 0
        for watcher in self._callbacks:
661 0
            obj = watcher.inst if watcher.inst is None else watcher.cls
662 0
            if obj is self:
663 0
                callbacks.append(watcher)
664 0
                continue
665 0
            obj.param.unwatch(watcher)
666 0
        self._callbacks = callbacks
667 0
        self._link_object_params()
668 0
        if object is not None:
669 0
            self._update_inner(self.eval(self.object))
670

671 7
    def _link_object_params(self):
672 7
        parameterized = get_method_owner(self.object)
673 7
        params = parameterized.param.params_depended_on(self.object.__name__)
674 7
        deps = params
675

676 7
        def update_pane(*events):
677
            # Update nested dependencies if parameterized object events
678 7
            if any(is_parameterized(event.new) for event in events):
679 7
                new_deps = parameterized.param.params_depended_on(self.object.__name__)
680 7
                for p in list(deps):
681 7
                    if p in new_deps: continue
682 7
                    watchers = self._callbacks
683 7
                    for w in list(watchers):
684 7
                        if (w.inst is p.inst and w.cls is p.cls and
685
                            p.name in w.parameter_names):
686 7
                            obj = p.cls if p.inst is None else p.inst
687 7
                            obj.param.unwatch(w)
688 7
                            watchers.pop(watchers.index(w))
689 7
                    deps.pop(deps.index(p))
690

691 7
                new_deps = [dep for dep in new_deps if dep not in deps]
692 7
                for _, params in full_groupby(new_deps, lambda x: (x.inst or x.cls, x.what)):
693 7
                    p = params[0]
694 7
                    pobj = p.cls if p.inst is None else p.inst
695 7
                    ps = [_p.name for _p in params]
696 7
                    watcher = pobj.param.watch(update_pane, ps, p.what)
697 7
                    self._callbacks.append(watcher)
698 7
                    for p in params:
699 7
                        deps.append(p)
700 7
            new_object = self.eval(self.object)
701 7
            self._update_inner(new_object)
702

703 7
        for _, params in full_groupby(params, lambda x: (x.inst or x.cls, x.what)):
704 7
            p = params[0]
705 7
            pobj = (p.inst or p.cls)
706 7
            ps = [_p.name for _p in params]
707 7
            watcher = pobj.param.watch(update_pane, ps, p.what)
708 7
            self._callbacks.append(watcher)
709

710
    #----------------------------------------------------------------
711
    # Public API
712
    #----------------------------------------------------------------
713

714 7
    @classmethod
715 2
    def applies(cls, obj):
716 7
        return inspect.ismethod(obj) and isinstance(get_method_owner(obj), param.Parameterized)
717

718

719

720 7
class ParamFunction(ParamMethod):
721
    """
722
    ParamFunction panes wrap functions decorated with the param.depends
723
    decorator and rerenders the output when any of the function's
724
    dependencies change. This allows building reactive components into
725
    a Panel which depend on other parameters, e.g. tying the value of
726
    a widget to some other output.
727
    """
728

729 7
    priority = 0.6
730

731 7
    def _replace_pane(self, *args):
732 7
        new_object = self.eval(self.object)
733 7
        self._update_inner(new_object)
734

735 7
    def _link_object_params(self):
736 7
        deps = self.object._dinfo
737 7
        dep_params = list(deps['dependencies']) + list(deps.get('kw', {}).values())
738 7
        grouped = defaultdict(list)
739 7
        for dep in dep_params:
740 7
            grouped[id(dep.owner)].append(dep)
741 7
        for group in grouped.values():
742 7
            watcher = group[0].owner.param.watch(self._replace_pane, [dep.name for dep in group])
743 7
            self._callbacks.append(watcher)
744

745
    #----------------------------------------------------------------
746
    # Public API
747
    #----------------------------------------------------------------
748

749 7
    @classmethod
750 2
    def applies(cls, obj):
751 7
        return isinstance(obj, types.FunctionType) and hasattr(obj, '_dinfo')
752

753

754 7
class JSONInit(param.Parameterized):
755
    """
756
    Callable that can be passed to Widgets.initializer to set Parameter
757
    values using JSON. There are three approaches that may be used:
758
    1. If the json_file argument is specified, this takes precedence.
759
    2. The JSON file path can be specified via an environment variable.
760
    3. The JSON can be read directly from an environment variable.
761
    Here is an easy example of setting such an environment variable on
762
    the commandline:
763
    PARAM_JSON_INIT='{"p1":5}' jupyter notebook
764
    This addresses any JSONInit instances that are inspecting the
765
    default environment variable called PARAM_JSON_INIT, instructing it to set
766
    the 'p1' parameter to 5.
767
    """
768

769 7
    varname = param.String(default='PARAM_JSON_INIT', doc="""
770
        The name of the environment variable containing the JSON
771
        specification.""")
772

773 7
    target = param.String(default=None, doc="""
774
        Optional key in the JSON specification dictionary containing the
775
        desired parameter values.""")
776

777 7
    json_file = param.String(default=None, doc="""
778
        Optional path to a JSON file containing the parameter settings.""")
779

780 7
    def __call__(self, parameterized):
781 7
        warnobj = param.main if isinstance(parameterized, type) else parameterized
782 7
        param_class = (parameterized if isinstance(parameterized, type)
783
                       else parameterized.__class__)
784

785 7
        target = self.target if self.target is not None else param_class.__name__
786

787 7
        env_var = os.environ.get(self.varname, None)
788 7
        if env_var is None and self.json_file is None: return
789

790 7
        if self.json_file or env_var.endswith('.json'):
791 0
            try:
792 0
                fname = self.json_file if self.json_file else env_var
793 0
                spec = json.load(open(os.path.abspath(fname), 'r'))
794 0
            except Exception:
795 0
                warnobj.warning('Could not load JSON file %r' % spec)
796
        else:
797 7
            spec = json.loads(env_var)
798

799 7
        if not isinstance(spec, dict):
800 0
            warnobj.warning('JSON parameter specification must be a dictionary.')
801 0
            return
802

803 7
        if target in spec:
804 0
            params = spec[target]
805
        else:
806 7
            params = spec
807

808 7
        for name, value in params.items():
809 7
            try:
810 7
                parameterized.param.set_param(**{name:value})
811 0
            except ValueError as e:
812 0
                warnobj.warning(str(e))

Read our documentation on viewing source code .

Loading