Files
F-r-ldraledigplannerare/backend/app/routers/planned_days.py
T
jacobh2 64734ae6e7 Silence the deprecation warnings our own code raises
Starlette renamed HTTP_422_UNPROCESSABLE_ENTITY to HTTP_422_UNPROCESSABLE_CONTENT
and now warns on the old spelling; the value is unchanged at 422, so the 20 call
sites in the routers move over with no wire-format or frontend impact.

Alembic >=1.16 supersedes version_path_separator with path_separator, which
governs both prepend_sys_path and version_locations, and warns when it is absent.

Together this takes pytest from 26 deprecation warnings to 6. The remainder are
slowapi calling asyncio.iscoroutinefunction; they are left visible deliberately,
with no filterwarnings entry, as a reminder to revisit once upstream ships a fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 18:48:02 +02:00

130 lines
4.6 KiB
Python

"""HTTP routes for planned leave days (calendar planning)."""
from uuid import UUID
from fastapi import APIRouter, HTTPException, Query, status
from app.dependencies import PlanningServiceDep, WorkspaceIdDep, WorkspaceScopeDep
from app.models.planned_leave import (
PlannedLeaveDayCreate,
PlannedLeaveDayResponse,
PlannedLeaveDayUpdate,
)
from app.utils.errors import NotFoundError, ValidationError
router = APIRouter(prefix="/api/planned-days", tags=["planned-days"])
def _to_response(day) -> PlannedLeaveDayResponse:
return PlannedLeaveDayResponse.from_entity(day)
def _to_http_error(exc: ValidationError) -> HTTPException:
"""Map a `ValidationError` to a 422, forwarding its `message_key` + `data`
(§3.5) as structured `detail` when present — e.g. the free-day hand-pin
guard (`PlanningService._reject_illegal_free_day`) — so the frontend can
translate it instead of parsing an English sentence. Falls back to the
plain message for the (more common) error raised without one. Mirrors
`app.routers.workspace._to_http_error`.
"""
if exc.message_key:
return HTTPException(
status.HTTP_422_UNPROCESSABLE_CONTENT,
detail={"message_key": exc.message_key, "data": exc.data},
)
return HTTPException(status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(exc))
@router.post("", response_model=PlannedLeaveDayResponse, status_code=status.HTTP_201_CREATED)
async def create_planned_day(
payload: PlannedLeaveDayCreate,
service: PlanningServiceDep,
scope: WorkspaceScopeDep,
) -> PlannedLeaveDayResponse:
scope.check_workspace(payload.workspace_id)
await scope.check_child(payload.child_id)
await scope.check_member(payload.parent_personal_data_uuid)
try:
day = await service.add_planned_day(
workspace_id=payload.workspace_id,
child_id=payload.child_id,
parent_personal_data_uuid=payload.parent_personal_data_uuid,
date=payload.date,
day_type=payload.day_type,
is_dubbeldag=payload.is_dubbeldag,
wish_id=payload.wish_id,
is_pinned=payload.is_pinned,
)
return _to_response(day)
except ValidationError as exc:
raise _to_http_error(exc) from exc
@router.get("", response_model=list[PlannedLeaveDayResponse])
async def list_planned_days(
service: PlanningServiceDep,
scope: WorkspaceScopeDep,
child_id: UUID = Query(...),
) -> list[PlannedLeaveDayResponse]:
await scope.check_child(child_id)
days = await service.list_by_child(child_id)
return [_to_response(d) for d in days]
@router.get("/{planned_leave_day_id}", response_model=PlannedLeaveDayResponse)
async def get_planned_day(
planned_leave_day_id: UUID,
service: PlanningServiceDep,
scope: WorkspaceScopeDep,
) -> PlannedLeaveDayResponse:
try:
day = await service.get(planned_leave_day_id, workspace_id=scope.workspace_id)
return _to_response(day)
except NotFoundError as exc:
raise HTTPException(status.HTTP_404_NOT_FOUND, str(exc)) from exc
@router.get("/workspace/{workspace_id}", response_model=list[PlannedLeaveDayResponse])
async def list_planned_days_by_workspace(
workspace_id: WorkspaceIdDep,
service: PlanningServiceDep,
) -> list[PlannedLeaveDayResponse]:
days = await service.list_by_workspace(workspace_id)
return [_to_response(d) for d in days]
@router.put("/{planned_leave_day_id}", response_model=PlannedLeaveDayResponse)
async def update_planned_day(
planned_leave_day_id: UUID,
payload: PlannedLeaveDayUpdate,
service: PlanningServiceDep,
scope: WorkspaceScopeDep,
) -> PlannedLeaveDayResponse:
try:
day = await service.update(
planned_leave_day_id,
day_type=payload.day_type,
is_dubbeldag=payload.is_dubbeldag,
wish_id=payload.wish_id,
is_pinned=payload.is_pinned,
workspace_id=scope.workspace_id,
)
return _to_response(day)
except NotFoundError as exc:
raise HTTPException(status.HTTP_404_NOT_FOUND, str(exc)) from exc
except ValidationError as exc:
raise _to_http_error(exc) from exc
@router.delete("/{planned_leave_day_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_planned_day(
planned_leave_day_id: UUID,
service: PlanningServiceDep,
scope: WorkspaceScopeDep,
) -> None:
try:
await service.remove_planned_day(planned_leave_day_id, workspace_id=scope.workspace_id)
except NotFoundError as exc:
raise HTTPException(status.HTTP_404_NOT_FOUND, str(exc)) from exc