Hide keyboard shortcuts

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 

9 

10from webob.compat import ( 

11 MutableMapping, 

12 PY2, 

13 iteritems_, 

14 itervalues_, 

15 url_encode, 

16 ) 

17 

18__all__ = ['MultiDict', 'NestedMultiDict', 'NoVars', 'GetDict'] 

19 

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 """ 

26 

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()) 

43 

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 

56 

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 

93 

94 def __getitem__(self, key): 

95 for k, v in reversed(self._items): 

96 if k == key: 

97 return v 

98 raise KeyError(key) 

99 

100 def __setitem__(self, key, value): 

101 try: 

102 del self[key] 

103 except KeyError: 

104 pass 

105 self._items.append((key, value)) 

106 

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)) 

112 

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] 

118 

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] 

130 

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 

153 

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 

162 

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) 

172 

173 def __contains__(self, key): 

174 for k, v in self._items: 

175 if k == key: 

176 return True 

177 return False 

178 

179 has_key = __contains__ 

180 

181 def clear(self): 

182 del self._items[:] 

183 

184 def copy(self): 

185 return self.__class__(self) 

186 

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 

193 

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) 

207 

208 def popitem(self): 

209 return self._items.pop() 

210 

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) 

222 

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) 

236 

237 def __repr__(self): 

238 items = map('(%r, %r)'.__mod__, _hide_passwd(self.items())) 

239 return '%s([%s])' % (self.__class__.__name__, ', '.join(items)) 

240 

241 def __len__(self): 

242 return len(self._items) 

243 

244 ## 

245 ## All the iteration: 

246 ## 

247 

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 

256 

257 __iter__ = iterkeys 

258 

259 def iteritems(self): 

260 return iter(self._items) 

261 

262 if PY2: 

263 def items(self): 

264 return self._items[:] 

265 else: 

266 items = iteritems 

267 

268 def itervalues(self): 

269 for k, v in self._items: 

270 yield v 

271 

272 if PY2: 

273 def values(self): 

274 return [v for k, v in self._items] 

275 else: 

276 values = itervalues 

277 

278_dummy = object() 

279 

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) 

330 

331class NestedMultiDict(MultiDict): 

332 """ 

333 Wraps several MultiDict objects, treating it as one large MultiDict 

334 """ 

335 

336 def __init__(self, *dicts): 

337 self.dicts = dicts 

338 

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) 

345 

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 

356 

357 def getall(self, key): 

358 result = [] 

359 for d in self.dicts: 

360 result.extend(d.getall(key)) 

361 return result 

362 

363 # Inherited: 

364 # getone 

365 # mixed 

366 # dict_of_lists 

367 

368 def copy(self): 

369 return MultiDict(self) 

370 

371 def __contains__(self, key): 

372 for d in self.dicts: 

373 if key in d: 

374 return True 

375 return False 

376 

377 has_key = __contains__ 

378 

379 def __len__(self): 

380 v = 0 

381 for d in self.dicts: 

382 v += len(d) 

383 return v 

384 

385 def __nonzero__(self): 

386 for d in self.dicts: 

387 if d: 

388 return True 

389 return False 

390 

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 

400 

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 

410 

411 def __iter__(self): 

412 for d in self.dicts: 

413 for key in d: 

414 yield key 

415 

416 iterkeys = __iter__ 

417 

418 if PY2: 

419 def keys(self): 

420 return list(self.iterkeys()) 

421 else: 

422 keys = iterkeys 

423 

424class NoVars(object): 

425 """ 

426 Represents no variables; used when no variables 

427 are applicable. 

428 

429 This is read-only 

430 """ 

431 

432 def __init__(self, reason=None): 

433 self.reason = reason or 'N/A' 

434 

435 def __getitem__(self, key): 

436 raise KeyError("No key %r: %s" % (key, self.reason)) 

437 

438 def __setitem__(self, *args, **kw): 

439 raise KeyError("Cannot add variables: %s" % self.reason) 

440 

441 add = __setitem__ 

442 setdefault = __setitem__ 

443 update = __setitem__ 

444 

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__ 

450 

451 def get(self, key, default=None): 

452 return default 

453 

454 def getall(self, key): 

455 return [] 

456 

457 def getone(self, key): 

458 return self[key] 

459 

460 def mixed(self): 

461 return {} 

462 dict_of_lists = mixed 

463 

464 def __contains__(self, key): 

465 return False 

466 has_key = __contains__ 

467 

468 def copy(self): 

469 return self 

470 

471 def __repr__(self): 

472 return '<%s: %s>' % (self.__class__.__name__, 

473 self.reason) 

474 

475 def __len__(self): 

476 return 0 

477 

478 def iterkeys(self): 

479 return iter([]) 

480 

481 if PY2: 

482 def __cmp__(self, other): 

483 return cmp({}, other) 

484 

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 

495 

496 __iter__ = iterkeys 

497 

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