app/django/utils/datastructures.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     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 __init__(self, data=None):
       
    58         if data is None:
       
    59             data = {}
       
    60         super(SortedDict, self).__init__(data)
       
    61         if isinstance(data, dict):
       
    62             self.keyOrder = data.keys()
       
    63         else:
       
    64             self.keyOrder = []
       
    65             for key, value in data:
       
    66                 if key not in self.keyOrder:
       
    67                     self.keyOrder.append(key)
       
    68 
       
    69     def __deepcopy__(self, memo):
       
    70         from copy import deepcopy
       
    71         return self.__class__([(key, deepcopy(value, memo))
       
    72                                for key, value in self.iteritems()])
       
    73 
       
    74     def __setitem__(self, key, value):
       
    75         super(SortedDict, self).__setitem__(key, value)
       
    76         if key not in self.keyOrder:
       
    77             self.keyOrder.append(key)
       
    78 
       
    79     def __delitem__(self, key):
       
    80         super(SortedDict, self).__delitem__(key)
       
    81         self.keyOrder.remove(key)
       
    82 
       
    83     def __iter__(self):
       
    84         for k in self.keyOrder:
       
    85             yield k
       
    86 
       
    87     def pop(self, k, *args):
       
    88         result = super(SortedDict, self).pop(k, *args)
       
    89         try:
       
    90             self.keyOrder.remove(k)
       
    91         except ValueError:
       
    92             # Key wasn't in the dictionary in the first place. No problem.
       
    93             pass
       
    94         return result
       
    95 
       
    96     def popitem(self):
       
    97         result = super(SortedDict, self).popitem()
       
    98         self.keyOrder.remove(result[0])
       
    99         return result
       
   100 
       
   101     def items(self):
       
   102         return zip(self.keyOrder, self.values())
       
   103 
       
   104     def iteritems(self):
       
   105         for key in self.keyOrder:
       
   106             yield key, super(SortedDict, self).__getitem__(key)
       
   107 
       
   108     def keys(self):
       
   109         return self.keyOrder[:]
       
   110 
       
   111     def iterkeys(self):
       
   112         return iter(self.keyOrder)
       
   113 
       
   114     def values(self):
       
   115         return [super(SortedDict, self).__getitem__(k) for k in self.keyOrder]
       
   116 
       
   117     def itervalues(self):
       
   118         for key in self.keyOrder:
       
   119             yield super(SortedDict, self).__getitem__(key)
       
   120 
       
   121     def update(self, dict_):
       
   122         for k, v in dict_.items():
       
   123             self.__setitem__(k, v)
       
   124 
       
   125     def setdefault(self, key, default):
       
   126         if key not in self.keyOrder:
       
   127             self.keyOrder.append(key)
       
   128         return super(SortedDict, self).setdefault(key, default)
       
   129 
       
   130     def value_for_index(self, index):
       
   131         """Returns the value of the item at the given zero-based index."""
       
   132         return self[self.keyOrder[index]]
       
   133 
       
   134     def insert(self, index, key, value):
       
   135         """Inserts the key, value pair before the item with the given index."""
       
   136         if key in self.keyOrder:
       
   137             n = self.keyOrder.index(key)
       
   138             del self.keyOrder[n]
       
   139             if n < index:
       
   140                 index -= 1
       
   141         self.keyOrder.insert(index, key)
       
   142         super(SortedDict, self).__setitem__(key, value)
       
   143 
       
   144     def copy(self):
       
   145         """Returns a copy of this object."""
       
   146         # This way of initializing the copy means it works for subclasses, too.
       
   147         obj = self.__class__(self)
       
   148         obj.keyOrder = self.keyOrder[:]
       
   149         return obj
       
   150 
       
   151     def __repr__(self):
       
   152         """
       
   153         Replaces the normal dict.__repr__ with a version that returns the keys
       
   154         in their sorted order.
       
   155         """
       
   156         return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()])
       
   157 
       
   158     def clear(self):
       
   159         super(SortedDict, self).clear()
       
   160         self.keyOrder = []
       
   161 
       
   162 class MultiValueDictKeyError(KeyError):
       
   163     pass
       
   164 
       
   165 class MultiValueDict(dict):
       
   166     """
       
   167     A subclass of dictionary customized to handle multiple values for the
       
   168     same key.
       
   169 
       
   170     >>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']})
       
   171     >>> d['name']
       
   172     'Simon'
       
   173     >>> d.getlist('name')
       
   174     ['Adrian', 'Simon']
       
   175     >>> d.get('lastname', 'nonexistent')
       
   176     'nonexistent'
       
   177     >>> d.setlist('lastname', ['Holovaty', 'Willison'])
       
   178 
       
   179     This class exists to solve the irritating problem raised by cgi.parse_qs,
       
   180     which returns a list for every key, even though most Web forms submit
       
   181     single name-value pairs.
       
   182     """
       
   183     def __init__(self, key_to_list_mapping=()):
       
   184         super(MultiValueDict, self).__init__(key_to_list_mapping)
       
   185 
       
   186     def __repr__(self):
       
   187         return "<%s: %s>" % (self.__class__.__name__,
       
   188                              super(MultiValueDict, self).__repr__())
       
   189 
       
   190     def __getitem__(self, key):
       
   191         """
       
   192         Returns the last data value for this key, or [] if it's an empty list;
       
   193         raises KeyError if not found.
       
   194         """
       
   195         try:
       
   196             list_ = super(MultiValueDict, self).__getitem__(key)
       
   197         except KeyError:
       
   198             raise MultiValueDictKeyError, "Key %r not found in %r" % (key, self)
       
   199         try:
       
   200             return list_[-1]
       
   201         except IndexError:
       
   202             return []
       
   203 
       
   204     def __setitem__(self, key, value):
       
   205         super(MultiValueDict, self).__setitem__(key, [value])
       
   206 
       
   207     def __copy__(self):
       
   208         return self.__class__(super(MultiValueDict, self).items())
       
   209 
       
   210     def __deepcopy__(self, memo=None):
       
   211         import copy
       
   212         if memo is None:
       
   213             memo = {}
       
   214         result = self.__class__()
       
   215         memo[id(self)] = result
       
   216         for key, value in dict.items(self):
       
   217             dict.__setitem__(result, copy.deepcopy(key, memo),
       
   218                              copy.deepcopy(value, memo))
       
   219         return result
       
   220 
       
   221     def get(self, key, default=None):
       
   222         """
       
   223         Returns the last data value for the passed key. If key doesn't exist
       
   224         or value is an empty list, then default is returned.
       
   225         """
       
   226         try:
       
   227             val = self[key]
       
   228         except KeyError:
       
   229             return default
       
   230         if val == []:
       
   231             return default
       
   232         return val
       
   233 
       
   234     def getlist(self, key):
       
   235         """
       
   236         Returns the list of values for the passed key. If key doesn't exist,
       
   237         then an empty list is returned.
       
   238         """
       
   239         try:
       
   240             return super(MultiValueDict, self).__getitem__(key)
       
   241         except KeyError:
       
   242             return []
       
   243 
       
   244     def setlist(self, key, list_):
       
   245         super(MultiValueDict, self).__setitem__(key, list_)
       
   246 
       
   247     def setdefault(self, key, default=None):
       
   248         if key not in self:
       
   249             self[key] = default
       
   250         return self[key]
       
   251 
       
   252     def setlistdefault(self, key, default_list=()):
       
   253         if key not in self:
       
   254             self.setlist(key, default_list)
       
   255         return self.getlist(key)
       
   256 
       
   257     def appendlist(self, key, value):
       
   258         """Appends an item to the internal list associated with key."""
       
   259         self.setlistdefault(key, [])
       
   260         super(MultiValueDict, self).__setitem__(key, self.getlist(key) + [value])
       
   261 
       
   262     def items(self):
       
   263         """
       
   264         Returns a list of (key, value) pairs, where value is the last item in
       
   265         the list associated with the key.
       
   266         """
       
   267         return [(key, self[key]) for key in self.keys()]
       
   268 
       
   269     def lists(self):
       
   270         """Returns a list of (key, list) pairs."""
       
   271         return super(MultiValueDict, self).items()
       
   272 
       
   273     def values(self):
       
   274         """Returns a list of the last value on every key list."""
       
   275         return [self[key] for key in self.keys()]
       
   276 
       
   277     def copy(self):
       
   278         """Returns a copy of this object."""
       
   279         return self.__deepcopy__()
       
   280 
       
   281     def update(self, *args, **kwargs):
       
   282         """
       
   283         update() extends rather than replaces existing key lists.
       
   284         Also accepts keyword args.
       
   285         """
       
   286         if len(args) > 1:
       
   287             raise TypeError, "update expected at most 1 arguments, got %d" % len(args)
       
   288         if args:
       
   289             other_dict = args[0]
       
   290             if isinstance(other_dict, MultiValueDict):
       
   291                 for key, value_list in other_dict.lists():
       
   292                     self.setlistdefault(key, []).extend(value_list)
       
   293             else:
       
   294                 try:
       
   295                     for key, value in other_dict.items():
       
   296                         self.setlistdefault(key, []).append(value)
       
   297                 except TypeError:
       
   298                     raise ValueError, "MultiValueDict.update() takes either a MultiValueDict or dictionary"
       
   299         for key, value in kwargs.iteritems():
       
   300             self.setlistdefault(key, []).append(value)
       
   301 
       
   302 class DotExpandedDict(dict):
       
   303     """
       
   304     A special dictionary constructor that takes a dictionary in which the keys
       
   305     may contain dots to specify inner dictionaries. It's confusing, but this
       
   306     example should make sense.
       
   307 
       
   308     >>> d = DotExpandedDict({'person.1.firstname': ['Simon'], \
       
   309             'person.1.lastname': ['Willison'], \
       
   310             'person.2.firstname': ['Adrian'], \
       
   311             'person.2.lastname': ['Holovaty']})
       
   312     >>> d
       
   313     {'person': {'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}}
       
   314     >>> d['person']
       
   315     {'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}
       
   316     >>> d['person']['1']
       
   317     {'lastname': ['Willison'], 'firstname': ['Simon']}
       
   318 
       
   319     # Gotcha: Results are unpredictable if the dots are "uneven":
       
   320     >>> DotExpandedDict({'c.1': 2, 'c.2': 3, 'c': 1})
       
   321     {'c': 1}
       
   322     """
       
   323     def __init__(self, key_to_list_mapping):
       
   324         for k, v in key_to_list_mapping.items():
       
   325             current = self
       
   326             bits = k.split('.')
       
   327             for bit in bits[:-1]:
       
   328                 current = current.setdefault(bit, {})
       
   329             # Now assign value to current position
       
   330             try:
       
   331                 current[bits[-1]] = v
       
   332             except TypeError: # Special-case if current isn't a dict.
       
   333                 current = {bits[-1]: v}
       
   334 
       
   335 class FileDict(dict):
       
   336     """
       
   337     A dictionary used to hold uploaded file contents. The only special feature
       
   338     here is that repr() of this object won't dump the entire contents of the
       
   339     file to the output. A handy safeguard for a large file upload.
       
   340     """
       
   341     def __repr__(self):
       
   342         if 'content' in self:
       
   343             d = dict(self, content='<omitted>')
       
   344             return dict.__repr__(d)
       
   345         return dict.__repr__(self)