hishel 0.1.4__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 (56) hide show
  1. hishel/__init__.py +59 -52
  2. hishel/_async_cache.py +213 -0
  3. hishel/_async_httpx.py +236 -0
  4. hishel/_core/_headers.py +646 -0
  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} +41 -30
  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 -260
  48. hishel/beta/_core/_base/_storages/_packing.py +0 -165
  49. hishel/beta/_core/_headers.py +0 -301
  50. hishel/beta/_core/_sync/_storages/_sqlite.py +0 -411
  51. hishel/beta/_sync_cache.py +0 -167
  52. hishel/beta/httpx.py +0 -317
  53. hishel-0.1.4.dist-info/METADATA +0 -404
  54. hishel-0.1.4.dist-info/RECORD +0 -41
  55. {hishel-0.1.4.dist-info → hishel-1.0.0b1.dist-info}/WHEEL +0 -0
  56. {hishel-0.1.4.dist-info → hishel-1.0.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -1,165 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import uuid
4
- from typing import TYPE_CHECKING, Any, Mapping, Optional, Union, overload
5
-
6
- import msgpack
7
- from typing_extensions import Literal, cast
8
-
9
- from hishel.beta._core._headers import Headers
10
- from hishel.beta._core.models import PairMeta, Request, Response
11
-
12
-
13
- def filter_out_hishel_metadata(data: Mapping[str, Any]) -> dict[str, Any]:
14
- return {k: v for k, v in data.items() if not k.startswith("hishel_")}
15
-
16
-
17
- if TYPE_CHECKING:
18
- from hishel.beta import CompletePair, IncompletePair
19
-
20
-
21
- @overload
22
- def pack(
23
- value: "CompletePair" | "IncompletePair",
24
- /,
25
- kind: Literal["pair"],
26
- ) -> bytes: ...
27
-
28
-
29
- @overload
30
- def pack(
31
- value: uuid.UUID,
32
- /,
33
- kind: Literal["entry_db_key_index"],
34
- ) -> bytes: ...
35
-
36
-
37
- def pack(
38
- value: Union["CompletePair", "IncompletePair", uuid.UUID],
39
- /,
40
- kind: Literal["pair", "entry_db_key_index"],
41
- ) -> bytes:
42
- from hishel.beta import CompletePair, IncompletePair
43
-
44
- if kind == "entry_db_key_index":
45
- assert isinstance(value, uuid.UUID)
46
- return value.bytes
47
- elif kind == "pair":
48
- assert isinstance(value, (CompletePair, IncompletePair))
49
- cache_key_dict = {"cache_key": value.cache_key} if isinstance(value, CompletePair) else {}
50
- return cast(
51
- bytes,
52
- msgpack.packb(
53
- {
54
- "id": value.id.bytes,
55
- "request": {
56
- "method": value.request.method,
57
- "url": value.request.url,
58
- "headers": value.request.headers._headers,
59
- "extra": filter_out_hishel_metadata(value.request.metadata),
60
- },
61
- "response": (
62
- {
63
- "status_code": value.response.status_code,
64
- "headers": value.response.headers._headers,
65
- "extra": filter_out_hishel_metadata(value.response.metadata),
66
- }
67
- )
68
- if isinstance(value, CompletePair)
69
- else None,
70
- "meta": {
71
- "created_at": value.meta.created_at,
72
- "deleted_at": value.meta.deleted_at,
73
- },
74
- **cache_key_dict,
75
- }
76
- ),
77
- )
78
- assert False, f"Unexpected kind: {kind}"
79
-
80
-
81
- @overload
82
- def unpack(
83
- value: bytes,
84
- /,
85
- kind: Literal["pair"],
86
- ) -> Union["CompletePair", "IncompletePair"]: ...
87
-
88
-
89
- @overload
90
- def unpack(
91
- value: bytes,
92
- /,
93
- kind: Literal["entry_db_key_index"],
94
- ) -> uuid.UUID: ...
95
-
96
-
97
- @overload
98
- def unpack(
99
- value: Optional[bytes],
100
- /,
101
- kind: Literal["pair"],
102
- ) -> Optional[Union["CompletePair", "IncompletePair"]]: ...
103
-
104
-
105
- @overload
106
- def unpack(
107
- value: Optional[bytes],
108
- /,
109
- kind: Literal["entry_db_key_index"],
110
- ) -> Optional[uuid.UUID]: ...
111
-
112
-
113
- def unpack(
114
- value: Optional[bytes],
115
- /,
116
- kind: Literal["pair", "entry_db_key_index"],
117
- ) -> Union["CompletePair", "IncompletePair", uuid.UUID, None]:
118
- from hishel.beta import CompletePair, IncompletePair
119
-
120
- if value is None:
121
- return None
122
- if kind == "entry_db_key_index":
123
- return uuid.UUID(bytes=value)
124
- elif kind == "pair":
125
- data = msgpack.unpackb(value)
126
- id = uuid.UUID(bytes=data["id"])
127
- if data.get("response"):
128
- return CompletePair(
129
- id=id,
130
- request=Request(
131
- method=data["request"]["method"],
132
- url=data["request"]["url"],
133
- headers=Headers(data["request"]["headers"]),
134
- metadata=data["request"]["extra"],
135
- stream=iter([]),
136
- ),
137
- response=(
138
- Response(
139
- status_code=data["response"]["status_code"],
140
- headers=Headers(data["response"]["headers"]),
141
- metadata=data["response"]["extra"],
142
- stream=iter([]),
143
- )
144
- ),
145
- meta=PairMeta(
146
- created_at=data["meta"]["created_at"],
147
- deleted_at=data["meta"]["deleted_at"],
148
- ),
149
- cache_key=data["cache_key"],
150
- )
151
- else:
152
- return IncompletePair(
153
- id=id,
154
- request=Request(
155
- method=data["request"]["method"],
156
- url=data["request"]["url"],
157
- headers=Headers(data["request"]["headers"]),
158
- metadata=data["request"]["extra"],
159
- stream=iter([]),
160
- ),
161
- meta=PairMeta(
162
- created_at=data["meta"]["created_at"],
163
- deleted_at=data["meta"]["deleted_at"],
164
- ),
165
- )
@@ -1,301 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import string
4
- from dataclasses import dataclass
5
- from typing import Any, Dict, Iterator, List, Literal, Mapping, MutableMapping, Optional, Union, cast
6
-
7
- from hishel._exceptions import ParseError, ValidationError
8
-
9
- ## Grammar
10
-
11
-
12
- HTAB = "\t"
13
- SP = " "
14
- obs_text = "".join(chr(i) for i in range(0x80, 0xFF + 1)) # 0x80-0xFF
15
-
16
- tchar = "!#$%&'*+-.^_`|~0123456789" + string.ascii_letters
17
- qdtext = "".join(
18
- [
19
- HTAB,
20
- SP,
21
- "\x21",
22
- "".join(chr(i) for i in range(0x23, 0x5B + 1)), # 0x23-0x5b
23
- "".join(chr(i) for i in range(0x5D, 0x7E + 1)), # 0x5D-0x7E
24
- obs_text,
25
- ]
26
- )
27
-
28
- TIME_FIELDS = [
29
- "max_age",
30
- "max_stale",
31
- "min_fresh",
32
- "s_maxage",
33
- ]
34
-
35
- BOOLEAN_FIELDS = [
36
- "immutable",
37
- "must_revalidate",
38
- "must_understand",
39
- "no_store",
40
- "no_transform",
41
- "only_if_cached",
42
- "public",
43
- "proxy_revalidate",
44
- ]
45
-
46
- LIST_FIELDS = ["no_cache", "private"]
47
-
48
- __all__ = (
49
- "CacheControl",
50
- "Vary",
51
- )
52
-
53
-
54
- def strip_ows_around(text: str) -> str:
55
- return text.strip(" ").strip("\t")
56
-
57
-
58
- def normalize_directive(text: str) -> str:
59
- return text.replace("-", "_")
60
-
61
-
62
- def parse_cache_control(cache_control_value: Optional[str]) -> "CacheControl":
63
- if cache_control_value is None:
64
- return CacheControl()
65
- directives = {}
66
-
67
- if "no-cache=" in cache_control_value or "private=" in cache_control_value:
68
- cache_control_splited = [cache_control_value]
69
- else:
70
- cache_control_splited = cache_control_value.split(",")
71
-
72
- for directive in cache_control_splited:
73
- key: str = ""
74
- value: Optional[str] = None
75
- dquote = False
76
-
77
- if not directive:
78
- raise ParseError("The directive should not be left blank.")
79
-
80
- directive = strip_ows_around(directive)
81
-
82
- if not directive:
83
- raise ParseError("The directive should not contain only whitespaces.")
84
-
85
- for i, key_char in enumerate(directive):
86
- if key_char == "=":
87
- value = directive[i + 1 :]
88
-
89
- if not value:
90
- raise ParseError("The directive value cannot be left blank.")
91
-
92
- if value[0] == '"':
93
- dquote = True
94
- if dquote and value[-1] != '"':
95
- raise ParseError("Invalid quotes around the value.")
96
-
97
- if not dquote:
98
- for value_char in value:
99
- if value_char not in tchar:
100
- raise ParseError(
101
- f"The character '{value_char!r}' is not permitted for the unquoted values."
102
- )
103
- else:
104
- for value_char in value[1:-1]:
105
- if value_char not in qdtext:
106
- raise ParseError(f"The character '{value_char!r}' is not permitted for the quoted values.")
107
- break
108
-
109
- if key_char not in tchar:
110
- raise ParseError(f"The character '{key_char!r}' is not permitted in the directive name.")
111
- key += key_char
112
- directives[key] = value
113
- validated_data = CacheControl.validate(directives)
114
- return CacheControl(**validated_data)
115
-
116
-
117
- class Vary:
118
- def __init__(self, values: List[str]) -> None:
119
- self.values = values
120
-
121
- @classmethod
122
- def from_value(cls, vary_value: str) -> "Vary":
123
- values = []
124
-
125
- for field_name in vary_value.split(","):
126
- field_name = field_name.strip()
127
- values.append(field_name)
128
- return Vary(values)
129
-
130
-
131
- @dataclass
132
- class ContentRange:
133
- unit: Literal["bytes"]
134
- range: tuple[int, int] | None
135
- size: int | None
136
-
137
- @classmethod
138
- def from_str(cls, content_range: str) -> "ContentRange":
139
- words = [word for word in content_range.split(" ") if word != ""]
140
-
141
- unit = words[0]
142
- range, size = words[1].split("/")
143
-
144
- splited_range = range.split("-")
145
-
146
- return cls(
147
- unit=cast(Literal["bytes"], unit),
148
- range=None if range == "*" else (int(splited_range[0]), int(splited_range[1])),
149
- size=None if size == "*" else int(size),
150
- )
151
-
152
-
153
- @dataclass
154
- class Range:
155
- unit: Literal["bytes"]
156
- range: tuple[int | None, int | None]
157
-
158
- @classmethod
159
- def try_from_str(cls, range_header: str) -> "Range" | None:
160
- # Example: "bytes=0-99,200-299,-500,100-"
161
- unit, values = range_header.split("=")
162
- unit = unit.strip()
163
- parts = [p.strip() for p in values.split(",")]
164
-
165
- parsed: list[tuple[int | None, int | None]] = []
166
- for part in parts:
167
- if "-" not in part:
168
- raise ValueError(f"Invalid range part: {part}")
169
- start_str, end_str = part.split("-", 1)
170
- start = int(start_str) if start_str else None
171
- end = int(end_str) if end_str else None
172
- parsed.append((start, end))
173
-
174
- if len(parsed) != 1:
175
- # we don't support multiple ranges
176
- return None
177
-
178
- return cls(
179
- unit=cast(Literal["bytes"], unit),
180
- range=parsed[0],
181
- )
182
-
183
-
184
- class CacheControl:
185
- def __init__(
186
- self,
187
- immutable: bool = False, # [RFC8246]
188
- max_age: Optional[int] = None, # [RFC9111, Section 5.2.1.1, 5.2.2.1]
189
- max_stale: Optional[int] = None, # [RFC9111, Section 5.2.1.2]
190
- min_fresh: Optional[int] = None, # [RFC9111, Section 5.2.1.3]
191
- must_revalidate: bool = False, # [RFC9111, Section 5.2.2.2]
192
- must_understand: bool = False, # [RFC9111, Section 5.2.2.3]
193
- no_cache: Union[bool, List[str]] = False, # [RFC9111, Section 5.2.1.4, 5.2.2.4]
194
- no_store: bool = False, # [RFC9111, Section 5.2.1.5, 5.2.2.5]
195
- no_transform: bool = False, # [RFC9111, Section 5.2.1.6, 5.2.2.6]
196
- only_if_cached: bool = False, # [RFC9111, Section 5.2.1.7]
197
- private: Union[bool, List[str]] = False, # [RFC9111, Section 5.2.2.7]
198
- proxy_revalidate: bool = False, # [RFC9111, Section 5.2.2.8]
199
- public: bool = False, # [RFC9111, Section 5.2.2.9]
200
- s_maxage: Optional[int] = None, # [RFC9111, Section 5.2.2.10]
201
- ) -> None:
202
- self.immutable = immutable
203
- self.max_age = max_age
204
- self.max_stale = max_stale
205
- self.min_fresh = min_fresh
206
- self.must_revalidate = must_revalidate
207
- self.must_understand = must_understand
208
- self.no_cache = no_cache
209
- self.no_store = no_store
210
- self.no_transform = no_transform
211
- self.only_if_cached = only_if_cached
212
- self.private = private
213
- self.proxy_revalidate = proxy_revalidate
214
- self.public = public
215
- self.s_maxage = s_maxage
216
-
217
- @classmethod
218
- def validate(cls, directives: Dict[str, Any]) -> Dict[str, Any]:
219
- validated_data: Dict[str, Any] = {}
220
-
221
- for key, value in directives.items():
222
- key = normalize_directive(key)
223
- if key in TIME_FIELDS:
224
- if value is None:
225
- raise ValidationError(f"The directive '{key}' necessitates a value.")
226
-
227
- if value[0] == '"' or value[-1] == '"':
228
- raise ValidationError(f"The argument '{key}' should be an integer, but a quote was found.")
229
-
230
- try:
231
- validated_data[key] = int(value)
232
- except Exception:
233
- raise ValidationError(f"The argument '{key}' should be an integer, but got '{value!r}'.")
234
- elif key in BOOLEAN_FIELDS:
235
- if value is not None:
236
- raise ValidationError(f"The directive '{key}' should have no value, but it does.")
237
- validated_data[key] = True
238
- elif key in LIST_FIELDS:
239
- if value is None:
240
- validated_data[key] = True
241
- else:
242
- values = []
243
- for list_value in value[1:-1].split(","):
244
- if not list_value:
245
- raise ValidationError("The list value must not be empty.")
246
- list_value = strip_ows_around(list_value)
247
- values.append(list_value)
248
- validated_data[key] = values
249
-
250
- return validated_data
251
-
252
- def __repr__(self) -> str:
253
- fields = ""
254
-
255
- for key in TIME_FIELDS:
256
- key = key.replace("-", "_")
257
- value = getattr(self, key)
258
- if value:
259
- fields += f"{key}={value}, "
260
-
261
- for key in BOOLEAN_FIELDS:
262
- key = key.replace("-", "_")
263
- value = getattr(self, key)
264
- if value:
265
- fields += f"{key}, "
266
-
267
- fields = fields[:-2]
268
-
269
- return f"<{type(self).__name__} {fields}>"
270
-
271
-
272
- class Headers(MutableMapping[str, str]):
273
- def __init__(self, headers: Mapping[str, Union[str, List[str]]]) -> None:
274
- self._headers = {k.lower(): ([v] if isinstance(v, str) else v[:]) for k, v in headers.items()}
275
-
276
- def get_list(self, key: str) -> Optional[List[str]]:
277
- return self._headers.get(key.lower(), None)
278
-
279
- def __getitem__(self, key: str) -> str:
280
- return ", ".join(self._headers[key.lower()])
281
-
282
- def __setitem__(self, key: str, value: str) -> None:
283
- self._headers.setdefault(key.lower(), []).append(value)
284
-
285
- def __delitem__(self, key: str) -> None:
286
- del self._headers[key.lower()]
287
-
288
- def __iter__(self) -> Iterator[str]:
289
- return iter(self._headers)
290
-
291
- def __len__(self) -> int:
292
- return len(self._headers)
293
-
294
- def __repr__(self) -> str:
295
- return repr(self._headers)
296
-
297
- def __str__(self) -> str:
298
- return str(self._headers)
299
-
300
- def __eq__(self, other_headers: Any) -> bool:
301
- return isinstance(other_headers, Headers) and self._headers == other_headers._headers # type: ignore