From 2e105a99b81e4a9b686826a634db4f5aab94b83a Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 14 May 2026 11:48:11 +0200 Subject: [PATCH] chore(version): update version and changelog for release 0.8.123 - Bumped APP_VERSION to 0.8.123 and updated the changelog to reflect recent changes. - Fixed internal calls in GET /api/dashboard/kpis to use unwrap_query_default, preventing 500 errors due to FastAPI query defaults. - Enhanced list_exercises and list_training_units functions to utilize unwrap_query_default for improved query handling. - Added unit tests for unwrap_query_default to ensure correct behavior in various scenarios. --- backend/fastapi_param_unwrap.py | 16 ++++++++++++++++ backend/routers/exercises.py | 7 +++++++ backend/routers/training_planning.py | 15 +++++++++++++++ backend/tests/test_dashboard_kpis.py | 10 +++++++++- backend/version.py | 9 ++++++++- 5 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 backend/fastapi_param_unwrap.py diff --git a/backend/fastapi_param_unwrap.py b/backend/fastapi_param_unwrap.py new file mode 100644 index 0000000..b67d84b --- /dev/null +++ b/backend/fastapi_param_unwrap.py @@ -0,0 +1,16 @@ +"""Hilfen für direkte Python-Aufrufe von FastAPI-Route-Handlern (ohne Request-Kontext).""" +from __future__ import annotations + +from typing import Any + + +def unwrap_query_default(value: Any) -> Any: + """ + Parameter mit Annotation ``= Query(default=…)`` sind im Funktionskörper ``fastapi.params.Query``-Instanzen, + solange FastAPI sie nicht durch echte Werte ersetzt hat (interne Aufrufe, Aggregat-Endpunkte). + """ + try: + from fastapi.params import Query + except ImportError: + return value + return value.default if isinstance(value, Query) else value diff --git a/backend/routers/exercises.py b/backend/routers/exercises.py index c85840b..02ffcc6 100644 --- a/backend/routers/exercises.py +++ b/backend/routers/exercises.py @@ -19,6 +19,8 @@ from fastapi.responses import FileResponse, Response, StreamingResponse from pydantic import BaseModel, Field, model_validator from psycopg2.extras import Json +from fastapi_param_unwrap import unwrap_query_default + from db import get_db, get_cursor, r2d from club_tenancy import ( assert_valid_governance_visibility, @@ -1773,6 +1775,11 @@ def list_exercises( Optional include_variants für Variantenauswahl in der Trainingsplanung. Keyset: cursor_updated_at + cursor_id ersetzt große OFFSET-Werte (Sortierung: updated_at DESC, id DESC). """ + cursor_updated_at = unwrap_query_default(cursor_updated_at) + cursor_id = unwrap_query_default(cursor_id) + limit = unwrap_query_default(limit) + offset = unwrap_query_default(offset) + profile_id = tenant.profile_id c_ts_raw = (cursor_updated_at or "").strip() or None diff --git a/backend/routers/training_planning.py b/backend/routers/training_planning.py index d2c635b..461999e 100644 --- a/backend/routers/training_planning.py +++ b/backend/routers/training_planning.py @@ -10,6 +10,8 @@ from typing import Any, Dict, List, Optional, Tuple from fastapi import APIRouter, Depends, HTTPException, Query from psycopg2.extras import Json as PsycopgJson +from fastapi_param_unwrap import unwrap_query_default + from db import get_db, get_cursor, r2d from tenant_context import TenantContext, get_tenant_context, library_content_visibility_sql from club_tenancy import ( @@ -1341,6 +1343,19 @@ def list_training_units( ), tenant: TenantContext = Depends(get_tenant_context), ): + group_id = unwrap_query_default(group_id) + club_id = unwrap_query_default(club_id) + start_date = unwrap_query_default(start_date) + end_date = unwrap_query_default(end_date) + status = unwrap_query_default(status) + assigned_to_me = unwrap_query_default(assigned_to_me) + debrief_pending = unwrap_query_default(debrief_pending) + sort = unwrap_query_default(sort) + limit = unwrap_query_default(limit) + cursor_planned_date = unwrap_query_default(cursor_planned_date) + cursor_planned_time = unwrap_query_default(cursor_planned_time) + cursor_id = unwrap_query_default(cursor_id) + profile_id = tenant.profile_id role = tenant.global_role diff --git a/backend/tests/test_dashboard_kpis.py b/backend/tests/test_dashboard_kpis.py index 8847f01..c9ea3dc 100644 --- a/backend/tests/test_dashboard_kpis.py +++ b/backend/tests/test_dashboard_kpis.py @@ -1,13 +1,15 @@ -"""GET /api/dashboard/kpis: Auth (kein DB nötig).""" +"""GET /api/dashboard/kpis: Auth + interne Aufruf-Hilfen.""" from __future__ import annotations import os import pytest +from fastapi import Query from fastapi.testclient import TestClient os.environ.setdefault("SKIP_DB_MIGRATE", "1") +from fastapi_param_unwrap import unwrap_query_default from main import app @@ -16,6 +18,12 @@ def client() -> TestClient: return TestClient(app) +def test_unwrap_query_default_for_direct_route_calls() -> None: + assert unwrap_query_default(Query(default=None)) is None + assert unwrap_query_default("2026-01-01") == "2026-01-01" + assert unwrap_query_default(7) == 7 + + def test_dashboard_kpis_unauthenticated_401(client: TestClient) -> None: r = client.get("/api/dashboard/kpis") assert r.status_code == 401 diff --git a/backend/version.py b/backend/version.py index e798998..7364ece 100644 --- a/backend/version.py +++ b/backend/version.py @@ -1,6 +1,6 @@ # Shinkan Jinkendo Version Information -APP_VERSION = "0.8.122" +APP_VERSION = "0.8.123" BUILD_DATE = "2026-05-12" DB_SCHEMA_VERSION = "20260514062" @@ -36,6 +36,13 @@ MODULE_VERSIONS = { } CHANGELOG = [ + { + "version": "0.8.123", + "date": "2026-05-13", + "changes": [ + "Fix: GET /api/dashboard/kpis — interne Aufrufe von list_exercises / list_training_units erhielten FastAPI-Query-Defaults statt None; .strip() auf Query-Objekt → 500. unwrap_query_default in beiden Handlern (Hilfsmodul fastapi_param_unwrap.py).", + ], + }, { "version": "0.8.122", "date": "2026-05-13",