gradio-app / gradio

Build and share delightful machine learning apps, all in Python. 🌟 Star to support our work!
http://www.gradio.app
Apache License 2.0
32k stars 2.38k forks source link

[Blocks] Support functions that run on page load and functions that don't have an output #651

Closed abidlabs closed 2 years ago

abidlabs commented 2 years ago

2 related issues:

  1. We've gotten requests from the Hugging Face team to have functions that run on page load. For example, it could be something as simple as a table that refreshes its content by loading from an external URL whenever you reload the page.

Implementation wise, it might look something like this (notice there is no inputs here):

import pandas as pd

def get_data():
   url="https://raw.githubusercontent.com/cs109/2014_data/master/countries.csv"
   return pd.read_csv(url)

with Blocks() as blocks:
   dataframe = outputs.DataFrame()
   blocks.load(fn=get_data, outputs=dataframe)
   ...
  1. We've also had requests for functions to call an external API after the prediction has been made:

image

Implementation wise, it might look something like this (notice there is no outputs here):

import requests

def get_data(data):
   url="https://raw.githubusercontent.com/cs109/2014_data/master/countries.csv"
   requests.posts(url, data=data)

with Blocks() as blocks:
   dataframe = inputs.DataFrame()
   btn = Button()
   btn.click(fn=post_data, inputs=dataframe)
   ...
omerXfaruq commented 2 years ago

Both feels doable to me.

  1. We would have a field of page_load in our config json and frontend would respect that at the initial load of the webpage.
  2. In this instance this event would have no output, and frontend would work according to this.

WDYT @pngwn?

julien-c commented 2 years ago

Example of Space that needs (and hacks) a on_page_load feature: https://huggingface.co/spaces/julien-c/persistent-data

omerXfaruq commented 2 years ago
  1. We've gotten requests from the Hugging Face team to have functions that run on page load. For example, it could be something as simple as a table that refreshes its content by loading from an external URL whenever you reload the page.

Let's add load into dependencies like this, WDYT @aliabid94 @pngwn @abidlabs? No component has id 0(and Blocks actually have id 0 within python Context), so let's make use of it like this. Updated the json in notion as well.

"dependencies": [
        {
            "targets": [0],
            "trigger": "load",
            "inputs": [],
            "outputs": [],
            "queue": False,
        },
        {
            "targets": [0],
            "trigger": "load",
            "inputs": [1],
            "outputs": [],
            "queue": False,
        },

        {
            "targets": [0],
            "trigger": "load",
            "inputs": [],
            "outputs": [1],
            "queue": False,
        },
omerXfaruq commented 2 years ago
  1. We've also had requests for functions to call an external API after the prediction has been made:

I think we are currently supporting this, don't we? Can't we have functions without an input or output or both? @pngwn

abidlabs commented 2 years ago

The root Blocks does not always have to have id 0, because you can have multiple Blocks defined within one Python session.

For example you could have:

blocks = gr.Blocks()
with blocks:
  gr.Textbox()

blocks2 = gr.Blocks()
with blocks2:
  gr.Textbox()

In this case, blocks2 would have an id that is not 0.

Which is to say that we should use the root Block id, whether it is or is not 0.

abidlabs commented 2 years ago

I think we are currently supporting this, don't we? Can't we have functions without an input or output or both? @pngwn

I don't think so, since in Block.set_event_trigger() and in all of the component event trigger methods like change() we do require all three function arguments fn, inputs, outputs. I think we still need to handle the situation when inputs or outputs is None, and update the dependencies in config suitably for these situations

omerXfaruq commented 2 years ago

The root Blocks does not always have to have id 0, because you can have multiple Blocks defined within one Python session.

For example you could have:

blocks = gr.Blocks()
with blocks:
  gr.Textbox()

blocks2 = gr.Blocks()
with blocks2:
  gr.Textbox()

In this case, blocks2 would have an id that is not 0.

Which is to say that we should use the root Block id, whether it is or is not 0.

That's correct, but it was OK if we used 0 as it was not going to affect anything.

This is the configs generated from this code:

import gradio as gr

blocks = gr.Blocks()
with blocks:
  gr.Textbox()

blocks2 = gr.Blocks()
with blocks2:
  gr.Textbox()

if __name__ == "__main__":
  assert True
  print(blocks.get_config_file())
  print(blocks2.get_config_file())

{'mode': 'blocks', 'components': [{'id': 1, 'type': 'textbox', 'props': {'lines': 1, 'placeholder': None, 'default_value': '', 'name': 'textbox', 'label': None, 'css': {}}}], 'theme': 'default', 'layout': {'id': 0, 'children': [{'id': 1}]}, 'dependencies': []}
{'mode': 'blocks', 'components': [{'id': 3, 'type': 'textbox', 'props': {'lines': 1, 'placeholder': None, 'default_value': '', 'name': 'textbox', 'label': None, 'css': {}}}], 'theme': 'default', 'layout': {'id': 2, 'children': [{'id': 3}]}, 'dependencies': []}

But let's use like this without any target


"dependencies": [
        {
            "targets": [],
            "trigger": "load",
            "inputs": [],
            "outputs": [],
            "queue": False,
        },
        {
            "targets": [],
            "trigger": "load",
            "inputs": [1],
            "outputs": [],
            "queue": False,
        },

        {
            "targets": [],
            "trigger": "load",
            "inputs": [],
            "outputs": [1],
            "queue": False,
        },
omerXfaruq commented 2 years ago

I think we are currently supporting this, don't we? Can't we have functions without an input or output or both? @pngwn

I don't think so, since in Block.set_event_trigger() and in all of the component event trigger methods like change() we do require all three function arguments fn, inputs, outputs. I think we still need to handle the situation when inputs or outputs is None, and update the dependencies in config suitably for these situations

We do accept empty list, wouldn't that work well?

click(fn, [], []) or click(fn,inputs=[], outputs=[]) I think this is more explicit and lets user know that there is no input or output.

abidlabs commented 2 years ago

Ok that seems reasonable. To summarize my understanding:

Is that right?

How does a user define a function in Python to run on page load? Would it be like this:

with gr.Blocks() as bl:
  t = gr.Textbox()
  bl.load(lambda x:"Welcome! This page has loaded.", [], t)
omerXfaruq commented 2 years ago

Yes to all 👍

cc: @pngwn

pngwn commented 2 years ago

Closed by #963 and #967.