Django

Code

root/django/trunk/django/utils/datastructures.py

Revision 10241, 13.8 kB (checked in by jacob, 3 months ago)

Fixed #8847, #10370: added some missing methods to MultiValueDict? after [8399]. Thanks, James Turk and rfk.

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