1 """Dictionary-like objects which allow multiple keys
2
3 Python dictionaries map a key to a value. Duplicate keys are not
4 allowed, and new entries replace old ones with the same key. Order is
5 not otherwise preserved, so there's no way to get the items in the
6 order they were added to a dictionary.
7
8 Some types of data is best stored in dictionary-like object which
9 allow multiple values per key. Some of these need the input order
10 strongly preserved, so the items can be retrieved in the same order as
11 they were added to the dictionary. That is the OrderedMultiDict.
12
13 Others need a weaker ordering guarantee where the order of values for
14 a given key is preserved but the order between the keys is not. That
15 is UnorderedMultiDict. (Because strong ordering isn't needed, it's
16 faster to delete from an UnorderedMultiDict.)
17
18 To create a MultiDict, pass in an object which implements the
19 'allitems' method and returns a list of (key, value) pairs, or
20 pass in the list of (key, value) pairs directly.
21
22 The two MultiDict classes implement the following dictionary methods
23 d["lookup"],
24 d["key"] = value
25 del d[key]
26 d.get("key", default = None)
27 d1 == d2, d1 != d2, len(d), iter(d), str(d)
28 d.keys(), d.values(), d.items()
29
30 The new methods are:
31 d.getall(key)
32 d.allkeys()
33 d.allvalues()
34 d.allitems()
35
36 >>> import MultiDict
37 >>> od = MultiDict.OrderedMultiDict()
38 >>> od["Name"] = "Andrew"
39 >>> od["Color"] = "BLUE"
40 >>> od["Name"] = "Dalke"
41 >>> od["Color"] = "Green"
42 >>> od[3] = 9
43 >>> len(od)
44 3
45 >>> od["Name"]
46 'Dalke'
47 >>> od.getall("Name")
48 ['Andrew', 'Dalke']
49 >>> for k, v in od.allitems():
50 ... print "%r == %r" % (k, v)
51 ...
52 'Name' == 'Andrew'
53 'Color' == 'BLUE'
54 'Name' == 'Dalke'
55 'Color' == 'Green'
56 3 == 9
57 >>> del od["Name"]
58 >>> len(od)
59 2
60 >>> for k, v in od.allitems():
61 ... print "%r == %r" % (k, v)
62 ...
63 'Color' == 'BLUE'
64 'Color' == 'Green'
65 3 == 9
66 >>>
67
68 The latest version of this code can be found at
69 http://www.dalkescientific.com/Python/
70 """
71
72
73
74
75 from __future__ import generators
76
77
78
79
80
81
82
83
84
87 """shows contents as if this is a dictionary
88
89 If multiple values exist for a given key, use the last
90 one added.
91 """
92 d = {}
93 for k in self.data:
94 d[k] = self.data[k][-1]
95 return str(d)
97 """the number of unique keys"""
98 return len(self.data)
99
101 """value for a given key
102
103 If more than one value exists for the key, use one added most recently
104 """
105 return self.data[key][-1]
106
107 - def get(self, key, default = None):
108 """value for the given key; default = None if not present
109
110 If more than one value exists for the key, use the one added
111 most recently.
112 """
113 return self.data.get(key, [default])[-1]
114
116 """check if the key exists"""
117 return key in self.data
118
120 """unordered list of unique keys"""
121 return self.data.keys()
122
124 """unordered list of values
125
126 If more than one value exists for a given key, use the value
127 added most recently.
128 """
129 return [x[-1] for x in self.data.values()]
130
132 """unordered list of key/value pairs
133
134 If more than one value exists for a given key, use the value
135 added most recently.
136 """
137 return [(k, v[-1]) for k, v in self.data.items()]
138
140 """Get all values for a given key
141
142 Multiple values are returned in input order.
143 If the key does not exists, returns an empty list.
144 """
145 return self.data[key]
146
148 """iterate through the list of unique keys"""
149 return iter(self.data)
150
151
153 """Store key/value mappings.
154
155 Acts like a standard dictionary with the following features:
156 - duplicate keys are allowed;
157
158 - input order is preserved for all key/value pairs.
159
160 >>> od = OrderedMultiDict([("Food", "Spam"), ("Color", "Blue"),
161 ... ("Food", "Eggs"), ("Color", "Green")])
162 >>> od["Food"]
163 'Eggs'
164 >>> od.getall("Food")
165 ['Spam', 'Eggs']
166 >>> list(od.allkeys())
167 ['Food', 'Color', 'Food', 'Color']
168 >>>
169
170 The order of keys and values(eg, od.allkeys() and od.allitems())
171 preserves input order.
172
173 Can also pass in an object to the constructor which has an
174 allitems() method that returns a list of key/value pairs.
175
176 """
178 self.data = {}
179 self.order_data = []
180 if multidict is not None:
181 if hasattr(multidict, "allitems"):
182 multidict = multidict.allitems()
183 for k, v in multidict:
184 self[k] = v
186 """Does this OrderedMultiDict have the same contents and order as another?"""
187 return self.order_data == other.order_data
189 """Does this OrderedMultiDict have different contents or order as another?"""
190 return self.order_data != other.order_data
191
193 return "<OrderedMultiDict %s>" % (self.order_data,)
194
196 """Add a new key/value pair
197
198 If the key already exists, replaces the existing value
199 so that d[key] is the new value and not the old one.
200
201 To get all values for a given key, use d.getall(key).
202 """
203 self.order_data.append((key, value))
204 self.data.setdefault(key, []).append(value)
205
207 """Remove all values for the given key"""
208 del self.data[key]
209 self.order_data[:] = [x for x in self.order_data if x[0] != key]
210
212 """iterate over all keys in input order"""
213 for x in self.order_data:
214 yield x[0]
216 """iterate over all values in input order"""
217 for x in self.order_data:
218 yield x[1]
220 """iterate over all key/value pairs in input order"""
221 return iter(self.order_data)
222
223
224
226 """Store key/value mappings.
227
228 Acts like a standard dictionary with the following features:
229 - duplicate keys are allowed;
230
231 - input order is preserved for all values of a given
232 key but not between different keys.
233
234 >>> ud = UnorderedMultiDict([("Food", "Spam"), ("Color", "Blue"),
235 ... ("Food", "Eggs"), ("Color", "Green")])
236 >>> ud["Food"]
237 'Eggs'
238 >>> ud.getall("Food")
239 ['Spam', 'Eggs']
240 >>>
241
242 The order of values from a given key (as from ud.getall("Food"))
243 is guaranteed but the order between keys (as from od.allkeys()
244 and od.allitems()) is not.
245
246 Can also pass in an object to the constructor which has an
247 allitems() method that returns a list of key/value pairs.
248
249 """
251 self.data = {}
252 if multidict is not None:
253 if hasattr(multidict, "allitems"):
254 multidict = multidict.allitems()
255 for k, v in multidict:
256 self[k] = v
257
259 """Does this UnorderedMultiDict have the same keys, with values in the same order, as another?"""
260 return self.data == other.data
261
263 """Does this UnorderedMultiDict NOT have the same keys, with values in the same order, as another?"""
264 return self.data != other.data
265
267 return "<UnorderedMultiDict %s>" % (self.data,)
268
270 """Add a new key/value pair
271
272 If the key already exists, replaces the existing value
273 so that d[key] is the new value and not the old one.
274
275 To get all values for a given key, use d.getall(key).
276 """
277 self.data.setdefault(key, []).append(value)
278
280 """Remove all values for the given key"""
281 del self.data[key]
282
284 """iterate over all keys in arbitrary order"""
285 for k, v in self.data.iteritems():
286 for x in v:
287 yield k
288
290 """iterate over all values in arbitrary order"""
291 for v in self.data.itervalues():
292 for x in v:
293 yield x
294
296 """iterate over all key/value pairs, in arbitrary order
297
298 Actually, the keys are iterated in arbitrary order but all
299 values for that key are iterated at sequence of addition
300 to the UnorderedMultiDict.
301
302 """
303 for k, v in self.data.iteritems():
304 for x in v:
305 yield (k, x)
306
307 __test__ = {
308 "test_ordered_multidict": """
309 >>> od = OrderedMultiDict()
310 >>> od["Name"] = "Andrew"
311 >>> od["Color"] = "BLUE"
312 >>> od["Name"] = "Dalke"
313 >>> od["Color"] = "Green"
314 >>> od[3] = 9
315 >>> len(od)
316 3
317 >>> len(od.keys())
318 3
319 >>> len(od.values())
320 3
321 >>> len(od.items())
322 3
323 >>> od.keys()
324 ['Color', 3, 'Name']
325 >>> "Name" in od and "Name" in od.keys() and "Name" in od.allkeys()
326 1
327 >>> "Color" in od and "Color" in od.keys() and "Color" in od.allkeys()
328 1
329 >>> 3 in od and 3 in od.keys() and 3 in od.allkeys()
330 1
331 >>> od == od
332 1
333 >>> od != OrderedMultiDict() # line 25
334 1
335 >>> list(od.allkeys())
336 ['Name', 'Color', 'Name', 'Color', 3]
337 >>> list(od.allvalues())
338 ['Andrew', 'BLUE', 'Dalke', 'Green', 9]
339 >>> list(od.allitems())
340 [('Name', 'Andrew'), ('Color', 'BLUE'), ('Name', 'Dalke'), ('Color', 'Green'), (3, 9)]
341 >>> len(list(od))
342 3
343 >>> od["invalid"]
344 Traceback (most recent call last):
345 File "<stdin>", line 1, in ?
346 File "MultiDict.py", line 33, in __getitem__
347 return self.data[key]
348 KeyError: invalid
349 >>> od["Color"]
350 'Green'
351 >>> od.getall("Color")
352 ['BLUE', 'Green']
353 >>> od2 = OrderedMultiDict(od)
354 >>> list(od2.allitems())
355 [('Name', 'Andrew'), ('Color', 'BLUE'), ('Name', 'Dalke'), ('Color', 'Green'), (3, 9)]
356 >>> od == od2
357 1
358 >>> od2 == od # line 53
359 1
360 >>> od2 != od
361 0
362 >>> del od["Color"]
363 >>> od["Color"]
364 Traceback (most recent call last):
365 File "<stdin>", line 1, in ?
366 File "MultiDict.py", line 33, in __getitem__
367 return self.data[key]
368 KeyError: Color
369 >>> list(od.allitems())
370 [('Name', 'Andrew'), ('Name', 'Dalke'), (3, 9)]
371 >>> list(od2.allkeys())
372 ['Name', 'Color', 'Name', 'Color', 3]
373 >>> od2["Color"]
374 'Green'
375 >>> od == od2
376 0
377 >>>
378 >>> s = str(od2)
379 >>> s = repr(od2)
380 """,
381 "test_unordered_multidict": """
382 >>> ud = UnorderedMultiDict()
383 >>> ud["Name"] = "Andrew"
384 >>> ud["Color"] = "BLUE"
385 >>> ud["Name"] = "Dalke"
386 >>> ud["Color"] = "GREEN"
387 >>> ud[3] = 9
388 >>> ud[3]
389 9
390 >>> ud["Name"]
391 'Dalke'
392 >>> ud["Color"] # line 11
393 'GREEN'
394 >>> ud[3]
395 9
396 >>> len(ud)
397 3
398 >>> len(list(ud)), len(ud.keys()), len(ud.values()), len(ud.items())
399 (3, 3, 3, 3)
400 >>> ud["invalid"]
401 Traceback (most recent call last):
402 File "<stdin>", line 1, in ?
403 File "MultiDict.py", line 105, in __getitem__
404 return self.data[key][-1]
405 KeyError: invalid
406 >>> ud.get("invalid")
407 >>> ud.get("invalid") is None
408 1
409 >>> ud.get("invalid", "red")
410 'red'
411 >>> "Color" in ud
412 1
413 >>> "Color" in ud.keys() # line 32
414 1
415 >>> "invalid" in ud
416 0
417 >>> "invalid" in ud.keys()
418 0
419 >>> ud.get("Color", "red")
420 'GREEN'
421 >>> "Andrew" in ud.values()
422 0
423 >>> "Dalke" in ud.values()
424 1
425 >>> ud.getall("Color") # line 44
426 ['BLUE', 'GREEN']
427 >>> ud.getall("invalid")
428 Traceback (most recent call last):
429 File "<stdin>", line 1, in ?
430 File "MultiDict.py", line 126, in __getitem__
431 return self.data[key]
432 KeyError: invalid
433 >>> len(list(ud.allkeys())), len(list(ud.allvalues())), len(list(ud.allitems()))
434 (5, 5, 5)
435 >>> ("Color", "BLUE") in ud.allitems()
436 1
437 >>> ("Color", "GREEN") in ud.allitems()
438 1
439 >>> ("Name", "Andrew") in ud.allitems() # line 58
440 1
441 >>> ("Name", "Dalke") in ud.allitems()
442 1
443 >>> (3, 9) in ud.allitems()
444 1
445 >>> x = list(ud.allkeys())
446 >>> x.sort()
447 >>> x
448 [3, 'Color', 'Color', 'Name', 'Name']
449 >>> x = list(ud.allvalues())
450 >>> x.sort()
451 >>> x
452 [9, 'Andrew', 'BLUE', 'Dalke', 'GREEN']
453 >>> x = list(ud)
454 >>> x.sort()
455 >>> x
456 [3, 'Color', 'Name']
457 >>> ud2 = UnorderedMultiDict(ud) # line 76
458 >>> ud == ud2
459 1
460 >>> ud != ud
461 0
462 >>> del ud["Color"]
463 >>> ud == ud2
464 0
465 >>> ud != ud2
466 1
467 >>> len(ud)
468 2
469 >>> "Color" in ud
470 0
471 >>> "Color" in ud2 # line 90
472 1
473 >>> s = str(ud2)
474 >>> s = repr(ud2)
475 """,
476 "__doc__": __doc__,
477 }
478
482
483 if __name__ == "__main__":
484 _test()
485