diff --git a/gradio/blocks.py b/gradio/blocks.py index 8edeeef0a7..c9ad3cb85e 100644 --- a/gradio/blocks.py +++ b/gradio/blocks.py @@ -307,7 +307,7 @@ class Blocks(BlockContext): { "id": _id, "type": (block.get_block_name()), - "props": block.get_template_context() + "props": utils.delete_none(block.get_template_context()) if hasattr(block, "get_template_context") else None, } diff --git a/gradio/components.py b/gradio/components.py index 440ad2fe2b..97e8406470 100644 --- a/gradio/components.py +++ b/gradio/components.py @@ -132,9 +132,7 @@ class Component(Block): Creates a component, where class name equals to str_shortcut. @param str_shortcut: string shortcut of a component - @return: - True, found_class or - False, None + @return: the insantiated component object, or None if no such component exists """ # If we do not import templates Python cannot recognize grandchild classes names. import gradio.templates @@ -2371,7 +2369,11 @@ class Variable(Component): return {"default_value": self.default_value, **super().get_template_context()} +############################ # Only Output Components +############################ + + class Label(Component): """ Component outputs a classification label, along with confidence scores of top categories if provided. Confidence scores are represented as a dictionary mapping labels to scores between 0 and 1. @@ -2998,7 +3000,97 @@ class Model3D(Component): self.set_event_trigger("clear", fn, inputs, outputs) +class Plot(Component): + """ + Used for plot output. + Output type: matplotlib plt, plotly figure, or Bokeh fig (json_item format) + Demos: outbreak_forecast + """ + + def __init__( + self, + type: str = None, + label: str = None, + css: Optional[Dict] = None, + **kwargs, + ): + """ + Parameters: + type (str): type of plot (matplotlib, plotly) + label (str): component name in interface. + """ + self.type = type + super().__init__(label=label, css=css, **kwargs) + + def get_template_context(self): + return {**super().get_template_context()} + + def postprocess(self, y): + """ + Parameters: + y (str): plot data + Returns: + (str): plot type + (str): plot base64 or json + """ + dtype = self.type + if self.type == "plotly": + out_y = y.to_json() + elif self.type == "matplotlib": + out_y = processing_utils.encode_plot_to_base64(y) + elif self.type == "bokeh": + out_y = json.dumps(y) + elif self.type == "auto": + if isinstance(y, (ModuleType, matplotlib.pyplot.Figure)): + dtype = "matplotlib" + out_y = processing_utils.encode_plot_to_base64(y) + elif isinstance(y, dict): + dtype = "bokeh" + out_y = json.dumps(y) + else: + dtype = "plotly" + out_y = y.to_json() + else: + raise ValueError( + "Unknown type. Please choose from: 'plotly', 'matplotlib', 'bokeh'." + ) + return {"type": dtype, "plot": out_y} + + def change( + self, + fn: Callable, + inputs: List[Component], + outputs: List[Component], + status_tracker: Optional[StatusTracker] = None, + ): + """ + Parameters: + fn: Callable function + inputs: List of inputs + outputs: List of outputs + status: StatusTracker to visualize function progress + Returns: None + """ + self.set_event_trigger( + "change", fn, inputs, outputs, status_tracker=status_tracker + ) + + def clear(self, fn: Callable, inputs: List[Component], outputs: List[Component]): + """ + Parameters: + fn: Callable function + inputs: List of inputs + outputs: List of outputs + Returns: None + """ + self.set_event_trigger("clear", fn, inputs, outputs) + + +############################ # Static Components +############################ + + class Markdown(Component): """ Used for Markdown output. Expects a valid string that is rendered into Markdown. @@ -3186,92 +3278,6 @@ class Dataset(Component): ) -class Plot(Component): - """ - Used for plot output. - Output type: matplotlib plt, plotly figure, or Bokeh fig (json_item format) - Demos: outbreak_forecast - """ - - def __init__( - self, - type: str = None, - label: str = None, - css: Optional[Dict] = None, - **kwargs, - ): - """ - Parameters: - type (str): type of plot (matplotlib, plotly) - label (str): component name in interface. - """ - self.type = type - super().__init__(label=label, css=css, **kwargs) - - def get_template_context(self): - return {**super().get_template_context()} - - def postprocess(self, y): - """ - Parameters: - y (str): plot data - Returns: - (str): plot type - (str): plot base64 or json - """ - dtype = self.type - if self.type == "plotly": - out_y = y.to_json() - elif self.type == "matplotlib": - out_y = processing_utils.encode_plot_to_base64(y) - elif self.type == "bokeh": - out_y = json.dumps(y) - elif self.type == "auto": - if isinstance(y, (ModuleType, matplotlib.pyplot.Figure)): - dtype = "matplotlib" - out_y = processing_utils.encode_plot_to_base64(y) - elif isinstance(y, dict): - dtype = "bokeh" - out_y = json.dumps(y) - else: - dtype = "plotly" - out_y = y.to_json() - else: - raise ValueError( - "Unknown type. Please choose from: 'plotly', 'matplotlib', 'bokeh'." - ) - return {"type": dtype, "plot": out_y} - - def change( - self, - fn: Callable, - inputs: List[Component], - outputs: List[Component], - status_tracker: Optional[StatusTracker] = None, - ): - """ - Parameters: - fn: Callable function - inputs: List of inputs - outputs: List of outputs - status: StatusTracker to visualize function progress - Returns: None - """ - self.set_event_trigger( - "change", fn, inputs, outputs, status_tracker=status_tracker - ) - - def clear(self, fn: Callable, inputs: List[Component], outputs: List[Component]): - """ - Parameters: - fn: Callable function - inputs: List of inputs - outputs: List of outputs - Returns: None - """ - self.set_event_trigger("clear", fn, inputs, outputs) - - class Interpretation(Component): """ Used to create an interpretation widget for a component. @@ -3295,14 +3301,12 @@ class Interpretation(Component): } -def component(str_shortcut: str) -> (bool, Optional[Component]): +def component(str_shortcut: str) -> Optional[Component]: """ Creates a component, where class name equals to str_shortcut. @param str_shortcut: string shortcut of a component - @return: - True, found_class or - False, None + @return component: the component object """ component = Component.get_component_shortcut(str_shortcut) if component is None: diff --git a/gradio/interface.py b/gradio/interface.py index 82d6ba2fbe..61986154f7 100644 --- a/gradio/interface.py +++ b/gradio/interface.py @@ -18,7 +18,7 @@ from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple from markdown_it import MarkdownIt from mdit_py_plugins.footnote import footnote_plugin -from gradio import context, interpretation, utils +from gradio import interpretation, utils from gradio.blocks import Blocks, Column, Row, TabItem, Tabs from gradio.components import ( Button, @@ -32,8 +32,6 @@ from gradio.components import ( ) from gradio.external import load_from_pipeline, load_interface # type: ignore from gradio.flagging import CSVLogger, FlaggingCallback # type: ignore -from gradio.inputs import State as i_State # type: ignore -from gradio.outputs import State as o_State # type: ignore from gradio.process_examples import cache_interface_examples, load_from_cache if TYPE_CHECKING: # Only import for type checking (is False at runtime). @@ -487,7 +485,10 @@ class Interface(Blocks): component.label = param_name for i, component in enumerate(self.output_components): if component.label is None: - component.label = "output_" + str(i) + if len(self.output_components) == 1: + component.label = "output" + else: + component.label = "output " + str(i) self.cache_examples = cache_examples if cache_examples: diff --git a/gradio/templates/frontend/index.html b/gradio/templates/frontend/index.html index b8f70bf43c..d48bdde20f 100644 --- a/gradio/templates/frontend/index.html +++ b/gradio/templates/frontend/index.html @@ -55,7 +55,7 @@ Gradio - + diff --git a/gradio/test_data/blocks_configs.py b/gradio/test_data/blocks_configs.py index 5032076a75..91712ce3f5 100644 --- a/gradio/test_data/blocks_configs.py +++ b/gradio/test_data/blocks_configs.py @@ -7,9 +7,7 @@ XRAY_CONFIG = { "props": { "default_value": "

Detect Disease From Scan

\n

With this model you can lorem ipsum

\n\n", "name": "markdown", - "label": None, "css": {}, - "interactive": None, }, }, { @@ -21,7 +19,6 @@ XRAY_CONFIG = { "name": "checkboxgroup", "label": "Disease to Scan For", "css": {}, - "interactive": None, }, }, {"id": 3, "type": "tabs", "props": {"css": {}, "default_value": True}}, @@ -40,14 +37,10 @@ XRAY_CONFIG = { "type": "image", "props": { "image_mode": "RGB", - "shape": None, "source": "upload", "tool": "editor", - "default_value": None, "name": "image", - "label": None, "css": {}, - "interactive": None, }, }, { @@ -56,9 +49,7 @@ XRAY_CONFIG = { "props": { "default_value": '""', "name": "json", - "label": None, "css": {}, - "interactive": None, }, }, { @@ -67,9 +58,7 @@ XRAY_CONFIG = { "props": { "default_value": "Run", "name": "button", - "label": None, "css": {"background-color": "red", "--hover-color": "orange"}, - "interactive": None, }, }, { @@ -87,14 +76,10 @@ XRAY_CONFIG = { "type": "image", "props": { "image_mode": "RGB", - "shape": None, "source": "upload", "tool": "editor", - "default_value": None, "name": "image", - "label": None, "css": {}, - "interactive": None, }, }, { @@ -103,9 +88,7 @@ XRAY_CONFIG = { "props": { "default_value": '""', "name": "json", - "label": None, "css": {}, - "interactive": None, }, }, { @@ -114,9 +97,7 @@ XRAY_CONFIG = { "props": { "default_value": "Run", "name": "button", - "label": None, "css": {}, - "interactive": None, }, }, { @@ -125,12 +106,9 @@ XRAY_CONFIG = { "props": { "lines": 1, "max_lines": 20, - "placeholder": None, "default_value": "", "name": "textbox", - "label": None, "css": {}, - "interactive": None, }, }, ], @@ -199,9 +177,7 @@ XRAY_CONFIG_DIFF_IDS = { "props": { "default_value": "

Detect Disease From Scan

\n

With this model you can lorem ipsum

\n\n", "name": "markdown", - "label": None, "css": {}, - "interactive": None, }, }, { @@ -213,7 +189,6 @@ XRAY_CONFIG_DIFF_IDS = { "name": "checkboxgroup", "label": "Disease to Scan For", "css": {}, - "interactive": None, }, }, {"id": 3, "type": "tabs", "props": {"css": {}, "default_value": True}}, @@ -232,14 +207,10 @@ XRAY_CONFIG_DIFF_IDS = { "type": "image", "props": { "image_mode": "RGB", - "shape": None, "source": "upload", "tool": "editor", - "default_value": None, "name": "image", - "label": None, "css": {}, - "interactive": None, }, }, { @@ -248,9 +219,7 @@ XRAY_CONFIG_DIFF_IDS = { "props": { "default_value": '""', "name": "json", - "label": None, "css": {}, - "interactive": None, }, }, { @@ -259,9 +228,7 @@ XRAY_CONFIG_DIFF_IDS = { "props": { "default_value": "Run", "name": "button", - "label": None, "css": {"background-color": "red", "--hover-color": "orange"}, - "interactive": None, }, }, { @@ -279,14 +246,10 @@ XRAY_CONFIG_DIFF_IDS = { "type": "image", "props": { "image_mode": "RGB", - "shape": None, "source": "upload", "tool": "editor", - "default_value": None, "name": "image", - "label": None, "css": {}, - "interactive": None, }, }, { @@ -295,9 +258,7 @@ XRAY_CONFIG_DIFF_IDS = { "props": { "default_value": '""', "name": "json", - "label": None, "css": {}, - "interactive": None, }, }, { @@ -306,9 +267,7 @@ XRAY_CONFIG_DIFF_IDS = { "props": { "default_value": "Run", "name": "button", - "label": None, "css": {}, - "interactive": None, }, }, { @@ -316,13 +275,10 @@ XRAY_CONFIG_DIFF_IDS = { "type": "textbox", "props": { "lines": 1, - "placeholder": None, "default_value": "", "name": "textbox", "max_lines": 20, - "label": None, "css": {}, - "interactive": None, }, }, ], @@ -383,9 +339,7 @@ XRAY_CONFIG_WITH_MISTAKE = { "props": { "default_value": "

Detect Disease From Scan

\n

With this model you can lorem ipsum

\n\n", "name": "markdown", - "label": None, "css": {}, - "interactive": None, }, }, { @@ -397,7 +351,6 @@ XRAY_CONFIG_WITH_MISTAKE = { "name": "checkboxgroup", "label": "Disease to Scan For", "css": {}, - "interactive": None, }, }, {"id": 3, "type": "tabs", "props": {"css": {}, "default_value": True}}, @@ -416,14 +369,10 @@ XRAY_CONFIG_WITH_MISTAKE = { "type": "image", "props": { "image_mode": "RGB", - "shape": None, "source": "upload", "tool": "editor", - "default_value": None, "name": "image", - "label": None, "css": {}, - "interactive": None, }, }, { @@ -432,9 +381,7 @@ XRAY_CONFIG_WITH_MISTAKE = { "props": { "default_value": '""', "name": "json", - "label": None, "css": {}, - "interactive": None, }, }, { @@ -443,9 +390,7 @@ XRAY_CONFIG_WITH_MISTAKE = { "props": { "default_value": "Run", "name": "button", - "label": None, "css": {"background-color": "red", "--hover-color": "orange"}, - "interactive": None, }, }, { @@ -463,14 +408,10 @@ XRAY_CONFIG_WITH_MISTAKE = { "type": "image", "props": { "image_mode": "RGB", - "shape": None, "source": "upload", "tool": "editor", - "default_value": None, "name": "image", - "label": None, "css": {}, - "interactive": None, }, }, { @@ -479,9 +420,7 @@ XRAY_CONFIG_WITH_MISTAKE = { "props": { "default_value": '""', "name": "json", - "label": None, "css": {}, - "interactive": None, }, }, { @@ -490,9 +429,7 @@ XRAY_CONFIG_WITH_MISTAKE = { "props": { "default_value": "Run", "name": "button", - "label": None, "css": {}, - "interactive": None, }, }, { @@ -500,12 +437,9 @@ XRAY_CONFIG_WITH_MISTAKE = { "type": "textbox", "props": { "lines": 1, - "placeholder": None, "default_value": "", "name": "textbox", - "label": None, "css": {}, - "interactive": None, }, }, ], diff --git a/gradio/utils.py b/gradio/utils.py index 8dc67e0351..c797f159ca 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -352,3 +352,21 @@ def format_ner_list(input_string: str, ner_groups: Dict[str : str | int]): output.append((input_string[end:], None)) return output + + +def delete_none(_dict): + """ + Delete None values recursively from all of the dictionaries, tuples, lists, sets. + Credit: https://stackoverflow.com/questions/33797126/proper-way-to-remove-keys-in-dictionary-with-none-values-in-python + """ + if isinstance(_dict, dict): + for key, value in list(_dict.items()): + if isinstance(value, (list, dict, tuple, set)): + _dict[key] = delete_none(value) + elif value is None or key is None: + del _dict[key] + + elif isinstance(_dict, (list, set, tuple)): + _dict = type(_dict)(delete_none(item) for item in _dict if item is not None) + + return _dict diff --git a/test/test_utils.py b/test/test_utils.py index fed9be68b6..e3f036bb24 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -15,6 +15,7 @@ from gradio.test_data.blocks_configs import ( from gradio.utils import ( assert_configs_are_equivalent_besides_ids, colab_check, + delete_none, error_analytics, format_ner_list, get_local_ip_address, @@ -164,5 +165,24 @@ class TestFormatNERList(unittest.TestCase): self.assertEqual(format_ner_list(string, groups), result) +class TestDeleteNone(unittest.TestCase): + """Credit: https://stackoverflow.com/questions/33797126/proper-way-to-remove-keys-in-dictionary-with-none-values-in-python""" + + def test_delete_none(self): + input = { + "a": 12, + "b": 34, + "c": None, + "k": { + "d": 34, + "t": None, + "m": [{"k": 23, "t": None}, [None, 1, 2, 3], {1, 2, None}], + None: 123, + }, + } + truth = {"a": 12, "b": 34, "k": {"d": 34, "m": [{"k": 23}, [1, 2, 3], {1, 2}]}} + self.assertEqual(delete_none(input), truth) + + if __name__ == "__main__": unittest.main() diff --git a/ui/packages/app/src/Render.svelte b/ui/packages/app/src/Render.svelte index 9f7bc7ea15..f35b48a46e 100644 --- a/ui/packages/app/src/Render.svelte +++ b/ui/packages/app/src/Render.svelte @@ -19,7 +19,7 @@ props.mode = "static"; } else if (props.interactive === true) { props.mode = "dynamic"; - } else if (props.interactive === null && dynamic_ids.has(id)) { + } else if (dynamic_ids.has(id)) { props.mode = "dynamic"; } else { props.mode = "static"; diff --git a/ui/packages/app/src/components/Checkbox/Checkbox.svelte b/ui/packages/app/src/components/Checkbox/Checkbox.svelte index 43723d7dbe..98ab96ecba 100644 --- a/ui/packages/app/src/components/Checkbox/Checkbox.svelte +++ b/ui/packages/app/src/components/Checkbox/Checkbox.svelte @@ -4,7 +4,7 @@ export let value: boolean = false; export let default_value: boolean = false; export let style: string = ""; - export let label: string; + export let label: string = "Checkbox"; export let mode: "static" | "dynamic"; if (default_value) value = default_value; diff --git a/ui/packages/app/src/components/CheckboxGroup/CheckboxGroup.svelte b/ui/packages/app/src/components/CheckboxGroup/CheckboxGroup.svelte index e87f0c979c..b08367e305 100644 --- a/ui/packages/app/src/components/CheckboxGroup/CheckboxGroup.svelte +++ b/ui/packages/app/src/components/CheckboxGroup/CheckboxGroup.svelte @@ -7,7 +7,7 @@ export let mode: "static" | "dynamic"; export let style: string = ""; - export let label: string; + export let label: string = "Checkbox Group"; if (default_value) value = default_value; diff --git a/ui/packages/app/src/components/Dropdown/Dropdown.svelte b/ui/packages/app/src/components/Dropdown/Dropdown.svelte index 6521104adc..cb2f7c998e 100644 --- a/ui/packages/app/src/components/Dropdown/Dropdown.svelte +++ b/ui/packages/app/src/components/Dropdown/Dropdown.svelte @@ -1,6 +1,6 @@