Coverage for src/data_api/core/upstream.py: 100%
23 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-13 19:28 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-13 19:28 +0000
1"""Classify expected failures at live upstream boundaries."""
3import json
4from collections.abc import AsyncIterator, Iterator
5from contextlib import asynccontextmanager, contextmanager
6from ssl import SSLContext
8import httpx
9from pydantic import ValidationError
11from data_api.core.exceptions import (
12 UpstreamException,
13 UpstreamResponseException,
14 UpstreamTimeoutException,
15)
18@asynccontextmanager
19async def live_client(
20 client: httpx.AsyncClient | None, *, verify: SSLContext | bool = True
21) -> AsyncIterator[httpx.AsyncClient]:
22 """Borrow a runtime client, or own one for a standalone service call."""
23 if client is not None:
24 yield client
25 else:
26 async with httpx.AsyncClient(verify=verify) as owned:
27 yield owned
30@contextmanager
31def upstream_errors() -> Iterator[None]:
32 try:
33 yield
34 except httpx.TimeoutException as exc:
35 raise UpstreamTimeoutException("Live upstream request timed out") from exc
36 except (httpx.HTTPStatusError, httpx.RequestError) as exc:
37 raise UpstreamException("Live upstream request failed") from exc
38 except (json.JSONDecodeError, UnicodeDecodeError, ValidationError) as exc:
39 raise UpstreamResponseException("Live upstream response could not be parsed") from exc