from __future__ import annotations import shutil from pathlib import Path from typing import Annotated, Optional import typer from rich import print from rich.panel import Panel from rich.prompt import Confirm, Prompt from tomlkit import dump, parse from gradio.analytics import custom_component_analytics from ..display import LivePanelDisplay from . import _create_utils from .install_component import _get_npm, _install_command def _create( name: Annotated[ str, typer.Argument( help="Name of the component. Preferably in camel case, i.e. MyTextBox." ), ], directory: Annotated[ Optional[Path], typer.Option( help="Directory to create the component in. Default is None. If None, will be created in directory in the current directory." ), ] = None, package_name: Annotated[ Optional[str], typer.Option(help="Name of the package. Default is gradio_{name.lower()}"), ] = None, template: Annotated[ str, typer.Option( help="Component to use as a template. Should use exact name of python class." ), ] = "", install: Annotated[ bool, typer.Option( help="Whether to install the component in your current environment as a development install. Recommended for development." ), ] = True, npm_install: Annotated[ str, typer.Option(help="NPM install command to use. Default is 'npm install'."), ] = "npm install", pip_path: Annotated[ Optional[str], typer.Option( help="Path to pip executable. If None, will use the default path found by `which pip3`. If pip3 is not found, `which pip` will be tried. If both fail an error will be raised." ), ] = None, overwrite: Annotated[ bool, typer.Option(help="Whether to overwrite the existing component if it exists."), ] = False, configure_metadata: Annotated[ bool, typer.Option( help="Whether to interactively configure project metadata based on user input" ), ] = True, ): custom_component_analytics( "create", template, None, None, None, npm_install=npm_install, ) if not directory: directory = Path(name.lower()) if not package_name: package_name = f"gradio_{name.lower()}" if directory.exists() and not overwrite: raise ValueError( f"The directory {directory.resolve()} already exists. " "Please set --overwrite flag or pass in the name " "of a directory that does not already exist via the --directory option." ) elif directory.exists() and overwrite: _create_utils.delete_contents(directory) directory.mkdir(exist_ok=overwrite) if _create_utils._in_test_dir(): npm_install = f"{shutil.which('pnpm')} i --ignore-scripts" else: npm_install = _get_npm(npm_install) with LivePanelDisplay() as live: live.update( f":building_construction: Creating component [orange3]{name}[/] in directory [orange3]{directory}[/]", add_sleep=0.2, ) if template: live.update(f":fax: Starting from template [orange3]{template}[/]") else: live.update(":page_facing_up: Creating a new component from scratch.") component = _create_utils._get_component_code(template) _create_utils._create_backend(name, component, directory, package_name) live.update(":snake: Created backend code", add_sleep=0.2) _create_utils._create_frontend( name.lower(), component, directory=directory, package_name=package_name ) live.update(":art: Created frontend code", add_sleep=0.2) if install: _install_command(directory, live, npm_install, pip_path) live._panel.stop() description = "A gradio custom component" keywords = [] if configure_metadata: print( Panel( "It is recommended to answer the following [bold][magenta]4 questions[/][/] to finish configuring your custom component's metadata." "\nYou can also answer them later by editing the [bold][magenta]pyproject.toml[/][/] file in your component directory." ) ) answer_qs = Confirm.ask("\nDo you want to answer them now?") pyproject_toml = parse((directory / "pyproject.toml").read_text()) if answer_qs: name = pyproject_toml["project"]["name"] # type: ignore description = Prompt.ask( "\n:pencil: Please enter a one sentence [bold][magenta]description[/][/] for your component" ) if description: pyproject_toml["project"]["description"] = description # type: ignore license_ = ( Prompt.ask( "\n:bookmark_tabs: Please enter a [bold][magenta]software license[/][/] for your component. Leave blank for 'apache-2.0'" ) or "apache-2.0" ) print(f":bookmark_tabs: Using license [bold][magenta]{license_}[/][/]") pyproject_toml["project"]["license"] = license_ # type: ignore requires_python = Prompt.ask( "\n:snake: Please enter the [bold][magenta]allowed python[/][/] versions for your component. Leave blank for '>=3.10'" ) requires_python = requires_python or ">=3.10" print( f":snake: Using requires-python of [bold][magenta]{requires_python}[/][/]" ) pyproject_toml["project"]["requires-python"] = ( # type: ignore requires_python or ">=3.10" ) print( "\n:label: Please add some keywords to help others discover your component." ) while True: keyword = Prompt.ask(":label: Leave blank to stop adding keywords") if keyword: keywords.append(keyword) else: break current_keywords = pyproject_toml["project"].get("keywords", []) # type: ignore pyproject_toml["project"]["keywords"] = current_keywords + keywords # type: ignore with open(directory / "pyproject.toml", "w", encoding="utf-8") as f: dump(pyproject_toml, f) (directory / "demo" / "requirements.txt").write_text(package_name) readme_path = Path(__file__).parent / "files" / "README.md" readme_contents = readme_path.read_text() tags = f", {', '.join(keywords)}" if keywords else "" template = f", {template}" readme_contents = ( readme_contents.replace("<>", package_name) .replace("<<short-description>>", description) .replace("<<tags>>", tags) .replace("<<template>>", template) ) (directory / "README.md").write_text(readme_contents) print("\nComponent creation [bold][magenta]complete[/][/]!")