wrapfast 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
wrapfast/__init__.py ADDED
@@ -0,0 +1,19 @@
1
+ """wrapfast — pluggable HTTP client: transport, session, and presentation layers."""
2
+
3
+ from .application import Endpoint, HttpClient
4
+ from .presentation import PresentationCodec
5
+ from .session import Session
6
+ from .transport import AsyncTransport, HttpRequest, HttpResponse, Transport
7
+
8
+ __all__ = [
9
+ "AsyncTransport",
10
+ "Endpoint",
11
+ "HttpClient",
12
+ "HttpRequest",
13
+ "HttpResponse",
14
+ "PresentationCodec",
15
+ "Session",
16
+ "Transport",
17
+ ]
18
+
19
+ __version__ = "0.1.0"
@@ -0,0 +1,43 @@
1
+ from dataclasses import dataclass
2
+
3
+ from .presentation import PresentationCodec
4
+ from .session import Session
5
+ from .transport import HttpRequest, Transport
6
+
7
+
8
+ @dataclass
9
+ class Endpoint[T_Request, T_Response]:
10
+ method: str
11
+ path: str
12
+ request_type: type[T_Request]
13
+ response_type: type[T_Response]
14
+
15
+
16
+ class HttpClient:
17
+ def __init__(
18
+ self,
19
+ base_url: str,
20
+ transport: Transport,
21
+ session: Session,
22
+ presentation_codec: PresentationCodec,
23
+ ) -> None:
24
+ self._base_url = base_url
25
+ self._transport = transport
26
+ self._session = session
27
+ self._presentation_codec = presentation_codec
28
+
29
+ def send[T_Request, T_Response](
30
+ self, endpoint: Endpoint[T_Request, T_Response], request: T_Request
31
+ ) -> T_Response:
32
+ http_req = HttpRequest(
33
+ method=endpoint.method,
34
+ url=f"{self._base_url}{endpoint.path}"
35
+ if not endpoint.path.startswith("/")
36
+ else endpoint.path,
37
+ headers={"Content-Type": self._presentation_codec.get_content_type()},
38
+ data=self._presentation_codec.encode(request),
39
+ )
40
+ http_req = self._session.wrap_request(http_req)
41
+ http_resp = self._transport.send(http_req)
42
+ http_resp = self._session.unwrap_response(http_resp)
43
+ return self._presentation_codec.decode(http_resp.data, endpoint.response_type)
@@ -0,0 +1,13 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+
4
+
5
+ class PresentationCodec(ABC):
6
+ @abstractmethod
7
+ def get_content_type(self) -> str: ...
8
+
9
+ @abstractmethod
10
+ def encode(self, obj: Any) -> bytes: ...
11
+
12
+ @abstractmethod
13
+ def decode(self, data: bytes, target: type) -> Any: ...
wrapfast/py.typed ADDED
File without changes
wrapfast/session.py ADDED
@@ -0,0 +1,11 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from .transport import HttpRequest, HttpResponse
4
+
5
+
6
+ class Session(ABC):
7
+ @abstractmethod
8
+ def wrap_request(self, request: HttpRequest) -> HttpRequest: ...
9
+
10
+ @abstractmethod
11
+ def unwrap_response(self, response: HttpResponse) -> HttpResponse: ...
wrapfast/transport.py ADDED
@@ -0,0 +1,28 @@
1
+ from abc import ABC, abstractmethod
2
+ from dataclasses import dataclass
3
+ from typing import Mapping
4
+
5
+
6
+ @dataclass
7
+ class HttpRequest:
8
+ method: str
9
+ url: str
10
+ headers: Mapping[str, str]
11
+ data: bytes
12
+
13
+
14
+ @dataclass
15
+ class HttpResponse:
16
+ status_code: int
17
+ headers: Mapping[str, str]
18
+ data: bytes
19
+
20
+
21
+ class Transport(ABC):
22
+ @abstractmethod
23
+ def send(self, request: HttpRequest) -> HttpResponse: ...
24
+
25
+
26
+ class AsyncTransport(ABC):
27
+ @abstractmethod
28
+ async def send(self, request: HttpRequest) -> HttpResponse: ...
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: wrapfast
3
+ Version: 0.0.2
4
+ Summary: Composable HTTP API client: pluggable transport, session, and presentation layers for maintainable wrappers.
5
+ Project-URL: Repository, https://github.com/UnknownAPI/wrapfast
6
+ Project-URL: Issues, https://github.com/UnknownAPI/wrapfast/issues
7
+ Author: wrapfast contributors
8
+ License-Expression: 0BSD
9
+ License-File: LICENSE
10
+ Keywords: api,api-client,http,http-client,rest,typing,wrapper
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Internet :: WWW/HTTP
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.13
21
+ Provides-Extra: examples
22
+ Requires-Dist: pydantic>=2; extra == 'examples'
23
+ Requires-Dist: requests>=2.31; extra == 'examples'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # wrapfast
27
+
28
+ **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.
29
+
30
+ 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.
31
+
32
+ ---
33
+
34
+ ## The idea in one glance
35
+
36
+ | Piece | Responsibility |
37
+ |--------|----------------|
38
+ | **`Transport`** | How a request leaves your process and bytes come back (`requests`, `httpx`, a mock, async later). |
39
+ | **`Session`** | Cross‑cutting behaviour around the wire call: tokens, headers, cookies, tracing, optional response handling. |
40
+ | **`PresentationCodec`** | How typed domain objects become bytes and back (JSON + Pydantic, `msgspec`, plain `dict`, …). |
41
+ | **`Endpoint`** | A named operation: HTTP method, path, and the request/response types you expect. |
42
+ | **`HttpClient`** | The thin orchestrator: build `HttpRequest` → session → transport → session → decode. |
43
+
44
+ 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).
45
+
46
+ ---
47
+
48
+ ## Code that shows the shape
49
+
50
+ This is intentionally dense: it is the whole architecture on one screen, using **Pydantic** for request/response models and JSON.
51
+
52
+ ```python
53
+ import requests
54
+ from pydantic import BaseModel, ConfigDict
55
+
56
+ from wrapfast import (
57
+ Endpoint,
58
+ HttpClient,
59
+ HttpRequest,
60
+ HttpResponse,
61
+ PresentationCodec,
62
+ Session,
63
+ Transport,
64
+ )
65
+
66
+
67
+ class User(BaseModel):
68
+ model_config = ConfigDict(extra="ignore")
69
+
70
+ id: int
71
+ name: str
72
+
73
+
74
+ GET_USER = Endpoint("GET", "users/1", type(None), User)
75
+
76
+
77
+ class RequestsTransport(Transport):
78
+ def send(self, request: HttpRequest) -> HttpResponse:
79
+ r = requests.request(
80
+ request.method,
81
+ request.url,
82
+ headers=request.headers,
83
+ data=request.data or None,
84
+ timeout=30,
85
+ )
86
+ return HttpResponse(r.status_code, {k.lower(): v for k, v in r.headers.items()}, r.content)
87
+
88
+
89
+ class BearerSession(Session):
90
+ def __init__(self, token: str) -> None:
91
+ self._token = token
92
+
93
+ def wrap_request(self, request: HttpRequest) -> HttpRequest:
94
+ h = {**request.headers, "authorization": f"Bearer {self._token}"}
95
+ return HttpRequest(request.method, request.url, h, request.data)
96
+
97
+ def unwrap_response(self, response: HttpResponse) -> HttpResponse:
98
+ return response # e.g. 401 → refresh token, logging, metrics
99
+
100
+
101
+ class PydanticJsonCodec(PresentationCodec):
102
+ def get_content_type(self) -> str:
103
+ return "application/json"
104
+
105
+ def encode(self, obj: object) -> bytes:
106
+ if obj is None:
107
+ return b""
108
+ if isinstance(obj, BaseModel):
109
+ return obj.model_dump_json(exclude_none=True).encode("utf-8")
110
+ raise TypeError("encode expects None or a Pydantic model")
111
+
112
+ def decode(self, data: bytes, target: type):
113
+ if not isinstance(target, type) or not issubclass(target, BaseModel):
114
+ raise TypeError("decode target must be a BaseModel subclass")
115
+ return target.model_validate_json(data)
116
+
117
+
118
+ client = HttpClient(
119
+ base_url="https://api.example.com/",
120
+ transport=RequestsTransport(),
121
+ session=BearerSession("<access token>"),
122
+ presentation_codec=PydanticJsonCodec(),
123
+ )
124
+
125
+ user = client.send(GET_USER, None) # User: validated model, not raw JSON
126
+ ```
127
+
128
+ Add **`pydantic`** and **`requests`** to your environment when using this pattern (also bundled as the optional **`examples`** extra in this repo).
129
+
130
+ **`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.
131
+
132
+ `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.
133
+
134
+ ---
135
+
136
+ ## Why it matters
137
+
138
+ - **Tests**: fake `Transport` returns canned `HttpResponse`; no sockets.
139
+ - **Auth**: evolve `Session` (login, refresh, header rules) without touching codecs.
140
+ - **Formats**: swap JSON for another codec at the edge without renaming your domain models’ usage sites.
141
+ - **Readability**: endpoints read like a table of operations; the “how we call HTTP” story lives in a few small classes.
142
+
143
+ ---
144
+
145
+ ## Project layout & example
146
+
147
+ | Path | Role |
148
+ |------|------|
149
+ | `src/wrapfast/` | Installable package: `HttpClient`, protocols, `Endpoint`. |
150
+ | `examples/dummyjson_requests.py` | End‑to‑end sample: `requests`, Pydantic, bearer **session** (login, `/auth/me`, refresh). |
151
+
152
+ 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.
153
+
154
+ ---
155
+
156
+ ## Requirements
157
+
158
+ 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`**.
159
+
160
+ ---
161
+
162
+ ## License
163
+
164
+ This project is released under the [**0BSD**](https://opensource.org/licenses/0BSD) license (see [`LICENSE`](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.
@@ -0,0 +1,10 @@
1
+ wrapfast/__init__.py,sha256=yCDfWEhNJIObr1UXPSTGhkYs6kGrf7r3KqUey9Qspbw,492
2
+ wrapfast/application.py,sha256=_81v0y1hKVrb5eQ3hdCjL2wNfGAxSfh4sMFghwC9a-M,1440
3
+ wrapfast/presentation.py,sha256=liTWdBYae-Vs8Ewvk3IKkKHjociRx6zrqGhRbTuy5nY,314
4
+ wrapfast/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ wrapfast/session.py,sha256=3UH9AZRB64S28cjBTKLs_4GK6Y4LqJrS_cJpyXI1GDk,304
6
+ wrapfast/transport.py,sha256=sUWxONdIYxsNFRrj4Wzp_OKEiuMwSH4xBCXtAg7mbYo,557
7
+ wrapfast-0.0.2.dist-info/METADATA,sha256=ppsmOMQTlQtM6fu07wVjB1oYSXGK0zOUmHztDfdCLvU,7088
8
+ wrapfast-0.0.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
9
+ wrapfast-0.0.2.dist-info/licenses/LICENSE,sha256=wlzGe99oZkutSpDmkYVZPUA3Wsz53o7QURMrn8OqsLo,661
10
+ wrapfast-0.0.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,12 @@
1
+ Copyright (c) 2026 wrapfast contributors
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any
4
+ purpose with or without fee is hereby granted.
5
+
6
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
7
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
8
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
9
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
10
+ OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
11
+ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
12
+ THIS SOFTWARE.