{"record":{"id":"38c5bba6cf233574","repo":"datawhalechina/hello-agents","slug":"tmdb-e-38c5bb","errorCode":null,"errorMessage":"TMDB 网络错误: {e}","messagePattern":"TMDB 网络错误: (.+?)","errorType":"exception","errorClass":"MovieServiceError","httpStatus":null,"severity":"error","filePath":"Co-creation-projects/aatanxiao12-beep-YingQian/backend/app/services/movie_service.py","lineNumber":120,"sourceCode":"        else:\n            params[\"api_key\"] = api_key  # type: ignore[assignment]\n        return headers, params\n\n    def _get(self, path: str, extra_params: Optional[Dict[str, Any]] = None) -> dict:\n        \"\"\"对 TMDB 发 GET，统一处理超时、网络错误与非 2xx / 非 JSON。\"\"\"\n        headers, params = self._auth_headers_and_params()\n        if extra_params:\n            for k, v in extra_params.items():\n                if v is not None and v != \"\":\n                    params[k] = v\n\n        url = f\"{TMDB_API_BASE}{path}\"\n        try:\n            resp = self._client.get(url, headers=headers, params=params)\n        except httpx.TimeoutException as e:\n            raise MovieServiceError(f\"TMDB 请求超时: {e}\") from e\n        except httpx.HTTPError as e:\n            raise MovieServiceError(f\"TMDB 网络错误: {e}\") from e\n\n        if resp.status_code == 401:\n            raise MovieServiceError(\n                \"TMDB 鉴权失败：请检查 Access Token / API Key\",\n                status_code=401,\n            )\n        if resp.status_code == 404:\n            raise MovieServiceError(\"影片不存在或已下架\", status_code=404)\n        if resp.status_code >= 400:\n            raise MovieServiceError(\n                f\"TMDB 请求失败: HTTP {resp.status_code}, {resp.text[:200]}\"\n            )\n\n        try:\n            return resp.json()\n        except ValueError as e:\n            raise MovieServiceError(\"TMDB 返回非 JSON\") from e\n","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/datawhalechina/hello-agents/blob/606a07d341a47be773fab7f4b71177f53f96b2c3/Co-creation-projects/aatanxiao12-beep-YingQian/backend/app/services/movie_service.py#L102-L138","documentation":"MovieServiceError raised when the httpx GET raises httpx.HTTPError that is not a TimeoutException — i.e. connection-level failures: DNS resolution errors (ConnectError wrapping socket.gaierror), TLS certificate failures, connection refused/reset, or protocol errors. Note the ordering matters: TimeoutException is a subclass scenario handled first, so this branch catches the remaining transport errors.","triggerScenarios":"No internet or DNS failure resolving api.themoviedb.org; TLS interception proxy presenting an untrusted certificate (SSLError); connection reset by firewall/GFW-style filtering; proxy misconfiguration (httpx honors HTTP_PROXY env vars, and a dead proxy yields ConnectError); IPv6-only environment where TMDB is unreachable.","commonSituations":"Laptop on captive-portal Wi-Fi; corporate TLS-inspection with a custom CA not in certifi's bundle; HTTP_PROXY set in the environment pointing to a stopped local proxy; cloud egress blocked by security group.","solutions":["Verify basic reachability: curl -I https://api.themoviedb.org/3/configuration from the same host/container","Unset or fix proxy env vars (HTTP_PROXY/HTTPS_PROXY/ALL_PROXY) if they point at a dead proxy; or configure trust_env=False","For TLS interception, provide the corporate CA bundle: httpx.Client(verify='/path/to/ca.pem')","Distinguish DNS vs TLS vs refused in the message by including type(e).__name__"],"exampleFix":"# before\nexcept httpx.HTTPError as e:\n    raise MovieServiceError(f\"TMDB 网络错误: {e}\") from e\n\n# after\nexcept httpx.ConnectError as e:\n    raise MovieServiceError(\n        f\"TMDB 连接失败（DNS/网络不可达）: {type(e).__name__}: {e}\",\n        status_code=502,\n    ) from e\nexcept httpx.HTTPError as e:\n    raise MovieServiceError(f\"TMDB 网络错误: {e}\") from e","handlingStrategy":"retry","validationCode":"# preflight reachability (cheap TCP/DNS check, not per-request)\nimport socket\n\ndef tmdb_reachable(host: str = 'api.themoviedb.org') -> bool:\n    try:\n        socket.getaddrinfo(host, 443)\n        return True\n    except socket.gaierror:\n        return False","typeGuard":null,"tryCatchPattern":"except MovieServiceError as e:\n    if '网络错误' in str(e):\n        # transport-level: one retry, then degrade\n        return fallback_cache_or_empty()\n    raise","preventionTips":["Verify DNS/proxy env in the deployment (curl the API base from the same container)","Provide corporate CA bundle to httpx(verify=...) when behind TLS inspection","Separate ConnectError (config/network) from other HTTPError — only the latter is usually worth retrying"],"tags":["tmdb","httpx","network","dns","tls","python"],"backgroundTag":null,"analyzedSha":"606a07d341a47be773fab7f4b71177f53f96b2c3","analyzedAt":"2026-08-14T22:57:27.446Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}