HTTP client¶
BaseHttpClient is a thin, typed base class for
building API clients on top of an aiohttp ClientSession. You subclass it and
add one method per endpoint.
Anatomy¶
from http import HTTPStatus
from types import MappingProxyType
from aiohttp import hdrs
from asyncly import BaseHttpClient, DEFAULT_TIMEOUT, ResponseHandlersType
from asyncly.client.handlers.pydantic import parse_model
from asyncly.client.timeout import TimeoutType
class CatfactClient(BaseHttpClient):
FACT_HANDLERS: ResponseHandlersType = MappingProxyType(
{HTTPStatus.OK: parse_model(CatFact)}
)
async def fetch_fact(self, timeout: TimeoutType = DEFAULT_TIMEOUT) -> CatFact:
return await self._make_req(
method=hdrs.METH_GET,
url=self._url / "fact",
handlers=self.FACT_HANDLERS,
timeout=timeout,
)
Key ideas:
- The session is injected — you create and own the
ClientSession, the client just uses it. This keeps connection pooling, auth, and lifecycle in your control. self._urlis the base URL as ayarl.URL; build endpoint URLs with the/operator.self._make_req(...)issues the request and dispatches the response to a handler chosen by status code.
Construction¶
from aiohttp import ClientSession
async with ClientSession() as session:
client = CatfactClient(
url="https://catfact.ninja", # str or yarl.URL
session=session,
client_name="catfact", # used in metrics and error messages
)
The url property exposes the configured base URL:
Timeouts¶
Every request method takes a timeout of type
TimeoutType — an aiohttp.ClientTimeout, a
datetime.timedelta, or a plain number of seconds. They are all normalized
internally:
from datetime import timedelta
await client.fetch_fact(timeout=2) # 2 seconds
await client.fetch_fact(timeout=2.5) # 2.5 seconds
await client.fetch_fact(timeout=timedelta(seconds=2)) # timedelta
The default is aiohttp's DEFAULT_TIMEOUT, re-exported from the top-level
asyncly package.
Proxy support¶
BaseHttpClient accepts proxy and proxy_auth, set once on the client or
overridden per request. They are forwarded to aiohttp:
from aiohttp import BasicAuth
client = CatfactClient(
url="https://catfact.ninja",
session=session,
client_name="catfact",
proxy="http://127.0.0.1:8080",
proxy_auth=BasicAuth("user", "secret"),
)
See Proxy testing for spinning up a mock proxy to verify a client really routes through one.
Extra request arguments¶
_make_req forwards any extra keyword arguments to
ClientSession.request, so you can pass headers, params, json, data,
etc.:
return await self._make_req(
method=hdrs.METH_POST,
url=self._url / "items",
handlers=self.CREATE_HANDLERS,
json={"name": name},
headers={"X-Tenant": tenant},
)
Error handling¶
If a response status has no matching handler, _make_req raises
UnhandledStatusException,
which carries the status, url, and client_name. Map the statuses you
expect — including error codes — to handlers. See
Response handlers.
Metrics¶
To record request metrics (latency, status, errors), use
InstrumentableHttpClient instead, which adds
enable_metrics() / instrument() on top of the same API.