=========== HTTP Client =========== ``HttpClient`` is the core interface of ``lightreq``. It wraps an adapter and an optional middleware chain into a single unified client for making HTTP requests. .. code-block:: python from src.client import HttpClient from src.middleware import logging, retry client = HttpClient( adapter="requests", middleware=[logging(), retry(retries=3)] ) response = client.get("https://api.example.com/data") client.close() ---- Constructor ----------- .. code-block:: text HttpClient(adapter="urllib", middleware=None, headers=None, **config) **Parameters:** .. list-table:: :header-rows: 1 :widths: 20 15 65 * - Parameter - Type - Description * - ``adapter`` - ``str`` - Adapter to use. One of ``"urllib"`` or ``"requests"``. Defaults to ``"urllib"``. * - ``middleware`` - ``list`` - Ordered list of middleware callables. Defaults to ``None``. * - ``headers`` - ``dict`` - Default headers applied to all requests. Overridden by per-request ``headers`` passed to HTTP methods. Defaults to ``None``. * - ``config`` - ``any`` - Additional configuration passed through to the underlying adapter. **Raises:** ``ValueError`` if the specified adapter name is not recognized. .. code-block:: python # raises ValueError: Adapter httpx not found client = HttpClient(adapter="httpx") .. note:: When the ``timeout`` middleware is present in the middleware list, ``HttpClient`` automatically suppresses the adapter's own timeout parameter to prevent a double-timer conflict. See :doc:`middleware` for details. ---- HTTP Methods ------------ All HTTP methods accept a ``url`` positional argument and optional ``**kwargs`` such as ``headers``, ``data``, and ``timeout``. They all return a :doc:`response` object. Per-request ``headers`` are merged with default headers set in the ``HttpClient`` constructor, where per-request values take precedence over defaults for matching keys. get() ~~~~~ Send a GET request. Used to retrieve a resource without side effects. .. code-block:: python response = client.get("https://api.example.com/todos/1") response = client.get("https://api.example.com/todos/1", headers={"Accept": "application/json"}) **Returns:** ``Response`` ---- post() ~~~~~~ Send a POST request. Used to create a new resource. .. code-block:: python response = client.post( "https://api.example.com/todos", data={"title": "Buy milk", "completed": False} ) **Returns:** ``Response`` ---- put() ~~~~~ Send a PUT request. Used to replace an existing resource entirely. .. code-block:: python response = client.put( "https://api.example.com/todos/1", data={"title": "Buy milk", "completed": True} ) **Returns:** ``Response`` ---- patch() ~~~~~~~ Send a PATCH request. Used to partially update an existing resource. .. code-block:: python response = client.patch( "https://api.example.com/todos/1", data={"completed": True} ) **Returns:** ``Response`` ---- delete() ~~~~~~~~ Send a DELETE request. Used to remove a resource. .. code-block:: python response = client.delete("https://api.example.com/todos/1") **Returns:** ``Response`` ---- head() ~~~~~~ Send a HEAD request. Returns the same headers as a GET request but with no response body. Useful for checking resource existence or retrieving metadata such as ``Content-Length`` without downloading the full body. .. code-block:: python response = client.head("https://api.example.com/todos/1") print(response.status_code) # 200 print(response.headers["content-length"]) # e.g. "83" print(response.body) # b'' (always empty for HEAD) **Returns:** ``Response`` — ``body`` is always ``b""``, headers are preserved. .. note:: Per the HTTP/1.1 specification, HEAD responses must not include a body. ``lightreq`` enforces this by stripping the body before returning the response, while preserving all headers including ``Content-Length``. ---- options() ~~~~~~~~~ Send an OPTIONS request. Used to query which HTTP methods are supported by a resource. .. code-block:: python response = client.options("https://api.example.com/todos") print(response.headers.get("Allow")) # e.g. "GET, POST, HEAD, OPTIONS" **Returns:** ``Response`` ---- close() ------- Closes the client and releases all underlying adapter resources, such as connection pools. .. code-block:: python client.close() Should be called when the client is no longer needed, especially when using the ``"requests"`` adapter which maintains a persistent connection pool. .. code-block:: python # recommended pattern — always close the client after use try: response = client.get("https://api.example.com/data") response.raise_for_status() finally: client.close() .. note:: Not all adapters require explicit cleanup. ``close()`` checks whether the underlying adapter supports it before attempting to close. It is safe to call on any adapter. ---- Usage Patterns -------------- Basic request with error handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python client = HttpClient(adapter="requests") try: response = client.get("https://api.example.com/data") response.raise_for_status() print(response.json()) except Exception as e: print("Request failed:", e) finally: client.close() Passing custom headers ~~~~~~~~~~~~~~~~~~~~~~ Headers can be configured at two levels: 1. **Default headers**: Set via the ``headers`` parameter in the ``HttpClient`` constructor, applied to all requests. 2. **Per-request headers**: Passed as ``headers`` in ``**kwargs`` of any HTTP method. Merged with default headers, with per-request values overriding defaults for matching keys (not replacing all defaults). .. code-block:: python from src.client import HttpClient # Set default headers for all requests client = HttpClient( adapter="requests", headers={"User-Agent": "lightreq/1.0", "Accept": "application/json"} ) # Per-request headers: adds Authorization, overrides Accept for this request only response = client.get( "https://api.example.com/protected", headers={"Authorization": "Bearer ", "Accept": "application/xml"} ) # Default headers are still used for subsequent requests client.get("https://api.example.com/data") # Uses default User-Agent and Accept Sending JSON data ~~~~~~~~~~~~~~~~~ .. code-block:: python response = client.post( "https://api.example.com/todos", data={"title": "Buy milk", "completed": False}, headers={"Content-Type": "application/json"} ) print(response.status_code) # 201 print(response.json()) Using with middleware ~~~~~~~~~~~~~~~~~~~~~ For a full guide on available middlewares, ordering rules, and cookbook recipes, see :doc:`middleware`. .. code-block:: python from src.middleware import logging, retry, timeout, rate_limit client = HttpClient( adapter="requests", middleware=[ logging(), rate_limit(calls=10, period=1.0), retry(retries=3, delay=0.5), timeout(seconds=5.0), ] )