from __future__ import annotations import copy import dataclasses import hashlib import inspect import json import os import random import secrets import string import sys import threading import time import warnings import webbrowser from collections import defaultdict from collections.abc import AsyncIterator, Callable, Coroutine, Sequence, Set from pathlib import Path from types import ModuleType from typing import ( TYPE_CHECKING, Any, Literal, cast, ) from urllib.parse import urlparse, urlunparse import anyio import fastapi import httpx from anyio import CapacityLimiter from gradio_client import utils as client_utils from gradio_client.documentation import document from gradio import ( analytics, components, networking, processing_utils, queueing, routes, strings, themes, utils, wasm_utils, ) from gradio.blocks_events import BlocksEvents, BlocksMeta from gradio.context import ( Context, LocalContext, get_blocks_context, get_render_context, set_render_context, ) from gradio.data_classes import ( BlocksConfigDict, DeveloperPath, FileData, GradioModel, GradioRootModel, ) from gradio.events import ( EventData, EventListener, EventListenerMethod, ) from gradio.exceptions import ( ChecksumMismatchError, DuplicateBlockError, InvalidApiNameError, InvalidComponentError, ) from gradio.helpers import create_tracker, skip, special_args from gradio.node_server import start_node_server from gradio.route_utils import API_PREFIX, MediaStream from gradio.state_holder import SessionState, StateHolder from gradio.themes import Default as DefaultTheme from gradio.themes import ThemeClass as Theme from gradio.tunneling import ( BINARY_FILENAME, BINARY_FOLDER, BINARY_PATH, BINARY_URL, CURRENT_TUNNELS, ) from gradio.utils import ( TupleNoPrint, check_function_inputs_match, component_or_layout_class, get_cancelled_fn_indices, get_node_path, get_package_version, get_upload_folder, ) try: import spaces # type: ignore except Exception: spaces = None if TYPE_CHECKING: # Only import for type checking (is False at runtime). from gradio.components.base import Component from gradio.renderable import Renderable BUILT_IN_THEMES: dict[str, Theme] = { t.name: t for t in [ themes.Base(), themes.Default(), themes.Monochrome(), themes.Soft(), themes.Glass(), themes.Origin(), themes.Citrus(), themes.Ocean(), ] } class Block: def __init__( self, *, elem_id: str | None = None, elem_classes: list[str] | str | None = None, render: bool = True, key: int | str | None = None, visible: bool = True, proxy_url: str | None = None, ): self._id = Context.id Context.id += 1 self.visible = visible self.elem_id = elem_id self.elem_classes = ( [elem_classes] if isinstance(elem_classes, str) else elem_classes ) self.proxy_url = proxy_url self.share_token = secrets.token_urlsafe(32) self.parent: BlockContext | None = None self.rendered_in: Renderable | None = None self.is_rendered: bool = False self._constructor_args: list[dict] self.state_session_capacity = 10000 self.temp_files: set[str] = set() self.GRADIO_CACHE = get_upload_folder() self.key = key # Keep tracks of files that should not be deleted when the delete_cache parmameter is set # These files are the default value of the component and files that are used in examples self.keep_in_cache = set() self.has_launched = False if render: self.render() @property def stateful(self) -> bool: return False @property def skip_api(self) -> bool: return False @property def constructor_args(self) -> dict[str, Any]: """Get the arguments passed to the component's initializer. Only set classes whose metaclass is ComponentMeta """ # the _constructor_args list is appended based on the mro of the class # so the first entry is for the bottom of the hierarchy return self._constructor_args[0] if self._constructor_args else {} @property def events( self, ) -> list[EventListener]: return getattr(self, "EVENTS", []) def render(self): """ Adds self into appropriate BlockContext """ root_context = get_blocks_context() render_context = get_render_context() self.rendered_in = LocalContext.renderable.get() if root_context is not None and self._id in root_context.blocks: raise DuplicateBlockError( f"A block with id: {self._id} has already been rendered in the current Blocks." ) if render_context is not None: render_context.add(self) if root_context is not None: root_context.blocks[self._id] = self self.is_rendered = True if isinstance(self, components.Component): root_context.root_block.temp_file_sets.append(self.temp_files) return self def unrender(self): """ Removes self from BlockContext if it has been rendered (otherwise does nothing). Removes self from the layout and collection of blocks, but does not delete any event triggers. """ root_context = get_blocks_context() render_context = get_render_context() if render_context is not None: try: render_context.children.remove(self) except ValueError: pass if root_context is not None: try: del root_context.blocks[self._id] self.is_rendered = False except KeyError: pass return self def get_block_name(self) -> str: """ Gets block's class name. If it is template component it gets the parent's class name. This is used to identify the Svelte file to use in the frontend. Override this method if a component should use a different Svelte file than the default naming convention. """ return ( self.__class__.__base__.__name__.lower() # type: ignore if hasattr(self, "is_template") else self.__class__.__name__.lower() ) def get_block_class(self) -> str: """ Gets block's class name. If it is template component it gets the parent's class name. Very similar to the get_block_name method, but this method is used to reconstruct a Gradio app that is loaded from a Space using gr.load(). This should generally NOT be overridden. """ return ( self.__class__.__base__.__name__.lower() # type: ignore if hasattr(self, "is_template") else self.__class__.__name__.lower() ) def get_expected_parent(self) -> type[BlockContext] | None: return None def get_config(self): config = {} signature = inspect.signature(self.__class__.__init__) for parameter in signature.parameters.values(): if hasattr(self, parameter.name): value = getattr(self, parameter.name) if dataclasses.is_dataclass(value): value = dataclasses.asdict(value) # type: ignore config[parameter.name] = value for e in self.events: to_add = e.config_data() if to_add: config = {**to_add, **config} config.pop("render", None) config = {**config, "proxy_url": self.proxy_url, "name": self.get_block_class()} if self.rendered_in is not None: config["rendered_in"] = self.rendered_in._id for event_attribute in ["_selectable", "_undoable", "_retryable", "likeable"]: if (attributable := getattr(self, event_attribute, None)) is not None: config[event_attribute] = attributable return config @classmethod def recover_kwargs( cls, props: dict[str, Any], additional_keys: list[str] | None = None ): """ Recovers kwargs from a dict of props. """ additional_keys = additional_keys or [] signature = inspect.signature(cls.__init__) kwargs = {} for parameter in signature.parameters.values(): if parameter.name in props and parameter.name not in additional_keys: kwargs[parameter.name] = props[parameter.name] return kwargs async def async_move_resource_to_block_cache( self, url_or_file_path: str | Path | None ) -> str | None: """Moves a file or downloads a file from a url to a block's cache directory, adds to to the block's temp_files, and returns the path to the file in cache. This ensures that the file is accessible to the Block and can be served to users. This async version of the function is used when this is being called within a FastAPI route, as this is not blocking. """ if url_or_file_path is None: return None if isinstance(url_or_file_path, Path): url_or_file_path = str(url_or_file_path) if client_utils.is_http_url_like(url_or_file_path): temp_file_path = await processing_utils.async_ssrf_protected_download( url_or_file_path, cache_dir=self.GRADIO_CACHE ) self.temp_files.add(temp_file_path) else: url_or_file_path = str(utils.abspath(url_or_file_path)) if not utils.is_in_or_equal(url_or_file_path, self.GRADIO_CACHE): try: temp_file_path = processing_utils.save_file_to_cache( url_or_file_path, cache_dir=self.GRADIO_CACHE ) except FileNotFoundError: # This can happen if when using gr.load() and the file is on a remote Space # but the file is not the `value` of the component. For example, if the file # is the `avatar_image` of the `Chatbot` component. In this case, we skip # copying the file to the cache and just use the remote file path. return url_or_file_path else: temp_file_path = url_or_file_path self.temp_files.add(temp_file_path) return temp_file_path def move_resource_to_block_cache( self, url_or_file_path: str | Path | None ) -> str | None: """Moves a file or downloads a file from a url to a block's cache directory, adds to to the block's temp_files, and returns the path to the file in cache. This ensures that the file is accessible to the Block and can be served to users. This sync version of the function is used when this is being called outside of a FastAPI route, e.g. when examples are being cached. """ if url_or_file_path is None: return None if isinstance(url_or_file_path, Path): url_or_file_path = str(url_or_file_path) if client_utils.is_http_url_like(url_or_file_path): temp_file_path = processing_utils.save_url_to_cache( url_or_file_path, cache_dir=self.GRADIO_CACHE ) self.temp_files.add(temp_file_path) else: url_or_file_path = str(utils.abspath(url_or_file_path)) if not utils.is_in_or_equal(url_or_file_path, self.GRADIO_CACHE): try: temp_file_path = processing_utils.save_file_to_cache( url_or_file_path, cache_dir=self.GRADIO_CACHE ) except FileNotFoundError: # This can happen if when using gr.load() and the file is on a remote Space # but the file is not the `value` of the component. For example, if the file # is the `avatar_image` of the `Chatbot` component. In this case, we skip # copying the file to the cache and just use the remote file path. return url_or_file_path else: temp_file_path = url_or_file_path self.temp_files.add(temp_file_path) return temp_file_path def serve_static_file( self, url_or_file_path: str | Path | dict | None ) -> dict | None: """If a file is a local file, moves it to the block's cache directory and returns a FileData-type dictionary corresponding to the file. If the file is a URL, returns a FileData-type dictionary corresponding to the URL. This ensures that the file is accessible in the frontend and can be served to users. Examples: >>> block.serve_static_file("https://gradio.app/logo.png") -> {"path": "https://gradio.app/logo.png", "url": "https://gradio.app/logo.png"} >>> block.serve_static_file("logo.png") -> {"path": "logo.png", "url": "/file=logo.png"} >>> block.serve_static_file({"path": "logo.png", "url": "/file=logo.png"}) -> {"path": "logo.png", "url": "/file=logo.png"} """ if url_or_file_path is None: return None if isinstance(url_or_file_path, dict): return url_or_file_path if isinstance(url_or_file_path, Path): url_or_file_path = str(url_or_file_path) if client_utils.is_http_url_like(url_or_file_path): return FileData(path=url_or_file_path, url=url_or_file_path).model_dump() else: data = {"path": url_or_file_path, "meta": {"_type": "gradio.FileData"}} try: return client_utils.synchronize_async( processing_utils.async_move_files_to_cache, data, self ) except AttributeError: # Can be raised if this function is called before the Block is fully initialized. return data class BlockContext(Block): def __init__( self, elem_id: str | None = None, elem_classes: list[str] | str | None = None, visible: bool = True, render: bool = True, ): """ Parameters: elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles. visible: If False, this will be hidden but included in the Blocks config file (its visibility can later be updated). render: If False, this will not be included in the Blocks config file at all. """ self.children: list[Block] = [] Block.__init__( self, elem_id=elem_id, elem_classes=elem_classes, visible=visible, render=render, ) TEMPLATE_DIR = DeveloperPath("./templates/") FRONTEND_DIR = "../../frontend/" @property def skip_api(self): return True @classmethod def get_component_class_id(cls) -> str: module_name = cls.__module__ module_path = sys.modules[module_name].__file__ module_hash = hashlib.md5(f"{cls.__name__}_{module_path}".encode()).hexdigest() return module_hash @property def component_class_id(self): return self.get_component_class_id() def add_child(self, child: Block): self.children.append(child) def __enter__(self): render_context = get_render_context() self.parent = render_context set_render_context(self) return self def add(self, child: Block): child.parent = self self.children.append(child) def fill_expected_parents(self): root_context = get_blocks_context() children = [] pseudo_parent = None for child in self.children: expected_parent = child.get_expected_parent() if not expected_parent or isinstance(self, expected_parent): pseudo_parent = None children.append(child) else: if pseudo_parent is not None and isinstance( pseudo_parent, expected_parent ): pseudo_parent.add_child(child) else: pseudo_parent = expected_parent(render=False) pseudo_parent.parent = self children.append(pseudo_parent) pseudo_parent.add_child(child) if root_context: root_context.blocks[pseudo_parent._id] = pseudo_parent child.parent = pseudo_parent self.children = children def __exit__(self, exc_type: type[BaseException] | None = None, *args): set_render_context(self.parent) if exc_type is not None: return if getattr(self, "allow_expected_parents", True): self.fill_expected_parents() def postprocess(self, y): """ Any postprocessing needed to be performed on a block context. """ return y class BlockFunction: def __init__( self, fn: Callable | None, inputs: Sequence[Component | BlockContext], outputs: Sequence[Component | BlockContext], preprocess: bool, postprocess: bool, inputs_as_dict: bool, targets: list[tuple[int | None, str]], _id: int, batch: bool = False, max_batch_size: int = 4, concurrency_limit: int | None | Literal["default"] = "default", concurrency_id: str | None = None, tracks_progress: bool = False, api_name: str | Literal[False] = False, js: str | None = None, show_progress: Literal["full", "minimal", "hidden"] = "full", cancels: list[int] | None = None, collects_event_data: bool = False, trigger_after: int | None = None, trigger_only_on_success: bool = False, trigger_mode: Literal["always_last", "once", "multiple"] = "once", queue: bool = True, scroll_to_output: bool = False, show_api: bool = True, renderable: Renderable | None = None, rendered_in: Renderable | None = None, is_cancel_function: bool = False, connection: Literal["stream", "sse"] = "sse", time_limit: float | None = None, stream_every: float = 0.5, like_user_message: bool = False, event_specific_args: list[str] | None = None, ): self.fn = fn self._id = _id self.inputs = inputs self.outputs = outputs self.preprocess = preprocess self.postprocess = postprocess self.tracks_progress = tracks_progress self.concurrency_limit: int | None | Literal["default"] = concurrency_limit self.concurrency_id = concurrency_id or str(id(fn)) self.batch = batch self.max_batch_size = max_batch_size self.total_runtime = 0 self.total_runs = 0 self.inputs_as_dict = inputs_as_dict self.targets = targets self.name = getattr(fn, "__name__", "fn") if fn is not None else None self.api_name = api_name self.js = js self.show_progress = show_progress self.cancels = cancels or [] self.collects_event_data = collects_event_data self.trigger_after = trigger_after self.trigger_only_on_success = trigger_only_on_success self.trigger_mode = trigger_mode self.queue = False if fn is None else queue self.scroll_to_output = False if utils.get_space() else scroll_to_output self.show_api = show_api self.zero_gpu = hasattr(self.fn, "zerogpu") self.types_generator = inspect.isgeneratorfunction( self.fn ) or inspect.isasyncgenfunction(self.fn) self.renderable = renderable self.rendered_in = rendered_in # We need to keep track of which events are cancel events # so that the client can call the /cancel route directly self.is_cancel_function = is_cancel_function self.time_limit = time_limit self.stream_every = stream_every self.connection = connection self.like_user_message = like_user_message self.event_specific_args = event_specific_args self.spaces_auto_wrap() def spaces_auto_wrap(self): if spaces is None: return if utils.get_space() is None: return self.fn = spaces.gradio_auto_wrap(self.fn) def __str__(self): return str( { "fn": self.name, "preprocess": self.preprocess, "postprocess": self.postprocess, } ) def __repr__(self): return str(self) def get_config(self): return { "id": self._id, "targets": self.targets, "inputs": [block._id for block in self.inputs], "outputs": [block._id for block in self.outputs], "backend_fn": self.fn is not None, "js": self.js, "queue": self.queue, "api_name": self.api_name, "scroll_to_output": self.scroll_to_output, "show_progress": self.show_progress, "batch": self.batch, "max_batch_size": self.max_batch_size, "cancels": self.cancels, "types": { "generator": self.types_generator, "cancel": self.is_cancel_function, }, "collects_event_data": self.collects_event_data, "trigger_after": self.trigger_after, "trigger_only_on_success": self.trigger_only_on_success, "trigger_mode": self.trigger_mode, "show_api": self.show_api, "zerogpu": self.zero_gpu, "rendered_in": self.rendered_in._id if self.rendered_in else None, "connection": self.connection, "time_limit": self.time_limit, "stream_every": self.stream_every, "like_user_message": self.like_user_message, "event_specific_args": self.event_specific_args, } def postprocess_update_dict( block: Component | BlockContext, update_dict: dict, postprocess: bool = True ): """ Converts a dictionary of updates into a format that can be sent to the frontend to update the component. E.g. {"value": "2", "visible": True, "invalid_arg": "hello"} Into -> {"__type__": "update", "value": 2.0, "visible": True} Parameters: block: The Block that is being updated with this update dictionary. update_dict: The original update dictionary postprocess: Whether to postprocess the "value" key of the update dictionary. """ value = update_dict.pop("value", components._Keywords.NO_VALUE) update_dict = {k: getattr(block, k) for k in update_dict if hasattr(block, k)} if value is not components._Keywords.NO_VALUE: if postprocess: update_dict["value"] = block.postprocess(value) if isinstance(update_dict["value"], (GradioModel, GradioRootModel)): update_dict["value"] = update_dict["value"].model_dump() else: update_dict["value"] = value update_dict["__type__"] = "update" return update_dict def convert_component_dict_to_list( outputs_ids: list[int], predictions: dict ) -> list | dict: """ Converts a dictionary of component updates into a list of updates in the order of the outputs_ids and including every output component. Leaves other types of dictionaries unchanged. E.g. {"textbox": "hello", "number": {"__type__": "generic_update", "value": "2"}} Into -> ["hello", {"__type__": "generic_update"}, {"__type__": "generic_update", "value": "2"}] """ keys_are_blocks = [isinstance(key, Block) for key in predictions] if all(keys_are_blocks): reordered_predictions = [skip() for _ in outputs_ids] for component, value in predictions.items(): if component._id not in outputs_ids: raise ValueError( f"Returned component {component} not specified as output of function." ) output_index = outputs_ids.index(component._id) reordered_predictions[output_index] = value predictions = utils.resolve_singleton(reordered_predictions) elif any(keys_are_blocks): raise ValueError( "Returned dictionary included some keys as Components. Either all keys must be Components to assign Component values, or return a List of values to assign output values in order." ) return predictions class BlocksConfig: def __init__(self, root_block: Blocks): self._id: int = 0 self.root_block = root_block self.blocks: dict[int, Component | Block] = {} self.fns: dict[int, BlockFunction] = {} self.fn_id: int = 0 def set_event_trigger( self, targets: Sequence[EventListenerMethod], fn: Callable | None, inputs: ( Component | BlockContext | Sequence[Component | BlockContext] | Set[Component | BlockContext] | None ), outputs: ( Component | BlockContext | Sequence[Component | BlockContext] | Set[Component | BlockContext] | None ), preprocess: bool = True, postprocess: bool = True, scroll_to_output: bool = False, show_progress: Literal["full", "minimal", "hidden"] = "full", api_name: str | None | Literal[False] = None, js: str | None = None, no_target: bool = False, queue: bool = True, batch: bool = False, max_batch_size: int = 4, cancels: list[int] | None = None, collects_event_data: bool | None = None, trigger_after: int | None = None, trigger_only_on_success: bool = False, trigger_mode: Literal["once", "multiple", "always_last"] | None = "once", concurrency_limit: int | None | Literal["default"] = "default", concurrency_id: str | None = None, show_api: bool = True, renderable: Renderable | None = None, is_cancel_function: bool = False, connection: Literal["stream", "sse"] = "sse", time_limit: float | None = None, stream_every: float = 0.5, like_user_message: bool = False, event_specific_args: list[str] | None = None, ) -> tuple[BlockFunction, int]: """ Adds an event to the component's dependencies. Parameters: targets: a list of EventListenerMethod objects that define the event trigger fn: the function to run when the event is triggered inputs: the list of input components whose values will be passed to the function outputs: the list of output components whose values will be updated by the function preprocess: whether to run the preprocess methods of the input components before running the function postprocess: whether to run the postprocess methods of the output components after running the function scroll_to_output: whether to scroll to output of dependency on trigger show_progress: how to show the progress animation while event is running: "full" shows a spinner which covers the output component area as well as a runtime display in the upper right corner, "minimal" only shows the runtime display, "hidden" shows no progress animation at all api_name: defines how the endpoint appears in the API docs. Can be a string, None, or False. If set to a string, the endpoint will be exposed in the API docs with the given name. If None (default), the name of the function will be used as the API endpoint. If False, the endpoint will not be exposed in the API docs and downstream apps (including those that `gr.load` this app) will not be able to use this event. js: Optional frontend js method to run before running 'fn'. Input arguments for js method are values of 'inputs' and 'outputs', return should be a list of values for output components no_target: if True, sets "targets" to [], used for the Blocks.load() event and .then() events queue: If True, will place the request on the queue, if the queue has been enabled. If False, will not put this event on the queue, even if the queue has been enabled. If None, will use the queue setting of the gradio app. batch: whether this function takes in a batch of inputs max_batch_size: the maximum batch size to send to the function cancels: a list of other events to cancel when this event is triggered. For example, setting cancels=[click_event] will cancel the click_event, where click_event is the return value of another components .click method. collects_event_data: whether to collect event data for this event trigger_after: if set, this event will be triggered after 'trigger_after' function index trigger_only_on_success: if True, this event will only be triggered if the previous event was successful (only applies if `trigger_after` is set) trigger_mode: If "once" (default for all events except `.change()`) would not allow any submissions while an event is pending. If set to "multiple", unlimited submissions are allowed while pending, and "always_last" (default for `.change()` and `.key_up()` events) would allow a second submission after the pending event is complete. concurrency_limit: If set, this is the maximum number of this event that can be running simultaneously. Can be set to None to mean no concurrency_limit (any number of this event can be running simultaneously). Set to "default" to use the default concurrency limit (defined by the `default_concurrency_limit` parameter in `queue()`, which itself is 1 by default). concurrency_id: If set, this is the id of the concurrency group. Events with the same concurrency_id will be limited by the lowest set concurrency_limit. show_api: whether to show this event in the "view API" page of the Gradio app, or in the ".view_api()" method of the Gradio clients. Unlike setting api_name to False, setting show_api to False will still allow downstream apps as well as the Clients to use this event. If fn is None, show_api will automatically be set to False. is_cancel_function: whether this event cancels another running event. connection: The connection format, either "sse" or "stream". time_limit: The time limit for the function to run. Parameter only used for the `.stream()` event. stream_every: The latency (in seconds) at which stream chunks are sent to the backend. Defaults to 0.5 seconds. Parameter only used for the `.stream()` event. Returns: dependency information, dependency index """ # Support for singular parameter _targets = [ ( target.block._id if not no_target and target.block else None, target.event_name, ) for target in targets ] if isinstance(inputs, Set): inputs_as_dict = True inputs = sorted(inputs, key=lambda x: x._id) else: inputs_as_dict = False if inputs is None: inputs = [] elif not isinstance(inputs, Sequence): inputs = [inputs] if isinstance(outputs, Set): outputs = sorted(outputs, key=lambda x: x._id) elif outputs is None: outputs = [] elif not isinstance(outputs, Sequence): outputs = [outputs] if fn is not None and not cancels: check_function_inputs_match(fn, inputs, inputs_as_dict) if _targets[0][1] in ["change", "key_up"] and trigger_mode is None: trigger_mode = "always_last" elif _targets[0][1] in ["stream"] and trigger_mode is None: trigger_mode = "multiple" elif trigger_mode is None: trigger_mode = "once" elif trigger_mode not in ["once", "multiple", "always_last"]: raise ValueError( f"Invalid value for parameter `trigger_mode`: {trigger_mode}. Please choose from: {['once', 'multiple', 'always_last']}" ) _, progress_index, event_data_index = ( special_args(fn) if fn else (None, None, None) ) # If api_name is None or empty string, use the function name if api_name is None or isinstance(api_name, str) and api_name.strip() == "": if fn is not None: if not hasattr(fn, "__name__"): if hasattr(fn, "__class__") and hasattr(fn.__class__, "__name__"): name = fn.__class__.__name__ else: name = "unnamed" else: name = fn.__name__ api_name = "".join( [s for s in name if s not in set(string.punctuation) - {"-", "_"}] ) elif js is not None: api_name = "js_fn" show_api = False else: api_name = "unnamed" show_api = False if api_name is not False: api_name = utils.append_unique_suffix( api_name, [ fn.api_name for fn in self.fns.values() if isinstance(fn.api_name, str) ], ) else: show_api = False # The `show_api` parameter is False if: (1) the user explicitly sets it (2) the user sets `api_name` to False # or (3) the user sets `fn` to None (there's no backend function) if collects_event_data is None: collects_event_data = event_data_index is not None rendered_in = LocalContext.renderable.get() block_fn = BlockFunction( fn, inputs, outputs, preprocess, postprocess, _id=self.fn_id, inputs_as_dict=inputs_as_dict, targets=_targets, batch=batch, max_batch_size=max_batch_size, concurrency_limit=concurrency_limit, concurrency_id=concurrency_id, tracks_progress=progress_index is not None, api_name=api_name, js=js, show_progress=show_progress, cancels=cancels, collects_event_data=collects_event_data, trigger_after=trigger_after, trigger_only_on_success=trigger_only_on_success, trigger_mode=trigger_mode, queue=queue, scroll_to_output=scroll_to_output, show_api=show_api, renderable=renderable, rendered_in=rendered_in, is_cancel_function=is_cancel_function, connection=connection, time_limit=time_limit, stream_every=stream_every, like_user_message=like_user_message, event_specific_args=event_specific_args, ) self.fns[self.fn_id] = block_fn self.fn_id += 1 return block_fn, block_fn._id def get_config(self, renderable: Renderable | None = None): config = {} rendered_ids = [] def get_layout(block: Block): rendered_ids.append(block._id) if not isinstance(block, BlockContext): return {"id": block._id} children_layout = [] for child in block.children: children_layout.append(get_layout(child)) return {"id": block._id, "children": children_layout} if renderable: root_block = self.blocks[renderable.container_id] else: root_block = self.root_block config["layout"] = get_layout(root_block) config["components"] = [] for _id, block in self.blocks.items(): if renderable: if _id not in rendered_ids: continue if block.key: block.key = f"{renderable._id}-{block.key}" props = block.get_config() if hasattr(block, "get_config") else {} block_config = { "id": _id, "type": block.get_block_name(), "props": utils.delete_none(props), "skip_api": block.skip_api, "component_class_id": getattr(block, "component_class_id", None), "key": block.key, } if renderable: block_config["renderable"] = renderable._id if not block.skip_api: block_config["api_info"] = block.api_info() # type: ignore if hasattr(block, "api_info_as_input"): block_config["api_info_as_input"] = block.api_info_as_input() # type: ignore else: block_config["api_info_as_input"] = block.api_info() # type: ignore if hasattr(block, "api_info_as_output"): block_config["api_info_as_output"] = block.api_info_as_output() # type: ignore else: block_config["api_info_as_output"] = block.api_info() # type: ignore block_config["example_inputs"] = block.example_inputs() # type: ignore config["components"].append(block_config) dependencies = [] for fn in self.fns.values(): if renderable is None or fn.rendered_in == renderable: dependencies.append(fn.get_config()) config["dependencies"] = dependencies return config def __copy__(self): new = BlocksConfig(self.root_block) new.blocks = copy.copy(self.blocks) new.fns = copy.copy(self.fns) new.fn_id = self.fn_id return new @document("launch", "queue", "integrate", "load", "unload") class Blocks(BlockContext, BlocksEvents, metaclass=BlocksMeta): """ Blocks is Gradio's low-level API that allows you to create more custom web applications and demos than Interfaces (yet still entirely in Python). Compared to the Interface class, Blocks offers more flexibility and control over: (1) the layout of components (2) the events that trigger the execution of functions (3) data flows (e.g. inputs can trigger outputs, which can trigger the next level of outputs). Blocks also offers ways to group together related demos such as with tabs. The basic usage of Blocks is as follows: create a Blocks object, then use it as a context (with the "with" statement), and then define layouts, components, or events within the Blocks context. Finally, call the launch() method to launch the demo. Example: import gradio as gr def update(name): return f"Welcome to Gradio, {name}!" with gr.Blocks() as demo: gr.Markdown("Start typing below and then click **Run** to see the output.") with gr.Row(): inp = gr.Textbox(placeholder="What is your name?") out = gr.Textbox() btn = gr.Button("Run") btn.click(fn=update, inputs=inp, outputs=out) demo.launch() Demos: blocks_hello, blocks_flipper, blocks_kinematics Guides: blocks-and-event-listeners, controlling-layout, state-in-blocks, custom-CSS-and-JS, using-blocks-like-functions """ def __init__( self, theme: Theme | str | None = None, analytics_enabled: bool | None = None, mode: str = "blocks", title: str = "Gradio", css: str | None = None, css_paths: str | Path | Sequence[str | Path] | None = None, js: str | None = None, head: str | None = None, head_paths: str | Path | Sequence[str | Path] | None = None, fill_height: bool = False, fill_width: bool = False, delete_cache: tuple[int, int] | None = None, **kwargs, ): """ Parameters: theme: A Theme object or a string representing a theme. If a string, will look for a built-in theme with that name (e.g. "soft" or "default"), or will attempt to load a theme from the Hugging Face Hub (e.g. "gradio/monochrome"). If None, will use the Default theme. analytics_enabled: Whether to allow basic telemetry. If None, will use GRADIO_ANALYTICS_ENABLED environment variable or default to True. mode: A human-friendly name for the kind of Blocks or Interface being created. Used internally for analytics. title: The tab title to display when this is opened in a browser window. css: Custom css as a code string. This css will be included in the demo webpage. css_paths: Custom css as a pathlib.Path to a css file or a list of such paths. This css files will be read, concatenated, and included in the demo webpage. If the `css` parameter is also set, the css from `css` will be included first. js: Custom js as a code string. The custom js should be in the form of a single js function. This function will automatically be executed when the page loads. For more flexibility, use the head parameter to insert js inside