web/lib/django/utils/datastructures.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     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