hishel 0.0.25__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.25 → hishel-0.0.26}/CHANGELOG.md +5 -0
- {hishel-0.0.25 → hishel-0.0.26}/PKG-INFO +6 -1
- {hishel-0.0.25 → hishel-0.0.26}/hishel/__init__.py +1 -1
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_async/_pool.py +45 -29
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_async/_storages.py +171 -13
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_async/_transports.py +72 -72
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_s3.py +27 -8
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_sync/_pool.py +45 -29
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_sync/_storages.py +171 -13
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_sync/_transports.py +72 -72
- {hishel-0.0.25 → hishel-0.0.26}/.gitignore +0 -0
- {hishel-0.0.25 → hishel-0.0.26}/LICENSE +0 -0
- {hishel-0.0.25 → hishel-0.0.26}/README.md +0 -0
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_async/__init__.py +0 -0
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_async/_client.py +0 -0
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_async/_mock.py +0 -0
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_controller.py +0 -0
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_exceptions.py +0 -0
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_files.py +0 -0
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_headers.py +0 -0
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_lfu_cache.py +0 -0
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_serializers.py +0 -0
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_sync/__init__.py +0 -0
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_sync/_client.py +0 -0
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_sync/_mock.py +0 -0
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_synchronization.py +0 -0
- {hishel-0.0.25 → hishel-0.0.26}/hishel/_utils.py +0 -0
- {hishel-0.0.25 → hishel-0.0.26}/hishel/py.typed +0 -0
- {hishel-0.0.25 → hishel-0.0.26}/pyproject.toml +0 -0
|
@@ -1,5 +1,10 @@
|
|
|
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
|
+
|
|
3
8
|
## 0.0.25 (26th March, 2024)
|
|
4
9
|
|
|
5
10
|
- Add `force_cache` property to the controller, allowing RFC9111 rules to be completely disabled. (#204)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
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,11 @@ 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
|
+
|
|
178
183
|
## 0.0.25 (26th March, 2024)
|
|
179
184
|
|
|
180
185
|
- Add `force_cache` property to the controller, allowing RFC9111 rules to be completely disabled. (#204)
|
|
@@ -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]:
|
|
@@ -97,7 +111,7 @@ class AsyncFileStorage(AsyncBaseStorage):
|
|
|
97
111
|
self._check_ttl_every = check_ttl_every
|
|
98
112
|
self._last_cleaned = time.monotonic()
|
|
99
113
|
|
|
100
|
-
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:
|
|
101
115
|
"""
|
|
102
116
|
Stores the response in the cache.
|
|
103
117
|
|
|
@@ -108,8 +122,12 @@ class AsyncFileStorage(AsyncBaseStorage):
|
|
|
108
122
|
:param request: An HTTP request
|
|
109
123
|
:type request: httpcore.Request
|
|
110
124
|
:param metadata: Additional information about the stored response
|
|
111
|
-
:type metadata: Metadata
|
|
125
|
+
:type metadata: Optional[Metadata]
|
|
112
126
|
"""
|
|
127
|
+
|
|
128
|
+
metadata = metadata or Metadata(
|
|
129
|
+
cache_key=key, created_at=datetime.datetime.now(datetime.timezone.utc), number_of_uses=0
|
|
130
|
+
)
|
|
113
131
|
response_path = self._base_path / key
|
|
114
132
|
|
|
115
133
|
async with self._lock:
|
|
@@ -119,6 +137,36 @@ class AsyncFileStorage(AsyncBaseStorage):
|
|
|
119
137
|
)
|
|
120
138
|
await self._remove_expired_caches(response_path)
|
|
121
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
|
+
|
|
122
170
|
async def retrieve(self, key: str) -> tp.Optional[StoredResponse]:
|
|
123
171
|
"""
|
|
124
172
|
Retreives the response from the cache using his key.
|
|
@@ -206,7 +254,7 @@ class AsyncSQLiteStorage(AsyncBaseStorage):
|
|
|
206
254
|
await self._connection.commit()
|
|
207
255
|
self._setup_completed = True
|
|
208
256
|
|
|
209
|
-
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:
|
|
210
258
|
"""
|
|
211
259
|
Stores the response in the cache.
|
|
212
260
|
|
|
@@ -217,12 +265,16 @@ class AsyncSQLiteStorage(AsyncBaseStorage):
|
|
|
217
265
|
:param request: An HTTP request
|
|
218
266
|
:type request: httpcore.Request
|
|
219
267
|
:param metadata: Additioal information about the stored response
|
|
220
|
-
:type metadata: Metadata
|
|
268
|
+
:type metadata: Optional[Metadata]
|
|
221
269
|
"""
|
|
222
270
|
|
|
223
271
|
await self._setup()
|
|
224
272
|
assert self._connection
|
|
225
273
|
|
|
274
|
+
metadata = metadata or Metadata(
|
|
275
|
+
cache_key=key, created_at=datetime.datetime.now(datetime.timezone.utc), number_of_uses=0
|
|
276
|
+
)
|
|
277
|
+
|
|
226
278
|
async with self._lock:
|
|
227
279
|
await self._connection.execute("DELETE FROM cache WHERE key = ?", [key])
|
|
228
280
|
serialized_response = self._serializer.dumps(response=response, request=request, metadata=metadata)
|
|
@@ -232,6 +284,33 @@ class AsyncSQLiteStorage(AsyncBaseStorage):
|
|
|
232
284
|
await self._connection.commit()
|
|
233
285
|
await self._remove_expired_caches()
|
|
234
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
|
+
|
|
235
314
|
async def retrieve(self, key: str) -> tp.Optional[StoredResponse]:
|
|
236
315
|
"""
|
|
237
316
|
Retreives the response from the cache using his key.
|
|
@@ -302,7 +381,7 @@ class AsyncRedisStorage(AsyncBaseStorage):
|
|
|
302
381
|
else: # pragma: no cover
|
|
303
382
|
self._client = client
|
|
304
383
|
|
|
305
|
-
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:
|
|
306
385
|
"""
|
|
307
386
|
Stores the response in the cache.
|
|
308
387
|
|
|
@@ -313,9 +392,13 @@ class AsyncRedisStorage(AsyncBaseStorage):
|
|
|
313
392
|
:param request: An HTTP request
|
|
314
393
|
:type request: httpcore.Request
|
|
315
394
|
:param metadata: Additioal information about the stored response
|
|
316
|
-
:type metadata: Metadata
|
|
395
|
+
:type metadata: Optional[Metadata]
|
|
317
396
|
"""
|
|
318
397
|
|
|
398
|
+
metadata = metadata or Metadata(
|
|
399
|
+
cache_key=key, created_at=datetime.datetime.now(datetime.timezone.utc), number_of_uses=0
|
|
400
|
+
)
|
|
401
|
+
|
|
319
402
|
if self._ttl is not None:
|
|
320
403
|
px = float_seconds_to_int_milliseconds(self._ttl)
|
|
321
404
|
else:
|
|
@@ -325,6 +408,31 @@ class AsyncRedisStorage(AsyncBaseStorage):
|
|
|
325
408
|
key, self._serializer.dumps(response=response, request=request, metadata=metadata), px=px
|
|
326
409
|
)
|
|
327
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
|
+
|
|
328
436
|
async def retrieve(self, key: str) -> tp.Optional[StoredResponse]:
|
|
329
437
|
"""
|
|
330
438
|
Retreives the response from the cache using his key.
|
|
@@ -373,7 +481,7 @@ class AsyncInMemoryStorage(AsyncBaseStorage):
|
|
|
373
481
|
self._cache: LFUCache[str, tp.Tuple[StoredResponse, float]] = LFUCache(capacity=capacity)
|
|
374
482
|
self._lock = AsyncLock()
|
|
375
483
|
|
|
376
|
-
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:
|
|
377
485
|
"""
|
|
378
486
|
Stores the response in the cache.
|
|
379
487
|
|
|
@@ -384,9 +492,13 @@ class AsyncInMemoryStorage(AsyncBaseStorage):
|
|
|
384
492
|
:param request: An HTTP request
|
|
385
493
|
:type request: httpcore.Request
|
|
386
494
|
:param metadata: Additioal information about the stored response
|
|
387
|
-
:type metadata: Metadata
|
|
495
|
+
:type metadata: Optional[Metadata]
|
|
388
496
|
"""
|
|
389
497
|
|
|
498
|
+
metadata = metadata or Metadata(
|
|
499
|
+
cache_key=key, created_at=datetime.datetime.now(datetime.timezone.utc), number_of_uses=0
|
|
500
|
+
)
|
|
501
|
+
|
|
390
502
|
async with self._lock:
|
|
391
503
|
response_clone = clone_model(response)
|
|
392
504
|
request_clone = clone_model(request)
|
|
@@ -394,6 +506,30 @@ class AsyncInMemoryStorage(AsyncBaseStorage):
|
|
|
394
506
|
self._cache.put(key, (stored_response, time.monotonic()))
|
|
395
507
|
await self._remove_expired_caches()
|
|
396
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
|
+
|
|
397
533
|
async def retrieve(self, key: str) -> tp.Optional[StoredResponse]:
|
|
398
534
|
"""
|
|
399
535
|
Retreives the response from the cache using his key.
|
|
@@ -432,7 +568,7 @@ class AsyncInMemoryStorage(AsyncBaseStorage):
|
|
|
432
568
|
self._cache.remove_key(key)
|
|
433
569
|
|
|
434
570
|
|
|
435
|
-
class AsyncS3Storage(AsyncBaseStorage):
|
|
571
|
+
class AsyncS3Storage(AsyncBaseStorage): # pragma: no cover
|
|
436
572
|
"""
|
|
437
573
|
AWS S3 storage.
|
|
438
574
|
|
|
@@ -478,7 +614,7 @@ class AsyncS3Storage(AsyncBaseStorage):
|
|
|
478
614
|
)
|
|
479
615
|
self._lock = AsyncLock()
|
|
480
616
|
|
|
481
|
-
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:
|
|
482
618
|
"""
|
|
483
619
|
Stores the response in the cache.
|
|
484
620
|
|
|
@@ -489,15 +625,37 @@ class AsyncS3Storage(AsyncBaseStorage):
|
|
|
489
625
|
:param request: An HTTP request
|
|
490
626
|
:type request: httpcore.Request
|
|
491
627
|
:param metadata: Additioal information about the stored response
|
|
492
|
-
:type metadata: Metadata`
|
|
628
|
+
:type metadata: Optional[Metadata]`
|
|
493
629
|
"""
|
|
494
630
|
|
|
631
|
+
metadata = metadata or Metadata(
|
|
632
|
+
cache_key=key, created_at=datetime.datetime.now(datetime.timezone.utc), number_of_uses=0
|
|
633
|
+
)
|
|
634
|
+
|
|
495
635
|
async with self._lock:
|
|
496
636
|
serialized = self._serializer.dumps(response=response, request=request, metadata=metadata)
|
|
497
637
|
await self._s3_manager.write_to(path=key, data=serialized)
|
|
498
638
|
|
|
499
639
|
await self._remove_expired_caches(key)
|
|
500
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
|
+
|
|
501
659
|
async def retrieve(self, key: str) -> tp.Optional[StoredResponse]:
|
|
502
660
|
"""
|
|
503
661
|
Retreives the response from the cache using his key.
|