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

1import copy 

2import os 

3from contextlib import contextmanager 

4 

5from webob.acceptparse import create_accept_header 

6 

7from zope.interface import implementer, alsoProvides 

8 

9from pyramid.interfaces import IRequest, ISession 

10 

11from pyramid.compat import PY3, PYPY, class_types, text_ 

12 

13from pyramid.config import Configurator 

14from pyramid.decorator import reify 

15from pyramid.path import caller_package 

16from pyramid.response import _get_response_factory 

17from pyramid.registry import Registry 

18 

19from pyramid.security import ( 

20 Authenticated, 

21 Everyone, 

22 AuthenticationAPIMixin, 

23 AuthorizationAPIMixin, 

24) 

25 

26from pyramid.threadlocal import get_current_registry, manager 

27 

28from pyramid.i18n import LocalizerRequestMixin 

29from pyramid.request import CallbackMethodsMixin 

30from pyramid.url import URLMethodsMixin 

31from pyramid.util import InstancePropertyMixin 

32from pyramid.view import ViewMethodsMixin 

33 

34 

35_marker = object() 

36 

37 

38class DummyRootFactory(object): 

39 __parent__ = None 

40 __name__ = None 

41 

42 def __init__(self, request): 

43 if 'bfg.routes.matchdict' in request: 

44 self.__dict__.update(request['bfg.routes.matchdict']) 

45 

46 

47class DummySecurityPolicy(object): 

48 """ A standin for both an IAuthentication and IAuthorization policy """ 

49 

50 def __init__( 

51 self, 

52 userid=None, 

53 groupids=(), 

54 permissive=True, 

55 remember_result=None, 

56 forget_result=None, 

57 ): 

58 self.userid = userid 

59 self.groupids = groupids 

60 self.permissive = permissive 

61 if remember_result is None: 

62 remember_result = [] 

63 if forget_result is None: 

64 forget_result = [] 

65 self.remember_result = remember_result 

66 self.forget_result = forget_result 

67 

68 def authenticated_userid(self, request): 

69 return self.userid 

70 

71 def unauthenticated_userid(self, request): 

72 return self.userid 

73 

74 def effective_principals(self, request): 

75 effective_principals = [Everyone] 

76 if self.userid: 

77 effective_principals.append(Authenticated) 

78 effective_principals.append(self.userid) 

79 effective_principals.extend(self.groupids) 

80 return effective_principals 

81 

82 def remember(self, request, userid, **kw): 

83 self.remembered = userid 

84 return self.remember_result 

85 

86 def forget(self, request): 

87 self.forgotten = True 

88 return self.forget_result 

89 

90 def permits(self, context, principals, permission): 

91 return self.permissive 

92 

93 def principals_allowed_by_permission(self, context, permission): 

94 if self.permissive: 

95 return self.effective_principals(None) 

96 else: 

97 return [] 

98 

99 

100class DummyTemplateRenderer(object): 

101 """ 

102 An instance of this class is returned from 

103 :meth:`pyramid.config.Configurator.testing_add_renderer`. It has a 

104 helper function (``assert_``) that makes it possible to make an 

105 assertion which compares data passed to the renderer by the view 

106 function against expected key/value pairs. 

107 """ 

108 

109 def __init__(self, string_response=''): 

110 self._received = {} 

111 self._string_response = string_response 

112 self._implementation = MockTemplate(string_response) 

113 

114 # For in-the-wild test code that doesn't create its own renderer, 

115 # but mutates our internals instead. When all you read is the 

116 # source code, *everything* is an API! 

117 def _get_string_response(self): 

118 return self._string_response 

119 

120 def _set_string_response(self, response): 

121 self._string_response = response 

122 self._implementation.response = response 

123 

124 string_response = property(_get_string_response, _set_string_response) 

125 

126 def implementation(self): 

127 return self._implementation 

128 

129 def __call__(self, kw, system=None): 

130 if system: 

131 self._received.update(system) 

132 self._received.update(kw) 

133 return self.string_response 

134 

135 def __getattr__(self, k): 

136 """ Backwards compatibility """ 

137 val = self._received.get(k, _marker) 

138 if val is _marker: 

139 val = self._implementation._received.get(k, _marker) 

140 if val is _marker: 

141 raise AttributeError(k) 

142 return val 

143 

144 def assert_(self, **kw): 

145 """ Accept an arbitrary set of assertion key/value pairs. For 

146 each assertion key/value pair assert that the renderer 

147 (eg. :func:`pyramid.renderers.render_to_response`) 

148 received the key with a value that equals the asserted 

149 value. If the renderer did not receive the key at all, or the 

150 value received by the renderer doesn't match the assertion 

151 value, raise an :exc:`AssertionError`.""" 

152 for k, v in kw.items(): 

153 myval = self._received.get(k, _marker) 

154 if myval is _marker: 

155 myval = self._implementation._received.get(k, _marker) 

156 if myval is _marker: 

157 raise AssertionError( 

158 'A value for key "%s" was not passed to the renderer' 

159 % k 

160 ) 

161 

162 if myval != v: 

163 raise AssertionError( 

164 '\nasserted value for %s: %r\nactual value: %r' 

165 % (k, v, myval) 

166 ) 

167 return True 

168 

169 

170class DummyResource: 

171 """ A dummy :app:`Pyramid` :term:`resource` object.""" 

172 

173 def __init__( 

174 self, __name__=None, __parent__=None, __provides__=None, **kw 

175 ): 

176 """ The resource's ``__name__`` attribute will be set to the 

177 value of the ``__name__`` argument, and the resource's 

178 ``__parent__`` attribute will be set to the value of the 

179 ``__parent__`` argument. If ``__provides__`` is specified, it 

180 should be an interface object or tuple of interface objects 

181 that will be attached to the resulting resource via 

182 :func:`zope.interface.alsoProvides`. Any extra keywords passed 

183 in the ``kw`` argumnent will be set as direct attributes of 

184 the resource object. 

185 

186 .. note:: For backwards compatibility purposes, this class can also 

187 be imported as :class:`pyramid.testing.DummyModel`. 

188 

189 """ 

190 self.__name__ = __name__ 

191 self.__parent__ = __parent__ 

192 if __provides__ is not None: 

193 alsoProvides(self, __provides__) 

194 self.kw = kw 

195 self.__dict__.update(**kw) 

196 self.subs = {} 

197 

198 def __setitem__(self, name, val): 

199 """ When the ``__setitem__`` method is called, the object 

200 passed in as ``val`` will be decorated with a ``__parent__`` 

201 attribute pointing at the dummy resource and a ``__name__`` 

202 attribute that is the value of ``name``. The value will then 

203 be returned when dummy resource's ``__getitem__`` is called with 

204 the name ``name```.""" 

205 val.__name__ = name 

206 val.__parent__ = self 

207 self.subs[name] = val 

208 

209 def __getitem__(self, name): 

210 """ Return a named subobject (see ``__setitem__``)""" 

211 ob = self.subs[name] 

212 return ob 

213 

214 def __delitem__(self, name): 

215 del self.subs[name] 

216 

217 def get(self, name, default=None): 

218 return self.subs.get(name, default) 

219 

220 def values(self): 

221 """ Return the values set by __setitem__ """ 

222 return self.subs.values() 

223 

224 def items(self): 

225 """ Return the items set by __setitem__ """ 

226 return self.subs.items() 

227 

228 def keys(self): 

229 """ Return the keys set by __setitem__ """ 

230 return self.subs.keys() 

231 

232 __iter__ = keys 

233 

234 def __nonzero__(self): 

235 return True 

236 

237 __bool__ = __nonzero__ 

238 

239 def __len__(self): 

240 return len(self.subs) 

241 

242 def __contains__(self, name): 

243 return name in self.subs 

244 

245 def clone(self, __name__=_marker, __parent__=_marker, **kw): 

246 """ Create a clone of the resource object. If ``__name__`` or 

247 ``__parent__`` arguments are passed, use these values to 

248 override the existing ``__name__`` or ``__parent__`` of the 

249 resource. If any extra keyword args are passed in via the ``kw`` 

250 argument, use these keywords to add to or override existing 

251 resource keywords (attributes).""" 

252 oldkw = self.kw.copy() 

253 oldkw.update(kw) 

254 inst = self.__class__(self.__name__, self.__parent__, **oldkw) 

255 inst.subs = copy.deepcopy(self.subs) 

256 if __name__ is not _marker: 

257 inst.__name__ = __name__ 

258 if __parent__ is not _marker: 

259 inst.__parent__ = __parent__ 

260 return inst 

261 

262 

263DummyModel = DummyResource # b/w compat (forever) 

264 

265 

266@implementer(ISession) 

267class DummySession(dict): 

268 created = None 

269 new = True 

270 

271 def changed(self): 

272 pass 

273 

274 def invalidate(self): 

275 self.clear() 

276 

277 def flash(self, msg, queue='', allow_duplicate=True): 

278 storage = self.setdefault('_f_' + queue, []) 

279 if allow_duplicate or (msg not in storage): 

280 storage.append(msg) 

281 

282 def pop_flash(self, queue=''): 

283 storage = self.pop('_f_' + queue, []) 

284 return storage 

285 

286 def peek_flash(self, queue=''): 

287 storage = self.get('_f_' + queue, []) 

288 return storage 

289 

290 def new_csrf_token(self): 

291 token = text_('0123456789012345678901234567890123456789') 

292 self['_csrft_'] = token 

293 return token 

294 

295 def get_csrf_token(self): 

296 token = self.get('_csrft_', None) 

297 if token is None: 

298 token = self.new_csrf_token() 

299 return token 

300 

301 

302@implementer(IRequest) 

303class DummyRequest( 

304 URLMethodsMixin, 

305 CallbackMethodsMixin, 

306 InstancePropertyMixin, 

307 LocalizerRequestMixin, 

308 AuthenticationAPIMixin, 

309 AuthorizationAPIMixin, 

310 ViewMethodsMixin, 

311): 

312 """ A DummyRequest object (incompletely) imitates a :term:`request` object. 

313 

314 The ``params``, ``environ``, ``headers``, ``path``, and 

315 ``cookies`` arguments correspond to their :term:`WebOb` 

316 equivalents. 

317 

318 The ``post`` argument, if passed, populates the request's 

319 ``POST`` attribute, but *not* ``params``, in order to allow testing 

320 that the app accepts data for a given view only from POST requests. 

321 This argument also sets ``self.method`` to "POST". 

322 

323 Extra keyword arguments are assigned as attributes of the request 

324 itself. 

325 

326 Note that DummyRequest does not have complete fidelity with a "real" 

327 request. For example, by default, the DummyRequest ``GET`` and ``POST`` 

328 attributes are of type ``dict``, unlike a normal Request's GET and POST, 

329 which are of type ``MultiDict``. If your code uses the features of 

330 MultiDict, you should either use a real :class:`pyramid.request.Request` 

331 or adapt your DummyRequest by replacing the attributes with ``MultiDict`` 

332 instances. 

333 

334 Other similar incompatibilities exist. If you need all the features of 

335 a Request, use the :class:`pyramid.request.Request` class itself rather 

336 than this class while writing tests. 

337 """ 

338 

339 method = 'GET' 

340 application_url = 'http://example.com' 

341 host = 'example.com:80' 

342 domain = 'example.com' 

343 content_length = 0 

344 query_string = '' 

345 charset = 'UTF-8' 

346 script_name = '' 

347 _registry = None 

348 _accept = None 

349 request_iface = IRequest 

350 

351 def __init__( 

352 self, 

353 params=None, 

354 environ=None, 

355 headers=None, 

356 path='/', 

357 cookies=None, 

358 post=None, 

359 accept=None, 

360 **kw 

361 ): 

362 if environ is None: 

363 environ = {} 

364 if params is None: 

365 params = {} 

366 if headers is None: 

367 headers = {} 

368 if cookies is None: 

369 cookies = {} 

370 self.environ = environ 

371 self.headers = headers 

372 self.params = params 

373 self.cookies = cookies 

374 self.matchdict = {} 

375 self.GET = params 

376 if post is not None: 

377 self.method = 'POST' 

378 self.POST = post 

379 else: 

380 self.POST = params 

381 self.host_url = self.application_url 

382 self.path_url = self.application_url 

383 self.url = self.application_url 

384 self.path = path 

385 self.path_info = path 

386 self.script_name = '' 

387 self.path_qs = '' 

388 self.body = '' 

389 self.view_name = '' 

390 self.subpath = () 

391 self.traversed = () 

392 self.virtual_root_path = () 

393 self.context = None 

394 self.root = None 

395 self.virtual_root = None 

396 self.marshalled = params # repoze.monty 

397 self.session = DummySession() 

398 self.accept = accept 

399 self.__dict__.update(kw) 

400 

401 def _get_registry(self): 

402 if self._registry is None: 

403 return get_current_registry() 

404 return self._registry 

405 

406 def _set_registry(self, registry): 

407 self._registry = registry 

408 

409 def _del_registry(self): 

410 self._registry = None 

411 

412 registry = property(_get_registry, _set_registry, _del_registry) 

413 

414 def _set_accept(self, value): 

415 self._accept = create_accept_header(value) 

416 

417 def _get_accept(self): 

418 if self._accept is None: 

419 self._accept = create_accept_header(None) 

420 return self._accept 

421 

422 def _del_accept(self): 

423 self._accept = None 

424 

425 accept = property(_get_accept, _set_accept, _del_accept) 

426 

427 @reify 

428 def response(self): 

429 f = _get_response_factory(self.registry) 

430 return f(self) 

431 

432 

433have_zca = True 

434 

435 

436def setUp( 

437 registry=None, 

438 request=None, 

439 hook_zca=True, 

440 autocommit=True, 

441 settings=None, 

442 package=None, 

443): 

444 """ 

445 Set :app:`Pyramid` registry and request thread locals for the 

446 duration of a single unit test. 

447 

448 Use this function in the ``setUp`` method of a unittest test case 

449 which directly or indirectly uses: 

450 

451 - any method of the :class:`pyramid.config.Configurator` 

452 object returned by this function. 

453 

454 - the :func:`pyramid.threadlocal.get_current_registry` or 

455 :func:`pyramid.threadlocal.get_current_request` functions. 

456 

457 If you use the ``get_current_*`` functions (or call :app:`Pyramid` code 

458 that uses these functions) without calling ``setUp``, 

459 :func:`pyramid.threadlocal.get_current_registry` will return a *global* 

460 :term:`application registry`, which may cause unit tests to not be 

461 isolated with respect to registrations they perform. 

462 

463 If the ``registry`` argument is ``None``, a new empty 

464 :term:`application registry` will be created (an instance of the 

465 :class:`pyramid.registry.Registry` class). If the ``registry`` 

466 argument is not ``None``, the value passed in should be an 

467 instance of the :class:`pyramid.registry.Registry` class or a 

468 suitable testing analogue. 

469 

470 After ``setUp`` is finished, the registry returned by the 

471 :func:`pyramid.threadlocal.get_current_registry` function will 

472 be the passed (or constructed) registry until 

473 :func:`pyramid.testing.tearDown` is called (or 

474 :func:`pyramid.testing.setUp` is called again) . 

475 

476 If the ``hook_zca`` argument is ``True``, ``setUp`` will attempt 

477 to perform the operation ``zope.component.getSiteManager.sethook( 

478 pyramid.threadlocal.get_current_registry)``, which will cause 

479 the :term:`Zope Component Architecture` global API 

480 (e.g. :func:`zope.component.getSiteManager`, 

481 :func:`zope.component.getAdapter`, and so on) to use the registry 

482 constructed by ``setUp`` as the value it returns from 

483 :func:`zope.component.getSiteManager`. If the 

484 :mod:`zope.component` package cannot be imported, or if 

485 ``hook_zca`` is ``False``, the hook will not be set. 

486 

487 If ``settings`` is not ``None``, it must be a dictionary representing the 

488 values passed to a Configurator as its ``settings=`` argument. 

489 

490 If ``package`` is ``None`` it will be set to the caller's package. The 

491 ``package`` setting in the :class:`pyramid.config.Configurator` will 

492 affect any relative imports made via 

493 :meth:`pyramid.config.Configurator.include` or 

494 :meth:`pyramid.config.Configurator.maybe_dotted`. 

495 

496 This function returns an instance of the 

497 :class:`pyramid.config.Configurator` class, which can be 

498 used for further configuration to set up an environment suitable 

499 for a unit or integration test. The ``registry`` attribute 

500 attached to the Configurator instance represents the 'current' 

501 :term:`application registry`; the same registry will be returned 

502 by :func:`pyramid.threadlocal.get_current_registry` during the 

503 execution of the test. 

504 """ 

505 manager.clear() 

506 if registry is None: 

507 registry = Registry('testing') 

508 if package is None: 

509 package = caller_package() 

510 config = Configurator( 

511 registry=registry, autocommit=autocommit, package=package 

512 ) 

513 if settings is None: 

514 settings = {} 

515 config._fix_registry() 

516 if getattr(registry, 'settings', None) is None: 

517 config._set_settings(settings) 

518 if hasattr(registry, 'registerUtility'): 

519 # Sometimes nose calls us with a non-registry object because 

520 # it thinks this function is module test setup. Likewise, 

521 # someone may be passing us an esoteric "dummy" registry, and 

522 # the below won't succeed if it doesn't have a registerUtility 

523 # method. 

524 config.add_default_response_adapters() 

525 config.add_default_renderers() 

526 config.add_default_accept_view_order() 

527 config.add_default_view_predicates() 

528 config.add_default_view_derivers() 

529 config.add_default_route_predicates() 

530 config.add_default_tweens() 

531 config.add_default_security() 

532 config.commit() 

533 global have_zca 

534 try: 

535 have_zca and hook_zca and config.hook_zca() 

536 except ImportError: # pragma: no cover 

537 # (dont choke on not being able to import z.component) 

538 have_zca = False 

539 config.begin(request=request) 

540 return config 

541 

542 

543def tearDown(unhook_zca=True): 

544 """Undo the effects of :func:`pyramid.testing.setUp`. Use this 

545 function in the ``tearDown`` method of a unit test that uses 

546 :func:`pyramid.testing.setUp` in its ``setUp`` method. 

547 

548 If the ``unhook_zca`` argument is ``True`` (the default), call 

549 :func:`zope.component.getSiteManager.reset`. This undoes the 

550 action of :func:`pyramid.testing.setUp` when called with the 

551 argument ``hook_zca=True``. If :mod:`zope.component` cannot be 

552 imported, ``unhook_zca`` is set to ``False``. 

553 """ 

554 global have_zca 

555 if unhook_zca and have_zca: 

556 try: 

557 from zope.component import getSiteManager 

558 

559 getSiteManager.reset() 

560 except ImportError: # pragma: no cover 

561 have_zca = False 

562 info = manager.pop() 

563 manager.clear() 

564 if info is not None: 

565 registry = info['registry'] 

566 if hasattr(registry, '__init__') and hasattr(registry, '__name__'): 

567 try: 

568 registry.__init__(registry.__name__) 

569 except TypeError: 

570 # calling __init__ is largely for the benefit of 

571 # people who want to use the global ZCA registry; 

572 # however maybe somebody's using a registry we don't 

573 # understand, let's not blow up 

574 pass 

575 

576 

577def cleanUp(*arg, **kw): 

578 """ An alias for :func:`pyramid.testing.setUp`. """ 

579 package = kw.get('package', None) 

580 if package is None: 

581 package = caller_package() 

582 kw['package'] = package 

583 return setUp(*arg, **kw) 

584 

585 

586class DummyRendererFactory(object): 

587 """ Registered by 

588 :meth:`pyramid.config.Configurator.testing_add_renderer` as 

589 a dummy renderer factory. The indecision about what to use as a 

590 key (a spec vs. a relative name) is caused by test suites in the 

591 wild believing they can register either. The ``factory`` argument 

592 passed to this constructor is usually the *real* template renderer 

593 factory, found when ``testing_add_renderer`` is called.""" 

594 

595 def __init__(self, name, factory): 

596 self.name = name 

597 self.factory = factory # the "real" renderer factory reg'd previously 

598 self.renderers = {} 

599 

600 def add(self, spec, renderer): 

601 self.renderers[spec] = renderer 

602 if ':' in spec: 

603 package, relative = spec.split(':', 1) 

604 self.renderers[relative] = renderer 

605 

606 def __call__(self, info): 

607 spec = info.name 

608 renderer = self.renderers.get(spec) 

609 if renderer is None: 

610 if ':' in spec: 

611 package, relative = spec.split(':', 1) 

612 renderer = self.renderers.get(relative) 

613 if renderer is None: 

614 if self.factory: 

615 renderer = self.factory(info) 

616 else: 

617 raise KeyError( 

618 'No testing renderer registered for %r' % spec 

619 ) 

620 return renderer 

621 

622 

623class MockTemplate(object): 

624 def __init__(self, response): 

625 self._received = {} 

626 self.response = response 

627 

628 def __getattr__(self, attrname): 

629 return self 

630 

631 def __getitem__(self, attrname): 

632 return self 

633 

634 def __call__(self, *arg, **kw): 

635 self._received.update(kw) 

636 return self.response 

637 

638 

639def skip_on(*platforms): # pragma: no cover 

640 skip = False 

641 for platform in platforms: 

642 if skip_on.os_name.startswith(platform): 

643 skip = True 

644 if platform == 'pypy' and PYPY: 

645 skip = True 

646 if platform == 'py3' and PY3: 

647 skip = True 

648 

649 def decorator(func): 

650 if isinstance(func, class_types): 

651 if skip: 

652 return None 

653 else: 

654 return func 

655 else: 

656 

657 def wrapper(*args, **kw): 

658 if skip: 

659 return 

660 return func(*args, **kw) 

661 

662 wrapper.__name__ = func.__name__ 

663 wrapper.__doc__ = func.__doc__ 

664 return wrapper 

665 

666 return decorator 

667 

668 

669skip_on.os_name = os.name # for testing 

670 

671 

672@contextmanager 

673def testConfig( 

674 registry=None, request=None, hook_zca=True, autocommit=True, settings=None 

675): 

676 """Returns a context manager for test set up. 

677 

678 This context manager calls :func:`pyramid.testing.setUp` when 

679 entering and :func:`pyramid.testing.tearDown` when exiting. 

680 

681 All arguments are passed directly to :func:`pyramid.testing.setUp`. 

682 If the ZCA is hooked, it will always be un-hooked in tearDown. 

683 

684 This context manager allows you to write test code like this: 

685 

686 .. code-block:: python 

687 :linenos: 

688 

689 with testConfig() as config: 

690 config.add_route('bar', '/bar/{id}') 

691 req = DummyRequest() 

692 resp = myview(req) 

693 """ 

694 config = setUp( 

695 registry=registry, 

696 request=request, 

697 hook_zca=hook_zca, 

698 autocommit=autocommit, 

699 settings=settings, 

700 ) 

701 try: 

702 yield config 

703 finally: 

704 tearDown(unhook_zca=hook_zca)