hishel 0.0.24__tar.gz → 0.0.26__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.
- {hishel-0.0.24 → hishel-0.0.26}/CHANGELOG.md +11 -0
- {hishel-0.0.24 → hishel-0.0.26}/PKG-INFO +13 -2
- {hishel-0.0.24 → hishel-0.0.26}/hishel/__init__.py +1 -1
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_async/_mock.py +1 -2
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_async/_pool.py +45 -29
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_async/_storages.py +176 -13
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_async/_transports.py +72 -72
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_controller.py +6 -2
- hishel-0.0.26/hishel/_exceptions.py +10 -0
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_headers.py +0 -6
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_s3.py +27 -8
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_sync/_mock.py +1 -2
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_sync/_pool.py +45 -29
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_sync/_storages.py +176 -13
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_sync/_transports.py +72 -72
- {hishel-0.0.24 → hishel-0.0.26}/pyproject.toml +4 -2
- hishel-0.0.24/hishel/_exceptions.py +0 -13
- {hishel-0.0.24 → hishel-0.0.26}/.gitignore +0 -0
- {hishel-0.0.24 → hishel-0.0.26}/LICENSE +0 -0
- {hishel-0.0.24 → hishel-0.0.26}/README.md +0 -0
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_async/__init__.py +0 -0
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_async/_client.py +0 -0
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_files.py +0 -0
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_lfu_cache.py +0 -0
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_serializers.py +0 -0
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_sync/__init__.py +0 -0
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_sync/_client.py +0 -0
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_synchronization.py +0 -0
- {hishel-0.0.24 → hishel-0.0.26}/hishel/_utils.py +0 -0
- {hishel-0.0.24 → hishel-0.0.26}/hishel/py.typed +0 -0
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.0.26 (12th April, 2024)
|
|
4
|
+
|
|
5
|
+
- Expose `AsyncBaseStorage` and `BaseStorage`. (#220)
|
|
6
|
+
- Prevent cache hits from resetting the ttl. (#215)
|
|
7
|
+
|
|
8
|
+
## 0.0.25 (26th March, 2024)
|
|
9
|
+
|
|
10
|
+
- Add `force_cache` property to the controller, allowing RFC9111 rules to be completely disabled. (#204)
|
|
11
|
+
- Add `.gitignore` to cache directory created by `FIleStorage`. (#197)
|
|
12
|
+
- Remove `stale_*` headers from the `CacheControl` class. (#199)
|
|
13
|
+
|
|
3
14
|
## 0.0.24 (14th February, 2024)
|
|
4
15
|
|
|
5
16
|
- Fix `botocore is not installed` exception when using any kind of storage. (#186)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: hishel
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.26
|
|
4
4
|
Summary: Persistent cache implementation for httpx and httpcore
|
|
5
5
|
Project-URL: Homepage, https://hishel.com
|
|
6
6
|
Project-URL: Source, https://github.com/karpetrosyan/hishel
|
|
@@ -175,6 +175,17 @@ Help us grow and continue developing good software for you ❤️
|
|
|
175
175
|
|
|
176
176
|
# Changelog
|
|
177
177
|
|
|
178
|
+
## 0.0.26 (12th April, 2024)
|
|
179
|
+
|
|
180
|
+
- Expose `AsyncBaseStorage` and `BaseStorage`. (#220)
|
|
181
|
+
- Prevent cache hits from resetting the ttl. (#215)
|
|
182
|
+
|
|
183
|
+
## 0.0.25 (26th March, 2024)
|
|
184
|
+
|
|
185
|
+
- Add `force_cache` property to the controller, allowing RFC9111 rules to be completely disabled. (#204)
|
|
186
|
+
- Add `.gitignore` to cache directory created by `FIleStorage`. (#197)
|
|
187
|
+
- Remove `stale_*` headers from the `CacheControl` class. (#199)
|
|
188
|
+
|
|
178
189
|
## 0.0.24 (14th February, 2024)
|
|
179
190
|
|
|
180
191
|
- Fix `botocore is not installed` exception when using any kind of storage. (#186)
|
|
@@ -30,8 +30,7 @@ class MockAsyncConnectionPool(AsyncRequestInterface):
|
|
|
30
30
|
exc_type: tp.Optional[tp.Type[BaseException]] = None,
|
|
31
31
|
exc_value: tp.Optional[BaseException] = None,
|
|
32
32
|
traceback: tp.Optional[TracebackType] = None,
|
|
33
|
-
) -> None:
|
|
34
|
-
...
|
|
33
|
+
) -> None: ...
|
|
35
34
|
|
|
36
35
|
|
|
37
36
|
class MockAsyncTransport(httpx.AsyncBaseTransport):
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import datetime
|
|
2
4
|
import types
|
|
3
5
|
import typing as tp
|
|
@@ -86,6 +88,9 @@ class AsyncCacheConnectionPool(AsyncRequestInterface):
|
|
|
86
88
|
|
|
87
89
|
stored_response, stored_request, metadata = stored_data
|
|
88
90
|
|
|
91
|
+
# Immediately read the stored response to avoid issues when trying to access the response body.
|
|
92
|
+
stored_response.read()
|
|
93
|
+
|
|
89
94
|
res = self._controller.construct_response_from_cache(
|
|
90
95
|
request=request,
|
|
91
96
|
response=stored_response,
|
|
@@ -94,55 +99,66 @@ class AsyncCacheConnectionPool(AsyncRequestInterface):
|
|
|
94
99
|
|
|
95
100
|
if isinstance(res, Response):
|
|
96
101
|
# Simply use the response if the controller determines it is ready for use.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
await self._storage.store(
|
|
100
|
-
key=key,
|
|
101
|
-
request=request,
|
|
102
|
-
response=stored_response,
|
|
103
|
-
metadata=metadata,
|
|
102
|
+
return await self._create_hishel_response(
|
|
103
|
+
key=key, response=stored_response, request=request, metadata=metadata, cached=True
|
|
104
104
|
)
|
|
105
|
-
res.extensions["from_cache"] = True # type: ignore[index]
|
|
106
|
-
res.extensions["cache_metadata"] = metadata # type: ignore[index]
|
|
107
|
-
return res
|
|
108
105
|
|
|
109
106
|
if request_cache_control.only_if_cached:
|
|
110
107
|
return generate_504()
|
|
111
108
|
|
|
112
109
|
if isinstance(res, Request):
|
|
113
|
-
#
|
|
110
|
+
# Controller has determined that the response needs to be re-validated.
|
|
114
111
|
|
|
115
112
|
try:
|
|
116
|
-
|
|
113
|
+
revalidation_response = await self._pool.handle_async_request(res)
|
|
117
114
|
except ConnectError:
|
|
115
|
+
# If there is a connection error, we can use the stale response if allowed.
|
|
118
116
|
if self._controller._allow_stale and allowed_stale(response=stored_response):
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
return await self._create_hishel_response(
|
|
118
|
+
key=key, response=stored_response, request=request, metadata=metadata, cached=True
|
|
119
|
+
)
|
|
122
120
|
raise # pragma: no cover
|
|
123
121
|
# Merge headers with the stale response.
|
|
124
|
-
|
|
125
|
-
old_response=stored_response, new_response=
|
|
122
|
+
final_response = self._controller.handle_validation_response(
|
|
123
|
+
old_response=stored_response, new_response=revalidation_response
|
|
126
124
|
)
|
|
127
125
|
|
|
128
|
-
await
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
await final_response.aread()
|
|
127
|
+
return await self._create_hishel_response(
|
|
128
|
+
key=key,
|
|
129
|
+
response=final_response,
|
|
130
|
+
request=request,
|
|
131
|
+
metadata=metadata,
|
|
132
|
+
cached=revalidation_response.status == 304,
|
|
133
|
+
)
|
|
135
134
|
|
|
136
|
-
|
|
135
|
+
regular_response = await self._pool.handle_async_request(request)
|
|
136
|
+
await regular_response.aread()
|
|
137
137
|
|
|
138
|
-
if self._controller.is_cachable(request=request, response=
|
|
139
|
-
await response.aread()
|
|
138
|
+
if self._controller.is_cachable(request=request, response=regular_response):
|
|
140
139
|
metadata = Metadata(
|
|
141
140
|
cache_key=key, created_at=datetime.datetime.now(datetime.timezone.utc), number_of_uses=0
|
|
142
141
|
)
|
|
143
|
-
await self._storage.store(key, response=
|
|
142
|
+
await self._storage.store(key, response=regular_response, request=request, metadata=metadata)
|
|
144
143
|
|
|
145
|
-
|
|
144
|
+
return await self._create_hishel_response(key=key, response=regular_response, request=request, cached=False)
|
|
145
|
+
|
|
146
|
+
async def _create_hishel_response(
|
|
147
|
+
self,
|
|
148
|
+
key: str,
|
|
149
|
+
response: Response,
|
|
150
|
+
request: Request,
|
|
151
|
+
cached: bool,
|
|
152
|
+
metadata: Metadata | None = None,
|
|
153
|
+
) -> Response:
|
|
154
|
+
if cached:
|
|
155
|
+
assert metadata
|
|
156
|
+
metadata["number_of_uses"] += 1
|
|
157
|
+
await self._storage.update_metadata(key=key, request=request, response=response, metadata=metadata)
|
|
158
|
+
response.extensions["from_cache"] = True # type: ignore[index]
|
|
159
|
+
response.extensions["cache_metadata"] = metadata # type: ignore[index]
|
|
160
|
+
else:
|
|
161
|
+
response.extensions["from_cache"] = False # type: ignore[index]
|
|
146
162
|
return response
|
|
147
163
|
|
|
148
164
|
async def aclose(self) -> None:
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
1
4
|
import logging
|
|
5
|
+
import os
|
|
2
6
|
import time
|
|
3
7
|
import typing as tp
|
|
4
8
|
import warnings
|
|
@@ -29,7 +33,14 @@ from .._utils import float_seconds_to_int_milliseconds
|
|
|
29
33
|
|
|
30
34
|
logger = logging.getLogger("hishel.storages")
|
|
31
35
|
|
|
32
|
-
__all__ = (
|
|
36
|
+
__all__ = (
|
|
37
|
+
"AsyncBaseStorage",
|
|
38
|
+
"AsyncFileStorage",
|
|
39
|
+
"AsyncRedisStorage",
|
|
40
|
+
"AsyncSQLiteStorage",
|
|
41
|
+
"AsyncInMemoryStorage",
|
|
42
|
+
"AsyncS3Storage",
|
|
43
|
+
)
|
|
33
44
|
|
|
34
45
|
StoredResponse: TypeAlias = tp.Tuple[Response, Request, Metadata]
|
|
35
46
|
|
|
@@ -48,7 +59,10 @@ class AsyncBaseStorage:
|
|
|
48
59
|
self._serializer = serializer or JSONSerializer()
|
|
49
60
|
self._ttl = ttl
|
|
50
61
|
|
|
51
|
-
async def store(self, key: str, response: Response, request: Request, metadata: Metadata) -> None:
|
|
62
|
+
async def store(self, key: str, response: Response, request: Request, metadata: Metadata | None = None) -> None:
|
|
63
|
+
raise NotImplementedError()
|
|
64
|
+
|
|
65
|
+
async def update_metadata(self, key: str, response: Response, request: Request, metadata: Metadata) -> None:
|
|
52
66
|
raise NotImplementedError()
|
|
53
67
|
|
|
54
68
|
async def retrieve(self, key: str) -> tp.Optional[StoredResponse]:
|
|
@@ -83,16 +97,21 @@ class AsyncFileStorage(AsyncBaseStorage):
|
|
|
83
97
|
super().__init__(serializer, ttl)
|
|
84
98
|
|
|
85
99
|
self._base_path = Path(base_path) if base_path is not None else Path(".cache/hishel")
|
|
100
|
+
self._gitignore_file = self._base_path / ".gitignore"
|
|
86
101
|
|
|
87
102
|
if not self._base_path.is_dir():
|
|
88
103
|
self._base_path.mkdir(parents=True)
|
|
89
104
|
|
|
105
|
+
if not self._gitignore_file.is_file():
|
|
106
|
+
with open(self._gitignore_file, "w", encoding="utf-8") as f:
|
|
107
|
+
f.write("# Automatically created by Hishel\n*")
|
|
108
|
+
|
|
90
109
|
self._file_manager = AsyncFileManager(is_binary=self._serializer.is_binary)
|
|
91
110
|
self._lock = AsyncLock()
|
|
92
111
|
self._check_ttl_every = check_ttl_every
|
|
93
112
|
self._last_cleaned = time.monotonic()
|
|
94
113
|
|
|
95
|
-
async def store(self, key: str, response: Response, request: Request, metadata: Metadata) -> None:
|
|
114
|
+
async def store(self, key: str, response: Response, request: Request, metadata: Metadata | None = None) -> None:
|
|
96
115
|
"""
|
|
97
116
|
Stores the response in the cache.
|
|
98
117
|
|
|
@@ -103,8 +122,12 @@ class AsyncFileStorage(AsyncBaseStorage):
|
|
|
103
122
|
:param request: An HTTP request
|
|
104
123
|
:type request: httpcore.Request
|
|
105
124
|
:param metadata: Additional information about the stored response
|
|
106
|
-
:type metadata: Metadata
|
|
125
|
+
:type metadata: Optional[Metadata]
|
|
107
126
|
"""
|
|
127
|
+
|
|
128
|
+
metadata = metadata or Metadata(
|
|
129
|
+
cache_key=key, created_at=datetime.datetime.now(datetime.timezone.utc), number_of_uses=0
|
|
130
|
+
)
|
|
108
131
|
response_path = self._base_path / key
|
|
109
132
|
|
|
110
133
|
async with self._lock:
|
|
@@ -114,6 +137,36 @@ class AsyncFileStorage(AsyncBaseStorage):
|
|
|
114
137
|
)
|
|
115
138
|
await self._remove_expired_caches(response_path)
|
|
116
139
|
|
|
140
|
+
async def update_metadata(self, key: str, response: Response, request: Request, metadata: Metadata) -> None:
|
|
141
|
+
"""
|
|
142
|
+
Updates the metadata of the stored response.
|
|
143
|
+
|
|
144
|
+
:param key: Hashed value of concatenated HTTP method and URI
|
|
145
|
+
:type key: str
|
|
146
|
+
:param response: An HTTP response
|
|
147
|
+
:type response: httpcore.Response
|
|
148
|
+
:param request: An HTTP request
|
|
149
|
+
:type request: httpcore.Request
|
|
150
|
+
:param metadata: Additional information about the stored response
|
|
151
|
+
:type metadata: Metadata
|
|
152
|
+
"""
|
|
153
|
+
response_path = self._base_path / key
|
|
154
|
+
|
|
155
|
+
async with self._lock:
|
|
156
|
+
if response_path.exists():
|
|
157
|
+
atime = response_path.stat().st_atime
|
|
158
|
+
old_mtime = response_path.stat().st_mtime
|
|
159
|
+
await self._file_manager.write_to(
|
|
160
|
+
str(response_path),
|
|
161
|
+
self._serializer.dumps(response=response, request=request, metadata=metadata),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Restore the old atime and mtime (we use mtime to check the cache expiration time)
|
|
165
|
+
os.utime(response_path, (atime, old_mtime))
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
return await self.store(key, response, request, metadata) # pragma: no cover
|
|
169
|
+
|
|
117
170
|
async def retrieve(self, key: str) -> tp.Optional[StoredResponse]:
|
|
118
171
|
"""
|
|
119
172
|
Retreives the response from the cache using his key.
|
|
@@ -201,7 +254,7 @@ class AsyncSQLiteStorage(AsyncBaseStorage):
|
|
|
201
254
|
await self._connection.commit()
|
|
202
255
|
self._setup_completed = True
|
|
203
256
|
|
|
204
|
-
async def store(self, key: str, response: Response, request: Request, metadata: Metadata) -> None:
|
|
257
|
+
async def store(self, key: str, response: Response, request: Request, metadata: Metadata | None = None) -> None:
|
|
205
258
|
"""
|
|
206
259
|
Stores the response in the cache.
|
|
207
260
|
|
|
@@ -212,12 +265,16 @@ class AsyncSQLiteStorage(AsyncBaseStorage):
|
|
|
212
265
|
:param request: An HTTP request
|
|
213
266
|
:type request: httpcore.Request
|
|
214
267
|
:param metadata: Additioal information about the stored response
|
|
215
|
-
:type metadata: Metadata
|
|
268
|
+
:type metadata: Optional[Metadata]
|
|
216
269
|
"""
|
|
217
270
|
|
|
218
271
|
await self._setup()
|
|
219
272
|
assert self._connection
|
|
220
273
|
|
|
274
|
+
metadata = metadata or Metadata(
|
|
275
|
+
cache_key=key, created_at=datetime.datetime.now(datetime.timezone.utc), number_of_uses=0
|
|
276
|
+
)
|
|
277
|
+
|
|
221
278
|
async with self._lock:
|
|
222
279
|
await self._connection.execute("DELETE FROM cache WHERE key = ?", [key])
|
|
223
280
|
serialized_response = self._serializer.dumps(response=response, request=request, metadata=metadata)
|
|
@@ -227,6 +284,33 @@ class AsyncSQLiteStorage(AsyncBaseStorage):
|
|
|
227
284
|
await self._connection.commit()
|
|
228
285
|
await self._remove_expired_caches()
|
|
229
286
|
|
|
287
|
+
async def update_metadata(self, key: str, response: Response, request: Request, metadata: Metadata) -> None:
|
|
288
|
+
"""
|
|
289
|
+
Updates the metadata of the stored response.
|
|
290
|
+
|
|
291
|
+
:param key: Hashed value of concatenated HTTP method and URI
|
|
292
|
+
:type key: str
|
|
293
|
+
:param response: An HTTP response
|
|
294
|
+
:type response: httpcore.Response
|
|
295
|
+
:param request: An HTTP request
|
|
296
|
+
:type request: httpcore.Request
|
|
297
|
+
:param metadata: Additional information about the stored response
|
|
298
|
+
:type metadata: Metadata
|
|
299
|
+
"""
|
|
300
|
+
|
|
301
|
+
await self._setup()
|
|
302
|
+
assert self._connection
|
|
303
|
+
|
|
304
|
+
async with self._lock:
|
|
305
|
+
cursor = await self._connection.execute("SELECT data FROM cache WHERE key = ?", [key])
|
|
306
|
+
row = await cursor.fetchone()
|
|
307
|
+
if row is not None:
|
|
308
|
+
serialized_response = self._serializer.dumps(response=response, request=request, metadata=metadata)
|
|
309
|
+
await self._connection.execute("UPDATE cache SET data = ? WHERE key = ?", [serialized_response, key])
|
|
310
|
+
await self._connection.commit()
|
|
311
|
+
return
|
|
312
|
+
return await self.store(key, response, request, metadata) # pragma: no cover
|
|
313
|
+
|
|
230
314
|
async def retrieve(self, key: str) -> tp.Optional[StoredResponse]:
|
|
231
315
|
"""
|
|
232
316
|
Retreives the response from the cache using his key.
|
|
@@ -297,7 +381,7 @@ class AsyncRedisStorage(AsyncBaseStorage):
|
|
|
297
381
|
else: # pragma: no cover
|
|
298
382
|
self._client = client
|
|
299
383
|
|
|
300
|
-
async def store(self, key: str, response: Response, request: Request, metadata: Metadata) -> None:
|
|
384
|
+
async def store(self, key: str, response: Response, request: Request, metadata: Metadata | None = None) -> None:
|
|
301
385
|
"""
|
|
302
386
|
Stores the response in the cache.
|
|
303
387
|
|
|
@@ -308,9 +392,13 @@ class AsyncRedisStorage(AsyncBaseStorage):
|
|
|
308
392
|
:param request: An HTTP request
|
|
309
393
|
:type request: httpcore.Request
|
|
310
394
|
:param metadata: Additioal information about the stored response
|
|
311
|
-
:type metadata: Metadata
|
|
395
|
+
:type metadata: Optional[Metadata]
|
|
312
396
|
"""
|
|
313
397
|
|
|
398
|
+
metadata = metadata or Metadata(
|
|
399
|
+
cache_key=key, created_at=datetime.datetime.now(datetime.timezone.utc), number_of_uses=0
|
|
400
|
+
)
|
|
401
|
+
|
|
314
402
|
if self._ttl is not None:
|
|
315
403
|
px = float_seconds_to_int_milliseconds(self._ttl)
|
|
316
404
|
else:
|
|
@@ -320,6 +408,31 @@ class AsyncRedisStorage(AsyncBaseStorage):
|
|
|
320
408
|
key, self._serializer.dumps(response=response, request=request, metadata=metadata), px=px
|
|
321
409
|
)
|
|
322
410
|
|
|
411
|
+
async def update_metadata(self, key: str, response: Response, request: Request, metadata: Metadata) -> None:
|
|
412
|
+
"""
|
|
413
|
+
Updates the metadata of the stored response.
|
|
414
|
+
|
|
415
|
+
:param key: Hashed value of concatenated HTTP method and URI
|
|
416
|
+
:type key: str
|
|
417
|
+
:param response: An HTTP response
|
|
418
|
+
:type response: httpcore.Response
|
|
419
|
+
:param request: An HTTP request
|
|
420
|
+
:type request: httpcore.Request
|
|
421
|
+
:param metadata: Additional information about the stored response
|
|
422
|
+
:type metadata: Metadata
|
|
423
|
+
"""
|
|
424
|
+
|
|
425
|
+
ttl_in_milliseconds = await self._client.pttl(key)
|
|
426
|
+
|
|
427
|
+
if ttl_in_milliseconds == -2: # pragma: no cover
|
|
428
|
+
await self.store(key, response, request, metadata)
|
|
429
|
+
else:
|
|
430
|
+
await self._client.set(
|
|
431
|
+
key,
|
|
432
|
+
self._serializer.dumps(response=response, request=request, metadata=metadata),
|
|
433
|
+
px=ttl_in_milliseconds,
|
|
434
|
+
)
|
|
435
|
+
|
|
323
436
|
async def retrieve(self, key: str) -> tp.Optional[StoredResponse]:
|
|
324
437
|
"""
|
|
325
438
|
Retreives the response from the cache using his key.
|
|
@@ -368,7 +481,7 @@ class AsyncInMemoryStorage(AsyncBaseStorage):
|
|
|
368
481
|
self._cache: LFUCache[str, tp.Tuple[StoredResponse, float]] = LFUCache(capacity=capacity)
|
|
369
482
|
self._lock = AsyncLock()
|
|
370
483
|
|
|
371
|
-
async def store(self, key: str, response: Response, request: Request, metadata: Metadata) -> None:
|
|
484
|
+
async def store(self, key: str, response: Response, request: Request, metadata: Metadata | None = None) -> None:
|
|
372
485
|
"""
|
|
373
486
|
Stores the response in the cache.
|
|
374
487
|
|
|
@@ -379,9 +492,13 @@ class AsyncInMemoryStorage(AsyncBaseStorage):
|
|
|
379
492
|
:param request: An HTTP request
|
|
380
493
|
:type request: httpcore.Request
|
|
381
494
|
:param metadata: Additioal information about the stored response
|
|
382
|
-
:type metadata: Metadata
|
|
495
|
+
:type metadata: Optional[Metadata]
|
|
383
496
|
"""
|
|
384
497
|
|
|
498
|
+
metadata = metadata or Metadata(
|
|
499
|
+
cache_key=key, created_at=datetime.datetime.now(datetime.timezone.utc), number_of_uses=0
|
|
500
|
+
)
|
|
501
|
+
|
|
385
502
|
async with self._lock:
|
|
386
503
|
response_clone = clone_model(response)
|
|
387
504
|
request_clone = clone_model(request)
|
|
@@ -389,6 +506,30 @@ class AsyncInMemoryStorage(AsyncBaseStorage):
|
|
|
389
506
|
self._cache.put(key, (stored_response, time.monotonic()))
|
|
390
507
|
await self._remove_expired_caches()
|
|
391
508
|
|
|
509
|
+
async def update_metadata(self, key: str, response: Response, request: Request, metadata: Metadata) -> None:
|
|
510
|
+
"""
|
|
511
|
+
Updates the metadata of the stored response.
|
|
512
|
+
|
|
513
|
+
:param key: Hashed value of concatenated HTTP method and URI
|
|
514
|
+
:type key: str
|
|
515
|
+
:param response: An HTTP response
|
|
516
|
+
:type response: httpcore.Response
|
|
517
|
+
:param request: An HTTP request
|
|
518
|
+
:type request: httpcore.Request
|
|
519
|
+
:param metadata: Additional information about the stored response
|
|
520
|
+
:type metadata: Metadata
|
|
521
|
+
"""
|
|
522
|
+
|
|
523
|
+
async with self._lock:
|
|
524
|
+
try:
|
|
525
|
+
stored_response, created_at = self._cache.get(key)
|
|
526
|
+
stored_response = (stored_response[0], stored_response[1], metadata)
|
|
527
|
+
self._cache.put(key, (stored_response, created_at))
|
|
528
|
+
return
|
|
529
|
+
except KeyError: # pragma: no cover
|
|
530
|
+
pass
|
|
531
|
+
await self.store(key, response, request, metadata) # pragma: no cover
|
|
532
|
+
|
|
392
533
|
async def retrieve(self, key: str) -> tp.Optional[StoredResponse]:
|
|
393
534
|
"""
|
|
394
535
|
Retreives the response from the cache using his key.
|
|
@@ -427,7 +568,7 @@ class AsyncInMemoryStorage(AsyncBaseStorage):
|
|
|
427
568
|
self._cache.remove_key(key)
|
|
428
569
|
|
|
429
570
|
|
|
430
|
-
class AsyncS3Storage(AsyncBaseStorage):
|
|
571
|
+
class AsyncS3Storage(AsyncBaseStorage): # pragma: no cover
|
|
431
572
|
"""
|
|
432
573
|
AWS S3 storage.
|
|
433
574
|
|
|
@@ -473,7 +614,7 @@ class AsyncS3Storage(AsyncBaseStorage):
|
|
|
473
614
|
)
|
|
474
615
|
self._lock = AsyncLock()
|
|
475
616
|
|
|
476
|
-
async def store(self, key: str, response: Response, request: Request, metadata: Metadata) -> None:
|
|
617
|
+
async def store(self, key: str, response: Response, request: Request, metadata: Metadata | None = None) -> None:
|
|
477
618
|
"""
|
|
478
619
|
Stores the response in the cache.
|
|
479
620
|
|
|
@@ -484,15 +625,37 @@ class AsyncS3Storage(AsyncBaseStorage):
|
|
|
484
625
|
:param request: An HTTP request
|
|
485
626
|
:type request: httpcore.Request
|
|
486
627
|
:param metadata: Additioal information about the stored response
|
|
487
|
-
:type metadata: Metadata`
|
|
628
|
+
:type metadata: Optional[Metadata]`
|
|
488
629
|
"""
|
|
489
630
|
|
|
631
|
+
metadata = metadata or Metadata(
|
|
632
|
+
cache_key=key, created_at=datetime.datetime.now(datetime.timezone.utc), number_of_uses=0
|
|
633
|
+
)
|
|
634
|
+
|
|
490
635
|
async with self._lock:
|
|
491
636
|
serialized = self._serializer.dumps(response=response, request=request, metadata=metadata)
|
|
492
637
|
await self._s3_manager.write_to(path=key, data=serialized)
|
|
493
638
|
|
|
494
639
|
await self._remove_expired_caches(key)
|
|
495
640
|
|
|
641
|
+
async def update_metadata(self, key: str, response: Response, request: Request, metadata: Metadata) -> None:
|
|
642
|
+
"""
|
|
643
|
+
Updates the metadata of the stored response.
|
|
644
|
+
|
|
645
|
+
:param key: Hashed value of concatenated HTTP method and URI
|
|
646
|
+
:type key: str
|
|
647
|
+
:param response: An HTTP response
|
|
648
|
+
:type response: httpcore.Response
|
|
649
|
+
:param request: An HTTP request
|
|
650
|
+
:type request: httpcore.Request
|
|
651
|
+
:param metadata: Additional information about the stored response
|
|
652
|
+
:type metadata: Metadata
|
|
653
|
+
"""
|
|
654
|
+
|
|
655
|
+
async with self._lock:
|
|
656
|
+
serialized = self._serializer.dumps(response=response, request=request, metadata=metadata)
|
|
657
|
+
await self._s3_manager.write_to(path=key, data=serialized, only_metadata=True)
|
|
658
|
+
|
|
496
659
|
async def retrieve(self, key: str) -> tp.Optional[StoredResponse]:
|
|
497
660
|
"""
|
|
498
661
|
Retreives the response from the cache using his key.
|