Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/webob/multidict.py : 33%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# (c) 2005 Ian Bicking and contributors; written for Paste
2# (http://pythonpaste.org) Licensed under the MIT license:
3# http://www.opensource.org/licenses/mit-license.php
4"""
5Gives a multi-value dictionary object (MultiDict) plus several wrappers
6"""
7import binascii
8import warnings
10from webob.compat import (
11 MutableMapping,
12 PY2,
13 iteritems_,
14 itervalues_,
15 url_encode,
16 )
18__all__ = ['MultiDict', 'NestedMultiDict', 'NoVars', 'GetDict']
20class MultiDict(MutableMapping):
21 """
22 An ordered dictionary that can have multiple values for each key.
23 Adds the methods getall, getone, mixed and extend and add to the normal
24 dictionary interface.
25 """
27 def __init__(self, *args, **kw):
28 if len(args) > 1:
29 raise TypeError("MultiDict can only be called with one positional "
30 "argument")
31 if args:
32 if hasattr(args[0], 'iteritems'):
33 items = list(args[0].iteritems())
34 elif hasattr(args[0], 'items'):
35 items = list(args[0].items())
36 else:
37 items = list(args[0])
38 self._items = items
39 else:
40 self._items = []
41 if kw:
42 self._items.extend(kw.items())
44 @classmethod
45 def view_list(cls, lst):
46 """
47 Create a dict that is a view on the given list
48 """
49 if not isinstance(lst, list):
50 raise TypeError(
51 "%s.view_list(obj) takes only actual list objects, not %r"
52 % (cls.__name__, lst))
53 obj = cls()
54 obj._items = lst
55 return obj
57 @classmethod
58 def from_fieldstorage(cls, fs):
59 """
60 Create a dict from a cgi.FieldStorage instance
61 """
62 obj = cls()
63 # fs.list can be None when there's nothing to parse
64 for field in fs.list or ():
65 charset = field.type_options.get('charset', 'utf8')
66 transfer_encoding = field.headers.get('Content-Transfer-Encoding', None)
67 supported_transfer_encoding = {
68 'base64' : binascii.a2b_base64,
69 'quoted-printable' : binascii.a2b_qp
70 }
71 if not PY2:
72 if charset == 'utf8':
73 decode = lambda b: b
74 else:
75 decode = lambda b: b.encode('utf8').decode(charset)
76 else:
77 decode = lambda b: b.decode(charset)
78 if field.filename:
79 field.filename = decode(field.filename)
80 obj.add(field.name, field)
81 else:
82 value = field.value
83 if transfer_encoding in supported_transfer_encoding:
84 if not PY2:
85 # binascii accepts bytes
86 value = value.encode('utf8')
87 value = supported_transfer_encoding[transfer_encoding](value)
88 if not PY2:
89 # binascii returns bytes
90 value = value.decode('utf8')
91 obj.add(field.name, decode(value))
92 return obj
94 def __getitem__(self, key):
95 for k, v in reversed(self._items):
96 if k == key:
97 return v
98 raise KeyError(key)
100 def __setitem__(self, key, value):
101 try:
102 del self[key]
103 except KeyError:
104 pass
105 self._items.append((key, value))
107 def add(self, key, value):
108 """
109 Add the key and value, not overwriting any previous value.
110 """
111 self._items.append((key, value))
113 def getall(self, key):
114 """
115 Return a list of all values matching the key (may be an empty list)
116 """
117 return [v for k, v in self._items if k == key]
119 def getone(self, key):
120 """
121 Get one value matching the key, raising a KeyError if multiple
122 values were found.
123 """
124 v = self.getall(key)
125 if not v:
126 raise KeyError('Key not found: %r' % key)
127 if len(v) > 1:
128 raise KeyError('Multiple values match %r: %r' % (key, v))
129 return v[0]
131 def mixed(self):
132 """
133 Returns a dictionary where the values are either single
134 values, or a list of values when a key/value appears more than
135 once in this dictionary. This is similar to the kind of
136 dictionary often used to represent the variables in a web
137 request.
138 """
139 result = {}
140 multi = {}
141 for key, value in self.items():
142 if key in result:
143 # We do this to not clobber any lists that are
144 # *actual* values in this dictionary:
145 if key in multi:
146 result[key].append(value)
147 else:
148 result[key] = [result[key], value]
149 multi[key] = None
150 else:
151 result[key] = value
152 return result
154 def dict_of_lists(self):
155 """
156 Returns a dictionary where each key is associated with a list of values.
157 """
158 r = {}
159 for key, val in self.items():
160 r.setdefault(key, []).append(val)
161 return r
163 def __delitem__(self, key):
164 items = self._items
165 found = False
166 for i in range(len(items)-1, -1, -1):
167 if items[i][0] == key:
168 del items[i]
169 found = True
170 if not found:
171 raise KeyError(key)
173 def __contains__(self, key):
174 for k, v in self._items:
175 if k == key:
176 return True
177 return False
179 has_key = __contains__
181 def clear(self):
182 del self._items[:]
184 def copy(self):
185 return self.__class__(self)
187 def setdefault(self, key, default=None):
188 for k, v in self._items:
189 if key == k:
190 return v
191 self._items.append((key, default))
192 return default
194 def pop(self, key, *args):
195 if len(args) > 1:
196 raise TypeError("pop expected at most 2 arguments, got %s"
197 % repr(1 + len(args)))
198 for i in range(len(self._items)):
199 if self._items[i][0] == key:
200 v = self._items[i][1]
201 del self._items[i]
202 return v
203 if args:
204 return args[0]
205 else:
206 raise KeyError(key)
208 def popitem(self):
209 return self._items.pop()
211 def update(self, *args, **kw):
212 if args:
213 lst = args[0]
214 if len(lst) != len(dict(lst)):
215 # this does not catch the cases where we overwrite existing
216 # keys, but those would produce too many warning
217 msg = ("Behavior of MultiDict.update() has changed "
218 "and overwrites duplicate keys. Consider using .extend()"
219 )
220 warnings.warn(msg, UserWarning, stacklevel=2)
221 MutableMapping.update(self, *args, **kw)
223 def extend(self, other=None, **kwargs):
224 if other is None:
225 pass
226 elif hasattr(other, 'items'):
227 self._items.extend(other.items())
228 elif hasattr(other, 'keys'):
229 for k in other.keys():
230 self._items.append((k, other[k]))
231 else:
232 for k, v in other:
233 self._items.append((k, v))
234 if kwargs:
235 self.update(kwargs)
237 def __repr__(self):
238 items = map('(%r, %r)'.__mod__, _hide_passwd(self.items()))
239 return '%s([%s])' % (self.__class__.__name__, ', '.join(items))
241 def __len__(self):
242 return len(self._items)
244 ##
245 ## All the iteration:
246 ##
248 def iterkeys(self):
249 for k, v in self._items:
250 yield k
251 if PY2:
252 def keys(self):
253 return [k for k, v in self._items]
254 else:
255 keys = iterkeys
257 __iter__ = iterkeys
259 def iteritems(self):
260 return iter(self._items)
262 if PY2:
263 def items(self):
264 return self._items[:]
265 else:
266 items = iteritems
268 def itervalues(self):
269 for k, v in self._items:
270 yield v
272 if PY2:
273 def values(self):
274 return [v for k, v in self._items]
275 else:
276 values = itervalues
278_dummy = object()
280class GetDict(MultiDict):
281# def __init__(self, data, tracker, encoding, errors):
282# d = lambda b: b.decode(encoding, errors)
283# data = [(d(k), d(v)) for k,v in data]
284 def __init__(self, data, env):
285 self.env = env
286 MultiDict.__init__(self, data)
287 def on_change(self):
288 e = lambda t: t.encode('utf8')
289 data = [(e(k), e(v)) for k,v in self.items()]
290 qs = url_encode(data)
291 self.env['QUERY_STRING'] = qs
292 self.env['webob._parsed_query_vars'] = (self, qs)
293 def __setitem__(self, key, value):
294 MultiDict.__setitem__(self, key, value)
295 self.on_change()
296 def add(self, key, value):
297 MultiDict.add(self, key, value)
298 self.on_change()
299 def __delitem__(self, key):
300 MultiDict.__delitem__(self, key)
301 self.on_change()
302 def clear(self):
303 MultiDict.clear(self)
304 self.on_change()
305 def setdefault(self, key, default=None):
306 result = MultiDict.setdefault(self, key, default)
307 self.on_change()
308 return result
309 def pop(self, key, *args):
310 result = MultiDict.pop(self, key, *args)
311 self.on_change()
312 return result
313 def popitem(self):
314 result = MultiDict.popitem(self)
315 self.on_change()
316 return result
317 def update(self, *args, **kwargs):
318 MultiDict.update(self, *args, **kwargs)
319 self.on_change()
320 def extend(self, *args, **kwargs):
321 MultiDict.extend(self, *args, **kwargs)
322 self.on_change()
323 def __repr__(self):
324 items = map('(%r, %r)'.__mod__, _hide_passwd(self.items()))
325 # TODO: GET -> GetDict
326 return 'GET([%s])' % (', '.join(items))
327 def copy(self):
328 # Copies shouldn't be tracked
329 return MultiDict(self)
331class NestedMultiDict(MultiDict):
332 """
333 Wraps several MultiDict objects, treating it as one large MultiDict
334 """
336 def __init__(self, *dicts):
337 self.dicts = dicts
339 def __getitem__(self, key):
340 for d in self.dicts:
341 value = d.get(key, _dummy)
342 if value is not _dummy:
343 return value
344 raise KeyError(key)
346 def _readonly(self, *args, **kw):
347 raise KeyError("NestedMultiDict objects are read-only")
348 __setitem__ = _readonly
349 add = _readonly
350 __delitem__ = _readonly
351 clear = _readonly
352 setdefault = _readonly
353 pop = _readonly
354 popitem = _readonly
355 update = _readonly
357 def getall(self, key):
358 result = []
359 for d in self.dicts:
360 result.extend(d.getall(key))
361 return result
363 # Inherited:
364 # getone
365 # mixed
366 # dict_of_lists
368 def copy(self):
369 return MultiDict(self)
371 def __contains__(self, key):
372 for d in self.dicts:
373 if key in d:
374 return True
375 return False
377 has_key = __contains__
379 def __len__(self):
380 v = 0
381 for d in self.dicts:
382 v += len(d)
383 return v
385 def __nonzero__(self):
386 for d in self.dicts:
387 if d:
388 return True
389 return False
391 def iteritems(self):
392 for d in self.dicts:
393 for item in iteritems_(d):
394 yield item
395 if PY2:
396 def items(self):
397 return list(self.iteritems())
398 else:
399 items = iteritems
401 def itervalues(self):
402 for d in self.dicts:
403 for value in itervalues_(d):
404 yield value
405 if PY2:
406 def values(self):
407 return list(self.itervalues())
408 else:
409 values = itervalues
411 def __iter__(self):
412 for d in self.dicts:
413 for key in d:
414 yield key
416 iterkeys = __iter__
418 if PY2:
419 def keys(self):
420 return list(self.iterkeys())
421 else:
422 keys = iterkeys
424class NoVars(object):
425 """
426 Represents no variables; used when no variables
427 are applicable.
429 This is read-only
430 """
432 def __init__(self, reason=None):
433 self.reason = reason or 'N/A'
435 def __getitem__(self, key):
436 raise KeyError("No key %r: %s" % (key, self.reason))
438 def __setitem__(self, *args, **kw):
439 raise KeyError("Cannot add variables: %s" % self.reason)
441 add = __setitem__
442 setdefault = __setitem__
443 update = __setitem__
445 def __delitem__(self, *args, **kw):
446 raise KeyError("No keys to delete: %s" % self.reason)
447 clear = __delitem__
448 pop = __delitem__
449 popitem = __delitem__
451 def get(self, key, default=None):
452 return default
454 def getall(self, key):
455 return []
457 def getone(self, key):
458 return self[key]
460 def mixed(self):
461 return {}
462 dict_of_lists = mixed
464 def __contains__(self, key):
465 return False
466 has_key = __contains__
468 def copy(self):
469 return self
471 def __repr__(self):
472 return '<%s: %s>' % (self.__class__.__name__,
473 self.reason)
475 def __len__(self):
476 return 0
478 def iterkeys(self):
479 return iter([])
481 if PY2:
482 def __cmp__(self, other):
483 return cmp({}, other)
485 def keys(self):
486 return []
487 items = keys
488 values = keys
489 itervalues = iterkeys
490 iteritems = iterkeys
491 else:
492 keys = iterkeys
493 items = iterkeys
494 values = iterkeys
496 __iter__ = iterkeys
498def _hide_passwd(items):
499 for k, v in items:
500 if ('password' in k
501 or 'passwd' in k
502 or 'pwd' in k
503 ):
504 yield k, '******'
505 else:
506 yield k, v