0001# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
0002# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
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    #@classmethod
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    #@classmethod
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            # fs.list can be None when there's nothing to parse
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                # We do this to not clobber any lists that are
0127                # *actual* values in this dictionary:
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    ## All the iteration:
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            # decode FieldStorage's field name and filename
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        # FIXME: should this do the FieldStorage stuff too?
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    ## All the iteration:
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    # Inherited:
0459    # getone
0460    # mixed
0461    # dict_of_lists
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