First commit
Browse files- Dockerfile +10 -11
- app/main.py +19 -23
Dockerfile
CHANGED
|
@@ -4,8 +4,7 @@ FROM python:3.11-slim
|
|
| 4 |
# --- base env ---
|
| 5 |
ENV PYTHONDONTWRITEBYTECODE=1 \
|
| 6 |
PYTHONUNBUFFERED=1 \
|
| 7 |
-
PIP_NO_CACHE_DIR=1
|
| 8 |
-
UVICORN_WORKERS=2
|
| 9 |
|
| 10 |
# --- system deps ---
|
| 11 |
RUN apt-get update \
|
|
@@ -15,21 +14,21 @@ RUN apt-get update \
|
|
| 15 |
# --- app dir ---
|
| 16 |
WORKDIR /app
|
| 17 |
|
| 18 |
-
# --- python deps
|
| 19 |
-
COPY requirements.txt ./
|
| 20 |
RUN pip install --upgrade pip && pip install -r requirements.txt
|
| 21 |
|
| 22 |
# --- copy app ---
|
| 23 |
COPY . .
|
| 24 |
|
| 25 |
-
#
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
EXPOSE ${PORT}
|
| 29 |
|
| 30 |
-
# Optional: run as non-root
|
| 31 |
# RUN useradd -ms /bin/bash appuser && chown -R appuser:appuser /app
|
| 32 |
# USER appuser
|
| 33 |
|
| 34 |
-
# --- start ---
|
| 35 |
-
|
|
|
|
|
|
| 4 |
# --- base env ---
|
| 5 |
ENV PYTHONDONTWRITEBYTECODE=1 \
|
| 6 |
PYTHONUNBUFFERED=1 \
|
| 7 |
+
PIP_NO_CACHE_DIR=1
|
|
|
|
| 8 |
|
| 9 |
# --- system deps ---
|
| 10 |
RUN apt-get update \
|
|
|
|
| 14 |
# --- app dir ---
|
| 15 |
WORKDIR /app
|
| 16 |
|
| 17 |
+
# --- python deps (cache friendly layer) ---
|
| 18 |
+
COPY requirements.txt ./
|
| 19 |
RUN pip install --upgrade pip && pip install -r requirements.txt
|
| 20 |
|
| 21 |
# --- copy app ---
|
| 22 |
COPY . .
|
| 23 |
|
| 24 |
+
# Hugging Face sets $PORT at runtime; keep a sane default for local runs
|
| 25 |
+
ENV PORT=7860
|
| 26 |
+
EXPOSE 7860
|
|
|
|
| 27 |
|
| 28 |
+
# Optional: run as non-root
|
| 29 |
# RUN useradd -ms /bin/bash appuser && chown -R appuser:appuser /app
|
| 30 |
# USER appuser
|
| 31 |
|
| 32 |
+
# --- start (shell form so $PORT expands) ---
|
| 33 |
+
# --proxy-headers is helpful behind HF’s proxy
|
| 34 |
+
CMD uvicorn app.main:app --host 0.0.0.0 --port $PORT --proxy-headers
|
app/main.py
CHANGED
|
@@ -7,12 +7,22 @@ from contextlib import asynccontextmanager
|
|
| 7 |
from typing import Any, Dict
|
| 8 |
|
| 9 |
from fastapi import FastAPI
|
| 10 |
-
from fastapi.responses import
|
| 11 |
|
| 12 |
-
#
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
from .routers import health, plan, chat
|
| 17 |
|
| 18 |
# Optional UI (Home/Chat/Dev). If missing, we gracefully fall back to a JSON root.
|
|
@@ -22,7 +32,6 @@ try:
|
|
| 22 |
except Exception: # pragma: no cover
|
| 23 |
HAS_UI = False
|
| 24 |
|
| 25 |
-
|
| 26 |
TAGS_METADATA = [
|
| 27 |
{"name": "Health", "description": "Liveness / readiness probes and basic service metadata."},
|
| 28 |
{"name": "Planning", "description": "AI plan generation for Matrix Guardian (/v1/plan)."},
|
|
@@ -30,17 +39,12 @@ TAGS_METADATA = [
|
|
| 30 |
{"name": "UI", "description": "Minimal web UI (Home, Chat, Dev) if enabled."},
|
| 31 |
]
|
| 32 |
|
| 33 |
-
|
| 34 |
@asynccontextmanager
|
| 35 |
async def lifespan(app: FastAPI):
|
| 36 |
-
"""
|
| 37 |
-
Lightweight startup/shutdown hooks.
|
| 38 |
-
Stores process start time for basic diagnostics and logs boot/shutdown.
|
| 39 |
-
"""
|
| 40 |
app.state.started_at = time.time()
|
| 41 |
app.state.version = os.getenv("APP_VERSION", "1.0.0")
|
| 42 |
logging.getLogger("uvicorn.error").info(
|
| 43 |
-
"matrix-ai starting (version=%s)", app.state.version
|
| 44 |
)
|
| 45 |
try:
|
| 46 |
yield
|
|
@@ -50,9 +54,7 @@ async def lifespan(app: FastAPI):
|
|
| 50 |
"matrix-ai shutting down (uptime=%.2fs)", uptime
|
| 51 |
)
|
| 52 |
|
| 53 |
-
|
| 54 |
def create_app() -> FastAPI:
|
| 55 |
-
"""Create and configure the FastAPI application instance."""
|
| 56 |
app = FastAPI(
|
| 57 |
title="matrix-ai",
|
| 58 |
version=os.getenv("APP_VERSION", "1.0.0"),
|
|
@@ -63,7 +65,7 @@ def create_app() -> FastAPI:
|
|
| 63 |
lifespan=lifespan,
|
| 64 |
)
|
| 65 |
|
| 66 |
-
# Middlewares (request-id, gzip, rate-limit,
|
| 67 |
attach_middlewares(app)
|
| 68 |
|
| 69 |
# Core routers
|
|
@@ -75,7 +77,7 @@ def create_app() -> FastAPI:
|
|
| 75 |
if HAS_UI:
|
| 76 |
app.include_router(ui_router, tags=["UI"])
|
| 77 |
else:
|
| 78 |
-
# Minimal root so HF
|
| 79 |
@app.get("/", include_in_schema=False)
|
| 80 |
async def root() -> Dict[str, Any]:
|
| 81 |
return {
|
|
@@ -83,19 +85,13 @@ def create_app() -> FastAPI:
|
|
| 83 |
"service": "matrix-ai",
|
| 84 |
"version": app.version,
|
| 85 |
"docs": "/docs",
|
| 86 |
-
"endpoints": {
|
| 87 |
-
"plan": "/v1/plan",
|
| 88 |
-
"chat": "/v1/chat",
|
| 89 |
-
"healthz": "/healthz",
|
| 90 |
-
},
|
| 91 |
}
|
| 92 |
|
| 93 |
-
# Optional convenience redirect to API docs
|
| 94 |
@app.get("/home", include_in_schema=False)
|
| 95 |
async def home_redirect():
|
| 96 |
return RedirectResponse(url="/docs", status_code=302)
|
| 97 |
|
| 98 |
return app
|
| 99 |
|
| 100 |
-
|
| 101 |
app = create_app()
|
|
|
|
| 7 |
from typing import Any, Dict
|
| 8 |
|
| 9 |
from fastapi import FastAPI
|
| 10 |
+
from fastapi.responses import RedirectResponse
|
| 11 |
|
| 12 |
+
# --- Middlewares ---
|
| 13 |
+
# Prefer the canonical package name; if your repo uses "middlewares/", this tries both.
|
| 14 |
+
try:
|
| 15 |
+
from .middleware import attach_middlewares # singular
|
| 16 |
+
except Exception:
|
| 17 |
+
try:
|
| 18 |
+
from .middlewares import attach_middlewares # plural
|
| 19 |
+
except Exception:
|
| 20 |
+
def attach_middlewares(app: FastAPI) -> None: # no-op fallback
|
| 21 |
+
logging.getLogger("uvicorn.error").warning(
|
| 22 |
+
"attach_middlewares not found; continuing without custom middlewares."
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
# --- Routers ---
|
| 26 |
from .routers import health, plan, chat
|
| 27 |
|
| 28 |
# Optional UI (Home/Chat/Dev). If missing, we gracefully fall back to a JSON root.
|
|
|
|
| 32 |
except Exception: # pragma: no cover
|
| 33 |
HAS_UI = False
|
| 34 |
|
|
|
|
| 35 |
TAGS_METADATA = [
|
| 36 |
{"name": "Health", "description": "Liveness / readiness probes and basic service metadata."},
|
| 37 |
{"name": "Planning", "description": "AI plan generation for Matrix Guardian (/v1/plan)."},
|
|
|
|
| 39 |
{"name": "UI", "description": "Minimal web UI (Home, Chat, Dev) if enabled."},
|
| 40 |
]
|
| 41 |
|
|
|
|
| 42 |
@asynccontextmanager
|
| 43 |
async def lifespan(app: FastAPI):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
app.state.started_at = time.time()
|
| 45 |
app.state.version = os.getenv("APP_VERSION", "1.0.0")
|
| 46 |
logging.getLogger("uvicorn.error").info(
|
| 47 |
+
"matrix-ai starting (version=%s, port=%s)", app.state.version, os.getenv("PORT", "7860")
|
| 48 |
)
|
| 49 |
try:
|
| 50 |
yield
|
|
|
|
| 54 |
"matrix-ai shutting down (uptime=%.2fs)", uptime
|
| 55 |
)
|
| 56 |
|
|
|
|
| 57 |
def create_app() -> FastAPI:
|
|
|
|
| 58 |
app = FastAPI(
|
| 59 |
title="matrix-ai",
|
| 60 |
version=os.getenv("APP_VERSION", "1.0.0"),
|
|
|
|
| 65 |
lifespan=lifespan,
|
| 66 |
)
|
| 67 |
|
| 68 |
+
# Middlewares (request-id, gzip, rate-limit, etc.)
|
| 69 |
attach_middlewares(app)
|
| 70 |
|
| 71 |
# Core routers
|
|
|
|
| 77 |
if HAS_UI:
|
| 78 |
app.include_router(ui_router, tags=["UI"])
|
| 79 |
else:
|
| 80 |
+
# Minimal root so HF root probes pass even without UI
|
| 81 |
@app.get("/", include_in_schema=False)
|
| 82 |
async def root() -> Dict[str, Any]:
|
| 83 |
return {
|
|
|
|
| 85 |
"service": "matrix-ai",
|
| 86 |
"version": app.version,
|
| 87 |
"docs": "/docs",
|
| 88 |
+
"endpoints": {"plan": "/v1/plan", "chat": "/v1/chat", "healthz": "/healthz"},
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
}
|
| 90 |
|
|
|
|
| 91 |
@app.get("/home", include_in_schema=False)
|
| 92 |
async def home_redirect():
|
| 93 |
return RedirectResponse(url="/docs", status_code=302)
|
| 94 |
|
| 95 |
return app
|
| 96 |
|
|
|
|
| 97 |
app = create_app()
|