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

  1. Send angle as a JSON number: {"angle": 90}.
  2. If building from UI state, coerce with Number() before serializing.
  3. 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

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


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/c9fccbf0f1c11f2d. Report an issue: GitHub.