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

1from contextlib import contextmanager 

2import functools 

3 

4try: 

5 # py2.7.7+ and py3.3+ have native comparison support 

6 from hmac import compare_digest 

7except ImportError: # pragma: no cover 

8 compare_digest = None 

9import inspect 

10import weakref 

11 

12from pyramid.exceptions import ConfigurationError, CyclicDependencyError 

13 

14from pyramid.compat import ( 

15 getargspec, 

16 im_func, 

17 is_nonstr_iter, 

18 integer_types, 

19 string_types, 

20 bytes_, 

21 text_, 

22 PY2, 

23 native_, 

24) 

25 

26from pyramid.path import DottedNameResolver as _DottedNameResolver 

27 

28_marker = object() 

29 

30 

31class DottedNameResolver(_DottedNameResolver): 

32 def __init__( 

33 self, package=None 

34 ): # default to package = None for bw compat 

35 _DottedNameResolver.__init__(self, package) 

36 

37 

38def is_string_or_iterable(v): 

39 if isinstance(v, string_types): 

40 return True 

41 if hasattr(v, '__iter__'): 

42 return True 

43 

44 

45def as_sorted_tuple(val): 

46 if not is_nonstr_iter(val): 

47 val = (val,) 

48 val = tuple(sorted(val)) 

49 return val 

50 

51 

52class InstancePropertyHelper(object): 

53 """A helper object for assigning properties and descriptors to instances. 

54 It is not normally possible to do this because descriptors must be 

55 defined on the class itself. 

56 

57 This class is optimized for adding multiple properties at once to an 

58 instance. This is done by calling :meth:`.add_property` once 

59 per-property and then invoking :meth:`.apply` on target objects. 

60 

61 """ 

62 

63 def __init__(self): 

64 self.properties = {} 

65 

66 @classmethod 

67 def make_property(cls, callable, name=None, reify=False): 

68 """ Convert a callable into one suitable for adding to the 

69 instance. This will return a 2-tuple containing the computed 

70 (name, property) pair. 

71 """ 

72 

73 is_property = isinstance(callable, property) 

74 if is_property: 

75 fn = callable 

76 if name is None: 

77 raise ValueError('must specify "name" for a property') 

78 if reify: 

79 raise ValueError('cannot reify a property') 

80 elif name is not None: 

81 fn = lambda this: callable(this) 

82 fn.__name__ = get_callable_name(name) 

83 fn.__doc__ = callable.__doc__ 

84 else: 

85 name = callable.__name__ 

86 fn = callable 

87 if reify: 

88 import pyramid.decorator # avoid circular import 

89 

90 fn = pyramid.decorator.reify(fn) 

91 elif not is_property: 

92 fn = property(fn) 

93 

94 return name, fn 

95 

96 @classmethod 

97 def apply_properties(cls, target, properties): 

98 """Accept a list or dict of ``properties`` generated from 

99 :meth:`.make_property` and apply them to a ``target`` object. 

100 """ 

101 attrs = dict(properties) 

102 if attrs: 

103 parent = target.__class__ 

104 # fix the module name so it appears to still be the parent 

105 # e.g. pyramid.request instead of pyramid.util 

106 attrs.setdefault('__module__', parent.__module__) 

107 newcls = type(parent.__name__, (parent, object), attrs) 

108 # We assign __provides__ and __implemented__ below to prevent a 

109 # memory leak that results from from the usage of this instance's 

110 # eventual use in an adapter lookup. Adapter lookup results in 

111 # ``zope.interface.implementedBy`` being called with the 

112 # newly-created class as an argument. Because the newly-created 

113 # class has no interface specification data of its own, lookup 

114 # causes new ClassProvides and Implements instances related to our 

115 # just-generated class to be created and set into the newly-created 

116 # class' __dict__. We don't want these instances to be created; we 

117 # want this new class to behave exactly like it is the parent class 

118 # instead. See GitHub issues #1212, #1529 and #1568 for more 

119 # information. 

120 for name in ('__implemented__', '__provides__'): 

121 # we assign these attributes conditionally to make it possible 

122 # to test this class in isolation without having any interfaces 

123 # attached to it 

124 val = getattr(parent, name, _marker) 

125 if val is not _marker: 

126 setattr(newcls, name, val) 

127 target.__class__ = newcls 

128 

129 @classmethod 

130 def set_property(cls, target, callable, name=None, reify=False): 

131 """A helper method to apply a single property to an instance.""" 

132 prop = cls.make_property(callable, name=name, reify=reify) 

133 cls.apply_properties(target, [prop]) 

134 

135 def add_property(self, callable, name=None, reify=False): 

136 """Add a new property configuration. 

137 

138 This should be used in combination with :meth:`.apply` as a 

139 more efficient version of :meth:`.set_property`. 

140 """ 

141 name, fn = self.make_property(callable, name=name, reify=reify) 

142 self.properties[name] = fn 

143 

144 def apply(self, target): 

145 """ Apply all configured properties to the ``target`` instance.""" 

146 if self.properties: 

147 self.apply_properties(target, self.properties) 

148 

149 

150class InstancePropertyMixin(object): 

151 """ Mixin that will allow an instance to add properties at 

152 run-time as if they had been defined via @property or @reify 

153 on the class itself. 

154 """ 

155 

156 def set_property(self, callable, name=None, reify=False): 

157 """ Add a callable or a property descriptor to the instance. 

158 

159 Properties, unlike attributes, are lazily evaluated by executing 

160 an underlying callable when accessed. They can be useful for 

161 adding features to an object without any cost if those features 

162 go unused. 

163 

164 A property may also be reified via the 

165 :class:`pyramid.decorator.reify` decorator by setting 

166 ``reify=True``, allowing the result of the evaluation to be 

167 cached. Using this method, the value of the property is only 

168 computed once for the lifetime of the object. 

169 

170 ``callable`` can either be a callable that accepts the instance 

171 as its single positional parameter, or it can be a property 

172 descriptor. 

173 

174 If the ``callable`` is a property descriptor, the ``name`` 

175 parameter must be supplied or a ``ValueError`` will be raised. 

176 Also note that a property descriptor cannot be reified, so 

177 ``reify`` must be ``False``. 

178 

179 If ``name`` is None, the name of the property will be computed 

180 from the name of the ``callable``. 

181 

182 .. code-block:: python 

183 :linenos: 

184 

185 class Foo(InstancePropertyMixin): 

186 _x = 1 

187 

188 def _get_x(self): 

189 return _x 

190 

191 def _set_x(self, value): 

192 self._x = value 

193 

194 foo = Foo() 

195 foo.set_property(property(_get_x, _set_x), name='x') 

196 foo.set_property(_get_x, name='y', reify=True) 

197 

198 >>> foo.x 

199 1 

200 >>> foo.y 

201 1 

202 >>> foo.x = 5 

203 >>> foo.x 

204 5 

205 >>> foo.y # notice y keeps the original value 

206 1 

207 """ 

208 InstancePropertyHelper.set_property( 

209 self, callable, name=name, reify=reify 

210 ) 

211 

212 

213class WeakOrderedSet(object): 

214 """ Maintain a set of items. 

215 

216 Each item is stored as a weakref to avoid extending their lifetime. 

217 

218 The values may be iterated over or the last item added may be 

219 accessed via the ``last`` property. 

220 

221 If items are added more than once, the most recent addition will 

222 be remembered in the order: 

223 

224 order = WeakOrderedSet() 

225 order.add('1') 

226 order.add('2') 

227 order.add('1') 

228 

229 list(order) == ['2', '1'] 

230 order.last == '1' 

231 """ 

232 

233 def __init__(self): 

234 self._items = {} 

235 self._order = [] 

236 

237 def add(self, item): 

238 """ Add an item to the set.""" 

239 oid = id(item) 

240 if oid in self._items: 

241 self._order.remove(oid) 

242 self._order.append(oid) 

243 return 

244 ref = weakref.ref(item, lambda x: self._remove_by_id(oid)) 

245 self._items[oid] = ref 

246 self._order.append(oid) 

247 

248 def _remove_by_id(self, oid): 

249 """ Remove an item from the set.""" 

250 if oid in self._items: 

251 del self._items[oid] 

252 self._order.remove(oid) 

253 

254 def remove(self, item): 

255 """ Remove an item from the set.""" 

256 self._remove_by_id(id(item)) 

257 

258 def empty(self): 

259 """ Clear all objects from the set.""" 

260 self._items = {} 

261 self._order = [] 

262 

263 def __len__(self): 

264 return len(self._order) 

265 

266 def __contains__(self, item): 

267 oid = id(item) 

268 return oid in self._items 

269 

270 def __iter__(self): 

271 return (self._items[oid]() for oid in self._order) 

272 

273 @property 

274 def last(self): 

275 if self._order: 

276 oid = self._order[-1] 

277 return self._items[oid]() 

278 

279 

280def strings_differ(string1, string2, compare_digest=compare_digest): 

281 """Check whether two strings differ while avoiding timing attacks. 

282 

283 This function returns True if the given strings differ and False 

284 if they are equal. It's careful not to leak information about *where* 

285 they differ as a result of its running time, which can be very important 

286 to avoid certain timing-related crypto attacks: 

287 

288 http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf 

289 

290 .. versionchanged:: 1.6 

291 Support :func:`hmac.compare_digest` if it is available (Python 2.7.7+ 

292 and Python 3.3+). 

293 

294 """ 

295 len_eq = len(string1) == len(string2) 

296 if len_eq: 

297 invalid_bits = 0 

298 left = string1 

299 else: 

300 invalid_bits = 1 

301 left = string2 

302 right = string2 

303 

304 if compare_digest is not None: 

305 invalid_bits += not compare_digest(left, right) 

306 else: 

307 for a, b in zip(left, right): 

308 invalid_bits += a != b 

309 return invalid_bits != 0 

310 

311 

312def object_description(object): 

313 """ Produce a human-consumable text description of ``object``, 

314 usually involving a Python dotted name. For example: 

315 

316 >>> object_description(None) 

317 u'None' 

318 >>> from xml.dom import minidom 

319 >>> object_description(minidom) 

320 u'module xml.dom.minidom' 

321 >>> object_description(minidom.Attr) 

322 u'class xml.dom.minidom.Attr' 

323 >>> object_description(minidom.Attr.appendChild) 

324 u'method appendChild of class xml.dom.minidom.Attr' 

325 

326 If this method cannot identify the type of the object, a generic 

327 description ala ``object <object.__name__>`` will be returned. 

328 

329 If the object passed is already a string, it is simply returned. If it 

330 is a boolean, an integer, a list, a tuple, a set, or ``None``, a 

331 (possibly shortened) string representation is returned. 

332 """ 

333 if isinstance(object, string_types): 

334 return text_(object) 

335 if isinstance(object, integer_types): 

336 return text_(str(object)) 

337 if isinstance(object, (bool, float, type(None))): 

338 return text_(str(object)) 

339 if isinstance(object, set): 

340 if PY2: 

341 return shortrepr(object, ')') 

342 else: 

343 return shortrepr(object, '}') 

344 if isinstance(object, tuple): 

345 return shortrepr(object, ')') 

346 if isinstance(object, list): 

347 return shortrepr(object, ']') 

348 if isinstance(object, dict): 

349 return shortrepr(object, '}') 

350 module = inspect.getmodule(object) 

351 if module is None: 

352 return text_('object %s' % str(object)) 

353 modulename = module.__name__ 

354 if inspect.ismodule(object): 

355 return text_('module %s' % modulename) 

356 if inspect.ismethod(object): 

357 oself = getattr(object, '__self__', None) 

358 if oself is None: # pragma: no cover 

359 oself = getattr(object, 'im_self', None) 

360 return text_( 

361 'method %s of class %s.%s' 

362 % (object.__name__, modulename, oself.__class__.__name__) 

363 ) 

364 

365 if inspect.isclass(object): 

366 dottedname = '%s.%s' % (modulename, object.__name__) 

367 return text_('class %s' % dottedname) 

368 if inspect.isfunction(object): 

369 dottedname = '%s.%s' % (modulename, object.__name__) 

370 return text_('function %s' % dottedname) 

371 return text_('object %s' % str(object)) 

372 

373 

374def shortrepr(object, closer): 

375 r = str(object) 

376 if len(r) > 100: 

377 r = r[:100] + ' ... %s' % closer 

378 return r 

379 

380 

381class Sentinel(object): 

382 def __init__(self, repr): 

383 self.repr = repr 

384 

385 def __repr__(self): 

386 return self.repr 

387 

388 

389FIRST = Sentinel('FIRST') 

390LAST = Sentinel('LAST') 

391 

392 

393class TopologicalSorter(object): 

394 """ A utility class which can be used to perform topological sorts against 

395 tuple-like data.""" 

396 

397 def __init__( 

398 self, default_before=LAST, default_after=None, first=FIRST, last=LAST 

399 ): 

400 self.names = [] 

401 self.req_before = set() 

402 self.req_after = set() 

403 self.name2before = {} 

404 self.name2after = {} 

405 self.name2val = {} 

406 self.order = [] 

407 self.default_before = default_before 

408 self.default_after = default_after 

409 self.first = first 

410 self.last = last 

411 

412 def values(self): 

413 return self.name2val.values() 

414 

415 def remove(self, name): 

416 """ Remove a node from the sort input """ 

417 self.names.remove(name) 

418 del self.name2val[name] 

419 after = self.name2after.pop(name, []) 

420 if after: 

421 self.req_after.remove(name) 

422 for u in after: 

423 self.order.remove((u, name)) 

424 before = self.name2before.pop(name, []) 

425 if before: 

426 self.req_before.remove(name) 

427 for u in before: 

428 self.order.remove((name, u)) 

429 

430 def add(self, name, val, after=None, before=None): 

431 """ Add a node to the sort input. The ``name`` should be a string or 

432 any other hashable object, the ``val`` should be the sortable (doesn't 

433 need to be hashable). ``after`` and ``before`` represents the name of 

434 one of the other sortables (or a sequence of such named) or one of the 

435 special sentinel values :attr:`pyramid.util.FIRST`` or 

436 :attr:`pyramid.util.LAST` representing the first or last positions 

437 respectively. ``FIRST`` and ``LAST`` can also be part of a sequence 

438 passed as ``before`` or ``after``. A sortable should not be added 

439 after LAST or before FIRST. An example:: 

440 

441 sorter = TopologicalSorter() 

442 sorter.add('a', {'a':1}, before=LAST, after='b') 

443 sorter.add('b', {'b':2}, before=LAST, after='c') 

444 sorter.add('c', {'c':3}) 

445 

446 sorter.sorted() # will be {'c':3}, {'b':2}, {'a':1} 

447 

448 """ 

449 if name in self.names: 

450 self.remove(name) 

451 self.names.append(name) 

452 self.name2val[name] = val 

453 if after is None and before is None: 

454 before = self.default_before 

455 after = self.default_after 

456 if after is not None: 

457 if not is_nonstr_iter(after): 

458 after = (after,) 

459 self.name2after[name] = after 

460 self.order += [(u, name) for u in after] 

461 self.req_after.add(name) 

462 if before is not None: 

463 if not is_nonstr_iter(before): 

464 before = (before,) 

465 self.name2before[name] = before 

466 self.order += [(name, o) for o in before] 

467 self.req_before.add(name) 

468 

469 def sorted(self): 

470 """ Returns the sort input values in topologically sorted order""" 

471 order = [(self.first, self.last)] 

472 roots = [] 

473 graph = {} 

474 names = [self.first, self.last] 

475 names.extend(self.names) 

476 

477 for a, b in self.order: 

478 order.append((a, b)) 

479 

480 def add_node(node): 

481 if node not in graph: 

482 roots.append(node) 

483 graph[node] = [0] # 0 = number of arcs coming into this node 

484 

485 def add_arc(fromnode, tonode): 

486 graph[fromnode].append(tonode) 

487 graph[tonode][0] += 1 

488 if tonode in roots: 

489 roots.remove(tonode) 

490 

491 for name in names: 

492 add_node(name) 

493 

494 has_before, has_after = set(), set() 

495 for a, b in order: 

496 if a in names and b in names: # deal with missing dependencies 

497 add_arc(a, b) 

498 has_before.add(a) 

499 has_after.add(b) 

500 

501 if not self.req_before.issubset(has_before): 

502 raise ConfigurationError( 

503 'Unsatisfied before dependencies: %s' 

504 % (', '.join(sorted(self.req_before - has_before))) 

505 ) 

506 if not self.req_after.issubset(has_after): 

507 raise ConfigurationError( 

508 'Unsatisfied after dependencies: %s' 

509 % (', '.join(sorted(self.req_after - has_after))) 

510 ) 

511 

512 sorted_names = [] 

513 

514 while roots: 

515 root = roots.pop(0) 

516 sorted_names.append(root) 

517 children = graph[root][1:] 

518 for child in children: 

519 arcs = graph[child][0] 

520 arcs -= 1 

521 graph[child][0] = arcs 

522 if arcs == 0: 

523 roots.insert(0, child) 

524 del graph[root] 

525 

526 if graph: 

527 # loop in input 

528 cycledeps = {} 

529 for k, v in graph.items(): 

530 cycledeps[k] = v[1:] 

531 raise CyclicDependencyError(cycledeps) 

532 

533 result = [] 

534 

535 for name in sorted_names: 

536 if name in self.names: 

537 result.append((name, self.name2val[name])) 

538 

539 return result 

540 

541 

542def get_callable_name(name): 

543 """ 

544 Verifies that the ``name`` is ascii and will raise a ``ConfigurationError`` 

545 if it is not. 

546 """ 

547 try: 

548 return native_(name, 'ascii') 

549 except (UnicodeEncodeError, UnicodeDecodeError): 

550 msg = ( 

551 '`name="%s"` is invalid. `name` must be ascii because it is ' 

552 'used on __name__ of the method' 

553 ) 

554 raise ConfigurationError(msg % name) 

555 

556 

557@contextmanager 

558def hide_attrs(obj, *attrs): 

559 """ 

560 Temporarily delete object attrs and restore afterward. 

561 """ 

562 obj_vals = obj.__dict__ if obj is not None else {} 

563 saved_vals = {} 

564 for name in attrs: 

565 saved_vals[name] = obj_vals.pop(name, _marker) 

566 try: 

567 yield 

568 finally: 

569 for name in attrs: 

570 saved_val = saved_vals[name] 

571 if saved_val is not _marker: 

572 obj_vals[name] = saved_val 

573 elif name in obj_vals: 

574 del obj_vals[name] 

575 

576 

577def is_same_domain(host, pattern): 

578 """ 

579 Return ``True`` if the host is either an exact match or a match 

580 to the wildcard pattern. 

581 Any pattern beginning with a period matches a domain and all of its 

582 subdomains. (e.g. ``.example.com`` matches ``example.com`` and 

583 ``foo.example.com``). Anything else is an exact string match. 

584 """ 

585 if not pattern: 

586 return False 

587 

588 pattern = pattern.lower() 

589 return ( 

590 pattern[0] == "." 

591 and (host.endswith(pattern) or host == pattern[1:]) 

592 or pattern == host 

593 ) 

594 

595 

596def make_contextmanager(fn): 

597 if inspect.isgeneratorfunction(fn): 

598 return contextmanager(fn) 

599 

600 if fn is None: 

601 fn = lambda *a, **kw: None 

602 

603 @contextmanager 

604 @functools.wraps(fn) 

605 def wrapper(*a, **kw): 

606 yield fn(*a, **kw) 

607 

608 return wrapper 

609 

610 

611def takes_one_arg(callee, attr=None, argname=None): 

612 ismethod = False 

613 if attr is None: 

614 attr = '__call__' 

615 if inspect.isroutine(callee): 

616 fn = callee 

617 elif inspect.isclass(callee): 

618 try: 

619 fn = callee.__init__ 

620 except AttributeError: 

621 return False 

622 ismethod = hasattr(fn, '__call__') 

623 else: 

624 try: 

625 fn = getattr(callee, attr) 

626 except AttributeError: 

627 return False 

628 

629 try: 

630 argspec = getargspec(fn) 

631 except TypeError: 

632 return False 

633 

634 args = argspec[0] 

635 

636 if hasattr(fn, im_func) or ismethod: 

637 # it's an instance method (or unbound method on py2) 

638 if not args: 

639 return False 

640 args = args[1:] 

641 

642 if not args: 

643 return False 

644 

645 if len(args) == 1: 

646 return True 

647 

648 if argname: 

649 

650 defaults = argspec[3] 

651 if defaults is None: 

652 defaults = () 

653 

654 if args[0] == argname: 

655 if len(args) - len(defaults) == 1: 

656 return True 

657 

658 return False 

659 

660 

661class SimpleSerializer(object): 

662 def loads(self, bstruct): 

663 return native_(bstruct) 

664 

665 def dumps(self, appstruct): 

666 return bytes_(appstruct)