Spaces:
Running
Running
Commit ·
471d73e
1
Parent(s): 9ef7f14
docs: humanize codebase, add architecture guide and clean docstrings
Browse files- ARCHITECTURE.md +188 -0
- README.md +38 -33
- main.py +13 -0
- prefetch.py +6 -0
ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Architecture Guide
|
| 2 |
+
|
| 3 |
+
This document describes the design, execution lifecycle, and operational contracts of `hf-backend`.
|
| 4 |
+
|
| 5 |
+
## System Overview
|
| 6 |
+
|
| 7 |
+
`hf-backend` is an aggregation gateway deployed on a Hugging Face Docker Space. Rather than hosting business logic directly, it mounts domain-specific routers from separate repositories under dedicated URL prefixes.
|
| 8 |
+
|
| 9 |
+
```
|
| 10 |
+
Internet Request
|
| 11 |
+
│
|
| 12 |
+
▼
|
| 13 |
+
[ Hugging Face Edge Proxy ] (:443)
|
| 14 |
+
│
|
| 15 |
+
▼
|
| 16 |
+
[ Uvicorn Worker ] (:7860)
|
| 17 |
+
│
|
| 18 |
+
▼
|
| 19 |
+
[ RequestLimitMiddleware ] (Body size cap + request timeout)
|
| 20 |
+
│
|
| 21 |
+
├── /health ─────────► [ Host Health Monitor ]
|
| 22 |
+
├── /semantic/* ─────► [ Mounted Subproject: Semantic-Comment-Analyze ]
|
| 23 |
+
└── /<prefix>/* ─────► [ Future Mounted Services ]
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
The gateway handles shared infrastructure requirements:
|
| 27 |
+
- Dynamic discovery and mounting of standalone service packages.
|
| 28 |
+
- Early input validation (payload length enforcement before buffering).
|
| 29 |
+
- Global request timeouts preventing worker stalls.
|
| 30 |
+
- Zero-cold-start initialization through baked image weights and startup warmups.
|
| 31 |
+
- Centralized health checks reflecting mount status and model readiness.
|
| 32 |
+
|
| 33 |
+
---
|
| 34 |
+
|
| 35 |
+
## The Cold-Start Problem and Solution
|
| 36 |
+
|
| 37 |
+
Machine learning services on serverless or restart-prone container hosts typically suffer from two cold-start stages:
|
| 38 |
+
|
| 39 |
+
1. **Storage delay**: Weights must be pulled from Hugging Face Hub over the network at first boot, taking anywhere from 30 seconds to several minutes.
|
| 40 |
+
2. **Compute delay**: Models must be deserialized, moved to memory, and compiled or JIT-traced on the first inference call.
|
| 41 |
+
|
| 42 |
+
`hf-backend` eliminates both delays across container restarts.
|
| 43 |
+
|
| 44 |
+
```
|
| 45 |
+
┌─────────────────────────────────────────────────────────────┐
|
| 46 |
+
│ 1. BUILD TIME (Docker Build via BuildKit) │
|
| 47 |
+
│ │
|
| 48 |
+
│ pip install requirements.txt │
|
| 49 |
+
│ │ │
|
| 50 |
+
│ COPY registry.py prefetch.py │
|
| 51 |
+
│ │ │
|
| 52 |
+
│ RUN python prefetch.py │
|
| 53 |
+
│ └─► Imports mounted packages │
|
| 54 |
+
│ └─► Calls warmup() │
|
| 55 |
+
│ └─► Models download into /home/user/hf-cache │
|
| 56 |
+
│ │ │
|
| 57 |
+
│ ENV HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 │
|
| 58 |
+
│ │ │
|
| 59 |
+
│ COPY . . (App code changes never bust prefetch cache) │
|
| 60 |
+
└─────────────────────────────────────────────────────────────┘
|
| 61 |
+
│
|
| 62 |
+
▼
|
| 63 |
+
┌─────────────────────────────────────────────────────────────┐
|
| 64 |
+
│ 2. CONTAINER STARTUP (Lifespan Hook) │
|
| 65 |
+
│ │
|
| 66 |
+
│ Uvicorn starts process [1] │
|
| 67 |
+
│ │ │
|
| 68 |
+
│ load_routers() mounts endpoints from registry.py │
|
| 69 |
+
│ │ │
|
| 70 |
+
│ Lifespan executes: │
|
| 71 |
+
│ ├─► Iterates over loaded projects │
|
| 72 |
+
│ ├─► Dispatches warmup() in a background thread │
|
| 73 |
+
│ └─► Applies WARMUP_TIMEOUT_SECONDS per project │
|
| 74 |
+
│ │ │
|
| 75 |
+
│ All warmups finish / weights load into memory │
|
| 76 |
+
│ │ │
|
| 77 |
+
│ Server opens socket :7860 and accepts incoming traffic │
|
| 78 |
+
└─────────────────────────────────────────────────────────────┘
|
| 79 |
+
│
|
| 80 |
+
▼
|
| 81 |
+
┌─────────────────────────────────────────────────────────────┐
|
| 82 |
+
│ 3. SERVING RUNTIME │
|
| 83 |
+
│ │
|
| 84 |
+
│ First incoming inference request (e.g. /semantic/analyze) │
|
| 85 |
+
│ └─�� Models already resident in memory │
|
| 86 |
+
│ └─► Response latency < 100 ms │
|
| 87 |
+
└─────────────────────────────────────────────────────────────┘
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
### Layer Caching Details
|
| 91 |
+
|
| 92 |
+
The build uses a two-phase copy strategy:
|
| 93 |
+
1. `requirements.txt` is installed first.
|
| 94 |
+
2. `registry.py` and `prefetch.py` are copied, then `prefetch.py` executes. Model weights are stored in `/home/user/hf-cache`.
|
| 95 |
+
3. `HF_HUB_OFFLINE=1` and `TRANSFORMERS_OFFLINE=1` are exported to lock down the filesystem at runtime.
|
| 96 |
+
4. Application code (`main.py`, tests, documentation) is copied last.
|
| 97 |
+
|
| 98 |
+
When you modify routing logic or update documentation, Docker reuses the cached model layers. The Space rebuilds and restarts in seconds rather than downloading multi-gigabyte checkpoints repeatedly.
|
| 99 |
+
|
| 100 |
+
---
|
| 101 |
+
|
| 102 |
+
## Service Contracts
|
| 103 |
+
|
| 104 |
+
Every service mounted into `hf-backend` follows a standard contract.
|
| 105 |
+
|
| 106 |
+
### 1. Package Structure
|
| 107 |
+
A subproject must be installable as a Python package (e.g., via git tag in `requirements.txt`) and expose a FastAPI `APIRouter` instance:
|
| 108 |
+
|
| 109 |
+
```python
|
| 110 |
+
# target: "semantic_backend.router:router"
|
| 111 |
+
from fastapi import APIRouter
|
| 112 |
+
|
| 113 |
+
router = APIRouter()
|
| 114 |
+
|
| 115 |
+
@router.get("/health")
|
| 116 |
+
def project_health():
|
| 117 |
+
...
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
### 2. Startup Warmup
|
| 121 |
+
If the top-level package defines a callable named `warmup()`, the host lifespan executes it at boot time:
|
| 122 |
+
|
| 123 |
+
```python
|
| 124 |
+
# semantic_backend/__init__.py
|
| 125 |
+
def warmup():
|
| 126 |
+
from .nlp_engine import warmup as _warmup
|
| 127 |
+
return _warmup()
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
Warmup conventions:
|
| 131 |
+
- Must load lazy checkpoints (embeddings, classification heads, tokenizers).
|
| 132 |
+
- Runs one dummy inference pass through the primary execution path.
|
| 133 |
+
- Runs synchronously; the host wraps execution in `asyncio.to_thread`.
|
| 134 |
+
- If a project does not define `warmup()`, the host records `"no warmup"` and continues without error.
|
| 135 |
+
|
| 136 |
+
---
|
| 137 |
+
|
| 138 |
+
## Guard Middleware
|
| 139 |
+
|
| 140 |
+
The host places `RequestLimitMiddleware` in front of all endpoints:
|
| 141 |
+
|
| 142 |
+
1. **Streaming Body Guard**:
|
| 143 |
+
Inspects the `Content-Length` header upfront. If the declared size exceeds `MAX_BODY_SIZE_BYTES` (default 10 MB), the connection returns HTTP 413 immediately. For chunked or undeclared streams, chunks are counted on arrival, raising HTTP 413 before memory exhaustion occurs.
|
| 144 |
+
|
| 145 |
+
2. **Request Timeout**:
|
| 146 |
+
Wraps downstream route evaluation with `asyncio.wait_for`. If route processing exceeds `REQUEST_TIMEOUT_SECONDS` (default 60.0s), the gateway returns an HTTP 504 Gateway Timeout.
|
| 147 |
+
|
| 148 |
+
---
|
| 149 |
+
|
| 150 |
+
## Health and Degradation Model
|
| 151 |
+
|
| 152 |
+
The root `/health` and `/` endpoints report the aggregate status of all registered projects:
|
| 153 |
+
|
| 154 |
+
```json
|
| 155 |
+
{
|
| 156 |
+
"status": "healthy",
|
| 157 |
+
"loaded": {
|
| 158 |
+
"/semantic": "semantic_backend.router:router"
|
| 159 |
+
},
|
| 160 |
+
"failed": {},
|
| 161 |
+
"warm_status": {
|
| 162 |
+
"/semantic": "warmed"
|
| 163 |
+
},
|
| 164 |
+
"total_warmup_seconds": 6.484
|
| 165 |
+
}
|
| 166 |
+
```
|
| 167 |
+
|
| 168 |
+
### Evaluation Logic
|
| 169 |
+
|
| 170 |
+
```
|
| 171 |
+
┌──────────────────────────────────────────────┐
|
| 172 |
+
│ Any projects failed to mount? │
|
| 173 |
+
│ OR │───► "degraded"
|
| 174 |
+
│ Any project warmup failed / timed out? │
|
| 175 |
+
└──────────────────────────────────────────────┘
|
| 176 |
+
│ No
|
| 177 |
+
▼
|
| 178 |
+
┌──────────────────────────────────────────────┐
|
| 179 |
+
│ Are there any loaded projects? │───► No: "unhealthy"
|
| 180 |
+
└──────────────────────────────────────────────┘
|
| 181 |
+
│ Yes
|
| 182 |
+
▼
|
| 183 |
+
"healthy"
|
| 184 |
+
```
|
| 185 |
+
|
| 186 |
+
- A project without a `warmup()` reports `"no warmup"` and remains `"healthy"`.
|
| 187 |
+
- If a warmup throws an exception or exceeds `WARMUP_TIMEOUT_SECONDS`, the host logs the error, marks the project as `"failed: <reason>"`, and marks overall status as `"degraded"`.
|
| 188 |
+
- Surviving projects continue serving traffic even if an adjacent service is degraded.
|
README.md
CHANGED
|
@@ -9,64 +9,65 @@ app_port: 7860
|
|
| 9 |
|
| 10 |
# HF Backend
|
| 11 |
|
| 12 |
-
FastAPI
|
| 13 |
|
| 14 |
-
|
| 15 |
|
| 16 |
-
|
| 17 |
|
| 18 |
-
The host
|
|
|
|
|
|
|
| 19 |
|
| 20 |
```
|
| 21 |
-
/project-1/* -> HEARTFLOW_OS
|
| 22 |
-
/
|
| 23 |
-
/health -> Host health status
|
| 24 |
```
|
| 25 |
|
| 26 |
-
## Adding a
|
| 27 |
|
| 28 |
Adding a new service takes two steps:
|
| 29 |
|
| 30 |
-
1.
|
| 31 |
```python
|
| 32 |
PROJECT_REGISTRY = {
|
| 33 |
-
"/
|
| 34 |
-
"/
|
| 35 |
-
"/project-3": "new_project.router:router",
|
| 36 |
}
|
| 37 |
```
|
| 38 |
|
| 39 |
-
2.
|
| 40 |
```text
|
| 41 |
-
git+https://github.com/<
|
| 42 |
```
|
| 43 |
|
| 44 |
-
Pushing to `main`
|
| 45 |
|
| 46 |
## Configuration
|
| 47 |
|
| 48 |
-
Set these in the Space settings under **Variables and secrets**:
|
| 49 |
|
| 50 |
-
| Variable | Required | Default |
|
| 51 |
| :--- | :--- | :--- | :--- |
|
| 52 |
| `CORS_ALLOWED_ORIGINS` | No | None | Comma-separated allowed origins. Wildcards (`*`) are ignored. |
|
| 53 |
-
| `DISABLE_PROJECTS` | No | None | Comma-separated prefixes to skip loading (e.g. `
|
| 54 |
-
| `MAX_BODY_SIZE_BYTES` | No | 10485760 (10 MB) |
|
| 55 |
-
| `REQUEST_TIMEOUT_SECONDS` | No | 60.0 | Request timeout before returning 504. |
|
| 56 |
-
| `WARMUP_TIMEOUT_SECONDS` | No | 300.0 | Warmup timeout per project
|
| 57 |
| `GITHUB_TOKEN` | Only for private repos | None | GitHub token passed as a build secret to clone private dependencies. |
|
| 58 |
|
| 59 |
-
|
| 60 |
|
| 61 |
-
| Secret |
|
| 62 |
| :--- | :--- |
|
| 63 |
-
| `HF_BACKEND_SPACE` | Hugging Face
|
| 64 |
-
| `HF_SPACE_URL` | Optional
|
| 65 |
|
| 66 |
## Endpoints
|
| 67 |
|
| 68 |
### `GET /health`
|
| 69 |
-
Returns the status of the host and each registered project
|
| 70 |
|
| 71 |
```json
|
| 72 |
{
|
|
@@ -78,17 +79,21 @@ Returns the status of the host and each registered project.
|
|
| 78 |
"warm_status": {
|
| 79 |
"/semantic": "warmed"
|
| 80 |
},
|
| 81 |
-
"total_warmup_seconds": 6.
|
| 82 |
}
|
| 83 |
```
|
| 84 |
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
-
Interactive documentation (`/docs`, `/redoc`, `/openapi.json`) is disabled.
|
| 88 |
|
| 89 |
-
## Release
|
| 90 |
|
| 91 |
-
1. Test changes in the
|
| 92 |
2. Tag the release (`git tag v1.0`, `git push origin v1.0`).
|
| 93 |
-
3. Update the tag in
|
| 94 |
-
4.
|
|
|
|
|
|
| 9 |
|
| 10 |
# HF Backend
|
| 11 |
|
| 12 |
+
FastAPI gateway running on a Hugging Face Docker Space. The gateway mounts backend routers from standalone project repositories under designated URL prefixes.
|
| 13 |
|
| 14 |
+
For in-depth lifecycle mechanics, prefetch caching, and degradation semantics, see [ARCHITECTURE.md](file:///c:/Users/manga/Projects/github/rachit-mangawa/hf-backend/ARCHITECTURE.md).
|
| 15 |
|
| 16 |
+
## How It Works
|
| 17 |
|
| 18 |
+
The host application does not contain domain business logic. Each subproject is maintained in its own repository, exposes a router in `<package>/router.py`, and installs as a standard Python package.
|
| 19 |
+
|
| 20 |
+
The host imports each router from `registry.py` and mounts it under its configured prefix. If a router fails to import, the host logs the error, records the failure, and keeps running for the remaining services.
|
| 21 |
|
| 22 |
```
|
| 23 |
+
/project-1/* -> HEARTFLOW_OS router
|
| 24 |
+
/semantic/* -> Semantic Comment Analyzer router
|
| 25 |
+
/health -> Host health and readiness status
|
| 26 |
```
|
| 27 |
|
| 28 |
+
## Adding a Service
|
| 29 |
|
| 30 |
Adding a new service takes two steps:
|
| 31 |
|
| 32 |
+
1. Register the URL prefix and target router in `registry.py`:
|
| 33 |
```python
|
| 34 |
PROJECT_REGISTRY = {
|
| 35 |
+
"/semantic": "semantic_backend.router:router",
|
| 36 |
+
"/new-service": "new_service.router:router",
|
|
|
|
| 37 |
}
|
| 38 |
```
|
| 39 |
|
| 40 |
+
2. Pin the Git tag or release in `requirements.in`, then compile `requirements.txt`:
|
| 41 |
```text
|
| 42 |
+
git+https://github.com/<owner>/<repo>.git@v1.0#subdirectory=backend
|
| 43 |
```
|
| 44 |
|
| 45 |
+
Pushing to `main` deploys the changes to Hugging Face and rebuilds the Space container.
|
| 46 |
|
| 47 |
## Configuration
|
| 48 |
|
| 49 |
+
Set these variables in the Space settings under **Variables and secrets**:
|
| 50 |
|
| 51 |
+
| Variable | Required | Default | Description |
|
| 52 |
| :--- | :--- | :--- | :--- |
|
| 53 |
| `CORS_ALLOWED_ORIGINS` | No | None | Comma-separated allowed origins. Wildcards (`*`) are ignored. |
|
| 54 |
+
| `DISABLE_PROJECTS` | No | None | Comma-separated prefixes to skip loading (e.g. `semantic`). |
|
| 55 |
+
| `MAX_BODY_SIZE_BYTES` | No | 10485760 (10 MB) | Maximum request body size before returning HTTP 413. |
|
| 56 |
+
| `REQUEST_TIMEOUT_SECONDS` | No | 60.0 | Request timeout before returning HTTP 504. |
|
| 57 |
+
| `WARMUP_TIMEOUT_SECONDS` | No | 300.0 | Warmup timeout per project during container boot. |
|
| 58 |
| `GITHUB_TOKEN` | Only for private repos | None | GitHub token passed as a build secret to clone private dependencies. |
|
| 59 |
|
| 60 |
+
Repository secrets for GitHub Actions:
|
| 61 |
|
| 62 |
+
| Secret | Description |
|
| 63 |
| :--- | :--- |
|
| 64 |
+
| `HF_BACKEND_SPACE` | Hugging Face token with write permissions to push code and restart the Space. |
|
| 65 |
+
| `HF_SPACE_URL` | Optional health URL for the keep-alive workflow. |
|
| 66 |
|
| 67 |
## Endpoints
|
| 68 |
|
| 69 |
### `GET /health`
|
| 70 |
+
Returns the status of the host and each registered project:
|
| 71 |
|
| 72 |
```json
|
| 73 |
{
|
|
|
|
| 79 |
"warm_status": {
|
| 80 |
"/semantic": "warmed"
|
| 81 |
},
|
| 82 |
+
"total_warmup_seconds": 6.48
|
| 83 |
}
|
| 84 |
```
|
| 85 |
|
| 86 |
+
Status outcomes:
|
| 87 |
+
- `healthy`: All registered projects mounted successfully and no warmup failed.
|
| 88 |
+
- `degraded`: One or more services failed to mount, or a warmup failed or timed out.
|
| 89 |
+
- `unhealthy`: No services loaded successfully.
|
| 90 |
|
| 91 |
+
Interactive documentation (`/docs`, `/redoc`, `/openapi.json`) is disabled for production hardening.
|
| 92 |
|
| 93 |
+
## Release Workflow
|
| 94 |
|
| 95 |
+
1. Test changes in the subproject repository.
|
| 96 |
2. Tag the release (`git tag v1.0`, `git push origin v1.0`).
|
| 97 |
+
3. Update the tag reference in `requirements.in` and recompile `requirements.txt`.
|
| 98 |
+
4. Run `pytest` locally to verify routing and warmups.
|
| 99 |
+
5. Push to `main`. The GitHub Actions workflow syncs the commit to Hugging Face and triggers a restart.
|
main.py
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import asyncio
|
| 2 |
import importlib
|
| 3 |
import logging
|
|
@@ -5,9 +12,11 @@ import os
|
|
| 5 |
import time
|
| 6 |
from contextlib import asynccontextmanager
|
| 7 |
from typing import Callable
|
|
|
|
| 8 |
from fastapi import FastAPI, HTTPException, Request, Response
|
| 9 |
from fastapi.middleware.cors import CORSMiddleware
|
| 10 |
from starlette.middleware.base import BaseHTTPMiddleware
|
|
|
|
| 11 |
from registry import PROJECT_REGISTRY
|
| 12 |
|
| 13 |
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
|
@@ -24,6 +33,8 @@ default_warmup_timeout = float(os.getenv("WARMUP_TIMEOUT_SECONDS", "300.0"))
|
|
| 24 |
|
| 25 |
|
| 26 |
class RequestLimitMiddleware(BaseHTTPMiddleware):
|
|
|
|
|
|
|
| 27 |
def __init__(self, app, max_size: int, timeout: float):
|
| 28 |
super().__init__(app)
|
| 29 |
self.max_size = max_size
|
|
@@ -55,6 +66,7 @@ class RequestLimitMiddleware(BaseHTTPMiddleware):
|
|
| 55 |
|
| 56 |
|
| 57 |
async def run_warmup_for_project(prefix: str, target: str, timeout: float):
|
|
|
|
| 58 |
logger.info(f"[{prefix}] Checking warmup for target '{target}'...")
|
| 59 |
try:
|
| 60 |
module_name = target.split(":")[0]
|
|
@@ -120,6 +132,7 @@ if allowed_origins:
|
|
| 120 |
|
| 121 |
|
| 122 |
def load_routers():
|
|
|
|
| 123 |
loaded_projects.clear()
|
| 124 |
failed_projects.clear()
|
| 125 |
warm_status.clear()
|
|
|
|
| 1 |
+
"""HF Backend Gateway.
|
| 2 |
+
|
| 3 |
+
Aggregates subproject routers mounted on a Hugging Face Space. Handles
|
| 4 |
+
model warmup during lifespan startup, streaming payload size limits,
|
| 5 |
+
and centralized health tracking.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
import asyncio
|
| 9 |
import importlib
|
| 10 |
import logging
|
|
|
|
| 12 |
import time
|
| 13 |
from contextlib import asynccontextmanager
|
| 14 |
from typing import Callable
|
| 15 |
+
|
| 16 |
from fastapi import FastAPI, HTTPException, Request, Response
|
| 17 |
from fastapi.middleware.cors import CORSMiddleware
|
| 18 |
from starlette.middleware.base import BaseHTTPMiddleware
|
| 19 |
+
|
| 20 |
from registry import PROJECT_REGISTRY
|
| 21 |
|
| 22 |
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
|
|
|
| 33 |
|
| 34 |
|
| 35 |
class RequestLimitMiddleware(BaseHTTPMiddleware):
|
| 36 |
+
"""Enforces incoming request payload limits and execution timeouts."""
|
| 37 |
+
|
| 38 |
def __init__(self, app, max_size: int, timeout: float):
|
| 39 |
super().__init__(app)
|
| 40 |
self.max_size = max_size
|
|
|
|
| 66 |
|
| 67 |
|
| 68 |
async def run_warmup_for_project(prefix: str, target: str, timeout: float):
|
| 69 |
+
"""Executes a mounted package's warmup function in a worker thread."""
|
| 70 |
logger.info(f"[{prefix}] Checking warmup for target '{target}'...")
|
| 71 |
try:
|
| 72 |
module_name = target.split(":")[0]
|
|
|
|
| 132 |
|
| 133 |
|
| 134 |
def load_routers():
|
| 135 |
+
"""Mounts routers dynamically from registry.py with isolated failure handling."""
|
| 136 |
loaded_projects.clear()
|
| 137 |
failed_projects.clear()
|
| 138 |
warm_status.clear()
|
prefetch.py
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import importlib
|
| 2 |
import logging
|
| 3 |
import sys
|
|
|
|
| 1 |
+
"""Build-time model prefetch script.
|
| 2 |
+
|
| 3 |
+
Reads project routes from registry.py and invokes warmup() on each
|
| 4 |
+
top-level package to bake weights into the Docker image filesystem.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
import importlib
|
| 8 |
import logging
|
| 9 |
import sys
|