Skip to main content

Composable HTTP API client: pluggable transport, session, and presentation layers for maintainable wrappers.

Project description

wrapfast

wrapfast is a small Python library with a big opinion: API clients stay maintainable when you separate concerns instead of growing a single “do everything” class.

It exists to promote good practice, clear organisation, and real flexibility when you wrap REST (or HTTP-shaped) APIs in Python. You compose a pipeline from a few roles—each one easy to test, swap, or extend—instead of hard‑coding requests.get next to auth logic next to JSON parsing next to URL strings scattered across the codebase.


The idea in one glance

Piece Responsibility
Transport How a request leaves your process and bytes come back (requests, httpx, a mock, async later).
Session Cross‑cutting behaviour around the wire call: tokens, headers, cookies, tracing, optional response handling.
PresentationCodec How typed domain objects become bytes and back (JSON + Pydantic, msgspec, plain dict, …).
Endpoint A named operation: HTTP method, path, and the request/response types you expect.
HttpClient The thin orchestrator: build HttpRequest → session → transport → session → decode.

That split is the point: organisation (each type has one job), good practice (test transports and codecs without the network; test sessions without JSON details), and flexibility (change transport or codec without rewriting your endpoints).


Code that shows the shape

This is intentionally dense: it is the whole architecture on one screen, using Pydantic for request/response models and JSON.

import requests
from pydantic import BaseModel, ConfigDict

from wrapfast import (
    Endpoint,
    HttpClient,
    HttpRequest,
    HttpResponse,
    PresentationCodec,
    Session,
    Transport,
)


class User(BaseModel):
    model_config = ConfigDict(extra="ignore")

    id: int
    name: str


GET_USER = Endpoint("GET", "users/1", type(None), User)


class RequestsTransport(Transport):
    def send(self, request: HttpRequest) -> HttpResponse:
        r = requests.request(
            request.method,
            request.url,
            headers=request.headers,
            data=request.data or None,
            timeout=30,
        )
        return HttpResponse(r.status_code, {k.lower(): v for k, v in r.headers.items()}, r.content)


class BearerSession(Session):
    def __init__(self, token: str) -> None:
        self._token = token

    def wrap_request(self, request: HttpRequest) -> HttpRequest:
        h = {**request.headers, "authorization": f"Bearer {self._token}"}
        return HttpRequest(request.method, request.url, h, request.data)

    def unwrap_response(self, response: HttpResponse) -> HttpResponse:
        return response  # e.g. 401 → refresh token, logging, metrics


class PydanticJsonCodec(PresentationCodec):
    def get_content_type(self) -> str:
        return "application/json"

    def encode(self, obj: object) -> bytes:
        if obj is None:
            return b""
        if isinstance(obj, BaseModel):
            return obj.model_dump_json(exclude_none=True).encode("utf-8")
        raise TypeError("encode expects None or a Pydantic model")

    def decode(self, data: bytes, target: type):
        if not isinstance(target, type) or not issubclass(target, BaseModel):
            raise TypeError("decode target must be a BaseModel subclass")
        return target.model_validate_json(data)


client = HttpClient(
    base_url="https://api.example.com/",
    transport=RequestsTransport(),
    session=BearerSession("<access token>"),
    presentation_codec=PydanticJsonCodec(),
)

user = client.send(GET_USER, None)  # User: validated model, not raw JSON

Add pydantic and requests to your environment when using this pattern (also bundled as the optional examples extra in this repo).

HttpClient is the spine: it does not know which HTTP library you use, how you authenticate, or how bodies are serialised. Those are policies you inject. Your API surface becomes a set of Endpoint values plus Pydantic models (or other types you teach the codec)—easier to read, review, and reuse.

PresentationCodec, Transport, Session, and async AsyncTransport are abstract bases (abc.ABC). Codecs implement get_content_type() (used for the outbound Content-Type header), encode, and decode; transports and sessions implement the send / wrap_request / unwrap_response hooks shown above.


Why it matters

  • Tests: fake Transport returns canned HttpResponse; no sockets.
  • Auth: evolve Session (login, refresh, header rules) without touching codecs.
  • Formats: swap JSON for another codec at the edge without renaming your domain models’ usage sites.
  • Readability: endpoints read like a table of operations; the “how we call HTTP” story lives in a few small classes.

Project layout & example

Path Role
src/wrapfast/ Installable package: HttpClient, protocols, Endpoint.
examples/dummyjson_requests.py End‑to‑end sample: requests, Pydantic, bearer session (login, /auth/me, refresh).

After pip install wrapfast, use import wrapfast. From a clone without installing, add the src directory to PYTHONPATH (see examples/dummyjson_requests.py). A fuller DummyJSON walkthrough lives in that example.


Requirements

Python 3.13+ (see pyproject.toml). The library itself has no required runtime dependencies; pair it with your transport and codec. The README snippet and examples extra use Pydantic and requests.


License

This project is released under the 0BSD license (see LICENSE): use it for anything, with no attribution requirement and minimal legal boilerplate. It is one of the most permissive widely used open-source terms for software.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wrapfast-0.0.2.tar.gz (20.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wrapfast-0.0.2-py3-none-any.whl (6.7 kB view details)

Uploaded Python 3

File details

Details for the file wrapfast-0.0.2.tar.gz.

File metadata

  • Download URL: wrapfast-0.0.2.tar.gz
  • Upload date:
  • Size: 20.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for wrapfast-0.0.2.tar.gz
Algorithm Hash digest
SHA256 e10a3e70268f89107b99d79d24b0b8d4ffaa80219fe30b666fe2dfb8cc457c5c
MD5 c6e68b145c69c2c72196c44f4120121a
BLAKE2b-256 88418037b49e524afa2db29aaf7bacbc718edc52141445b7bff7868c0e5b8eca

See more details on using hashes here.

File details

Details for the file wrapfast-0.0.2-py3-none-any.whl.

File metadata

  • Download URL: wrapfast-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 6.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for wrapfast-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 306f66e33dc8febbaf3febb478477e69422c441e4abe4414be1ca115f9abf706
MD5 e4f9276e622af114d5ec0d8be5b2da56
BLAKE2b-256 380413c937d1f1af69c12ee3cc2a3e316528df8919705f816ba636bf78afd396

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page