""" 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)