hishel 0.1.5__py3-none-any.whl → 1.0.0b1__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 (55) hide show
  1. hishel/__init__.py +59 -52
  2. hishel/_async_cache.py +213 -0
  3. hishel/_async_httpx.py +236 -0
  4. hishel/{beta/_core → _core}/_headers.py +11 -1
  5. hishel/{beta/_core → _core}/_spec.py +270 -136
  6. hishel/_core/_storages/_async_base.py +71 -0
  7. hishel/_core/_storages/_async_sqlite.py +420 -0
  8. hishel/_core/_storages/_packing.py +144 -0
  9. hishel/_core/_storages/_sync_base.py +71 -0
  10. hishel/_core/_storages/_sync_sqlite.py +420 -0
  11. hishel/{beta/_core → _core}/models.py +100 -37
  12. hishel/_policies.py +49 -0
  13. hishel/_sync_cache.py +213 -0
  14. hishel/_sync_httpx.py +236 -0
  15. hishel/_utils.py +37 -366
  16. hishel/asgi.py +400 -0
  17. hishel/fastapi.py +263 -0
  18. hishel/httpx.py +12 -0
  19. hishel/{beta/requests.py → requests.py} +31 -25
  20. hishel-1.0.0b1.dist-info/METADATA +509 -0
  21. hishel-1.0.0b1.dist-info/RECORD +24 -0
  22. hishel/_async/__init__.py +0 -5
  23. hishel/_async/_client.py +0 -30
  24. hishel/_async/_mock.py +0 -43
  25. hishel/_async/_pool.py +0 -201
  26. hishel/_async/_storages.py +0 -768
  27. hishel/_async/_transports.py +0 -282
  28. hishel/_controller.py +0 -581
  29. hishel/_exceptions.py +0 -10
  30. hishel/_files.py +0 -54
  31. hishel/_headers.py +0 -215
  32. hishel/_lfu_cache.py +0 -71
  33. hishel/_lmdb_types_.pyi +0 -53
  34. hishel/_s3.py +0 -122
  35. hishel/_serializers.py +0 -329
  36. hishel/_sync/__init__.py +0 -5
  37. hishel/_sync/_client.py +0 -30
  38. hishel/_sync/_mock.py +0 -43
  39. hishel/_sync/_pool.py +0 -201
  40. hishel/_sync/_storages.py +0 -768
  41. hishel/_sync/_transports.py +0 -282
  42. hishel/_synchronization.py +0 -37
  43. hishel/beta/__init__.py +0 -59
  44. hishel/beta/_async_cache.py +0 -167
  45. hishel/beta/_core/__init__.py +0 -0
  46. hishel/beta/_core/_async/_storages/_sqlite.py +0 -411
  47. hishel/beta/_core/_base/_storages/_base.py +0 -272
  48. hishel/beta/_core/_base/_storages/_packing.py +0 -165
  49. hishel/beta/_core/_sync/_storages/_sqlite.py +0 -411
  50. hishel/beta/_sync_cache.py +0 -167
  51. hishel/beta/httpx.py +0 -328
  52. hishel-0.1.5.dist-info/METADATA +0 -258
  53. hishel-0.1.5.dist-info/RECORD +0 -41
  54. {hishel-0.1.5.dist-info → hishel-1.0.0b1.dist-info}/WHEEL +0 -0
  55. {hishel-0.1.5.dist-info → hishel-1.0.0b1.dist-info}/licenses/LICENSE +0 -0
hishel/_async/_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._async.interfaces import AsyncRequestInterface
7
-
8
- if tp.TYPE_CHECKING: # pragma: no cover
9
- from typing_extensions import Self
10
-
11
- __all__ = ("MockAsyncConnectionPool", "MockAsyncTransport")
12
-
13
-
14
- class MockAsyncConnectionPool(AsyncRequestInterface):
15
- async def handle_async_request(self, request: httpcore.Request) -> httpcore.Response:
16
- assert isinstance(request.stream, tp.AsyncIterable)
17
- data = b"".join([chunk async 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
- async def __aenter__(self) -> "Self":
26
- return self
27
-
28
- async def __aexit__(
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 MockAsyncTransport(httpx.AsyncBaseTransport):
37
- async def handle_async_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/_async/_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._async.interfaces import AsyncRequestInterface
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 AsyncBaseStorage, AsyncFileStorage
15
-
16
- T = tp.TypeVar("T")
17
-
18
- __all__ = ("AsyncCacheConnectionPool",)
19
-
20
-
21
- async def fake_stream(content: bytes) -> tp.AsyncIterable[bytes]:
22
- yield content
23
-
24
-
25
- def generate_504() -> Response:
26
- return Response(status=504)
27
-
28
-
29
- class AsyncCacheConnectionPool(AsyncRequestInterface):
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: AsyncRequestInterface
34
- :param storage: Storage that handles how the responses should be saved., defaults to None
35
- :type storage: tp.Optional[AsyncBaseStorage], 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: AsyncRequestInterface,
43
- storage: tp.Optional[AsyncBaseStorage] = None,
44
- controller: tp.Optional[Controller] = None,
45
- ) -> None:
46
- self._pool = pool
47
-
48
- self._storage = storage if storage is not None else AsyncFileStorage(serializer=JSONSerializer())
49
-
50
- if not isinstance(self._storage, AsyncBaseStorage): # pragma: no cover
51
- raise TypeError(f"Expected subclass of `AsyncBaseStorage` but got `{storage.__class__.__name__}`")
52
-
53
- self._controller = controller if controller is not None else Controller()
54
-
55
- async def handle_async_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.AsyncIterable)
72
- body_for_key = b"".join([chunk async 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 = await 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 await 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 = await self._pool.handle_async_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 await 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
- await final_response.aread()
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
- await self._storage.store(key, response=final_response, request=request)
146
-
147
- return await 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 = await self._pool.handle_async_request(request)
157
- await regular_response.aread()
158
-
159
- if self._controller.is_cachable(request=request, response=regular_response):
160
- await self._storage.store(key, response=regular_response, request=request)
161
-
162
- return await self._create_hishel_response(
163
- key=key, response=regular_response, request=request, cached=False, revalidated=False
164
- )
165
-
166
- async 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
- await 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
- async def aclose(self) -> None:
187
- await self._storage.aclose()
188
-
189
- if hasattr(self._pool, "aclose"): # pragma: no cover
190
- await self._pool.aclose()
191
-
192
- async def __aenter__(self: T) -> T:
193
- return self
194
-
195
- async def __aexit__(
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
- await self.aclose()