| """Gradio app runner β launches Gradio apps as subprocess servers. |
| |
| Manages the lifecycle of Gradio app processes: start, status check, and stop. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import logging |
| import os |
| import re |
| import subprocess |
| import sys |
| import tempfile |
| from pathlib import Path |
| from typing import Any |
|
|
| logger = logging.getLogger(__name__) |
|
|
| |
|
|
| _running_gradio_procs: dict[str, subprocess.Popen] = {} |
|
|
|
|
| def run_gradio_app(code: str, port: int = 7861) -> dict[str, Any]: |
| """Launch a Gradio app as a subprocess and return its URL. |
| |
| The Gradio app is run on the specified port. We modify the code |
| to ensure it launches on the correct port and is accessible. |
| """ |
| |
| _stop_all_procs() |
|
|
| |
| patched_code = code |
|
|
| |
| patched_code = re.sub( |
| r"(\w+)\.launch\([^)]*\)", |
| f'\\1.launch(server_name="0.0.0.0", server_port={port}, share=False)', |
| patched_code, |
| ) |
|
|
| |
| if ".launch(" not in patched_code: |
| patched_code += ( |
| f'\n\nif __name__ == "__main__":\n' |
| f' iface.launch(server_name="0.0.0.0", server_port={port}, share=False)\n' |
| ) |
|
|
| with tempfile.TemporaryDirectory(prefix="gradio_app_") as tmp: |
| app_path = Path(tmp) / "gradio_app.py" |
| app_path.write_text(patched_code, encoding="utf-8") |
|
|
| env = { |
| **os.environ, |
| "PYTHONUNBUFFERED": "1", |
| "GRADIO_SERVER_NAME": "0.0.0.0", |
| "GRADIO_SERVER_PORT": str(port), |
| } |
|
|
| try: |
| proc = subprocess.Popen( |
| [sys.executable, str(app_path)], |
| cwd=tmp, |
| env=env, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE, |
| text=True, |
| ) |
|
|
| proc_id = f"gradio_{port}" |
| _running_gradio_procs[proc_id] = proc |
|
|
| |
| import time as _time |
| _time.sleep(3) |
|
|
| |
| poll = proc.poll() |
| if poll is not None: |
| stdout = proc.stdout.read() if proc.stdout else "" |
| stderr = proc.stderr.read() if proc.stderr else "" |
| return { |
| "success": False, |
| "url": "", |
| "message": f"Gradio app exited with code {poll}", |
| "stdout": stdout[-2000:] if stdout else "", |
| "stderr": stderr[-2000:] if stderr else "", |
| } |
|
|
| gradio_url = f"http://localhost:{port}" |
| return { |
| "success": True, |
| "url": gradio_url, |
| "message": f"Gradio app running at {gradio_url}", |
| "port": port, |
| } |
|
|
| except Exception as exc: |
| logger.exception("Failed to launch Gradio app") |
| return { |
| "success": False, |
| "url": "", |
| "message": f"Failed to launch: {exc}", |
| } |
|
|
|
|
| def stop_gradio_app() -> dict[str, Any]: |
| """Stop any running Gradio app subprocess.""" |
| stopped = _stop_all_procs() |
| return {"success": True, "message": f"Stopped {stopped} Gradio app(s)"} |
|
|
|
|
| def _stop_all_procs() -> int: |
| """Stop all running Gradio processes. Returns count of stopped procs.""" |
| stopped = 0 |
| for pid, proc in list(_running_gradio_procs.items()): |
| try: |
| proc.terminate() |
| proc.wait(timeout=3) |
| stopped += 1 |
| except Exception: |
| try: |
| proc.kill() |
| stopped += 1 |
| except Exception: |
| pass |
| _running_gradio_procs.clear() |
| return stopped |
|
|