Fix timer action 500s after FastAPI upgrade
FastAPI 0.141 raises when response serialization hits a lazy-load (current_block.subject.options) in async context, where 0.115 silently skipped it. Eager-load Subject.options in session get/timer and the TV dashboard queries. Pin pydantic 2.13.5. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
4130467b22
commit
5a2510059d
5 files changed
+10
-4
No files matched your search
@@ -7,7 +7,7 @@ Maintenance release from the 2026-09-24 review (`reports/maintenance-2026-09-24.
|
||||
### Security
|
||||
- Rate limits now apply to each client. nginx resolves the real client IP through Cloudflare → NPM (`set_real_ip_from` + `real_ip_recursive`). Before this, every request looked like it came from NPM, so all users shared one login limit.
|
||||
- ntfy alerts show the real client IP (`X-Real-IP` from nginx). They no longer use the first `X-Forwarded-For` entry, which the client controls.
|
||||
- Python dependency updates:
|
||||
- Python dependency updates (pydantic now pinned at 2.13.5):
|
||||
- PyJWT 2.15.0 (fixes an auth bypass)
|
||||
- anyio 4.15.1
|
||||
- fastapi 0.141.1 / starlette 1.7.0
|
||||
@@ -29,6 +29,7 @@ Maintenance release from the 2026-09-24 review (`reports/maintenance-2026-09-24.
|
||||
- Docker log rotation on all services: json-file, 10 MB × 3.
|
||||
|
||||
### Fixes
|
||||
- **Timer actions returned 500** after the FastAPI 0.141 upgrade. The session queries didn't load `Subject.options`, which `DailySessionOut` serializes. Old FastAPI silently swallowed the failed lazy-load; the new one raises it. Now eager-loaded in `sessions.py` (get/timer) and `dashboard.py`.
|
||||
- The meeting alert catch-up window fix (`8e92ae6`) is now deployed.
|
||||
|
||||
### Known / accepted
|
||||
|
||||
@@ -39,7 +39,7 @@ async def get_dashboard(tv_token: int, db: AsyncSession = Depends(get_db)):
|
||||
DailySession.session_date == date.today(),
|
||||
DailySession.is_active == True,
|
||||
)
|
||||
.options(selectinload(DailySession.current_block))
|
||||
.options(selectinload(DailySession.current_block).selectinload(ScheduleBlock.subject).selectinload(Subject.options))
|
||||
.limit(1)
|
||||
)
|
||||
session = session_result.scalar_one_or_none()
|
||||
|
||||
@@ -172,7 +172,7 @@ async def get_session(
|
||||
select(DailySession)
|
||||
.join(Child)
|
||||
.where(DailySession.id == session_id, Child.user_id == current_user.id)
|
||||
.options(selectinload(DailySession.current_block).selectinload(ScheduleBlock.subject))
|
||||
.options(selectinload(DailySession.current_block).selectinload(ScheduleBlock.subject).selectinload(Subject.options))
|
||||
)
|
||||
session = result.scalar_one_or_none()
|
||||
if not session:
|
||||
@@ -191,7 +191,7 @@ async def timer_action(
|
||||
select(DailySession)
|
||||
.join(Child)
|
||||
.where(DailySession.id == session_id, Child.user_id == current_user.id)
|
||||
.options(selectinload(DailySession.current_block).selectinload(ScheduleBlock.subject))
|
||||
.options(selectinload(DailySession.current_block).selectinload(ScheduleBlock.subject).selectinload(Subject.options))
|
||||
)
|
||||
session = result.scalar_one_or_none()
|
||||
if not session:
|
||||
|
||||
@@ -9,6 +9,7 @@ PyJWT==2.15.0
|
||||
cryptography==50.0.1
|
||||
passlib[bcrypt]==1.7.4
|
||||
bcrypt==3.2.2
|
||||
pydantic==2.13.5
|
||||
pydantic-settings==2.5.2
|
||||
alembic==1.20.0
|
||||
Mako==1.4.3
|
||||
|
||||
@@ -156,6 +156,10 @@ This section was first blocked on backups. The user then asked for the HIGH find
|
||||
| homeschool_backend | homeschool-backend | sha256:51b49afb4ec4… |
|
||||
| homeschool_frontend | homeschool-frontend | sha256:f9a737699cda… |
|
||||
|
||||
### Post-deploy regression
|
||||
|
||||
After the HIGH deploy, `POST /api/sessions/{id}/timer` returned **500 on every call**: 8 failures, 0 successes, starting 06:43 UTC. FastAPI 0.141 raises on a failed lazy-load of `current_block.subject.options` during response serialization, where 0.115 silently skipped it. The first smoke test didn't cover timer actions. Fixed in v1.1.0 by eager-loading `Subject.options` in `sessions.py` and `dashboard.py`. The smoke test now covers the full timer lifecycle, the TV dashboard with an active block, schedule/subject writes, and every parameterless GET route (from the OpenAPI spec).
|
||||
|
||||
### Post-update scan (trivy HIGH/CRITICAL)
|
||||
|
||||
| Image | Before | After |
|
||||
|
||||
Reference in new issue
Block a user