Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/pyramid/testing.py : 34%

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
5from webob.acceptparse import create_accept_header
7from zope.interface import implementer, alsoProvides
9from pyramid.interfaces import IRequest, ISession
11from pyramid.compat import PY3, PYPY, class_types, text_
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
19from pyramid.security import (
20 Authenticated,
21 Everyone,
22 AuthenticationAPIMixin,
23 AuthorizationAPIMixin,
24)
26from pyramid.threadlocal import get_current_registry, manager
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
35_marker = object()
38class DummyRootFactory(object):
39 __parent__ = None
40 __name__ = None
42 def __init__(self, request):
43 if 'bfg.routes.matchdict' in request:
44 self.__dict__.update(request['bfg.routes.matchdict'])
47class DummySecurityPolicy(object):
48 """ A standin for both an IAuthentication and IAuthorization policy """
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
68 def authenticated_userid(self, request):
69 return self.userid
71 def unauthenticated_userid(self, request):
72 return self.userid
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
82 def remember(self, request, userid, **kw):
83 self.remembered = userid
84 return self.remember_result
86 def forget(self, request):
87 self.forgotten = True
88 return self.forget_result
90 def permits(self, context, principals, permission):
91 return self.permissive
93 def principals_allowed_by_permission(self, context, permission):
94 if self.permissive:
95 return self.effective_principals(None)
96 else:
97 return []
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 """
109 def __init__(self, string_response=''):
110 self._received = {}
111 self._string_response = string_response
112 self._implementation = MockTemplate(string_response)
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
120 def _set_string_response(self, response):
121 self._string_response = response
122 self._implementation.response = response
124 string_response = property(_get_string_response, _set_string_response)
126 def implementation(self):
127 return self._implementation
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
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
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 )
162 if myval != v:
163 raise AssertionError(
164 '\nasserted value for %s: %r\nactual value: %r'
165 % (k, v, myval)
166 )
167 return True
170class DummyResource:
171 """ A dummy :app:`Pyramid` :term:`resource` object."""
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.
186 .. note:: For backwards compatibility purposes, this class can also
187 be imported as :class:`pyramid.testing.DummyModel`.
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 = {}
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
209 def __getitem__(self, name):
210 """ Return a named subobject (see ``__setitem__``)"""
211 ob = self.subs[name]
212 return ob
214 def __delitem__(self, name):
215 del self.subs[name]
217 def get(self, name, default=None):
218 return self.subs.get(name, default)
220 def values(self):
221 """ Return the values set by __setitem__ """
222 return self.subs.values()
224 def items(self):
225 """ Return the items set by __setitem__ """
226 return self.subs.items()
228 def keys(self):
229 """ Return the keys set by __setitem__ """
230 return self.subs.keys()
232 __iter__ = keys
234 def __nonzero__(self):
235 return True
237 __bool__ = __nonzero__
239 def __len__(self):
240 return len(self.subs)
242 def __contains__(self, name):
243 return name in self.subs
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
263DummyModel = DummyResource # b/w compat (forever)
266@implementer(ISession)
267class DummySession(dict):
268 created = None
269 new = True
271 def changed(self):
272 pass
274 def invalidate(self):
275 self.clear()
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)
282 def pop_flash(self, queue=''):
283 storage = self.pop('_f_' + queue, [])
284 return storage
286 def peek_flash(self, queue=''):
287 storage = self.get('_f_' + queue, [])
288 return storage
290 def new_csrf_token(self):
291 token = text_('0123456789012345678901234567890123456789')
292 self['_csrft_'] = token
293 return token
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
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.
314 The ``params``, ``environ``, ``headers``, ``path``, and
315 ``cookies`` arguments correspond to their :term:`WebOb`
316 equivalents.
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".
323 Extra keyword arguments are assigned as attributes of the request
324 itself.
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.
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 """
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
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)
401 def _get_registry(self):
402 if self._registry is None:
403 return get_current_registry()
404 return self._registry
406 def _set_registry(self, registry):
407 self._registry = registry
409 def _del_registry(self):
410 self._registry = None
412 registry = property(_get_registry, _set_registry, _del_registry)
414 def _set_accept(self, value):
415 self._accept = create_accept_header(value)
417 def _get_accept(self):
418 if self._accept is None:
419 self._accept = create_accept_header(None)
420 return self._accept
422 def _del_accept(self):
423 self._accept = None
425 accept = property(_get_accept, _set_accept, _del_accept)
427 @reify
428 def response(self):
429 f = _get_response_factory(self.registry)
430 return f(self)
433have_zca = True
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.
448 Use this function in the ``setUp`` method of a unittest test case
449 which directly or indirectly uses:
451 - any method of the :class:`pyramid.config.Configurator`
452 object returned by this function.
454 - the :func:`pyramid.threadlocal.get_current_registry` or
455 :func:`pyramid.threadlocal.get_current_request` functions.
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.
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.
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) .
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.
487 If ``settings`` is not ``None``, it must be a dictionary representing the
488 values passed to a Configurator as its ``settings=`` argument.
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`.
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
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.
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
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
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)
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."""
595 def __init__(self, name, factory):
596 self.name = name
597 self.factory = factory # the "real" renderer factory reg'd previously
598 self.renderers = {}
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
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
623class MockTemplate(object):
624 def __init__(self, response):
625 self._received = {}
626 self.response = response
628 def __getattr__(self, attrname):
629 return self
631 def __getitem__(self, attrname):
632 return self
634 def __call__(self, *arg, **kw):
635 self._received.update(kw)
636 return self.response
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
649 def decorator(func):
650 if isinstance(func, class_types):
651 if skip:
652 return None
653 else:
654 return func
655 else:
657 def wrapper(*args, **kw):
658 if skip:
659 return
660 return func(*args, **kw)
662 wrapper.__name__ = func.__name__
663 wrapper.__doc__ = func.__doc__
664 return wrapper
666 return decorator
669skip_on.os_name = os.name # for testing
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.
678 This context manager calls :func:`pyramid.testing.setUp` when
679 entering and :func:`pyramid.testing.tearDown` when exiting.
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.
684 This context manager allows you to write test code like this:
686 .. code-block:: python
687 :linenos:
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)