odysseus-dev/odysseus · error · HTTPException
No image provided
Error message
No image provided
What it means
HTTP 400 raised by POST /api/gallery/{image_id}/replace when the multipart form has no usable file. The handler requires a form field named 'image' that is a file-like object (has a .read attribute). A missing field, a plain text form value, or a non-multipart body all fail the check `not file or not hasattr(file, 'read')`.
Source
Thrown at routes/gallery/gallery_routes.py:449
db.close()
# ---- POST /api/gallery/{id}/replace ----
@router.post("/api/gallery/{image_id}/replace")
async def gallery_replace(request: Request, image_id: str):
"""Replace an existing gallery image file with a new one."""
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")
form = await request.form()
file = form.get("image")
if not file or not hasattr(file, 'read'):
raise HTTPException(400, "No image provided")
content = await read_upload_limited(file, GALLERY_UPLOAD_MAX_BYTES, "Gallery replacement")
GALLERY_IMAGE_DIR.mkdir(parents=True, exist_ok=True)
img_path = _gallery_image_path(img.filename)
img_path.write_bytes(content)
# Refresh dimensions in case the editor resized the canvas.
# updated_at auto-bumps via TimestampMixin's onupdate hook.
try:
from PIL import Image
from io import BytesIO
with Image.open(BytesIO(content)) as new_im:
img.width = new_im.width
img.height = new_im.height
except Exception:
pass
try:
db.commit()View on GitHub (pinned to f9235ebbf1)
Solutions
- Send multipart/form-data with a file part named exactly 'image'.
- Make the file input required client-side before submitting.
- For curl: curl -F 'image=@new.png' .../replace.
- Check no proxy or body-parsing middleware strips multipart bodies.
Example fix
// before
const fd = new FormData(); fd.append('file', blob); // wrong field name -> 400
// after
const fd = new FormData(); fd.append('image', blob, 'edit.png'); Defensive patterns
Strategy: validation
Validate before calling
if (!(blob instanceof Blob) || blob.size === 0) return showError('Select an image');
const fd = new FormData(); fd.append('image', blob, 'edit.png'); Type guard
const isFilePart = (v) => v != null && typeof v.read === 'function'; // server-side shape
Try / catch
try { await fetch(url, {method:'POST', body: fd}); } catch (e) { if (e.status === 400) showError('Attach an image file in the "image" field'); } Prevention
- Always append the file under the exact key 'image'
- Disable submit until a file is chosen
- Send multipart/form-data, never JSON, for upload endpoints
When it happens
Trigger: POSTing JSON instead of multipart/form-data; submitting the form with the file input left empty; naming the part 'file' or 'imageFile' instead of 'image'; sending a string value in the 'image' field.
Common situations: Frontend FormData built with the wrong field name; file input not required in the UI so users submit empty; curl invocations missing -F 'image=@path'; test clients sending urlencoded bodies.
Related errors
- No image
- HTTP ${resp.status}${detail ? `: ${detail}` : ''}
- HTTP ${saveRes.status}: ${errBody.substring(0, 120)}
- Not your image
- Name cannot be empty
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/c8095792815283d5.
Report an issue: GitHub.