Spaces:
Paused
Paused
| """ | |
| A robust wrapper for the Face/Off application that extracts a bundled zip file and | |
| locates the real `app.py` file within it. This wrapper avoids executing the | |
| embedded app's `if __name__ == '__main__'` block and supports multiple archive | |
| layouts (with or without a top-level directory). | |
| When deployed on Hugging Face Spaces, the repository root is `/home/user/app`. | |
| This script will extract `faceoff-space.zip` into a dedicated directory and | |
| search for `app.py` within the extracted contents. The first match is loaded | |
| with `runpy.run_path` using a custom module name to prevent side effects. | |
| """ | |
| import os | |
| import zipfile | |
| import sys | |
| import runpy | |
| # Name of the bundled zip containing the full Face/Off project | |
| ZIP_NAME = "faceoff-space.zip" | |
| # Determine absolute paths based on current file location | |
| base_dir = os.path.dirname(__file__) | |
| zip_path = os.path.join(base_dir, ZIP_NAME) | |
| # Directory where the archive will be unpacked. Using a distinct name ensures we | |
| # don't pollute the repo root and can detect if we've already extracted it. | |
| extract_target = os.path.join(base_dir, "faceoff-space-extracted") | |
| # Always (re)unpack the zip on startup. If the extraction directory exists from | |
| # a previous run, remove it first to ensure that stale files are not served. | |
| if os.path.isfile(zip_path): | |
| # Clean out any old extracted contents | |
| if os.path.isdir(extract_target): | |
| import shutil | |
| shutil.rmtree(extract_target, ignore_errors=True) | |
| os.makedirs(extract_target, exist_ok=True) | |
| with zipfile.ZipFile(zip_path, "r") as archive: | |
| archive.extractall(extract_target) | |
| # Ensure extracted directories are in the module search path. We insert both | |
| # the extraction root and any immediate subdirectories to ease module imports. | |
| if os.path.isdir(extract_target) and extract_target not in sys.path: | |
| sys.path.insert(0, extract_target) | |
| # Also add one level deeper (e.g., faceoff-space) to sys.path if present | |
| for entry in os.listdir(extract_target): | |
| candidate = os.path.join(extract_target, entry) | |
| if os.path.isdir(candidate) and candidate not in sys.path: | |
| sys.path.insert(0, candidate) | |
| def find_app_py(root: str) -> str: | |
| """Recursively search for an `app.py` file starting at `root` and return | |
| its path. Raises FileNotFoundError if none is found. | |
| """ | |
| for current_dir, subdirs, files in os.walk(root): | |
| if "app.py" in files: | |
| return os.path.join(current_dir, "app.py") | |
| raise FileNotFoundError(f"`app.py` not found under {root}") | |
| # Locate the actual application file within the extracted archive | |
| try: | |
| app_path = find_app_py(extract_target) | |
| except FileNotFoundError: | |
| # Fall back to the repository root in case extraction failed but files exist directly | |
| try: | |
| app_path = find_app_py(base_dir) | |
| except FileNotFoundError as exc: | |
| raise RuntimeError( | |
| "Unable to locate the Face/Off application (`app.py`). Ensure that " | |
| f"{ZIP_NAME} contains the app files." | |
| ) from exc | |
| # Dynamically load the application while preventing execution of its main block. | |
| app_globals = runpy.run_path(app_path, run_name="faceoff_app") | |
| # Gradio Spaces expect either a `demo` object (Blocks/Interface) or, for FastAPI | |
| # Spaces, an `app` object. The Face/Off project defines both. Surface both | |
| # variables from the loaded module if they exist so that the runtime can | |
| # initialize properly regardless of the selected SDK. | |
| demo = app_globals.get("demo") | |
| app = app_globals.get("app") | |
| # If neither object was found, raise an informative error. This makes it clear | |
| # that the wrapped project did not expose the expected variables. | |
| if demo is None and app is None: | |
| raise RuntimeError( | |
| "Neither `demo` nor `app` was found in the extracted Face/Off project. " | |
| "Ensure that `demo` (Gradio Blocks) or `app` (FastAPI) is defined." | |
| ) | |
| # If running in a Hugging Face Space, automatically launch the Gradio demo. Many | |
| # Gradio Spaces rely on an implicit call to `demo.launch()` provided by the | |
| # platform. However, because we dynamically import the application, the | |
| # automatic initialization does not occur. To ensure the server starts, | |
| # explicitly launch the demo when this file is executed as the main module. We | |
| # use `queue()` to enable concurrency and disable analytics endpoints with | |
| # `show_api=False`. When running locally, you can still execute this file | |
| # directly (e.g. `python app.py`) and the server will start. | |
| if __name__ == "__main__": | |
| import os | |
| if demo is not None: | |
| # Determine the port Hugging Face spaces use (defaults to 7860 locally). | |
| port = int(os.getenv("PORT", "7860")) | |
| # Launch the Gradio app with queuing and without external share links. | |
| demo.queue().launch(server_name="0.0.0.0", server_port=port, show_api=False, share=False) | |
| elif app is not None: | |
| # Fallback: run FastAPI using uvicorn if no Gradio demo is available. | |
| import uvicorn | |
| port = int(os.getenv("PORT", "7860")) | |
| uvicorn.run(app, host="0.0.0.0", port=port) | |