wrapfast 0.0.2__tar.gz
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-0.0.2/.gitignore +10 -0
- wrapfast-0.0.2/.python-version +1 -0
- wrapfast-0.0.2/LICENSE +12 -0
- wrapfast-0.0.2/PKG-INFO +164 -0
- wrapfast-0.0.2/README.md +139 -0
- wrapfast-0.0.2/examples/dummyjson_requests.py +267 -0
- wrapfast-0.0.2/pyproject.toml +41 -0
- wrapfast-0.0.2/src/wrapfast/__init__.py +19 -0
- wrapfast-0.0.2/src/wrapfast/application.py +43 -0
- wrapfast-0.0.2/src/wrapfast/presentation.py +13 -0
- wrapfast-0.0.2/src/wrapfast/py.typed +0 -0
- wrapfast-0.0.2/src/wrapfast/session.py +11 -0
- wrapfast-0.0.2/src/wrapfast/transport.py +28 -0
- wrapfast-0.0.2/uv.lock +221 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
wrapfast-0.0.2/LICENSE
ADDED
|
@@ -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.
|
wrapfast-0.0.2/PKG-INFO
ADDED
|
@@ -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.
|
wrapfast-0.0.2/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# wrapfast
|
|
2
|
+
|
|
3
|
+
**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.
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## The idea in one glance
|
|
10
|
+
|
|
11
|
+
| Piece | Responsibility |
|
|
12
|
+
|--------|----------------|
|
|
13
|
+
| **`Transport`** | How a request leaves your process and bytes come back (`requests`, `httpx`, a mock, async later). |
|
|
14
|
+
| **`Session`** | Cross‑cutting behaviour around the wire call: tokens, headers, cookies, tracing, optional response handling. |
|
|
15
|
+
| **`PresentationCodec`** | How typed domain objects become bytes and back (JSON + Pydantic, `msgspec`, plain `dict`, …). |
|
|
16
|
+
| **`Endpoint`** | A named operation: HTTP method, path, and the request/response types you expect. |
|
|
17
|
+
| **`HttpClient`** | The thin orchestrator: build `HttpRequest` → session → transport → session → decode. |
|
|
18
|
+
|
|
19
|
+
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).
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Code that shows the shape
|
|
24
|
+
|
|
25
|
+
This is intentionally dense: it is the whole architecture on one screen, using **Pydantic** for request/response models and JSON.
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import requests
|
|
29
|
+
from pydantic import BaseModel, ConfigDict
|
|
30
|
+
|
|
31
|
+
from wrapfast import (
|
|
32
|
+
Endpoint,
|
|
33
|
+
HttpClient,
|
|
34
|
+
HttpRequest,
|
|
35
|
+
HttpResponse,
|
|
36
|
+
PresentationCodec,
|
|
37
|
+
Session,
|
|
38
|
+
Transport,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class User(BaseModel):
|
|
43
|
+
model_config = ConfigDict(extra="ignore")
|
|
44
|
+
|
|
45
|
+
id: int
|
|
46
|
+
name: str
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
GET_USER = Endpoint("GET", "users/1", type(None), User)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class RequestsTransport(Transport):
|
|
53
|
+
def send(self, request: HttpRequest) -> HttpResponse:
|
|
54
|
+
r = requests.request(
|
|
55
|
+
request.method,
|
|
56
|
+
request.url,
|
|
57
|
+
headers=request.headers,
|
|
58
|
+
data=request.data or None,
|
|
59
|
+
timeout=30,
|
|
60
|
+
)
|
|
61
|
+
return HttpResponse(r.status_code, {k.lower(): v for k, v in r.headers.items()}, r.content)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class BearerSession(Session):
|
|
65
|
+
def __init__(self, token: str) -> None:
|
|
66
|
+
self._token = token
|
|
67
|
+
|
|
68
|
+
def wrap_request(self, request: HttpRequest) -> HttpRequest:
|
|
69
|
+
h = {**request.headers, "authorization": f"Bearer {self._token}"}
|
|
70
|
+
return HttpRequest(request.method, request.url, h, request.data)
|
|
71
|
+
|
|
72
|
+
def unwrap_response(self, response: HttpResponse) -> HttpResponse:
|
|
73
|
+
return response # e.g. 401 → refresh token, logging, metrics
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class PydanticJsonCodec(PresentationCodec):
|
|
77
|
+
def get_content_type(self) -> str:
|
|
78
|
+
return "application/json"
|
|
79
|
+
|
|
80
|
+
def encode(self, obj: object) -> bytes:
|
|
81
|
+
if obj is None:
|
|
82
|
+
return b""
|
|
83
|
+
if isinstance(obj, BaseModel):
|
|
84
|
+
return obj.model_dump_json(exclude_none=True).encode("utf-8")
|
|
85
|
+
raise TypeError("encode expects None or a Pydantic model")
|
|
86
|
+
|
|
87
|
+
def decode(self, data: bytes, target: type):
|
|
88
|
+
if not isinstance(target, type) or not issubclass(target, BaseModel):
|
|
89
|
+
raise TypeError("decode target must be a BaseModel subclass")
|
|
90
|
+
return target.model_validate_json(data)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
client = HttpClient(
|
|
94
|
+
base_url="https://api.example.com/",
|
|
95
|
+
transport=RequestsTransport(),
|
|
96
|
+
session=BearerSession("<access token>"),
|
|
97
|
+
presentation_codec=PydanticJsonCodec(),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
user = client.send(GET_USER, None) # User: validated model, not raw JSON
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Add **`pydantic`** and **`requests`** to your environment when using this pattern (also bundled as the optional **`examples`** extra in this repo).
|
|
104
|
+
|
|
105
|
+
**`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.
|
|
106
|
+
|
|
107
|
+
`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.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Why it matters
|
|
112
|
+
|
|
113
|
+
- **Tests**: fake `Transport` returns canned `HttpResponse`; no sockets.
|
|
114
|
+
- **Auth**: evolve `Session` (login, refresh, header rules) without touching codecs.
|
|
115
|
+
- **Formats**: swap JSON for another codec at the edge without renaming your domain models’ usage sites.
|
|
116
|
+
- **Readability**: endpoints read like a table of operations; the “how we call HTTP” story lives in a few small classes.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Project layout & example
|
|
121
|
+
|
|
122
|
+
| Path | Role |
|
|
123
|
+
|------|------|
|
|
124
|
+
| `src/wrapfast/` | Installable package: `HttpClient`, protocols, `Endpoint`. |
|
|
125
|
+
| `examples/dummyjson_requests.py` | End‑to‑end sample: `requests`, Pydantic, bearer **session** (login, `/auth/me`, refresh). |
|
|
126
|
+
|
|
127
|
+
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.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Requirements
|
|
132
|
+
|
|
133
|
+
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`**.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
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,267 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Example: wrapfast HttpClient + DummyJSON (https://dummyjson.com) using `requests`,
|
|
3
|
+
Pydantic models, and a **Session** that injects ``Authorization: Bearer …`` after login
|
|
4
|
+
and updates tokens after refresh.
|
|
5
|
+
|
|
6
|
+
Install from the repo root::
|
|
7
|
+
|
|
8
|
+
pip install requests pydantic
|
|
9
|
+
# or: uv venv && uv sync --extra examples
|
|
10
|
+
|
|
11
|
+
Run::
|
|
12
|
+
|
|
13
|
+
python examples/dummyjson_requests.py
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from urllib.parse import urlparse
|
|
21
|
+
|
|
22
|
+
import requests
|
|
23
|
+
from pydantic import BaseModel, ConfigDict
|
|
24
|
+
|
|
25
|
+
# Repo layout: package lives under src/wrapfast/ (add ``src`` so ``import wrapfast`` works without install).
|
|
26
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
|
27
|
+
|
|
28
|
+
from wrapfast import (
|
|
29
|
+
Endpoint,
|
|
30
|
+
HttpClient,
|
|
31
|
+
HttpRequest,
|
|
32
|
+
HttpResponse,
|
|
33
|
+
PresentationCodec,
|
|
34
|
+
Session,
|
|
35
|
+
Transport,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _url_resource_path(url: str) -> str:
|
|
40
|
+
"""``https://dummyjson.com/auth/me`` -> ``auth/me``."""
|
|
41
|
+
return urlparse(url).path.lstrip("/")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class RequestsTransport(Transport):
|
|
45
|
+
"""Bridge wrapfast's HttpRequest/HttpResponse to the `requests` library."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, timeout: float = 30.0) -> None:
|
|
48
|
+
self._timeout = timeout
|
|
49
|
+
|
|
50
|
+
def send(self, request: HttpRequest) -> HttpResponse:
|
|
51
|
+
resp = requests.request(
|
|
52
|
+
method=request.method,
|
|
53
|
+
url=request.url,
|
|
54
|
+
headers=request.headers,
|
|
55
|
+
data=request.data or None,
|
|
56
|
+
timeout=self._timeout,
|
|
57
|
+
)
|
|
58
|
+
return HttpResponse(
|
|
59
|
+
status_code=resp.status_code,
|
|
60
|
+
headers={k.lower(): v for k, v in resp.headers.items()},
|
|
61
|
+
data=resp.content,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# --- DummyJSON shapes (subset of real responses; extra JSON keys ignored) ---
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class LoginRequest(BaseModel):
|
|
69
|
+
model_config = ConfigDict(extra="forbid")
|
|
70
|
+
|
|
71
|
+
username: str
|
|
72
|
+
password: str
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class LoginResponse(BaseModel):
|
|
76
|
+
model_config = ConfigDict(extra="ignore")
|
|
77
|
+
|
|
78
|
+
id: int
|
|
79
|
+
username: str
|
|
80
|
+
email: str
|
|
81
|
+
firstName: str
|
|
82
|
+
lastName: str
|
|
83
|
+
gender: str
|
|
84
|
+
image: str
|
|
85
|
+
accessToken: str
|
|
86
|
+
refreshToken: str
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class MeResponse(BaseModel):
|
|
90
|
+
"""Subset of ``GET /auth/me`` (current user)."""
|
|
91
|
+
|
|
92
|
+
model_config = ConfigDict(extra="ignore")
|
|
93
|
+
|
|
94
|
+
id: int
|
|
95
|
+
username: str
|
|
96
|
+
email: str
|
|
97
|
+
firstName: str
|
|
98
|
+
lastName: str
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class RefreshRequest(BaseModel):
|
|
102
|
+
model_config = ConfigDict(extra="forbid")
|
|
103
|
+
|
|
104
|
+
refreshToken: str
|
|
105
|
+
expiresInMins: int | None = None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class RefreshResponse(BaseModel):
|
|
109
|
+
model_config = ConfigDict(extra="ignore")
|
|
110
|
+
|
|
111
|
+
accessToken: str
|
|
112
|
+
refreshToken: str
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class Product(BaseModel):
|
|
116
|
+
model_config = ConfigDict(extra="ignore")
|
|
117
|
+
|
|
118
|
+
id: int
|
|
119
|
+
title: str
|
|
120
|
+
description: str | None = None
|
|
121
|
+
price: float | None = None
|
|
122
|
+
brand: str | None = None
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class DummyJsonBearerSession(Session):
|
|
126
|
+
"""Session that attaches a bearer access token to outbound requests.
|
|
127
|
+
|
|
128
|
+
DummyJSON expects ``Authorization: Bearer <accessToken>`` for endpoints such as
|
|
129
|
+
``GET /auth/me``. Password login must **not** send a stale bearer token, so
|
|
130
|
+
``POST /auth/login`` is excluded from injection.
|
|
131
|
+
|
|
132
|
+
Use :meth:`bind_login` after a successful login and :meth:`apply_refresh` after
|
|
133
|
+
``POST /auth/refresh`` so subsequent calls use the latest tokens.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
#: Path prefixes that must not receive ``Authorization`` (password login).
|
|
137
|
+
_no_bearer_paths: frozenset[str] = frozenset({"auth/login"})
|
|
138
|
+
|
|
139
|
+
def __init__(self) -> None:
|
|
140
|
+
self._access_token: str | None = None
|
|
141
|
+
self._refresh_token: str | None = None
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def access_token(self) -> str | None:
|
|
145
|
+
return self._access_token
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def refresh_token(self) -> str | None:
|
|
149
|
+
return self._refresh_token
|
|
150
|
+
|
|
151
|
+
def bind_login(self, login: LoginResponse) -> None:
|
|
152
|
+
"""Store tokens from ``POST /auth/login``."""
|
|
153
|
+
self._access_token = login.accessToken
|
|
154
|
+
self._refresh_token = login.refreshToken
|
|
155
|
+
|
|
156
|
+
def apply_refresh(self, tokens: RefreshResponse) -> None:
|
|
157
|
+
"""Replace tokens after ``POST /auth/refresh``."""
|
|
158
|
+
self._access_token = tokens.accessToken
|
|
159
|
+
self._refresh_token = tokens.refreshToken
|
|
160
|
+
|
|
161
|
+
def clear(self) -> None:
|
|
162
|
+
"""Drop tokens (e.g. logout)."""
|
|
163
|
+
self._access_token = None
|
|
164
|
+
self._refresh_token = None
|
|
165
|
+
|
|
166
|
+
def wrap_request(self, request: HttpRequest) -> HttpRequest:
|
|
167
|
+
path = _url_resource_path(request.url)
|
|
168
|
+
if self._access_token is None or path in self._no_bearer_paths:
|
|
169
|
+
return request
|
|
170
|
+
headers = {**request.headers, "authorization": f"Bearer {self._access_token}"}
|
|
171
|
+
return HttpRequest(
|
|
172
|
+
method=request.method,
|
|
173
|
+
url=request.url,
|
|
174
|
+
headers=headers,
|
|
175
|
+
data=request.data,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def unwrap_response(self, response: HttpResponse) -> HttpResponse:
|
|
179
|
+
"""Hook for logging, retry, or error mapping; pass-through here."""
|
|
180
|
+
return response
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class PydanticJsonCodec(PresentationCodec):
|
|
184
|
+
"""JSON codec using Pydantic ``model_dump_json`` / ``model_validate_json``."""
|
|
185
|
+
|
|
186
|
+
def get_content_type(self) -> str:
|
|
187
|
+
return "application/json"
|
|
188
|
+
|
|
189
|
+
def encode(self, obj: object) -> bytes:
|
|
190
|
+
if obj is None:
|
|
191
|
+
return b""
|
|
192
|
+
if isinstance(obj, BaseModel):
|
|
193
|
+
return obj.model_dump_json(exclude_none=True, by_alias=True).encode("utf-8")
|
|
194
|
+
raise TypeError(
|
|
195
|
+
f"PydanticJsonCodec.encode expects None or BaseModel, got {type(obj).__name__}"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def decode(self, data: bytes, target: type):
|
|
199
|
+
if not isinstance(target, type) or not issubclass(target, BaseModel):
|
|
200
|
+
raise TypeError(
|
|
201
|
+
f"PydanticJsonCodec.decode expects a BaseModel subclass, got {target!r}"
|
|
202
|
+
)
|
|
203
|
+
return target.model_validate_json(data)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# Paths must not start with "/" if ``base_url`` ends with "/" (see HttpClient URL rules).
|
|
207
|
+
AUTH_LOGIN = Endpoint("POST", "auth/login", LoginRequest, LoginResponse)
|
|
208
|
+
AUTH_ME = Endpoint("GET", "auth/me", type(None), MeResponse)
|
|
209
|
+
AUTH_REFRESH = Endpoint("POST", "auth/refresh", RefreshRequest, RefreshResponse)
|
|
210
|
+
PRODUCT_BY_ID = Endpoint("GET", "products/1", type(None), Product)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def main() -> None:
|
|
214
|
+
base_url = "https://dummyjson.com/"
|
|
215
|
+
session = DummyJsonBearerSession()
|
|
216
|
+
codec = PydanticJsonCodec()
|
|
217
|
+
client = HttpClient(
|
|
218
|
+
base_url=base_url,
|
|
219
|
+
transport=RequestsTransport(),
|
|
220
|
+
session=session,
|
|
221
|
+
presentation_codec=codec,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Public data: no bearer required (session has no token yet).
|
|
225
|
+
product = client.send(PRODUCT_BY_ID, None)
|
|
226
|
+
print("1) GET /products/1 (no session token) ->", product.title)
|
|
227
|
+
|
|
228
|
+
# Login: session skips Authorization on auth/login (see wrap_request).
|
|
229
|
+
logged_in = client.send(
|
|
230
|
+
AUTH_LOGIN,
|
|
231
|
+
LoginRequest(username="emilys", password="emilyspass"),
|
|
232
|
+
)
|
|
233
|
+
session.bind_login(logged_in)
|
|
234
|
+
print("2) POST /auth/login -> tokens stored on session")
|
|
235
|
+
|
|
236
|
+
# Authenticated route: HttpClient builds the request; session adds Bearer.
|
|
237
|
+
me = client.send(AUTH_ME, None)
|
|
238
|
+
print(
|
|
239
|
+
"3) GET /auth/me (Bearer from session) ->",
|
|
240
|
+
me.firstName,
|
|
241
|
+
me.lastName,
|
|
242
|
+
f"<{me.email}>",
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Refresh: body carries refreshToken; session still adds current access Bearer.
|
|
246
|
+
refreshed = client.send(
|
|
247
|
+
AUTH_REFRESH,
|
|
248
|
+
RefreshRequest(refreshToken=session.refresh_token or ""),
|
|
249
|
+
)
|
|
250
|
+
session.apply_refresh(refreshed)
|
|
251
|
+
print("4) POST /auth/refresh -> session updated with new access + refresh tokens")
|
|
252
|
+
|
|
253
|
+
me_again = client.send(AUTH_ME, None)
|
|
254
|
+
print(
|
|
255
|
+
"5) GET /auth/me again (new bearer) -> still",
|
|
256
|
+
me_again.username,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
session.clear()
|
|
260
|
+
print(
|
|
261
|
+
"6) session.clear() - without new login, /auth/me would return 401 "
|
|
262
|
+
"and decoding would fail unless you handle errors."
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
if __name__ == "__main__":
|
|
267
|
+
main()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.26.0"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "wrapfast"
|
|
7
|
+
version = "0.0.2"
|
|
8
|
+
description = "Composable HTTP API client: pluggable transport, session, and presentation layers for maintainable wrappers."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.13"
|
|
11
|
+
license = "0BSD"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "wrapfast contributors" }]
|
|
14
|
+
keywords = [
|
|
15
|
+
"api",
|
|
16
|
+
"api-client",
|
|
17
|
+
"http",
|
|
18
|
+
"http-client",
|
|
19
|
+
"rest",
|
|
20
|
+
"typing",
|
|
21
|
+
"wrapper",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 4 - Beta",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
30
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
31
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
32
|
+
"Typing :: Typed",
|
|
33
|
+
]
|
|
34
|
+
dependencies = []
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Repository = "https://github.com/UnknownAPI/wrapfast"
|
|
38
|
+
Issues = "https://github.com/UnknownAPI/wrapfast/issues"
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
examples = ["pydantic>=2", "requests>=2.31"]
|
|
@@ -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: ...
|
|
File without changes
|
|
@@ -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: ...
|
|
@@ -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: ...
|
wrapfast-0.0.2/uv.lock
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 3
|
|
3
|
+
requires-python = ">=3.13"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "annotated-types"
|
|
7
|
+
version = "0.7.0"
|
|
8
|
+
source = { registry = "https://pypi.org/simple" }
|
|
9
|
+
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
|
|
10
|
+
wheels = [
|
|
11
|
+
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[[package]]
|
|
15
|
+
name = "certifi"
|
|
16
|
+
version = "2026.4.22"
|
|
17
|
+
source = { registry = "https://pypi.org/simple" }
|
|
18
|
+
sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" }
|
|
19
|
+
wheels = [
|
|
20
|
+
{ url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" },
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[[package]]
|
|
24
|
+
name = "charset-normalizer"
|
|
25
|
+
version = "3.4.7"
|
|
26
|
+
source = { registry = "https://pypi.org/simple" }
|
|
27
|
+
sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" }
|
|
28
|
+
wheels = [
|
|
29
|
+
{ url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" },
|
|
30
|
+
{ url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" },
|
|
31
|
+
{ url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" },
|
|
32
|
+
{ url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" },
|
|
33
|
+
{ url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" },
|
|
34
|
+
{ url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" },
|
|
35
|
+
{ url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" },
|
|
36
|
+
{ url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" },
|
|
37
|
+
{ url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" },
|
|
38
|
+
{ url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" },
|
|
39
|
+
{ url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" },
|
|
40
|
+
{ url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" },
|
|
41
|
+
{ url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" },
|
|
42
|
+
{ url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" },
|
|
43
|
+
{ url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" },
|
|
44
|
+
{ url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" },
|
|
45
|
+
{ url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" },
|
|
46
|
+
{ url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" },
|
|
47
|
+
{ url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" },
|
|
48
|
+
{ url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" },
|
|
49
|
+
{ url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" },
|
|
50
|
+
{ url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" },
|
|
51
|
+
{ url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" },
|
|
52
|
+
{ url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" },
|
|
53
|
+
{ url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" },
|
|
54
|
+
{ url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" },
|
|
55
|
+
{ url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" },
|
|
56
|
+
{ url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" },
|
|
57
|
+
{ url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" },
|
|
58
|
+
{ url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" },
|
|
59
|
+
{ url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" },
|
|
60
|
+
{ url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" },
|
|
61
|
+
{ url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" },
|
|
62
|
+
{ url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" },
|
|
63
|
+
{ url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" },
|
|
64
|
+
{ url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" },
|
|
65
|
+
{ url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" },
|
|
66
|
+
{ url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" },
|
|
67
|
+
{ url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" },
|
|
68
|
+
{ url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" },
|
|
69
|
+
{ url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" },
|
|
70
|
+
{ url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" },
|
|
71
|
+
{ url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" },
|
|
72
|
+
{ url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" },
|
|
73
|
+
{ url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" },
|
|
74
|
+
{ url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" },
|
|
75
|
+
{ url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" },
|
|
76
|
+
{ url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" },
|
|
77
|
+
{ url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" },
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
[[package]]
|
|
81
|
+
name = "idna"
|
|
82
|
+
version = "3.14"
|
|
83
|
+
source = { registry = "https://pypi.org/simple" }
|
|
84
|
+
sdist = { url = "https://files.pythonhosted.org/packages/05/b1/efac073e0c297ecf2fb33c346989a529d4e19164f1759102dee5953ee17e/idna-3.14.tar.gz", hash = "sha256:466d810d7a2cc1022bea9b037c39728d51ae7dad40d480fc9b7d7ecf98ba8ee3", size = 198272, upload-time = "2026-05-10T20:32:15.935Z" }
|
|
85
|
+
wheels = [
|
|
86
|
+
{ url = "https://files.pythonhosted.org/packages/6c/3c/3f62dee257eb3d6b2c1ef2a09d36d9793c7111156a73b5654d2c2305e5ce/idna-3.14-py3-none-any.whl", hash = "sha256:e677eaf072e290f7b725f9acf0b3a2bd55f9fd6f7c70abe5f0e34823d0accf69", size = 72184, upload-time = "2026-05-10T20:32:14.295Z" },
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
[[package]]
|
|
90
|
+
name = "pydantic"
|
|
91
|
+
version = "2.13.4"
|
|
92
|
+
source = { registry = "https://pypi.org/simple" }
|
|
93
|
+
dependencies = [
|
|
94
|
+
{ name = "annotated-types" },
|
|
95
|
+
{ name = "pydantic-core" },
|
|
96
|
+
{ name = "typing-extensions" },
|
|
97
|
+
{ name = "typing-inspection" },
|
|
98
|
+
]
|
|
99
|
+
sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" }
|
|
100
|
+
wheels = [
|
|
101
|
+
{ url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" },
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
[[package]]
|
|
105
|
+
name = "pydantic-core"
|
|
106
|
+
version = "2.46.4"
|
|
107
|
+
source = { registry = "https://pypi.org/simple" }
|
|
108
|
+
dependencies = [
|
|
109
|
+
{ name = "typing-extensions" },
|
|
110
|
+
]
|
|
111
|
+
sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" }
|
|
112
|
+
wheels = [
|
|
113
|
+
{ url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" },
|
|
114
|
+
{ url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" },
|
|
115
|
+
{ url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" },
|
|
116
|
+
{ url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" },
|
|
117
|
+
{ url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" },
|
|
118
|
+
{ url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" },
|
|
119
|
+
{ url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" },
|
|
120
|
+
{ url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" },
|
|
121
|
+
{ url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" },
|
|
122
|
+
{ url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" },
|
|
123
|
+
{ url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" },
|
|
124
|
+
{ url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" },
|
|
125
|
+
{ url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" },
|
|
126
|
+
{ url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" },
|
|
127
|
+
{ url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" },
|
|
128
|
+
{ url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" },
|
|
129
|
+
{ url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" },
|
|
130
|
+
{ url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" },
|
|
131
|
+
{ url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" },
|
|
132
|
+
{ url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" },
|
|
133
|
+
{ url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" },
|
|
134
|
+
{ url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" },
|
|
135
|
+
{ url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" },
|
|
136
|
+
{ url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" },
|
|
137
|
+
{ url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" },
|
|
138
|
+
{ url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" },
|
|
139
|
+
{ url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" },
|
|
140
|
+
{ url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" },
|
|
141
|
+
{ url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" },
|
|
142
|
+
{ url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" },
|
|
143
|
+
{ url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" },
|
|
144
|
+
{ url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" },
|
|
145
|
+
{ url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" },
|
|
146
|
+
{ url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" },
|
|
147
|
+
{ url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" },
|
|
148
|
+
{ url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" },
|
|
149
|
+
{ url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" },
|
|
150
|
+
{ url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" },
|
|
151
|
+
{ url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" },
|
|
152
|
+
{ url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" },
|
|
153
|
+
{ url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" },
|
|
154
|
+
{ url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" },
|
|
155
|
+
{ url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" },
|
|
156
|
+
{ url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" },
|
|
157
|
+
{ url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" },
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
[[package]]
|
|
161
|
+
name = "requests"
|
|
162
|
+
version = "2.34.0"
|
|
163
|
+
source = { registry = "https://pypi.org/simple" }
|
|
164
|
+
dependencies = [
|
|
165
|
+
{ name = "certifi" },
|
|
166
|
+
{ name = "charset-normalizer" },
|
|
167
|
+
{ name = "idna" },
|
|
168
|
+
{ name = "urllib3" },
|
|
169
|
+
]
|
|
170
|
+
sdist = { url = "https://files.pythonhosted.org/packages/43/b8/7a707d60fea4c49094e40262cc0e2ca6c768cca21587e34d3f705afec47e/requests-2.34.0.tar.gz", hash = "sha256:7d62fe92f50eb82c529b0916bb445afa1531a566fc8f35ffdc64446e771b856a", size = 142436, upload-time = "2026-05-11T19:29:51.717Z" }
|
|
171
|
+
wheels = [
|
|
172
|
+
{ url = "https://files.pythonhosted.org/packages/ef/e6/e300fce5fe83c30520607a015dabd985df3251e188d234bfe9492e17a389/requests-2.34.0-py3-none-any.whl", hash = "sha256:917520a21b767485ce7c588f4ebb917c436b24a31231b44228715eaeb5a52c60", size = 73021, upload-time = "2026-05-11T19:29:49.923Z" },
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
[[package]]
|
|
176
|
+
name = "typing-extensions"
|
|
177
|
+
version = "4.15.0"
|
|
178
|
+
source = { registry = "https://pypi.org/simple" }
|
|
179
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
|
180
|
+
wheels = [
|
|
181
|
+
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
[[package]]
|
|
185
|
+
name = "typing-inspection"
|
|
186
|
+
version = "0.4.2"
|
|
187
|
+
source = { registry = "https://pypi.org/simple" }
|
|
188
|
+
dependencies = [
|
|
189
|
+
{ name = "typing-extensions" },
|
|
190
|
+
]
|
|
191
|
+
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
|
|
192
|
+
wheels = [
|
|
193
|
+
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
[[package]]
|
|
197
|
+
name = "urllib3"
|
|
198
|
+
version = "2.7.0"
|
|
199
|
+
source = { registry = "https://pypi.org/simple" }
|
|
200
|
+
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
|
|
201
|
+
wheels = [
|
|
202
|
+
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
[[package]]
|
|
206
|
+
name = "wrapfast"
|
|
207
|
+
version = "0.0.2"
|
|
208
|
+
source = { editable = "." }
|
|
209
|
+
|
|
210
|
+
[package.optional-dependencies]
|
|
211
|
+
examples = [
|
|
212
|
+
{ name = "pydantic" },
|
|
213
|
+
{ name = "requests" },
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
[package.metadata]
|
|
217
|
+
requires-dist = [
|
|
218
|
+
{ name = "pydantic", marker = "extra == 'examples'", specifier = ">=2" },
|
|
219
|
+
{ name = "requests", marker = "extra == 'examples'", specifier = ">=2.31" },
|
|
220
|
+
]
|
|
221
|
+
provides-extras = ["examples"]
|