hishel 1.0.0.dev2__py3-none-any.whl → 1.0.0.dev3__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.
- hishel/__init__.py +15 -14
- hishel/_async_cache.py +42 -36
- hishel/_async_httpx.py +243 -0
- hishel/_core/_headers.py +11 -1
- hishel/_core/_spec.py +88 -84
- hishel/_core/_storages/_async_base.py +71 -0
- hishel/_core/{_async/_storages/_sqlite.py → _storages/_async_sqlite.py} +95 -132
- hishel/_core/_storages/_packing.py +144 -0
- hishel/_core/_storages/_sync_base.py +71 -0
- hishel/_core/{_sync/_storages/_sqlite.py → _storages/_sync_sqlite.py} +95 -132
- hishel/_core/models.py +6 -22
- hishel/_sync_cache.py +42 -36
- hishel/_sync_httpx.py +243 -0
- hishel/_utils.py +49 -2
- hishel/asgi.py +400 -0
- hishel/fastapi.py +263 -0
- hishel/httpx.py +3 -326
- hishel/requests.py +25 -17
- {hishel-1.0.0.dev2.dist-info → hishel-1.0.0.dev3.dist-info}/METADATA +100 -6
- hishel-1.0.0.dev3.dist-info/RECORD +23 -0
- hishel/_core/__init__.py +0 -59
- hishel/_core/_base/_storages/_base.py +0 -272
- hishel/_core/_base/_storages/_packing.py +0 -165
- hishel-1.0.0.dev2.dist-info/RECORD +0 -19
- {hishel-1.0.0.dev2.dist-info → hishel-1.0.0.dev3.dist-info}/WHEEL +0 -0
- {hishel-1.0.0.dev2.dist-info → hishel-1.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
hishel/httpx.py
CHANGED
|
@@ -1,20 +1,5 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import ssl
|
|
4
|
-
import typing as t
|
|
5
|
-
from typing import AsyncIterator, Iterable, Iterator, Union, overload
|
|
6
|
-
|
|
7
|
-
from hishel import Headers, Request, Response
|
|
8
|
-
from hishel._async_cache import AsyncCacheProxy
|
|
9
|
-
from hishel._core._base._storages._base import AsyncBaseStorage, SyncBaseStorage
|
|
10
|
-
from hishel._core._spec import (
|
|
11
|
-
CacheOptions,
|
|
12
|
-
)
|
|
13
|
-
from hishel._core.models import AnyIterable
|
|
14
|
-
from hishel._sync_cache import SyncCacheProxy
|
|
15
|
-
|
|
16
1
|
try:
|
|
17
|
-
import httpx
|
|
2
|
+
import httpx # noqa: F401
|
|
18
3
|
except ImportError as e:
|
|
19
4
|
raise ImportError(
|
|
20
5
|
"httpx is required to use hishel.httpx module. "
|
|
@@ -22,314 +7,6 @@ except ImportError as e:
|
|
|
22
7
|
"e.g., 'pip install hishel[httpx]'."
|
|
23
8
|
) from e
|
|
24
9
|
|
|
25
|
-
SOCKET_OPTION = t.Union[
|
|
26
|
-
t.Tuple[int, int, int],
|
|
27
|
-
t.Tuple[int, int, t.Union[bytes, bytearray]],
|
|
28
|
-
t.Tuple[int, int, None, int],
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
# 128 KB
|
|
32
|
-
CHUNK_SIZE = 131072
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class IteratorStream(httpx.SyncByteStream, httpx.AsyncByteStream):
|
|
36
|
-
def __init__(self, iterator: Iterator[bytes] | AsyncIterator[bytes]) -> None:
|
|
37
|
-
self.iterator = iterator
|
|
38
|
-
|
|
39
|
-
def __iter__(self) -> Iterator[bytes]:
|
|
40
|
-
assert isinstance(self.iterator, (Iterator))
|
|
41
|
-
yield from self.iterator
|
|
42
|
-
|
|
43
|
-
async def __aiter__(self) -> AsyncIterator[bytes]:
|
|
44
|
-
assert isinstance(self.iterator, (AsyncIterator))
|
|
45
|
-
async for chunk in self.iterator:
|
|
46
|
-
yield chunk
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@overload
|
|
50
|
-
def internal_to_httpx(
|
|
51
|
-
value: Request,
|
|
52
|
-
) -> httpx.Request: ...
|
|
53
|
-
@overload
|
|
54
|
-
def internal_to_httpx(
|
|
55
|
-
value: Response,
|
|
56
|
-
) -> httpx.Response: ...
|
|
57
|
-
def internal_to_httpx(
|
|
58
|
-
value: Union[Request, Response],
|
|
59
|
-
) -> Union[httpx.Request, httpx.Response]:
|
|
60
|
-
"""
|
|
61
|
-
Convert internal Request/Response to httpx.Request/httpx.Response.
|
|
62
|
-
"""
|
|
63
|
-
if isinstance(value, Request):
|
|
64
|
-
return httpx.Request(
|
|
65
|
-
method=value.method,
|
|
66
|
-
url=value.url,
|
|
67
|
-
headers=value.headers,
|
|
68
|
-
stream=IteratorStream(value.stream),
|
|
69
|
-
extensions=value.metadata,
|
|
70
|
-
)
|
|
71
|
-
elif isinstance(value, Response):
|
|
72
|
-
return httpx.Response(
|
|
73
|
-
status_code=value.status_code,
|
|
74
|
-
headers=value.headers,
|
|
75
|
-
stream=IteratorStream(value.stream),
|
|
76
|
-
extensions=value.metadata,
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
@overload
|
|
81
|
-
def httpx_to_internal(
|
|
82
|
-
value: httpx.Request,
|
|
83
|
-
) -> Request: ...
|
|
84
|
-
@overload
|
|
85
|
-
def httpx_to_internal(
|
|
86
|
-
value: httpx.Response,
|
|
87
|
-
) -> Response: ...
|
|
88
|
-
def httpx_to_internal(
|
|
89
|
-
value: Union[httpx.Request, httpx.Response],
|
|
90
|
-
) -> Union[Request, Response]:
|
|
91
|
-
"""
|
|
92
|
-
Convert httpx.Request/httpx.Response to internal Request/Response.
|
|
93
|
-
"""
|
|
94
|
-
stream: Union[Iterator[bytes], AsyncIterator[bytes]]
|
|
95
|
-
try:
|
|
96
|
-
stream = AnyIterable(value.content)
|
|
97
|
-
except (httpx.RequestNotRead, httpx.ResponseNotRead):
|
|
98
|
-
if isinstance(value, httpx.Response):
|
|
99
|
-
stream = (
|
|
100
|
-
value.iter_raw(chunk_size=CHUNK_SIZE)
|
|
101
|
-
if isinstance(value.stream, Iterable)
|
|
102
|
-
else value.aiter_raw(chunk_size=CHUNK_SIZE)
|
|
103
|
-
)
|
|
104
|
-
else:
|
|
105
|
-
stream = value.stream # type: ignore
|
|
106
|
-
if isinstance(value, httpx.Request):
|
|
107
|
-
return Request(
|
|
108
|
-
method=value.method,
|
|
109
|
-
url=str(value.url),
|
|
110
|
-
headers=Headers({key: value for key, value in value.headers.items()}),
|
|
111
|
-
stream=stream,
|
|
112
|
-
metadata={
|
|
113
|
-
"hishel_refresh_ttl_on_access": value.extensions.get("hishel_refresh_ttl_on_access"),
|
|
114
|
-
"hishel_ttl": value.extensions.get("hishel_ttl"),
|
|
115
|
-
"hishel_spec_ignore": value.extensions.get("hishel_spec_ignore"),
|
|
116
|
-
},
|
|
117
|
-
)
|
|
118
|
-
elif isinstance(value, httpx.Response):
|
|
119
|
-
return Response(
|
|
120
|
-
status_code=value.status_code,
|
|
121
|
-
headers=Headers({key: value for key, value in value.headers.items()}),
|
|
122
|
-
stream=stream,
|
|
123
|
-
metadata={},
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
class SyncCacheTransport(httpx.BaseTransport):
|
|
128
|
-
def __init__(
|
|
129
|
-
self,
|
|
130
|
-
next_transport: httpx.BaseTransport,
|
|
131
|
-
storage: SyncBaseStorage | None = None,
|
|
132
|
-
cache_options: CacheOptions | None = None,
|
|
133
|
-
ignore_specification: bool = False,
|
|
134
|
-
) -> None:
|
|
135
|
-
self.next_transport = next_transport
|
|
136
|
-
self._cache_proxy: SyncCacheProxy = SyncCacheProxy(
|
|
137
|
-
send_request=self.sync_send_request,
|
|
138
|
-
storage=storage,
|
|
139
|
-
cache_options=cache_options,
|
|
140
|
-
ignore_specification=ignore_specification,
|
|
141
|
-
)
|
|
142
|
-
self.storage = self._cache_proxy.storage
|
|
143
|
-
|
|
144
|
-
def handle_request(
|
|
145
|
-
self,
|
|
146
|
-
request: httpx.Request,
|
|
147
|
-
) -> httpx.Response:
|
|
148
|
-
internal_request = httpx_to_internal(request)
|
|
149
|
-
internal_response = self._cache_proxy.handle_request(internal_request)
|
|
150
|
-
response = internal_to_httpx(internal_response)
|
|
151
|
-
return response
|
|
152
|
-
|
|
153
|
-
def close(self) -> None:
|
|
154
|
-
self.next_transport.close()
|
|
155
|
-
self.storage.close()
|
|
156
|
-
super().close()
|
|
157
|
-
|
|
158
|
-
def sync_send_request(self, request: Request) -> Response:
|
|
159
|
-
httpx_request = internal_to_httpx(request)
|
|
160
|
-
httpx_response = self.next_transport.handle_request(httpx_request)
|
|
161
|
-
return httpx_to_internal(httpx_response)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
class SyncCacheClient(httpx.Client):
|
|
165
|
-
@overload
|
|
166
|
-
def __init__(
|
|
167
|
-
self,
|
|
168
|
-
*,
|
|
169
|
-
storage: SyncBaseStorage | None = None,
|
|
170
|
-
cache_options: CacheOptions | None = None,
|
|
171
|
-
**kwargs: t.Any,
|
|
172
|
-
) -> None: ...
|
|
173
|
-
@overload
|
|
174
|
-
def __init__(
|
|
175
|
-
self,
|
|
176
|
-
*args: t.Any,
|
|
177
|
-
**kwargs: t.Any,
|
|
178
|
-
) -> None: ...
|
|
179
|
-
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
|
180
|
-
self.storage: SyncBaseStorage | None = kwargs.pop("storage", None)
|
|
181
|
-
self.cache_options: CacheOptions | None = kwargs.pop("cache_options", None)
|
|
182
|
-
super().__init__(*args, **kwargs)
|
|
183
|
-
|
|
184
|
-
def _init_transport(
|
|
185
|
-
self,
|
|
186
|
-
verify: ssl.SSLContext | str | bool = True,
|
|
187
|
-
cert: t.Union[str, t.Tuple[str, str], t.Tuple[str, str, str], None] = None,
|
|
188
|
-
trust_env: bool = True,
|
|
189
|
-
http1: bool = True,
|
|
190
|
-
http2: bool = False,
|
|
191
|
-
limits: httpx.Limits = httpx.Limits(max_connections=100, max_keepalive_connections=20),
|
|
192
|
-
transport: httpx.BaseTransport | None = None,
|
|
193
|
-
**kwargs: t.Any,
|
|
194
|
-
) -> httpx.BaseTransport:
|
|
195
|
-
if transport is not None:
|
|
196
|
-
return transport
|
|
197
|
-
|
|
198
|
-
return SyncCacheTransport(
|
|
199
|
-
next_transport=httpx.HTTPTransport(
|
|
200
|
-
verify=verify,
|
|
201
|
-
cert=cert,
|
|
202
|
-
trust_env=trust_env,
|
|
203
|
-
http1=http1,
|
|
204
|
-
http2=http2,
|
|
205
|
-
limits=limits,
|
|
206
|
-
),
|
|
207
|
-
storage=self.storage,
|
|
208
|
-
cache_options=self.cache_options,
|
|
209
|
-
ignore_specification=False,
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
def _init_proxy_transport(
|
|
213
|
-
self,
|
|
214
|
-
proxy: httpx.Proxy,
|
|
215
|
-
verify: ssl.SSLContext | str | bool = True,
|
|
216
|
-
cert: t.Union[str, t.Tuple[str, str], t.Tuple[str, str, str], None] = None,
|
|
217
|
-
trust_env: bool = True,
|
|
218
|
-
http1: bool = True,
|
|
219
|
-
http2: bool = False,
|
|
220
|
-
limits: httpx.Limits = httpx.Limits(max_connections=100, max_keepalive_connections=20),
|
|
221
|
-
**kwargs: t.Any,
|
|
222
|
-
) -> httpx.BaseTransport:
|
|
223
|
-
return SyncCacheTransport(
|
|
224
|
-
next_transport=httpx.HTTPTransport(
|
|
225
|
-
verify=verify,
|
|
226
|
-
cert=cert,
|
|
227
|
-
trust_env=trust_env,
|
|
228
|
-
http1=http1,
|
|
229
|
-
http2=http2,
|
|
230
|
-
limits=limits,
|
|
231
|
-
proxy=proxy,
|
|
232
|
-
),
|
|
233
|
-
storage=self.storage,
|
|
234
|
-
cache_options=self.cache_options,
|
|
235
|
-
ignore_specification=False,
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
class AsyncCacheTransport(httpx.AsyncBaseTransport):
|
|
240
|
-
def __init__(
|
|
241
|
-
self,
|
|
242
|
-
next_transport: httpx.AsyncBaseTransport,
|
|
243
|
-
storage: AsyncBaseStorage | None = None,
|
|
244
|
-
cache_options: CacheOptions | None = None,
|
|
245
|
-
ignore_specification: bool = False,
|
|
246
|
-
) -> None:
|
|
247
|
-
self.next_transport = next_transport
|
|
248
|
-
self._cache_proxy: AsyncCacheProxy = AsyncCacheProxy(
|
|
249
|
-
send_request=self.async_send_request,
|
|
250
|
-
storage=storage,
|
|
251
|
-
cache_options=cache_options,
|
|
252
|
-
ignore_specification=ignore_specification,
|
|
253
|
-
)
|
|
254
|
-
self.storage = self._cache_proxy.storage
|
|
255
|
-
|
|
256
|
-
async def handle_async_request(
|
|
257
|
-
self,
|
|
258
|
-
request: httpx.Request,
|
|
259
|
-
) -> httpx.Response:
|
|
260
|
-
internal_request = httpx_to_internal(request)
|
|
261
|
-
internal_response = await self._cache_proxy.handle_request(internal_request)
|
|
262
|
-
response = internal_to_httpx(internal_response)
|
|
263
|
-
return response
|
|
264
|
-
|
|
265
|
-
async def aclose(self) -> None:
|
|
266
|
-
await self.next_transport.aclose()
|
|
267
|
-
await self.storage.close()
|
|
268
|
-
await super().aclose()
|
|
269
|
-
|
|
270
|
-
async def async_send_request(self, request: Request) -> Response:
|
|
271
|
-
httpx_request = internal_to_httpx(request)
|
|
272
|
-
httpx_response = await self.next_transport.handle_async_request(httpx_request)
|
|
273
|
-
return httpx_to_internal(httpx_response)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
class AsyncCacheClient(httpx.AsyncClient):
|
|
277
|
-
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
|
278
|
-
self.storage: AsyncBaseStorage | None = kwargs.pop("storage", None)
|
|
279
|
-
self.cache_options: CacheOptions | None = kwargs.pop("cache_options", None)
|
|
280
|
-
self.ignore_specification: bool = kwargs.pop("ignore_specification", False)
|
|
281
|
-
super().__init__(*args, **kwargs)
|
|
282
|
-
|
|
283
|
-
def _init_transport(
|
|
284
|
-
self,
|
|
285
|
-
verify: ssl.SSLContext | str | bool = True,
|
|
286
|
-
cert: t.Union[str, t.Tuple[str, str], t.Tuple[str, str, str], None] = None,
|
|
287
|
-
trust_env: bool = True,
|
|
288
|
-
http1: bool = True,
|
|
289
|
-
http2: bool = False,
|
|
290
|
-
limits: httpx.Limits = httpx.Limits(max_connections=100, max_keepalive_connections=20),
|
|
291
|
-
transport: httpx.AsyncBaseTransport | None = None,
|
|
292
|
-
**kwargs: t.Any,
|
|
293
|
-
) -> httpx.AsyncBaseTransport:
|
|
294
|
-
if transport is not None:
|
|
295
|
-
return transport
|
|
296
|
-
|
|
297
|
-
return AsyncCacheTransport(
|
|
298
|
-
next_transport=httpx.AsyncHTTPTransport(
|
|
299
|
-
verify=verify,
|
|
300
|
-
cert=cert,
|
|
301
|
-
trust_env=trust_env,
|
|
302
|
-
http1=http1,
|
|
303
|
-
http2=http2,
|
|
304
|
-
limits=limits,
|
|
305
|
-
),
|
|
306
|
-
storage=self.storage,
|
|
307
|
-
cache_options=self.cache_options,
|
|
308
|
-
ignore_specification=False,
|
|
309
|
-
)
|
|
310
10
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
proxy: httpx.Proxy,
|
|
314
|
-
verify: ssl.SSLContext | str | bool = True,
|
|
315
|
-
cert: t.Union[str, t.Tuple[str, str], t.Tuple[str, str, str], None] = None,
|
|
316
|
-
trust_env: bool = True,
|
|
317
|
-
http1: bool = True,
|
|
318
|
-
http2: bool = False,
|
|
319
|
-
limits: httpx.Limits = httpx.Limits(max_connections=100, max_keepalive_connections=20),
|
|
320
|
-
**kwargs: t.Any,
|
|
321
|
-
) -> httpx.AsyncBaseTransport:
|
|
322
|
-
return AsyncCacheTransport(
|
|
323
|
-
next_transport=httpx.AsyncHTTPTransport(
|
|
324
|
-
verify=verify,
|
|
325
|
-
cert=cert,
|
|
326
|
-
trust_env=trust_env,
|
|
327
|
-
http1=http1,
|
|
328
|
-
http2=http2,
|
|
329
|
-
limits=limits,
|
|
330
|
-
proxy=proxy,
|
|
331
|
-
),
|
|
332
|
-
storage=self.storage,
|
|
333
|
-
cache_options=self.cache_options,
|
|
334
|
-
ignore_specification=self.ignore_specification,
|
|
335
|
-
)
|
|
11
|
+
from ._async_httpx import AsyncCacheClient as AsyncCacheClient, AsyncCacheTransport as AsyncCacheTransport
|
|
12
|
+
from ._sync_httpx import SyncCacheClient as SyncCacheClient, SyncCacheTransport as SyncCacheTransport
|
hishel/requests.py
CHANGED
|
@@ -6,8 +6,8 @@ from typing import Any, Iterator, Mapping, Optional, overload
|
|
|
6
6
|
from typing_extensions import assert_never
|
|
7
7
|
|
|
8
8
|
from hishel import Headers, Request, Response as Response
|
|
9
|
-
from hishel._core._base._storages._base import SyncBaseStorage
|
|
10
9
|
from hishel._core._spec import CacheOptions
|
|
10
|
+
from hishel._core._storages._sync_base import SyncBaseStorage
|
|
11
11
|
from hishel._core.models import extract_metadata_from_headers
|
|
12
12
|
from hishel._sync_cache import SyncCacheProxy
|
|
13
13
|
from hishel._utils import snake_to_header
|
|
@@ -27,7 +27,7 @@ except ImportError: # pragma: no cover
|
|
|
27
27
|
CHUNK_SIZE = 131072
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
class
|
|
30
|
+
class _IteratorStream(RawIOBase):
|
|
31
31
|
def __init__(self, iterator: Iterator[bytes]):
|
|
32
32
|
self.iterator = iterator
|
|
33
33
|
self.leftover = b""
|
|
@@ -61,18 +61,18 @@ class IteratorStream(RawIOBase):
|
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
@overload
|
|
64
|
-
def
|
|
64
|
+
def _requests_to_internal(
|
|
65
65
|
model: requests.models.PreparedRequest,
|
|
66
66
|
) -> Request: ...
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
@overload
|
|
70
|
-
def
|
|
70
|
+
def _requests_to_internal(
|
|
71
71
|
model: requests.models.Response,
|
|
72
72
|
) -> Response: ...
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
def
|
|
75
|
+
def _requests_to_internal(
|
|
76
76
|
model: requests.models.PreparedRequest | requests.models.Response,
|
|
77
77
|
) -> Request | Response:
|
|
78
78
|
if isinstance(model, requests.models.PreparedRequest):
|
|
@@ -108,19 +108,24 @@ def requests_to_internal(
|
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
@overload
|
|
111
|
-
def
|
|
111
|
+
def _internal_to_requests(model: Request) -> requests.models.PreparedRequest: ...
|
|
112
112
|
@overload
|
|
113
|
-
def
|
|
114
|
-
def
|
|
113
|
+
def _internal_to_requests(model: Response) -> requests.models.Response: ...
|
|
114
|
+
def _internal_to_requests(
|
|
115
|
+
model: Request | Response,
|
|
116
|
+
) -> requests.models.Response | requests.models.PreparedRequest:
|
|
115
117
|
if isinstance(model, Response):
|
|
116
118
|
response = requests.models.Response()
|
|
117
119
|
|
|
118
120
|
assert isinstance(model.stream, Iterator)
|
|
119
|
-
stream =
|
|
121
|
+
stream = _IteratorStream(model.stream)
|
|
120
122
|
|
|
121
123
|
urllib_response = HTTPResponse(
|
|
122
124
|
body=stream,
|
|
123
|
-
headers={
|
|
125
|
+
headers={
|
|
126
|
+
**model.headers,
|
|
127
|
+
**{snake_to_header(k): str(v) for k, v in model.metadata.items()},
|
|
128
|
+
},
|
|
124
129
|
status=model.status_code,
|
|
125
130
|
preload_content=False,
|
|
126
131
|
decode_content=False,
|
|
@@ -163,7 +168,7 @@ class CacheAdapter(HTTPAdapter):
|
|
|
163
168
|
):
|
|
164
169
|
super().__init__(pool_connections, pool_maxsize, max_retries, pool_block)
|
|
165
170
|
self._cache_proxy = SyncCacheProxy(
|
|
166
|
-
|
|
171
|
+
request_sender=self._send_request,
|
|
167
172
|
storage=storage,
|
|
168
173
|
cache_options=cache_options,
|
|
169
174
|
ignore_specification=ignore_specification,
|
|
@@ -179,9 +184,9 @@ class CacheAdapter(HTTPAdapter):
|
|
|
179
184
|
cert: None | bytes | str | tuple[bytes | str, bytes | str] = None,
|
|
180
185
|
proxies: Mapping[str, str] | None = None,
|
|
181
186
|
) -> requests.models.Response:
|
|
182
|
-
internal_request =
|
|
187
|
+
internal_request = _requests_to_internal(request)
|
|
183
188
|
internal_response = self._cache_proxy.handle_request(internal_request)
|
|
184
|
-
response =
|
|
189
|
+
response = _internal_to_requests(internal_response)
|
|
185
190
|
|
|
186
191
|
# Set the original request on the response
|
|
187
192
|
response.request = request
|
|
@@ -189,10 +194,13 @@ class CacheAdapter(HTTPAdapter):
|
|
|
189
194
|
|
|
190
195
|
return response
|
|
191
196
|
|
|
192
|
-
def
|
|
193
|
-
requests_request =
|
|
194
|
-
response = super().send(
|
|
195
|
-
|
|
197
|
+
def _send_request(self, request: Request) -> Response:
|
|
198
|
+
requests_request = _internal_to_requests(request)
|
|
199
|
+
response = super().send(
|
|
200
|
+
requests_request,
|
|
201
|
+
stream=True,
|
|
202
|
+
)
|
|
203
|
+
return _requests_to_internal(response)
|
|
196
204
|
|
|
197
205
|
def close(self) -> Any:
|
|
198
206
|
self.storage.close()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hishel
|
|
3
|
-
Version: 1.0.0.
|
|
3
|
+
Version: 1.0.0.dev3
|
|
4
4
|
Summary: Elegant HTTP Caching for Python
|
|
5
5
|
Project-URL: Homepage, https://hishel.com
|
|
6
6
|
Project-URL: Source, https://github.com/karpetrosyan/hishel
|
|
@@ -29,6 +29,8 @@ Requires-Dist: typing-extensions>=4.14.1
|
|
|
29
29
|
Provides-Extra: async
|
|
30
30
|
Requires-Dist: anyio>=4.9.0; extra == 'async'
|
|
31
31
|
Requires-Dist: anysqlite>=0.0.5; extra == 'async'
|
|
32
|
+
Provides-Extra: fastapi
|
|
33
|
+
Requires-Dist: fastapi>=0.119.1; extra == 'fastapi'
|
|
32
34
|
Provides-Extra: httpx
|
|
33
35
|
Requires-Dist: anyio>=4.9.0; extra == 'httpx'
|
|
34
36
|
Requires-Dist: anysqlite>=0.0.5; extra == 'httpx'
|
|
@@ -37,6 +39,7 @@ Provides-Extra: requests
|
|
|
37
39
|
Requires-Dist: requests>=2.32.5; extra == 'requests'
|
|
38
40
|
Description-Content-Type: text/markdown
|
|
39
41
|
|
|
42
|
+
|
|
40
43
|
<p align="center">
|
|
41
44
|
<img alt="Hishel Logo" width="350" src="https://raw.githubusercontent.com/karpetrosyan/hishel/master/docs/static/Shelkopryad_350x250_yellow.png#gh-dark-mode-only">
|
|
42
45
|
<img alt="Hishel Logo" width="350" src="https://raw.githubusercontent.com/karpetrosyan/hishel/master/docs/static/Shelkopryad_350x250_black.png#gh-light-mode-only">
|
|
@@ -73,14 +76,15 @@ Description-Content-Type: text/markdown
|
|
|
73
76
|
## ✨ Features
|
|
74
77
|
|
|
75
78
|
- 🎯 **RFC 9111 Compliant** - Fully compliant with the latest HTTP caching specification
|
|
76
|
-
- 🔌 **Easy Integration** - Drop-in support for HTTPX and
|
|
79
|
+
- 🔌 **Easy Integration** - Drop-in support for HTTPX, Requests, ASGI, FastAPI, and BlackSheep
|
|
77
80
|
- 💾 **Flexible Storage** - SQLite backend with more coming soon
|
|
78
81
|
- ⚡ **High Performance** - Efficient caching with minimal overhead
|
|
79
82
|
- 🔄 **Async & Sync** - Full support for both synchronous and asynchronous workflows
|
|
80
83
|
- 🎨 **Type Safe** - Fully typed with comprehensive type hints
|
|
81
84
|
- 🧪 **Well Tested** - Extensive test coverage and battle-tested
|
|
82
85
|
- 🎛️ **Configurable** - Fine-grained control over caching behavior
|
|
83
|
-
-
|
|
86
|
+
- � **Memory Efficient** - Streaming support prevents loading large payloads into memory
|
|
87
|
+
- 🌐 **Universal** - Works with any ASGI application (Starlette, Litestar, BlackSheep, etc.)
|
|
84
88
|
|
|
85
89
|
## 📦 Installation
|
|
86
90
|
|
|
@@ -90,19 +94,23 @@ pip install hishel
|
|
|
90
94
|
|
|
91
95
|
### Optional Dependencies
|
|
92
96
|
|
|
93
|
-
Install with specific
|
|
97
|
+
Install with specific integration support:
|
|
94
98
|
|
|
95
99
|
```bash
|
|
96
100
|
pip install hishel[httpx] # For HTTPX support
|
|
97
101
|
pip install hishel[requests] # For Requests support
|
|
102
|
+
pip install hishel[fastapi] # For FastAPI support (includes ASGI)
|
|
98
103
|
```
|
|
99
104
|
|
|
100
|
-
Or install
|
|
105
|
+
Or install multiple:
|
|
101
106
|
|
|
102
107
|
```bash
|
|
103
|
-
pip install hishel[httpx,requests]
|
|
108
|
+
pip install hishel[httpx,requests,fastapi]
|
|
104
109
|
```
|
|
105
110
|
|
|
111
|
+
> [!NOTE]
|
|
112
|
+
> ASGI middleware has no extra dependencies - it's included in the base installation.
|
|
113
|
+
|
|
106
114
|
## 🚀 Quick Start
|
|
107
115
|
|
|
108
116
|
### With HTTPX
|
|
@@ -156,6 +164,65 @@ response = session.get("https://api.example.com/data")
|
|
|
156
164
|
print(response.headers.get("X-Hishel-From-Cache")) # "True"
|
|
157
165
|
```
|
|
158
166
|
|
|
167
|
+
### With ASGI Applications
|
|
168
|
+
|
|
169
|
+
Add caching middleware to any ASGI application:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from hishel.asgi import ASGICacheMiddleware
|
|
173
|
+
|
|
174
|
+
# Wrap your ASGI app
|
|
175
|
+
app = ASGICacheMiddleware(app)
|
|
176
|
+
|
|
177
|
+
# Or configure with options
|
|
178
|
+
from hishel import AsyncSqliteStorage, CacheOptions
|
|
179
|
+
|
|
180
|
+
app = ASGICacheMiddleware(
|
|
181
|
+
app,
|
|
182
|
+
storage=AsyncSqliteStorage(),
|
|
183
|
+
cache_options=CacheOptions(shared=True)
|
|
184
|
+
)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### With FastAPI
|
|
188
|
+
|
|
189
|
+
Add Cache-Control headers using the `cache()` dependency:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from fastapi import FastAPI
|
|
193
|
+
from hishel.fastapi import cache
|
|
194
|
+
|
|
195
|
+
app = FastAPI()
|
|
196
|
+
|
|
197
|
+
@app.get("/api/data", dependencies=[cache(max_age=300, public=True)])
|
|
198
|
+
async def get_data():
|
|
199
|
+
# Cache-Control: public, max-age=300
|
|
200
|
+
return {"data": "cached for 5 minutes"}
|
|
201
|
+
|
|
202
|
+
# Optionally wrap with ASGI middleware for local caching according to specified rules
|
|
203
|
+
from hishel.asgi import ASGICacheMiddleware
|
|
204
|
+
from hishel import AsyncSqliteStorage
|
|
205
|
+
|
|
206
|
+
app = ASGICacheMiddleware(app, storage=AsyncSqliteStorage())
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### With BlackSheep
|
|
210
|
+
|
|
211
|
+
Use BlackSheep's native `cache_control` decorator with Hishel's ASGI middleware:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
from blacksheep import Application, get
|
|
215
|
+
from blacksheep.server.headers.cache import cache_control
|
|
216
|
+
|
|
217
|
+
app = Application()
|
|
218
|
+
|
|
219
|
+
@get("/api/data")
|
|
220
|
+
@cache_control(max_age=300, public=True)
|
|
221
|
+
async def get_data():
|
|
222
|
+
# Cache-Control: public, max-age=300
|
|
223
|
+
return {"data": "cached for 5 minutes"}
|
|
224
|
+
```
|
|
225
|
+
|
|
159
226
|
## 🎛️ Advanced Configuration
|
|
160
227
|
|
|
161
228
|
### Custom Cache Options
|
|
@@ -214,7 +281,11 @@ Comprehensive documentation is available at [https://hishel.com/dev](https://his
|
|
|
214
281
|
- [Getting Started](https://hishel.com)
|
|
215
282
|
- [HTTPX Integration](https://hishel.com/dev/integrations/httpx)
|
|
216
283
|
- [Requests Integration](https://hishel.com/dev/integrations/requests)
|
|
284
|
+
- [ASGI Integration](https://hishel.com/dev/asgi)
|
|
285
|
+
- [FastAPI Integration](https://hishel.com/dev/fastapi)
|
|
286
|
+
- [BlackSheep Integration](https://hishel.com/dev/integrations/blacksheep)
|
|
217
287
|
- [Storage Backends](https://hishel.com/dev/storages)
|
|
288
|
+
- [Request/Response Metadata](https://hishel.com/dev/metadata)
|
|
218
289
|
- [RFC 9111 Specification](https://hishel.com/dev/specification)
|
|
219
290
|
|
|
220
291
|
## 🤝 Contributing
|
|
@@ -255,10 +326,33 @@ Hishel is inspired by and builds upon the excellent work in the Python HTTP ecos
|
|
|
255
326
|
|
|
256
327
|
All notable changes to this project will be documented in this file.
|
|
257
328
|
|
|
329
|
+
## 1.0.0.dev3 - 2025-10-26
|
|
330
|
+
### ♻️ Refactoring
|
|
331
|
+
- Replace pairs with entries, simplify storage API
|
|
332
|
+
- Automatically generate httpx sync integration from async
|
|
333
|
+
|
|
334
|
+
### ⚙️ Miscellaneous Tasks
|
|
335
|
+
- Simplify metadata docs
|
|
336
|
+
- Add custom integrations docs
|
|
337
|
+
- More robust compressed response caching
|
|
338
|
+
|
|
339
|
+
### 🐛 Bug Fixes
|
|
340
|
+
- Add missing permissions into `publish.yml`
|
|
341
|
+
- Raise on consumed httpx streams, which we can't store as is (it's already decoded)
|
|
342
|
+
- Fix compressed data caching for requests
|
|
343
|
+
- Handle httpx iterable usage instead of iterator correctly
|
|
344
|
+
- Add date header for proper age calculation
|
|
345
|
+
|
|
346
|
+
### 🚀 Features
|
|
347
|
+
- Add integrations with fastapi and asgi
|
|
348
|
+
- Add blacksheep integration examples
|
|
349
|
+
- Add logging for asgi
|
|
350
|
+
|
|
258
351
|
## 1.0.0.dev2 - 2025-10-21
|
|
259
352
|
### ⚙️ Miscellaneous Tasks
|
|
260
353
|
- Remove redundant utils and tests
|
|
261
354
|
- Add import without extras check in ci
|
|
355
|
+
- Fix time travel date, explicitly specify the timezone
|
|
262
356
|
|
|
263
357
|
### 🐛 Bug Fixes
|
|
264
358
|
- Fix check for storing auth requests
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
hishel/__init__.py,sha256=HqU6RUrtlvEMTo5i7WbdSJvYoLQiyqkMJVbXrefd6Ww,1566
|
|
2
|
+
hishel/_async_cache.py,sha256=He_wRJYMSy0aLinzlgQ76ohOos15Bbw3N2uu6cLGkZk,7216
|
|
3
|
+
hishel/_async_httpx.py,sha256=e8JO37K6fF4UZlTGeMpsmMJtLLhGfdWQNLIhPahlbkU,7894
|
|
4
|
+
hishel/_sync_cache.py,sha256=zYNrRoOeBUta-GO0yNRs7n-PQ4WglQLEzvbY5N9oZCU,6962
|
|
5
|
+
hishel/_sync_httpx.py,sha256=eQlIwb3RrJNV3CEqs3xGQJByVOO3C_DXK8W5x60HZlQ,7726
|
|
6
|
+
hishel/_utils.py,sha256=kD5ki4PKeYsNBMh3tAjsXggF2-_oeNz6aVlARxyl0H0,3842
|
|
7
|
+
hishel/asgi.py,sha256=mbYFJHQJCRE6w0KcZiSe9qIHHPgRFuCwAwg9haJoMWI,14983
|
|
8
|
+
hishel/fastapi.py,sha256=CVWCyXTxBPwG_XALo-Oldekv4lqMgH2-W-PPZ9rZjXg,10826
|
|
9
|
+
hishel/httpx.py,sha256=99a8X9COPiPHSgGW61O2uMWMZB7dY93Ty9DTCJ9C18Q,467
|
|
10
|
+
hishel/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
hishel/requests.py,sha256=Bi0_1O2xgFlWqG1g6OR1gpb5MY8CKJmgLPDEG6bhuXM,6533
|
|
12
|
+
hishel/_core/_headers.py,sha256=hGaT6o1F-gs1pm5RpdGb0IMQL3uJYDH1xpwJLy28Cys,17514
|
|
13
|
+
hishel/_core/_spec.py,sha256=yJsOmNU5dMXXBw80ZLnse9Tl4UGkHmiyyfjv1Q0eP9w,103871
|
|
14
|
+
hishel/_core/models.py,sha256=YBXpk5GRWsB8ppksehTaIDJ_V5KAENKspSj-kLvJUPY,5169
|
|
15
|
+
hishel/_core/_storages/_async_base.py,sha256=iZ6Mb30P0ho5h4UU5bgOrcsSMZ1427j9tht-tupZs68,2106
|
|
16
|
+
hishel/_core/_storages/_async_sqlite.py,sha256=QNo5oWnnHAU0rTPJJbE9d6__xG_jWrG3-atoyE2j3q0,15555
|
|
17
|
+
hishel/_core/_storages/_packing.py,sha256=mC8LMFQ5uPfFOgingKm2WKFO_DwcZ1OjTgI6xc0hfJI,3708
|
|
18
|
+
hishel/_core/_storages/_sync_base.py,sha256=qfOvcFY5qvrzSh4ztV2Trlxft-BF7An5SFsLlEb8EeE,2075
|
|
19
|
+
hishel/_core/_storages/_sync_sqlite.py,sha256=A-L6fDU1JvVDzHNzV396V3U3sFPLycrxSnD00DHrTvs,15048
|
|
20
|
+
hishel-1.0.0.dev3.dist-info/METADATA,sha256=CLK-XP376Y4IuDGQgYxzRhJhfoi9Z281HlZccTOVXqE,12884
|
|
21
|
+
hishel-1.0.0.dev3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
22
|
+
hishel-1.0.0.dev3.dist-info/licenses/LICENSE,sha256=1qQj7pE0V2O9OIedvyOgLGLvZLaPd3nFEup3IBEOZjQ,1493
|
|
23
|
+
hishel-1.0.0.dev3.dist-info/RECORD,,
|