|
1 from types import GeneratorType |
|
2 |
|
3 from django.utils.copycompat import deepcopy |
|
4 |
|
5 |
|
6 class MergeDict(object): |
|
7 """ |
|
8 A simple class for creating new "virtual" dictionaries that actually look |
|
9 up values in more than one dictionary, passed in the constructor. |
|
10 |
|
11 If a key appears in more than one of the given dictionaries, only the |
|
12 first occurrence will be used. |
|
13 """ |
|
14 def __init__(self, *dicts): |
|
15 self.dicts = dicts |
|
16 |
|
17 def __getitem__(self, key): |
|
18 for dict_ in self.dicts: |
|
19 try: |
|
20 return dict_[key] |
|
21 except KeyError: |
|
22 pass |
|
23 raise KeyError |
|
24 |
|
25 def __copy__(self): |
|
26 return self.__class__(*self.dicts) |
|
27 |
|
28 def get(self, key, default=None): |
|
29 try: |
|
30 return self[key] |
|
31 except KeyError: |
|
32 return default |
|
33 |
|
34 def getlist(self, key): |
|
35 for dict_ in self.dicts: |
|
36 if key in dict_.keys(): |
|
37 return dict_.getlist(key) |
|
38 return [] |
|
39 |
|
40 def iteritems(self): |
|
41 seen = set() |
|
42 for dict_ in self.dicts: |
|
43 for item in dict_.iteritems(): |
|
44 k, v = item |
|
45 if k in seen: |
|
46 continue |
|
47 seen.add(k) |
|
48 yield item |
|
49 |
|
50 def iterkeys(self): |
|
51 for k, v in self.iteritems(): |
|
52 yield k |
|
53 |
|
54 def itervalues(self): |
|
55 for k, v in self.iteritems(): |
|
56 yield v |
|
57 |
|
58 def items(self): |
|
59 return list(self.iteritems()) |
|
60 |
|
61 def keys(self): |
|
62 return list(self.iterkeys()) |
|
63 |
|
64 def values(self): |
|
65 return list(self.itervalues()) |
|
66 |
|
67 def has_key(self, key): |
|
68 for dict_ in self.dicts: |
|
69 if key in dict_: |
|
70 return True |
|
71 return False |
|
72 |
|
73 __contains__ = has_key |
|
74 __iter__ = iterkeys |
|
75 |
|
76 def copy(self): |
|
77 """Returns a copy of this object.""" |
|
78 return self.__copy__() |
|
79 |
|
80 class SortedDict(dict): |
|
81 """ |
|
82 A dictionary that keeps its keys in the order in which they're inserted. |
|
83 """ |
|
84 def __new__(cls, *args, **kwargs): |
|
85 instance = super(SortedDict, cls).__new__(cls, *args, **kwargs) |
|
86 instance.keyOrder = [] |
|
87 return instance |
|
88 |
|
89 def __init__(self, data=None): |
|
90 if data is None: |
|
91 data = {} |
|
92 elif isinstance(data, GeneratorType): |
|
93 # Unfortunately we need to be able to read a generator twice. Once |
|
94 # to get the data into self with our super().__init__ call and a |
|
95 # second time to setup keyOrder correctly |
|
96 data = list(data) |
|
97 super(SortedDict, self).__init__(data) |
|
98 if isinstance(data, dict): |
|
99 self.keyOrder = data.keys() |
|
100 else: |
|
101 self.keyOrder = [] |
|
102 for key, value in data: |
|
103 if key not in self.keyOrder: |
|
104 self.keyOrder.append(key) |
|
105 |
|
106 def __deepcopy__(self, memo): |
|
107 return self.__class__([(key, deepcopy(value, memo)) |
|
108 for key, value in self.iteritems()]) |
|
109 |
|
110 def __setitem__(self, key, value): |
|
111 if key not in self: |
|
112 self.keyOrder.append(key) |
|
113 super(SortedDict, self).__setitem__(key, value) |
|
114 |
|
115 def __delitem__(self, key): |
|
116 super(SortedDict, self).__delitem__(key) |
|
117 self.keyOrder.remove(key) |
|
118 |
|
119 def __iter__(self): |
|
120 return iter(self.keyOrder) |
|
121 |
|
122 def pop(self, k, *args): |
|
123 result = super(SortedDict, self).pop(k, *args) |
|
124 try: |
|
125 self.keyOrder.remove(k) |
|
126 except ValueError: |
|
127 # Key wasn't in the dictionary in the first place. No problem. |
|
128 pass |
|
129 return result |
|
130 |
|
131 def popitem(self): |
|
132 result = super(SortedDict, self).popitem() |
|
133 self.keyOrder.remove(result[0]) |
|
134 return result |
|
135 |
|
136 def items(self): |
|
137 return zip(self.keyOrder, self.values()) |
|
138 |
|
139 def iteritems(self): |
|
140 for key in self.keyOrder: |
|
141 yield key, self[key] |
|
142 |
|
143 def keys(self): |
|
144 return self.keyOrder[:] |
|
145 |
|
146 def iterkeys(self): |
|
147 return iter(self.keyOrder) |
|
148 |
|
149 def values(self): |
|
150 return map(self.__getitem__, self.keyOrder) |
|
151 |
|
152 def itervalues(self): |
|
153 for key in self.keyOrder: |
|
154 yield self[key] |
|
155 |
|
156 def update(self, dict_): |
|
157 for k, v in dict_.iteritems(): |
|
158 self[k] = v |
|
159 |
|
160 def setdefault(self, key, default): |
|
161 if key not in self: |
|
162 self.keyOrder.append(key) |
|
163 return super(SortedDict, self).setdefault(key, default) |
|
164 |
|
165 def value_for_index(self, index): |
|
166 """Returns the value of the item at the given zero-based index.""" |
|
167 return self[self.keyOrder[index]] |
|
168 |
|
169 def insert(self, index, key, value): |
|
170 """Inserts the key, value pair before the item with the given index.""" |
|
171 if key in self.keyOrder: |
|
172 n = self.keyOrder.index(key) |
|
173 del self.keyOrder[n] |
|
174 if n < index: |
|
175 index -= 1 |
|
176 self.keyOrder.insert(index, key) |
|
177 super(SortedDict, self).__setitem__(key, value) |
|
178 |
|
179 def copy(self): |
|
180 """Returns a copy of this object.""" |
|
181 # This way of initializing the copy means it works for subclasses, too. |
|
182 obj = self.__class__(self) |
|
183 obj.keyOrder = self.keyOrder[:] |
|
184 return obj |
|
185 |
|
186 def __repr__(self): |
|
187 """ |
|
188 Replaces the normal dict.__repr__ with a version that returns the keys |
|
189 in their sorted order. |
|
190 """ |
|
191 return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()]) |
|
192 |
|
193 def clear(self): |
|
194 super(SortedDict, self).clear() |
|
195 self.keyOrder = [] |
|
196 |
|
197 class MultiValueDictKeyError(KeyError): |
|
198 pass |
|
199 |
|
200 class MultiValueDict(dict): |
|
201 """ |
|
202 A subclass of dictionary customized to handle multiple values for the |
|
203 same key. |
|
204 |
|
205 >>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']}) |
|
206 >>> d['name'] |
|
207 'Simon' |
|
208 >>> d.getlist('name') |
|
209 ['Adrian', 'Simon'] |
|
210 >>> d.get('lastname', 'nonexistent') |
|
211 'nonexistent' |
|
212 >>> d.setlist('lastname', ['Holovaty', 'Willison']) |
|
213 |
|
214 This class exists to solve the irritating problem raised by cgi.parse_qs, |
|
215 which returns a list for every key, even though most Web forms submit |
|
216 single name-value pairs. |
|
217 """ |
|
218 def __init__(self, key_to_list_mapping=()): |
|
219 super(MultiValueDict, self).__init__(key_to_list_mapping) |
|
220 |
|
221 def __repr__(self): |
|
222 return "<%s: %s>" % (self.__class__.__name__, |
|
223 super(MultiValueDict, self).__repr__()) |
|
224 |
|
225 def __getitem__(self, key): |
|
226 """ |
|
227 Returns the last data value for this key, or [] if it's an empty list; |
|
228 raises KeyError if not found. |
|
229 """ |
|
230 try: |
|
231 list_ = super(MultiValueDict, self).__getitem__(key) |
|
232 except KeyError: |
|
233 raise MultiValueDictKeyError("Key %r not found in %r" % (key, self)) |
|
234 try: |
|
235 return list_[-1] |
|
236 except IndexError: |
|
237 return [] |
|
238 |
|
239 def __setitem__(self, key, value): |
|
240 super(MultiValueDict, self).__setitem__(key, [value]) |
|
241 |
|
242 def __copy__(self): |
|
243 return self.__class__(super(MultiValueDict, self).items()) |
|
244 |
|
245 def __deepcopy__(self, memo=None): |
|
246 import django.utils.copycompat as copy |
|
247 if memo is None: |
|
248 memo = {} |
|
249 result = self.__class__() |
|
250 memo[id(self)] = result |
|
251 for key, value in dict.items(self): |
|
252 dict.__setitem__(result, copy.deepcopy(key, memo), |
|
253 copy.deepcopy(value, memo)) |
|
254 return result |
|
255 |
|
256 def __getstate__(self): |
|
257 obj_dict = self.__dict__.copy() |
|
258 obj_dict['_data'] = dict([(k, self.getlist(k)) for k in self]) |
|
259 return obj_dict |
|
260 |
|
261 def __setstate__(self, obj_dict): |
|
262 data = obj_dict.pop('_data', {}) |
|
263 for k, v in data.items(): |
|
264 self.setlist(k, v) |
|
265 self.__dict__.update(obj_dict) |
|
266 |
|
267 def get(self, key, default=None): |
|
268 """ |
|
269 Returns the last data value for the passed key. If key doesn't exist |
|
270 or value is an empty list, then default is returned. |
|
271 """ |
|
272 try: |
|
273 val = self[key] |
|
274 except KeyError: |
|
275 return default |
|
276 if val == []: |
|
277 return default |
|
278 return val |
|
279 |
|
280 def getlist(self, key): |
|
281 """ |
|
282 Returns the list of values for the passed key. If key doesn't exist, |
|
283 then an empty list is returned. |
|
284 """ |
|
285 try: |
|
286 return super(MultiValueDict, self).__getitem__(key) |
|
287 except KeyError: |
|
288 return [] |
|
289 |
|
290 def setlist(self, key, list_): |
|
291 super(MultiValueDict, self).__setitem__(key, list_) |
|
292 |
|
293 def setdefault(self, key, default=None): |
|
294 if key not in self: |
|
295 self[key] = default |
|
296 return self[key] |
|
297 |
|
298 def setlistdefault(self, key, default_list=()): |
|
299 if key not in self: |
|
300 self.setlist(key, default_list) |
|
301 return self.getlist(key) |
|
302 |
|
303 def appendlist(self, key, value): |
|
304 """Appends an item to the internal list associated with key.""" |
|
305 self.setlistdefault(key, []) |
|
306 super(MultiValueDict, self).__setitem__(key, self.getlist(key) + [value]) |
|
307 |
|
308 def items(self): |
|
309 """ |
|
310 Returns a list of (key, value) pairs, where value is the last item in |
|
311 the list associated with the key. |
|
312 """ |
|
313 return [(key, self[key]) for key in self.keys()] |
|
314 |
|
315 def iteritems(self): |
|
316 """ |
|
317 Yields (key, value) pairs, where value is the last item in the list |
|
318 associated with the key. |
|
319 """ |
|
320 for key in self.keys(): |
|
321 yield (key, self[key]) |
|
322 |
|
323 def lists(self): |
|
324 """Returns a list of (key, list) pairs.""" |
|
325 return super(MultiValueDict, self).items() |
|
326 |
|
327 def iterlists(self): |
|
328 """Yields (key, list) pairs.""" |
|
329 return super(MultiValueDict, self).iteritems() |
|
330 |
|
331 def values(self): |
|
332 """Returns a list of the last value on every key list.""" |
|
333 return [self[key] for key in self.keys()] |
|
334 |
|
335 def itervalues(self): |
|
336 """Yield the last value on every key list.""" |
|
337 for key in self.iterkeys(): |
|
338 yield self[key] |
|
339 |
|
340 def copy(self): |
|
341 """Returns a copy of this object.""" |
|
342 return self.__deepcopy__() |
|
343 |
|
344 def update(self, *args, **kwargs): |
|
345 """ |
|
346 update() extends rather than replaces existing key lists. |
|
347 Also accepts keyword args. |
|
348 """ |
|
349 if len(args) > 1: |
|
350 raise TypeError("update expected at most 1 arguments, got %d" % len(args)) |
|
351 if args: |
|
352 other_dict = args[0] |
|
353 if isinstance(other_dict, MultiValueDict): |
|
354 for key, value_list in other_dict.lists(): |
|
355 self.setlistdefault(key, []).extend(value_list) |
|
356 else: |
|
357 try: |
|
358 for key, value in other_dict.items(): |
|
359 self.setlistdefault(key, []).append(value) |
|
360 except TypeError: |
|
361 raise ValueError("MultiValueDict.update() takes either a MultiValueDict or dictionary") |
|
362 for key, value in kwargs.iteritems(): |
|
363 self.setlistdefault(key, []).append(value) |
|
364 |
|
365 class DotExpandedDict(dict): |
|
366 """ |
|
367 A special dictionary constructor that takes a dictionary in which the keys |
|
368 may contain dots to specify inner dictionaries. It's confusing, but this |
|
369 example should make sense. |
|
370 |
|
371 >>> d = DotExpandedDict({'person.1.firstname': ['Simon'], \ |
|
372 'person.1.lastname': ['Willison'], \ |
|
373 'person.2.firstname': ['Adrian'], \ |
|
374 'person.2.lastname': ['Holovaty']}) |
|
375 >>> d |
|
376 {'person': {'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}} |
|
377 >>> d['person'] |
|
378 {'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}} |
|
379 >>> d['person']['1'] |
|
380 {'lastname': ['Willison'], 'firstname': ['Simon']} |
|
381 |
|
382 # Gotcha: Results are unpredictable if the dots are "uneven": |
|
383 >>> DotExpandedDict({'c.1': 2, 'c.2': 3, 'c': 1}) |
|
384 {'c': 1} |
|
385 """ |
|
386 def __init__(self, key_to_list_mapping): |
|
387 for k, v in key_to_list_mapping.items(): |
|
388 current = self |
|
389 bits = k.split('.') |
|
390 for bit in bits[:-1]: |
|
391 current = current.setdefault(bit, {}) |
|
392 # Now assign value to current position |
|
393 try: |
|
394 current[bits[-1]] = v |
|
395 except TypeError: # Special-case if current isn't a dict. |
|
396 current = {bits[-1]: v} |
|
397 |
|
398 class ImmutableList(tuple): |
|
399 """ |
|
400 A tuple-like object that raises useful errors when it is asked to mutate. |
|
401 |
|
402 Example:: |
|
403 |
|
404 >>> a = ImmutableList(range(5), warning="You cannot mutate this.") |
|
405 >>> a[3] = '4' |
|
406 Traceback (most recent call last): |
|
407 ... |
|
408 AttributeError: You cannot mutate this. |
|
409 """ |
|
410 |
|
411 def __new__(cls, *args, **kwargs): |
|
412 if 'warning' in kwargs: |
|
413 warning = kwargs['warning'] |
|
414 del kwargs['warning'] |
|
415 else: |
|
416 warning = 'ImmutableList object is immutable.' |
|
417 self = tuple.__new__(cls, *args, **kwargs) |
|
418 self.warning = warning |
|
419 return self |
|
420 |
|
421 def complain(self, *wargs, **kwargs): |
|
422 if isinstance(self.warning, Exception): |
|
423 raise self.warning |
|
424 else: |
|
425 raise AttributeError(self.warning) |
|
426 |
|
427 # All list mutation functions complain. |
|
428 __delitem__ = complain |
|
429 __delslice__ = complain |
|
430 __iadd__ = complain |
|
431 __imul__ = complain |
|
432 __setitem__ = complain |
|
433 __setslice__ = complain |
|
434 append = complain |
|
435 extend = complain |
|
436 insert = complain |
|
437 pop = complain |
|
438 remove = complain |
|
439 sort = complain |
|
440 reverse = complain |
|
441 |
|
442 class DictWrapper(dict): |
|
443 """ |
|
444 Wraps accesses to a dictionary so that certain values (those starting with |
|
445 the specified prefix) are passed through a function before being returned. |
|
446 The prefix is removed before looking up the real value. |
|
447 |
|
448 Used by the SQL construction code to ensure that values are correctly |
|
449 quoted before being used. |
|
450 """ |
|
451 def __init__(self, data, func, prefix): |
|
452 super(DictWrapper, self).__init__(data) |
|
453 self.func = func |
|
454 self.prefix = prefix |
|
455 |
|
456 def __getitem__(self, key): |
|
457 """ |
|
458 Retrieves the real value after stripping the prefix string (if |
|
459 present). If the prefix is present, pass the value through self.func |
|
460 before returning, otherwise return the raw value. |
|
461 """ |
|
462 if key.startswith(self.prefix): |
|
463 use_func = True |
|
464 key = key[len(self.prefix):] |
|
465 else: |
|
466 use_func = False |
|
467 value = super(DictWrapper, self).__getitem__(key) |
|
468 if use_func: |
|
469 return self.func(value) |
|
470 return value |
|
471 |