0001
0002
0003"""
0004Gives a multi-value dictionary object (MultiDict) plus several wrappers
0005"""
0006import cgi
0007import copy
0008import sys
0009from webob.util.dictmixin import DictMixin
0010try:
0011 reversed
0012except NameError:
0013 from webob.util.reversed import reversed
0014
0015__all__ = ['MultiDict', 'UnicodeMultiDict', 'NestedMultiDict', 'NoVars']
0016
0017class MultiDict(DictMixin):
0018
0019 """
0020 An ordered dictionary that can have multiple values for each key.
0021 Adds the methods getall, getone, mixed, and add to the normal
0022 dictionary interface.
0023 """
0024
0025 def __init__(self, *args, **kw):
0026 if len(args) > 1:
0027 raise TypeError(
0028 "MultiDict can only be called with one positional argument")
0029 if args:
0030 if hasattr(args[0], 'iteritems'):
0031 items = list(args[0].iteritems())
0032 elif hasattr(args[0], 'items'):
0033 items = args[0].items()
0034 else:
0035 items = list(args[0])
0036 self._items = items
0037 else:
0038 self._items = []
0039 self._items.extend(kw.iteritems())
0040
0041
0042 def view_list(cls, lst):
0043 """
0044 Create a dict that is a view on the given list
0045 """
0046 if not isinstance(lst, list):
0047 raise TypeError(
0048 "%s.view_list(obj) takes only actual list objects, not %r"
0049 % (cls.__name__, lst))
0050 obj = cls()
0051 obj._items = lst
0052 return obj
0053
0054 view_list = classmethod(view_list)
0055
0056
0057 def from_fieldstorage(cls, fs):
0058 """
0059 Create a dict from a cgi.FieldStorage instance
0060 """
0061 obj = cls()
0062 if fs.list:
0063
0064 for field in fs.list:
0065 if field.filename:
0066 obj.add(field.name, field)
0067 else:
0068 obj.add(field.name, field.value)
0069 return obj
0070
0071 from_fieldstorage = classmethod(from_fieldstorage)
0072
0073 def __getitem__(self, key):
0074 for k, v in reversed(self._items):
0075 if k == key:
0076 return v
0077 raise KeyError(key)
0078
0079 def __setitem__(self, key, value):
0080 try:
0081 del self[key]
0082 except KeyError:
0083 pass
0084 self._items.append((key, value))
0085
0086 def add(self, key, value):
0087 """
0088 Add the key and value, not overwriting any previous value.
0089 """
0090 self._items.append((key, value))
0091
0092 def getall(self, key):
0093 """
0094 Return a list of all values matching the key (may be an empty list)
0095 """
0096 result = []
0097 for k, v in self._items:
0098 if key == k:
0099 result.append(v)
0100 return result
0101
0102 def getone(self, key):
0103 """
0104 Get one value matching the key, raising a KeyError if multiple
0105 values were found.
0106 """
0107 v = self.getall(key)
0108 if not v:
0109 raise KeyError('Key not found: %r' % key)
0110 if len(v) > 1:
0111 raise KeyError('Multiple values match %r: %r' % (key, v))
0112 return v[0]
0113
0114 def mixed(self):
0115 """
0116 Returns a dictionary where the values are either single
0117 values, or a list of values when a key/value appears more than
0118 once in this dictionary. This is similar to the kind of
0119 dictionary often used to represent the variables in a web
0120 request.
0121 """
0122 result = {}
0123 multi = {}
0124 for key, value in self.iteritems():
0125 if key in result:
0126
0127
0128 if key in multi:
0129 result[key].append(value)
0130 else:
0131 result[key] = [result[key], value]
0132 multi[key] = None
0133 else:
0134 result[key] = value
0135 return result
0136
0137 def dict_of_lists(self):
0138 """
0139 Returns a dictionary where each key is associated with a
0140 list of values.
0141 """
0142 result = {}
0143 for key, value in self.iteritems():
0144 if key in result:
0145 result[key].append(value)
0146 else:
0147 result[key] = [value]
0148 return result
0149
0150 def __delitem__(self, key):
0151 items = self._items
0152 found = False
0153 for i in range(len(items)-1, -1, -1):
0154 if items[i][0] == key:
0155 del items[i]
0156 found = True
0157 if not found:
0158 raise KeyError(key)
0159
0160 def __contains__(self, key):
0161 for k, v in self._items:
0162 if k == key:
0163 return True
0164 return False
0165
0166 has_key = __contains__
0167
0168 def clear(self):
0169 self._items = []
0170
0171 def copy(self):
0172 return self.__class__(self)
0173
0174 def setdefault(self, key, default=None):
0175 for k, v in self._items:
0176 if key == k:
0177 return v
0178 self._items.append((key, default))
0179 return default
0180
0181 def pop(self, key, *args):
0182 if len(args) > 1:
0183 raise TypeError, "pop expected at most 2 arguments, got " + repr(1 + len(args))
0185 for i in range(len(self._items)):
0186 if self._items[i][0] == key:
0187 v = self._items[i][1]
0188 del self._items[i]
0189 return v
0190 if args:
0191 return args[0]
0192 else:
0193 raise KeyError(key)
0194
0195 def popitem(self):
0196 return self._items.pop()
0197
0198 def update(self, other=None, **kwargs):
0199 if other is None:
0200 pass
0201 elif hasattr(other, 'items'):
0202 self._items.extend(other.items())
0203 elif hasattr(other, 'keys'):
0204 for k in other.keys():
0205 self._items.append((k, other[k]))
0206 else:
0207 for k, v in other:
0208 self._items.append((k, v))
0209 if kwargs:
0210 self.update(kwargs)
0211
0212 def __repr__(self):
0213 items = ', '.join(['(%r, %r)' % v for v in self.iteritems()])
0214 return '%s([%s])' % (self.__class__.__name__, items)
0215
0216 def __len__(self):
0217 return len(self._items)
0218
0219
0220
0221
0222
0223 def keys(self):
0224 return [k for k, v in self._items]
0225
0226 def iterkeys(self):
0227 for k, v in self._items:
0228 yield k
0229
0230 __iter__ = iterkeys
0231
0232 def items(self):
0233 return self._items[:]
0234
0235 def iteritems(self):
0236 return iter(self._items)
0237
0238 def values(self):
0239 return [v for k, v in self._items]
0240
0241 def itervalues(self):
0242 for k, v in self._items:
0243 yield v
0244
0245class UnicodeMultiDict(DictMixin):
0246 """
0247 A MultiDict wrapper that decodes returned values to unicode on the
0248 fly. Decoding is not applied to assigned values.
0249
0250 The key/value contents are assumed to be ``str``/``strs`` or
0251 ``str``/``FieldStorages`` (as is returned by the ``paste.request.parse_``
0252 functions).
0253
0254 Can optionally also decode keys when the ``decode_keys`` argument is
0255 True.
0256
0257 ``FieldStorage`` instances are cloned, and the clone's ``filename``
0258 variable is decoded. Its ``name`` variable is decoded when ``decode_keys``
0259 is enabled.
0260
0261 """
0262 def __init__(self, multi=None, encoding=None, errors='strict',
0263 decode_keys=False):
0264 self.multi = multi
0265 if encoding is None:
0266 encoding = sys.getdefaultencoding()
0267 self.encoding = encoding
0268 self.errors = errors
0269 self.decode_keys = decode_keys
0270
0271 def _decode_key(self, key):
0272 if self.decode_keys:
0273 try:
0274 key = key.decode(self.encoding, self.errors)
0275 except AttributeError:
0276 pass
0277 return key
0278
0279 def _encode_key(self, key):
0280 if self.decode_keys and isinstance(key, unicode):
0281 return key.encode(self.encoding, self.errors)
0282 return key
0283
0284 def _decode_value(self, value):
0285 """
0286 Decode the specified value to unicode. Assumes value is a ``str`` or
0287 `FieldStorage`` object.
0288
0289 ``FieldStorage`` objects are specially handled.
0290 """
0291 if isinstance(value, cgi.FieldStorage):
0292
0293 value = copy.copy(value)
0294 if self.decode_keys:
0295 value.name = value.name.decode(self.encoding, self.errors)
0296 if value.filename:
0297 value.filename = value.filename.decode(self.encoding,
0298 self.errors)
0299 elif not isinstance(value, unicode):
0300 try:
0301 value = value.decode(self.encoding, self.errors)
0302 except AttributeError:
0303 pass
0304 return value
0305
0306 def _encode_value(self, value):
0307
0308 if isinstance(value, unicode):
0309 value = value.encode(self.encoding, self.errors)
0310 return value
0311
0312 def __getitem__(self, key):
0313 return self._decode_value(self.multi.__getitem__(self._encode_key(key)))
0314
0315 def __setitem__(self, key, value):
0316 self.multi.__setitem__(self._encode_key(key), self._encode_value(value))
0317
0318 def add(self, key, value):
0319 """
0320 Add the key and value, not overwriting any previous value.
0321 """
0322 self.multi.add(self._encode_key(key), self._encode_value(value))
0323
0324 def getall(self, key):
0325 """
0326 Return a list of all values matching the key (may be an empty list)
0327 """
0328 return [self._decode_value(v) for v in self.multi.getall(self._encode_key(key))]
0329
0330 def getone(self, key):
0331 """
0332 Get one value matching the key, raising a KeyError if multiple
0333 values were found.
0334 """
0335 return self._decode_value(self.multi.getone(self._encode_key(key)))
0336
0337 def mixed(self):
0338 """
0339 Returns a dictionary where the values are either single
0340 values, or a list of values when a key/value appears more than
0341 once in this dictionary. This is similar to the kind of
0342 dictionary often used to represent the variables in a web
0343 request.
0344 """
0345 unicode_mixed = {}
0346 for key, value in self.multi.mixed().iteritems():
0347 if isinstance(value, list):
0348 value = [self._decode_value(value) for value in value]
0349 else:
0350 value = self._decode_value(value)
0351 unicode_mixed[self._decode_key(key)] = value
0352 return unicode_mixed
0353
0354 def dict_of_lists(self):
0355 """
0356 Returns a dictionary where each key is associated with a
0357 list of values.
0358 """
0359 unicode_dict = {}
0360 for key, value in self.multi.dict_of_lists().iteritems():
0361 value = [self._decode_value(value) for value in value]
0362 unicode_dict[self._decode_key(key)] = value
0363 return unicode_dict
0364
0365 def __delitem__(self, key):
0366 self.multi.__delitem__(self._encode_key(key))
0367
0368 def __contains__(self, key):
0369 return self.multi.__contains__(self._encode_key(key))
0370
0371 has_key = __contains__
0372
0373 def clear(self):
0374 self.multi.clear()
0375
0376 def copy(self):
0377 return UnicodeMultiDict(self.multi.copy(), self.encoding, self.errors)
0378
0379 def setdefault(self, key, default=None):
0380 return self._decode_value(self.multi.setdefault(self._encode_key(key), self._encode_value(default)))
0381
0382 def pop(self, key, *args):
0383 return self._decode_value(self.multi.pop(self._encode_key(key), *args))
0384
0385 def popitem(self):
0386 k, v = self.multi.popitem()
0387 return (self._decode_key(k), self._decode_value(v))
0388
0389 def __repr__(self):
0390 items = ', '.join(['(%r, %r)' % v for v in self.items()])
0391 return '%s([%s])' % (self.__class__.__name__, items)
0392
0393 def __len__(self):
0394 return self.multi.__len__()
0395
0396
0397
0398
0399
0400 def keys(self):
0401 return [self._decode_key(k) for k in self.multi.iterkeys()]
0402
0403 def iterkeys(self):
0404 for k in self.multi.iterkeys():
0405 yield self._decode_key(k)
0406
0407 __iter__ = iterkeys
0408
0409 def items(self):
0410 return [(self._decode_key(k), self._decode_value(v))
0411 for k, v in self.multi.iteritems()]
0412
0413 def iteritems(self):
0414 for k, v in self.multi.iteritems():
0415 yield (self._decode_key(k), self._decode_value(v))
0416
0417 def values(self):
0418 return [self._decode_value(v) for v in self.multi.itervalues()]
0419
0420 def itervalues(self):
0421 for v in self.multi.itervalues():
0422 yield self._decode_value(v)
0423
0424_dummy = object()
0425
0426class NestedMultiDict(MultiDict):
0427 """
0428 Wraps several MultiDict objects, treating it as one large MultiDict
0429 """
0430
0431 def __init__(self, *dicts):
0432 self.dicts = dicts
0433
0434 def __getitem__(self, key):
0435 for d in self.dicts:
0436 value = d.get(key, _dummy)
0437 if value is not _dummy:
0438 return value
0439 raise KeyError(key)
0440
0441 def _readonly(self, *args, **kw):
0442 raise KeyError("NestedMultiDict objects are read-only")
0443 __setitem__ = _readonly
0444 add = _readonly
0445 __delitem__ = _readonly
0446 clear = _readonly
0447 setdefault = _readonly
0448 pop = _readonly
0449 popitem = _readonly
0450 update = _readonly
0451
0452 def getall(self, key):
0453 result = []
0454 for d in self.dicts:
0455 result.extend(d.getall(key))
0456 return result
0457
0458
0459
0460
0461
0462
0463 def copy(self):
0464 return MultiDict(self)
0465
0466 def __contains__(self, key):
0467 for d in self.dicts:
0468 if key in d:
0469 return True
0470 return False
0471
0472 has_key = __contains__
0473
0474 def __len__(self):
0475 v = 0
0476 for d in self.dicts:
0477 v += len(d)
0478 return v
0479
0480 def __nonzero__(self):
0481 for d in self.dicts:
0482 if d:
0483 return True
0484 return False
0485
0486 def items(self):
0487 return list(self.iteritems())
0488
0489 def iteritems(self):
0490 for d in self.dicts:
0491 for item in d.iteritems():
0492 yield item
0493
0494 def values(self):
0495 return list(self.itervalue