{"record":{"id":"9fb57032186e0952","repo":"PaddlePaddle/PaddleOCR","slug":"failed-to-download-resource-e","errorCode":null,"errorMessage":"Failed to download resource: {e}","messagePattern":"Failed to download resource: (.+?)","errorType":"exception","errorClass":"NetworkError","httpStatus":null,"severity":"error","filePath":"paddleocr/_api_client/_resources.py","lineNumber":57,"sourceCode":"\n    parsed_url = urlparse(resource_url)\n    if parsed_url.scheme not in (\"http\", \"https\") or not parsed_url.netloc:\n        raise InvalidRequestError(f\"Invalid resource URL: {resource_url}\")\n\n    target = _resolve_destination(parsed_url.path, destination, filename)\n    _require_writable_target(target, overwrite)\n\n    try:\n        response = requests.get(resource_url, timeout=timeout)\n    except requests.Timeout as e:\n        raise RequestTimeoutError(f\"Request timed out: {e}\") from e\n    except requests.ConnectionError as e:\n        raise NetworkError(f\"Connection failed: {e}\") from e\n\n    try:\n        response.raise_for_status()\n    except requests.RequestException as e:\n        raise NetworkError(f\"Failed to download resource: {e}\") from e\n\n    _atomic_write(target, response.content, overwrite)\n    return str(target)\n\n\ndef save_ocr_result_resources(\n    result: OCRResult,\n    destination: str,\n    *,\n    overwrite: bool = False,\n    timeout: float = 300.0,\n) -> List[str]:\n    if result is None:\n        raise InvalidRequestError(\"OCR result is required.\")\n    dest_dir = _require_existing_directory(destination)\n    saved_paths = []\n    for index, page in enumerate(result.pages):\n        if not page.ocr_image_url:","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/PaddlePaddle/PaddleOCR/blob/2661c7c0ef5c613e8f93c6e93b2e052399f0f854/paddleocr/_api_client/_resources.py#L39-L75","documentation":"NetworkError raised when response.raise_for_status() fails after a successful download connection: the server answered with an HTTP error status (4xx/5xx). Unlike 190, the connection worked — the resource request itself was rejected.","triggerScenarios":"save_resource hitting an expired signed result URL (403/404 from object storage), a result URL invalidated because the job's retention window elapsed, an auth-protected bucket returning 401, or the storage backend returning 5xx under load.","commonSituations":"Fetching OCR result images hours/days after job completion once presigned URLs expire; re-running an old job's saved URLs; bucket permission changes; transient 502/503 from the CDN in front of result storage.","solutions":["Re-create the job (or re-poll it) to obtain fresh result URLs — expired signatures are the most common cause","Check the status code in the chained exception to distinguish 403/404 (expired/gone — must resubmit) from 5xx (retry later)","Retry with backoff for 5xx statuses","Ensure you download resources promptly after job completion, within the provider's retention window"],"exampleFix":"# before\nresult = client.ocr(...)  # much later:\nsave_resource(result.pages[0].ocr_image_url, dest)  # expired URL -> 403\n\n# after\nresult = client.ocr(...)  # download immediately after job completes\nsave_ocr_result_resources(result, dest_dir, timeout=300)","handlingStrategy":"retry","validationCode":"r = requests.head(url, timeout=30)\nif r.status_code in (401, 403, 404):\n    raise RuntimeError('result URL expired; resubmit the job')","typeGuard":null,"tryCatchPattern":"try:\n    save_resource(url, dest)\nexcept NetworkError as e:\n    code = e.__cause__.response.status_code if getattr(e.__cause__, 'response', None) else None\n    if code and 500 <= code < 600:\n        time.sleep(30); save_resource(url, dest)  # retry 5xx only\n    else:\n        raise  # 4xx: URL expired/gone, resubmit job","preventionTips":["Download result artifacts immediately after job completion, before presigned URLs expire","Persist enough state (file hashes) to re-create jobs instead of reusing stale URLs","Distinguish 4xx (permanent) from 5xx (retryable) before retrying"],"tags":["http-status","download","expired-url"],"backgroundTag":null,"analyzedSha":"2661c7c0ef5c613e8f93c6e93b2e052399f0f854","analyzedAt":"2026-08-14T20:17:30.180Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}