odysseus-dev/odysseus · warning · HTTPException
Invalid angle
Error message
Invalid angle
What it means
HTTP 400 raised by POST /api/gallery/{image_id}/rotate when the "angle" field cannot be converted to int. int(data.get('angle', 90)) raises TypeError (for non-numeric containers) or ValueError (for non-numeric strings like "45deg" or ""), and both are caught and mapped to this 400.
Source
Thrown at routes/gallery/gallery_routes.py:515
db.commit()
return {"ok": True, "name": new_name}
finally:
db.close()
# ---- POST /api/gallery/{image_id}/rotate ----
@router.post("/api/gallery/{image_id}/rotate")
async def gallery_rotate(request: Request, image_id: str):
"""Rotate an image by ±90° or 180°. Updates the file on disk and the
width/height in the DB. Body: {angle: 90 | -90 | 180}."""
from pathlib import Path
from PIL import Image
from io import BytesIO
data = await request.json()
try:
angle = int(data.get("angle", 90))
except (TypeError, ValueError):
raise HTTPException(400, "Invalid angle")
if angle not in (90, -90, 180, 270):
raise HTTPException(400, "Angle must be 90, -90, 180, or 270")
user = get_current_user(request)
db = SessionLocal()
try:
img = db.query(GalleryImage).filter(GalleryImage.id == image_id).first()
if not img:
raise HTTPException(404, "Image not found")
if not user or img.owner != user:
raise HTTPException(403, "Not your image")
img_path = _gallery_image_path(img.filename)
if not img_path.exists():
raise HTTPException(404, "Image file not found")
# PIL rotates counter-clockwise; the API takes "clockwise"
# convention so we negate to match user expectation.View on GitHub (pinned to f9235ebbf1)
Solutions
- Send angle as a JSON number: {"angle": 90}.
- If building from UI state, coerce with Number() before serializing.
- Strip unit suffixes before sending.
Example fix
// before
body: JSON.stringify({angle: `${deg}°`})
// after
body: JSON.stringify({angle: Number(deg)}) Defensive patterns
Strategy: type-guard
Validate before calling
const angle = Number(rawAngle);
if (!Number.isFinite(angle) || !Number.isInteger(angle)) return showError('Angle must be a number'); Type guard
const isParsableAngle = (v) => { const n = Number(v); return Number.isFinite(n); }; Prevention
- Serialize angle as a JSON number
- Coerce slider/string values with Number() first
- Strip unit suffixes before sending
When it happens
Trigger: Sending {"angle": "sideways"}, {"angle": ""}, {"angle": null}-adjacent values like a list, or omitting a valid default path by passing a dict/object.
Common situations: Frontend sending the angle as a formatted string; sliders that emit "90°"; API consumers assuming the field is optional-but-typed.
Related errors
- Angle must be 90, -90, 180, or 270
- No image provided
- Name cannot be empty
- Name too long
- Image file not found
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/c9fccbf0f1c11f2d.
Report an issue: GitHub.