Source code for action0.client.client

"""
The generic HTTP client (:py:class:`Client`): one class, any backend —
the return type of :py:meth:`Client.send` is exactly what the given
backend's ``send`` returns.
"""

from typing import Generic

from action0.req import Request

from .backend import Backend
from .backend import SendResultT_co


[docs] class Client(Generic[SendResultT_co]): """ A thin, fully typed facade over a backend: ``Client(backend).send(request)`` sends a raw :py:class:`~action0.req.request.Request` and returns the :py:class:`~action0.req.response.Response` in whatever wrapper the backend's execution model dictates. The wrapper type is *derived from the backend* (the class is generic over :py:data:`~action0.client.backend.SendResultT_co`), not enumerated anywhere — so this works for any execution model, including ones this library has never heard of: - ``Client(RequestsBackend()).send(request)`` is a ``Response``, - ``await Client(AsyncHttpxBackend()).send(request)`` is a ``Response``, - ``Client(TwistedBackend()).send(request)`` is a ``Deferred[Response]``, - with your own ``Backend[SomeWrapper[Response]]``, ``send`` returns a ``SomeWrapper[Response]``. For talking to a specific API with typed operations, use :py:class:`~action0.client.api.APIClient` instead — this class is the raw-request building block. Example (with the test-double backend standing in for a real one):: >>> from action0.client.testing import StubBackend >>> from action0.req import Request, Response >>> >>> client = Client(StubBackend(Response(204))) >>> client.send(Request("https://api.example.com/ping")).status 204 The backend decides the execution model, the code stays the same:: >>> import asyncio >>> from action0.client.testing import AsyncStubBackend >>> >>> client = Client(AsyncStubBackend(Response(204))) >>> asyncio.run(client.send(Request("https://api.example.com/ping"))).status 204 """ def __init__(self, backend: Backend[SendResultT_co]) -> None: """ :param backend: the backend performing the HTTP I/O — any implementation of the :py:class:`~action0.client.backend.Backend` protocol, whatever its execution model """ self._backend = backend @property def backend(self) -> Backend[SendResultT_co]: """The backend this client sends through, as the :py:class:`~action0.client.backend.Backend` protocol. (The client is generic over the backend's *wrapper type*, not its concrete class — keep your own reference for backend-specific API like ``close()``.)""" return self._backend
[docs] def send(self, request: Request) -> SendResultT_co: """ Send the request through the backend. :param request: the request to send :return: exactly what the backend's ``send`` returns: the response, wrapped according to the backend's execution model — the plain :py:class:`~action0.req.response.Response` for a sync backend, an ``Awaitable[Response]`` for an async backend, a ``Deferred[Response]`` for a Twisted backend :raises action0.client.errors.TransportError: if no response could be obtained (async-style backends deliver the error through their wrapper instead of raising here) """ return self._backend.send(request)
[docs] def __repr__(self) -> str: """ :return: the client with its backend, e.g. ``Client(StubBackend(0 requests))`` """ return f"{self.__class__.__name__}({self._backend!r})"