From 1622588922791cf88e68410e08bc491a4af90ff6 Mon Sep 17 00:00:00 2001 From: Joshua Price Date: Fri, 3 Oct 2025 15:24:19 +0100 Subject: [PATCH 1/7] Allow all json files to reach all_analytics It turns out pure aif can be sent to this api, therefore, to deal with this, and any future surprises, lets just forward the raw file to the function and let it fail if there are any problems. --- src/__init__.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/__init__.py b/src/__init__.py index 4d604b4..a5858ea 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -3,15 +3,8 @@ from . import papa -# TODO: Flesh this out? -class Xaif(BaseModel): - AIF: dict - text: str - OVA: dict | None = None - - class RequestBody(BaseModel): - xaif: Xaif + xaif: dict node_level: bool | None = None speaker: bool | None = None forecast: bool | None = None @@ -23,9 +16,9 @@ class RequestBody(BaseModel): @app.post("/api/all_analytics") # Call without async so that fastapi does the work on a threadpool as # all_analytics is cpu-bound and we don't want to block the current thread. -def all_analytics(body: RequestBody | Xaif): +def all_analytics(body: RequestBody | dict): if isinstance(body, RequestBody): - xaif = dict(body.xaif) + xaif = body.xaif kwargs = {} for name, value in body: if name == "xaif": @@ -33,6 +26,6 @@ def all_analytics(body: RequestBody | Xaif): kwargs[name] = value if value is not None else False else: - xaif = dict(body) + xaif = body return papa.all_analytics(xaif) From f880068a0476d602b9f0a8d08d448a98c52ae149 Mon Sep 17 00:00:00 2001 From: Joshua Price Date: Fri, 3 Oct 2025 18:13:43 +0100 Subject: [PATCH 2/7] Fix forwarding of kwargs in body to all_analytics --- src/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/__init__.py b/src/__init__.py index a5858ea..9ee5f26 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -17,9 +17,9 @@ class RequestBody(BaseModel): # Call without async so that fastapi does the work on a threadpool as # all_analytics is cpu-bound and we don't want to block the current thread. def all_analytics(body: RequestBody | dict): + kwargs = {} if isinstance(body, RequestBody): xaif = body.xaif - kwargs = {} for name, value in body: if name == "xaif": continue @@ -28,4 +28,4 @@ def all_analytics(body: RequestBody | dict): else: xaif = body - return papa.all_analytics(xaif) + return papa.all_analytics(xaif, **kwargs) From 50a457cc16cd45ea7180fa83ce4a3817678a7614 Mon Sep 17 00:00:00 2001 From: Joshua Price Date: Fri, 3 Oct 2025 18:34:43 +0100 Subject: [PATCH 3/7] Allow passthrough of arbitrary parameters In case the function signature of all_analytics changes slightly in the future, we need to handle that as gracefully as possible. --- src/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/__init__.py b/src/__init__.py index 9ee5f26..51249a8 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,13 +1,13 @@ from fastapi import FastAPI -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from . import papa class RequestBody(BaseModel): + model_config = ConfigDict( + extra="allow", + ) xaif: dict - node_level: bool | None = None - speaker: bool | None = None - forecast: bool | None = None app = FastAPI() From ae82fb233727399cb4e375dfa73e2a19ca65372e Mon Sep 17 00:00:00 2001 From: Joshua Price Date: Fri, 3 Oct 2025 18:51:13 +0100 Subject: [PATCH 4/7] Refactor all_analytics api endpoint Now that the logic is simpler, we can make the flow of this wrapper simpler. --- src/__init__.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/__init__.py b/src/__init__.py index 51249a8..28860ee 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -17,15 +17,7 @@ class RequestBody(BaseModel): # Call without async so that fastapi does the work on a threadpool as # all_analytics is cpu-bound and we don't want to block the current thread. def all_analytics(body: RequestBody | dict): - kwargs = {} if isinstance(body, RequestBody): - xaif = body.xaif - for name, value in body: - if name == "xaif": - continue - - kwargs[name] = value if value is not None else False + return papa.all_analytics(body.xaif, **{k: v for k, v in body if k != "xaif"}) else: - xaif = body - - return papa.all_analytics(xaif, **kwargs) + return papa.all_analytics(body) From 0f124d4870af62b9d8305f9b68f706b2e0a7c5c8 Mon Sep 17 00:00:00 2001 From: Joshua Price Date: Fri, 3 Oct 2025 19:20:17 +0100 Subject: [PATCH 5/7] Return traceback when all_analytics fails Although this may be unusual to do in a more production-like setting, it may be very helpful to people trying to call this. --- src/__init__.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/__init__.py b/src/__init__.py index 28860ee..3b21fdd 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,4 +1,6 @@ -from fastapi import FastAPI +import traceback + +from fastapi import FastAPI, HTTPException from pydantic import BaseModel, ConfigDict from . import papa @@ -17,7 +19,12 @@ class RequestBody(BaseModel): # Call without async so that fastapi does the work on a threadpool as # all_analytics is cpu-bound and we don't want to block the current thread. def all_analytics(body: RequestBody | dict): - if isinstance(body, RequestBody): - return papa.all_analytics(body.xaif, **{k: v for k, v in body if k != "xaif"}) - else: - return papa.all_analytics(body) + try: + if isinstance(body, RequestBody): + return papa.all_analytics( + body.xaif, **{k: v for k, v in body if k != "xaif"} + ) + else: + return papa.all_analytics(body) + except Exception: + raise HTTPException(status_code=500, detail=traceback.format_exc()) From 0e4a2dbf3e8afccd23967f95a47fd17498f9881f Mon Sep 17 00:00:00 2001 From: Joshua Price Date: Fri, 3 Oct 2025 20:54:38 +0100 Subject: [PATCH 6/7] Make tweaks to docs --- src/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/__init__.py b/src/__init__.py index 3b21fdd..473f879 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -12,13 +12,14 @@ class RequestBody(BaseModel): xaif: dict -app = FastAPI() +app = FastAPI(title="papa", summary="Papa: Amazing Python Analytics") @app.post("/api/all_analytics") # Call without async so that fastapi does the work on a threadpool as # all_analytics is cpu-bound and we don't want to block the current thread. -def all_analytics(body: RequestBody | dict): +def all_analytics(body: RequestBody | dict) -> dict: + """Wrapper around papa's all_analytics function.""" try: if isinstance(body, RequestBody): return papa.all_analytics( From 37a4d25a58e4318bb8c1074b3272770acacb1c94 Mon Sep 17 00:00:00 2001 From: Joshua Price Date: Fri, 3 Oct 2025 20:58:46 +0100 Subject: [PATCH 7/7] Update dockerfile to use trixie The newest version of debian is now out. --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 11acf59..51b98cb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # With inspiration from uv's example multistage dockerfile. -FROM python:3.13-slim-bookworm AS builder +FROM python:3.13-slim-trixie AS builder WORKDIR /app COPY --from=ghcr.io/astral-sh/uv:0.7 /uv /uvx /bin/ @@ -26,7 +26,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \ #&& uv run -m nltk.downloader all -d /app/.venv/nltk_data -FROM python:3.13-slim-bookworm AS runtime +FROM python:3.13-slim-trixie AS runtime COPY --from=builder /app /app