|
29
|
1 |
from types import GeneratorType |
|
|
2 |
|
|
|
3 |
from django.utils.copycompat import deepcopy |
|
|
4 |
|
|
|
5 |
|
|
0
|
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 |
|
|
29
|
40 |
def iteritems(self): |
|
|
41 |
seen = set() |
|
0
|
42 |
for dict_ in self.dicts: |
|
29
|
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()) |
|
0
|
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 |
|
29
|
74 |
__iter__ = iterkeys |
|
0
|
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 = {} |
|
29
|
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) |
|
0
|
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): |
|
29
|
111 |
if key not in self: |
|
|
112 |
self.keyOrder.append(key) |
|
0
|
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): |
|
29
|
120 |
return iter(self.keyOrder) |
|
0
|
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: |
|
29
|
141 |
yield key, self[key] |
|
0
|
142 |
|
|
|
143 |
def keys(self): |
|
|
144 |
return self.keyOrder[:] |
|
|
145 |
|
|
|
146 |
def iterkeys(self): |
|
|
147 |
return iter(self.keyOrder) |
|
|
148 |
|
|
|
149 |
def values(self): |
|
29
|
150 |
return map(self.__getitem__, self.keyOrder) |
|
0
|
151 |
|
|
|
152 |
def itervalues(self): |
|
|
153 |
for key in self.keyOrder: |
|
29
|
154 |
yield self[key] |
|
0
|
155 |
|
|
|
156 |
def update(self, dict_): |
|
29
|
157 |
for k, v in dict_.iteritems(): |
|
|
158 |
self[k] = v |
|
0
|
159 |
|
|
|
160 |
def setdefault(self, key, default): |
|
29
|
161 |
if key not in self: |
|
0
|
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: |
|
29
|
233 |
raise MultiValueDictKeyError("Key %r not found in %r" % (key, self)) |
|
0
|
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): |
|
29
|
246 |
import django.utils.copycompat as copy |
|
0
|
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 |
|
29
|
255 |
|
|
0
|
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 |
|
29
|
260 |
|
|
0
|
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) |
|
29
|
266 |
|
|
0
|
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()] |
|
29
|
334 |
|
|
0
|
335 |
def itervalues(self): |
|
|
336 |
"""Yield the last value on every key list.""" |
|
|
337 |
for key in self.iterkeys(): |
|
|
338 |
yield self[key] |
|
29
|
339 |
|
|
0
|
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: |
|
29
|
350 |
raise TypeError("update expected at most 1 arguments, got %d" % len(args)) |
|
0
|
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: |
|
29
|
361 |
raise ValueError("MultiValueDict.update() takes either a MultiValueDict or dictionary") |
|
0
|
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: |
|
29
|
425 |
raise AttributeError(self.warning) |
|
0
|
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 |
|