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.
Files changed (30) hide show
  1. {hishel-0.0.24 → hishel-0.0.26}/CHANGELOG.md +11 -0
  2. {hishel-0.0.24 → hishel-0.0.26}/PKG-INFO +13 -2
  3. {hishel-0.0.24 → hishel-0.0.26}/hishel/__init__.py +1 -1
  4. {hishel-0.0.24 → hishel-0.0.26}/hishel/_async/_mock.py +1 -2
  5. {hishel-0.0.24 → hishel-0.0.26}/hishel/_async/_pool.py +45 -29
  6. {hishel-0.0.24 → hishel-0.0.26}/hishel/_async/_storages.py +176 -13
  7. {hishel-0.0.24 → hishel-0.0.26}/hishel/_async/_transports.py +72 -72
  8. {hishel-0.0.24 → hishel-0.0.26}/hishel/_controller.py +6 -2
  9. hishel-0.0.26/hishel/_exceptions.py +10 -0
  10. {hishel-0.0.24 → hishel-0.0.26}/hishel/_headers.py +0 -6
  11. {hishel-0.0.24 → hishel-0.0.26}/hishel/_s3.py +27 -8
  12. {hishel-0.0.24 → hishel-0.0.26}/hishel/_sync/_mock.py +1 -2
  13. {hishel-0.0.24 → hishel-0.0.26}/hishel/_sync/_pool.py +45 -29
  14. {hishel-0.0.24 → hishel-0.0.26}/hishel/_sync/_storages.py +176 -13
  15. {hishel-0.0.24 → hishel-0.0.26}/hishel/_sync/_transports.py +72 -72
  16. {hishel-0.0.24 → hishel-0.0.26}/pyproject.toml +4 -2
  17. hishel-0.0.24/hishel/_exceptions.py +0 -13
  18. {hishel-0.0.24 → hishel-0.0.26}/.gitignore +0 -0
  19. {hishel-0.0.24 → hishel-0.0.26}/LICENSE +0 -0
  20. {hishel-0.0.24 → hishel-0.0.26}/README.md +0 -0
  21. {hishel-0.0.24 → hishel-0.0.26}/hishel/_async/__init__.py +0 -0
  22. {hishel-0.0.24 → hishel-0.0.26}/hishel/_async/_client.py +0 -0
  23. {hishel-0.0.24 → hishel-0.0.26}/hishel/_files.py +0 -0
  24. {hishel-0.0.24 → hishel-0.0.26}/hishel/_lfu_cache.py +0 -0
  25. {hishel-0.0.24 → hishel-0.0.26}/hishel/_serializers.py +0 -0
  26. {hishel-0.0.24 → hishel-0.0.26}/hishel/_sync/__init__.py +0 -0
  27. {hishel-0.0.24 → hishel-0.0.26}/hishel/_sync/_client.py +0 -0
  28. {hishel-0.0.24 → hishel-0.0.26}/hishel/_synchronization.py +0 -0
  29. {hishel-0.0.24 → hishel-0.0.26}/hishel/_utils.py +0 -0
  30. {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
1
+ Metadata-Version: 2.3
2
2
  Name: hishel
3
- Version: 0.0.24
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)
@@ -14,4 +14,4 @@ def install_cache() -> None: # pragma: no cover
14
14
  httpx.Client = CacheClient # type: ignore
15
15
 
16
16
 
17
- __version__ = "0.0.24"
17
+ __version__ = "0.0.26"
@@ -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
- metadata["number_of_uses"] += 1
98
- stored_response.read()
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
- # Re-validating the response.
110
+ # Controller has determined that the response needs to be re-validated.
114
111
 
115
112
  try:
116
- response = await self._pool.handle_async_request(res)
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
- stored_response.extensions["from_cache"] = True # type: ignore[index]
120
- stored_response.extensions["cache_metadata"] = metadata # type: ignore[index]
121
- return stored_response
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
- full_response = self._controller.handle_validation_response(
125
- old_response=stored_response, new_response=response
122
+ final_response = self._controller.handle_validation_response(
123
+ old_response=stored_response, new_response=revalidation_response
126
124
  )
127
125
 
128
- await full_response.aread()
129
- metadata["number_of_uses"] += response.status == 304
130
- await self._storage.store(key, response=full_response, request=request, metadata=metadata)
131
- full_response.extensions["from_cache"] = response.status == 304 # type: ignore[index]
132
- if full_response.extensions["from_cache"]:
133
- full_response.extensions["cache_metadata"] = metadata # type: ignore[index]
134
- return full_response
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
- response = await self._pool.handle_async_request(request)
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=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=response, request=request, metadata=metadata)
142
+ await self._storage.store(key, response=regular_response, request=request, metadata=metadata)
144
143
 
145
- response.extensions["from_cache"] = False # type: ignore[index]
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__ = ("AsyncFileStorage", "AsyncRedisStorage", "AsyncSQLiteStorage", "AsyncInMemoryStorage", "AsyncS3Storage")
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.