1 34
import sys
2 34
from array import array
3 34
from collections import abc
4

5 34
from ._abc import MultiMapping, MutableMultiMapping
6

7 34
_marker = object()
8

9

10 34
class istr(str):
11

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

14 34
    __is_istr__ = True
15

16

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

19

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

25

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

28

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

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

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

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

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

47

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

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

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

72
    # Mapping interface #
73

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

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

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

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

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

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

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

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

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

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

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

133

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

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

144 34
        self._impl = arg._impl
145

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

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

153

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

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

164 34
        self._impl = arg._impl
165

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

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

173

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

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

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

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

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

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

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

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

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

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

211 34
    __copy__ = copy
212

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

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

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

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

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

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

263
    # Mapping interface #
264

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

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

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

290 34
    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 34
        identity = self._title(key)
298 34
        for i in range(len(self._impl._items)):
299 34
            if self._impl._items[i][0] == identity:
300 34
                value = self._impl._items[i][2]
301 34
                del self._impl._items[i]
302 34
                self._impl.incr_version()
303 34
                return value
304 34
        if default is _marker:
305 34
            raise KeyError(key)
306
        else:
307 34
            return default
308

309 34
    pop = popone  # type: ignore
310

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

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

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

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

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

381 34
        self._impl.incr_version()
382

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

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

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

410

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

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

417

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

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

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

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

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

434

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

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

443

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

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

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

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

469

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

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

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

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

493

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

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

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

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

Read our documentation on viewing source code .

Loading