Pass updated widgets values to a function

Hello!

I am currently learning the Panel library while attempting to create something useful for my work. Below is the code, which serves as a minimum working example extracted from a larger project. My goal is to update the content of the “Graph” tab with some other data. The code reads an intake catalog file using the input file provided. After clicking a button, the file is saved to disk (credit to Marc for providing this code - thank you!). Then, I utilize the data source names from within the catalog to update and populate the widgets (this piece of code was also provided by other community fellow - thank you!). However, I am facing an issue with updating the “Graph” tab when the widget values change. Currently, my code doesn’t handle the updated widgets correctly, specifically when passing them to the plotGraph function. Does anyone know how to achieve it?

My code:

import os
import intake
import panel as pn

pn.extension()

# Widgets to choose and read the catalog file
file_input = pn.widgets.FileInput(name='Choose data catalog', accept='yml', mime_type='text/yml', multiple= False)
read_catalog = pn.widgets.Button(name='Read data catalog', button_type='primary')

# Lists that will be updated once the catalog file is read
Regs = []
StatsT = []
Exps = []
Refs = []

# Widgets that will hold the lists options (they will be also updated once the catalog is read)
WRegs = pn.widgets.Select(name='Region', options=Regs) 
WStatsT = pn.widgets.Select(name='Statistic', options=StatsT) 
WExps = pn.widgets.Select(name='Experiment', options=Exps) 
WRefs = pn.widgets.Select(name='Reference', options=Refs) 

# This logical variable is used to tell the plotGraph function if there's something to display
global loaded
loaded = False

# The plotGraph function depends on the updated widgets
# Note: at this point, I just want to output the filename instead of an actual graph
@pn.depends(WRegs, WStatsT, WExps, WRefs)
def plotGraph(WRegs, WStatsT, WExps, WRefs):
    if loaded:
        kname = 'test-' + WRegs + '-' + WStatsT + '-' + WExps + '-' + WRefs + '-table'
        ax = pn.pane.Str(kname)
    else:
        ax = pn.pane.Str('Nothing to show!')    
    return ax
        
# Callback function for the 'Read data catalog' (read_catalog) button - here, all the lists and widgets are updated
def readCatalog(event):
    global data_catalog, loaded
    
    # Define the file name (this file will be saved on disk)
    fname = os.path.join(os.getcwd(), 'catalog.yml')
    
    try:
        
        # Open the catalog file
        data_catalog = intake.open_catalog(os.path.join(os.getcwd(), 'catalog.yml'))

        # Update the logical variable
        loaded = True
        
        global Regs, StatsT, Exps, Refs

        # For each data source in the catalog data, reads the entry name
        # and get the attributes
        for source in data_catalog:
            attrs = source.split('-')
            Regs.append(attrs[1])
            if attrs[5] == 'field':
                pass
            else:
                StatsT.append(attrs[2].upper())
                Exps.append(attrs[3].upper())
                Refs.append(attrs[4])

        # Remove repeated elements
        Regs = [*set(Regs)]
        StatsT = [*set(StatsT)]
        Exps = [*set(Exps)]
        Refs = [*set(Refs)]
        
        # Update the widgets
        WRegs = pn.widgets.Select(name='Region', value=Regs[0], options=Regs) 
        WStatsT = pn.widgets.Select(name='Statistic', value=StatsT[0], options=StatsT) 
        WExps = pn.widgets.Select(name='Experiment', value=Exps[0], options=Exps) 
        WRefs = pn.widgets.Select(name='Reference', value=Refs[0], options=Refs) 
        
        Winpt.objects = pn.Column(file_input, read_catalog, WRegs, WStatsT, WExps, WRefs, width=400).objects
        
    except IOError:
        
        print('File ' + fname + ' does not exist!')

read_catalog.on_click(readCatalog)

Winpt = pn.Column(file_input, read_catalog, WRegs, WStatsT, WExps, WRefs, width=400)

# Function to save the catalog file on disk
@pn.depends(file_input, watch=True)
def save(value):
    if file_input.value is not None:
        file_input.save(os.path.join(os.getcwd(),'catalog.yml'))
        print('Catalog file successfully saved!')
    else:
        print('Nothing to save.') 

# Tab where the plogGraph should be placed
Tabs = pn.Tabs(('Graph', plotGraph), dynamic=False)    
    
# Serve    
pn.Row(Winpt, Tabs).servable()

For testing, you can grab the sample catalog from here: https://github.com/cfbastarz/teste/blob/main/catalog.yml

How it works:

Any assistance or guidance would be greatly appreciated.

Thank you!

1 Like

The main problem is that your plotGraph function is never rerun because does not depends on loaded.

Below I’ve changed loaded to a widget and added it to the list of dependencies.

import os
import intake
import panel as pn

pn.extension()

# Widgets to choose and read the catalog file
file_input = pn.widgets.FileInput(name='Choose data catalog', accept='yml', mime_type='text/yml', multiple= False)
read_catalog = pn.widgets.Button(name='Read data catalog')

# Widgets that will hold the lists options (they will be also updated once the catalog is read)
WRegs = pn.widgets.Select(name='Region', disabled=True) 
WStatsT = pn.widgets.Select(name='Statistic', disabled=True) 
WExps = pn.widgets.Select(name='Experiment', disabled=True) 
WRefs = pn.widgets.Select(name='Reference', disabled=True) 

# This logical variable is used to tell the plotGraph function if there's something to display
loaded = pn.widgets.Checkbox(name="Catalogue Loaded", value=False, disabled=True)

# The plotGraph function depends on the updated widgets
# Note: at this point, I just want to output the filename instead of an actual graph
@pn.depends(WRegs, WStatsT, WExps, WRefs, loaded)
def plotGraph(WRegs, WStatsT, WExps, WRefs, loaded):
    if loaded and WRefs and WStatsT and WExps and WRefs:
        kname = 'test-' + WRegs + '-' + WStatsT + '-' + WExps + '-' + WRefs + '-table'
        ax = pn.pane.Str(kname)
    else:
        ax = pn.pane.Str('Nothing to show!')    
    return ax
        
# Callback function for the 'Read data catalog' (read_catalog) button - here, all the lists and widgets are updated
def readCatalog(event):
    global data_catalog
    
    # Define the file name (this file will be saved on disk)
    fname = os.path.join(os.getcwd(), 'catalog.yml')
    
    try:
        
        # Open the catalog file
        data_catalog = intake.open_catalog(os.path.join(os.getcwd(), 'catalog.yml'))

        # Update the logical variable
        loaded.value=True
        
        # For each data source in the catalog data, reads the entry name
        # and get the attributes
        Regs=[]
        StatsT=[]
        Exps=[]
        Refs=[]
        
        for source in data_catalog:
            attrs = source.split('-')
            Regs.append(attrs[1])
            if attrs[5] == 'field':
                pass
            else:
                StatsT.append(attrs[2].upper())
                Exps.append(attrs[3].upper())
                Refs.append(attrs[4])

        # Update the widgets
        WRegs.options=[*set(Regs)]
        WStatsT.options=[*set(StatsT)]
        WExps.options=[*set(Exps)]
        WRefs.options=[*set(Refs)]

        WRefs.disabled=WExps.disabled=WStatsT.disabled=WRegs.disabled=False
        read_catalog.visible=False
        
    except IOError:
        
        print('File ' + fname + ' does not exist!')

read_catalog.on_click(readCatalog)

Winpt = pn.Column(file_input, WRegs, WStatsT, WExps, WRefs, read_catalog, loaded, width=400)

# Function to save the catalog file on disk
@pn.depends(file_input, watch=True)
def save(value):
    if file_input.value is not None:
        file_input.save(os.path.join(os.getcwd(),'catalog.yml'))
        print('Catalog file successfully saved!')
    else:
        print('Nothing to save.') 

# Tab where the plogGraph should be placed
Tabs = pn.Tabs(('Graph', plotGraph), dynamic=False)    
    
# Serve    
pn.Row(Winpt, Tabs).servable()

Some suggestions :+1: for further improvements

  • Try using Python style guide pep8. Specifically use snake case for variables and functions. It will make it easier for other Pythonistas to read.
  • Avoid using global
    • Consider using a Parameterized class to store your state. I.e. to store the data_catalog, WRegs, WStatsT, WExps and WRefs parameters values.
1 Like

Hi @Marc, your suggestions are very welcome. Thank you very much!

1 Like