aio-libs / multidict
1 17
import sys
2 17
from array import array
3 17
from collections import abc
4

5 17
from ._abc import MultiMapping, MutableMultiMapping
6

7 17
_marker = object()
8

9

10 17
class istr(str):
11

12
    """Case insensitive str."""
13

14 17
    __is_istr__ = True
15

16

17 17
upstr = istr  # for relaxing backward compatibility problems
18

19

20 17
def getversion(md):
21 17
    if not isinstance(md, _Base):
22 17
        raise TypeError("Parameter should be multidict or proxy")
23 17
    return md._impl._version
24

25

26 17
_version = array("Q", [0])
27

28

29 17
class _Impl:
30 17
    __slots__ = ("_items", "_version")
31

32 17
    def __init__(self):
33 17
        self._items = []
34 17
        self.incr_version()
35

36 17
    def incr_version(self):
37
        global _version
38 17
        v = _version
39 17
        v[0] += 1
40 17
        self._version = v[0]
41

42 17
    if sys.implementation.name != "pypy":
43

44 16
        def __sizeof__(self):
45 16
            return object.__sizeof__(self) + sys.getsizeof(self._items)
46

47

48 17
class _Base:
49 17
    def _title(self, key):
50 17
        return key
51

52 17
    def getall(self, key, default=_marker):
53
        """Return a list of all values matching the key."""
54 17
        identity = self._title(key)
55 17
        res = [v for i, k, v in self._impl._items if i == identity]
56 17
        if res:
57 17
            return res
58 17
        if not res and default is not _marker:
59 17
            return default
60 17
        raise KeyError("Key not found: %r" % key)
61

62 17
    def getone(self, key, default=_marker):
63
        """Get first value matching the key."""
64 17
        identity = self._title(key)
65 17
        for i, k, v in self._impl._items:
66 17
            if i == identity:
67 17
                return v
68 17
        if default is not _marker:
69 17
            return default
70 17
        raise KeyError("Key not found: %r" % key)
71

72
    # Mapping interface #
73

74 17
    def __getitem__(self, key):
75 17
        return self.getone(key)
76

77 17
    def get(self, key, default=None):
78
        """Get first value matching the key.
79

80
        The method is alias for .getone().
81
        """
82 17
        return self.getone(key, default)
83

84 17
    def __iter__(self):
85 17
        return iter(self.keys())
86

87 17
    def __len__(self):
88 17
        return len(self._impl._items)
89

90 17
    def keys(self):
91
        """Return a new view of the dictionary's keys."""
92 17
        return _KeysView(self._impl)
93

94 17
    def items(self):
95
        """Return a new view of the dictionary's items *(key, value) pairs)."""
96 17
        return _ItemsView(self._impl)
97

98 17
    def values(self):
99
        """Return a new view of the dictionary's values."""
100 17
        return _ValuesView(self._impl)
101

102 17
    def __eq__(self, other):
103 17
        if not isinstance(other, abc.Mapping):
104 17
            return NotImplemented
105 17
        if isinstance(other, _Base):
106 17
            lft = self._impl._items
107 17
            rht = other._impl._items
108 17
            if len(lft) != len(rht):
109 17
                return False
110 17
            for (i1, k2, v1), (i2, k2, v2) in zip(lft, rht):
111 17
                if i1 != i2 or v1 != v2:
112 17
                    return False
113 17
            return True
114 17
        if len(self._impl._items) != len(other):
115 17
            return False
116 17
        for k, v in self.items():
117 17
            nv = other.get(k, _marker)
118 17
            if v != nv:
119 17
                return False
120 17
        return True
121

122 17
    def __contains__(self, key):
123 17
        identity = self._title(key)
124 17
        for i, k, v in self._impl._items:
125 17
            if i == identity:
126 17
                return True
127 17
        return False
128

129 17
    def __repr__(self):
130 17
        body = ", ".join("'{}': {!r}".format(k, v) for k, v in self.items())
131 17
        return "<{}({})>".format(self.__class__.__name__, body)
132

133

134 17
class MultiDictProxy(_Base, MultiMapping):
135
    """Read-only proxy for MultiDict instance."""
136

137 17
    def __init__(self, arg):
138 17
        if not isinstance(arg, (MultiDict, MultiDictProxy)):
139 17
            raise TypeError(
140
                "ctor requires MultiDict or MultiDictProxy instance"
141
                ", not {}".format(type(arg))
142
            )
143

144 17
        self._impl = arg._impl
145

146 17
    def __reduce__(self):
147 17
        raise TypeError("can't pickle {} objects".format(self.__class__.__name__))
148

149 17
    def copy(self):
150
        """Return a copy of itself."""
151 17
        return MultiDict(self.items())
152

153

154 17
class CIMultiDictProxy(MultiDictProxy):
155
    """Read-only proxy for CIMultiDict instance."""
156

157 17
    def __init__(self, arg):
158 17
        if not isinstance(arg, (CIMultiDict, CIMultiDictProxy)):
159 17
            raise TypeError(
160
                "ctor requires CIMultiDict or CIMultiDictProxy instance"
161
                ", not {}".format(type(arg))
162
            )
163

164 17
        self._impl = arg._impl
165

166 17
    def _title(self, key):
167 17
        return key.title()
168

169 17
    def copy(self):
170
        """Return a copy of itself."""
171 17
        return CIMultiDict(self.items())
172

173

174 17
class MultiDict(_Base, MutableMultiMapping):
175
    """Dictionary with the support for duplicate keys."""
176

177 17
    def __init__(self, *args, **kwargs):
178 17
        self._impl = _Impl()
179

180 17
        self._extend(args, kwargs, self.__class__.__name__, self._extend_items)
181

182 17
    if sys.implementation.name != "pypy":
183

184 16
        def __sizeof__(self):
185 16
            return object.__sizeof__(self) + sys.getsizeof(self._impl)
186

187 17
    def __reduce__(self):
188 17
        return (self.__class__, (list(self.items()),))
189

190 17
    def _title(self, key):
191 17
        return key
192

193 17
    def _key(self, key):
194 17
        if isinstance(key, str):
195 17
            return key
196
        else:
197 17
            raise TypeError(
198
                "MultiDict keys should be either str " "or subclasses of str"
199
            )
200

201 17
    def add(self, key, value):
202 17
        identity = self._title(key)
203 17
        self._impl._items.append((identity, self._key(key), value))
204 17
        self._impl.incr_version()
205

206 17
    def copy(self):
207
        """Return a copy of itself."""
208 17
        cls = self.__class__
209 17
        return cls(self.items())
210

211 17
    __copy__ = copy
212

213 17
    def extend(self, *args, **kwargs):
214
        """Extend current MultiDict with more values.
215

216
        This method must be used instead of update.
217
        """
218 17
        self._extend(args, kwargs, "extend", self._extend_items)
219

220 17
    def _extend(self, args, kwargs, name, method):
221 17
        if len(args) > 1:
222 17
            raise TypeError(
223
                "{} takes at most 1 positional argument"
224
                " ({} given)".format(name, len(args))
225
            )
226 17
        if args:
227 17
            arg = args[0]
228 17
            if isinstance(args[0], (MultiDict, MultiDictProxy)) and not kwargs:
229 17
                items = arg._impl._items
230
            else:
231 17
                if hasattr(arg, "items"):
232 17
                    arg = arg.items()
233 17
                if kwargs:
234 17
                    arg = list(arg)
235 17
                    arg.extend(list(kwargs.items()))
236 17
                items = []
237 17
                for item in arg:
238 17
                    if not len(item) == 2:
239 17
                        raise TypeError(
240
                            "{} takes either dict or list of (key, value) "
241
                            "tuples".format(name)
242
                        )
243 17
                    items.append((self._title(item[0]), self._key(item[0]), item[1]))
244

245 17
            method(items)
246
        else:
247 17
            method(
248
                [
249
                    (self._title(key), self._key(key), value)
250
                    for key, value in kwargs.items()
251
                ]
252
            )
253

254 17
    def _extend_items(self, items):
255 17
        for identity, key, value in items:
256 17
            self.add(key, value)
257

258 17
    def clear(self):
259
        """Remove all items from MultiDict."""
260 17
        self._impl._items.clear()
261 17
        self._impl.incr_version()
262

263
    # Mapping interface #
264

265 17
    def __setitem__(self, key, value):
266 17
        self._replace(key, value)
267

268 17
    def __delitem__(self, key):
269 17
        identity = self._title(key)
270 17
        items = self._impl._items
271 17
        found = False
272 17
        for i in range(len(items) - 1, -1, -1):
273 17
            if items[i][0] == identity:
274 17
                del items[i]
275 17
                found = True
276 17
        if not found:
277 17
            raise KeyError(key)
278
        else:
279 17
            self._impl.incr_version()
280

281 17
    def setdefault(self, key, default=None):
282
        """Return value for key, set value to default if key is not present."""
283 17
        identity = self._title(key)
284 17
        for i, k, v in self._impl._items:
285 17
            if i == identity:
286 17
                return v
287 17
        self.add(key, default)
288 17
        return default
289

290 17
    def popone(self, key, default=_marker):
291
        """Remove specified key and return the corresponding value.
292

293
        If key is not found, d is returned if given, otherwise
294
        KeyError is raised.
295

296
        """
297 17
        identity = self._title(key)
298 17
        for i in range(len(self._impl._items)):
299 17
            if self._impl._items[i][0] == identity:
300 17
                value = self._impl._items[i][2]
301 17
                del self._impl._items[i]
302 17
                self._impl.incr_version()
303 17
                return value
304 17
        if default is _marker:
305 17
            raise KeyError(key)
306
        else:
307 17
            return default
308

309 17
    pop = popone  # type: ignore
310

311 17
    def popall(self, key, default=_marker):
312
        """Remove all occurrences of key and return the list of corresponding
313
        values.
314

315
        If key is not found, default is returned if given, otherwise
316
        KeyError is raised.
317

318
        """
319 17
        found = False
320 17
        identity = self._title(key)
321 17
        ret = []
322 17
        for i in range(len(self._impl._items) - 1, -1, -1):
323 17
            item = self._impl._items[i]
324 17
            if item[0] == identity:
325 17
                ret.append(item[2])
326 17
                del self._impl._items[i]
327 17
                self._impl.incr_version()
328 17
                found = True
329 17
        if not found:
330 17
            if default is _marker:
331 17
                raise KeyError(key)
332
            else:
333 17
                return default
334
        else:
335 17
            ret.reverse()
336 17
            return ret
337

338 17
    def popitem(self):
339
        """Remove and return an arbitrary (key, value) pair."""
340 17
        if self._impl._items:
341 17
            i = self._impl._items.pop(0)
342 17
            self._impl.incr_version()
343 17
            return i[1], i[2]
344
        else:
345 17
            raise KeyError("empty multidict")
346

347 17
    def update(self, *args, **kwargs):
348
        """Update the dictionary from *other*, overwriting existing keys."""
349 17
        self._extend(args, kwargs, "update", self._update_items)
350

351 17
    def _update_items(self, items):
352 17
        if not items:
353 0
            return
354 17
        used_keys = {}
355 17
        for identity, key, value in items:
356 17
            start = used_keys.get(identity, 0)
357 17
            for i in range(start, len(self._impl._items)):
358 17
                item = self._impl._items[i]
359 17
                if item[0] == identity:
360 17
                    used_keys[identity] = i + 1
361 17
                    self._impl._items[i] = (identity, key, value)
362 17
                    break
363
            else:
364 17
                self._impl._items.append((identity, key, value))
365 17
                used_keys[identity] = len(self._impl._items)
366

367
        # drop tails
368 17
        i = 0
369 17
        while i < len(self._impl._items):
370 17
            item = self._impl._items[i]
371 17
            identity = item[0]
372 17
            pos = used_keys.get(identity)
373 17
            if pos is None:
374 17
                i += 1
375 17
                continue
376 17
            if i >= pos:
377 17
                del self._impl._items[i]
378
            else:
379 17
                i += 1
380

381 17
        self._impl.incr_version()
382

383 17
    def _replace(self, key, value):
384 17
        key = self._key(key)
385 17
        identity = self._title(key)
386 17
        items = self._impl._items
387

388 17
        for i in range(len(items)):
389 17
            item = items[i]
390 17
            if item[0] == identity:
391 17
                items[i] = (identity, key, value)
392
                # i points to last found item
393 17
                rgt = i
394 17
                self._impl.incr_version()
395 17
                break
396
        else:
397 17
            self._impl._items.append((identity, key, value))
398 17
            self._impl.incr_version()
399 17
            return
400

401
        # remove all tail items
402 17
        i = rgt + 1
403 17
        while i < len(items):
404 17
            item = items[i]
405 17
            if item[0] == identity:
406 17
                del items[i]
407
            else:
408 17
                i += 1
409

410

411 17
class CIMultiDict(MultiDict):
412
    """Dictionary with the support for duplicate case-insensitive keys."""
413

414 17
    def _title(self, key):
415 17
        return key.title()
416

417

418 17
class _Iter:
419 17
    __slots__ = ("_size", "_iter")
420

421 17
    def __init__(self, size, iterator):
422 17
        self._size = size
423 17
        self._iter = iterator
424

425 17
    def __iter__(self):
426 0
        return self
427

428 17
    def __next__(self):
429 17
        return next(self._iter)
430

431 17
    def __length_hint__(self):
432 17
        return self._size
433

434

435 17
class _ViewBase:
436 17
    def __init__(self, impl):
437 17
        self._impl = impl
438 17
        self._version = impl._version
439

440 17
    def __len__(self):
441 17
        return len(self._impl._items)
442

443

444 17
class _ItemsView(_ViewBase, abc.ItemsView):
445 17
    def __contains__(self, item):
446 17
        assert isinstance(item, tuple) or isinstance(item, list)
447 17
        assert len(item) == 2
448 17
        for i, k, v in self._impl._items:
449 17
            if item[0] == k and item[1] == v:
450 17
                return True
451 17
        return False
452

453 17
    def __iter__(self):
454 17
        return _Iter(len(self), self._iter())
455

456 17
    def _iter(self):
457 17
        for i, k, v in self._impl._items:
458 17
            if self._version != self._impl._version:
459 17
                raise RuntimeError("Dictionary changed during iteration")
460 17
            yield k, v
461

462 17
    def __repr__(self):
463 17
        lst = []
464 17
        for item in self._impl._items:
465 17
            lst.append("{!r}: {!r}".format(item[1], item[2]))
466 17
        body = ", ".join(lst)
467 17
        return "{}({})".format(self.__class__.__name__, body)
468

469

470 17
class _ValuesView(_ViewBase, abc.ValuesView):
471 17
    def __contains__(self, value):
472 17
        for item in self._impl._items:
473 17
            if item[2] == value:
474 17
                return True
475 17
        return False
476

477 17
    def __iter__(self):
478 17
        return _Iter(len(self), self._iter())
479

480 17
    def _iter(self):
481 17
        for item in self._impl._items:
482 17
            if self._version != self._impl._version:
483 17
                raise RuntimeError("Dictionary changed during iteration")
484 17
            yield item[2]
485

486 17
    def __repr__(self):
487 17
        lst = []
488 17
        for item in self._impl._items:
489 17
            lst.append("{!r}".format(item[2]))
490 17
        body = ", ".join(lst)
491 17
        return "{}({})".format(self.__class__.__name__, body)
492

493

494 17
class _KeysView(_ViewBase, abc.KeysView):
495 17
    def __contains__(self, key):
496 17
        for item in self._impl._items:
497 17
            if item[1] == key:
498 17
                return True
499 17
        return False
500

501 17
    def __iter__(self):
502 17
        return _Iter(len(self), self._iter())
503

504 17
    def _iter(self):
505 17
        for item in self._impl._items:
506 17
            if self._version != self._impl._version:
507 17
                raise RuntimeError("Dictionary changed during iteration")
508 17
            yield item[1]
509

510 17
    def __repr__(self):
511 17
        lst = []
512 17
        for item in self._impl._items:
513 17
            lst.append("{!r}".format(item[1]))
514 17
        body = ", ".join(lst)
515 17
        return "{}({})".format(self.__class__.__name__, body)

Read our documentation on viewing source code .

Loading