Skip to content

Commit

Permalink
Load example from external Spaces (#2594)
Browse files Browse the repository at this point in the history
* started

* loading examples

* saves

* fixes

* formatting

* iocomponent

* added support to other components

* fixed tests

* added video support

* added 1 test

* more changes

* reverted components.py

* remaining components

* formatting

* fixes

* fixes

* tests

* fixed ability to load multiple spaces
  • Loading branch information
abidlabs committed Nov 3, 2022
1 parent 26abbe6 commit 43f5cf5
Show file tree
Hide file tree
Showing 14 changed files with 104 additions and 47 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -19,6 +19,7 @@ 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)
* Fixed links in Plotly map guide + demo by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2578](https://github.com/gradio-app/gradio/pull/2578)
* `gr.Blocks.load()` now correctly loads example files from Spaces [@abidlabs](https://github.com/abidlabs) in [PR 2594](https://github.com/gradio-app/gradio/pull/2594)

## Documentation Changes:
No changes to highlight.
Expand Down
59 changes: 42 additions & 17 deletions gradio/blocks.py
Expand Up @@ -66,11 +66,20 @@


class Block:
def __init__(self, *, render=True, elem_id=None, visible=True, **kwargs):
def __init__(
self,
*,
render: bool = True,
elem_id: str | None = None,
visible: bool = True,
root_url: str | None = None, # URL that is prepended to all file paths
**kwargs,
):
self._id = Context.id
Context.id += 1
self.visible = visible
self.elem_id = elem_id
self.root_url = root_url
self._style = {}
if render:
self.render()
Expand Down Expand Up @@ -246,6 +255,7 @@ def get_config(self):
"visible": self.visible,
"elem_id": self.elem_id,
"style": self._style,
"root_url": self.root_url,
}

@classmethod
Expand Down Expand Up @@ -570,8 +580,17 @@ def share(self, value: Optional[bool]):
self._share = value

@classmethod
def from_config(cls, config: dict, fns: List[Callable]) -> Blocks:
"""Factory method that creates a Blocks from a config and list of functions."""
def from_config(
cls, config: dict, fns: List[Callable], root_url: str | None = None
) -> Blocks:
"""
Factory method that creates a Blocks from a config and list of functions.
Parameters:
config: a dictionary containing the configuration of the Blocks.
fns: a list of functions that are used in the Blocks. Must be in the same order as the dependencies in the config.
root_url: an optional root url to use for the components in the Blocks. Allows serving files from an external URL.
"""
config = copy.deepcopy(config)
components_config = config["components"]
original_mapping: Dict[int, Block] = {}
Expand All @@ -586,6 +605,8 @@ def get_block_instance(id: int) -> Block:
block_config["props"].pop("type", None)
block_config["props"].pop("name", None)
style = block_config["props"].pop("style", None)
if block_config["props"].get("root_url") is None and root_url:
block_config["props"]["root_url"] = root_url + "/"
block = cls(**block_config["props"])
if style:
block.style(**style)
Expand All @@ -603,8 +624,13 @@ def iterate_over_children(children_list):
iterate_over_children(children)

with Blocks(theme=config["theme"], css=config["theme"]) as blocks:
# ID 0 should be the root Blocks component
original_mapping[0] = Context.root_block or blocks

iterate_over_children(config["layout"]["children"])

first_dependency = None

# add the event triggers
for dependency, fn in zip(config["dependencies"], fns):
targets = dependency.pop("targets")
Expand All @@ -618,19 +644,24 @@ def iterate_over_children(children_list):
original_mapping[o] for o in dependency["outputs"]
]
dependency.pop("status_tracker", None)
dependency["_js"] = dependency.pop("js", None)
dependency["preprocess"] = False
dependency["postprocess"] = False

for target in targets:
event_method = getattr(original_mapping[target], trigger)
event_method(fn=fn, **dependency)
dependency = original_mapping[target].set_event_trigger(
event_name=trigger, fn=fn, **dependency
)
if first_dependency is None:
first_dependency = dependency

# Allows some use of Interface-specific methods with loaded Spaces
blocks.predict = [fns[0]]
dependency = blocks.dependencies[0]
blocks.input_components = [blocks.blocks[i] for i in dependency["inputs"]]
blocks.output_components = [blocks.blocks[o] for o in dependency["outputs"]]
blocks.input_components = [
Context.root_block.blocks[i] for i in first_dependency["inputs"]
]
blocks.output_components = [
Context.root_block.blocks[o] for o in first_dependency["outputs"]
]

if config.get("mode", "blocks") == "interface":
blocks.__name__ = "Interface"
Expand Down Expand Up @@ -1073,7 +1104,7 @@ def load(
fn: Instance Method - Callable function
inputs: Instance Method - input list
outputs: Instance Method - output list
every: Run this event 'every' number of seconds. Interpreted in seconds. Queue must be enabled.
every: Instance Method - Run this event 'every' number of seconds. Interpreted in seconds. Queue must be enabled.
Example:
import gradio as gr
import datetime
Expand All @@ -1088,14 +1119,8 @@ def get_time():
if isinstance(self_or_cls, type):
if name is None:
raise ValueError(
"Blocks.load() requires passing `name` as a keyword argument"
"Blocks.load() requires passing parameters as keyword arguments"
)
if fn is not None:
kwargs["fn"] = fn
if inputs is not None:
kwargs["inputs"] = inputs
if outputs is not None:
kwargs["outputs"] = outputs
return external.load_blocks_from_repo(name, src, api_key, alias, **kwargs)
else:
return self_or_cls.set_event_trigger(
Expand Down
10 changes: 6 additions & 4 deletions gradio/components.py
Expand Up @@ -2022,7 +2022,7 @@ def style(
**kwargs,
)

def as_example(self, input_data: str) -> str:
def as_example(self, input_data: str | None) -> str:
return Path(input_data).name if input_data else ""


Expand Down Expand Up @@ -2462,8 +2462,10 @@ def style(
**kwargs,
)

def as_example(self, input_data):
if isinstance(input_data, pd.DataFrame):
def as_example(self, input_data: pd.DataFrame | np.ndarray | str | None):
if input_data is None:
return ""
elif isinstance(input_data, pd.DataFrame):
return input_data.head(n=5).to_dict(orient="split")["data"]
elif isinstance(input_data, np.ndarray):
return input_data.tolist()
Expand Down Expand Up @@ -3616,7 +3618,7 @@ def style(self, **kwargs):
**kwargs,
)

def as_example(self, input_data: str) -> str:
def as_example(self, input_data: str | None) -> str:
return Path(input_data).name if input_data else ""


Expand Down
12 changes: 5 additions & 7 deletions gradio/external.py
Expand Up @@ -21,7 +21,6 @@
get_ws_fn,
postprocess_label,
rows_to_cols,
streamline_spaces_blocks,
streamline_spaces_interface,
use_websocket,
)
Expand Down Expand Up @@ -312,6 +311,7 @@ def query_huggingface_api(*params):

def from_spaces(space_name: str, api_key: str | None, alias: str, **kwargs) -> Blocks:
space_url = "https://huggingface.co/spaces/{}".format(space_name)

print("Fetching Space from: {}".format(space_url))

headers = {}
Expand Down Expand Up @@ -345,14 +345,12 @@ def from_spaces(space_name: str, api_key: str | None, alias: str, **kwargs) -> B
space_name, config, alias, api_key, iframe_url, **kwargs
)
else: # Create a Blocks for Gradio 3.x Spaces
return from_spaces_blocks(space_name, config, api_key, iframe_url)
return from_spaces_blocks(config, api_key, iframe_url)


def from_spaces_blocks(
model_name: str, config: Dict, api_key: str | None, iframe_url: str
) -> Blocks:
config = streamline_spaces_blocks(config)
def from_spaces_blocks(config: Dict, api_key: str | None, iframe_url: str) -> Blocks:
api_url = "{}/api/predict/".format(iframe_url)

headers = {"Content-Type": "application/json"}
if api_key is not None:
headers["Authorization"] = f"Bearer {api_key}"
Expand Down Expand Up @@ -398,7 +396,7 @@ def fn(*data):
fns.append(fn)
else:
fns.append(None)
return gradio.Blocks.from_config(config, fns)
return gradio.Blocks.from_config(config, fns, iframe_url)


def from_spaces_interface(
Expand Down
11 changes: 1 addition & 10 deletions gradio/external_utils.py
Expand Up @@ -153,7 +153,7 @@ def use_websocket(config, dependency):


##################
# Helper functions for cleaning up Interfaces/Blocks loaded from HF Spaces
# Helper function for cleaning up an Interface loaded from HF Spaces
##################


Expand All @@ -178,12 +178,3 @@ def streamline_spaces_interface(config: Dict) -> Dict:
}
config = {k: config[k] for k in parameters}
return config


def streamline_spaces_blocks(config: dict) -> dict:
"""Streamlines the blocks config dictionary to fix components that don't render correctly."""
# TODO(abidlabs): Need a better way to fix relative paths in dataset component
for c, component in enumerate(config["components"]):
if component["type"] == "dataset":
config["components"][c]["props"]["visible"] = False
return config
2 changes: 1 addition & 1 deletion gradio/interface.py
Expand Up @@ -105,7 +105,7 @@ def load(
demo = gr.Interface.load("models/EleutherAI/gpt-neo-1.3B", description=description, examples=examples)
demo.launch()
"""
return super().load(name=name, src=src, api_key=api_key, alias=alias, **kwargs)
return super().load(name=name, src=src, api_key=api_key, alias=alias)

@classmethod
def from_pipeline(cls, pipeline: transformers.Pipeline, **kwargs) -> Interface:
Expand Down

0 comments on commit 43f5cf5

Please sign in to comment.