|
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 <component-name> 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"] |
|
|
|
description = Prompt.ask( |
|
"\n:pencil: Please enter a one sentence [bold][magenta]description[/][/] for your component" |
|
) |
|
if description: |
|
pyproject_toml["project"]["description"] = description |
|
|
|
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_ |
|
|
|
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"] = ( |
|
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", []) |
|
pyproject_toml["project"]["keywords"] = current_keywords + keywords |
|
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("<<title>>", 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[/][/]!") |
|
|