multidict 6.7.0__cp314-cp314t-macosx_10_13_universal2.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.
Potentially problematic release.
This version of multidict might be problematic. Click here for more details.
- multidict/__init__.py +60 -0
- multidict/_abc.py +73 -0
- multidict/_compat.py +15 -0
- multidict/_multidict.cpython-314t-darwin.so +0 -0
- multidict/_multidict_py.py +1242 -0
- multidict/py.typed +1 -0
- multidict-6.7.0.dist-info/METADATA +149 -0
- multidict-6.7.0.dist-info/RECORD +11 -0
- multidict-6.7.0.dist-info/WHEEL +6 -0
- multidict-6.7.0.dist-info/licenses/LICENSE +13 -0
- multidict-6.7.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1242 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
import functools
|
|
3
|
+
import reprlib
|
|
4
|
+
import sys
|
|
5
|
+
from array import array
|
|
6
|
+
from collections.abc import (
|
|
7
|
+
ItemsView,
|
|
8
|
+
Iterable,
|
|
9
|
+
Iterator,
|
|
10
|
+
KeysView,
|
|
11
|
+
Mapping,
|
|
12
|
+
ValuesView,
|
|
13
|
+
)
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import (
|
|
16
|
+
TYPE_CHECKING,
|
|
17
|
+
Any,
|
|
18
|
+
ClassVar,
|
|
19
|
+
Generic,
|
|
20
|
+
NoReturn,
|
|
21
|
+
Optional,
|
|
22
|
+
TypeVar,
|
|
23
|
+
Union,
|
|
24
|
+
cast,
|
|
25
|
+
overload,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
from ._abc import MDArg, MultiMapping, MutableMultiMapping, SupportsKeys
|
|
29
|
+
|
|
30
|
+
if sys.version_info >= (3, 11):
|
|
31
|
+
from typing import Self
|
|
32
|
+
else:
|
|
33
|
+
from typing_extensions import Self
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class istr(str):
|
|
37
|
+
"""Case insensitive str."""
|
|
38
|
+
|
|
39
|
+
__is_istr__ = True
|
|
40
|
+
__istr_identity__: Optional[str] = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
_V = TypeVar("_V")
|
|
44
|
+
_T = TypeVar("_T")
|
|
45
|
+
|
|
46
|
+
_SENTINEL = enum.Enum("_SENTINEL", "sentinel")
|
|
47
|
+
sentinel = _SENTINEL.sentinel
|
|
48
|
+
|
|
49
|
+
_version = array("Q", [0])
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class _Iter(Generic[_T]):
|
|
53
|
+
__slots__ = ("_size", "_iter")
|
|
54
|
+
|
|
55
|
+
def __init__(self, size: int, iterator: Iterator[_T]):
|
|
56
|
+
self._size = size
|
|
57
|
+
self._iter = iterator
|
|
58
|
+
|
|
59
|
+
def __iter__(self) -> Self:
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
def __next__(self) -> _T:
|
|
63
|
+
return next(self._iter)
|
|
64
|
+
|
|
65
|
+
def __length_hint__(self) -> int:
|
|
66
|
+
return self._size
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class _ViewBase(Generic[_V]):
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
md: "MultiDict[_V]",
|
|
73
|
+
):
|
|
74
|
+
self._md = md
|
|
75
|
+
|
|
76
|
+
def __len__(self) -> int:
|
|
77
|
+
return len(self._md)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class _ItemsView(_ViewBase[_V], ItemsView[str, _V]):
|
|
81
|
+
def __contains__(self, item: object) -> bool:
|
|
82
|
+
if not isinstance(item, (tuple, list)) or len(item) != 2:
|
|
83
|
+
return False
|
|
84
|
+
key, value = item
|
|
85
|
+
try:
|
|
86
|
+
identity = self._md._identity(key)
|
|
87
|
+
except TypeError:
|
|
88
|
+
return False
|
|
89
|
+
hash_ = hash(identity)
|
|
90
|
+
for slot, idx, e in self._md._keys.iter_hash(hash_):
|
|
91
|
+
if e.identity == identity and value == e.value:
|
|
92
|
+
return True
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
def __iter__(self) -> _Iter[tuple[str, _V]]:
|
|
96
|
+
return _Iter(len(self), self._iter(self._md._version))
|
|
97
|
+
|
|
98
|
+
def _iter(self, version: int) -> Iterator[tuple[str, _V]]:
|
|
99
|
+
for e in self._md._keys.iter_entries():
|
|
100
|
+
if version != self._md._version:
|
|
101
|
+
raise RuntimeError("Dictionary changed during iteration")
|
|
102
|
+
yield self._md._key(e.key), e.value
|
|
103
|
+
|
|
104
|
+
@reprlib.recursive_repr()
|
|
105
|
+
def __repr__(self) -> str:
|
|
106
|
+
lst = []
|
|
107
|
+
for e in self._md._keys.iter_entries():
|
|
108
|
+
lst.append(f"'{e.key}': {e.value!r}")
|
|
109
|
+
body = ", ".join(lst)
|
|
110
|
+
return f"<{self.__class__.__name__}({body})>"
|
|
111
|
+
|
|
112
|
+
def _parse_item(
|
|
113
|
+
self, arg: Union[tuple[str, _V], _T]
|
|
114
|
+
) -> Optional[tuple[int, str, str, _V]]:
|
|
115
|
+
if not isinstance(arg, tuple):
|
|
116
|
+
return None
|
|
117
|
+
if len(arg) != 2:
|
|
118
|
+
return None
|
|
119
|
+
try:
|
|
120
|
+
identity = self._md._identity(arg[0])
|
|
121
|
+
return (hash(identity), identity, arg[0], arg[1])
|
|
122
|
+
except TypeError:
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
def _tmp_set(self, it: Iterable[_T]) -> set[tuple[str, _V]]:
|
|
126
|
+
tmp = set()
|
|
127
|
+
for arg in it:
|
|
128
|
+
item = self._parse_item(arg)
|
|
129
|
+
if item is None:
|
|
130
|
+
continue
|
|
131
|
+
else:
|
|
132
|
+
tmp.add((item[1], item[3]))
|
|
133
|
+
return tmp
|
|
134
|
+
|
|
135
|
+
def __and__(self, other: Iterable[Any]) -> set[tuple[str, _V]]:
|
|
136
|
+
ret = set()
|
|
137
|
+
try:
|
|
138
|
+
it = iter(other)
|
|
139
|
+
except TypeError:
|
|
140
|
+
return NotImplemented
|
|
141
|
+
for arg in it:
|
|
142
|
+
item = self._parse_item(arg)
|
|
143
|
+
if item is None:
|
|
144
|
+
continue
|
|
145
|
+
hash_, identity, key, value = item
|
|
146
|
+
for slot, idx, e in self._md._keys.iter_hash(hash_):
|
|
147
|
+
e.hash = -1
|
|
148
|
+
if e.identity == identity and e.value == value:
|
|
149
|
+
ret.add((e.key, e.value))
|
|
150
|
+
self._md._keys.restore_hash(hash_)
|
|
151
|
+
return ret
|
|
152
|
+
|
|
153
|
+
def __rand__(self, other: Iterable[_T]) -> set[_T]:
|
|
154
|
+
ret = set()
|
|
155
|
+
try:
|
|
156
|
+
it = iter(other)
|
|
157
|
+
except TypeError:
|
|
158
|
+
return NotImplemented
|
|
159
|
+
for arg in it:
|
|
160
|
+
item = self._parse_item(arg)
|
|
161
|
+
if item is None:
|
|
162
|
+
continue
|
|
163
|
+
hash_, identity, key, value = item
|
|
164
|
+
for slot, idx, e in self._md._keys.iter_hash(hash_):
|
|
165
|
+
if e.identity == identity and e.value == value:
|
|
166
|
+
ret.add(arg)
|
|
167
|
+
break
|
|
168
|
+
return ret
|
|
169
|
+
|
|
170
|
+
def __or__(self, other: Iterable[_T]) -> set[Union[tuple[str, _V], _T]]:
|
|
171
|
+
ret: set[Union[tuple[str, _V], _T]] = set(self)
|
|
172
|
+
try:
|
|
173
|
+
it = iter(other)
|
|
174
|
+
except TypeError:
|
|
175
|
+
return NotImplemented
|
|
176
|
+
for arg in it:
|
|
177
|
+
item: Optional[tuple[int, str, str, _V]] = self._parse_item(arg)
|
|
178
|
+
if item is None:
|
|
179
|
+
ret.add(arg)
|
|
180
|
+
continue
|
|
181
|
+
hash_, identity, key, value = item
|
|
182
|
+
for slot, idx, e in self._md._keys.iter_hash(hash_):
|
|
183
|
+
if e.identity == identity and e.value == value: # pragma: no branch
|
|
184
|
+
break
|
|
185
|
+
else:
|
|
186
|
+
ret.add(arg)
|
|
187
|
+
return ret
|
|
188
|
+
|
|
189
|
+
def __ror__(self, other: Iterable[_T]) -> set[Union[tuple[str, _V], _T]]:
|
|
190
|
+
try:
|
|
191
|
+
ret: set[Union[tuple[str, _V], _T]] = set(other)
|
|
192
|
+
except TypeError:
|
|
193
|
+
return NotImplemented
|
|
194
|
+
tmp = self._tmp_set(ret)
|
|
195
|
+
|
|
196
|
+
for e in self._md._keys.iter_entries():
|
|
197
|
+
if (e.identity, e.value) not in tmp:
|
|
198
|
+
ret.add((e.key, e.value))
|
|
199
|
+
return ret
|
|
200
|
+
|
|
201
|
+
def __sub__(self, other: Iterable[_T]) -> set[Union[tuple[str, _V], _T]]:
|
|
202
|
+
ret: set[Union[tuple[str, _V], _T]] = set()
|
|
203
|
+
try:
|
|
204
|
+
it = iter(other)
|
|
205
|
+
except TypeError:
|
|
206
|
+
return NotImplemented
|
|
207
|
+
tmp = self._tmp_set(it)
|
|
208
|
+
|
|
209
|
+
for e in self._md._keys.iter_entries():
|
|
210
|
+
if (e.identity, e.value) not in tmp:
|
|
211
|
+
ret.add((e.key, e.value))
|
|
212
|
+
|
|
213
|
+
return ret
|
|
214
|
+
|
|
215
|
+
def __rsub__(self, other: Iterable[_T]) -> set[_T]:
|
|
216
|
+
ret: set[_T] = set()
|
|
217
|
+
try:
|
|
218
|
+
it = iter(other)
|
|
219
|
+
except TypeError:
|
|
220
|
+
return NotImplemented
|
|
221
|
+
for arg in it:
|
|
222
|
+
item = self._parse_item(arg)
|
|
223
|
+
if item is None:
|
|
224
|
+
ret.add(arg)
|
|
225
|
+
continue
|
|
226
|
+
|
|
227
|
+
hash_, identity, key, value = item
|
|
228
|
+
for slot, idx, e in self._md._keys.iter_hash(hash_):
|
|
229
|
+
if e.identity == identity and e.value == value: # pragma: no branch
|
|
230
|
+
break
|
|
231
|
+
else:
|
|
232
|
+
ret.add(arg)
|
|
233
|
+
return ret
|
|
234
|
+
|
|
235
|
+
def __xor__(self, other: Iterable[_T]) -> set[Union[tuple[str, _V], _T]]:
|
|
236
|
+
try:
|
|
237
|
+
rgt = set(other)
|
|
238
|
+
except TypeError:
|
|
239
|
+
return NotImplemented
|
|
240
|
+
ret: set[Union[tuple[str, _V], _T]] = self - rgt
|
|
241
|
+
ret |= rgt - self
|
|
242
|
+
return ret
|
|
243
|
+
|
|
244
|
+
__rxor__ = __xor__
|
|
245
|
+
|
|
246
|
+
def isdisjoint(self, other: Iterable[tuple[str, _V]]) -> bool:
|
|
247
|
+
for arg in other:
|
|
248
|
+
item = self._parse_item(arg)
|
|
249
|
+
if item is None:
|
|
250
|
+
continue
|
|
251
|
+
|
|
252
|
+
hash_, identity, key, value = item
|
|
253
|
+
for slot, idx, e in self._md._keys.iter_hash(hash_):
|
|
254
|
+
if e.identity == identity and e.value == value: # pragma: no branch
|
|
255
|
+
return False
|
|
256
|
+
return True
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class _ValuesView(_ViewBase[_V], ValuesView[_V]):
|
|
260
|
+
def __contains__(self, value: object) -> bool:
|
|
261
|
+
for e in self._md._keys.iter_entries():
|
|
262
|
+
if e.value == value:
|
|
263
|
+
return True
|
|
264
|
+
return False
|
|
265
|
+
|
|
266
|
+
def __iter__(self) -> _Iter[_V]:
|
|
267
|
+
return _Iter(len(self), self._iter(self._md._version))
|
|
268
|
+
|
|
269
|
+
def _iter(self, version: int) -> Iterator[_V]:
|
|
270
|
+
for e in self._md._keys.iter_entries():
|
|
271
|
+
if version != self._md._version:
|
|
272
|
+
raise RuntimeError("Dictionary changed during iteration")
|
|
273
|
+
yield e.value
|
|
274
|
+
|
|
275
|
+
@reprlib.recursive_repr()
|
|
276
|
+
def __repr__(self) -> str:
|
|
277
|
+
lst = []
|
|
278
|
+
for e in self._md._keys.iter_entries():
|
|
279
|
+
lst.append(repr(e.value))
|
|
280
|
+
body = ", ".join(lst)
|
|
281
|
+
return f"<{self.__class__.__name__}({body})>"
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class _KeysView(_ViewBase[_V], KeysView[str]):
|
|
285
|
+
def __contains__(self, key: object) -> bool:
|
|
286
|
+
if not isinstance(key, str):
|
|
287
|
+
return False
|
|
288
|
+
identity = self._md._identity(key)
|
|
289
|
+
hash_ = hash(identity)
|
|
290
|
+
for slot, idx, e in self._md._keys.iter_hash(hash_):
|
|
291
|
+
if e.identity == identity: # pragma: no branch
|
|
292
|
+
return True
|
|
293
|
+
return False
|
|
294
|
+
|
|
295
|
+
def __iter__(self) -> _Iter[str]:
|
|
296
|
+
return _Iter(len(self), self._iter(self._md._version))
|
|
297
|
+
|
|
298
|
+
def _iter(self, version: int) -> Iterator[str]:
|
|
299
|
+
for e in self._md._keys.iter_entries():
|
|
300
|
+
if version != self._md._version:
|
|
301
|
+
raise RuntimeError("Dictionary changed during iteration")
|
|
302
|
+
yield self._md._key(e.key)
|
|
303
|
+
|
|
304
|
+
def __repr__(self) -> str:
|
|
305
|
+
lst = []
|
|
306
|
+
for e in self._md._keys.iter_entries():
|
|
307
|
+
lst.append(f"'{e.key}'")
|
|
308
|
+
body = ", ".join(lst)
|
|
309
|
+
return f"<{self.__class__.__name__}({body})>"
|
|
310
|
+
|
|
311
|
+
def __and__(self, other: Iterable[object]) -> set[str]:
|
|
312
|
+
ret = set()
|
|
313
|
+
try:
|
|
314
|
+
it = iter(other)
|
|
315
|
+
except TypeError:
|
|
316
|
+
return NotImplemented
|
|
317
|
+
for key in it:
|
|
318
|
+
if not isinstance(key, str):
|
|
319
|
+
continue
|
|
320
|
+
identity = self._md._identity(key)
|
|
321
|
+
hash_ = hash(identity)
|
|
322
|
+
for slot, idx, e in self._md._keys.iter_hash(hash_):
|
|
323
|
+
if e.identity == identity: # pragma: no branch
|
|
324
|
+
ret.add(e.key)
|
|
325
|
+
break
|
|
326
|
+
return ret
|
|
327
|
+
|
|
328
|
+
def __rand__(self, other: Iterable[_T]) -> set[_T]:
|
|
329
|
+
ret = set()
|
|
330
|
+
try:
|
|
331
|
+
it = iter(other)
|
|
332
|
+
except TypeError:
|
|
333
|
+
return NotImplemented
|
|
334
|
+
for key in it:
|
|
335
|
+
if not isinstance(key, str):
|
|
336
|
+
continue
|
|
337
|
+
if key in self._md:
|
|
338
|
+
ret.add(key)
|
|
339
|
+
return cast(set[_T], ret)
|
|
340
|
+
|
|
341
|
+
def __or__(self, other: Iterable[_T]) -> set[Union[str, _T]]:
|
|
342
|
+
ret: set[Union[str, _T]] = set(self)
|
|
343
|
+
try:
|
|
344
|
+
it = iter(other)
|
|
345
|
+
except TypeError:
|
|
346
|
+
return NotImplemented
|
|
347
|
+
for key in it:
|
|
348
|
+
if not isinstance(key, str):
|
|
349
|
+
ret.add(key)
|
|
350
|
+
continue
|
|
351
|
+
if key not in self._md:
|
|
352
|
+
ret.add(key)
|
|
353
|
+
return ret
|
|
354
|
+
|
|
355
|
+
def __ror__(self, other: Iterable[_T]) -> set[Union[str, _T]]:
|
|
356
|
+
try:
|
|
357
|
+
ret: set[Union[str, _T]] = set(other)
|
|
358
|
+
except TypeError:
|
|
359
|
+
return NotImplemented
|
|
360
|
+
|
|
361
|
+
tmp = set()
|
|
362
|
+
for key in ret:
|
|
363
|
+
if not isinstance(key, str):
|
|
364
|
+
continue
|
|
365
|
+
identity = self._md._identity(key)
|
|
366
|
+
tmp.add(identity)
|
|
367
|
+
|
|
368
|
+
for e in self._md._keys.iter_entries():
|
|
369
|
+
if e.identity not in tmp:
|
|
370
|
+
ret.add(e.key)
|
|
371
|
+
return ret
|
|
372
|
+
|
|
373
|
+
def __sub__(self, other: Iterable[object]) -> set[str]:
|
|
374
|
+
ret = set(self)
|
|
375
|
+
try:
|
|
376
|
+
it = iter(other)
|
|
377
|
+
except TypeError:
|
|
378
|
+
return NotImplemented
|
|
379
|
+
for key in it:
|
|
380
|
+
if not isinstance(key, str):
|
|
381
|
+
continue
|
|
382
|
+
identity = self._md._identity(key)
|
|
383
|
+
hash_ = hash(identity)
|
|
384
|
+
for slot, idx, e in self._md._keys.iter_hash(hash_):
|
|
385
|
+
if e.identity == identity: # pragma: no branch
|
|
386
|
+
ret.discard(e.key)
|
|
387
|
+
break
|
|
388
|
+
return ret
|
|
389
|
+
|
|
390
|
+
def __rsub__(self, other: Iterable[_T]) -> set[_T]:
|
|
391
|
+
try:
|
|
392
|
+
ret: set[_T] = set(other)
|
|
393
|
+
except TypeError:
|
|
394
|
+
return NotImplemented
|
|
395
|
+
for key in other:
|
|
396
|
+
if not isinstance(key, str):
|
|
397
|
+
continue
|
|
398
|
+
if key in self._md:
|
|
399
|
+
ret.discard(key) # type: ignore[arg-type]
|
|
400
|
+
return ret
|
|
401
|
+
|
|
402
|
+
def __xor__(self, other: Iterable[_T]) -> set[Union[str, _T]]:
|
|
403
|
+
try:
|
|
404
|
+
rgt = set(other)
|
|
405
|
+
except TypeError:
|
|
406
|
+
return NotImplemented
|
|
407
|
+
ret: set[Union[str, _T]] = self - rgt # type: ignore[assignment]
|
|
408
|
+
ret |= rgt - self
|
|
409
|
+
return ret
|
|
410
|
+
|
|
411
|
+
__rxor__ = __xor__
|
|
412
|
+
|
|
413
|
+
def isdisjoint(self, other: Iterable[object]) -> bool:
|
|
414
|
+
for key in other:
|
|
415
|
+
if not isinstance(key, str):
|
|
416
|
+
continue
|
|
417
|
+
if key in self._md:
|
|
418
|
+
return False
|
|
419
|
+
return True
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
class _CSMixin:
|
|
423
|
+
_ci: ClassVar[bool] = False
|
|
424
|
+
|
|
425
|
+
def _key(self, key: str) -> str:
|
|
426
|
+
return key
|
|
427
|
+
|
|
428
|
+
def _identity(self, key: str) -> str:
|
|
429
|
+
if isinstance(key, str):
|
|
430
|
+
return key
|
|
431
|
+
else:
|
|
432
|
+
raise TypeError("MultiDict keys should be either str or subclasses of str")
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
class _CIMixin:
|
|
436
|
+
_ci: ClassVar[bool] = True
|
|
437
|
+
|
|
438
|
+
def _key(self, key: str) -> str:
|
|
439
|
+
if type(key) is istr:
|
|
440
|
+
return key
|
|
441
|
+
else:
|
|
442
|
+
return istr(key)
|
|
443
|
+
|
|
444
|
+
def _identity(self, key: str) -> str:
|
|
445
|
+
if isinstance(key, istr):
|
|
446
|
+
ret = key.__istr_identity__
|
|
447
|
+
if ret is None:
|
|
448
|
+
ret = key.lower()
|
|
449
|
+
key.__istr_identity__ = ret
|
|
450
|
+
return ret
|
|
451
|
+
if isinstance(key, str):
|
|
452
|
+
return key.lower()
|
|
453
|
+
else:
|
|
454
|
+
raise TypeError("MultiDict keys should be either str or subclasses of str")
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def estimate_log2_keysize(n: int) -> int:
|
|
458
|
+
# 7 == HT_MINSIZE - 1
|
|
459
|
+
return (((n * 3 + 1) // 2) | 7).bit_length()
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
@dataclass
|
|
463
|
+
class _Entry(Generic[_V]):
|
|
464
|
+
hash: int
|
|
465
|
+
identity: str
|
|
466
|
+
key: str
|
|
467
|
+
value: _V
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
@dataclass
|
|
471
|
+
class _HtKeys(Generic[_V]): # type: ignore[misc]
|
|
472
|
+
LOG_MINSIZE: ClassVar[int] = 3
|
|
473
|
+
MINSIZE: ClassVar[int] = 8
|
|
474
|
+
PREALLOCATED_INDICES: ClassVar[dict[int, array]] = { # type: ignore[type-arg]
|
|
475
|
+
log2_size: array(
|
|
476
|
+
"b" if log2_size < 8 else "h", (-1 for i in range(1 << log2_size))
|
|
477
|
+
)
|
|
478
|
+
for log2_size in range(3, 10)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
log2_size: int
|
|
482
|
+
usable: int
|
|
483
|
+
|
|
484
|
+
indices: array # type: ignore[type-arg] # in py3.9 array is not generic
|
|
485
|
+
entries: list[Optional[_Entry[_V]]]
|
|
486
|
+
|
|
487
|
+
@functools.cached_property
|
|
488
|
+
def nslots(self) -> int:
|
|
489
|
+
return 1 << self.log2_size
|
|
490
|
+
|
|
491
|
+
@functools.cached_property
|
|
492
|
+
def mask(self) -> int:
|
|
493
|
+
return self.nslots - 1
|
|
494
|
+
|
|
495
|
+
if sys.implementation.name != "pypy":
|
|
496
|
+
|
|
497
|
+
def __sizeof__(self) -> int:
|
|
498
|
+
return (
|
|
499
|
+
object.__sizeof__(self)
|
|
500
|
+
+ sys.getsizeof(self.indices)
|
|
501
|
+
+ sys.getsizeof(self.entries)
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
@classmethod
|
|
505
|
+
def new(cls, log2_size: int, entries: list[Optional[_Entry[_V]]]) -> Self:
|
|
506
|
+
size = 1 << log2_size
|
|
507
|
+
usable = (size << 1) // 3
|
|
508
|
+
if log2_size < 10:
|
|
509
|
+
indices = cls.PREALLOCATED_INDICES[log2_size].__copy__()
|
|
510
|
+
elif log2_size < 16:
|
|
511
|
+
indices = array("h", (-1 for i in range(size)))
|
|
512
|
+
elif log2_size < 32:
|
|
513
|
+
indices = array("l", (-1 for i in range(size)))
|
|
514
|
+
else: # pragma: no cover # don't test huge multidicts
|
|
515
|
+
indices = array("q", (-1 for i in range(size)))
|
|
516
|
+
ret = cls(
|
|
517
|
+
log2_size=log2_size,
|
|
518
|
+
usable=usable,
|
|
519
|
+
indices=indices,
|
|
520
|
+
entries=entries,
|
|
521
|
+
)
|
|
522
|
+
return ret
|
|
523
|
+
|
|
524
|
+
def clone(self) -> "_HtKeys[_V]":
|
|
525
|
+
entries = [
|
|
526
|
+
_Entry(e.hash, e.identity, e.key, e.value) if e is not None else None
|
|
527
|
+
for e in self.entries
|
|
528
|
+
]
|
|
529
|
+
|
|
530
|
+
return _HtKeys(
|
|
531
|
+
log2_size=self.log2_size,
|
|
532
|
+
usable=self.usable,
|
|
533
|
+
indices=self.indices.__copy__(),
|
|
534
|
+
entries=entries,
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
def build_indices(self, update: bool) -> None:
|
|
538
|
+
mask = self.mask
|
|
539
|
+
indices = self.indices
|
|
540
|
+
for idx, e in enumerate(self.entries):
|
|
541
|
+
assert e is not None
|
|
542
|
+
hash_ = e.hash
|
|
543
|
+
if update:
|
|
544
|
+
if hash_ == -1:
|
|
545
|
+
hash_ = hash(e.identity)
|
|
546
|
+
else:
|
|
547
|
+
assert hash_ != -1
|
|
548
|
+
i = hash_ & mask
|
|
549
|
+
perturb = hash_ & sys.maxsize
|
|
550
|
+
while indices[i] != -1:
|
|
551
|
+
perturb >>= 5
|
|
552
|
+
i = mask & (i * 5 + perturb + 1)
|
|
553
|
+
indices[i] = idx
|
|
554
|
+
|
|
555
|
+
def find_empty_slot(self, hash_: int) -> int:
|
|
556
|
+
mask = self.mask
|
|
557
|
+
indices = self.indices
|
|
558
|
+
i = hash_ & mask
|
|
559
|
+
perturb = hash_ & sys.maxsize
|
|
560
|
+
ix = indices[i]
|
|
561
|
+
while ix != -1:
|
|
562
|
+
perturb >>= 5
|
|
563
|
+
i = (i * 5 + perturb + 1) & mask
|
|
564
|
+
ix = indices[i]
|
|
565
|
+
return i
|
|
566
|
+
|
|
567
|
+
def iter_hash(self, hash_: int) -> Iterator[tuple[int, int, _Entry[_V]]]:
|
|
568
|
+
mask = self.mask
|
|
569
|
+
indices = self.indices
|
|
570
|
+
entries = self.entries
|
|
571
|
+
i = hash_ & mask
|
|
572
|
+
perturb = hash_ & sys.maxsize
|
|
573
|
+
ix = indices[i]
|
|
574
|
+
while ix != -1:
|
|
575
|
+
if ix != -2:
|
|
576
|
+
e = entries[ix]
|
|
577
|
+
if e.hash == hash_:
|
|
578
|
+
yield i, ix, e
|
|
579
|
+
perturb >>= 5
|
|
580
|
+
i = (i * 5 + perturb + 1) & mask
|
|
581
|
+
ix = indices[i]
|
|
582
|
+
|
|
583
|
+
def del_idx(self, hash_: int, idx: int) -> None:
|
|
584
|
+
mask = self.mask
|
|
585
|
+
indices = self.indices
|
|
586
|
+
i = hash_ & mask
|
|
587
|
+
perturb = hash_ & sys.maxsize
|
|
588
|
+
ix = indices[i]
|
|
589
|
+
while ix != idx:
|
|
590
|
+
perturb >>= 5
|
|
591
|
+
i = (i * 5 + perturb + 1) & mask
|
|
592
|
+
ix = indices[i]
|
|
593
|
+
indices[i] = -2
|
|
594
|
+
|
|
595
|
+
def iter_entries(self) -> Iterator[_Entry[_V]]:
|
|
596
|
+
return filter(None, self.entries)
|
|
597
|
+
|
|
598
|
+
def restore_hash(self, hash_: int) -> None:
|
|
599
|
+
mask = self.mask
|
|
600
|
+
indices = self.indices
|
|
601
|
+
entries = self.entries
|
|
602
|
+
i = hash_ & mask
|
|
603
|
+
perturb = hash_ & sys.maxsize
|
|
604
|
+
ix = indices[i]
|
|
605
|
+
while ix != -1:
|
|
606
|
+
if ix != -2:
|
|
607
|
+
entry = entries[ix]
|
|
608
|
+
if entry.hash == -1:
|
|
609
|
+
entry.hash = hash_
|
|
610
|
+
perturb >>= 5
|
|
611
|
+
i = (i * 5 + perturb + 1) & mask
|
|
612
|
+
ix = indices[i]
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
class MultiDict(_CSMixin, MutableMultiMapping[_V]):
|
|
616
|
+
"""Dictionary with the support for duplicate keys."""
|
|
617
|
+
|
|
618
|
+
__slots__ = ("_keys", "_used", "_version")
|
|
619
|
+
|
|
620
|
+
def __init__(self, arg: MDArg[_V] = None, /, **kwargs: _V):
|
|
621
|
+
self._used = 0
|
|
622
|
+
v = _version
|
|
623
|
+
v[0] += 1
|
|
624
|
+
self._version = v[0]
|
|
625
|
+
if not kwargs:
|
|
626
|
+
md = None
|
|
627
|
+
if isinstance(arg, MultiDictProxy):
|
|
628
|
+
md = arg._md
|
|
629
|
+
elif isinstance(arg, MultiDict):
|
|
630
|
+
md = arg
|
|
631
|
+
if md is not None and md._ci is self._ci:
|
|
632
|
+
self._from_md(md)
|
|
633
|
+
return
|
|
634
|
+
|
|
635
|
+
it = self._parse_args(arg, kwargs)
|
|
636
|
+
log2_size = estimate_log2_keysize(cast(int, next(it)))
|
|
637
|
+
if log2_size > 17: # pragma: no cover
|
|
638
|
+
# Don't overallocate really huge keys space in init
|
|
639
|
+
log2_size = 17
|
|
640
|
+
self._keys: _HtKeys[_V] = _HtKeys.new(log2_size, [])
|
|
641
|
+
self._extend_items(cast(Iterator[_Entry[_V]], it))
|
|
642
|
+
|
|
643
|
+
def _from_md(self, md: "MultiDict[_V]") -> None:
|
|
644
|
+
# Copy everything as-is without compacting the new multidict,
|
|
645
|
+
# otherwise it requires reindexing
|
|
646
|
+
self._keys = md._keys.clone()
|
|
647
|
+
self._used = md._used
|
|
648
|
+
|
|
649
|
+
@overload
|
|
650
|
+
def getall(self, key: str) -> list[_V]: ...
|
|
651
|
+
@overload
|
|
652
|
+
def getall(self, key: str, default: _T) -> Union[list[_V], _T]: ...
|
|
653
|
+
def getall(
|
|
654
|
+
self, key: str, default: Union[_T, _SENTINEL] = sentinel
|
|
655
|
+
) -> Union[list[_V], _T]:
|
|
656
|
+
"""Return a list of all values matching the key."""
|
|
657
|
+
identity = self._identity(key)
|
|
658
|
+
hash_ = hash(identity)
|
|
659
|
+
res = []
|
|
660
|
+
restore = []
|
|
661
|
+
for slot, idx, e in self._keys.iter_hash(hash_):
|
|
662
|
+
if e.identity == identity: # pragma: no branch
|
|
663
|
+
res.append(e.value)
|
|
664
|
+
e.hash = -1
|
|
665
|
+
restore.append(idx)
|
|
666
|
+
|
|
667
|
+
if res:
|
|
668
|
+
entries = self._keys.entries
|
|
669
|
+
for idx in restore:
|
|
670
|
+
entries[idx].hash = hash_ # type: ignore[union-attr]
|
|
671
|
+
return res
|
|
672
|
+
if not res and default is not sentinel:
|
|
673
|
+
return default
|
|
674
|
+
raise KeyError("Key not found: %r" % key)
|
|
675
|
+
|
|
676
|
+
@overload
|
|
677
|
+
def getone(self, key: str) -> _V: ...
|
|
678
|
+
@overload
|
|
679
|
+
def getone(self, key: str, default: _T) -> Union[_V, _T]: ...
|
|
680
|
+
def getone(
|
|
681
|
+
self, key: str, default: Union[_T, _SENTINEL] = sentinel
|
|
682
|
+
) -> Union[_V, _T]:
|
|
683
|
+
"""Get first value matching the key.
|
|
684
|
+
|
|
685
|
+
Raises KeyError if the key is not found and no default is provided.
|
|
686
|
+
"""
|
|
687
|
+
identity = self._identity(key)
|
|
688
|
+
hash_ = hash(identity)
|
|
689
|
+
for slot, idx, e in self._keys.iter_hash(hash_):
|
|
690
|
+
if e.identity == identity: # pragma: no branch
|
|
691
|
+
return e.value
|
|
692
|
+
if default is not sentinel:
|
|
693
|
+
return default
|
|
694
|
+
raise KeyError("Key not found: %r" % key)
|
|
695
|
+
|
|
696
|
+
# Mapping interface #
|
|
697
|
+
|
|
698
|
+
def __getitem__(self, key: str) -> _V:
|
|
699
|
+
return self.getone(key)
|
|
700
|
+
|
|
701
|
+
@overload
|
|
702
|
+
def get(self, key: str, /) -> Union[_V, None]: ...
|
|
703
|
+
@overload
|
|
704
|
+
def get(self, key: str, /, default: _T) -> Union[_V, _T]: ...
|
|
705
|
+
def get(self, key: str, default: Union[_T, None] = None) -> Union[_V, _T, None]:
|
|
706
|
+
"""Get first value matching the key.
|
|
707
|
+
|
|
708
|
+
If the key is not found, returns the default (or None if no default is provided)
|
|
709
|
+
"""
|
|
710
|
+
return self.getone(key, default)
|
|
711
|
+
|
|
712
|
+
def __iter__(self) -> Iterator[str]:
|
|
713
|
+
return iter(self.keys())
|
|
714
|
+
|
|
715
|
+
def __len__(self) -> int:
|
|
716
|
+
return self._used
|
|
717
|
+
|
|
718
|
+
def keys(self) -> KeysView[str]:
|
|
719
|
+
"""Return a new view of the dictionary's keys."""
|
|
720
|
+
return _KeysView(self)
|
|
721
|
+
|
|
722
|
+
def items(self) -> ItemsView[str, _V]:
|
|
723
|
+
"""Return a new view of the dictionary's items *(key, value) pairs)."""
|
|
724
|
+
return _ItemsView(self)
|
|
725
|
+
|
|
726
|
+
def values(self) -> _ValuesView[_V]:
|
|
727
|
+
"""Return a new view of the dictionary's values."""
|
|
728
|
+
return _ValuesView(self)
|
|
729
|
+
|
|
730
|
+
def __eq__(self, other: object) -> bool:
|
|
731
|
+
if not isinstance(other, Mapping):
|
|
732
|
+
return NotImplemented
|
|
733
|
+
if isinstance(other, MultiDictProxy):
|
|
734
|
+
return self == other._md
|
|
735
|
+
if isinstance(other, MultiDict):
|
|
736
|
+
lft = self._keys
|
|
737
|
+
rht = other._keys
|
|
738
|
+
if self._used != other._used:
|
|
739
|
+
return False
|
|
740
|
+
for e1, e2 in zip(lft.iter_entries(), rht.iter_entries()):
|
|
741
|
+
if e1.identity != e2.identity or e1.value != e2.value:
|
|
742
|
+
return False
|
|
743
|
+
return True
|
|
744
|
+
if self._used != len(other):
|
|
745
|
+
return False
|
|
746
|
+
for k, v in self.items():
|
|
747
|
+
nv = other.get(k, sentinel)
|
|
748
|
+
if v != nv:
|
|
749
|
+
return False
|
|
750
|
+
return True
|
|
751
|
+
|
|
752
|
+
def __contains__(self, key: object) -> bool:
|
|
753
|
+
if not isinstance(key, str):
|
|
754
|
+
return False
|
|
755
|
+
identity = self._identity(key)
|
|
756
|
+
hash_ = hash(identity)
|
|
757
|
+
for slot, idx, e in self._keys.iter_hash(hash_):
|
|
758
|
+
if e.identity == identity: # pragma: no branch
|
|
759
|
+
return True
|
|
760
|
+
return False
|
|
761
|
+
|
|
762
|
+
@reprlib.recursive_repr()
|
|
763
|
+
def __repr__(self) -> str:
|
|
764
|
+
body = ", ".join(f"'{e.key}': {e.value!r}" for e in self._keys.iter_entries())
|
|
765
|
+
return f"<{self.__class__.__name__}({body})>"
|
|
766
|
+
|
|
767
|
+
if sys.implementation.name != "pypy":
|
|
768
|
+
|
|
769
|
+
def __sizeof__(self) -> int:
|
|
770
|
+
return object.__sizeof__(self) + sys.getsizeof(self._keys)
|
|
771
|
+
|
|
772
|
+
def __reduce__(self) -> tuple[type[Self], tuple[list[tuple[str, _V]]]]:
|
|
773
|
+
return (self.__class__, (list(self.items()),))
|
|
774
|
+
|
|
775
|
+
def add(self, key: str, value: _V) -> None:
|
|
776
|
+
identity = self._identity(key)
|
|
777
|
+
hash_ = hash(identity)
|
|
778
|
+
self._add_with_hash(_Entry(hash_, identity, key, value))
|
|
779
|
+
self._incr_version()
|
|
780
|
+
|
|
781
|
+
def copy(self) -> Self:
|
|
782
|
+
"""Return a copy of itself."""
|
|
783
|
+
cls = self.__class__
|
|
784
|
+
return cls(self)
|
|
785
|
+
|
|
786
|
+
__copy__ = copy
|
|
787
|
+
|
|
788
|
+
def extend(self, arg: MDArg[_V] = None, /, **kwargs: _V) -> None:
|
|
789
|
+
"""Extend current MultiDict with more values.
|
|
790
|
+
|
|
791
|
+
This method must be used instead of update.
|
|
792
|
+
"""
|
|
793
|
+
it = self._parse_args(arg, kwargs)
|
|
794
|
+
newsize = self._used + cast(int, next(it))
|
|
795
|
+
self._resize(estimate_log2_keysize(newsize), False)
|
|
796
|
+
self._extend_items(cast(Iterator[_Entry[_V]], it))
|
|
797
|
+
|
|
798
|
+
def _parse_args(
|
|
799
|
+
self,
|
|
800
|
+
arg: MDArg[_V],
|
|
801
|
+
kwargs: Mapping[str, _V],
|
|
802
|
+
) -> Iterator[Union[int, _Entry[_V]]]:
|
|
803
|
+
identity_func = self._identity
|
|
804
|
+
if arg:
|
|
805
|
+
if isinstance(arg, MultiDictProxy):
|
|
806
|
+
arg = arg._md
|
|
807
|
+
if isinstance(arg, MultiDict):
|
|
808
|
+
yield len(arg) + len(kwargs)
|
|
809
|
+
if self._ci is not arg._ci:
|
|
810
|
+
for e in arg._keys.iter_entries():
|
|
811
|
+
identity = identity_func(e.key)
|
|
812
|
+
yield _Entry(hash(identity), identity, e.key, e.value)
|
|
813
|
+
else:
|
|
814
|
+
for e in arg._keys.iter_entries():
|
|
815
|
+
yield _Entry(e.hash, e.identity, e.key, e.value)
|
|
816
|
+
if kwargs:
|
|
817
|
+
for key, value in kwargs.items():
|
|
818
|
+
identity = identity_func(key)
|
|
819
|
+
yield _Entry(hash(identity), identity, key, value)
|
|
820
|
+
else:
|
|
821
|
+
if hasattr(arg, "keys"):
|
|
822
|
+
arg = cast(SupportsKeys[_V], arg)
|
|
823
|
+
arg = [(k, arg[k]) for k in arg.keys()]
|
|
824
|
+
if kwargs:
|
|
825
|
+
arg = list(arg)
|
|
826
|
+
arg.extend(list(kwargs.items()))
|
|
827
|
+
try:
|
|
828
|
+
yield len(arg) + len(kwargs) # type: ignore[arg-type]
|
|
829
|
+
except TypeError:
|
|
830
|
+
yield 0
|
|
831
|
+
for pos, item in enumerate(arg):
|
|
832
|
+
if not len(item) == 2:
|
|
833
|
+
raise ValueError(
|
|
834
|
+
f"multidict update sequence element #{pos}"
|
|
835
|
+
f"has length {len(item)}; 2 is required"
|
|
836
|
+
)
|
|
837
|
+
identity = identity_func(item[0])
|
|
838
|
+
yield _Entry(hash(identity), identity, item[0], item[1])
|
|
839
|
+
else:
|
|
840
|
+
yield len(kwargs)
|
|
841
|
+
for key, value in kwargs.items():
|
|
842
|
+
identity = identity_func(key)
|
|
843
|
+
yield _Entry(hash(identity), identity, key, value)
|
|
844
|
+
|
|
845
|
+
def _extend_items(self, items: Iterable[_Entry[_V]]) -> None:
|
|
846
|
+
for e in items:
|
|
847
|
+
self._add_with_hash(e)
|
|
848
|
+
self._incr_version()
|
|
849
|
+
|
|
850
|
+
def clear(self) -> None:
|
|
851
|
+
"""Remove all items from MultiDict."""
|
|
852
|
+
self._used = 0
|
|
853
|
+
self._keys = _HtKeys.new(_HtKeys.LOG_MINSIZE, [])
|
|
854
|
+
self._incr_version()
|
|
855
|
+
|
|
856
|
+
# Mapping interface #
|
|
857
|
+
|
|
858
|
+
def __setitem__(self, key: str, value: _V) -> None:
|
|
859
|
+
identity = self._identity(key)
|
|
860
|
+
hash_ = hash(identity)
|
|
861
|
+
found = False
|
|
862
|
+
|
|
863
|
+
for slot, idx, e in self._keys.iter_hash(hash_):
|
|
864
|
+
if e.identity == identity: # pragma: no branch
|
|
865
|
+
if not found:
|
|
866
|
+
e.key = key
|
|
867
|
+
e.value = value
|
|
868
|
+
e.hash = -1
|
|
869
|
+
found = True
|
|
870
|
+
self._incr_version()
|
|
871
|
+
elif e.hash != -1: # pragma: no branch
|
|
872
|
+
self._del_at(slot, idx)
|
|
873
|
+
|
|
874
|
+
if not found:
|
|
875
|
+
self._add_with_hash(_Entry(hash_, identity, key, value))
|
|
876
|
+
else:
|
|
877
|
+
self._keys.restore_hash(hash_)
|
|
878
|
+
|
|
879
|
+
def __delitem__(self, key: str) -> None:
|
|
880
|
+
found = False
|
|
881
|
+
identity = self._identity(key)
|
|
882
|
+
hash_ = hash(identity)
|
|
883
|
+
for slot, idx, e in self._keys.iter_hash(hash_):
|
|
884
|
+
if e.identity == identity: # pragma: no branch
|
|
885
|
+
self._del_at(slot, idx)
|
|
886
|
+
found = True
|
|
887
|
+
if not found:
|
|
888
|
+
raise KeyError(key)
|
|
889
|
+
else:
|
|
890
|
+
self._incr_version()
|
|
891
|
+
|
|
892
|
+
@overload
|
|
893
|
+
def setdefault(
|
|
894
|
+
self: "MultiDict[Union[_T, None]]", key: str, default: None = None
|
|
895
|
+
) -> Union[_T, None]: ...
|
|
896
|
+
@overload
|
|
897
|
+
def setdefault(self, key: str, default: _V) -> _V: ...
|
|
898
|
+
def setdefault(self, key: str, default: Union[_V, None] = None) -> Union[_V, None]: # type: ignore[misc]
|
|
899
|
+
"""Return value for key, set value to default if key is not present."""
|
|
900
|
+
identity = self._identity(key)
|
|
901
|
+
hash_ = hash(identity)
|
|
902
|
+
for slot, idx, e in self._keys.iter_hash(hash_):
|
|
903
|
+
if e.identity == identity: # pragma: no branch
|
|
904
|
+
return e.value
|
|
905
|
+
self.add(key, default) # type: ignore[arg-type]
|
|
906
|
+
return default
|
|
907
|
+
|
|
908
|
+
@overload
|
|
909
|
+
def popone(self, key: str) -> _V: ...
|
|
910
|
+
@overload
|
|
911
|
+
def popone(self, key: str, default: _T) -> Union[_V, _T]: ...
|
|
912
|
+
def popone(
|
|
913
|
+
self, key: str, default: Union[_T, _SENTINEL] = sentinel
|
|
914
|
+
) -> Union[_V, _T]:
|
|
915
|
+
"""Remove specified key and return the corresponding value.
|
|
916
|
+
|
|
917
|
+
If key is not found, d is returned if given, otherwise
|
|
918
|
+
KeyError is raised.
|
|
919
|
+
|
|
920
|
+
"""
|
|
921
|
+
identity = self._identity(key)
|
|
922
|
+
hash_ = hash(identity)
|
|
923
|
+
for slot, idx, e in self._keys.iter_hash(hash_):
|
|
924
|
+
if e.identity == identity: # pragma: no branch
|
|
925
|
+
value = e.value
|
|
926
|
+
self._del_at(slot, idx)
|
|
927
|
+
self._incr_version()
|
|
928
|
+
return value
|
|
929
|
+
if default is sentinel:
|
|
930
|
+
raise KeyError(key)
|
|
931
|
+
else:
|
|
932
|
+
return default
|
|
933
|
+
|
|
934
|
+
# Type checking will inherit signature for pop() if we don't confuse it here.
|
|
935
|
+
if not TYPE_CHECKING:
|
|
936
|
+
pop = popone
|
|
937
|
+
|
|
938
|
+
@overload
|
|
939
|
+
def popall(self, key: str) -> list[_V]: ...
|
|
940
|
+
@overload
|
|
941
|
+
def popall(self, key: str, default: _T) -> Union[list[_V], _T]: ...
|
|
942
|
+
def popall(
|
|
943
|
+
self, key: str, default: Union[_T, _SENTINEL] = sentinel
|
|
944
|
+
) -> Union[list[_V], _T]:
|
|
945
|
+
"""Remove all occurrences of key and return the list of corresponding
|
|
946
|
+
values.
|
|
947
|
+
|
|
948
|
+
If key is not found, default is returned if given, otherwise
|
|
949
|
+
KeyError is raised.
|
|
950
|
+
|
|
951
|
+
"""
|
|
952
|
+
found = False
|
|
953
|
+
identity = self._identity(key)
|
|
954
|
+
hash_ = hash(identity)
|
|
955
|
+
ret = []
|
|
956
|
+
for slot, idx, e in self._keys.iter_hash(hash_):
|
|
957
|
+
if e.identity == identity: # pragma: no branch
|
|
958
|
+
found = True
|
|
959
|
+
ret.append(e.value)
|
|
960
|
+
self._del_at(slot, idx)
|
|
961
|
+
self._incr_version()
|
|
962
|
+
|
|
963
|
+
if not found:
|
|
964
|
+
if default is sentinel:
|
|
965
|
+
raise KeyError(key)
|
|
966
|
+
else:
|
|
967
|
+
return default
|
|
968
|
+
else:
|
|
969
|
+
return ret
|
|
970
|
+
|
|
971
|
+
def popitem(self) -> tuple[str, _V]:
|
|
972
|
+
"""Remove and return an arbitrary (key, value) pair."""
|
|
973
|
+
if self._used <= 0:
|
|
974
|
+
raise KeyError("empty multidict")
|
|
975
|
+
|
|
976
|
+
pos = len(self._keys.entries) - 1
|
|
977
|
+
entry = self._keys.entries.pop()
|
|
978
|
+
|
|
979
|
+
while entry is None:
|
|
980
|
+
pos -= 1
|
|
981
|
+
entry = self._keys.entries.pop()
|
|
982
|
+
|
|
983
|
+
ret = self._key(entry.key), entry.value
|
|
984
|
+
self._keys.del_idx(entry.hash, pos)
|
|
985
|
+
self._used -= 1
|
|
986
|
+
self._incr_version()
|
|
987
|
+
return ret
|
|
988
|
+
|
|
989
|
+
def update(self, arg: MDArg[_V] = None, /, **kwargs: _V) -> None:
|
|
990
|
+
"""Update the dictionary, overwriting existing keys."""
|
|
991
|
+
it = self._parse_args(arg, kwargs)
|
|
992
|
+
newsize = self._used + cast(int, next(it))
|
|
993
|
+
log2_size = estimate_log2_keysize(newsize)
|
|
994
|
+
if log2_size > 17: # pragma: no cover
|
|
995
|
+
# Don't overallocate really huge keys space in update,
|
|
996
|
+
# duplicate keys could reduce the resulting anount of entries
|
|
997
|
+
log2_size = 17
|
|
998
|
+
if log2_size > self._keys.log2_size:
|
|
999
|
+
self._resize(log2_size, False)
|
|
1000
|
+
try:
|
|
1001
|
+
self._update_items(cast(Iterator[_Entry[_V]], it))
|
|
1002
|
+
finally:
|
|
1003
|
+
self._post_update()
|
|
1004
|
+
|
|
1005
|
+
def _update_items(self, items: Iterator[_Entry[_V]]) -> None:
|
|
1006
|
+
for entry in items:
|
|
1007
|
+
found = False
|
|
1008
|
+
hash_ = entry.hash
|
|
1009
|
+
identity = entry.identity
|
|
1010
|
+
for slot, idx, e in self._keys.iter_hash(hash_):
|
|
1011
|
+
if e.identity == identity: # pragma: no branch
|
|
1012
|
+
if not found:
|
|
1013
|
+
found = True
|
|
1014
|
+
e.key = entry.key
|
|
1015
|
+
e.value = entry.value
|
|
1016
|
+
e.hash = -1
|
|
1017
|
+
else:
|
|
1018
|
+
self._del_at_for_upd(e)
|
|
1019
|
+
if not found:
|
|
1020
|
+
self._add_with_hash_for_upd(entry)
|
|
1021
|
+
|
|
1022
|
+
def _post_update(self) -> None:
|
|
1023
|
+
keys = self._keys
|
|
1024
|
+
indices = keys.indices
|
|
1025
|
+
entries = keys.entries
|
|
1026
|
+
for slot in range(keys.nslots):
|
|
1027
|
+
idx = indices[slot]
|
|
1028
|
+
if idx >= 0:
|
|
1029
|
+
e2 = entries[idx]
|
|
1030
|
+
assert e2 is not None
|
|
1031
|
+
if e2.key is None:
|
|
1032
|
+
entries[idx] = None
|
|
1033
|
+
indices[slot] = -2
|
|
1034
|
+
self._used -= 1
|
|
1035
|
+
if e2.hash == -1:
|
|
1036
|
+
e2.hash = hash(e2.identity)
|
|
1037
|
+
|
|
1038
|
+
self._incr_version()
|
|
1039
|
+
|
|
1040
|
+
def merge(self, arg: MDArg[_V] = None, /, **kwargs: _V) -> None:
|
|
1041
|
+
"""Merge into the dictionary, adding non-existing keys."""
|
|
1042
|
+
it = self._parse_args(arg, kwargs)
|
|
1043
|
+
newsize = self._used + cast(int, next(it))
|
|
1044
|
+
log2_size = estimate_log2_keysize(newsize)
|
|
1045
|
+
if log2_size > 17: # pragma: no cover
|
|
1046
|
+
# Don't overallocate really huge keys space in update,
|
|
1047
|
+
# duplicate keys could reduce the resulting anount of entries
|
|
1048
|
+
log2_size = 17
|
|
1049
|
+
if log2_size > self._keys.log2_size:
|
|
1050
|
+
self._resize(log2_size, False)
|
|
1051
|
+
try:
|
|
1052
|
+
self._merge_items(cast(Iterator[_Entry[_V]], it))
|
|
1053
|
+
finally:
|
|
1054
|
+
self._post_update()
|
|
1055
|
+
|
|
1056
|
+
def _merge_items(self, items: Iterator[_Entry[_V]]) -> None:
|
|
1057
|
+
for entry in items:
|
|
1058
|
+
hash_ = entry.hash
|
|
1059
|
+
identity = entry.identity
|
|
1060
|
+
for slot, idx, e in self._keys.iter_hash(hash_):
|
|
1061
|
+
if e.identity == identity: # pragma: no branch
|
|
1062
|
+
break
|
|
1063
|
+
else:
|
|
1064
|
+
self._add_with_hash_for_upd(entry)
|
|
1065
|
+
|
|
1066
|
+
def _incr_version(self) -> None:
|
|
1067
|
+
v = _version
|
|
1068
|
+
v[0] += 1
|
|
1069
|
+
self._version = v[0]
|
|
1070
|
+
|
|
1071
|
+
def _resize(self, log2_newsize: int, update: bool) -> None:
|
|
1072
|
+
oldkeys = self._keys
|
|
1073
|
+
newentries = self._used
|
|
1074
|
+
|
|
1075
|
+
if len(oldkeys.entries) == newentries:
|
|
1076
|
+
entries = oldkeys.entries
|
|
1077
|
+
else:
|
|
1078
|
+
entries = [e for e in oldkeys.entries if e is not None]
|
|
1079
|
+
newkeys: _HtKeys[_V] = _HtKeys.new(log2_newsize, entries)
|
|
1080
|
+
newkeys.usable -= newentries
|
|
1081
|
+
newkeys.build_indices(update)
|
|
1082
|
+
self._keys = newkeys
|
|
1083
|
+
|
|
1084
|
+
def _add_with_hash(self, entry: _Entry[_V]) -> None:
|
|
1085
|
+
if self._keys.usable <= 0:
|
|
1086
|
+
self._resize((self._used * 3 | _HtKeys.MINSIZE - 1).bit_length(), False)
|
|
1087
|
+
keys = self._keys
|
|
1088
|
+
slot = keys.find_empty_slot(entry.hash)
|
|
1089
|
+
keys.indices[slot] = len(keys.entries)
|
|
1090
|
+
keys.entries.append(entry)
|
|
1091
|
+
self._incr_version()
|
|
1092
|
+
self._used += 1
|
|
1093
|
+
keys.usable -= 1
|
|
1094
|
+
|
|
1095
|
+
def _add_with_hash_for_upd(self, entry: _Entry[_V]) -> None:
|
|
1096
|
+
if self._keys.usable <= 0:
|
|
1097
|
+
self._resize((self._used * 3 | _HtKeys.MINSIZE - 1).bit_length(), True)
|
|
1098
|
+
keys = self._keys
|
|
1099
|
+
slot = keys.find_empty_slot(entry.hash)
|
|
1100
|
+
keys.indices[slot] = len(keys.entries)
|
|
1101
|
+
entry.hash = -1
|
|
1102
|
+
keys.entries.append(entry)
|
|
1103
|
+
self._incr_version()
|
|
1104
|
+
self._used += 1
|
|
1105
|
+
keys.usable -= 1
|
|
1106
|
+
|
|
1107
|
+
def _del_at(self, slot: int, idx: int) -> None:
|
|
1108
|
+
self._keys.entries[idx] = None
|
|
1109
|
+
self._keys.indices[slot] = -2
|
|
1110
|
+
self._used -= 1
|
|
1111
|
+
|
|
1112
|
+
def _del_at_for_upd(self, entry: _Entry[_V]) -> None:
|
|
1113
|
+
entry.key = None # type: ignore[assignment]
|
|
1114
|
+
entry.value = None # type: ignore[assignment]
|
|
1115
|
+
|
|
1116
|
+
|
|
1117
|
+
class CIMultiDict(_CIMixin, MultiDict[_V]):
|
|
1118
|
+
"""Dictionary with the support for duplicate case-insensitive keys."""
|
|
1119
|
+
|
|
1120
|
+
|
|
1121
|
+
class MultiDictProxy(_CSMixin, MultiMapping[_V]):
|
|
1122
|
+
"""Read-only proxy for MultiDict instance."""
|
|
1123
|
+
|
|
1124
|
+
__slots__ = ("_md",)
|
|
1125
|
+
|
|
1126
|
+
_md: MultiDict[_V]
|
|
1127
|
+
|
|
1128
|
+
def __init__(self, arg: Union[MultiDict[_V], "MultiDictProxy[_V]"]):
|
|
1129
|
+
if not isinstance(arg, (MultiDict, MultiDictProxy)):
|
|
1130
|
+
raise TypeError(
|
|
1131
|
+
f"ctor requires MultiDict or MultiDictProxy instance, not {type(arg)}"
|
|
1132
|
+
)
|
|
1133
|
+
if isinstance(arg, MultiDictProxy):
|
|
1134
|
+
self._md = arg._md
|
|
1135
|
+
else:
|
|
1136
|
+
self._md = arg
|
|
1137
|
+
|
|
1138
|
+
def __reduce__(self) -> NoReturn:
|
|
1139
|
+
raise TypeError(f"can't pickle {self.__class__.__name__} objects")
|
|
1140
|
+
|
|
1141
|
+
@overload
|
|
1142
|
+
def getall(self, key: str) -> list[_V]: ...
|
|
1143
|
+
@overload
|
|
1144
|
+
def getall(self, key: str, default: _T) -> Union[list[_V], _T]: ...
|
|
1145
|
+
def getall(
|
|
1146
|
+
self, key: str, default: Union[_T, _SENTINEL] = sentinel
|
|
1147
|
+
) -> Union[list[_V], _T]:
|
|
1148
|
+
"""Return a list of all values matching the key."""
|
|
1149
|
+
if default is not sentinel:
|
|
1150
|
+
return self._md.getall(key, default)
|
|
1151
|
+
else:
|
|
1152
|
+
return self._md.getall(key)
|
|
1153
|
+
|
|
1154
|
+
@overload
|
|
1155
|
+
def getone(self, key: str) -> _V: ...
|
|
1156
|
+
@overload
|
|
1157
|
+
def getone(self, key: str, default: _T) -> Union[_V, _T]: ...
|
|
1158
|
+
def getone(
|
|
1159
|
+
self, key: str, default: Union[_T, _SENTINEL] = sentinel
|
|
1160
|
+
) -> Union[_V, _T]:
|
|
1161
|
+
"""Get first value matching the key.
|
|
1162
|
+
|
|
1163
|
+
Raises KeyError if the key is not found and no default is provided.
|
|
1164
|
+
"""
|
|
1165
|
+
if default is not sentinel:
|
|
1166
|
+
return self._md.getone(key, default)
|
|
1167
|
+
else:
|
|
1168
|
+
return self._md.getone(key)
|
|
1169
|
+
|
|
1170
|
+
# Mapping interface #
|
|
1171
|
+
|
|
1172
|
+
def __getitem__(self, key: str) -> _V:
|
|
1173
|
+
return self.getone(key)
|
|
1174
|
+
|
|
1175
|
+
@overload
|
|
1176
|
+
def get(self, key: str, /) -> Union[_V, None]: ...
|
|
1177
|
+
@overload
|
|
1178
|
+
def get(self, key: str, /, default: _T) -> Union[_V, _T]: ...
|
|
1179
|
+
def get(self, key: str, default: Union[_T, None] = None) -> Union[_V, _T, None]:
|
|
1180
|
+
"""Get first value matching the key.
|
|
1181
|
+
|
|
1182
|
+
If the key is not found, returns the default (or None if no default is provided)
|
|
1183
|
+
"""
|
|
1184
|
+
return self._md.getone(key, default)
|
|
1185
|
+
|
|
1186
|
+
def __iter__(self) -> Iterator[str]:
|
|
1187
|
+
return iter(self._md.keys())
|
|
1188
|
+
|
|
1189
|
+
def __len__(self) -> int:
|
|
1190
|
+
return len(self._md)
|
|
1191
|
+
|
|
1192
|
+
def keys(self) -> KeysView[str]:
|
|
1193
|
+
"""Return a new view of the dictionary's keys."""
|
|
1194
|
+
return self._md.keys()
|
|
1195
|
+
|
|
1196
|
+
def items(self) -> ItemsView[str, _V]:
|
|
1197
|
+
"""Return a new view of the dictionary's items *(key, value) pairs)."""
|
|
1198
|
+
return self._md.items()
|
|
1199
|
+
|
|
1200
|
+
def values(self) -> _ValuesView[_V]:
|
|
1201
|
+
"""Return a new view of the dictionary's values."""
|
|
1202
|
+
return self._md.values()
|
|
1203
|
+
|
|
1204
|
+
def __eq__(self, other: object) -> bool:
|
|
1205
|
+
return self._md == other
|
|
1206
|
+
|
|
1207
|
+
def __contains__(self, key: object) -> bool:
|
|
1208
|
+
return key in self._md
|
|
1209
|
+
|
|
1210
|
+
@reprlib.recursive_repr()
|
|
1211
|
+
def __repr__(self) -> str:
|
|
1212
|
+
body = ", ".join(f"'{k}': {v!r}" for k, v in self.items())
|
|
1213
|
+
return f"<{self.__class__.__name__}({body})>"
|
|
1214
|
+
|
|
1215
|
+
def copy(self) -> MultiDict[_V]:
|
|
1216
|
+
"""Return a copy of itself."""
|
|
1217
|
+
return MultiDict(self._md)
|
|
1218
|
+
|
|
1219
|
+
|
|
1220
|
+
class CIMultiDictProxy(_CIMixin, MultiDictProxy[_V]):
|
|
1221
|
+
"""Read-only proxy for CIMultiDict instance."""
|
|
1222
|
+
|
|
1223
|
+
def __init__(self, arg: Union[MultiDict[_V], MultiDictProxy[_V]]):
|
|
1224
|
+
if not isinstance(arg, (CIMultiDict, CIMultiDictProxy)):
|
|
1225
|
+
raise TypeError(
|
|
1226
|
+
"ctor requires CIMultiDict or CIMultiDictProxy instance"
|
|
1227
|
+
f", not {type(arg)}"
|
|
1228
|
+
)
|
|
1229
|
+
|
|
1230
|
+
super().__init__(arg)
|
|
1231
|
+
|
|
1232
|
+
def copy(self) -> CIMultiDict[_V]:
|
|
1233
|
+
"""Return a copy of itself."""
|
|
1234
|
+
return CIMultiDict(self._md)
|
|
1235
|
+
|
|
1236
|
+
|
|
1237
|
+
def getversion(md: Union[MultiDict[object], MultiDictProxy[object]]) -> int:
|
|
1238
|
+
if isinstance(md, MultiDictProxy):
|
|
1239
|
+
md = md._md
|
|
1240
|
+
elif not isinstance(md, MultiDict):
|
|
1241
|
+
raise TypeError("Parameter should be multidict or proxy")
|
|
1242
|
+
return md._version
|