hishel 0.1.4__py3-none-any.whl → 1.0.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. hishel/__init__.py +55 -53
  2. hishel/{beta/_async_cache.py → _async_cache.py} +3 -3
  3. hishel/{beta → _core}/__init__.py +6 -6
  4. hishel/{beta/_core → _core}/_async/_storages/_sqlite.py +3 -3
  5. hishel/{beta/_core → _core}/_base/_storages/_base.py +13 -1
  6. hishel/{beta/_core → _core}/_base/_storages/_packing.py +5 -5
  7. hishel/_core/_headers.py +636 -0
  8. hishel/{beta/_core → _core}/_spec.py +89 -2
  9. hishel/{beta/_core → _core}/_sync/_storages/_sqlite.py +3 -3
  10. hishel/{beta/_core → _core}/models.py +1 -1
  11. hishel/{beta/_sync_cache.py → _sync_cache.py} +3 -3
  12. hishel/{beta/httpx.py → httpx.py} +18 -7
  13. hishel/{beta/requests.py → requests.py} +15 -10
  14. hishel-1.0.0.dev0.dist-info/METADATA +321 -0
  15. hishel-1.0.0.dev0.dist-info/RECORD +19 -0
  16. hishel/_async/__init__.py +0 -5
  17. hishel/_async/_client.py +0 -30
  18. hishel/_async/_mock.py +0 -43
  19. hishel/_async/_pool.py +0 -201
  20. hishel/_async/_storages.py +0 -768
  21. hishel/_async/_transports.py +0 -282
  22. hishel/_controller.py +0 -581
  23. hishel/_exceptions.py +0 -10
  24. hishel/_files.py +0 -54
  25. hishel/_headers.py +0 -215
  26. hishel/_lfu_cache.py +0 -71
  27. hishel/_lmdb_types_.pyi +0 -53
  28. hishel/_s3.py +0 -122
  29. hishel/_serializers.py +0 -329
  30. hishel/_sync/__init__.py +0 -5
  31. hishel/_sync/_client.py +0 -30
  32. hishel/_sync/_mock.py +0 -43
  33. hishel/_sync/_pool.py +0 -201
  34. hishel/_sync/_storages.py +0 -768
  35. hishel/_sync/_transports.py +0 -282
  36. hishel/_synchronization.py +0 -37
  37. hishel/beta/_core/__init__.py +0 -0
  38. hishel/beta/_core/_headers.py +0 -301
  39. hishel-0.1.4.dist-info/METADATA +0 -404
  40. hishel-0.1.4.dist-info/RECORD +0 -41
  41. {hishel-0.1.4.dist-info → hishel-1.0.0.dev0.dist-info}/WHEEL +0 -0
  42. {hishel-0.1.4.dist-info → hishel-1.0.0.dev0.dist-info}/licenses/LICENSE +0 -0
hishel/_serializers.py DELETED
@@ -1,329 +0,0 @@
1
- import base64
2
- import json
3
- import pickle
4
- import typing as tp
5
- from datetime import datetime
6
-
7
- from httpcore import Request, Response
8
-
9
- from ._utils import normalized_url
10
-
11
- try:
12
- import yaml
13
- except ImportError: # pragma: no cover
14
- yaml = None # type: ignore
15
-
16
- HEADERS_ENCODING = "iso-8859-1"
17
- KNOWN_RESPONSE_EXTENSIONS = ("http_version", "reason_phrase")
18
- KNOWN_REQUEST_EXTENSIONS = ("timeout", "sni_hostname")
19
-
20
- __all__ = ("BaseSerializer", "JSONSerializer", "Metadata", "PickleSerializer", "YAMLSerializer", "clone_model")
21
-
22
- T = tp.TypeVar("T", Request, Response)
23
-
24
-
25
- def clone_model(model: T) -> T:
26
- if isinstance(model, Response):
27
- return Response(
28
- status=model.status,
29
- headers=model.headers,
30
- content=model.content,
31
- extensions={key: value for key, value in model.extensions.items() if key in KNOWN_RESPONSE_EXTENSIONS},
32
- ) # type: ignore
33
- else:
34
- return Request(
35
- method=model.method,
36
- url=normalized_url(model.url),
37
- headers=model.headers,
38
- extensions={key: value for key, value in model.extensions.items() if key in KNOWN_REQUEST_EXTENSIONS},
39
- ) # type: ignore
40
-
41
-
42
- class Metadata(tp.TypedDict):
43
- number_of_uses: int
44
- created_at: datetime
45
- cache_key: str
46
-
47
-
48
- class BaseSerializer:
49
- def dumps(self, response: Response, request: Request, metadata: Metadata) -> tp.Union[str, bytes]:
50
- raise NotImplementedError()
51
-
52
- def loads(self, data: tp.Union[str, bytes]) -> tp.Tuple[Response, Request, Metadata]:
53
- raise NotImplementedError()
54
-
55
- @property
56
- def is_binary(self) -> bool:
57
- raise NotImplementedError()
58
-
59
-
60
- class PickleSerializer(BaseSerializer):
61
- """
62
- A simple pickle-based serializer.
63
- """
64
-
65
- def dumps(self, response: Response, request: Request, metadata: Metadata) -> tp.Union[str, bytes]:
66
- """
67
- Dumps the HTTP response and its HTTP request.
68
-
69
- :param response: An HTTP response
70
- :type response: Response
71
- :param request: An HTTP request
72
- :type request: Request
73
- :param metadata: Additional information about the stored response
74
- :type metadata: Metadata
75
- :return: Serialized response
76
- :rtype: tp.Union[str, bytes]
77
- """
78
- clone_response = clone_model(response)
79
- clone_request = clone_model(request)
80
- return pickle.dumps((clone_response, clone_request, metadata))
81
-
82
- def loads(self, data: tp.Union[str, bytes]) -> tp.Tuple[Response, Request, Metadata]:
83
- """
84
- Loads the HTTP response and its HTTP request from serialized data.
85
-
86
- :param data: Serialized data
87
- :type data: tp.Union[str, bytes]
88
- :return: HTTP response and its HTTP request
89
- :rtype: tp.Tuple[Response, Request, Metadata]
90
- """
91
- assert isinstance(data, bytes)
92
- return tp.cast(tp.Tuple[Response, Request, Metadata], pickle.loads(data))
93
-
94
- @property
95
- def is_binary(self) -> bool: # pragma: no cover
96
- return True
97
-
98
-
99
- class JSONSerializer(BaseSerializer):
100
- """A simple json-based serializer."""
101
-
102
- def dumps(self, response: Response, request: Request, metadata: Metadata) -> tp.Union[str, bytes]:
103
- """
104
- Dumps the HTTP response and its HTTP request.
105
-
106
- :param response: An HTTP response
107
- :type response: Response
108
- :param request: An HTTP request
109
- :type request: Request
110
- :param metadata: Additional information about the stored response
111
- :type metadata: Metadata
112
- :return: Serialized response
113
- :rtype: tp.Union[str, bytes]
114
- """
115
- response_dict = {
116
- "status": response.status,
117
- "headers": [
118
- (key.decode(HEADERS_ENCODING), value.decode(HEADERS_ENCODING)) for key, value in response.headers
119
- ],
120
- "content": base64.b64encode(response.content).decode("ascii"),
121
- "extensions": {
122
- key: value.decode("ascii")
123
- for key, value in response.extensions.items()
124
- if key in KNOWN_RESPONSE_EXTENSIONS
125
- },
126
- }
127
-
128
- request_dict = {
129
- "method": request.method.decode("ascii"),
130
- "url": normalized_url(request.url),
131
- "headers": [
132
- (key.decode(HEADERS_ENCODING), value.decode(HEADERS_ENCODING)) for key, value in request.headers
133
- ],
134
- "extensions": {key: value for key, value in request.extensions.items() if key in KNOWN_REQUEST_EXTENSIONS},
135
- }
136
-
137
- metadata_dict = {
138
- "cache_key": metadata["cache_key"],
139
- "number_of_uses": metadata["number_of_uses"],
140
- "created_at": metadata["created_at"].strftime("%a, %d %b %Y %H:%M:%S GMT"),
141
- }
142
-
143
- full_json = {
144
- "response": response_dict,
145
- "request": request_dict,
146
- "metadata": metadata_dict,
147
- }
148
-
149
- return json.dumps(full_json, indent=4)
150
-
151
- def loads(self, data: tp.Union[str, bytes]) -> tp.Tuple[Response, Request, Metadata]:
152
- """
153
- Loads the HTTP response and its HTTP request from serialized data.
154
-
155
- :param data: Serialized data
156
- :type data: tp.Union[str, bytes]
157
- :return: HTTP response and its HTTP request
158
- :rtype: tp.Tuple[Response, Request, Metadata]
159
- """
160
-
161
- full_json = json.loads(data)
162
-
163
- response_dict = full_json["response"]
164
- request_dict = full_json["request"]
165
- metadata_dict = full_json["metadata"]
166
- metadata_dict["created_at"] = datetime.strptime(
167
- metadata_dict["created_at"],
168
- "%a, %d %b %Y %H:%M:%S GMT",
169
- )
170
-
171
- response = Response(
172
- status=response_dict["status"],
173
- headers=[
174
- (key.encode(HEADERS_ENCODING), value.encode(HEADERS_ENCODING))
175
- for key, value in response_dict["headers"]
176
- ],
177
- content=base64.b64decode(response_dict["content"].encode("ascii")),
178
- extensions={
179
- key: value.encode("ascii")
180
- for key, value in response_dict["extensions"].items()
181
- if key in KNOWN_RESPONSE_EXTENSIONS
182
- },
183
- )
184
-
185
- request = Request(
186
- method=request_dict["method"],
187
- url=request_dict["url"],
188
- headers=[
189
- (key.encode(HEADERS_ENCODING), value.encode(HEADERS_ENCODING)) for key, value in request_dict["headers"]
190
- ],
191
- extensions={
192
- key: value for key, value in request_dict["extensions"].items() if key in KNOWN_REQUEST_EXTENSIONS
193
- },
194
- )
195
-
196
- metadata = Metadata(
197
- cache_key=metadata_dict["cache_key"],
198
- created_at=metadata_dict["created_at"],
199
- number_of_uses=metadata_dict["number_of_uses"],
200
- )
201
-
202
- return response, request, metadata
203
-
204
- @property
205
- def is_binary(self) -> bool:
206
- return False
207
-
208
-
209
- class YAMLSerializer(BaseSerializer):
210
- """A simple yaml-based serializer."""
211
-
212
- def dumps(self, response: Response, request: Request, metadata: Metadata) -> tp.Union[str, bytes]:
213
- """
214
- Dumps the HTTP response and its HTTP request.
215
-
216
- :param response: An HTTP response
217
- :type response: Response
218
- :param request: An HTTP request
219
- :type request: Request
220
- :param metadata: Additional information about the stored response
221
- :type metadata: Metadata
222
- :return: Serialized response
223
- :rtype: tp.Union[str, bytes]
224
- """
225
- if yaml is None: # pragma: no cover
226
- raise RuntimeError(
227
- f"The `{type(self).__name__}` was used, but the required packages were not found. "
228
- "Check that you have `Hishel` installed with the `yaml` extension as shown.\n"
229
- "```pip install hishel[yaml]```"
230
- )
231
- response_dict = {
232
- "status": response.status,
233
- "headers": [
234
- (key.decode(HEADERS_ENCODING), value.decode(HEADERS_ENCODING)) for key, value in response.headers
235
- ],
236
- "content": base64.b64encode(response.content).decode("ascii"),
237
- "extensions": {
238
- key: value.decode("ascii")
239
- for key, value in response.extensions.items()
240
- if key in KNOWN_RESPONSE_EXTENSIONS
241
- },
242
- }
243
-
244
- request_dict = {
245
- "method": request.method.decode("ascii"),
246
- "url": normalized_url(request.url),
247
- "headers": [
248
- (key.decode(HEADERS_ENCODING), value.decode(HEADERS_ENCODING)) for key, value in request.headers
249
- ],
250
- "extensions": {key: value for key, value in request.extensions.items() if key in KNOWN_REQUEST_EXTENSIONS},
251
- }
252
-
253
- metadata_dict = {
254
- "cache_key": metadata["cache_key"],
255
- "number_of_uses": metadata["number_of_uses"],
256
- "created_at": metadata["created_at"].strftime("%a, %d %b %Y %H:%M:%S GMT"),
257
- }
258
-
259
- full_json = {
260
- "response": response_dict,
261
- "request": request_dict,
262
- "metadata": metadata_dict,
263
- }
264
-
265
- return yaml.safe_dump(full_json, sort_keys=False)
266
-
267
- def loads(self, data: tp.Union[str, bytes]) -> tp.Tuple[Response, Request, Metadata]:
268
- """
269
- Loads the HTTP response and its HTTP request from serialized data.
270
-
271
- :param data: Serialized data
272
- :type data: tp.Union[str, bytes]
273
- :raises RuntimeError: When used without the `yaml` extension installed
274
- :return: HTTP response and its HTTP request
275
- :rtype: tp.Tuple[Response, Request, Metadata]
276
- """
277
- if yaml is None: # pragma: no cover
278
- raise RuntimeError(
279
- f"The `{type(self).__name__}` was used, but the required packages were not found. "
280
- "Check that you have `Hishel` installed with the `yaml` extension as shown.\n"
281
- "```pip install hishel[yaml]```"
282
- )
283
-
284
- full_json = yaml.safe_load(data)
285
-
286
- response_dict = full_json["response"]
287
- request_dict = full_json["request"]
288
- metadata_dict = full_json["metadata"]
289
- metadata_dict["created_at"] = datetime.strptime(
290
- metadata_dict["created_at"],
291
- "%a, %d %b %Y %H:%M:%S GMT",
292
- )
293
-
294
- response = Response(
295
- status=response_dict["status"],
296
- headers=[
297
- (key.encode(HEADERS_ENCODING), value.encode(HEADERS_ENCODING))
298
- for key, value in response_dict["headers"]
299
- ],
300
- content=base64.b64decode(response_dict["content"].encode("ascii")),
301
- extensions={
302
- key: value.encode("ascii")
303
- for key, value in response_dict["extensions"].items()
304
- if key in KNOWN_RESPONSE_EXTENSIONS
305
- },
306
- )
307
-
308
- request = Request(
309
- method=request_dict["method"],
310
- url=request_dict["url"],
311
- headers=[
312
- (key.encode(HEADERS_ENCODING), value.encode(HEADERS_ENCODING)) for key, value in request_dict["headers"]
313
- ],
314
- extensions={
315
- key: value for key, value in request_dict["extensions"].items() if key in KNOWN_REQUEST_EXTENSIONS
316
- },
317
- )
318
-
319
- metadata = Metadata(
320
- cache_key=metadata_dict["cache_key"],
321
- created_at=metadata_dict["created_at"],
322
- number_of_uses=metadata_dict["number_of_uses"],
323
- )
324
-
325
- return response, request, metadata
326
-
327
- @property
328
- def is_binary(self) -> bool: # pragma: no cover
329
- return False
hishel/_sync/__init__.py DELETED
@@ -1,5 +0,0 @@
1
- from ._client import * # noqa: F403
2
- from ._mock import * # noqa: F403
3
- from ._pool import * # noqa: F403
4
- from ._storages import * # noqa: F403
5
- from ._transports import * # noqa: F403
hishel/_sync/_client.py DELETED
@@ -1,30 +0,0 @@
1
- import typing as tp
2
-
3
- import httpx
4
-
5
- from ._transports import CacheTransport
6
-
7
- __all__ = ("CacheClient",)
8
-
9
-
10
- class CacheClient(httpx.Client):
11
- def __init__(self, *args: tp.Any, **kwargs: tp.Any):
12
- self._storage = kwargs.pop("storage") if "storage" in kwargs else None
13
- self._controller = kwargs.pop("controller") if "controller" in kwargs else None
14
- super().__init__(*args, **kwargs)
15
-
16
- def _init_transport(self, *args, **kwargs) -> CacheTransport: # type: ignore
17
- _transport = super()._init_transport(*args, **kwargs)
18
- return CacheTransport(
19
- transport=_transport,
20
- storage=self._storage,
21
- controller=self._controller,
22
- )
23
-
24
- def _init_proxy_transport(self, *args, **kwargs) -> CacheTransport: # type: ignore
25
- _transport = super()._init_proxy_transport(*args, **kwargs) # pragma: no cover
26
- return CacheTransport( # pragma: no cover
27
- transport=_transport,
28
- storage=self._storage,
29
- controller=self._controller,
30
- )
hishel/_sync/_mock.py DELETED
@@ -1,43 +0,0 @@
1
- import typing as tp
2
- from types import TracebackType
3
-
4
- import httpcore
5
- import httpx
6
- from httpcore._sync.interfaces import RequestInterface
7
-
8
- if tp.TYPE_CHECKING: # pragma: no cover
9
- from typing_extensions import Self
10
-
11
- __all__ = ("MockConnectionPool", "MockTransport")
12
-
13
-
14
- class MockConnectionPool(RequestInterface):
15
- def handle_request(self, request: httpcore.Request) -> httpcore.Response:
16
- assert isinstance(request.stream, tp.Iterable)
17
- data = b"".join([chunk for chunk in request.stream]) # noqa: F841
18
- return self.mocked_responses.pop(0)
19
-
20
- def add_responses(self, responses: tp.List[httpcore.Response]) -> None:
21
- if not hasattr(self, "mocked_responses"):
22
- self.mocked_responses = []
23
- self.mocked_responses.extend(responses)
24
-
25
- def __enter__(self) -> "Self":
26
- return self
27
-
28
- def __exit__(
29
- self,
30
- exc_type: tp.Optional[tp.Type[BaseException]] = None,
31
- exc_value: tp.Optional[BaseException] = None,
32
- traceback: tp.Optional[TracebackType] = None,
33
- ) -> None: ...
34
-
35
-
36
- class MockTransport(httpx.BaseTransport):
37
- def handle_request(self, request: httpx.Request) -> httpx.Response:
38
- return self.mocked_responses.pop(0)
39
-
40
- def add_responses(self, responses: tp.List[httpx.Response]) -> None:
41
- if not hasattr(self, "mocked_responses"):
42
- self.mocked_responses = []
43
- self.mocked_responses.extend(responses)
hishel/_sync/_pool.py DELETED
@@ -1,201 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import types
4
- import typing as tp
5
-
6
- from httpcore._sync.interfaces import RequestInterface
7
- from httpcore._exceptions import ConnectError
8
- from httpcore._models import Request, Response
9
-
10
- from .._controller import Controller, allowed_stale
11
- from .._headers import parse_cache_control
12
- from .._serializers import JSONSerializer, Metadata
13
- from .._utils import extract_header_values_decoded
14
- from ._storages import BaseStorage, FileStorage
15
-
16
- T = tp.TypeVar("T")
17
-
18
- __all__ = ("CacheConnectionPool",)
19
-
20
-
21
- def fake_stream(content: bytes) -> tp.Iterable[bytes]:
22
- yield content
23
-
24
-
25
- def generate_504() -> Response:
26
- return Response(status=504)
27
-
28
-
29
- class CacheConnectionPool(RequestInterface):
30
- """An HTTP Core Connection Pool that supports HTTP caching.
31
-
32
- :param pool: `Connection Pool` that our class wraps in order to add an HTTP Cache layer on top of
33
- :type pool: RequestInterface
34
- :param storage: Storage that handles how the responses should be saved., defaults to None
35
- :type storage: tp.Optional[BaseStorage], optional
36
- :param controller: Controller that manages the cache behavior at the specification level, defaults to None
37
- :type controller: tp.Optional[Controller], optional
38
- """
39
-
40
- def __init__(
41
- self,
42
- pool: RequestInterface,
43
- storage: tp.Optional[BaseStorage] = None,
44
- controller: tp.Optional[Controller] = None,
45
- ) -> None:
46
- self._pool = pool
47
-
48
- self._storage = storage if storage is not None else FileStorage(serializer=JSONSerializer())
49
-
50
- if not isinstance(self._storage, BaseStorage): # pragma: no cover
51
- raise TypeError(f"Expected subclass of `BaseStorage` but got `{storage.__class__.__name__}`")
52
-
53
- self._controller = controller if controller is not None else Controller()
54
-
55
- def handle_request(self, request: Request) -> Response:
56
- """
57
- Handles HTTP requests while also implementing HTTP caching.
58
-
59
- :param request: An HTTP request
60
- :type request: httpcore.Request
61
- :return: An HTTP response
62
- :rtype: httpcore.Response
63
- """
64
-
65
- if request.extensions.get("cache_disabled", False):
66
- request.headers.extend([(b"cache-control", b"no-cache"), (b"cache-control", b"max-age=0")])
67
-
68
- if request.method.upper() not in [b"GET", b"HEAD"]:
69
- # If the HTTP method is, for example, POST,
70
- # we must also use the request data to generate the hash.
71
- assert isinstance(request.stream, tp.Iterable)
72
- body_for_key = b"".join([chunk for chunk in request.stream])
73
- request.stream = fake_stream(body_for_key)
74
- else:
75
- body_for_key = b""
76
-
77
- key = self._controller._key_generator(request, body_for_key)
78
- stored_data = self._storage.retrieve(key)
79
-
80
- request_cache_control = parse_cache_control(extract_header_values_decoded(request.headers, b"Cache-Control"))
81
-
82
- if request_cache_control.only_if_cached and not stored_data:
83
- return generate_504()
84
-
85
- if stored_data:
86
- # Try using the stored response if it was discovered.
87
-
88
- stored_response, stored_request, metadata = stored_data
89
-
90
- # Immediately read the stored response to avoid issues when trying to access the response body.
91
- stored_response.read()
92
-
93
- res = self._controller.construct_response_from_cache(
94
- request=request,
95
- response=stored_response,
96
- original_request=stored_request,
97
- )
98
-
99
- if isinstance(res, Response):
100
- # Simply use the response if the controller determines it is ready for use.
101
- return self._create_hishel_response(
102
- key=key,
103
- response=stored_response,
104
- request=request,
105
- metadata=metadata,
106
- cached=True,
107
- revalidated=False,
108
- )
109
-
110
- if request_cache_control.only_if_cached:
111
- return generate_504()
112
-
113
- if isinstance(res, Request):
114
- # Controller has determined that the response needs to be re-validated.
115
-
116
- try:
117
- revalidation_response = self._pool.handle_request(res)
118
- except ConnectError:
119
- # If there is a connection error, we can use the stale response if allowed.
120
- if self._controller._allow_stale and allowed_stale(response=stored_response):
121
- return self._create_hishel_response(
122
- key=key,
123
- response=stored_response,
124
- request=request,
125
- metadata=metadata,
126
- cached=True,
127
- revalidated=False,
128
- )
129
- raise # pragma: no cover
130
- # Merge headers with the stale response.
131
- final_response = self._controller.handle_validation_response(
132
- old_response=stored_response, new_response=revalidation_response
133
- )
134
-
135
- final_response.read()
136
-
137
- # RFC 9111: 4.3.3. Handling a Validation Response
138
- # A 304 (Not Modified) response status code indicates that the stored response can be updated and
139
- # reused. A full response (i.e., one containing content) indicates that none of the stored responses
140
- # nominated in the conditional request are suitable. Instead, the cache MUST use the full response to
141
- # satisfy the request. The cache MAY store such a full response, subject to its constraints.
142
- if revalidation_response.status != 304 and self._controller.is_cachable(
143
- request=request, response=final_response
144
- ):
145
- self._storage.store(key, response=final_response, request=request)
146
-
147
- return self._create_hishel_response(
148
- key=key,
149
- response=final_response,
150
- request=request,
151
- cached=revalidation_response.status == 304,
152
- revalidated=True,
153
- metadata=metadata,
154
- )
155
-
156
- regular_response = self._pool.handle_request(request)
157
- regular_response.read()
158
-
159
- if self._controller.is_cachable(request=request, response=regular_response):
160
- self._storage.store(key, response=regular_response, request=request)
161
-
162
- return self._create_hishel_response(
163
- key=key, response=regular_response, request=request, cached=False, revalidated=False
164
- )
165
-
166
- def _create_hishel_response(
167
- self,
168
- key: str,
169
- response: Response,
170
- request: Request,
171
- cached: bool,
172
- revalidated: bool,
173
- metadata: Metadata | None = None,
174
- ) -> Response:
175
- if cached:
176
- assert metadata
177
- metadata["number_of_uses"] += 1
178
- self._storage.update_metadata(key=key, request=request, response=response, metadata=metadata)
179
- response.extensions["from_cache"] = True # type: ignore[index]
180
- response.extensions["cache_metadata"] = metadata # type: ignore[index]
181
- else:
182
- response.extensions["from_cache"] = False # type: ignore[index]
183
- response.extensions["revalidated"] = revalidated # type: ignore[index]
184
- return response
185
-
186
- def close(self) -> None:
187
- self._storage.close()
188
-
189
- if hasattr(self._pool, "close"): # pragma: no cover
190
- self._pool.close()
191
-
192
- def __enter__(self: T) -> T:
193
- return self
194
-
195
- def __exit__(
196
- self,
197
- exc_type: tp.Optional[tp.Type[BaseException]] = None,
198
- exc_value: tp.Optional[BaseException] = None,
199
- traceback: tp.Optional[types.TracebackType] = None,
200
- ) -> None:
201
- self.close()