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

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

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

17 1
import param
18

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

22 1
from .io import state
23 1
from .layout import Row, Panel, Tabs, Column
24 1
from .pane.base import PaneBase, ReplacementPane
25 1
from .util import (
26
    abbreviated_repr, full_groupby, get_method_owner, is_parameterized,
27
    param_name, recursive_parameterized
28
)
29 1
from .viewable import Layoutable
30 1
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 1
from .widgets.button import _ButtonBase
37

38

39 1
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 1
def LiteralInputTyped(pobj):
50 1
    if isinstance(pobj, param.Tuple):
51 0
        return type(str('TupleInput'), (LiteralInput,), {'type': tuple})
52 1
    elif isinstance(pobj, param.Number):
53 0
        return type(str('NumberInput'), (LiteralInput,), {'type': (int, float)})
54 1
    elif isinstance(pobj, param.Dict):
55 0
        return type(str('DictInput'), (LiteralInput,), {'type': dict})
56 1
    elif isinstance(pobj, param.List):
57 1
        return type(str('ListInput'), (LiteralInput,), {'type': list})
58 1
    return LiteralInput
59

60

61 1
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 1
    display_threshold = param.Number(default=0, precedence=-10, doc="""
68
        Parameters with precedence below this value are not displayed.""")
69

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

73 1
    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 1
    expand = param.Boolean(default=False, doc="""
81
        Whether parameterized subobjects are expanded or collapsed on
82
        instantiation.""")
83

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

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

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

93 1
    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 1
    name = param.String(default='', doc="""
99
        Title of the pane.""")
100

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

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

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

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

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

118 1
    priority = 0.1
119

120 1
    _unpack = True
121

122 1
    _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 1
    _rerender_params = []
148

149 1
    def __init__(self, object=None, **params):
150 1
        if isinstance(object, param.Parameter):
151 1
            if not 'show_name' in params:
152 1
                params['show_name'] = False
153 1
            params['parameters'] = [object.name]
154 1
            object = object.owner
155 1
        if isinstance(object, param.parameterized.Parameters):
156 1
            object = object.cls if object.self is None else object.self
157 1
        if 'parameters' not in params and object is not None:
158 1
            params['parameters'] = [p for p in object.param if p != 'name']
159 1
        if object and 'name' not in params:
160 1
            params['name'] = param_name(object.name)
161 1
        super(Param, self).__init__(object, **params)
162 1
        self._updating = []
163

164
        # Construct Layout
165 1
        kwargs = {p: v for p, v in self.param.get_param_values()
166
                  if p in Layoutable.param and v is not None}
167 1
        self._widget_box = self.default_layout(**kwargs)
168

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

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

206
    #----------------------------------------------------------------
207
    # Callback API
208
    #----------------------------------------------------------------
209

210 1
    def _synced_params(self):
211 1
        ignored_params = ['default_layout']
212 1
        return [p for p in Layoutable.param if p not in ignored_params]
213

214 1
    def _update_widgets(self, *events):
215 1
        parameters = []
216 1
        for event in sorted(events, key=lambda x: x.name):
217 1
            if event.name == 'object':
218 1
                if isinstance(event.new, param.parameterized.Parameters):
219 1
                    self.object = event.new.cls if event.new.self is None else event.new.self
220 1
                    return
221 1
                if self.parameters:
222 1
                    parameters = self.parameters
223 1
                elif event.new is None:
224 0
                    parameters = []
225
                else:
226 1
                    parameters = [p for p in event.new.param if p != 'name']
227 1
                    self.name = param_name(event.new.name)
228 1
            if event.name == 'parameters':
229 1
                parameters = [] if event.new == [] else event.new
230

231 1
        if parameters != [] and parameters != self.parameters:
232 1
            self.parameters = parameters
233 1
            return
234

235 1
        for cb in list(self._callbacks):
236 1
            if cb.inst in self._widget_box.objects:
237 1
                cb.inst.param.unwatch(cb)
238 1
                self._callbacks.remove(cb)
239

240
        # Construct widgets
241 1
        if self.object is None:
242 1
            self._widgets = {}
243
        else:
244 1
            self._widgets = self._get_widgets()
245

246 1
        alias = {'_title': 'name'}
247 1
        widgets = [widget for p, widget in self._widgets.items()
248
                   if (self.object.param[alias.get(p, p)].precedence is None)
249
                   or (self.object.param[alias.get(p, p)].precedence >= self.display_threshold)]
250 1
        self._widget_box.objects = widgets
251 1
        if not (self.expand_button == False and not self.expand):
252 1
            self._link_subobjects()
253

254 1
    def _link_subobjects(self):
255 1
        for pname, widget in self._widgets.items():
256 1
            widgets = [widget] if isinstance(widget, Widget) else widget
257 1
            if not any(is_parameterized(getattr(w, 'value', None)) or
258
                       any(is_parameterized(o) for o in getattr(w, 'options', []))
259
                       for w in widgets):
260 1
                continue
261 1
            if (isinstance(widgets, Row) and isinstance(widgets[1], Toggle)):
262 1
                selector, toggle = (widgets[0], widgets[1])
263
            else:
264 1
                selector, toggle = (widget, None)
265

266 1
            def toggle_pane(change, parameter=pname):
267
                "Adds or removes subpanel from layout"
268 1
                parameterized = getattr(self.object, parameter)
269 1
                existing = [p for p in self._expand_layout.objects
270
                            if isinstance(p, Param) and
271
                            p.object in recursive_parameterized(parameterized)]
272 1
                if not change.new:
273 1
                    self._expand_layout[:] = [
274
                        e for e in self._expand_layout.objects
275
                        if e not in existing
276
                    ]
277 1
                elif change.new:
278 1
                    kwargs = {k: v for k, v in self.param.get_param_values()
279
                              if k not in ['name', 'object', 'parameters']}
280 1
                    pane = Param(parameterized, name=parameterized.name,
281
                                 **kwargs)
282 1
                    if isinstance(self._expand_layout, Tabs):
283 1
                        title = self.object.param[pname].label
284 1
                        pane = (title, pane)
285 1
                    self._expand_layout.append(pane)
286

287 1
            def update_pane(change, parameter=pname):
288
                "Adds or removes subpanel from layout"
289 1
                layout = self._expand_layout
290 1
                existing = [p for p in layout.objects if isinstance(p, Param)
291
                            and p.object is change.old]
292

293 1
                if toggle:
294 1
                    toggle.disabled = not is_parameterized(change.new)
295 1
                if not existing:
296 0
                    return
297 1
                elif is_parameterized(change.new):
298 1
                    parameterized = change.new
299 1
                    kwargs = {k: v for k, v in self.param.get_param_values()
300
                              if k not in ['name', 'object', 'parameters']}
301 1
                    pane = Param(parameterized, name=parameterized.name,
302
                                 **kwargs)
303 1
                    layout[layout.objects.index(existing[0])] = pane
304
                else:
305 0
                    layout.pop(existing[0])
306

307 1
            watchers = [selector.param.watch(update_pane, 'value')]
308 1
            if toggle:
309 1
                watchers.append(toggle.param.watch(toggle_pane, 'value'))
310 1
            self._callbacks += watchers
311

312 1
            if self.expand:
313 1
                if self.expand_button:
314 1
                    toggle.value = True
315
                else:
316 1
                    toggle_pane(namedtuple('Change', 'new')(True))
317

318 1
    def widget(self, p_name):
319
        """Get widget for param_name"""
320 1
        p_obj = self.object.param[p_name]
321 1
        kw_widget = {}
322

323 1
        widget_class_overridden = True
324 1
        if self.widgets is None or p_name not in self.widgets:
325 1
            widget_class_overridden = False
326 1
            widget_class = self.widget_type(p_obj)
327 1
        elif isinstance(self.widgets[p_name], dict):
328 1
            if 'type' in self.widgets[p_name]:
329 1
                widget_class = self.widgets[p_name].pop('type')
330
            else:
331 1
                widget_class_overridden = False
332 1
                widget_class = self.widget_type(p_obj)
333 1
            kw_widget = self.widgets[p_name]
334
        else:
335 1
            widget_class = self.widgets[p_name]
336

337 1
        if not self.show_labels and not issubclass(widget_class, _ButtonBase):
338 1
            label = ''
339
        else:
340 1
            label = p_obj.label
341 1
        kw = dict(disabled=p_obj.constant, name=label)
342

343 1
        value = getattr(self.object, p_name)
344 1
        if value is not None:
345 1
            kw['value'] = value
346

347
        # Update kwargs
348 1
        kw.update(kw_widget)
349

350 1
        if hasattr(p_obj, 'get_range'):
351 1
            options = p_obj.get_range()
352 1
            if not options and value is not None:
353 0
                options = [value]
354 1
            kw['options'] = options
355 1
        if hasattr(p_obj, 'get_soft_bounds'):
356 1
            bounds = p_obj.get_soft_bounds()
357 1
            if bounds[0] is not None:
358 1
                kw['start'] = bounds[0]
359 1
            if bounds[1] is not None:
360 1
                kw['end'] = bounds[1]
361 1
            if ('start' not in kw or 'end' not in kw):
362
                # Do not change widget class if _mapping was overridden
363 1
                if not widget_class_overridden:
364 1
                    if (isinstance(p_obj, param.Number) and
365
                        not isinstance(p_obj, (param.Date, param.CalendarDate))):
366 1
                        widget_class = Spinner
367 1
                        if isinstance(p_obj, param.Integer):
368 1
                            kw['step'] = 1
369 0
                    elif not issubclass(widget_class, LiteralInput):
370 0
                        widget_class = LiteralInput
371 1
            if hasattr(widget_class, 'step') and getattr(p_obj, 'step', None):
372 1
                kw['step'] = p_obj.step
373

374 1
        kwargs = {k: v for k, v in kw.items() if k in widget_class.param}
375

376 1
        if isinstance(widget_class, Widget):
377 1
            widget = widget_class
378
        else:
379 1
            widget = widget_class(**kwargs)
380 1
        widget._param_pane = self
381

382 1
        watchers = self._callbacks
383

384 1
        def link_widget(change):
385 1
            if p_name in self._updating:
386 1
                return
387 1
            try:
388 1
                self._updating.append(p_name)
389 1
                self.object.param.set_param(**{p_name: change.new})
390
            finally:
391 1
                self._updating.remove(p_name)
392

393 1
        if isinstance(p_obj, param.Action):
394 1
            def action(change):
395 1
                value(self.object)
396 1
            watcher = widget.param.watch(action, 'clicks')
397
        else:
398 1
            watcher = widget.param.watch(link_widget, 'value')
399 1
        watchers.append(watcher)
400

401 1
        def link(change, watchers=[watcher]):
402 1
            updates = {}
403 1
            if change.what == 'constant':
404 1
                updates['disabled'] = change.new
405 1
            elif change.what == 'precedence':
406 1
                if (change.new < self.display_threshold and
407
                    widget in self._widget_box.objects):
408 1
                    self._widget_box.pop(widget)
409 1
                elif change.new >= self.display_threshold:
410 1
                    precedence = lambda k: self.object.param['name' if k == '_title' else k].precedence
411 1
                    params = self._ordered_params
412 1
                    if self.show_name:
413 1
                        params.insert(0, '_title')
414 1
                    widgets = []
415 1
                    for k in params:
416 1
                        if precedence(k) is None or precedence(k) >= self.display_threshold:
417 1
                            widgets.append(self._widgets[k])
418 1
                    self._widget_box.objects = widgets
419 1
                return
420 1
            elif change.what == 'objects':
421 1
                updates['options'] = p_obj.get_range()
422 1
            elif change.what == 'bounds':
423 1
                start, end = p_obj.get_soft_bounds()
424 1
                updates['start'] = start
425 1
                updates['end'] = end
426 1
            elif change.what == 'step':
427 1
                updates['step'] = p_obj.step
428 1
            elif change.what == 'label':
429 1
                updates['name'] = p_obj.label
430 1
            elif p_name in self._updating:
431 1
                return
432 1
            elif isinstance(p_obj, param.Action):
433 1
                prev_watcher = watchers[0]
434 1
                widget.param.unwatch(prev_watcher)
435 1
                def action(event):
436 0
                    change.new(self.object)
437 1
                watchers[0] = widget.param.watch(action, 'clicks')
438 1
                idx = self._callbacks.index(prev_watcher)
439 1
                self._callbacks[idx] = watchers[0]
440 1
                return
441
            else:
442 1
                updates['value'] = change.new
443

444 1
            try:
445 1
                self._updating.append(p_name)
446 1
                widget.param.set_param(**updates)
447
            finally:
448 1
                self._updating.remove(p_name)
449

450
        # Set up links to parameterized object
451 1
        watchers.append(self.object.param.watch(link, p_name, 'constant'))
452 1
        watchers.append(self.object.param.watch(link, p_name, 'precedence'))
453 1
        watchers.append(self.object.param.watch(link, p_name, 'label'))
454 1
        if hasattr(p_obj, 'get_range'):
455 1
            watchers.append(self.object.param.watch(link, p_name, 'objects'))
456 1
        if hasattr(p_obj, 'get_soft_bounds'):
457 1
            watchers.append(self.object.param.watch(link, p_name, 'bounds'))
458 1
        if 'step' in kw:
459 1
            watchers.append(self.object.param.watch(link, p_name, 'step'))
460 1
        watchers.append(self.object.param.watch(link, p_name))
461

462 1
        options = kwargs.get('options', [])
463 1
        if isinstance(options, dict):
464 1
            options = options.values()
465 1
        if ((is_parameterized(value) or any(is_parameterized(o) for o in options))
466
            and (self.expand_button or (self.expand_button is None and not self.expand))):
467 1
            widget.margin = (5, 0, 5, 10)
468 1
            toggle = Toggle(name='\u22EE', button_type='primary',
469
                            disabled=not is_parameterized(value), max_height=30,
470
                            max_width=20, height_policy='fit', align='end',
471
                            margin=(0, 0, 5, 10))
472 1
            widget.width = self._widget_box.width-60
473 1
            return Row(widget, toggle, width_policy='max', margin=0)
474
        else:
475 1
            return widget
476

477 1
    @property
478
    def _ordered_params(self):
479 1
        params = [(p, pobj) for p, pobj in self.object.param.objects('existing').items()
480
                  if p in self.parameters or p == 'name']
481 1
        key_fn = lambda x: x[1].precedence if x[1].precedence is not None else self.default_precedence
482 1
        sorted_precedence = sorted(params, key=key_fn)
483 1
        filtered = [(k, p) for k, p in sorted_precedence]
484 1
        groups = itertools.groupby(filtered, key=key_fn)
485
        # Params preserve definition order in Python 3.6+
486 1
        dict_ordered_py3 = (sys.version_info.major == 3 and sys.version_info.minor >= 6)
487 1
        dict_ordered = dict_ordered_py3 or (sys.version_info.major > 3)
488 1
        ordered_groups = [list(grp) if dict_ordered else sorted(grp) for (_, grp) in groups]
489 1
        ordered_params = [el[0] for group in ordered_groups for el in group
490
                          if (el[0] != 'name' or el[0] in self.parameters)]
491 1
        return ordered_params
492

493
    #----------------------------------------------------------------
494
    # Model API
495
    #----------------------------------------------------------------
496

497 1
    def _get_widgets(self):
498
        """Return name,widget boxes for all parameters (i.e., a property sheet)"""
499
        # Format name specially
500 1
        if self.expand_layout is Tabs:
501 1
            widgets = []
502 1
        elif self.show_name:
503 1
            widgets = [('_title', StaticText(value='<b>{0}</b>'.format(self.name)))]
504
        else:
505 1
            widgets = []
506 1
        widgets += [(pname, self.widget(pname)) for pname in self._ordered_params]
507 1
        return OrderedDict(widgets)
508

509 1
    def _get_model(self, doc, root=None, parent=None, comm=None):
510 1
        model = self.layout._get_model(doc, root, parent, comm)
511 1
        self._models[root.ref['id']] = (model, parent)
512 1
        return model
513

514 1
    def _cleanup(self, root):
515 1
        self.layout._cleanup(root)
516 1
        super(Param, self)._cleanup(root)
517

518
    #----------------------------------------------------------------
519
    # Public API
520
    #----------------------------------------------------------------
521

522 1
    @classmethod
523
    def applies(cls, obj):
524 1
        return (is_parameterized(obj) or
525
                isinstance(obj, param.parameterized.Parameters) or
526
                (isinstance(obj, param.Parameter) and obj.owner is not None))
527

528 1
    @classmethod
529
    def widget_type(cls, pobj):
530 1
        ptype = type(pobj)
531 1
        for t in classlist(ptype)[::-1]:
532 1
            if t in cls._mapping:
533 1
                if isinstance(cls._mapping[t], types.FunctionType):
534 1
                    return cls._mapping[t](pobj)
535 1
                return cls._mapping[t]
536

537 1
    def select(self, selector=None):
538
        """
539
        Iterates over the Viewable and any potential children in the
540
        applying the Selector.
541

542
        Arguments
543
        ---------
544
        selector: type or callable or None
545
          The selector allows selecting a subset of Viewables by
546
          declaring a type or callable function to filter by.
547

548
        Returns
549
        -------
550
        viewables: list(Viewable)
551
        """
552 1
        return super().select(selector) + self.layout.select(selector)
553

554 1
    def get_root(self, doc=None, comm=None):
555
        """
556
        Returns the root model and applies pre-processing hooks
557

558
        Arguments
559
        ---------
560
        doc: bokeh.Document
561
          Bokeh document the bokeh model will be attached to.
562
        comm: pyviz_comms.Comm
563
          Optional pyviz_comms when working in notebook
564

565
        Returns
566
        -------
567
        Returns the bokeh model corresponding to this panel object
568
        """
569 1
        doc = doc or _curdoc()
570 1
        root = self.layout.get_root(doc, comm)
571 1
        ref = root.ref['id']
572 1
        self._models[ref] = (root, None)
573 1
        state._views[ref] = (self, root, doc, comm)
574 1
        return root
575

576

577 1
class ParamMethod(ReplacementPane):
578
    """
579
    ParamMethod panes wrap methods on parameterized classes and
580
    rerenders the plot when any of the method's parameters change. By
581
    default ParamMethod will watch all parameters on the class owning
582
    the method or can be restricted to certain parameters by annotating
583
    the method using the param.depends decorator. The method may
584
    return any object which itself can be rendered as a Pane.
585
    """
586

587 1
    def __init__(self, object=None, **params):
588 1
        super(ParamMethod, self).__init__(object, **params)
589 1
        self._link_object_params()
590 1
        if object is not None:
591 1
            self._validate_object()
592 1
            self._update_inner(self.eval(object))
593

594 1
    @param.depends('object', watch=True)
595
    def _validate_object(self):
596 1
        dependencies = getattr(self.object, '_dinfo', None)
597 1
        if not dependencies or not dependencies.get('watch'):
598 1
            return
599 0
        fn_type = 'method' if type(self) is ParamMethod else 'function'
600 0
        self.param.warning(f"The {fn_type} supplied for Panel to display "
601
                           "was declared with `watch=True`, which will "
602
                           f"cause the {fn_type} to be called twice for "
603
                           "any change in a dependent Parameter. "
604
                           "`watch` should be False when Panel is "
605
                           "responsible for displaying the result "
606
                           f"of the {fn_type} call, while `watch=True` "
607
                           f"should be reserved for {fn_type}s that work "
608
                           "via side-effects, e.g. by modifying internal  "
609
                           "state of a class or global state in an "
610
                           "application's namespace.")
611

612
    #----------------------------------------------------------------
613
    # Callback API
614
    #----------------------------------------------------------------
615

616 1
    @classmethod
617
    def eval(self, function):
618 1
        args, kwargs = (), {}
619 1
        if hasattr(function, '_dinfo'):
620 1
            arg_deps = function._dinfo['dependencies']
621 1
            kw_deps = function._dinfo.get('kw', {})
622 1
            if kw_deps or any(isinstance(d, param.Parameter) for d in arg_deps):
623 1
                args = (getattr(dep.owner, dep.name) for dep in arg_deps)
624 1
                kwargs = {n: getattr(dep.owner, dep.name) for n, dep in kw_deps.items()}
625 1
        return function(*args, **kwargs)
626

627 1
    def _update_pane(self, *events):
628 0
        callbacks = []
629 0
        for watcher in self._callbacks:
630 0
            obj = watcher.inst if watcher.inst is None else watcher.cls
631 0
            if obj is self:
632 0
                callbacks.append(watcher)
633 0
                continue
634 0
            obj.param.unwatch(watcher)
635 0
        self._callbacks = callbacks
636 0
        self._link_object_params()
637 0
        if object is not None:
638 0
            self._update_inner(self.eval(self.object))
639

640 1
    def _link_object_params(self):
641 1
        parameterized = get_method_owner(self.object)
642 1
        params = parameterized.param.params_depended_on(self.object.__name__)
643 1
        deps = params
644

645 1
        def update_pane(*events):
646
            # Update nested dependencies if parameterized object events
647 1
            if any(is_parameterized(event.new) for event in events):
648 1
                new_deps = parameterized.param.params_depended_on(self.object.__name__)
649 1
                for p in list(deps):
650 1
                    if p in new_deps: continue
651 1
                    watchers = self._callbacks
652 1
                    for w in list(watchers):
653 1
                        if (w.inst is p.inst and w.cls is p.cls and
654
                            p.name in w.parameter_names):
655 1
                            obj = p.cls if p.inst is None else p.inst
656 1
                            obj.param.unwatch(w)
657 1
                            watchers.pop(watchers.index(w))
658 1
                    deps.pop(deps.index(p))
659

660 1
                new_deps = [dep for dep in new_deps if dep not in deps]
661 1
                for _, params in full_groupby(new_deps, lambda x: (x.inst or x.cls, x.what)):
662 1
                    p = params[0]
663 1
                    pobj = p.cls if p.inst is None else p.inst
664 1
                    ps = [_p.name for _p in params]
665 1
                    watcher = pobj.param.watch(update_pane, ps, p.what)
666 1
                    self._callbacks.append(watcher)
667 1
                    for p in params:
668 1
                        deps.append(p)
669 1
            new_object = self.eval(self.object)
670 1
            self._update_inner(new_object)
671

672 1
        for _, params in full_groupby(params, lambda x: (x.inst or x.cls, x.what)):
673 1
            p = params[0]
674 1
            pobj = (p.inst or p.cls)
675 1
            ps = [_p.name for _p in params]
676 1
            watcher = pobj.param.watch(update_pane, ps, p.what)
677 1
            self._callbacks.append(watcher)
678

679
    #----------------------------------------------------------------
680
    # Public API
681
    #----------------------------------------------------------------
682

683 1
    @classmethod
684
    def applies(cls, obj):
685 1
        return inspect.ismethod(obj) and isinstance(get_method_owner(obj), param.Parameterized)
686

687

688

689 1
class ParamFunction(ParamMethod):
690
    """
691
    ParamFunction panes wrap functions decorated with the param.depends
692
    decorator and rerenders the output when any of the function's
693
    dependencies change. This allows building reactive components into
694
    a Panel which depend on other parameters, e.g. tying the value of
695
    a widget to some other output.
696
    """
697

698 1
    priority = 0.6
699

700 1
    def _replace_pane(self, *args):
701 1
        new_object = self.eval(self.object)
702 1
        self._update_inner(new_object)
703

704 1
    def _link_object_params(self):
705 1
        deps = self.object._dinfo
706 1
        dep_params = list(deps['dependencies']) + list(deps.get('kw', {}).values())
707 1
        grouped = defaultdict(list)
708 1
        for dep in dep_params:
709 1
            grouped[id(dep.owner)].append(dep)
710 1
        for group in grouped.values():
711 1
            watcher = group[0].owner.param.watch(self._replace_pane, [dep.name for dep in group])
712 1
            self._callbacks.append(watcher)
713

714
    #----------------------------------------------------------------
715
    # Public API
716
    #----------------------------------------------------------------
717

718 1
    @classmethod
719
    def applies(cls, obj):
720 1
        return isinstance(obj, types.FunctionType) and hasattr(obj, '_dinfo')
721

722

723 1
class JSONInit(param.Parameterized):
724
    """
725
    Callable that can be passed to Widgets.initializer to set Parameter
726
    values using JSON. There are three approaches that may be used:
727
    1. If the json_file argument is specified, this takes precedence.
728
    2. The JSON file path can be specified via an environment variable.
729
    3. The JSON can be read directly from an environment variable.
730
    Here is an easy example of setting such an environment variable on
731
    the commandline:
732
    PARAM_JSON_INIT='{"p1":5}' jupyter notebook
733
    This addresses any JSONInit instances that are inspecting the
734
    default environment variable called PARAM_JSON_INIT, instructing it to set
735
    the 'p1' parameter to 5.
736
    """
737

738 1
    varname = param.String(default='PARAM_JSON_INIT', doc="""
739
        The name of the environment variable containing the JSON
740
        specification.""")
741

742 1
    target = param.String(default=None, doc="""
743
        Optional key in the JSON specification dictionary containing the
744
        desired parameter values.""")
745

746 1
    json_file = param.String(default=None, doc="""
747
        Optional path to a JSON file containing the parameter settings.""")
748

749 1
    def __call__(self, parameterized):
750 1
        warnobj = param.main if isinstance(parameterized, type) else parameterized
751 1
        param_class = (parameterized if isinstance(parameterized, type)
752
                       else parameterized.__class__)
753

754 1
        target = self.target if self.target is not None else param_class.__name__
755

756 1
        env_var = os.environ.get(self.varname, None)
757 1
        if env_var is None and self.json_file is None: return
758

759 1
        if self.json_file or env_var.endswith('.json'):
760 0
            try:
761 0
                fname = self.json_file if self.json_file else env_var
762 0
                spec = json.load(open(os.path.abspath(fname), 'r'))
763 0
            except Exception:
764 0
                warnobj.warning('Could not load JSON file %r' % spec)
765
        else:
766 1
            spec = json.loads(env_var)
767

768 1
        if not isinstance(spec, dict):
769 0
            warnobj.warning('JSON parameter specification must be a dictionary.')
770 0
            return
771

772 1
        if target in spec:
773 0
            params = spec[target]
774
        else:
775 1
            params = spec
776

777 1
        for name, value in params.items():
778 1
            try:
779 1
                parameterized.param.set_param(**{name:value})
780 0
            except ValueError as e:
781 0
                warnobj.warning(str(e))

Read our documentation on viewing source code .

Loading