hishel 0.1.5__py3-none-any.whl → 1.0.0.dev0__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 +55 -53
- hishel/{beta/_async_cache.py → _async_cache.py} +3 -3
- hishel/{beta → _core}/__init__.py +6 -6
- hishel/{beta/_core → _core}/_async/_storages/_sqlite.py +3 -3
- hishel/{beta/_core → _core}/_base/_storages/_base.py +1 -1
- hishel/{beta/_core → _core}/_base/_storages/_packing.py +5 -5
- hishel/{beta/_core → _core}/_spec.py +89 -2
- hishel/{beta/_core → _core}/_sync/_storages/_sqlite.py +3 -3
- hishel/{beta/_core → _core}/models.py +1 -1
- hishel/{beta/_sync_cache.py → _sync_cache.py} +3 -3
- hishel/{beta/httpx.py → httpx.py} +6 -6
- hishel/{beta/requests.py → requests.py} +5 -5
- hishel-1.0.0.dev0.dist-info/METADATA +321 -0
- hishel-1.0.0.dev0.dist-info/RECORD +19 -0
- hishel/_async/__init__.py +0 -5
- hishel/_async/_client.py +0 -30
- hishel/_async/_mock.py +0 -43
- hishel/_async/_pool.py +0 -201
- hishel/_async/_storages.py +0 -768
- hishel/_async/_transports.py +0 -282
- hishel/_controller.py +0 -581
- hishel/_exceptions.py +0 -10
- hishel/_files.py +0 -54
- hishel/_headers.py +0 -215
- hishel/_lfu_cache.py +0 -71
- hishel/_lmdb_types_.pyi +0 -53
- hishel/_s3.py +0 -122
- hishel/_serializers.py +0 -329
- hishel/_sync/__init__.py +0 -5
- hishel/_sync/_client.py +0 -30
- hishel/_sync/_mock.py +0 -43
- hishel/_sync/_pool.py +0 -201
- hishel/_sync/_storages.py +0 -768
- hishel/_sync/_transports.py +0 -282
- hishel/_synchronization.py +0 -37
- hishel/beta/_core/__init__.py +0 -0
- hishel-0.1.5.dist-info/METADATA +0 -258
- hishel-0.1.5.dist-info/RECORD +0 -41
- /hishel/{beta/_core → _core}/_headers.py +0 -0
- {hishel-0.1.5.dist-info → hishel-1.0.0.dev0.dist-info}/WHEEL +0 -0
- {hishel-0.1.5.dist-info → hishel-1.0.0.dev0.dist-info}/licenses/LICENSE +0 -0
hishel/_sync/_transports.py
DELETED
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import types
|
|
4
|
-
import typing as tp
|
|
5
|
-
|
|
6
|
-
import httpcore
|
|
7
|
-
import httpx
|
|
8
|
-
from httpx import SyncByteStream, Request, Response
|
|
9
|
-
from httpx._exceptions import ConnectError
|
|
10
|
-
|
|
11
|
-
from .._controller import Controller, allowed_stale
|
|
12
|
-
from .._headers import parse_cache_control
|
|
13
|
-
from .._serializers import JSONSerializer, Metadata
|
|
14
|
-
from .._utils import extract_header_values_decoded, normalized_url
|
|
15
|
-
from ._storages import BaseStorage, FileStorage
|
|
16
|
-
|
|
17
|
-
if tp.TYPE_CHECKING: # pragma: no cover
|
|
18
|
-
from typing_extensions import Self
|
|
19
|
-
|
|
20
|
-
__all__ = ("CacheTransport",)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def fake_stream(content: bytes) -> tp.Iterable[bytes]:
|
|
24
|
-
yield content
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def generate_504() -> Response:
|
|
28
|
-
return Response(status_code=504)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class CacheStream(SyncByteStream):
|
|
32
|
-
def __init__(self, httpcore_stream: tp.Iterable[bytes]):
|
|
33
|
-
self._httpcore_stream = httpcore_stream
|
|
34
|
-
|
|
35
|
-
def __iter__(self) -> tp.Iterator[bytes]:
|
|
36
|
-
for part in self._httpcore_stream:
|
|
37
|
-
yield part
|
|
38
|
-
|
|
39
|
-
def close(self) -> None:
|
|
40
|
-
if hasattr(self._httpcore_stream, "close"):
|
|
41
|
-
self._httpcore_stream.close()
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class CacheTransport(httpx.BaseTransport):
|
|
45
|
-
"""
|
|
46
|
-
An HTTPX Transport that supports HTTP caching.
|
|
47
|
-
|
|
48
|
-
:param transport: `Transport` that our class wraps in order to add an HTTP Cache layer on top of
|
|
49
|
-
:type transport: httpx.BaseTransport
|
|
50
|
-
:param storage: Storage that handles how the responses should be saved., defaults to None
|
|
51
|
-
:type storage: tp.Optional[BaseStorage], optional
|
|
52
|
-
:param controller: Controller that manages the cache behavior at the specification level, defaults to None
|
|
53
|
-
:type controller: tp.Optional[Controller], optional
|
|
54
|
-
"""
|
|
55
|
-
|
|
56
|
-
def __init__(
|
|
57
|
-
self,
|
|
58
|
-
transport: httpx.BaseTransport,
|
|
59
|
-
storage: tp.Optional[BaseStorage] = None,
|
|
60
|
-
controller: tp.Optional[Controller] = None,
|
|
61
|
-
) -> None:
|
|
62
|
-
self._transport = transport
|
|
63
|
-
|
|
64
|
-
self._storage = storage if storage is not None else FileStorage(serializer=JSONSerializer())
|
|
65
|
-
|
|
66
|
-
if not isinstance(self._storage, BaseStorage): # pragma: no cover
|
|
67
|
-
raise TypeError(f"Expected subclass of `BaseStorage` but got `{storage.__class__.__name__}`")
|
|
68
|
-
|
|
69
|
-
self._controller = controller if controller is not None else Controller()
|
|
70
|
-
|
|
71
|
-
def handle_request(self, request: Request) -> Response:
|
|
72
|
-
"""
|
|
73
|
-
Handles HTTP requests while also implementing HTTP caching.
|
|
74
|
-
|
|
75
|
-
:param request: An HTTP request
|
|
76
|
-
:type request: httpx.Request
|
|
77
|
-
:return: An HTTP response
|
|
78
|
-
:rtype: httpx.Response
|
|
79
|
-
"""
|
|
80
|
-
|
|
81
|
-
if request.extensions.get("cache_disabled", False):
|
|
82
|
-
request.headers.update(
|
|
83
|
-
[
|
|
84
|
-
("Cache-Control", "no-store"),
|
|
85
|
-
("Cache-Control", "no-cache"),
|
|
86
|
-
*[("cache-control", value) for value in request.headers.get_list("cache-control")],
|
|
87
|
-
]
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
if request.method not in ["GET", "HEAD"]:
|
|
91
|
-
# If the HTTP method is, for example, POST,
|
|
92
|
-
# we must also use the request data to generate the hash.
|
|
93
|
-
body_for_key = request.read()
|
|
94
|
-
request.stream = CacheStream(fake_stream(body_for_key))
|
|
95
|
-
else:
|
|
96
|
-
body_for_key = b""
|
|
97
|
-
|
|
98
|
-
# Construct the HTTPCore request because Controllers and Storages work with HTTPCore requests.
|
|
99
|
-
httpcore_request = httpcore.Request(
|
|
100
|
-
method=request.method,
|
|
101
|
-
url=httpcore.URL(
|
|
102
|
-
scheme=request.url.raw_scheme,
|
|
103
|
-
host=request.url.raw_host,
|
|
104
|
-
port=request.url.port,
|
|
105
|
-
target=request.url.raw_path,
|
|
106
|
-
),
|
|
107
|
-
headers=request.headers.raw,
|
|
108
|
-
content=request.stream,
|
|
109
|
-
extensions=request.extensions,
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
key = self._controller._key_generator(httpcore_request, body_for_key)
|
|
113
|
-
stored_data = self._storage.retrieve(key)
|
|
114
|
-
|
|
115
|
-
request_cache_control = parse_cache_control(
|
|
116
|
-
extract_header_values_decoded(request.headers.raw, b"Cache-Control")
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
if request_cache_control.only_if_cached and not stored_data:
|
|
120
|
-
return generate_504()
|
|
121
|
-
|
|
122
|
-
if stored_data:
|
|
123
|
-
# Try using the stored response if it was discovered.
|
|
124
|
-
|
|
125
|
-
stored_response, stored_request, metadata = stored_data
|
|
126
|
-
|
|
127
|
-
# Immediately read the stored response to avoid issues when trying to access the response body.
|
|
128
|
-
stored_response.read()
|
|
129
|
-
|
|
130
|
-
res = self._controller.construct_response_from_cache(
|
|
131
|
-
request=httpcore_request,
|
|
132
|
-
response=stored_response,
|
|
133
|
-
original_request=stored_request,
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
if isinstance(res, httpcore.Response):
|
|
137
|
-
# Simply use the response if the controller determines it is ready for use.
|
|
138
|
-
return self._create_hishel_response(
|
|
139
|
-
key=key,
|
|
140
|
-
response=res,
|
|
141
|
-
request=httpcore_request,
|
|
142
|
-
cached=True,
|
|
143
|
-
revalidated=False,
|
|
144
|
-
metadata=metadata,
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
if request_cache_control.only_if_cached:
|
|
148
|
-
return generate_504()
|
|
149
|
-
|
|
150
|
-
if isinstance(res, httpcore.Request):
|
|
151
|
-
# Controller has determined that the response needs to be re-validated.
|
|
152
|
-
assert isinstance(res.stream, tp.Iterable)
|
|
153
|
-
revalidation_request = Request(
|
|
154
|
-
method=res.method.decode(),
|
|
155
|
-
url=normalized_url(res.url),
|
|
156
|
-
headers=res.headers,
|
|
157
|
-
stream=CacheStream(res.stream),
|
|
158
|
-
extensions=res.extensions,
|
|
159
|
-
)
|
|
160
|
-
try:
|
|
161
|
-
revalidation_response = self._transport.handle_request(revalidation_request)
|
|
162
|
-
except ConnectError:
|
|
163
|
-
# If there is a connection error, we can use the stale response if allowed.
|
|
164
|
-
if self._controller._allow_stale and allowed_stale(response=stored_response):
|
|
165
|
-
return self._create_hishel_response(
|
|
166
|
-
key=key,
|
|
167
|
-
response=stored_response,
|
|
168
|
-
request=httpcore_request,
|
|
169
|
-
cached=True,
|
|
170
|
-
revalidated=False,
|
|
171
|
-
metadata=metadata,
|
|
172
|
-
)
|
|
173
|
-
raise # pragma: no cover
|
|
174
|
-
assert isinstance(revalidation_response.stream, tp.Iterable)
|
|
175
|
-
httpcore_revalidation_response = httpcore.Response(
|
|
176
|
-
status=revalidation_response.status_code,
|
|
177
|
-
headers=revalidation_response.headers.raw,
|
|
178
|
-
content=CacheStream(revalidation_response.stream),
|
|
179
|
-
extensions=revalidation_response.extensions,
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
# Merge headers with the stale response.
|
|
183
|
-
final_httpcore_response = self._controller.handle_validation_response(
|
|
184
|
-
old_response=stored_response,
|
|
185
|
-
new_response=httpcore_revalidation_response,
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
final_httpcore_response.read()
|
|
189
|
-
revalidation_response.close()
|
|
190
|
-
|
|
191
|
-
assert isinstance(final_httpcore_response.stream, tp.Iterable)
|
|
192
|
-
|
|
193
|
-
# RFC 9111: 4.3.3. Handling a Validation Response
|
|
194
|
-
# A 304 (Not Modified) response status code indicates that the stored response can be updated and
|
|
195
|
-
# reused. A full response (i.e., one containing content) indicates that none of the stored responses
|
|
196
|
-
# nominated in the conditional request are suitable. Instead, the cache MUST use the full response to
|
|
197
|
-
# satisfy the request. The cache MAY store such a full response, subject to its constraints.
|
|
198
|
-
if revalidation_response.status_code != 304 and self._controller.is_cachable(
|
|
199
|
-
request=httpcore_request, response=final_httpcore_response
|
|
200
|
-
):
|
|
201
|
-
self._storage.store(key, response=final_httpcore_response, request=httpcore_request)
|
|
202
|
-
|
|
203
|
-
return self._create_hishel_response(
|
|
204
|
-
key=key,
|
|
205
|
-
response=final_httpcore_response,
|
|
206
|
-
request=httpcore_request,
|
|
207
|
-
cached=revalidation_response.status_code == 304,
|
|
208
|
-
revalidated=True,
|
|
209
|
-
metadata=metadata,
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
regular_response = self._transport.handle_request(request)
|
|
213
|
-
try:
|
|
214
|
-
# Prefer already-read content, if available
|
|
215
|
-
stream = fake_stream(regular_response.content)
|
|
216
|
-
except httpx.ResponseNotRead:
|
|
217
|
-
# Fall back to stream if not yet read
|
|
218
|
-
assert isinstance(regular_response.stream, tp.Iterable)
|
|
219
|
-
stream = regular_response.stream
|
|
220
|
-
httpcore_regular_response = httpcore.Response(
|
|
221
|
-
status=regular_response.status_code,
|
|
222
|
-
headers=regular_response.headers.raw,
|
|
223
|
-
content=CacheStream(stream),
|
|
224
|
-
extensions=regular_response.extensions,
|
|
225
|
-
)
|
|
226
|
-
httpcore_regular_response.read()
|
|
227
|
-
httpcore_regular_response.close()
|
|
228
|
-
|
|
229
|
-
if self._controller.is_cachable(request=httpcore_request, response=httpcore_regular_response):
|
|
230
|
-
self._storage.store(
|
|
231
|
-
key,
|
|
232
|
-
response=httpcore_regular_response,
|
|
233
|
-
request=httpcore_request,
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
return self._create_hishel_response(
|
|
237
|
-
key=key,
|
|
238
|
-
response=httpcore_regular_response,
|
|
239
|
-
request=httpcore_request,
|
|
240
|
-
cached=False,
|
|
241
|
-
revalidated=False,
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
def _create_hishel_response(
|
|
245
|
-
self,
|
|
246
|
-
key: str,
|
|
247
|
-
response: httpcore.Response,
|
|
248
|
-
request: httpcore.Request,
|
|
249
|
-
cached: bool,
|
|
250
|
-
revalidated: bool,
|
|
251
|
-
metadata: Metadata | None = None,
|
|
252
|
-
) -> Response:
|
|
253
|
-
if cached:
|
|
254
|
-
assert metadata
|
|
255
|
-
metadata["number_of_uses"] += 1
|
|
256
|
-
self._storage.update_metadata(key=key, request=request, response=response, metadata=metadata)
|
|
257
|
-
response.extensions["from_cache"] = True # type: ignore[index]
|
|
258
|
-
response.extensions["cache_metadata"] = metadata # type: ignore[index]
|
|
259
|
-
else:
|
|
260
|
-
response.extensions["from_cache"] = False # type: ignore[index]
|
|
261
|
-
response.extensions["revalidated"] = revalidated # type: ignore[index]
|
|
262
|
-
return Response(
|
|
263
|
-
status_code=response.status,
|
|
264
|
-
headers=response.headers,
|
|
265
|
-
stream=CacheStream(fake_stream(response.content)),
|
|
266
|
-
extensions=response.extensions,
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
def close(self) -> None:
|
|
270
|
-
self._storage.close()
|
|
271
|
-
self._transport.close()
|
|
272
|
-
|
|
273
|
-
def __enter__(self) -> Self:
|
|
274
|
-
return self
|
|
275
|
-
|
|
276
|
-
def __exit__(
|
|
277
|
-
self,
|
|
278
|
-
exc_type: tp.Optional[tp.Type[BaseException]] = None,
|
|
279
|
-
exc_value: tp.Optional[BaseException] = None,
|
|
280
|
-
traceback: tp.Optional[types.TracebackType] = None,
|
|
281
|
-
) -> None:
|
|
282
|
-
self.close()
|
hishel/_synchronization.py
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import types
|
|
2
|
-
import typing as tp
|
|
3
|
-
from threading import Lock as T_LOCK
|
|
4
|
-
|
|
5
|
-
import anyio
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class AsyncLock:
|
|
9
|
-
def __init__(self) -> None:
|
|
10
|
-
self._lock = anyio.Lock()
|
|
11
|
-
|
|
12
|
-
async def __aenter__(self) -> None:
|
|
13
|
-
await self._lock.acquire()
|
|
14
|
-
|
|
15
|
-
async def __aexit__(
|
|
16
|
-
self,
|
|
17
|
-
exc_type: tp.Optional[tp.Type[BaseException]] = None,
|
|
18
|
-
exc_value: tp.Optional[BaseException] = None,
|
|
19
|
-
traceback: tp.Optional[types.TracebackType] = None,
|
|
20
|
-
) -> None:
|
|
21
|
-
self._lock.release()
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class Lock:
|
|
25
|
-
def __init__(self) -> None:
|
|
26
|
-
self._lock = T_LOCK()
|
|
27
|
-
|
|
28
|
-
def __enter__(self) -> None:
|
|
29
|
-
self._lock.acquire()
|
|
30
|
-
|
|
31
|
-
def __exit__(
|
|
32
|
-
self,
|
|
33
|
-
exc_type: tp.Optional[tp.Type[BaseException]] = None,
|
|
34
|
-
exc_value: tp.Optional[BaseException] = None,
|
|
35
|
-
traceback: tp.Optional[types.TracebackType] = None,
|
|
36
|
-
) -> None:
|
|
37
|
-
self._lock.release()
|
hishel/beta/_core/__init__.py
DELETED
|
File without changes
|
hishel-0.1.5.dist-info/METADATA
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: hishel
|
|
3
|
-
Version: 0.1.5
|
|
4
|
-
Summary: Persistent cache implementation for httpx and httpcore
|
|
5
|
-
Project-URL: Homepage, https://hishel.com
|
|
6
|
-
Project-URL: Source, https://github.com/karpetrosyan/hishel
|
|
7
|
-
Author-email: Kar Petrosyan <kar.petrosyanpy@gmail.com>
|
|
8
|
-
License-Expression: BSD-3-Clause
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
Classifier: Development Status :: 3 - Alpha
|
|
11
|
-
Classifier: Environment :: Web Environment
|
|
12
|
-
Classifier: Framework :: AsyncIO
|
|
13
|
-
Classifier: Framework :: Trio
|
|
14
|
-
Classifier: Intended Audience :: Developers
|
|
15
|
-
Classifier: License :: OSI Approved :: BSD License
|
|
16
|
-
Classifier: Operating System :: OS Independent
|
|
17
|
-
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
-
Classifier: Topic :: Internet :: WWW/HTTP
|
|
26
|
-
Requires-Python: >=3.9
|
|
27
|
-
Requires-Dist: anyio>=4.9.0
|
|
28
|
-
Requires-Dist: anysqlite>=0.0.5
|
|
29
|
-
Requires-Dist: httpx>=0.28.0
|
|
30
|
-
Requires-Dist: msgpack>=1.1.2
|
|
31
|
-
Requires-Dist: typing-extensions>=4.14.1
|
|
32
|
-
Provides-Extra: httpx
|
|
33
|
-
Requires-Dist: httpx>=0.28.1; extra == 'httpx'
|
|
34
|
-
Provides-Extra: redis
|
|
35
|
-
Requires-Dist: redis==6.2.0; extra == 'redis'
|
|
36
|
-
Provides-Extra: requests
|
|
37
|
-
Requires-Dist: requests>=2.32.5; extra == 'requests'
|
|
38
|
-
Provides-Extra: s3
|
|
39
|
-
Requires-Dist: boto3<=1.15.3,>=1.15.0; (python_version < '3.12') and extra == 's3'
|
|
40
|
-
Requires-Dist: boto3>=1.15.3; (python_version >= '3.12') and extra == 's3'
|
|
41
|
-
Provides-Extra: sqlite
|
|
42
|
-
Requires-Dist: anysqlite>=0.0.5; extra == 'sqlite'
|
|
43
|
-
Provides-Extra: yaml
|
|
44
|
-
Requires-Dist: pyyaml==6.0.2; extra == 'yaml'
|
|
45
|
-
Description-Content-Type: text/markdown
|
|
46
|
-
|
|
47
|
-
<p align="center" class="logo">
|
|
48
|
-
<div align="center">
|
|
49
|
-
<picture>
|
|
50
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/karpetrosyan/hishel/master/docs/static/Shelkopryad_350x250_yellow.png">
|
|
51
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/karpetrosyan/hishel/master/docs/static/Shelkopryad_350x250_black.png">
|
|
52
|
-
<img alt="Logo" src="https://raw.githubusercontent.com/karpetrosyan/hishel/master/docs/static/Shelkopryad_350x250_yellow.png">
|
|
53
|
-
</picture>
|
|
54
|
-
</div>
|
|
55
|
-
</p>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
<p align="center"><strong>Hishel</strong> <em>- An elegant HTTP Cache implementation for httpx and httpcore.</em></p>
|
|
60
|
-
|
|
61
|
-
<p align="center">
|
|
62
|
-
|
|
63
|
-
<a href="https://pypi.org/project/hishel">
|
|
64
|
-
<img src="https://img.shields.io/pypi/v/hishel.svg" alt="pypi">
|
|
65
|
-
</a>
|
|
66
|
-
|
|
67
|
-
<a href="https://img.shields.io/pypi/l/hishel">
|
|
68
|
-
<img src="https://img.shields.io/pypi/l/hishel" alt="license">
|
|
69
|
-
</a>
|
|
70
|
-
|
|
71
|
-
<a href="https://coveralls.io/github/karpetrosyan/hishel">
|
|
72
|
-
<img src="https://img.shields.io/coverallsCoverage/github/karpetrosyan/hishel" alt="license">
|
|
73
|
-
</a>
|
|
74
|
-
|
|
75
|
-
<a href="https://github.com/karpetrosyan/hishel">
|
|
76
|
-
<img src="https://img.shields.io/pypi/dm/hishel.svg" alt="Downloads">
|
|
77
|
-
</a>
|
|
78
|
-
</p>
|
|
79
|
-
|
|
80
|
-
<p align="center">
|
|
81
|
-
<a href="https://buymeacoffee.com/karpetrosyan" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
|
|
82
|
-
</p>
|
|
83
|
-
|
|
84
|
-
-----
|
|
85
|
-
|
|
86
|
-
**Hishel (հիշել, remember)** is a library that implements HTTP Caching for [HTTPX](https://github.com/encode/httpx) and [HTTP Core](https://github.com/encode/httpcore) libraries in accordance with [**RFC 9111**](https://www.rfc-editor.org/rfc/rfc9111.html), the most recent caching specification.
|
|
87
|
-
|
|
88
|
-
## Features
|
|
89
|
-
|
|
90
|
-
- 💾 **Persistence**: Responses are cached in the [**persistent memory**](https://en.m.wikipedia.org/wiki/Persistent_memory) for later use.
|
|
91
|
-
- 🤲 **Compatibility**: It is completely compatible with your existing transports or connection pools, *whether they are default, custom, or provided by third-party libraries.*
|
|
92
|
-
- 🤗 **Easy to use**: You continue to use httpx while also enabling [web cache](https://en.wikipedia.org/wiki/Web_cache).
|
|
93
|
-
- 🧠 **Smart**: Attempts to clearly implement RFC 9111, understands `Vary`, `Etag`, `Last-Modified`, `Cache-Control`, and `Expires` headers, and *handles response re-validation automatically*.
|
|
94
|
-
- ⚙️ **Configurable**: You have complete control over how the responses are stored and serialized.
|
|
95
|
-
- 📦 **From the package**:
|
|
96
|
-
- Built-in support for [File system](https://en.wikipedia.org/wiki/File_system), [Redis](https://en.wikipedia.org/wiki/Redis), [SQLite](https://en.wikipedia.org/wiki/SQLite), and [AWS S3](https://aws.amazon.com/s3/) backends.
|
|
97
|
-
- Built-in support for [JSON](https://en.wikipedia.org/wiki/JSON), [YAML](https://en.wikipedia.org/wiki/YAML), and [pickle](https://docs.python.org/3/library/pickle.html) serializers.
|
|
98
|
-
- 🚀 **Very fast**: Your requests will be even faster if there are *no IO operations*.
|
|
99
|
-
|
|
100
|
-
## Documentation
|
|
101
|
-
Go through the [Hishel documentation](https://hishel.com).
|
|
102
|
-
|
|
103
|
-
## QuickStart
|
|
104
|
-
|
|
105
|
-
Install `Hishel` using pip:
|
|
106
|
-
``` shell
|
|
107
|
-
$ pip install hishel
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
Let's begin with an example of a httpx client.
|
|
111
|
-
|
|
112
|
-
```python
|
|
113
|
-
import hishel
|
|
114
|
-
|
|
115
|
-
with hishel.CacheClient() as client:
|
|
116
|
-
client.get("https://hishel.com") # 0.4749558370003797s
|
|
117
|
-
client.get("https://hishel.com") # 0.002873589000046195s (~250x faster!)
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
or in asynchronous context
|
|
121
|
-
|
|
122
|
-
```python
|
|
123
|
-
import hishel
|
|
124
|
-
|
|
125
|
-
async with hishel.AsyncCacheClient() as client:
|
|
126
|
-
await client.get("https://hishel.com")
|
|
127
|
-
await client.get("https://hishel.com") # takes from the cache
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
## Configurations
|
|
131
|
-
|
|
132
|
-
Configure when and how you want to store your responses.
|
|
133
|
-
|
|
134
|
-
```python
|
|
135
|
-
import hishel
|
|
136
|
-
|
|
137
|
-
# All the specification configs
|
|
138
|
-
controller = hishel.Controller(
|
|
139
|
-
# Cache only GET and POST methods
|
|
140
|
-
cacheable_methods=["GET", "POST"],
|
|
141
|
-
|
|
142
|
-
# Cache only 200 status codes
|
|
143
|
-
cacheable_status_codes=[200],
|
|
144
|
-
|
|
145
|
-
# Use the stale response if there is a connection issue and the new response cannot be obtained.
|
|
146
|
-
allow_stale=True,
|
|
147
|
-
|
|
148
|
-
# First, revalidate the response and then utilize it.
|
|
149
|
-
# If the response has not changed, do not download the
|
|
150
|
-
# entire response data from the server; instead,
|
|
151
|
-
# use the one you have because you know it has not been modified.
|
|
152
|
-
always_revalidate=True,
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
# All the storage configs
|
|
156
|
-
storage = hishel.S3Storage(
|
|
157
|
-
bucket_name="my_bucket_name", # store my cache files in the `my_bucket_name` bucket
|
|
158
|
-
ttl=3600, # delete the response if it is in the cache for more than an hour
|
|
159
|
-
)
|
|
160
|
-
client = hishel.CacheClient(controller=controller, storage=storage)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
# Ignore the fact that the server does not recommend you cache this request!
|
|
164
|
-
client.post(
|
|
165
|
-
"https://example.com",
|
|
166
|
-
extensions={"force_cache": True}
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
# Return a regular response if it is in the cache; else, return a 504 status code. DO NOT SEND A REQUEST!
|
|
171
|
-
client.post(
|
|
172
|
-
"https://example.com",
|
|
173
|
-
headers=[("Cache-Control", "only-if-cached")]
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
# Ignore cached responses and do not store incoming responses!
|
|
178
|
-
response = client.post(
|
|
179
|
-
"https://example.com",
|
|
180
|
-
extensions={"cache_disabled": True}
|
|
181
|
-
)
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
## How and where are the responses saved?
|
|
185
|
-
|
|
186
|
-
The responses are stored by `Hishel` in [storages](https://hishel.com/userguide/#storages).
|
|
187
|
-
You have complete control over them; you can change storage or even write your own if necessary.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
## Support the project
|
|
191
|
-
|
|
192
|
-
You can support the project by simply leaving a GitHub star ⭐ or by [contributing](https://hishel.com/contributing/).
|
|
193
|
-
Help us grow and continue developing good software for you ❤️
|
|
194
|
-
|
|
195
|
-
## [0.1.5] - 2025-10-18
|
|
196
|
-
|
|
197
|
-
### 🚀 Features
|
|
198
|
-
|
|
199
|
-
- *(perf)* Set chunk size to 128KB for httpx to reduce SQLite read/writes
|
|
200
|
-
- Better cache-control parsing
|
|
201
|
-
- Add close method to storages API (#384)
|
|
202
|
-
- *(perf)* Increase requests buffer size to 128KB, disable charset detection
|
|
203
|
-
|
|
204
|
-
### 🐛 Bug Fixes
|
|
205
|
-
|
|
206
|
-
- *(docs)* Fix some line breaks
|
|
207
|
-
|
|
208
|
-
### ⚙️ Miscellaneous Tasks
|
|
209
|
-
|
|
210
|
-
- Remove some redundant files from repo
|
|
211
|
-
## [0.1.4] - 2025-10-14
|
|
212
|
-
|
|
213
|
-
### 🚀 Features
|
|
214
|
-
|
|
215
|
-
- Add support for a sans-IO API (#366)
|
|
216
|
-
- Allow already consumed streams with `CacheTransport` (#377)
|
|
217
|
-
- Add sqlite storage for beta storages
|
|
218
|
-
- Get rid of some locks from sqlite storage
|
|
219
|
-
- Better async implemetation for sqlite storage
|
|
220
|
-
|
|
221
|
-
### 🐛 Bug Fixes
|
|
222
|
-
|
|
223
|
-
- Create an sqlite file in a cache folder
|
|
224
|
-
- Fix beta imports
|
|
225
|
-
|
|
226
|
-
### ⚙️ Miscellaneous Tasks
|
|
227
|
-
|
|
228
|
-
- Improve CI (#369)
|
|
229
|
-
- *(internal)* Remove src folder (#373)
|
|
230
|
-
- *(internal)* Temporary remove python3.14 from CI
|
|
231
|
-
- *(tests)* Add sqlite tests for new storage
|
|
232
|
-
- *(tests)* Move some tests to beta
|
|
233
|
-
## [0.1.3] - 2025-07-06
|
|
234
|
-
|
|
235
|
-
### 🚀 Features
|
|
236
|
-
|
|
237
|
-
- Support providing a path prefix to S3 storage (#342)
|
|
238
|
-
|
|
239
|
-
### 📚 Documentation
|
|
240
|
-
|
|
241
|
-
- Update link to httpx transports page (#337)
|
|
242
|
-
## [0.1.2] - 2025-04-04
|
|
243
|
-
|
|
244
|
-
### 🐛 Bug Fixes
|
|
245
|
-
|
|
246
|
-
- Requirements.txt to reduce vulnerabilities (#263)
|
|
247
|
-
## [0.0.30] - 2024-07-12
|
|
248
|
-
|
|
249
|
-
### 🐛 Bug Fixes
|
|
250
|
-
|
|
251
|
-
- Requirements.txt to reduce vulnerabilities (#245)
|
|
252
|
-
- Requirements.txt to reduce vulnerabilities (#255)
|
|
253
|
-
## [0.0.27] - 2024-05-31
|
|
254
|
-
|
|
255
|
-
### 🐛 Bug Fixes
|
|
256
|
-
|
|
257
|
-
- *(redis)* Do not update metadata with negative ttl (#231)
|
|
258
|
-
## [0.0.1] - 2023-07-22
|
hishel-0.1.5.dist-info/RECORD
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
hishel/__init__.py,sha256=4HWxHaEihJ5bsey4XEZA28meUO2Iw3mJrOFhQtWu4FY,1221
|
|
2
|
-
hishel/_controller.py,sha256=nQMEF-upuBf6-r0wyjd2CGYgBhB-JbEw6IgGxtvADJ4,24629
|
|
3
|
-
hishel/_exceptions.py,sha256=qbg55RNlzwhv5JreWY9Zog_zmmiKdn5degtqJKijuRs,198
|
|
4
|
-
hishel/_files.py,sha256=7J5uX7Nnzd7QQWfYuDGh8v6XGLG3eUDBjoJZ4aTaY1c,2228
|
|
5
|
-
hishel/_headers.py,sha256=BPvas0LQgwbz-HZhFykZiHEIeNgnY-E33U__oskYzJw,7323
|
|
6
|
-
hishel/_lfu_cache.py,sha256=GBxToQI8u_a9TzYnLlZMLhgZ8Lb83boPHzTvIgqV6pA,2707
|
|
7
|
-
hishel/_lmdb_types_.pyi,sha256=2qx4-3kZhkKxhkWApZdnoGck3CbPrhUJQIIMLPBT90w,1492
|
|
8
|
-
hishel/_s3.py,sha256=HkmWYjYWAiWgEMp1_Jl79hG5aUpMcD0NDXjj8VHRmQE,4322
|
|
9
|
-
hishel/_serializers.py,sha256=wQ5xqeQPAnzDFxLrjmWUP6f0IC0RihpgpTgy3jljxjE,11646
|
|
10
|
-
hishel/_synchronization.py,sha256=xOmU9_8KAWTAv3r8EpqPISrtSF3slyh1J0Sc7ZQO1rg,897
|
|
11
|
-
hishel/_utils.py,sha256=uO8PcY_E1sHSgBGzZ2GNB4kpKqAlzmnzPCc3s-yDd44,13826
|
|
12
|
-
hishel/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
hishel/_async/__init__.py,sha256=_oAltH4emAtUF7_9SSxz_KKGYzSXL34o_EGgGvoPG7E,187
|
|
14
|
-
hishel/_async/_client.py,sha256=WqYHV8SeAXlK6Q0uQe0ixkC99i64iOwD2EPOYhQcUL8,1088
|
|
15
|
-
hishel/_async/_mock.py,sha256=995v9p5xiw3svGSOJATkLMqwodlhZhcwmGygLHM2VFw,1515
|
|
16
|
-
hishel/_async/_pool.py,sha256=xCcSAl4bHI5eVsZyPap7iHDpxzzgfMqYE_txcjQJ1Hs,8195
|
|
17
|
-
hishel/_async/_storages.py,sha256=zR0kIJSN1WK6P0qWpOTceEYtwWTcfgabf7t3qnGDK3w,28899
|
|
18
|
-
hishel/_async/_transports.py,sha256=YTZecg1A5vnv4GnF9y9n394nzqGBAkr-M4BF2lvci5U,11445
|
|
19
|
-
hishel/_sync/__init__.py,sha256=_oAltH4emAtUF7_9SSxz_KKGYzSXL34o_EGgGvoPG7E,187
|
|
20
|
-
hishel/_sync/_client.py,sha256=hHTuS9wOBZhcNSQmbopSWTtTTGYu-q_0FQOc9g5TuRI,1048
|
|
21
|
-
hishel/_sync/_mock.py,sha256=im88tZr-XhP9BpzvIt3uOjndAlNcJvFP7Puv3H-6lKU,1430
|
|
22
|
-
hishel/_sync/_pool.py,sha256=U2b9ZGYUltwTjI2q2KHZwmj4boIqUExJ_rUKWuLmYSs,7960
|
|
23
|
-
hishel/_sync/_storages.py,sha256=PAt0HdkGE-LNd-UV3EclrahuFFPcatDK4DoYiTXhiBM,28108
|
|
24
|
-
hishel/_sync/_transports.py,sha256=ivPztcm84PxObIXMZ3hLD_o5HMo8XXGhRlSg0Lm_sNo,11117
|
|
25
|
-
hishel/beta/__init__.py,sha256=VzlIfaGQaYfpesqOujNcG0HMCaIF9CzEyhIY04A4c8g,1477
|
|
26
|
-
hishel/beta/_async_cache.py,sha256=vhdTyRIjMpKzc4qKPZGVoFQa9-60aCXLNGltOE1mDg8,6807
|
|
27
|
-
hishel/beta/_sync_cache.py,sha256=a0fcNY5qApPBXQ_kCUBW2Ccwwj0bEkNTVmv-6W3cqPI,6553
|
|
28
|
-
hishel/beta/httpx.py,sha256=zOnUUafa1zyi2hmcWeP7uaifcj7OIHlXvvuh19gKfL0,11027
|
|
29
|
-
hishel/beta/requests.py,sha256=20i5H7m67BH90iT7RzEbeyiNsbS8lx7eU6kAdWInfQY,6454
|
|
30
|
-
hishel/beta/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
-
hishel/beta/_core/_headers.py,sha256=ii4x2L6GoQFpqpgg28OtFh7p2DoM9mhE4q6CjW6xUWc,17473
|
|
32
|
-
hishel/beta/_core/_spec.py,sha256=5z5kZ7Q9BO8yZynQIiDiIly77iTMzUljekhmrF1Otyc,99529
|
|
33
|
-
hishel/beta/_core/models.py,sha256=rbU72bCT_lpwASIs5Qzmo3vcjiDm-lu0C99-CgYWl7k,5524
|
|
34
|
-
hishel/beta/_core/_async/_storages/_sqlite.py,sha256=_PZ4t8KXk-YErTCze80Uh8XnG8WZ2323VrRd0NwoKHY,14719
|
|
35
|
-
hishel/beta/_core/_base/_storages/_base.py,sha256=mHOQ1p1BTuhV5vgeIdn61OAbVjiD8vu4S4CCal6sS5A,8521
|
|
36
|
-
hishel/beta/_core/_base/_storages/_packing.py,sha256=8s0UxYTLdwzs4A_1E896EVj5VPT8Aqv9UabWbeMD8lw,4898
|
|
37
|
-
hishel/beta/_core/_sync/_storages/_sqlite.py,sha256=1sBU_geVbUSC8e46gao5p_k8MogYEWnZodse55FQQ6Q,14153
|
|
38
|
-
hishel-0.1.5.dist-info/METADATA,sha256=ZB-4dyu3L8icekMrIwMEhZrjlSfryzlSBITW0-b7BCQ,9310
|
|
39
|
-
hishel-0.1.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
40
|
-
hishel-0.1.5.dist-info/licenses/LICENSE,sha256=1qQj7pE0V2O9OIedvyOgLGLvZLaPd3nFEup3IBEOZjQ,1493
|
|
41
|
-
hishel-0.1.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|