Repository: holoviz/panel

Issue:
Breaking Change: 0.14.0 -> 0.14.1 Tabulator empty df
#### ALL software version info
(this library, plus any other relevant software, e.g. bokeh, python, notebook, OS, browser, etc)
https://gist.github.com/tomascsantos/6609dabc4f45e26758d1be5eb3ac6df2

#### Description of expected behavior and the observed behavior
Updating hidden columns on an empty dataframe should not crash tabulator. It didn't used to, but now it does.

#### Complete, minimal, self-contained example code that reproduces the issue

```python
import pandas as pd
import panel as pn


class Test(pn.viewable.Viewer):

    def __init__(self):
        super().__init__()

        self.btn = pn.widgets.Button(name='break things',)
        self.btn.on_click(self._test_event)

        self._tabulator = pn.widgets.Tabulator(
            pd.DataFrame(),
            show_index=False,
        )

    def _test_event(self, *args):
        """When button is pressed break things"""
        self._tabulator.hidden_columns = ['a', 'b', 'c']

    def __panel__(self):
        return pn.Column(self.btn, self._tabulator)


Test().servable()
```

#### Stack traceback and/or browser JavaScript console output

```
2022-11-10 16:12:52,567 error handling message
 message: Message 'PATCH-DOC' content: {'events': [{'kind': 'MessageSent', 'msg_type': 'bokeh_event', 'msg_data': {'event_name': 'button_click', 'event_values': {}}}], 'references': []} 
 error: NameError("name 'pd' is not defined")
Traceback (most recent call last):
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/bokeh/server/protocol_handler.py", line 97, in handle
    work = await handler(message, connection)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/bokeh/server/session.py", line 95, in _needs_document_lock_wrapper
    result = func(self, *args, **kwargs)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/bokeh/server/session.py", line 289, in _handle_patch
    message.apply_to_document(self.document, self)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/bokeh/protocol/messages/patch_doc.py", line 115, in apply_to_document
    invoke_with_curdoc(doc, lambda: doc.apply_json_patch(self.content, setter))
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/bokeh/document/callbacks.py", line 408, in invoke_with_curdoc
    return f()
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/bokeh/protocol/messages/patch_doc.py", line 115, in <lambda>
    invoke_with_curdoc(doc, lambda: doc.apply_json_patch(self.content, setter))
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/bokeh/document/document.py", line 391, in apply_json_patch
    DocumentPatchedEvent.handle_json(self, event_json, references, setter)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/bokeh/document/events.py", line 259, in handle_json
    handler(doc, event_json, references, setter)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/bokeh/document/events.py", line 300, in _handle_json
    cb(event_json["msg_data"])
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/bokeh/document/callbacks.py", line 355, in trigger_json_event
    model._trigger_event(event)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/bokeh/util/callback_manager.py", line 119, in _trigger_event
    self.document.callbacks.notify_event(cast(Model, self), event, invoke)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/bokeh/document/callbacks.py", line 247, in notify_event
    invoke_with_curdoc(doc, callback_invoker)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/bokeh/document/callbacks.py", line 408, in invoke_with_curdoc
    return f()
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/bokeh/util/callback_manager.py", line 115, in invoke
    cast(EventCallbackWithEvent, callback)(event)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/panel/reactive.py", line 428, in _server_event
    self._comm_event(doc, event)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/panel/reactive.py", line 415, in _comm_event
    state._handle_exception(e)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/panel/io/state.py", line 399, in _handle_exception
    raise exception
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/panel/reactive.py", line 413, in _comm_event
    self._process_bokeh_event(doc, event)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/panel/reactive.py", line 350, in _process_bokeh_event
    self._process_event(event)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/panel/widgets/button.py", line 184, in _process_event
    self.clicks += 1
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/param/parameterized.py", line 367, in _f
    instance_param.__set__(obj, val)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/param/parameterized.py", line 369, in _f
    return f(self, obj, val)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/param/__init__.py", line 625, in __set__
    super(Dynamic,self).__set__(obj,val)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/param/parameterized.py", line 369, in _f
    return f(self, obj, val)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/param/parameterized.py", line 1248, in __set__
    obj.param._call_watcher(watcher, event)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/param/parameterized.py", line 2039, in _call_watcher
    self_._execute_watcher(watcher, (event,))
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/param/parameterized.py", line 2021, in _execute_watcher
    watcher.fn(*args, **kwargs)
  File "/Users/tcastrosantos/Documents/dashboard-portal/dashboards/bms-developers/mvp/app.py", line 20, in _test_event
    self._tabulator.hidden_columns = ['a', 'b', 'c']
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/param/parameterized.py", line 369, in _f
    return f(self, obj, val)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/param/parameterized.py", line 1248, in __set__
    obj.param._call_watcher(watcher, event)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/param/parameterized.py", line 2039, in _call_watcher
    self_._execute_watcher(watcher, (event,))
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/param/parameterized.py", line 2021, in _execute_watcher
    watcher.fn(*args, **kwargs)
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/panel/reactive.py", line 298, in _param_change
    msg = self._process_param_change({event.name: event.new})
  File "/opt/anaconda3/envs/holoviz/lib/python3.9/site-packages/panel/widgets/tables.py", line 1213, in _process_param_change
    if not self.show_index and self.value is not None and not isinstance(self.value.index, pd.MultiIndex):
NameError: name 'pd' is not defined

```
Clicking this throws an error:

<img width="431" alt="image" src="https://user-images.githubusercontent.com/7690514/201232626-b1185b4e-c11b-4aa4-9141-198decdedf83.png">



Discussion:
The pandas import is hidden behind a `TYPE_CHECKING` and is, therefore, never imported when running as a script. I will fix this in a PR today. 

https://github.com/holoviz/panel/blob/0627261f254b7eb0a015639ce06b1669f90b45a0/panel/widgets/tables.py#L38-L43

And I can get the error even with something like this:
``` python
import pandas as pd
import panel as pn

_tabulator = pn.widgets.Tabulator(
    pd.DataFrame(),
    show_index=False,
)
_tabulator.hidden_columns = ["a", "b", "c"]
```

Resolving patch:
diff --git a/panel/widgets/tables.py b/panel/widgets/tables.py
--- a/panel/widgets/tables.py
+++ b/panel/widgets/tables.py
@@ -1208,6 +1208,7 @@ def _get_theme(self, theme, resources=None):
         return theme_url, theme
 
     def _process_param_change(self, msg):
+        import pandas as pd
         msg = super()._process_param_change(msg)
         if 'hidden_columns' in msg:
             if not self.show_index and self.value is not None and not isinstance(self.value.index, pd.MultiIndex):


Test patch:
diff --git a/panel/tests/widgets/test_tables.py b/panel/tests/widgets/test_tables.py
--- a/panel/tests/widgets/test_tables.py
+++ b/panel/tests/widgets/test_tables.py
@@ -1918,3 +1918,12 @@ def test_tabulator_styling_empty_dataframe():
             4: [('border-color', '#dc3545'), ('border-style', 'solid')]
         }
     }
+
+
+def test_tabulator_pandas_import():
+    # Checks for: https://github.com/holoviz/panel/issues/4102
+    _tabulator = Tabulator(
+        pd.DataFrame(),
+        show_index=False,
+    )
+    _tabulator.hidden_columns = ["a", "b", "c"]