S-Dreamer commited on
Commit
6cf4784
·
verified ·
1 Parent(s): ce0a172

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +151 -0
app.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ CodeCraftLab — FastAPI entrypoint.
3
+
4
+ Startup order:
5
+ 1. Logging configured (structlog)
6
+ 2. Settings validated (Pydantic v2)
7
+ 3. Database initialised
8
+ 4. Routers mounted
9
+ 5. Background job scheduler started
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import contextlib
15
+ from collections.abc import AsyncGenerator
16
+
17
+ import structlog
18
+ import uvicorn
19
+ from fastapi import Depends, FastAPI, HTTPException, Request, status
20
+ from fastapi.middleware.cors import CORSMiddleware
21
+ from fastapi.middleware.trustedhost import TrustedHostMiddleware
22
+ from fastapi.responses import JSONResponse
23
+
24
+ from core.logging import configure_logging
25
+ from core.settings import Settings, get_settings
26
+ from routers import auth, datasets, inference, training
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # Logging — configure before anything else imports the logger
30
+ # ---------------------------------------------------------------------------
31
+ configure_logging()
32
+ log = structlog.get_logger(__name__)
33
+
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # Lifespan
37
+ # ---------------------------------------------------------------------------
38
+ @contextlib.asynccontextmanager
39
+ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
40
+ settings: Settings = get_settings()
41
+ log.info(
42
+ "codecraftlab.startup",
43
+ env=settings.env,
44
+ max_concurrent_jobs=settings.max_concurrent_jobs,
45
+ hub_enabled=bool(settings.hf_token),
46
+ )
47
+ yield
48
+ log.info("codecraftlab.shutdown")
49
+
50
+
51
+ # ---------------------------------------------------------------------------
52
+ # App factory
53
+ # ---------------------------------------------------------------------------
54
+ def create_app(settings: Settings | None = None) -> FastAPI:
55
+ cfg = settings or get_settings()
56
+
57
+ app = FastAPI(
58
+ title="CodeCraftLab",
59
+ description=(
60
+ "Production fine-tuning platform for code generation models. "
61
+ "Manage datasets, launch training jobs, evaluate models, and serve inference."
62
+ ),
63
+ version="1.0.0",
64
+ docs_url="/docs" if cfg.env != "production" else None,
65
+ redoc_url="/redoc" if cfg.env != "production" else None,
66
+ lifespan=lifespan,
67
+ )
68
+
69
+ # ------------------------------------------------------------------
70
+ # Middleware
71
+ # ------------------------------------------------------------------
72
+ app.add_middleware(
73
+ CORSMiddleware,
74
+ allow_origins=cfg.cors_origins,
75
+ allow_credentials=True,
76
+ allow_methods=["*"],
77
+ allow_headers=["*"],
78
+ )
79
+ if cfg.env == "production":
80
+ app.add_middleware(TrustedHostMiddleware, allowed_hosts=cfg.allowed_hosts)
81
+
82
+ # ------------------------------------------------------------------
83
+ # Request logging middleware
84
+ # ------------------------------------------------------------------
85
+ @app.middleware("http")
86
+ async def log_requests(request: Request, call_next): # type: ignore[no-untyped-def]
87
+ bound_log = log.bind(
88
+ method=request.method,
89
+ path=request.url.path,
90
+ client=request.client.host if request.client else "unknown",
91
+ )
92
+ bound_log.info("request.received")
93
+ response = await call_next(request)
94
+ bound_log.info("request.completed", status_code=response.status_code)
95
+ return response
96
+
97
+ # ------------------------------------------------------------------
98
+ # Global exception handler
99
+ # ------------------------------------------------------------------
100
+ @app.exception_handler(Exception)
101
+ async def unhandled_exception_handler(request: Request, exc: Exception) -> JSONResponse:
102
+ log.error(
103
+ "unhandled_exception",
104
+ path=request.url.path,
105
+ error=str(exc),
106
+ exc_info=True,
107
+ )
108
+ return JSONResponse(
109
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
110
+ content={"detail": "Internal server error"},
111
+ )
112
+
113
+ # ------------------------------------------------------------------
114
+ # Routers
115
+ # ------------------------------------------------------------------
116
+ app.include_router(auth.router, prefix="/auth", tags=["Authentication"])
117
+ app.include_router(datasets.router, prefix="/datasets", tags=["Datasets"])
118
+ app.include_router(training.router, prefix="/training", tags=["Training"])
119
+ app.include_router(inference.router, prefix="/inference", tags=["Inference"])
120
+
121
+ # ------------------------------------------------------------------
122
+ # Health endpoints
123
+ # ------------------------------------------------------------------
124
+ @app.get("/health", tags=["System"])
125
+ async def health() -> dict[str, str]:
126
+ return {"status": "ok"}
127
+
128
+ @app.get("/health/ready", tags=["System"])
129
+ async def readiness(settings: Settings = Depends(get_settings)) -> dict[str, object]:
130
+ return {
131
+ "status": "ready",
132
+ "env": settings.env,
133
+ "hub_connected": bool(settings.hf_token),
134
+ }
135
+
136
+ return app
137
+
138
+
139
+ app = create_app()
140
+
141
+
142
+ if __name__ == "__main__":
143
+ settings = get_settings()
144
+ uvicorn.run(
145
+ "app:app",
146
+ host="0.0.0.0",
147
+ port=8000,
148
+ reload=settings.env == "development",
149
+ log_config=None, # structlog handles logging
150
+ workers=1 if settings.env == "development" else 4,
151
+ )