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/_headers.py DELETED
@@ -1,215 +0,0 @@
1
- import string
2
- from typing import Any, Dict, List, Optional, Union
3
-
4
- from ._exceptions import ParseError, ValidationError
5
-
6
- ## Grammar
7
-
8
-
9
- HTAB = "\t"
10
- SP = " "
11
- obs_text = "".join(chr(i) for i in range(0x80, 0xFF + 1)) # 0x80-0xFF
12
-
13
- tchar = "!#$%&'*+-.^_`|~0123456789" + string.ascii_letters
14
- qdtext = "".join(
15
- [
16
- HTAB,
17
- SP,
18
- "\x21",
19
- "".join(chr(i) for i in range(0x23, 0x5B + 1)), # 0x23-0x5b
20
- "".join(chr(i) for i in range(0x5D, 0x7E + 1)), # 0x5D-0x7E
21
- obs_text,
22
- ]
23
- )
24
-
25
- TIME_FIELDS = [
26
- "max_age",
27
- "max_stale",
28
- "min_fresh",
29
- "s_maxage",
30
- ]
31
-
32
- BOOLEAN_FIELDS = [
33
- "immutable",
34
- "must_revalidate",
35
- "must_understand",
36
- "no_store",
37
- "no_transform",
38
- "only_if_cached",
39
- "public",
40
- "proxy_revalidate",
41
- ]
42
-
43
- LIST_FIELDS = ["no_cache", "private"]
44
-
45
- __all__ = (
46
- "CacheControl",
47
- "Vary",
48
- )
49
-
50
-
51
- def strip_ows_around(text: str) -> str:
52
- return text.strip(" ").strip("\t")
53
-
54
-
55
- def normalize_directive(text: str) -> str:
56
- return text.replace("-", "_")
57
-
58
-
59
- def parse_cache_control(cache_control_values: List[str]) -> "CacheControl":
60
- directives = {}
61
-
62
- for cache_control_value in cache_control_values:
63
- if "no-cache=" in cache_control_value or "private=" in cache_control_value:
64
- cache_control_splited = [cache_control_value]
65
- else:
66
- cache_control_splited = cache_control_value.split(",")
67
-
68
- for directive in cache_control_splited:
69
- key: str = ""
70
- value: Optional[str] = None
71
- dquote = False
72
-
73
- if not directive:
74
- raise ParseError("The directive should not be left blank.")
75
-
76
- directive = strip_ows_around(directive)
77
-
78
- if not directive:
79
- raise ParseError("The directive should not contain only whitespaces.")
80
-
81
- for i, key_char in enumerate(directive):
82
- if key_char == "=":
83
- value = directive[i + 1 :]
84
-
85
- if not value:
86
- raise ParseError("The directive value cannot be left blank.")
87
-
88
- if value[0] == '"':
89
- dquote = True
90
- if dquote and value[-1] != '"':
91
- raise ParseError("Invalid quotes around the value.")
92
-
93
- if not dquote:
94
- for value_char in value:
95
- if value_char not in tchar:
96
- raise ParseError(
97
- f"The character '{value_char!r}' is not permitted for the unquoted values."
98
- )
99
- else:
100
- for value_char in value[1:-1]:
101
- if value_char not in qdtext:
102
- raise ParseError(
103
- f"The character '{value_char!r}' is not permitted for the quoted values."
104
- )
105
- break
106
-
107
- if key_char not in tchar:
108
- raise ParseError(f"The character '{key_char!r}' is not permitted in the directive name.")
109
- key += key_char
110
- directives[key] = value
111
- validated_data = CacheControl.validate(directives)
112
- return CacheControl(**validated_data)
113
-
114
-
115
- class Vary:
116
- def __init__(self, values: List[str]) -> None:
117
- self._values = values
118
-
119
- @classmethod
120
- def from_value(cls, vary_values: List[str]) -> "Vary":
121
- values = []
122
-
123
- for vary_value in vary_values:
124
- for field_name in vary_value.split(","):
125
- field_name = field_name.strip()
126
- values.append(field_name)
127
- return Vary(values)
128
-
129
-
130
- class CacheControl:
131
- def __init__(
132
- self,
133
- immutable: bool = False, # [RFC8246]
134
- max_age: Optional[int] = None, # [RFC9111, Section 5.2.1.1, 5.2.2.1]
135
- max_stale: Optional[int] = None, # [RFC9111, Section 5.2.1.2]
136
- min_fresh: Optional[int] = None, # [RFC9111, Section 5.2.1.3]
137
- must_revalidate: bool = False, # [RFC9111, Section 5.2.2.2]
138
- must_understand: bool = False, # [RFC9111, Section 5.2.2.3]
139
- no_cache: Union[bool, List[str]] = False, # [RFC9111, Section 5.2.1.4, 5.2.2.4]
140
- no_store: bool = False, # [RFC9111, Section 5.2.1.5, 5.2.2.5]
141
- no_transform: bool = False, # [RFC9111, Section 5.2.1.6, 5.2.2.6]
142
- only_if_cached: bool = False, # [RFC9111, Section 5.2.1.7]
143
- private: Union[bool, List[str]] = False, # [RFC9111, Section 5.2.2.7]
144
- proxy_revalidate: bool = False, # [RFC9111, Section 5.2.2.8]
145
- public: bool = False, # [RFC9111, Section 5.2.2.9]
146
- s_maxage: Optional[int] = None, # [RFC9111, Section 5.2.2.10]
147
- ) -> None:
148
- self.immutable = immutable
149
- self.max_age = max_age
150
- self.max_stale = max_stale
151
- self.min_fresh = min_fresh
152
- self.must_revalidate = must_revalidate
153
- self.must_understand = must_understand
154
- self.no_cache = no_cache
155
- self.no_store = no_store
156
- self.no_transform = no_transform
157
- self.only_if_cached = only_if_cached
158
- self.private = private
159
- self.proxy_revalidate = proxy_revalidate
160
- self.public = public
161
- self.s_maxage = s_maxage
162
-
163
- @classmethod
164
- def validate(cls, directives: Dict[str, Any]) -> Dict[str, Any]:
165
- validated_data: Dict[str, Any] = {}
166
-
167
- for key, value in directives.items():
168
- key = normalize_directive(key)
169
- if key in TIME_FIELDS:
170
- if value is None:
171
- raise ValidationError(f"The directive '{key}' necessitates a value.")
172
-
173
- if value[0] == '"' or value[-1] == '"':
174
- raise ValidationError(f"The argument '{key}' should be an integer, but a quote was found.")
175
-
176
- try:
177
- validated_data[key] = int(value)
178
- except Exception:
179
- raise ValidationError(f"The argument '{key}' should be an integer, but got '{value!r}'.")
180
- elif key in BOOLEAN_FIELDS:
181
- if value is not None:
182
- raise ValidationError(f"The directive '{key}' should have no value, but it does.")
183
- validated_data[key] = True
184
- elif key in LIST_FIELDS:
185
- if value is None:
186
- validated_data[key] = True
187
- else:
188
- values = []
189
- for list_value in value[1:-1].split(","):
190
- if not list_value:
191
- raise ValidationError("The list value must not be empty.")
192
- list_value = strip_ows_around(list_value)
193
- values.append(list_value)
194
- validated_data[key] = values
195
-
196
- return validated_data
197
-
198
- def __repr__(self) -> str:
199
- fields = ""
200
-
201
- for key in TIME_FIELDS:
202
- key = key.replace("-", "_")
203
- value = getattr(self, key)
204
- if value:
205
- fields += f"{key}={value}, "
206
-
207
- for key in BOOLEAN_FIELDS:
208
- key = key.replace("-", "_")
209
- value = getattr(self, key)
210
- if value:
211
- fields += f"{key}, "
212
-
213
- fields = fields[:-2]
214
-
215
- return f"<{type(self).__name__} {fields}>"
hishel/_lfu_cache.py DELETED
@@ -1,71 +0,0 @@
1
- from collections import OrderedDict
2
- from typing import DefaultDict, Dict, Generic, Iterator, Tuple, TypeVar
3
-
4
- K = TypeVar("K")
5
- V = TypeVar("V")
6
-
7
- __all__ = ["LFUCache"]
8
-
9
-
10
- class LFUCache(Generic[K, V]):
11
- def __init__(self, capacity: int):
12
- if capacity <= 0:
13
- raise ValueError("Capacity must be positive")
14
-
15
- self.capacity = capacity
16
- self.cache: Dict[K, Tuple[V, int]] = {} # To store key-value pairs
17
- self.freq_count: DefaultDict[int, OrderedDict[K, V]] = DefaultDict(
18
- OrderedDict
19
- ) # To store frequency of each key
20
- self.min_freq = 0 # To keep track of the minimum frequency
21
-
22
- def get(self, key: K) -> V:
23
- if key in self.cache:
24
- value, freq = self.cache[key]
25
- # Update frequency and move the key to the next frequency bucket
26
- self.freq_count[freq].pop(key)
27
- if not self.freq_count[freq]: # If the current frequency has no keys, update min_freq
28
- del self.freq_count[freq]
29
- if freq == self.min_freq:
30
- self.min_freq += 1
31
- freq += 1
32
- self.freq_count[freq][key] = value
33
- self.cache[key] = (value, freq)
34
- return value
35
- raise KeyError(f"Key {key} not found")
36
-
37
- def put(self, key: K, value: V) -> None:
38
- if key in self.cache:
39
- _, freq = self.cache[key]
40
- # Update frequency and move the key to the next frequency bucket
41
- self.freq_count[freq].pop(key)
42
- if not self.freq_count[freq]:
43
- del self.freq_count[freq]
44
- if freq == self.min_freq:
45
- self.min_freq += 1
46
- freq += 1
47
- self.freq_count[freq][key] = value
48
- self.cache[key] = (value, freq)
49
- else:
50
- # Check if cache is full, evict the least frequently used item
51
- if len(self.cache) == self.capacity:
52
- evicted_key, _ = self.freq_count[self.min_freq].popitem(last=False)
53
- del self.cache[evicted_key]
54
-
55
- # Add the new key-value pair with frequency 1
56
- self.cache[key] = (value, 1)
57
- self.freq_count[1][key] = value
58
- self.min_freq = 1
59
-
60
- def remove_key(self, key: K) -> None:
61
- if key in self.cache:
62
- _, freq = self.cache[key]
63
- self.freq_count[freq].pop(key)
64
- if not self.freq_count[freq]: # If the current frequency has no keys, update min_freq
65
- del self.freq_count[freq]
66
- if freq == self.min_freq:
67
- self.min_freq += 1
68
- del self.cache[key]
69
-
70
- def __iter__(self) -> Iterator[K]:
71
- yield from self.cache
hishel/_lmdb_types_.pyi DELETED
@@ -1,53 +0,0 @@
1
- from contextlib import contextmanager
2
- from typing import Any, Iterator
3
-
4
- class Database: ...
5
-
6
- class Transaction:
7
- def get(self, key: bytes, *, db: Database | None = None) -> bytes | None:
8
- """
9
- Get the value associated with the given key.
10
- """
11
- pass
12
-
13
- def put(self, key: bytes, value: bytes, *, db: Database | None = None, dupdata: bool = False) -> None:
14
- """
15
- Put a key-value pair into the database.
16
- """
17
- pass
18
-
19
- def delete(self, key: bytes, *, db: Database | None = None) -> bool:
20
- """
21
- Delete the key from the database.
22
- """
23
- pass
24
-
25
- def cursor(self, db: Database) -> Any:
26
- """
27
- Create a cursor for iterating over key-value pairs in the database.
28
- """
29
- pass
30
-
31
- class Environment:
32
- @contextmanager
33
- def begin(self, *, db: Database | None = None, write: bool = False) -> Iterator[Transaction]:
34
- """
35
- Begin a transaction in the environment.
36
- """
37
- raise NotImplementedError("It's only for type hinting purposes")
38
-
39
- def open_db(self, key: bytes, dupsort: bool = False) -> Database:
40
- """
41
- Open a database within the environment.
42
- """
43
- raise NotImplementedError("It's only for type hinting purposes")
44
-
45
- def open(
46
- path: str,
47
- *,
48
- max_dbs: int = 0,
49
- ) -> Environment:
50
- """
51
- Open an LMDB environment at the specified path.
52
- """
53
- raise NotImplementedError("It's only for type hinting purposes")
hishel/_s3.py DELETED
@@ -1,122 +0,0 @@
1
- import time
2
- import typing as tp
3
-
4
- from anyio import to_thread
5
- from botocore.exceptions import ClientError
6
-
7
-
8
- def get_timestamp_in_ms() -> float:
9
- return time.time() * 1000
10
-
11
-
12
- class S3Manager:
13
- def __init__(
14
- self,
15
- client: tp.Any,
16
- bucket_name: str,
17
- check_ttl_every: tp.Union[int, float],
18
- is_binary: bool = False,
19
- path_prefix: str = "hishel-",
20
- ):
21
- self._client = client
22
- self._bucket_name = bucket_name
23
- self._is_binary = is_binary
24
- self._last_cleaned = time.monotonic()
25
- self._check_ttl_every = check_ttl_every
26
- self._path_prefix = path_prefix
27
-
28
- def write_to(self, path: str, data: tp.Union[bytes, str], only_metadata: bool = False) -> None:
29
- path = self._path_prefix + path
30
- if isinstance(data, str):
31
- data = data.encode("utf-8")
32
-
33
- created_at = None
34
- if only_metadata:
35
- try:
36
- response = self._client.get_object(
37
- Bucket=self._bucket_name,
38
- Key=path,
39
- )
40
- created_at = response["Metadata"]["created_at"]
41
- except Exception:
42
- pass
43
-
44
- self._client.put_object(
45
- Bucket=self._bucket_name,
46
- Key=path,
47
- Body=data,
48
- Metadata={"created_at": created_at or str(get_timestamp_in_ms())},
49
- )
50
-
51
- def read_from(self, path: str) -> tp.Union[bytes, str]:
52
- path = self._path_prefix + path
53
- response = self._client.get_object(
54
- Bucket=self._bucket_name,
55
- Key=path,
56
- )
57
-
58
- content = response["Body"].read()
59
-
60
- if self._is_binary: # pragma: no cover
61
- return tp.cast(bytes, content)
62
-
63
- return tp.cast(str, content.decode("utf-8"))
64
-
65
- def remove_expired(self, ttl: int, key: str) -> None:
66
- path = self._path_prefix + key
67
-
68
- if time.monotonic() - self._last_cleaned < self._check_ttl_every:
69
- try:
70
- response = self._client.get_object(Bucket=self._bucket_name, Key=path)
71
- if get_timestamp_in_ms() - float(response["Metadata"]["created_at"]) > ttl:
72
- self._client.delete_object(Bucket=self._bucket_name, Key=path)
73
- return
74
- except ClientError as e:
75
- if e.response["Error"]["Code"] == "NoSuchKey":
76
- return
77
- raise e
78
-
79
- self._last_cleaned = time.monotonic()
80
- for obj in self._client.list_objects(Bucket=self._bucket_name).get("Contents", []):
81
- if not obj["Key"].startswith(self._path_prefix): # pragma: no cover
82
- continue
83
-
84
- try:
85
- metadata_obj = self._client.head_object(Bucket=self._bucket_name, Key=obj["Key"]).get("Metadata", {})
86
- except ClientError as e:
87
- if e.response["Error"]["Code"] == "404":
88
- continue
89
-
90
- if not metadata_obj or "created_at" not in metadata_obj:
91
- continue
92
-
93
- if get_timestamp_in_ms() - float(metadata_obj["created_at"]) > ttl:
94
- self._client.delete_object(Bucket=self._bucket_name, Key=obj["Key"])
95
-
96
- def remove_entry(self, key: str) -> None:
97
- path = self._path_prefix + key
98
- self._client.delete_object(Bucket=self._bucket_name, Key=path)
99
-
100
-
101
- class AsyncS3Manager: # pragma: no cover
102
- def __init__(
103
- self,
104
- client: tp.Any,
105
- bucket_name: str,
106
- check_ttl_every: tp.Union[int, float],
107
- is_binary: bool = False,
108
- path_prefix: str = "hishel-",
109
- ):
110
- self._sync_manager = S3Manager(client, bucket_name, check_ttl_every, is_binary, path_prefix)
111
-
112
- async def write_to(self, path: str, data: tp.Union[bytes, str], only_metadata: bool = False) -> None:
113
- return await to_thread.run_sync(self._sync_manager.write_to, path, data, only_metadata)
114
-
115
- async def read_from(self, path: str) -> tp.Union[bytes, str]:
116
- return await to_thread.run_sync(self._sync_manager.read_from, path)
117
-
118
- async def remove_expired(self, ttl: int, key: str) -> None:
119
- return await to_thread.run_sync(self._sync_manager.remove_expired, ttl, key)
120
-
121
- async def remove_entry(self, key: str) -> None:
122
- return await to_thread.run_sync(self._sync_manager.remove_entry, key)