Skip to content

Commit

Permalink
Add --python-path, --pip-path, --gradio-path CLI arguments to let cus…
Browse files Browse the repository at this point in the history
…tom component developers control which executable is used (#7638)

* backend code

* Frontend code

* add changeset

* Add to FAQ

* lint

* Check pip3/python3

* Check pip/python3 and edit docstrings.

* Build docstring

* more informative error

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
  • Loading branch information
freddyaboulton and gradio-pr-bot committed Mar 8, 2024
1 parent 57510f9 commit b3b0ea3
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 61 deletions.
6 changes: 6 additions & 0 deletions .changeset/fast-heads-visit.md
@@ -0,0 +1,6 @@
---
"@gradio/preview": patch
"gradio": patch
---

fix:Add --python-path, --pip-path, --gradio-path CLI arguments to let custom component developers control which executable is used
16 changes: 15 additions & 1 deletion gradio/cli/commands/components/build.py
Expand Up @@ -2,6 +2,7 @@
import shutil
import subprocess
from pathlib import Path
from typing import Optional

import semantic_version
import typer
Expand All @@ -13,6 +14,7 @@
get_deep,
)
from gradio.cli.commands.components.docs import run_command
from gradio.cli.commands.components.install_component import _get_executable_path
from gradio.cli.commands.display import LivePanelDisplay

gradio_template_path = Path(gradio.__file__).parent / "templates" / "frontend"
Expand All @@ -32,6 +34,12 @@ def _build(
generate_docs: Annotated[
bool, typer.Option(help="Whether to generate the documentation as well.")
] = True,
python_path: Annotated[
Optional[str],
typer.Option(
help="Path to python executable. If None, will use the default path found by `which python3`. If python3 is not found, `which python` will be tried. If both fail an error will be raised."
),
] = None,
):
name = Path(path).resolve()
if not (name / "pyproject.toml").exists():
Expand All @@ -44,6 +52,10 @@ def _build(
pyproject_toml = parse((path / "pyproject.toml").read_text())
package_name = get_deep(pyproject_toml, ["project", "name"])

python_path = _get_executable_path(
"python", None, "--python-path", check_3=True
)

if not isinstance(package_name, str):
raise ValueError(
"Your pyproject.toml file does not have a [project] name field!"
Expand Down Expand Up @@ -115,6 +127,8 @@ def _build(
gradio_template_path,
"--mode",
"build",
"--python-path",
python_path,
]
pipe = subprocess.run(
node_cmds, capture_output=True, text=True, check=False
Expand All @@ -127,7 +141,7 @@ def _build(
else:
live.update(":white_check_mark: Build succeeded!")

cmds = [shutil.which("python"), "-m", "build", str(name)]
cmds = [python_path, "-m", "build", str(name)]
live.update(f":construction_worker: Building... [grey37]({' '.join(cmds)})[/]")
pipe = subprocess.run(cmds, capture_output=True, text=True, check=False)
if pipe.returncode != 0:
Expand Down
8 changes: 7 additions & 1 deletion gradio/cli/commands/components/create.py
Expand Up @@ -48,6 +48,12 @@ def _create(
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."),
Expand Down Expand Up @@ -101,7 +107,7 @@ def _create(
live.update(":art: Created frontend code", add_sleep=0.2)

if install:
_install_command(directory, live, npm_install)
_install_command(directory, live, npm_install, pip_path)

live._panel.stop()

Expand Down
25 changes: 25 additions & 0 deletions gradio/cli/commands/components/dev.py
@@ -1,12 +1,14 @@
import shutil
import subprocess
from pathlib import Path
from typing import Optional

import typer
from rich import print
from typing_extensions import Annotated

import gradio
from gradio.cli.commands.components.install_component import _get_executable_path

gradio_template_path = Path(gradio.__file__).parent / "templates" / "frontend"
gradio_node_path = Path(gradio.__file__).parent / "node" / "dev" / "files" / "index.js"
Expand All @@ -31,6 +33,18 @@ def _dev(
help="The host to run the front end server on. Defaults to localhost.",
),
] = "localhost",
python_path: Annotated[
Optional[str],
typer.Option(
help="Path to python executable. If None, will use the default path found by `which python3`. If python3 is not found, `which python` will be tried. If both fail an error will be raised."
),
] = None,
gradio_path: Annotated[
Optional[str],
typer.Option(
help="Path to gradio executable. If None, will use the default path found by `shutil.which`."
),
] = None,
):
component_directory = component_directory.resolve()

Expand All @@ -40,6 +54,13 @@ def _dev(
if not node:
raise ValueError("node must be installed in order to run dev mode.")

python_path = _get_executable_path(
"python", python_path, cli_arg_name="--python-path", check_3=True
)
gradio_path = _get_executable_path(
"gradio", gradio_path, cli_arg_name="--gradio-path"
)

proc = subprocess.Popen(
[
node,
Expand All @@ -54,6 +75,10 @@ def _dev(
"dev",
"--host",
host,
"--python-path",
python_path,
"--gradio-path",
gradio_path,
],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
Expand Down
47 changes: 44 additions & 3 deletions gradio/cli/commands/components/install_component.py
@@ -1,6 +1,7 @@
import shutil
import subprocess
from pathlib import Path
from typing import Optional

from rich.markup import escape
from typer import Argument, Option
Expand All @@ -24,8 +25,42 @@ def _get_npm(npm_install: str):
return npm_install


def _install_command(directory: Path, live: LivePanelDisplay, npm_install: str):
cmds = [shutil.which("pip"), "install", "-e", f"{str(directory)}[dev]"]
def _get_executable_path(
executable: str,
executable_path: str | None,
cli_arg_name: str,
check_3: bool = False,
) -> str:
"""Get the path to an executable, either from the provided path or from the PATH environment variable.
The value of executable_path takes precedence in case the value in PATH is incorrect.
This should give more control to the developer in case their envrinment is not set up correctly.
If check_3 is True, we append 3 to the executable name to give python3 priority over python (same for pip).
"""
if executable_path:
if not Path(executable_path).exists() or not Path(executable_path).is_file():
raise ValueError(
f"The provided {executable} path ({executable_path}) does not exist or is not a file."
)
return executable_path
path = shutil.which(executable)
if check_3:
path = shutil.which(f"{executable}3") or path
if not path:
raise ValueError(
f"Could not find {executable}. Please ensure it is installed and in your PATH or pass the {cli_arg_name} parameter."
)
return path


def _install_command(
directory: Path, live: LivePanelDisplay, npm_install: str, pip_path: str | None
):
pip_executable_path = _get_executable_path(
"pip", executable_path=pip_path, cli_arg_name="--pip-path", check_3=True
)
cmds = [pip_executable_path, "install", "-e", f"{str(directory)}[dev]"]
live.update(
f":construction_worker: Installing python... [grey37]({escape(' '.join(cmds))})[/]"
)
Expand Down Expand Up @@ -59,7 +94,13 @@ def _install(
npm_install: Annotated[
str, Option(help="NPM install command to use. Default is 'npm install'.")
] = "npm install",
pip_path: Annotated[
Optional[str],
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,
):
npm_install = _get_npm(npm_install)
with LivePanelDisplay() as live:
_install_command(directory, live, npm_install)
_install_command(directory, live, npm_install, pip_path)
2 changes: 2 additions & 0 deletions guides/05_custom-components/06_frequently-asked-questions.md
Expand Up @@ -15,6 +15,8 @@ This is like when you run `python <app-file>.py`, however the `gradio` command w
## The development server didn't work for me
Make sure you have your package installed along with any dependencies you have added by running `gradio cc install`.
Make sure there aren't any syntax or import errors in the Python or JavaScript code.
If the development server is still not working for you, please use the `--python-path` and `gradio-path` CLI arguments to specify the path of the python and gradio executables for the environment your component is installed in.
It is likely that the wrong envrironment is being used. For example, if you are using a virtualenv located at `/Users/mary/venv`, pass in `/Users/mary/bin/python` and `/Users/mary/bin/gradio` respectively.

## Do I need to host my custom component on HuggingFace Spaces?
You can develop and build your custom component without hosting or connecting to HuggingFace.
Expand Down
11 changes: 9 additions & 2 deletions js/preview/src/build.ts
Expand Up @@ -8,16 +8,23 @@ import { examine_module } from "./index";
interface BuildOptions {
component_dir: string;
root_dir: string;
python_path: string;
}

export async function make_build({
component_dir,
root_dir
root_dir,
python_path
}: BuildOptions): Promise<void> {
process.env.gradio_mode = "dev";
const svelte_dir = join(root_dir, "assets", "svelte");

const module_meta = examine_module(component_dir, root_dir, "build");
const module_meta = examine_module(
component_dir,
root_dir,
python_path,
"build"
);
try {
for (const comp of module_meta) {
const template_dir = comp.template_dir;
Expand Down
19 changes: 15 additions & 4 deletions js/preview/src/dev.ts
Expand Up @@ -23,17 +23,19 @@ interface ServerOptions {
frontend_port: number;
backend_port: number;
host: string;
python_path: string;
}

export async function create_server({
component_dir,
root_dir,
frontend_port,
backend_port,
host
host,
python_path
}: ServerOptions): Promise<void> {
process.env.gradio_mode = "dev";
const imports = generate_imports(component_dir, root_dir);
const imports = generate_imports(component_dir, root_dir, python_path);

const NODE_DIR = join(root_dir, "..", "..", "node", "dev");
const svelte_dir = join(root_dir, "assets", "svelte");
Expand Down Expand Up @@ -109,12 +111,21 @@ function to_posix(_path: string): string {
return _path.replace(/\\/g, "/");
}

function generate_imports(component_dir: string, root: string): string {
function generate_imports(
component_dir: string,
root: string,
python_path: string
): string {
const components = find_frontend_folders(component_dir);

const component_entries = components.flatMap((component) => {
return examine_module(component, root, "dev");
return examine_module(component, root, python_path, "dev");
});
if (component_entries.length === 0) {
console.info(
`No custom components were found in ${component_dir}. It is likely that dev mode does not work properly. Please pass the --gradio-path and --python-path CLI arguments so that gradio uses the right executables.`
);
}

const imports = component_entries.reduce((acc, component) => {
const pkg = JSON.parse(
Expand Down

0 comments on commit b3b0ea3

Please sign in to comment.