1
1
import pprint
2
- from itertools import chain
3
- from collections import OrderedDict , abc
2
+ from itertools import chain , filterfalse
3
+ from collections import abc
4
4
5
5
_marker = object ()
6
6
7
7
8
+ def _unique_everseen (iterable ):
9
+ """List unique elements, preserving order.
10
+ Remember all elements ever seen.
11
+ Recipe from
12
+ https://docs.python.org/3/library/itertools.html#itertools-recipes"""
13
+ # unique_everseen('AAAABBBCCDAABBB') --> A B C D
14
+ # unique_everseen('ABBCcAD', str.lower) --> A B C D
15
+ seen = set ()
16
+ seen_add = seen .add
17
+ for element in filterfalse (seen .__contains__ , iterable ):
18
+ seen_add (element )
19
+ yield element
20
+
21
+
8
22
class MultiDict (abc .Mapping ):
9
23
"""Read-only ordered dictionary that can have multiple values for each key.
10
24
@@ -16,46 +30,45 @@ class MultiDict(abc.Mapping):
16
30
def __init__ (self , * args , ** kwargs ):
17
31
if len (args ) > 1 :
18
32
raise TypeError ("MultiDict takes at most 1 positional "
19
- "arguments ({} given)" .format (len (args )))
20
- self ._items = OrderedDict ()
33
+ "argument ({} given)" .format (len (args )))
21
34
35
+ self ._items = []
22
36
if args :
23
37
if hasattr (args [0 ], 'items' ):
24
38
args = list (args [0 ].items ())
25
39
else :
26
40
args = list (args [0 ])
41
+ for arg in args :
42
+ if not len (arg ) == 2 :
43
+ raise TypeError ("MultiDict takes either dict "
44
+ "or list of (key, value) tuples" )
27
45
28
46
self ._fill (chain (args , kwargs .items ()))
29
47
30
48
def _fill (self , ipairs ):
31
- for key , value in ipairs :
32
- if key in self ._items :
33
- self ._items [key ].append (value )
34
- else :
35
- self ._items [key ] = [value ]
36
-
37
- def get (self , key , default = None ):
38
- """Return first value stored at key."""
39
- if key in self ._items and self ._items [key ]:
40
- return self ._items [key ][0 ]
41
- else :
42
- return default
49
+ self ._items .extend (ipairs )
43
50
44
51
def getall (self , key , default = _marker ):
45
- """Returns all values stored at key as a tuple.
46
-
47
- Raises KeyError if key doesn't exist."""
48
- if key in self ._items :
49
- return tuple (self ._items [key ])
50
- else :
51
- if default is not _marker :
52
- return default
53
- else :
54
- raise KeyError (key )
52
+ """
53
+ Return a list of all values matching the key (may be an empty list)
54
+ """
55
+ res = tuple ([v for k , v in self ._items if k == key ])
56
+ if res :
57
+ return res
58
+ if not res and default is not _marker :
59
+ return default
60
+ raise KeyError ('Key not found: %r' % key )
55
61
56
- def getone (self , key ):
57
- """Return first value stored at key."""
58
- return self ._items [key ][0 ]
62
+ def getone (self , key , default = _marker ):
63
+ """
64
+ Get first value matching the key
65
+ """
66
+ for k , v in self ._items :
67
+ if k == key :
68
+ return v
69
+ if default is not _marker :
70
+ return default
71
+ raise KeyError ('Key not found: %r' % key )
59
72
60
73
# extra methods #
61
74
@@ -67,14 +80,20 @@ def copy(self):
67
80
# Mapping interface #
68
81
69
82
def __getitem__ (self , key ):
70
- return self ._items [key ][0 ]
83
+ for k , v in self ._items :
84
+ if k == key :
85
+ return v
86
+ raise KeyError (key )
71
87
72
88
def __iter__ (self ):
73
89
return iter (self ._items )
74
90
75
91
def __len__ (self ):
76
92
return len (self ._items )
77
93
94
+ def keys (self , * , getall = False ):
95
+ return _KeysView (self ._items , getall = getall )
96
+
78
97
def items (self , * , getall = False ):
79
98
return _ItemsView (self ._items , getall = getall )
80
99
@@ -89,12 +108,16 @@ def __eq__(self, other):
89
108
return dict (self .items ()) == dict (other .items ())
90
109
91
110
def __contains__ (self , key ):
92
- return key in self ._items
111
+ for k , v in self ._items :
112
+ if k == key :
113
+ return True
114
+ return False
93
115
94
116
def __repr__ (self ):
95
117
return '<{}>\n {}' .format (
96
118
self .__class__ .__name__ , pprint .pformat (
97
- list (self .items (getall = True ))))
119
+ list (self .items (getall = True )))
120
+ )
98
121
99
122
100
123
class CaseInsensitiveMultiDict (MultiDict ):
@@ -109,51 +132,29 @@ def _from_uppercase_multidict(cls, dct):
109
132
110
133
def _fill (self , ipairs ):
111
134
for key , value in ipairs :
112
- key = key .upper ()
113
- if key in self ._items :
114
- self ._items [key ].append (value )
115
- else :
116
- self ._items [key ] = [value ]
135
+ uppkey = key .upper ()
136
+ self ._items .append ((uppkey , value ))
117
137
118
138
def getall (self , key , default = _marker ):
119
139
return super ().getall (key .upper (), default )
120
140
121
- def get (self , key , default = None ):
122
- key = key .upper ()
123
- if key in self ._items and self ._items [key ]:
124
- return self ._items [key ][0 ]
125
- else :
126
- return default
127
-
128
- def getone (self , key ):
129
- return self ._items [key .upper ()][0 ]
141
+ def getone (self , key , default = _marker ):
142
+ return super ().getone (key .upper (), default )
130
143
131
144
def __getitem__ (self , key ):
132
- return self . _items [ key .upper ()][ 0 ]
145
+ return super (). __getitem__ ( key .upper ())
133
146
134
147
def __contains__ (self , key ):
135
- return key .upper () in self . _items
148
+ return super (). __contains__ ( key .upper ())
136
149
137
150
138
- class BaseMutableMultiDict (abc .MutableMapping ):
139
-
140
- def getall (self , key , default = _marker ):
141
- """Returns all values stored at key as list.
142
-
143
- Raises KeyError if key doesn't exist.
144
- """
145
- result = super ().getall (key , default )
146
- if result is not default :
147
- return list (result )
148
- else :
149
- return result
151
+ class MutableMultiDictMixin (abc .MutableMapping ):
150
152
151
153
def add (self , key , value ):
152
- """Adds value to a key."""
153
- if key in self ._items :
154
- self ._items [key ].append (value )
155
- else :
156
- self ._items [key ] = [value ]
154
+ """
155
+ Add the key and value, not overwriting any previous value.
156
+ """
157
+ self ._items .append ((key , value ))
157
158
158
159
def extend (self , * args , ** kwargs ):
159
160
"""Extends current MutableMultiDict with more values.
@@ -182,10 +183,21 @@ def clear(self):
182
183
# MutableMapping interface #
183
184
184
185
def __setitem__ (self , key , value ):
185
- self ._items [key ] = [value ]
186
+ try :
187
+ del self [key ]
188
+ except KeyError :
189
+ pass
190
+ self ._items .append ((key , value ))
186
191
187
192
def __delitem__ (self , key ):
188
- del self ._items [key ]
193
+ items = self ._items
194
+ found = False
195
+ for i in range (len (items )- 1 , - 1 , - 1 ):
196
+ if items [i ][0 ] == key :
197
+ del items [i ]
198
+ found = True
199
+ if not found :
200
+ raise KeyError (key )
189
201
190
202
def pop (self , key , default = None ):
191
203
"""Method not allowed."""
@@ -200,71 +212,74 @@ def update(self, *args, **kw):
200
212
raise NotImplementedError ("Use extend method instead" )
201
213
202
214
203
- class MutableMultiDict (BaseMutableMultiDict , MultiDict ):
215
+ class MutableMultiDict (MutableMultiDictMixin , MultiDict ):
204
216
"""An ordered dictionary that can have multiple values for each key."""
205
217
206
218
207
219
class CaseInsensitiveMutableMultiDict (
208
- BaseMutableMultiDict , CaseInsensitiveMultiDict ):
220
+ MutableMultiDictMixin , CaseInsensitiveMultiDict ):
209
221
"""An ordered dictionary that can have multiple values for each key."""
210
222
211
- def getall (self , key , default = _marker ):
212
- return super ().getall (key .upper (), default )
213
-
214
223
def add (self , key , value ):
215
224
super ().add (key .upper (), value )
216
225
217
226
def __setitem__ (self , key , value ):
218
- self . _items [ key .upper ()] = [ value ]
227
+ super (). __setitem__ ( key .upper (), value )
219
228
220
229
def __delitem__ (self , key ):
221
- del self . _items [ key .upper ()]
230
+ super (). __delitem__ ( key .upper ())
222
231
223
232
224
- class _ItemsView ( abc . ItemsView ) :
233
+ class _ViewBase :
225
234
226
- def __init__ (self , mapping , * , getall = False ):
227
- super ().__init__ (mapping )
235
+ def __init__ (self , items , * , getall = False ):
228
236
self ._getall = getall
237
+ self ._keys = [item [0 ] for item in items ]
238
+ if not getall :
239
+ self ._keys = list (_unique_everseen (self ._keys ))
229
240
230
- def __contains__ (self , item ):
231
- key , value = item
232
- try :
233
- values = self ._mapping [key ]
234
- except KeyError :
235
- return False
241
+ items_to_use = []
242
+ if getall :
243
+ items_to_use = items
236
244
else :
237
- if self ._getall :
238
- return value in values
239
- else :
240
- return value == values [0 ]
245
+ for key in self ._keys :
246
+ for k , v in items :
247
+ if k == key :
248
+ items_to_use .append ((k , v ))
249
+ break
250
+ assert len (items_to_use ) == len (self ._keys )
241
251
242
- def __iter__ (self ):
243
- for key , values in self ._mapping .items ():
244
- if self ._getall :
245
- for value in values :
246
- yield key , value
247
- else :
248
- yield key , values [0 ]
252
+ super ().__init__ (items_to_use )
249
253
250
254
251
- class _ValuesView ( abc .KeysView ):
255
+ class _ItemsView ( _ViewBase , abc .ItemsView ):
252
256
253
- def __init__ (self , mapping , * , getall = False ):
254
- super ().__init__ (mapping )
255
- self ._getall = getall
257
+ def __contains__ (self , item ):
258
+ assert isinstance (item , tuple ) or isinstance (item , list )
259
+ assert len (item ) == 2
260
+ return item in self ._mapping
261
+
262
+ def __iter__ (self ):
263
+ yield from self ._mapping
264
+
265
+
266
+ class _ValuesView (_ViewBase , abc .ValuesView ):
256
267
257
268
def __contains__ (self , value ):
258
- for values in self ._mapping .values ():
259
- if self ._getall and value in values :
260
- return True
261
- elif value == values [0 ]:
269
+ for item in self ._mapping :
270
+ if item [1 ] == value :
262
271
return True
263
272
return False
264
273
265
274
def __iter__ (self ):
266
- for values in self ._mapping .values ():
267
- if self ._getall :
268
- yield from iter (values )
269
- else :
270
- yield values [0 ]
275
+ for item in self ._mapping :
276
+ yield item [1 ]
277
+
278
+
279
+ class _KeysView (_ViewBase , abc .KeysView ):
280
+
281
+ def __contains__ (self , key ):
282
+ return key in self ._keys
283
+
284
+ def __iter__ (self ):
285
+ yield from self ._keys
0 commit comments