Skip to content

Commit f239ace

Browse files
committed
Merge pull request #227 from KeepSafe/multidict_refactoring
Multidict refactoring
2 parents 4279a79 + 3c00edb commit f239ace

21 files changed

+594
-317
lines changed

CHANGES.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ CHANGES
2222

2323
- multidict.getall() returns `list`, not `tuple`.
2424

25+
- Backward imcompatible change: now there are two mutable multidicts
26+
(`MultiDict`, `CIMultiDict`) and two immutable multidict proxies
27+
(`MultiDictProxy` and `CIMultiDictProxy`). Previous edition of
28+
multidicts was not a part of public API though.
29+
2530

2631
0.13.1 (12-31-2014)
2732
--------------------

aiohttp/_multidict.pyx

Lines changed: 163 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -26,49 +26,14 @@ class upstr(str):
2626
return self
2727

2828

29-
cdef class MultiDict:
30-
"""Read-only ordered dictionary that can have multiple values for each key.
31-
32-
This type of MultiDict must be used for request headers and query args.
33-
"""
29+
cdef class _Base:
3430

3531
cdef list _items
3632
cdef object _upstr
3733

38-
def __init__(self, *args, **kwargs):
34+
def __cinit__(self):
3935
self._upstr = upstr
40-
self._items = []
41-
42-
self._extend(args, kwargs, self.__class__.__name__)
43-
44-
cdef _extend(self, tuple args, dict kwargs, name):
45-
cdef tuple item
46-
47-
if len(args) > 1:
48-
raise TypeError("{} takes at most 1 positional argument"
49-
" ({} given)".format(name, len(args)))
50-
51-
if args:
52-
if hasattr(args[0], 'items'):
53-
for item in args[0].items():
54-
self._add(item)
55-
else:
56-
for arg in args[0]:
57-
if not len(arg) == 2:
58-
raise TypeError(
59-
"{} takes either dict or list of (key, value) "
60-
"tuples".format(name))
61-
if not isinstance(arg, tuple):
62-
item = tuple(arg)
63-
else:
64-
item = arg
65-
self._add(item)
66-
67-
for item in kwargs.items():
68-
self._add(item)
6936

70-
cdef _add(self, tuple item):
71-
self._items.append(item)
7237

7338
def getall(self, key, default=_marker):
7439
"""
@@ -100,13 +65,6 @@ cdef class MultiDict:
10065
return default
10166
raise KeyError('Key not found: %r' % key)
10267

103-
# extra methods #
104-
105-
def copy(self):
106-
"""Returns a copy itself."""
107-
cls = self.__class__
108-
return cls(self._items)
109-
11068
# Mapping interface #
11169

11270
def __getitem__(self, key):
@@ -125,16 +83,6 @@ cdef class MultiDict:
12583
return True
12684
return False
12785

128-
cdef _delitem(self, key, int raise_key_error):
129-
cdef int found
130-
found = False
131-
for i in range(len(self._items) - 1, -1, -1):
132-
if self._items[i][0] == key:
133-
del self._items[i]
134-
found = True
135-
if not found and raise_key_error:
136-
raise KeyError(key)
137-
13886
def __iter__(self):
13987
return iter(self.keys())
14088

@@ -159,27 +107,56 @@ cdef class MultiDict:
159107
cdef _ValuesView _values_view(self, getall):
160108
return _ValuesView.__new__(_ValuesView, self._items, getall)
161109

110+
def __repr__(self):
111+
body = ', '.join("'{}': {!r}".format(k, v) for k, v in self.items())
112+
return '<{} {{{}}}>'.format(self.__class__.__name__, body)
113+
114+
115+
116+
117+
118+
cdef class MultiDictProxy(_Base):
119+
120+
def __init__(self, arg):
121+
cdef MultiDict mdict
122+
if not isinstance(arg, MultiDict):
123+
raise TypeError(
124+
'MultiDictProxy requires MultiDict instance, not {}'.format(
125+
type(arg)))
126+
127+
mdict = arg
128+
self._items = mdict._items
129+
130+
def copy(self):
131+
return MultiDict(self._items)
132+
162133
def __richcmp__(self, other, op):
163-
cdef MultiDict typed_self = self
164-
cdef MultiDict typed_other
134+
cdef MultiDictProxy typed_self = self
135+
cdef MultiDictProxy typed_other
165136
cdef tuple item
166137
if op == 2:
167-
if not isinstance(other, abc.Mapping):
168-
return NotImplemented
169-
if isinstance(other, MultiDict):
138+
if isinstance(other, MultiDictProxy):
170139
typed_other = other
171140
return typed_self._items == typed_other._items
141+
elif isinstance(other, MultiDict):
142+
typed_other = other
143+
return typed_self._items == typed_other._items
144+
elif not isinstance(other, abc.Mapping):
145+
return NotImplemented
172146
for item in typed_self._items:
173147
nv = other.get(item[0], _marker)
174148
if item[1] != nv:
175149
return False
176150
return True
177151
elif op != 2:
178-
if not isinstance(other, abc.Mapping):
179-
return NotImplemented
180-
if isinstance(other, MultiDict):
152+
if isinstance(other, MultiDictProxy):
181153
typed_other = other
182154
return typed_self._items != typed_other._items
155+
elif isinstance(other, MultiDict):
156+
typed_other = other
157+
return typed_self._items == typed_other._items
158+
elif not isinstance(other, abc.Mapping):
159+
return NotImplemented
183160
for item in typed_self._items:
184161
nv = other.get(item[0], _marker)
185162
if item[1] == nv:
@@ -188,32 +165,29 @@ cdef class MultiDict:
188165
else:
189166
return NotImplemented
190167

191-
def __repr__(self):
192-
body = ', '.join("'{}': {!r}".format(k, v) for k, v in self.items())
193-
return '<{} {{{}}}>'.format(self.__class__.__name__, body)
194168

169+
abc.Mapping.register(MultiDictProxy)
195170

196-
abc.Mapping.register(MultiDict)
197171

172+
cdef class CIMultiDictProxy(MultiDictProxy):
198173

199-
cdef class CIMultiDict(MultiDict):
200-
"""Case insensitive multi dict."""
174+
def __init__(self, arg):
175+
cdef CIMultiDict mdict
176+
if not isinstance(arg, CIMultiDict):
177+
raise TypeError(
178+
'CIMultiDictProxy requires CIMultiDict instance, not {}'.format(
179+
type(arg)))
201180

202-
@classmethod
203-
def _from_uppercase_multidict(cls, MultiDict dct):
204-
# NB: doesn't check for uppercase keys!
205-
cdef CIMultiDict ret
206-
ret = cls.__new__(cls)
207-
ret._items = dct._items
208-
return ret
181+
mdict = arg
182+
self._items = mdict._items
209183

210184
cdef _upper(self, s):
211185
if type(s) is self._upstr:
212186
return s
213187
return s.upper()
214188

215-
cdef _add(self, tuple item):
216-
self._items.append((self._upper(item[0]), item[1]))
189+
def copy(self):
190+
return CIMultiDict(self._items)
217191

218192
def getall(self, key, default=_marker):
219193
return self._getall(self._upper(key), default)
@@ -231,28 +205,67 @@ cdef class CIMultiDict(MultiDict):
231205
return self._contains(self._upper(key))
232206

233207

234-
abc.Mapping.register(CIMultiDict)
208+
abc.Mapping.register(CIMultiDictProxy)
235209

236210

237-
cdef class MutableMultiDict(MultiDict):
211+
cdef class MultiDict(_Base):
238212
"""An ordered dictionary that can have multiple values for each key."""
239213

214+
def __init__(self, *args, **kwargs):
215+
self._items = []
216+
217+
self._extend(args, kwargs, self.__class__.__name__)
218+
219+
cdef _extend(self, tuple args, dict kwargs, name):
220+
cdef tuple item
221+
222+
if len(args) > 1:
223+
raise TypeError("{} takes at most 1 positional argument"
224+
" ({} given)".format(name, len(args)))
225+
226+
if args:
227+
if hasattr(args[0], 'items'):
228+
for item in args[0].items():
229+
self._add(item)
230+
else:
231+
for arg in args[0]:
232+
if not len(arg) == 2:
233+
raise TypeError(
234+
"{} takes either dict or list of (key, value) "
235+
"tuples".format(name))
236+
if not isinstance(arg, tuple):
237+
item = tuple(arg)
238+
else:
239+
item = arg
240+
self._add(item)
241+
242+
for item in kwargs.items():
243+
self._add(item)
244+
245+
cdef _add(self, tuple item):
246+
self._items.append(item)
247+
240248
def add(self, key, value):
241249
"""
242250
Add the key and value, not overwriting any previous value.
243251
"""
244252
self._add((key, value))
245253

254+
def copy(self):
255+
"""Returns a copy itself."""
256+
cls = self.__class__
257+
return cls(self._items)
258+
246259
def extend(self, *args, **kwargs):
247-
"""Extends current MutableMultiDict with more values.
260+
"""Extends current MultiDict with more values.
248261
249262
This method must be used instead of update.
250263
"""
251264
self._extend(args, kwargs, "extend")
252265

253266
def clear(self):
254-
"""Remove all items from MutableMultiDict"""
255-
self._items = []
267+
"""Remove all items from MultiDict"""
268+
self._items.clear()
256269

257270
# MutableMapping interface #
258271

@@ -263,6 +276,16 @@ cdef class MutableMultiDict(MultiDict):
263276
def __delitem__(self, key):
264277
self._delitem(key, True)
265278

279+
cdef _delitem(self, key, int raise_key_error):
280+
cdef int found
281+
found = False
282+
for i in range(len(self._items) - 1, -1, -1):
283+
if self._items[i][0] == key:
284+
del self._items[i]
285+
found = True
286+
if not found and raise_key_error:
287+
raise KeyError(key)
288+
266289
def setdefault(self, key, default=None):
267290
for k, v in self._items:
268291
if k == key:
@@ -282,28 +305,81 @@ cdef class MutableMultiDict(MultiDict):
282305
"""Method not allowed."""
283306
raise NotImplementedError("Use extend method instead")
284307

308+
def __richcmp__(self, other, op):
309+
cdef MultiDict typed_self = self
310+
cdef MultiDict typed_other
311+
cdef tuple item
312+
if op == 2:
313+
if isinstance(other, MultiDict):
314+
typed_other = other
315+
return typed_self._items == typed_other._items
316+
elif not isinstance(other, abc.Mapping):
317+
return NotImplemented
318+
for item in typed_self._items:
319+
nv = other.get(item[0], _marker)
320+
if item[1] != nv:
321+
return False
322+
return True
323+
elif op != 2:
324+
if isinstance(other, MultiDict):
325+
typed_other = other
326+
return typed_self._items == typed_other._items
327+
elif not isinstance(other, abc.Mapping):
328+
return NotImplemented
329+
for item in typed_self._items:
330+
nv = other.get(item[0], _marker)
331+
if item[1] == nv:
332+
return True
333+
return False
334+
else:
335+
return NotImplemented
336+
337+
285338

286-
abc.MutableMapping.register(MutableMultiDict)
339+
abc.MutableMapping.register(MultiDict)
287340

288341

289-
cdef class CIMutableMultiDict(CIMultiDict):
342+
cdef class CIMultiDict(MultiDict):
290343
"""An ordered dictionary that can have multiple values for each key."""
291344

345+
cdef _add(self, tuple item):
346+
self._items.append((self._upper(item[0]), item[1]))
347+
348+
cdef _upper(self, s):
349+
if type(s) is self._upstr:
350+
return s
351+
return s.upper()
352+
353+
def getall(self, key, default=_marker):
354+
return self._getall(self._upper(key), default)
355+
356+
def getone(self, key, default=_marker):
357+
return self._getone(self._upper(key), default)
358+
359+
def get(self, key, default=None):
360+
return self._getone(self._upper(key), default)
361+
362+
def __getitem__(self, key):
363+
return self._getone(self._upper(key), _marker)
364+
365+
def __contains__(self, key):
366+
return self._contains(self._upper(key))
367+
292368
def add(self, key, value):
293369
"""
294370
Add the key and value, not overwriting any previous value.
295371
"""
296372
self._add((key, value))
297373

298374
def extend(self, *args, **kwargs):
299-
"""Extends current MutableMultiDict with more values.
375+
"""Extends current MultiDict with more values.
300376
301377
This method must be used instead of update.
302378
"""
303379
self._extend(args, kwargs, "extend")
304380

305381
def clear(self):
306-
"""Remove all items from MutableMultiDict"""
382+
"""Remove all items from MultiDict"""
307383
self._items = []
308384

309385
# MutableMapping interface #
@@ -337,7 +413,7 @@ cdef class CIMutableMultiDict(CIMultiDict):
337413
raise NotImplementedError("Use extend method instead")
338414

339415

340-
abc.MutableMapping.register(CIMutableMultiDict)
416+
abc.MutableMapping.register(CIMultiDict)
341417

342418

343419
cdef class _ViewBase:

0 commit comments

Comments
 (0)