Handling returns from button.on_click

Hello,

In my dashboard, I am trying to create a button that gets data from a number of sources and saves it into a Pandas DataFrame, like this:

import pandas as pd
import panel as pn

df = 'Whatever'
button = pn.widgets.Button(name='Get data', button_type='primary')

def getData(event):
    df = pd.read_csv('mydata.csv')
    return df

df = button.on_click(getData)

button

With the code above, pushing the button leaves the df variable empty, since on_click doesn’t actually return anything.

I have worked around the problem by using a global variable to change df directly from within the getData function, like this:

def getData(event):
    global df 
    df = pd.read_csv('mydata.csv')

button.on_click(getData)

I am quite new to Python and even newer to coding UI:s, but I have read that using global variables should be avoided as much as possible, so I am wondering whether there is a better way to do this?

HI @dabbatelli

Welcome to the community. Good question.

There are several ways to accomplish what you want to do. I love to used param.Parameterized classes. The reason being

  • You will get code that can be used from code and without the UI. But of course also from the UI.
  • You will get code that can be maintained and further developed over time. I.e. a solution that can scale.

So I will provide a solution based on param.Parameterized.

I know there are a few things to learn if you are a new user to Python and Panel. Feel free to ask questions.

I hope others will contribute their preferred solution.

image

"""Answer to [Discourse Question]\
(https://discourse.holoviz.org/t/handling-returns-from-button-on-click/733)"""
import pandas as pd
import panel as pn
import param

class MyDataframeExtractorApp(param.Parameterized):
   """Enables a user to extract and view a DataFrame"""

   data = param.DataFrame(precedence=2)
   extract = param.Action(label="Get data", precedence=1)
   view = param.Parameter()

   def __init__(self, **params):
       params["data"] = pd.DataFrame()
       params["extract"] = self._extract
       params["view"] = self._get_view()

       super().__init__(**params)

   def _extract(self, event=None):
       # self.data = pd.read_csv('mydata.csv')
       self.data = pd.DataFrame({"x": [1, 2, 3], "y": [2, 4, 6]})

   def _get_view(self):
       top_app_bar = pn.Row(
           pn.pane.Markdown("# Panel Data Extractor"),
           sizing_mode="stretch_width",
       )

       content = pn.Param(
           self,
           parameters=["extract", "data"],
           widgets={
               "extract": {"button_type": "success", "width": 150, "sizing_mode": "fixed"},
               "data": {
                   "height": 600
               },  # Needed due to https://github.com/holoviz/panel/issues/919
           },
           show_name=False,
       )

       return pn.Column(
           top_app_bar,
           content,
           max_width=1000,
           sizing_mode="stretch_width",
       )

app = MyDataframeExtractorApp()
app.view # In notebook
# app.view.servable() # with panel serve

Hello!

I had been reading about parameterized classes in the documentation, but I had not looked into it closely: I guess now it is a good time to start reading more about it.

Thanks for the great example!

1 Like