hishel 0.0.25__tar.gz → 0.0.27__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.27}/CHANGELOG.md +9 -0
- {hishel-0.0.25 → hishel-0.0.27}/PKG-INFO +10 -1
- {hishel-0.0.25 → hishel-0.0.27}/hishel/__init__.py +1 -1
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_async/_pool.py +45 -29
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_async/_storages.py +173 -13
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_async/_transports.py +72 -72
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_s3.py +27 -8
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_sync/_pool.py +45 -29
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_sync/_storages.py +173 -13
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_sync/_transports.py +72 -72
- {hishel-0.0.25 → hishel-0.0.27}/.gitignore +0 -0
- {hishel-0.0.25 → hishel-0.0.27}/LICENSE +0 -0
- {hishel-0.0.25 → hishel-0.0.27}/README.md +0 -0
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_async/__init__.py +0 -0
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_async/_client.py +0 -0
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_async/_mock.py +0 -0
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_controller.py +0 -0
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_exceptions.py +0 -0
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_files.py +0 -0
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_headers.py +0 -0
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_lfu_cache.py +0 -0
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_serializers.py +0 -0
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_sync/__init__.py +0 -0
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_sync/_client.py +0 -0
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_sync/_mock.py +0 -0
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_synchronization.py +0 -0
- {hishel-0.0.25 → hishel-0.0.27}/hishel/_utils.py +0 -0
- {hishel-0.0.25 → hishel-0.0.27}/hishel/py.typed +0 -0
- {hishel-0.0.25 → hishel-0.0.27}/pyproject.toml +0 -0
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.0.27 (31th May, 2024)
|
|
4
|
+
|
|
5
|
+
- Fix `RedisStorage` when using without ttl. (#231)
|
|
6
|
+
|
|
7
|
+
## 0.0.26 (12th April, 2024)
|
|
8
|
+
|
|
9
|
+
- Expose `AsyncBaseStorage` and `BaseStorage`. (#220)
|
|
10
|
+
- Prevent cache hits from resetting the ttl. (#215)
|
|
11
|
+
|
|
3
12
|
## 0.0.25 (26th March, 2024)
|
|
4
13
|
|
|
5
14
|
- 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.27
|
|
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,15 @@ Help us grow and continue developing good software for you ❤️
|
|
|
175
175
|
|
|
176
176
|
# Changelog
|
|
177
177
|
|
|
178
|
+
## 0.0.27 (31th May, 2024)
|
|
179
|
+
|
|
180
|
+
- Fix `RedisStorage` when using without ttl. (#231)
|
|
181
|
+
|
|
182
|
+
## 0.0.26 (12th April, 2024)
|
|
183
|
+
|
|
184
|
+
- Expose `AsyncBaseStorage` and `BaseStorage`. (#220)
|
|
185
|
+
- Prevent cache hits from resetting the ttl. (#215)
|
|
186
|
+
|
|
178
187
|
## 0.0.25 (26th March, 2024)
|
|
179
188
|
|
|
180
189
|
- 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,33 @@ 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
|
+
# -2: if the key does not exist in Redis
|
|
428
|
+
# -1: if the key exists in Redis but has no expiration
|
|
429
|
+
if ttl_in_milliseconds == -2 or ttl_in_milliseconds == -1: # pragma: no cover
|
|
430
|
+
await self.store(key, response, request, metadata)
|
|
431
|
+
else:
|
|
432
|
+
await self._client.set(
|
|
433
|
+
key,
|
|
434
|
+
self._serializer.dumps(response=response, request=request, metadata=metadata),
|
|
435
|
+
px=ttl_in_milliseconds,
|
|
436
|
+
)
|
|
437
|
+
|
|
328
438
|
async def retrieve(self, key: str) -> tp.Optional[StoredResponse]:
|
|
329
439
|
"""
|
|
330
440
|
Retreives the response from the cache using his key.
|
|
@@ -373,7 +483,7 @@ class AsyncInMemoryStorage(AsyncBaseStorage):
|
|
|
373
483
|
self._cache: LFUCache[str, tp.Tuple[StoredResponse, float]] = LFUCache(capacity=capacity)
|
|
374
484
|
self._lock = AsyncLock()
|
|
375
485
|
|
|
376
|
-
async def store(self, key: str, response: Response, request: Request, metadata: Metadata) -> None:
|
|
486
|
+
async def store(self, key: str, response: Response, request: Request, metadata: Metadata | None = None) -> None:
|
|
377
487
|
"""
|
|
378
488
|
Stores the response in the cache.
|
|
379
489
|
|
|
@@ -384,9 +494,13 @@ class AsyncInMemoryStorage(AsyncBaseStorage):
|
|
|
384
494
|
:param request: An HTTP request
|
|
385
495
|
:type request: httpcore.Request
|
|
386
496
|
:param metadata: Additioal information about the stored response
|
|
387
|
-
:type metadata: Metadata
|
|
497
|
+
:type metadata: Optional[Metadata]
|
|
388
498
|
"""
|
|
389
499
|
|
|
500
|
+
metadata = metadata or Metadata(
|
|
501
|
+
cache_key=key, created_at=datetime.datetime.now(datetime.timezone.utc), number_of_uses=0
|
|
502
|
+
)
|
|
503
|
+
|
|
390
504
|
async with self._lock:
|
|
391
505
|
response_clone = clone_model(response)
|
|
392
506
|
request_clone = clone_model(request)
|
|
@@ -394,6 +508,30 @@ class AsyncInMemoryStorage(AsyncBaseStorage):
|
|
|
394
508
|
self._cache.put(key, (stored_response, time.monotonic()))
|
|
395
509
|
await self._remove_expired_caches()
|
|
396
510
|
|
|
511
|
+
async def update_metadata(self, key: str, response: Response, request: Request, metadata: Metadata) -> None:
|
|
512
|
+
"""
|
|
513
|
+
Updates the metadata of the stored response.
|
|
514
|
+
|
|
515
|
+
:param key: Hashed value of concatenated HTTP method and URI
|
|
516
|
+
:type key: str
|
|
517
|
+
:param response: An HTTP response
|
|
518
|
+
:type response: httpcore.Response
|
|
519
|
+
:param request: An HTTP request
|
|
520
|
+
:type request: httpcore.Request
|
|
521
|
+
:param metadata: Additional information about the stored response
|
|
522
|
+
:type metadata: Metadata
|
|
523
|
+
"""
|
|
524
|
+
|
|
525
|
+
async with self._lock:
|
|
526
|
+
try:
|
|
527
|
+
stored_response, created_at = self._cache.get(key)
|
|
528
|
+
stored_response = (stored_response[0], stored_response[1], metadata)
|
|
529
|
+
self._cache.put(key, (stored_response, created_at))
|
|
530
|
+
return
|
|
531
|
+
except KeyError: # pragma: no cover
|
|
532
|
+
pass
|
|
533
|
+
await self.store(key, response, request, metadata) # pragma: no cover
|
|
534
|
+
|
|
397
535
|
async def retrieve(self, key: str) -> tp.Optional[StoredResponse]:
|
|
398
536
|
"""
|
|
399
537
|
Retreives the response from the cache using his key.
|
|
@@ -432,7 +570,7 @@ class AsyncInMemoryStorage(AsyncBaseStorage):
|
|
|
432
570
|
self._cache.remove_key(key)
|
|
433
571
|
|
|
434
572
|
|
|
435
|
-
class AsyncS3Storage(AsyncBaseStorage):
|
|
573
|
+
class AsyncS3Storage(AsyncBaseStorage): # pragma: no cover
|
|
436
574
|
"""
|
|
437
575
|
AWS S3 storage.
|
|
438
576
|
|
|
@@ -478,7 +616,7 @@ class AsyncS3Storage(AsyncBaseStorage):
|
|
|
478
616
|
)
|
|
479
617
|
self._lock = AsyncLock()
|
|
480
618
|
|
|
481
|
-
async def store(self, key: str, response: Response, request: Request, metadata: Metadata) -> None:
|
|
619
|
+
async def store(self, key: str, response: Response, request: Request, metadata: Metadata | None = None) -> None:
|
|
482
620
|
"""
|
|
483
621
|
Stores the response in the cache.
|
|
484
622
|
|
|
@@ -489,15 +627,37 @@ class AsyncS3Storage(AsyncBaseStorage):
|
|
|
489
627
|
:param request: An HTTP request
|
|
490
628
|
:type request: httpcore.Request
|
|
491
629
|
:param metadata: Additioal information about the stored response
|
|
492
|
-
:type metadata: Metadata`
|
|
630
|
+
:type metadata: Optional[Metadata]`
|
|
493
631
|
"""
|
|
494
632
|
|
|
633
|
+
metadata = metadata or Metadata(
|
|
634
|
+
cache_key=key, created_at=datetime.datetime.now(datetime.timezone.utc), number_of_uses=0
|
|
635
|
+
)
|
|
636
|
+
|
|
495
637
|
async with self._lock:
|
|
496
638
|
serialized = self._serializer.dumps(response=response, request=request, metadata=metadata)
|
|
497
639
|
await self._s3_manager.write_to(path=key, data=serialized)
|
|
498
640
|
|
|
499
641
|
await self._remove_expired_caches(key)
|
|
500
642
|
|
|
643
|
+
async def update_metadata(self, key: str, response: Response, request: Request, metadata: Metadata) -> None:
|
|
644
|
+
"""
|
|
645
|
+
Updates the metadata of the stored response.
|
|
646
|
+
|
|
647
|
+
:param key: Hashed value of concatenated HTTP method and URI
|
|
648
|
+
:type key: str
|
|
649
|
+
:param response: An HTTP response
|
|
650
|
+
:type response: httpcore.Response
|
|
651
|
+
:param request: An HTTP request
|
|
652
|
+
:type request: httpcore.Request
|
|
653
|
+
:param metadata: Additional information about the stored response
|
|
654
|
+
:type metadata: Metadata
|
|
655
|
+
"""
|
|
656
|
+
|
|
657
|
+
async with self._lock:
|
|
658
|
+
serialized = self._serializer.dumps(response=response, request=request, metadata=metadata)
|
|
659
|
+
await self._s3_manager.write_to(path=key, data=serialized, only_metadata=True)
|
|
660
|
+
|
|
501
661
|
async def retrieve(self, key: str) -> tp.Optional[StoredResponse]:
|
|
502
662
|
"""
|
|
503
663
|
Retreives the response from the cache using his key.
|