refactor: lifespan al posto di @app.on_event (deprecato) + bump v2.5.0

- startup (collection+indici) e cleanup task nel context manager lifespan
- shutdown: cancel del cleanup task
- verificato sul server: startup OK, scrittura OK (istanza di test)
This commit is contained in:
Matteo Benedetto
2026-08-16 19:17:19 +02:00
parent 25ccb13f5b
commit ec0fdb972a
+32 -28
View File
@@ -26,6 +26,7 @@ import os
import time import time
import uuid import uuid
from collections import Counter from collections import Counter
from contextlib import asynccontextmanager
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Any, Literal, Optional from typing import Any, Literal, Optional
@@ -53,7 +54,37 @@ MAX_TEXT_LEN = int(os.environ.get("MAX_TEXT_LEN", "8000"))
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("memory-gateway") log = logging.getLogger("memory-gateway")
app = FastAPI(title="Memory Gateway", version="2.4.0") @asynccontextmanager
async def lifespan(_app: FastAPI):
"""Startup/shutdown: crea collection e indici, avvia il cleanup periodico."""
collections = qdrant.get_collections().collections
if not any(c.name == COLLECTION for c in collections):
qdrant.create_collection(
collection_name=COLLECTION,
vectors_config=qm.VectorParams(size=EMBED_DIM, distance=qm.Distance.COSINE),
)
for field in ("agent_id", "project_id", "scope", "kind", "supersedes_id", "superseded_by"):
qdrant.create_payload_index(
collection_name=COLLECTION,
field_name=field,
field_schema=qm.PayloadSchemaType.KEYWORD,
)
log.info("collection %s creata con indici", COLLECTION)
else:
log.info("collection %s già esistente", COLLECTION)
cleanup_task = asyncio.create_task(_cleanup_loop())
try:
yield
finally:
cleanup_task.cancel()
try:
await cleanup_task
except asyncio.CancelledError:
pass
app = FastAPI(title="Memory Gateway", version="2.5.0", lifespan=lifespan)
qdrant = QdrantClient(url=QDRANT_URL, api_key=QDRANT_API_KEY) qdrant = QdrantClient(url=QDRANT_URL, api_key=QDRANT_API_KEY)
# Rate limit in-memory: {key: [timestamps]} # Rate limit in-memory: {key: [timestamps]}
@@ -165,28 +196,6 @@ async def embed(text: str) -> list[float]:
return r.json()["embeddings"][0] return r.json()["embeddings"][0]
# ---------------------------------------------------------------------------
# Startup: crea collection e indici se non esistono
# ---------------------------------------------------------------------------
@app.on_event("startup")
def startup() -> None:
collections = qdrant.get_collections().collections
if not any(c.name == COLLECTION for c in collections):
qdrant.create_collection(
collection_name=COLLECTION,
vectors_config=qm.VectorParams(size=EMBED_DIM, distance=qm.Distance.COSINE),
)
for field in ("agent_id", "project_id", "scope", "kind", "supersedes_id", "superseded_by"):
qdrant.create_payload_index(
collection_name=COLLECTION,
field_name=field,
field_schema=qm.PayloadSchemaType.KEYWORD,
)
log.info("collection %s creata con indici", COLLECTION)
else:
log.info("collection %s già esistente", COLLECTION)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Endpoints # Endpoints
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -425,10 +434,5 @@ async def _cleanup_loop() -> None:
await asyncio.sleep(3600) await asyncio.sleep(3600)
@app.on_event("startup")
async def start_cleanup() -> None:
asyncio.create_task(_cleanup_loop())
if __name__ == "__main__": if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8080) uvicorn.run(app, host="0.0.0.0", port=8080)