Call fn by api_name when using Blocks.load (#2593)

* Updated implementation + test

* Add changelog

* Fix unit test
This commit is contained in:
Freddy Boulton 2022-11-02 13:38:56 -04:00 committed by GitHub
parent fa08e711fa
commit 4a51cec5b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 74 additions and 11 deletions

View File

@ -1,7 +1,20 @@
# Upcoming Release
## New Features:
No changes to highlight.
### Calling functions by api_name in loaded apps
When you load an upstream app with `gr.Blocks.load`, you can now specify which fn
to call with the `api_name` parameter.
```python
import gradio as gr
english_translator = gr.Blocks.load(name="spaces/gradio/english-translator")
german = english_translator("My name is Freddy", api_name='translate-to-german')
```
The `api_name` parameter will take precendence over the `fn_index` parameter.
## Bug Fixes:
* Fixed bug where None could not be used for File,Model3D, and Audio examples by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2588](https://github.com/gradio-app/gradio/pull/2588)
@ -17,7 +30,7 @@ No changes to highlight.
No changes to highlight.
## Full Changelog:
No changes to highlight.
* Add `api_name` to `Blocks.__call__` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2593](https://github.com/gradio-app/gradio/pull/2593)
## Contributors Shoutout:
No changes to highlight.

View File

@ -17,7 +17,7 @@ with gr.Blocks() as demo:
with gr.Column():
german = gr.Textbox(label="German Text")
translate_btn.click(translate, inputs=english, outputs=german)
translate_btn.click(translate, inputs=english, outputs=german, api_name="translate-to-german")
examples = gr.Examples(examples=["I went to the supermarket yesterday.", "Helen is a good swimmer."],
inputs=[english])

View File

@ -2,7 +2,7 @@ import gradio as gr
from transformers import pipeline
english_translator = gr.Blocks.load(name="spaces/freddyaboulton/english-translator")
english_translator = gr.Blocks.load(name="spaces/gradio/english-translator")
english_generator = pipeline("text-generation", model="distilgpt2")

View File

@ -45,7 +45,7 @@ from gradio.documentation import (
document_component_api,
set_documentation_group,
)
from gradio.exceptions import DuplicateBlockError
from gradio.exceptions import DuplicateBlockError, InvalidApiName
from gradio.utils import (
check_function_inputs_match,
component_or_layout_class,
@ -734,7 +734,7 @@ class Blocks(BlockContext):
return True
def __call__(self, *inputs, fn_index: int = 0):
def __call__(self, *inputs, fn_index: int = 0, api_name: str = None):
"""
Allows Blocks objects to be called as functions. Supply the parameters to the
function as positional arguments. To choose which function to call, use the
@ -743,7 +743,19 @@ class Blocks(BlockContext):
Parameters:
*inputs: the parameters to pass to the function
fn_index: the index of the function to call (defaults to 0, which for Interfaces, is the default prediction function)
api_name: The api_name of the dependency to call. Will take precedence over fn_index.
"""
if api_name is not None:
fn_index = next(
(
i
for i, d in enumerate(self.dependencies)
if d.get("api_name") == api_name
),
None,
)
if fn_index is None:
raise InvalidApiName(f"Cannot find a function with api_name {api_name}")
if not (self.is_callable(fn_index)):
raise ValueError(
"This function is not callable because it is either stateful or is a generator. Please use the .launch() method instead to create an interactive user interface."

View File

@ -10,6 +10,10 @@ class TooManyRequestsError(Exception):
pass
class InvalidApiName(ValueError):
pass
class Error(Exception):
def __init__(self, message: str):
self.message = message

View File

@ -25,7 +25,7 @@ $code_english_translator
I already went ahead and hosted it in Hugging Face spaces at [freddyaboulton/english-to-german](https://huggingface.co/spaces/freddyaboulton/english-to-german).
You can see the demo below as well:
<gradio-app space="freddyaboulton/english-to-german"> </gradio-app>
<gradio-app space="gradio/english-to-german"> </gradio-app>
Now, let's say you have an app that generates english text, but you wanted to additionally generate german text.
@ -48,13 +48,29 @@ Note that the variable `english_translator` is my english to german app, but its
$code_generate_english_german
<gradio-app space="freddyaboulton/generate-english-german"> </gradio-app>
<gradio-app space="gradio/generate-english-german"> </gradio-app>
## How to control which function in the app to use
If the app you are loading defines more than one function, you can specify which function to use with the `fn_index` parameter.
Imagine my app also defined an english to spanish translation function. In order to use it in our text generation app,
we would use the following code:
If the app you are loading defines more than one function, you can specify which function to use
with the `fn_index` and `api_name` parameters.
In the code for our english to german demo, you'll see the following line:
```python
translate_btn.click(translate, inputs=english, outputs=german, api_name="translate-to-german")
```
The `api_name` gives this function a unique name in our app. You can use this name to tell gradio which
function in the upstream space you want to use:
```python
english_generator(text, api_name="translate-to-german")[0]["generated_text"]
```
You can also use the `fn_index` parameter.
Imagine my app also defined an english to spanish translation function.
In order to use it in our text generation app, we would use the following code:
```python
english_generator(text, fn_index=1)[0]["generated_text"]

View File

@ -11,6 +11,7 @@ import pytest
import gradio
import gradio as gr
from gradio import utils
from gradio.exceptions import InvalidApiName
from gradio.external import (
TooManyRequestsError,
cols_to_rows,
@ -417,5 +418,22 @@ def test_respect_queue_when_load_from_config():
assert interface("bob") == "foo"
def test_raise_value_error_when_api_name_invalid():
with pytest.raises(InvalidApiName):
demo = gr.Blocks.load(name="spaces/gradio/hello_world")
demo("freddy", api_name="route does not exist")
def test_use_api_name_in_call_method():
# Interface
demo = gr.Blocks.load(name="spaces/gradio/hello_world")
assert demo("freddy", api_name="predict") == "Hello freddy!"
# Blocks demo with multiple functions
app = gr.Blocks.load(name="spaces/gradio/multiple-api-name-test")
assert app(15, api_name="minus_one") == 14
assert app(4, api_name="double") == 8
if __name__ == "__main__":
unittest.main()