Spaces:
Running
Running
File size: 7,078 Bytes
6c24b50 8fe1f14 6c24b50 08919be 89157f5 6c24b50 2ebf97a 3379d24 c2bb116 6c24b50 d02a73b 6c24b50 3379d24 c2bb116 3379d24 6c24b50 c25809b 6c24b50 c28ae12 6c24b50 08919be c2bb116 4b54fab 3379d24 4b54fab c2bb116 3379d24 2ebf97a 6c24b50 2ebf97a c2bb116 89157f5 6c24b50 f7c1a9c 6c24b50 3379d24 ff6b176 c2bb116 f87115f 6c24b50 d02a73b 8ee0135 d02a73b 4789772 6c24b50 62ec94a 8604693 62ec94a 6c24b50 c2bb116 6c24b50 89157f5 6c24b50 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | from __future__ import annotations
import asyncio
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from app.config import get_settings
from app.core.auth.deps import init_auth_db
from app.core.database import pool_manager
from app.core.logger import get_logger
from app.core.redis_client import create_redis_client, close_redis
from app.core.scripts import load_scripts
from app.services.embeddings_service import EmbeddingService
from app.services.vector_store_service import VectorStoreService
from app.api.v1.router import api_v1_router
from app.api.v1.system import is_maintenance
_logger = get_logger(__name__)
_settings = get_settings()
_embedding_service: EmbeddingService = EmbeddingService()
_vector_store_service: VectorStoreService = VectorStoreService(_embedding_service)
async def _self_ping():
import httpx
health_url = _settings.self_ping_url
while True:
try:
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.get(health_url)
if response.status_code == 200:
_logger.info("Self-ping successful: %s", health_url)
else:
_logger.warning("Self-ping returned: %s - %s", health_url, response.status_code)
except Exception as exc:
_logger.error("Self-ping error: %s", exc)
await asyncio.sleep(900)
@asynccontextmanager
async def lifespan(app: FastAPI):
_logger.info("Initializing authentication database...")
await init_auth_db()
_logger.info("Authentication database initialized")
_logger.info("Initializing vector store database...")
await _vector_store_service.init_db()
_logger.info("Vector store database initialized with %d stores", len(_vector_store_service.list_stores()))
_logger.info("Initializing embedding service (loading 384-dim model)...")
loop = asyncio.get_running_loop()
await loop.run_in_executor(None, _embedding_service.load_model, 384)
_logger.info("Embedding service initialized with dims: %s", _embedding_service.loaded_dimensions)
_logger.info("Vector store service initialized with %d existing stores", len(_vector_store_service.list_stores()))
redis = create_redis_client(_settings.redis_url) if _settings.redis_url else None
scripts = await load_scripts(redis) if redis else {}
app.state.redis = redis
app.state.scripts = scripts
if redis:
_logger.info("Redis and Lua scripts initialized")
else:
_logger.warning("Redis not configured, running in degraded mode")
asyncio.create_task(_self_ping())
yield
_logger.info("Shutting down...")
await close_redis(redis)
await _vector_store_service.close_all()
await pool_manager.close_all()
def create_application() -> FastAPI:
app = FastAPI(
title=_settings.app_name,
description="All API Collection",
version=_settings.app_version,
docs_url="/docs",
redoc_url="/redoc",
openapi_tags=[
{"name": "Convert", "description": "Single-file and single-URL conversion"},
{"name": "Batch", "description": "Bulk conversion of files and URLs"},
{"name": "System", "description": "Health, info, and supported formats"},
{"name": "Embeddings", "description": "Text embedding generation using transformer models"},
{"name": "Verify", "description": "Phone number and identity verification"},
{"name": "Vector Stores", "description": "Create, manage, and search vector stores for RAG"},
{"name": "URL Shortener", "description": "Create and manage short URLs with analytics"},
],
lifespan=lifespan,
)
app.add_middleware(GZipMiddleware, minimum_size=1000)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
@app.middleware("http")
async def maintenance_middleware(request: Request, call_next):
if is_maintenance():
method = request.method
path = request.url.path
if method not in ("GET", "HEAD", "OPTIONS"):
if not path.startswith("/api/v1/maintenance"):
if method == "POST" and path.startswith("/api/v1/backup"):
pass # allow backup/restore during maintenance
else:
from starlette.responses import JSONResponse
return JSONResponse(
status_code=503,
content={
"success": False,
"detail": "Service is under maintenance. No write operations allowed.",
},
)
return await call_next(request)
app.include_router(api_v1_router, prefix="/api/v1")
@app.get("/", include_in_schema=False)
async def root(request: Request):
from collections import defaultdict
routes_by_tag: dict[str, list[dict]] = defaultdict(list)
for route in app.routes:
if not hasattr(route, "methods") or not hasattr(route, "path"):
continue
if route.path in ("/", "/health", "/ping", "/openapi.json", "/docs", "/redoc", "/docs/oauth2-redirect"):
continue
tags = getattr(route, "tags", None) or ["default"]
for tag in tags:
routes_by_tag[tag].append({
"method": list(route.methods - {"HEAD", "OPTIONS"}),
"path": route.path,
"summary": getattr(route, "summary", ""),
})
return {
"name": _settings.app_name,
"version": _settings.app_version,
"docs": {
"swagger": str(request.base_url) + "docs",
"redoc": str(request.base_url) + "redoc",
},
"endpoints": [
{"tag": tag, "routes": sorted(routes, key=lambda r: r["path"])}
for tag, routes in sorted(routes_by_tag.items())
],
}
@app.get("/health", include_in_schema=False)
async def root_health():
store_count = len(_vector_store_service.list_stores())
doc_count = await _vector_store_service.get_total_document_count()
return {
"success": True,
"app_name": _settings.app_name,
"version": _settings.app_version,
"embedding_dimension": _settings.embedding_dimension,
"vector_store_count": store_count,
"total_documents": doc_count,
"model_loaded": _embedding_service.is_loaded(384),
}
@app.get("/ping", include_in_schema=False)
async def ping():
return {"name": f"{_settings.app_name}", "version": _settings.app_version}
return app
app = create_application()
|