From 9ea7601aaca6373068f53a1f8d00353adbb93427 Mon Sep 17 00:00:00 2001 From: listless <124798751+listlessbird@users.noreply.github.com> Date: Mon, 23 Feb 2026 20:28:49 +0530 Subject: [PATCH 1/2] Bump FastAPI baseline and add explicit response models --- backend-api/pyproject.toml | 2 +- backend-api/src/api/models/__init__.py | 4 ++++ backend-api/src/api/models/health.py | 21 +++++++++++++++++++ backend-api/src/api/routers/health.py | 11 +++++----- backend-api/src/api/routers/jobs.py | 29 +++++++++++++------------- 5 files changed, 47 insertions(+), 20 deletions(-) create mode 100644 backend-api/src/api/models/health.py diff --git a/backend-api/pyproject.toml b/backend-api/pyproject.toml index 4051130..fa2a8b2 100644 --- a/backend-api/pyproject.toml +++ b/backend-api/pyproject.toml @@ -18,7 +18,7 @@ dependencies = [ "numpy>=2.0", "bitsandbytes>=0.48.2", "torchvision>=0.24.0", - "fastapi[standard]>=0.122.0", + "fastapi[standard]>=0.131.0", "uvicorn[standard]>=0.38.0", "httpx>=0.28.1", "pyvips>=2.2.3", diff --git a/backend-api/src/api/models/__init__.py b/backend-api/src/api/models/__init__.py index 8f1a652..617846a 100644 --- a/backend-api/src/api/models/__init__.py +++ b/backend-api/src/api/models/__init__.py @@ -1,3 +1,4 @@ +from api.models.health import HealthResponse, IndexVersionResponse, IndexVersionsResponse from api.models.images import ( ImageIngestRequest, ImageIngestResponse, @@ -14,6 +15,9 @@ "ImageListResponse", "ImageResponse", "ImageStatus", + "HealthResponse", + "IndexVersionResponse", + "IndexVersionsResponse", "JobListResponse", "JobResponse", "RebuildIndexRequest", diff --git a/backend-api/src/api/models/health.py b/backend-api/src/api/models/health.py new file mode 100644 index 0000000..e3f2d14 --- /dev/null +++ b/backend-api/src/api/models/health.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from pydantic import BaseModel, Field + + +class HealthResponse(BaseModel): + status: str = Field(description="Service health state") + + +class IndexVersionResponse(BaseModel): + version: int + embed_model: str + index_type: str + num_vectors: int + dimension: int + is_active: bool + created_at: str | None = None + + +class IndexVersionsResponse(BaseModel): + versions: list[IndexVersionResponse] diff --git a/backend-api/src/api/routers/health.py b/backend-api/src/api/routers/health.py index e4688e8..8f3d473 100644 --- a/backend-api/src/api/routers/health.py +++ b/backend-api/src/api/routers/health.py @@ -1,12 +1,12 @@ from __future__ import annotations import structlog -from fastapi import APIRouter, status -from fastapi.responses import JSONResponse +from fastapi import APIRouter, Response, status from sqlalchemy import text from temporalio.client import Client from temporalio.contrib.pydantic import pydantic_data_converter +from api.models.health import HealthResponse from shared.config import settings from shared.db import get_engine from shared.services.storage import get_storage_service @@ -49,8 +49,8 @@ async def _check_temporal() -> bool: return False -@router.get("/live") -async def healthcheck() -> JSONResponse: +@router.get("/live", response_model=HealthResponse) +async def healthcheck(response: Response) -> HealthResponse: pg_ok = _check_postgres() s3_ok = _check_s3() temporal_ok = await _check_temporal() @@ -67,4 +67,5 @@ async def healthcheck() -> JSONResponse: status_code = status.HTTP_200_OK if healthy else status.HTTP_503_SERVICE_UNAVAILABLE - return JSONResponse(status_code=status_code, content={"status": "ok" if healthy else "degraded"}) + response.status_code = status_code + return HealthResponse(status="ok" if healthy else "degraded") diff --git a/backend-api/src/api/routers/jobs.py b/backend-api/src/api/routers/jobs.py index 816cd2e..aa59fba 100644 --- a/backend-api/src/api/routers/jobs.py +++ b/backend-api/src/api/routers/jobs.py @@ -7,6 +7,7 @@ from api.auth import AdminRequired from api.deps import DbSession, TemporalClientDep +from api.models.health import IndexVersionResponse, IndexVersionsResponse from api.models.jobs import JobListResponse, JobResponse, RebuildIndexRequest from shared.config import settings from shared.models import IndexBuild, Job, JobStatus, JobType @@ -136,25 +137,25 @@ async def list_jobs( ) -@router.get("/indexes/versions") +@router.get("/indexes/versions", response_model=IndexVersionsResponse) async def list_index_versions( _auth: AdminRequired, db: DbSession, limit: int = Query(default=10, ge=1, le=50), -) -> dict: +) -> IndexVersionsResponse: builds = db.query(IndexBuild).order_by(IndexBuild.created_at.desc()).limit(limit).all() - return { - "versions": [ - { - "version": b.version, - "embed_model": b.embed_model, - "index_type": b.index_type, - "num_vectors": b.num_vectors, - "dimension": b.dimension, - "is_active": b.is_active, - "created_at": b.created_at.isoformat() if b.created_at else None, - } + return IndexVersionsResponse( + versions=[ + IndexVersionResponse( + version=b.version, + embed_model=b.embed_model, + index_type=b.index_type, + num_vectors=b.num_vectors, + dimension=b.dimension, + is_active=b.is_active, + created_at=b.created_at.isoformat() if b.created_at else None, + ) for b in builds ] - } + ) From 13a39a2c75c73a59091bb6963ad72492a1dc3c08 Mon Sep 17 00:00:00 2001 From: listlessbird <124798751+listlessbird@users.noreply.github.com> Date: Tue, 24 Feb 2026 20:57:23 +0530 Subject: [PATCH 2/2] fix: match with orm schema --- backend-api/src/api/models/health.py | 10 +-- backend-api/uv.lock | 107 ++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 8 deletions(-) diff --git a/backend-api/src/api/models/health.py b/backend-api/src/api/models/health.py index e3f2d14..b6c26aa 100644 --- a/backend-api/src/api/models/health.py +++ b/backend-api/src/api/models/health.py @@ -8,11 +8,11 @@ class HealthResponse(BaseModel): class IndexVersionResponse(BaseModel): - version: int - embed_model: str - index_type: str - num_vectors: int - dimension: int + version: str + embed_model: str | None = None + index_type: str | None = None + num_vectors: int | None = None + dimension: int | None = None is_active: bool created_at: str | None = None diff --git a/backend-api/uv.lock b/backend-api/uv.lock index 2e35e65..654b083 100644 --- a/backend-api/uv.lock +++ b/backend-api/uv.lock @@ -670,17 +670,18 @@ wheels = [ [[package]] name = "fastapi" -version = "0.122.0" +version = "0.133.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, { name = "pydantic" }, { name = "starlette" }, { name = "typing-extensions" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b2/de/3ee97a4f6ffef1fb70bf20561e4f88531633bb5045dc6cebc0f8471f764d/fastapi-0.122.0.tar.gz", hash = "sha256:cd9b5352031f93773228af8b4c443eedc2ac2aa74b27780387b853c3726fb94b", size = 346436, upload-time = "2025-11-24T19:17:47.95Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/04/ab382c7c03dd545f2c964d06e87ad0d5faa944a2434186ad9c285f5d87e0/fastapi-0.133.0.tar.gz", hash = "sha256:b900a2bf5685cdb0647a41d5900bdeafc3a9e8a28ac08c6246b76699e164d60d", size = 373265, upload-time = "2026-02-24T09:53:40.143Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/93/aa8072af4ff37b795f6bbf43dcaf61115f40f49935c7dbb180c9afc3f421/fastapi-0.122.0-py3-none-any.whl", hash = "sha256:a456e8915dfc6c8914a50d9651133bd47ec96d331c5b44600baa635538a30d67", size = 110671, upload-time = "2025-11-24T19:17:45.96Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b4/023e75a2ec3f5440e380df6caf4d28edc0806d007193e6fb0707237886a4/fastapi-0.133.0-py3-none-any.whl", hash = "sha256:0a78878483d60702a1dde864c24ab349a1a53ef4db6b6f74f8cd4a2b2bc67d2f", size = 104787, upload-time = "2026-02-24T09:53:41.404Z" }, ] [package.optional-dependencies] @@ -689,6 +690,8 @@ standard = [ { name = "fastapi-cli", extra = ["standard"] }, { name = "httpx" }, { name = "jinja2" }, + { name = "pydantic-extra-types" }, + { name = "pydantic-settings" }, { name = "python-multipart" }, { name = "uvicorn", extra = ["standard"] }, ] @@ -1614,6 +1617,91 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "mimeme-backend-api" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "accelerate" }, + { name = "alembic" }, + { name = "bitsandbytes" }, + { name = "boto3" }, + { name = "einops" }, + { name = "faiss-cpu" }, + { name = "fastapi", extra = ["standard"] }, + { name = "httpx" }, + { name = "imagehash" }, + { name = "modal" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "psycopg2-binary" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyvips" }, + { name = "slowapi" }, + { name = "sqlalchemy" }, + { name = "structlog" }, + { name = "temporalio" }, + { name = "torch" }, + { name = "torchvision" }, + { name = "transformers" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[package.dev-dependencies] +dev = [ + { name = "ipykernel" }, + { name = "matplotlib" }, + { name = "pandas" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "ruff" }, + { name = "seaborn" }, + { name = "types-boto3-custom" }, +] + +[package.metadata] +requires-dist = [ + { name = "accelerate", specifier = ">=0.34" }, + { name = "alembic", specifier = ">=1.13" }, + { name = "bitsandbytes", specifier = ">=0.48.2" }, + { name = "boto3", specifier = ">=1.40.59" }, + { name = "einops", specifier = ">=0.8.1" }, + { name = "faiss-cpu", specifier = ">=1.13.0" }, + { name = "fastapi", extras = ["standard"], specifier = ">=0.131.0" }, + { name = "httpx", specifier = ">=0.28.1" }, + { name = "imagehash", specifier = ">=4.3.2" }, + { name = "modal", specifier = ">=0.73" }, + { name = "numpy", specifier = ">=2.0" }, + { name = "pillow", specifier = ">=12.0.0" }, + { name = "psycopg2-binary", specifier = ">=2.9" }, + { name = "pydantic", specifier = ">=2.7" }, + { name = "pydantic-settings", specifier = ">=2.12.0" }, + { name = "pyvips", specifier = ">=2.2.3" }, + { name = "slowapi", specifier = ">=0.1.9" }, + { name = "sqlalchemy", specifier = ">=2.0" }, + { name = "structlog", specifier = ">=25.5.0" }, + { name = "temporalio", specifier = ">=1.9.0" }, + { name = "torch", specifier = ">=2.3" }, + { name = "torchvision", specifier = ">=0.24.0" }, + { name = "transformers", specifier = ">=4.45" }, + { name = "uvicorn", extras = ["standard"], specifier = ">=0.38.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "ipykernel", specifier = ">=7.2.0" }, + { name = "matplotlib", specifier = ">=3.10.8" }, + { name = "pandas", specifier = ">=3.0.0" }, + { name = "pytest", specifier = ">=9.0.2" }, + { name = "pytest-asyncio", specifier = ">=1.3.0" }, + { name = "pytest-cov", specifier = ">=7.0.0" }, + { name = "ruff", specifier = ">=0.14.6" }, + { name = "seaborn", specifier = ">=0.13.2" }, + { name = "types-boto3-custom", path = "vendored/types_boto3_custom-1.40.59-py3-none-any.whl" }, +] + [[package]] name = "modal" version = "1.3.2" @@ -2411,6 +2499,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/48/f7/925f65d930802e3ea2eb4d5afa4cb8730c8dc0d2cb89a59dc4ed2fcb2d74/pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f", size = 2147775, upload-time = "2025-10-14T10:23:45.406Z" }, ] +[[package]] +name = "pydantic-extra-types" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/35/2fee58b1316a73e025728583d3b1447218a97e621933fc776fb8c0f2ebdd/pydantic_extra_types-2.11.0.tar.gz", hash = "sha256:4e9991959d045b75feb775683437a97991d02c138e00b59176571db9ce634f0e", size = 157226, upload-time = "2025-12-31T16:18:27.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/17/fabd56da47096d240dd45ba627bead0333b0cf0ee8ada9bec579287dadf3/pydantic_extra_types-2.11.0-py3-none-any.whl", hash = "sha256:84b864d250a0fc62535b7ec591e36f2c5b4d1325fa0017eb8cda9aeb63b374a6", size = 74296, upload-time = "2025-12-31T16:18:26.38Z" }, +] + [[package]] name = "pydantic-settings" version = "2.12.0"