"""Hugging Face Spaces default entry (Streamlit SDK expects `app.py`). Local development can still use `streamlit run streamlit_app.py`; Docker Compose uses `app.py` so the same entry path works on the Hub and in containers. On Hugging Face Streamlit Spaces only `streamlit run app.py` is started — no separate uvicorn process — so we spawn the FastAPI app on 127.0.0.1:8000 when `SPACE_ID` is present (see Hub built-in env vars). Set `DOC_AUDI_EMBED_API=0` to disable. Use `DOC_AUDI_EMBED_API=1` to force embedding elsewhere (e.g. demos). """ from __future__ import annotations import atexit import os import socket import subprocess import sys import time _uvicorn_proc: subprocess.Popen[bytes] | None = None _cleanup_registered = False def _port_accepting_connections(host: str, port: int) -> bool: try: with socket.create_connection((host, port), timeout=0.3): return True except OSError: return False def _want_embedded_api() -> bool: if os.environ.get("DOC_AUDI_EMBED_API", "").lower() in ("0", "false", "no"): return False if os.environ.get("DOC_AUDI_EMBED_API", "").lower() in ("1", "true", "yes"): return True return bool(os.environ.get("SPACE_ID")) def _propagate_streamlit_secrets_to_environ() -> None: """Copy Hub tokens from Streamlit secrets into os.environ for the embedded uvicorn child. On Hugging Face Streamlit Spaces, repository secrets are often available as ``st.secrets`` but are not always present in ``os.environ``. ``subprocess.Popen`` only forwards the process environment, so the API would miss ``HF_TOKEN`` / ``HUGGINGFACE_API_KEY`` otherwise. """ try: import streamlit as st except ImportError: return secrets = getattr(st, "secrets", None) if secrets is None: return for key in ("HF_TOKEN", "HUGGINGFACE_API_KEY", "HUGGING_FACE_HUB_TOKEN"): if (os.environ.get(key) or "").strip(): continue try: raw = secrets[key] except Exception: continue if raw is not None and str(raw).strip(): os.environ[key] = str(raw).strip() def _maybe_start_embedded_uvicorn() -> None: """Start uvicorn in-process when running on HF Spaces (or when DOC_AUDI_EMBED_API=1).""" global _uvicorn_proc, _cleanup_registered if not _want_embedded_api(): return _propagate_streamlit_secrets_to_environ() if _port_accepting_connections("127.0.0.1", 8000): return if _uvicorn_proc is not None and _uvicorn_proc.poll() is None: for _ in range(120): if _port_accepting_connections("127.0.0.1", 8000): return time.sleep(0.05) return cmd = [ sys.executable, "-m", "uvicorn", "api.main:app", "--host", "127.0.0.1", "--port", "8000", ] _uvicorn_proc = subprocess.Popen(cmd) proc = _uvicorn_proc if not _cleanup_registered: def _cleanup(p: subprocess.Popen[bytes] = proc) -> None: if p.poll() is None: p.terminate() try: p.wait(timeout=10) except subprocess.TimeoutExpired: p.kill() atexit.register(_cleanup) _cleanup_registered = True for _ in range(120): if _port_accepting_connections("127.0.0.1", 8000): return time.sleep(0.05) _maybe_start_embedded_uvicorn() from streamlit_app import main # noqa: E402 — start API before importing Streamlit stack main()