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
76
77
78
79
80
81
82
85 """shows contents as if this is a dictionary
86
87 If multiple values exist for a given key, use the last
88 one added.
89 """
90 d = {}
91 for k in self.data:
92 d[k] = self.data[k][-1]
93 return str(d)
95 """the number of unique keys"""
96 return len(self.data)
97
99 """value for a given key
100
101 If more than one value exists for the key, use one added most recently
102 """
103 return self.data[key][-1]
104
105 - def get(self, key, default = None):
106 """value for the given key; default = None if not present
107
108 If more than one value exists for the key, use the one added
109 most recently.
110 """
111 return self.data.get(key, [default])[-1]
112
114 """check if the key exists"""
115 return key in self.data
116
118 """unordered list of unique keys"""
119 return self.data.keys()
120
122 """unordered list of values
123
124 If more than one value exists for a given key, use the value
125 added most recently.
126 """
127 return [x[-1] for x in self.data.values()]
128
130 """unordered list of key/value pairs
131
132 If more than one value exists for a given key, use the value
133 added most recently.
134 """
135 return [(k, v[-1]) for k, v in self.data.items()]
136
138 """Get all values for a given key
139
140 Multiple values are returned in input order.
141 If the key does not exists, returns an empty list.
142 """
143 return self.data[key]
144
146 """iterate through the list of unique keys"""
147 return iter(self.data)
148
149
151 """Store key/value mappings.
152
153 Acts like a standard dictionary with the following features:
154 - duplicate keys are allowed;
155
156 - input order is preserved for all key/value pairs.
157
158 >>> od = OrderedMultiDict([("Food", "Spam"), ("Color", "Blue"),
159 ... ("Food", "Eggs"), ("Color", "Green")])
160 >>> od["Food"]
161 'Eggs'
162 >>> od.getall("Food")
163 ['Spam', 'Eggs']
164 >>> list(od.allkeys())
165 ['Food', 'Color', 'Food', 'Color']
166 >>>
167
168 The order of keys and values(eg, od.allkeys() and od.allitems())
169 preserves input order.
170
171 Can also pass in an object to the constructor which has an
172 allitems() method that returns a list of key/value pairs.
173
174 """
176 self.data = {}
177 self.order_data = []
178 if multidict is not None:
179 if hasattr(multidict, "allitems"):
180 multidict = multidict.allitems()
181 for k, v in multidict:
182 self[k] = v
184 """Does this OrderedMultiDict have the same contents and order as another?"""
185 return self.order_data == other.order_data
187 """Does this OrderedMultiDict have different contents or order as another?"""
188 return self.order_data != other.order_data
189
191 return "<OrderedMultiDict %s>" % (self.order_data,)
192
194 """Add a new key/value pair
195
196 If the key already exists, replaces the existing value
197 so that d[key] is the new value and not the old one.
198
199 To get all values for a given key, use d.getall(key).
200 """
201 self.order_data.append((key, value))
202 self.data.setdefault(key, []).append(value)
203
205 """Remove all values for the given key"""
206 del self.data[key]
207 self.order_data[:] = [x for x in self.order_data if x[0] != key]
208
210 """iterate over all keys in input order"""
211 for x in self.order_data:
212 yield x[0]
214 """iterate over all values in input order"""
215 for x in self.order_data:
216 yield x[1]
218 """iterate over all key/value pairs in input order"""
219 return iter(self.order_data)
220
221
222
224 """Store key/value mappings.
225
226 Acts like a standard dictionary with the following features:
227 - duplicate keys are allowed;
228
229 - input order is preserved for all values of a given
230 key but not between different keys.
231
232 >>> ud = UnorderedMultiDict([("Food", "Spam"), ("Color", "Blue"),
233 ... ("Food", "Eggs"), ("Color", "Green")])
234 >>> ud["Food"]
235 'Eggs'
236 >>> ud.getall("Food")
237 ['Spam', 'Eggs']
238 >>>
239
240 The order of values from a given key (as from ud.getall("Food"))
241 is guaranteed but the order between keys (as from od.allkeys()
242 and od.allitems()) is not.
243
244 Can also pass in an object to the constructor which has an
245 allitems() method that returns a list of key/value pairs.
246
247 """
249 self.data = {}
250 if multidict is not None:
251 if hasattr(multidict, "allitems"):
252 multidict = multidict.allitems()
253 for k, v in multidict:
254 self[k] = v
255
257 """Does this UnorderedMultiDict have the same keys, with values in the same order, as another?"""
258 return self.data == other.data
259
261 """Does this UnorderedMultiDict NOT have the same keys, with values in the same order, as another?"""
262 return self.data != other.data
263
265 return "<UnorderedMultiDict %s>" % (self.data,)
266
268 """Add a new key/value pair
269
270 If the key already exists, replaces the existing value
271 so that d[key] is the new value and not the old one.
272
273 To get all values for a given key, use d.getall(key).
274 """
275 self.data.setdefault(key, []).append(value)
276
278 """Remove all values for the given key"""
279 del self.data[key]
280
282 """iterate over all keys in arbitrary order"""
283 for k, v in self.data.iteritems():
284 for x in v:
285 yield k
286
288 """iterate over all values in arbitrary order"""
289 for v in self.data.itervalues():
290 for x in v:
291 yield x
292
294 """iterate over all key/value pairs, in arbitrary order
295
296 Actually, the keys are iterated in arbitrary order but all
297 values for that key are iterated at sequence of addition
298 to the UnorderedMultiDict.
299
300 """
301 for k, v in self.data.iteritems():
302 for x in v:
303 yield (k, x)
304
305 __test__ = {
306 "test_ordered_multidict": """
307 >>> od = OrderedMultiDict()
308 >>> od["Name"] = "Andrew"
309 >>> od["Color"] = "BLUE"
310 >>> od["Name"] = "Dalke"
311 >>> od["Color"] = "Green"
312 >>> od[3] = 9
313 >>> len(od)
314 3
315 >>> len(od.keys())
316 3
317 >>> len(od.values())
318 3
319 >>> len(od.items())
320 3
321 >>> od.keys()
322 ['Color', 3, 'Name']
323 >>> "Name" in od and "Name" in od.keys() and "Name" in od.allkeys()
324 1
325 >>> "Color" in od and "Color" in od.keys() and "Color" in od.allkeys()
326 1
327 >>> 3 in od and 3 in od.keys() and 3 in od.allkeys()
328 1
329 >>> od == od
330 1
331 >>> od != OrderedMultiDict() # line 25
332 1
333 >>> list(od.allkeys())
334 ['Name', 'Color', 'Name', 'Color', 3]
335 >>> list(od.allvalues())
336 ['Andrew', 'BLUE', 'Dalke', 'Green', 9]
337 >>> list(od.allitems())
338 [('Name', 'Andrew'), ('Color', 'BLUE'), ('Name', 'Dalke'), ('Color', 'Green'), (3, 9)]
339 >>> len(list(od))
340 3
341 >>> od["invalid"]
342 Traceback (most recent call last):
343 File "<stdin>", line 1, in ?
344 File "MultiDict.py", line 33, in __getitem__
345 return self.data[key]
346 KeyError: invalid
347 >>> od["Color"]
348 'Green'
349 >>> od.getall("Color")
350 ['BLUE', 'Green']
351 >>> od2 = OrderedMultiDict(od)
352 >>> list(od2.allitems())
353 [('Name', 'Andrew'), ('Color', 'BLUE'), ('Name', 'Dalke'), ('Color', 'Green'), (3, 9)]
354 >>> od == od2
355 1
356 >>> od2 == od # line 53
357 1
358 >>> od2 != od
359 0
360 >>> del od["Color"]
361 >>> od["Color"]
362 Traceback (most recent call last):
363 File "<stdin>", line 1, in ?
364 File "MultiDict.py", line 33, in __getitem__
365 return self.data[key]
366 KeyError: Color
367 >>> list(od.allitems())
368 [('Name', 'Andrew'), ('Name', 'Dalke'), (3, 9)]
369 >>> list(od2.allkeys())
370 ['Name', 'Color', 'Name', 'Color', 3]
371 >>> od2["Color"]
372 'Green'
373 >>> od == od2
374 0
375 >>>
376 >>> s = str(od2)
377 >>> s = repr(od2)
378 """,
379 "test_unordered_multidict": """
380 >>> ud = UnorderedMultiDict()
381 >>> ud["Name"] = "Andrew"
382 >>> ud["Color"] = "BLUE"
383 >>> ud["Name"] = "Dalke"
384 >>> ud["Color"] = "GREEN"
385 >>> ud[3] = 9
386 >>> ud[3]
387 9
388 >>> ud["Name"]
389 'Dalke'
390 >>> ud["Color"] # line 11
391 'GREEN'
392 >>> ud[3]
393 9
394 >>> len(ud)
395 3
396 >>> len(list(ud)), len(ud.keys()), len(ud.values()), len(ud.items())
397 (3, 3, 3, 3)
398 >>> ud["invalid"]
399 Traceback (most recent call last):
400 File "<stdin>", line 1, in ?
401 File "MultiDict.py", line 105, in __getitem__
402 return self.data[key][-1]
403 KeyError: invalid
404 >>> ud.get("invalid")
405 >>> ud.get("invalid") is None
406 1
407 >>> ud.get("invalid", "red")
408 'red'
409 >>> "Color" in ud
410 1
411 >>> "Color" in ud.keys() # line 32
412 1
413 >>> "invalid" in ud
414 0
415 >>> "invalid" in ud.keys()
416 0
417 >>> ud.get("Color", "red")
418 'GREEN'
419 >>> "Andrew" in ud.values()
420 0
421 >>> "Dalke" in ud.values()
422 1
423 >>> ud.getall("Color") # line 44
424 ['BLUE', 'GREEN']
425 >>> ud.getall("invalid")
426 Traceback (most recent call last):
427 File "<stdin>", line 1, in ?
428 File "MultiDict.py", line 126, in __getitem__
429 return self.data[key]
430 KeyError: invalid
431 >>> len(list(ud.allkeys())), len(list(ud.allvalues())), len(list(ud.allitems()))
432 (5, 5, 5)
433 >>> ("Color", "BLUE") in ud.allitems()
434 1
435 >>> ("Color", "GREEN") in ud.allitems()
436 1
437 >>> ("Name", "Andrew") in ud.allitems() # line 58
438 1
439 >>> ("Name", "Dalke") in ud.allitems()
440 1
441 >>> (3, 9) in ud.allitems()
442 1
443 >>> x = list(ud.allkeys())
444 >>> x.sort()
445 >>> x
446 [3, 'Color', 'Color', 'Name', 'Name']
447 >>> x = list(ud.allvalues())
448 >>> x.sort()
449 >>> x
450 [9, 'Andrew', 'BLUE', 'Dalke', 'GREEN']
451 >>> x = list(ud)
452 >>> x.sort()
453 >>> x
454 [3, 'Color', 'Name']
455 >>> ud2 = UnorderedMultiDict(ud) # line 76
456 >>> ud == ud2
457 1
458 >>> ud != ud
459 0
460 >>> del ud["Color"]
461 >>> ud == ud2
462 0
463 >>> ud != ud2
464 1
465 >>> len(ud)
466 2
467 >>> "Color" in ud
468 0
469 >>> "Color" in ud2 # line 90
470 1
471 >>> s = str(ud2)
472 >>> s = repr(ud2)
473 """,
474 "__doc__": __doc__,
475 }
476
480
481 if __name__ == "__main__":
482 _test()
483