holoviz / panel

Panel: The powerful data exploration & web app framework for Python
https://panel.holoviz.org
BSD 3-Clause "New" or "Revised" License
4.69k stars 508 forks source link

Deep link: failed in setting param.Integer to None with allow_None=True #1885

Open thuydotm opened 3 years ago

thuydotm commented 3 years ago

ALL software version info

panel 0.10.0

Description of expected behavior and the observed behavior

Please see the example below. tab_id is a param.Integer object with allow_None=True. When directly set tab_id to None in the URL, panel reads it as a string of 'None' instead of a NoneType object. And this leads to validation fail.

Complete, minimal, self-contained example code that reproduces the issue

import param
import panel as pn
class Settings(param.Parameterized):
    tab_id = param.Integer(allow_None=True)
    def __init__(self, **params):
        super().__init__(**params)
        pn.state.location.sync(self)
settings = Settings()
tabs = pn.Tabs(('Tab0', 0), ('Tab1', 1), active=settings.tab_id)
def update_tab(event):
    tabs.active = event.new
def update_setting(event):
    settings.tab_id = event.new
tabs.param.watch(update_setting, 'active')
settings.param.watch(update_tab, 'tab_id')
tabs.servable()

Set the url as: http://localhost:5006/test_deeplink?tab_id=None

Stack traceback and/or browser JavaScript console output

2021-01-05 10:44:40,577 Exception in callback functools.partial(<bound method IOLoop._discard_future_result of <tornado.platform.asyncio.AsyncIOMainLoop object at 0x7fc0a6b699e8>>, <Task finished coro=<_needs_document_lock.<locals>._needs_document_lock_wrapper() done, defined at /home/mdo/.local/lib/python3.7/site-packages/bokeh/server/session.py:51> exception=ValueError("Parameter 'tab_id' must be an integer.")>)
Traceback (most recent call last):
  File "/home/mdo/.local/lib/python3.7/site-packages/tornado/ioloop.py", line 743, in _run_callback
    ret = callback()
  File "/home/mdo/.local/lib/python3.7/site-packages/tornado/ioloop.py", line 767, in _discard_future_result
    future.result()
  File "/home/mdo/.local/lib/python3.7/site-packages/bokeh/server/session.py", line 71, in _needs_document_lock_wrapper
    result = await result
  File "/home/mdo/.local/lib/python3.7/site-packages/tornado/gen.py", line 191, in wrapper
    result = func(*args, **kwargs)
  File "/home/mdo/.local/lib/python3.7/site-packages/panel/reactive.py", line 194, in _change_coroutine
    self._change_event(doc)
  File "/home/mdo/.local/lib/python3.7/site-packages/panel/reactive.py", line 204, in _change_event
    self._process_events(events)
  File "/home/mdo/.local/lib/python3.7/site-packages/panel/reactive.py", line 187, in _process_events
    self.param.set_param(**self._process_property_change(events))
  File "/home/mdo/.local/lib/python3.7/site-packages/param/parameterized.py", line 1365, in set_param
    self_._batch_call_watchers()
  File "/home/mdo/.local/lib/python3.7/site-packages/param/parameterized.py", line 1480, in _batch_call_watchers
    watcher.fn(*events)
  File "/home/mdo/.local/lib/python3.7/site-packages/panel/io/location.py", line 108, in _update_synced
    p.param.set_param(**mapped)
  File "/home/mdo/.local/lib/python3.7/site-packages/param/parameterized.py", line 1358, in set_param
    setattr(self_or_cls, k, v)
  File "/home/mdo/.local/lib/python3.7/site-packages/param/parameterized.py", line 294, in _f
    instance_param.__set__(obj, val)
  File "/home/mdo/.local/lib/python3.7/site-packages/param/parameterized.py", line 296, in _f
    return f(self, obj, val)
  File "/home/mdo/.local/lib/python3.7/site-packages/param/__init__.py", line 623, in __set__
    super(Dynamic,self).__set__(obj,val)
  File "/home/mdo/.local/lib/python3.7/site-packages/param/parameterized.py", line 296, in _f
    return f(self, obj, val)
  File "/home/mdo/.local/lib/python3.7/site-packages/param/parameterized.py", line 823, in __set__
    self._validate(val)
  File "/home/mdo/.local/lib/python3.7/site-packages/param/__init__.py", line 937, in _validate
    raise ValueError("Parameter '%s' must be an integer."%self.name)
ValueError: Parameter 'tab_id' must be an integer.
jbednar commented 3 years ago

Panel support for allow_None is definitely patchy, because Param supports it consistently but GUI toolkits don't typically handle None directly. I'm not sure what the best way to address this would be. For this particular example for numeric types Panel could convert the string "None" to the Python attribute None, or maybe Param could be changed to accept both None and "None", but that won't work for string types, where we'd need to distinguish between None and "None". And in general the widget libraries don't provide ways of setting something numeric or string type to None. So it's a tricky area...