Hi and thank you in advance.
I am new to Panel and am trying to put together a fairly simple UI + data processing pipeline.
I successfully set up a text input widget that receives text input and submits it with a button and has a loading indicator until the next step is complete, but I am getting the following error:
ValueError("Could not hash object of type function")
Here’s how my code is setup (using Python 3.9)
import panel as pn
import json
from gspread import Client
from authlib.integrations.requests_client import AssertionSession
pn.extension('tabulator')
SCOPES = [
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive',
]
CONF_FILE = "path_to_file.json"
# Text input component
spreadsheet_name = pn.widgets.TextInput(
name="Spreadsheet Title", placeholder="Enter *title* of your spreadsheet")
# Submit button
submit_button = pn.widgets.Button(name='Get Data', button_type='primary')
# loading indicator
loading_indicator = pn.indicators.LoadingSpinner(
value=False, width=50, height=50)
@pn.cache
def get_data(spreadsheet_name):
loading_indicator.value = True
submit_button.button_type = 'default'
submit_button.disabled = True
try:
session = create_assertion_session(CONF_FILE, SCOPES)
gc = Client(None, session)
wks = gc.open(spreadsheet_name).sheet1
# wks = gc.open("Movavi-Brazil-20221130-20231130").sheet1
df = pd.DataFrame(wks.get_all_records())
df.Item.replace('', np.nan, inplace=True)
df.dropna(subset=['Item'], inplace=True)
df['Total'] = df['Total'].apply(lambda x: x.strip("$"))
df['Total'] = df['Total'].astype(float)
print(df.head().to_markdown(index=False,
numalign="left", stralign="left"))
return df
except Exception as e:
print(f"Error occured: {e}")
return pn.pane.Markdown(f"**Error: {e}")
# return pn.pane.Markdown("**Error: Spreadsheet not found.** Please check the title")
finally:
loading_indicator.value = False
submit_button.button_type = 'primary'
submit_button.disabled = False
def create_assertion_session(conf_file, scopes, subject=None):
with open(conf_file, 'r') as f:
conf = json.load(f)
token_url = conf['token_uri']
issuer = conf['client_email']
key = conf['private_key']
key_id = conf.get('private_key_id')
header = {'alg': 'RS256'}
if key_id:
header['kid'] = key_id
# Google puts scope in payload
claims = {'scope': ' '.join(scopes)}
return AssertionSession(
grant_type=AssertionSession.JWT_BEARER_GRANT_TYPE,
token_endpoint=token_url,
issuer=issuer,
audience=token_url,
claims=claims,
subject=subject,
key=key,
header=header,
)
# Attach the functions to widgets
spreadsheet_name.param.watch(get_data, 'value')
# create template to organize components
template = pn.template.BootstrapTemplate(title='Channel Spend Analysis')
template.main.append(
pn.Row(
pn.Column(
spreadsheet_name, submit_button, loading_indicator, width=700),
pn.Column(column_selector_component, model_output_text, width=600)
)
)
template.servable()
I have been able to execute the text input and reading the spreadsheet using
@pn.cache in the same way in a different function, but there I bound the function and variable text input. Is
spreadsheet_name.param.watch(get_data, 'value')
not the correct way to invoke get_data when receiving the value of spreadsheet_name?
Thank you!!