Newbie question about data loading etc

I am a new user. Hard for me to grasp pn.bind all the way through, would appriciate help.
I would like to have one load_button button which loads the file (if filename has changed) and returns a connection to a database.
The other function, when another run_button when clicked will run, assuming data was loaded.

This is what I have so far but it does not work due to hashing issues of the input.

input_file = pn.widgets.TextInput()
load_button = pn.widgets.Button()
question = pn.widgets.TextInput() # question on the data
answer_button = pn.widgets.Button()

to bind these I do

con = pn.bind(get_db_connection, input_file=input_file, loading=load_button) # returns db connection
answer = pn.bind(run, running=answer_button, con=con, question=question) # get answer from calc

and the underlying methods are

@pn.cache
def run(running, con, question, nemo):
    if not running:
        yield "Calculation did not run yet"
        return
    if con is None:
        yield "Data has not been loaded"
    else:
        yield [1,2,3]

@pn.cache
def get_db_connection(input_file, loading):
    if loading:
        con = csv_to_db(input_file)
        yield con
    else:
        yield None

I get:

ValueError: User hash function <function _container_hash at 0x122b98430> failed for input {'running': False, 'con': <generator object get_db_connection at 0x1312ef920>, 'question': ''} with following error: ValueError("User hash function <function _container_hash at 0x122b98430> failed for input ('con', <generator object get_db_connection at 0x1312ef920>) with following error: ValueError("Could not hash object of type generator").").

Since con cannot be hashed. However, I am not sure I even understand what’s going on. Does con hold the connection to the database or not?
Maybe I should have global con variable?

Would appreciate any help

1 Like

Not certain I understand your goals.

You can use Button.on_click or bind and provide it the function you want to invoke. on_click might be a little easier to reason about.

https://panel.holoviz.org/reference/widgets/Button.html

text = pn.widgets.TextInput(value='Ready')

def b(event):
    text.value = 'Clicked {0} times'.format(button.clicks)
    
button.on_click(b)
pn.Row(button, text)

Also, is the input_file a File or a Database Connection string? This is an example of using a db connection string SQLite my_sqlite.db and how I like to work with the data returned as a list of dict (or an array of objects in JSON speak) …

    sql = f"""
    SELECT * FROM some_table
    """
    con = sqlite3.connect("my_sqlite.db")    
    cur = con.cursor()
    res = cur.execute(sql)
    column_names = [t[0] for t in cur.description]
    result = res.fetchall() 
    result_array = [dict(zip(column_names, values)) for values in result]

If it is a file you want to work with I suggest looking at FileInput
https://panel.holoviz.org/reference/widgets/FileInput.html

1 Like

Thanks for answering. The question was not about sqlite. I am fine with that part.
It was about @cache that get the output of yield.
Essentially I have 1 button to load the data and 2nd button to run the analysis.
I would like the output of the load_data function to be sent to the run_analysis function

So I did this just to get the buttons and binds to work and see what cache does (only outputs it once) and to test the use of yield (which does not work). Your con value is the bound function so it is not the con return value, note I used variables outside of the function. But, I’m still not 100% following what you are trying to do with cache.

I think there are multiple issues.

  1. when you do bind it returns the function because it is a delayed callback you can’t set it equal to con or answer.
  2. yield (and generators) I tend to stay away from unless you have a very explicit need for it, I don’t think they work in bind event functions from what I tested
  3. bind gets call twice for the first time, not exactly certain why–I think I’ve seen something like this before
import panel as pn

# Define your Panel layout here
layout = pn.Column('# Hello, World!')
input_file = pn.widgets.TextInput()
load_button = pn.widgets.Button()
question = pn.widgets.TextInput() # question on the data
answer_button = pn.widgets.Button()


running = False
loading = False
con = None

@pn.cache
#def run(running, con, question, nemo):
def run(watch, running, con, question):
    print("run", watch, running, con, question)
    if not running:
        return "Calculation did not run yet"
    if con is None:
        return "Data has not been loaded"
    else:
        return [1,2,3]

@pn.cache
#def get_db_connection(input_file, loading):
def get_db_connection(watch, input_file, loading):
    print("get_db_connection", watch, input_file, loading)
    if loading:
        con = "csv_to_db(input_file)"
        return con
    else:
        return None

pn.bind(get_db_connection, load_button, watch=True, input_file=input_file, loading=load_button) # returns db connection
pn.bind(run, answer_button, watch=True, running=answer_button, con=con, question=question) # get answer from calc


layout.append(pn.Row(input_file, load_button))
layout.append(pn.Row(question, answer_button))


layout.servable()

Thanks, according to documentation one needs to use yield rather than return for bindining.
What I am trying to do is pretty straight forward:

  1. load button to load data (cached according to file name)
  2. run button which gets the loaded data and returns an output. If file is not loaded it tells load file first.

The output of the load button is a connection to a database (temporary one created dynanically from csv files)

Hi @chanansh

Welcome to the community. I believe the problem is that you are using yield instead of return. Try using return.

If you know where you saw that you have to use yield with bind please let us know because that is not the case. Thanks.

1 Like