ZhuLinsen/daily_stock_analysis · error · HTTPException

read_failed

read_failed

Error message

读取上传文件失败

What it means

Raised by /extract-from-image when reading the request body's underlying file object raises an unexpected exception (the HTTPException for size is re-raised untouched; everything else lands here). It wraps transport/stream errors during file.file.read into a controlled 400 with error code read_failed and logs a warning '读取上传文件失败'.

Source

Thrown at api/v1/endpoints/stocks.py:172

            },
        )

    try:
        # 先读取限定大小,再检查是否还有剩余(语义清晰:超出则拒绝)
        data = file.file.read(MAX_SIZE_BYTES)
        if file.file.read(1):
            raise HTTPException(
                status_code=400,
                detail={
                    "error": "file_too_large",
                    "message": f"图片超过 {MAX_SIZE_BYTES // (1024 * 1024)}MB 限制",
                },
            )
    except HTTPException:
        raise
    except Exception as e:
        logger.warning(f"读取上传文件失败: {e}")
        raise HTTPException(
            status_code=400,
            detail={"error": "read_failed", "message": "读取上传文件失败"},
        )

    try:
        items, raw_text = extract_stock_codes_from_image(data, content_type)
        extract_items = [
            ExtractItem(code=code, name=name, confidence=conf) for code, name, conf in items
        ]
        codes = [i.code for i in extract_items]
        return ExtractFromImageResponse(
            codes=codes,
            items=extract_items,
            raw_text=raw_text if include_raw else None,
        )
    except ValueError as e:
        raise HTTPException(status_code=400, detail={"error": "extract_failed", "message": str(e)})
    except Exception as e:

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Retry the request — transient stream errors often succeed on a second attempt.
  2. Check proxy limits: set nginx client_max_body_size 6m and adequate proxy_read_timeout/proxy_send_timeout.
  3. Verify the API container has writable temp space and free disk for spooled uploads.

Example fix

# before (nginx)
client_max_body_size 1m;

# after
client_max_body_size 6m;
proxy_read_timeout 120s;
Defensive patterns

Strategy: retry

Try / catch

resp = None
for attempt in range(3):
    try:
        resp = requests.post(f'{base}/api/v1/stocks/extract-from-image',
                             files={'file': fh}, timeout=60)
        if resp.status_code != 400 or resp.json().get('detail', {}).get('error') != 'read_failed':
            break
    except requests.RequestException:
        pass
    time.sleep(2 ** attempt)
assert resp is not None

Prevention

When it happens

Trigger: Client aborts the upload mid-body so the multipart stream is truncated; a reverse proxy (nginx client_body_temp) fails or times out while streaming; spooled temp file deleted or unwritable (starlette spools >1MB to disk); malformed multipart chunking.

Common situations: nginx proxy with client_max_body_size smaller than the upload and interrupted transfers; disk-full on the API container's temp dir; flaky mobile networks dropping connections; load balancer idle timeouts during slow uploads.

Related errors


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/272504a1d72feb93. Report an issue: GitHub.