Michael Merickel
2018-10-15 433efe06191a7007ca8c5bf8fafee5c7c1439ebb
commit | author | age
6a7914 1 import copy
35c5a3 2 import os
fbbb20 3 from contextlib import contextmanager
6a7914 4
0c1c39 5 from zope.interface import (
CM 6     implementer,
7     alsoProvides,
8     )
deb0dc 9
0c1c39 10 from pyramid.interfaces import (
CM 11     IRequest,
12     ISession,
13     )
17b8bc 14
0c1c39 15 from pyramid.compat import (
CM 16     PY3,
17     PYPY,
18     class_types,
183804 19     text_,
0c1c39 20     )
CM 21
427a02 22 from pyramid.config import Configurator
a7b1a9 23 from pyramid.decorator import reify
d24055 24 from pyramid.path import caller_package
25c64c 25 from pyramid.response import _get_response_factory
b60bdb 26 from pyramid.registry import Registry
0c1c39 27
CM 28 from pyramid.security import (
29     Authenticated,
30     Everyone,
3c2f95 31     AuthenticationAPIMixin,
MR 32     AuthorizationAPIMixin,
0c1c39 33     )
CM 34
35 from pyramid.threadlocal import (
36     get_current_registry,
37     manager,
38     )
39
330164 40 from pyramid.i18n import LocalizerRequestMixin
8fe57d 41 from pyramid.request import CallbackMethodsMixin
fb90f0 42 from pyramid.url import URLMethodsMixin
303abc 43 from pyramid.util import InstancePropertyMixin
7f1174 44 from pyramid.view import ViewMethodsMixin
303abc 45
13c923 46
7ec9e7 47 _marker = object()
e8b295 48
927acd 49 class DummyRootFactory(object):
CM 50     __parent__ = None
51     __name__ = None
e0f40e 52     def __init__(self, request):
CM 53         if 'bfg.routes.matchdict' in request:
54             self.__dict__.update(request['bfg.routes.matchdict'])
927acd 55
250c02 56 class DummySecurityPolicy(object):
a1a9fb 57     """ A standin for both an IAuthentication and IAuthorization policy """
1273d5 58     def __init__(self, userid=None, groupids=(), permissive=True,
CM 59                  remember_result=None, forget_result=None):
deb0dc 60         self.userid = userid
CM 61         self.groupids = groupids
a1a9fb 62         self.permissive = permissive
1273d5 63         if remember_result is None:
CM 64             remember_result = []
65         if forget_result is None:
66             forget_result = []
67         self.remember_result = remember_result
68         self.forget_result = forget_result
deb0dc 69
7ec9e7 70     def authenticated_userid(self, request):
deb0dc 71         return self.userid
CM 72
2526d8 73     def unauthenticated_userid(self, request):
deb0dc 74         return self.userid
CM 75
7ec9e7 76     def effective_principals(self, request):
deb0dc 77         effective_principals = [Everyone]
CM 78         if self.userid:
79             effective_principals.append(Authenticated)
80             effective_principals.append(self.userid)
81             effective_principals.extend(self.groupids)
82         return effective_principals
83
c7afe4 84     def remember(self, request, userid, **kw):
KOP 85         self.remembered = userid
1273d5 86         return self.remember_result
a1a9fb 87
7ec9e7 88     def forget(self, request):
1273d5 89         self.forgotten = True
CM 90         return self.forget_result
a1a9fb 91
CM 92     def permits(self, context, principals, permission):
93         return self.permissive
94
95     def principals_allowed_by_permission(self, context, permission):
7ec9e7 96         return self.effective_principals(None)
deb0dc 97
250c02 98 class DummyTemplateRenderer(object):
7d32df 99     """
CM 100     An instance of this class is returned from
d533b1 101     :meth:`pyramid.config.Configurator.testing_add_renderer`.  It has a
addf99 102     helper function (``assert_``) that makes it possible to make an
CM 103     assertion which compares data passed to the renderer by the view
104     function against expected key/value pairs.
7d32df 105     """
e02ba1 106     def __init__(self, string_response=''):
7d32df 107         self._received = {}
250c02 108         self._string_response = string_response
CM 109         self._implementation = MockTemplate(string_response)
110
111     # For in-the-wild test code that doesn't create its own renderer,
112     # but mutates our internals instead.  When all you read is the
113     # source code, *everything* is an API!
114     def _get_string_response(self):
115         return self._string_response
116     def _set_string_response(self, response):
117         self._string_response = response
118         self._implementation.response = response
119     string_response = property(_get_string_response, _set_string_response)
120
deb0dc 121     def implementation(self):
250c02 122         return self._implementation
c5f557 123
e46105 124     def __call__(self, kw, system=None):
250c02 125         if system:
CM 126             self._received.update(system)
7d32df 127         self._received.update(kw)
e02ba1 128         return self.string_response
deb0dc 129
7d32df 130     def __getattr__(self, k):
db0467 131         """ Backwards compatibility """
7d32df 132         val = self._received.get(k, _marker)
CM 133         if val is _marker:
250c02 134             val = self._implementation._received.get(k, _marker)
CM 135             if val is _marker:
136                 raise AttributeError(k)
7d32df 137         return val
CM 138
139     def assert_(self, **kw):
140         """ Accept an arbitrary set of assertion key/value pairs.  For
141         each assertion key/value pair assert that the renderer
aaedf5 142         (eg. :func:`pyramid.renderers.render_to_response`)
addf99 143         received the key with a value that equals the asserted
CM 144         value. If the renderer did not receive the key at all, or the
145         value received by the renderer doesn't match the assertion
146         value, raise an :exc:`AssertionError`."""
7d32df 147         for k, v in kw.items():
CM 148             myval = self._received.get(k, _marker)
149             if myval is _marker:
250c02 150                 myval = self._implementation._received.get(k, _marker)
CM 151                 if myval is _marker:
152                     raise AssertionError(
153                         'A value for key "%s" was not passed to the renderer'
154                         % k)
c5f557 155
7d32df 156             if myval != v:
CM 157                 raise AssertionError(
158                     '\nasserted value for %s: %r\nactual value: %r' % (
25c64c 159                         k, v, myval))
7d32df 160         return True
25c64c 161
7d32df 162
3e2f12 163 class DummyResource:
CM 164     """ A dummy :app:`Pyramid` :term:`resource` object."""
ab4e7b 165     def __init__(self, __name__=None, __parent__=None, __provides__=None,
CM 166                  **kw):
3e2f12 167         """ The resource's ``__name__`` attribute will be set to the
CM 168         value of the ``__name__`` argument, and the resource's
addf99 169         ``__parent__`` attribute will be set to the value of the
CM 170         ``__parent__`` argument.  If ``__provides__`` is specified, it
171         should be an interface object or tuple of interface objects
3e2f12 172         that will be attached to the resulting resource via
addf99 173         :func:`zope.interface.alsoProvides`. Any extra keywords passed
CM 174         in the ``kw`` argumnent will be set as direct attributes of
fec457 175         the resource object.
CM 176
177         .. note:: For backwards compatibility purposes, this class can also
178                   be imported as :class:`pyramid.testing.DummyModel`.
179
180         """
6a7914 181         self.__name__ = __name__
CM 182         self.__parent__ = __parent__
ab4e7b 183         if __provides__ is not None:
CM 184             alsoProvides(self, __provides__)
6a7914 185         self.kw = kw
CM 186         self.__dict__.update(**kw)
deb0dc 187         self.subs = {}
CM 188
189     def __setitem__(self, name, val):
a9a8a2 190         """ When the ``__setitem__`` method is called, the object
6a7914 191         passed in as ``val`` will be decorated with a ``__parent__``
3e2f12 192         attribute pointing at the dummy resource and a ``__name__``
a9a8a2 193         attribute that is the value of ``name``.  The value will then
3e2f12 194         be returned when dummy resource's ``__getitem__`` is called with
a9a8a2 195         the name ``name```."""
deb0dc 196         val.__name__ = name
CM 197         val.__parent__ = self
198         self.subs[name] = val
c5f557 199
deb0dc 200     def __getitem__(self, name):
a9a8a2 201         """ Return a named subobject (see ``__setitem__``)"""
deb0dc 202         ob = self.subs[name]
CM 203         return ob
6a7914 204
ee036d 205     def __delitem__(self, name):
CM 206         del self.subs[name]
207
40232e 208     def get(self, name, default=None):
CM 209         return self.subs.get(name, default)
210
e8ebc2 211     def values(self):
CM 212         """ Return the values set by __setitem__ """
213         return self.subs.values()
214
215     def items(self):
216         """ Return the items set by __setitem__ """
217         return self.subs.items()
218
219     def keys(self):
220         """ Return the keys set by __setitem__ """
221         return self.subs.keys()
222
40232e 223     __iter__ = keys
CM 224
42b1e9 225     def __nonzero__(self):
TS 226         return True
227
050b71 228     __bool__ = __nonzero__
CM 229
42b1e9 230     def __len__(self):
TS 231         return len(self.subs)
232
e8ebc2 233     def __contains__(self, name):
CM 234         return name in self.subs
c5f557 235
6a7914 236     def clone(self, __name__=_marker, __parent__=_marker, **kw):
3e2f12 237         """ Create a clone of the resource object.  If ``__name__`` or
addf99 238         ``__parent__`` arguments are passed, use these values to
CM 239         override the existing ``__name__`` or ``__parent__`` of the
3e2f12 240         resource.  If any extra keyword args are passed in via the ``kw``
b74cd4 241         argument, use these keywords to add to or override existing
3e2f12 242         resource keywords (attributes)."""
6a7914 243         oldkw = self.kw.copy()
CM 244         oldkw.update(kw)
245         inst = self.__class__(self.__name__, self.__parent__, **oldkw)
246         inst.subs = copy.deepcopy(self.subs)
247         if __name__ is not _marker:
248             inst.__name__ = __name__
249         if __parent__ is not _marker:
250             inst.__parent__ = __parent__
251         return inst
3e2f12 252
CM 253 DummyModel = DummyResource # b/w compat (forever)
6a7914 254
3b7334 255 @implementer(ISession)
51b2e8 256 class DummySession(dict):
CM 257     created = None
258     new = True
259     def changed(self):
260         pass
261
262     def invalidate(self):
263         self.clear()
264
265     def flash(self, msg, queue='', allow_duplicate=True):
266         storage = self.setdefault('_f_' + queue, [])
267         if allow_duplicate or (msg not in storage):
268             storage.append(msg)
269
270     def pop_flash(self, queue=''):
271         storage = self.pop('_f_' + queue, [])
272         return storage
273
274     def peek_flash(self, queue=''):
275         storage = self.get('_f_' + queue, [])
276         return storage
277
278     def new_csrf_token(self):
183804 279         token = text_('0123456789012345678901234567890123456789')
51b2e8 280         self['_csrft_'] = token
CM 281         return token
282
283     def get_csrf_token(self):
4d2003 284         token = self.get('_csrft_', None)
RB 285         if token is None:
286             token = self.new_csrf_token()
287         return token
288
3b7334 289 @implementer(IRequest)
0184b5 290 class DummyRequest(
CM 291     URLMethodsMixin,
292     CallbackMethodsMixin,
293     InstancePropertyMixin,
294     LocalizerRequestMixin,
295     AuthenticationAPIMixin,
296     AuthorizationAPIMixin,
7f1174 297     ViewMethodsMixin,
0184b5 298     ):
097d37 299     """ A DummyRequest object (incompletely) imitates a :term:`request` object.
c5f557 300
8b1f6e 301     The ``params``, ``environ``, ``headers``, ``path``, and
22d325 302     ``cookies`` arguments correspond to their :term:`WebOb`
8b1f6e 303     equivalents.
6801b5 304
TS 305     The ``post`` argument,  if passed, populates the request's
306     ``POST`` attribute, but *not* ``params``, in order to allow testing
307     that the app accepts data for a given view only from POST requests.
308     This argument also sets ``self.method`` to "POST".
309
310     Extra keyword arguments are assigned as attributes of the request
311     itself.
3e2f12 312
097d37 313     Note that DummyRequest does not have complete fidelity with a "real"
CM 314     request.  For example, by default, the DummyRequest ``GET`` and ``POST``
315     attributes are of type ``dict``, unlike a normal Request's GET and POST,
316     which are of type ``MultiDict``. If your code uses the features of
07cb8f 317     MultiDict, you should either use a real :class:`pyramid.request.Request`
097d37 318     or adapt your DummyRequest by replacing the attributes with ``MultiDict``
CM 319     instances.
320
321     Other similar incompatibilities exist.  If you need all the features of
322     a Request, use the :class:`pyramid.request.Request` class itself rather
323     than this class while writing tests.
6801b5 324     """
516729 325     method = 'GET'
CM 326     application_url = 'http://example.com'
327     host = 'example.com:80'
767e44 328     domain = 'example.com'
2dc0b5 329     content_length = 0
c5f557 330     query_string = ''
f2681a 331     charset = 'UTF-8'
0a0edf 332     script_name = ''
a7b1a9 333     _registry = None
849196 334     request_iface = IRequest
a7b1a9 335
516729 336     def __init__(self, params=None, environ=None, headers=None, path='/',
6801b5 337                  cookies=None, post=None, **kw):
a9a8a2 338         if environ is None:
CM 339             environ = {}
340         if params is None:
341             params = {}
342         if headers is None:
343             headers = {}
516729 344         if cookies is None:
CM 345             cookies = {}
a9a8a2 346         self.environ = environ
CM 347         self.headers = headers
168c10 348         self.params = params
516729 349         self.cookies = cookies
927acd 350         self.matchdict = {}
168c10 351         self.GET = params
6801b5 352         if post is not None:
TS 353             self.method = 'POST'
354             self.POST = post
355         else:
168c10 356             self.POST = params
a9a8a2 357         self.host_url = self.application_url
CM 358         self.path_url = self.application_url
516729 359         self.url = self.application_url
a9a8a2 360         self.path = path
CM 361         self.path_info = path
362         self.script_name = ''
363         self.path_qs = ''
364         self.body = ''
516729 365         self.view_name = ''
1cd598 366         self.subpath = ()
CM 367         self.traversed = ()
368         self.virtual_root_path = ()
516729 369         self.context = None
1cd598 370         self.root = None
CM 371         self.virtual_root = None
516729 372         self.marshalled = params # repoze.monty
51b2e8 373         self.session = DummySession()
a9a8a2 374         self.__dict__.update(kw)
844e98 375
a7b1a9 376     def _get_registry(self):
CM 377         if self._registry is None:
378             return get_current_registry()
379         return self._registry
380
381     def _set_registry(self, registry):
382         self._registry = registry
383
384     def _del_registry(self):
385         self._registry = None
386
387     registry = property(_get_registry, _set_registry, _del_registry)
388
389     @reify
390     def response(self):
25c64c 391         f = _get_response_factory(self.registry)
32cb80 392         return f(self)
8af47b 393
bce463 394 have_zca = True
25c64c 395
bce463 396
2a13b0 397 def setUp(registry=None, request=None, hook_zca=True, autocommit=True,
d24055 398           settings=None, package=None):
a7e6f2 399     """
fd5ae9 400     Set :app:`Pyramid` registry and request thread locals for the
addf99 401     duration of a single unit test.
d0b398 402
7ac5aa 403     Use this function in the ``setUp`` method of a unittest test case
fe399b 404     which directly or indirectly uses:
CM 405
aff443 406     - any method of the :class:`pyramid.config.Configurator`
addf99 407       object returned by this function.
CM 408
c81aad 409     - the :func:`pyramid.threadlocal.get_current_registry` or
CM 410       :func:`pyramid.threadlocal.get_current_request` functions.
d0b398 411
d533b1 412     If you use the ``get_current_*`` functions (or call :app:`Pyramid` code
CM 413     that uses these functions) without calling ``setUp``,
414     :func:`pyramid.threadlocal.get_current_registry` will return a *global*
415     :term:`application registry`, which may cause unit tests to not be
416     isolated with respect to registrations they perform.
d0b398 417
CM 418     If the ``registry`` argument is ``None``, a new empty
addf99 419     :term:`application registry` will be created (an instance of the
c81aad 420     :class:`pyramid.registry.Registry` class).  If the ``registry``
addf99 421     argument is not ``None``, the value passed in should be an
c81aad 422     instance of the :class:`pyramid.registry.Registry` class or a
addf99 423     suitable testing analogue.
CM 424
425     After ``setUp`` is finished, the registry returned by the
8a7d6c 426     :func:`pyramid.threadlocal.get_current_registry` function will
d0b398 427     be the passed (or constructed) registry until
c81aad 428     :func:`pyramid.testing.tearDown` is called (or
CM 429     :func:`pyramid.testing.setUp` is called again) .
d0b398 430
addf99 431     If the ``hook_zca`` argument is ``True``, ``setUp`` will attempt
CM 432     to perform the operation ``zope.component.getSiteManager.sethook(
c81aad 433     pyramid.threadlocal.get_current_registry)``, which will cause
d0b398 434     the :term:`Zope Component Architecture` global API
addf99 435     (e.g. :func:`zope.component.getSiteManager`,
CM 436     :func:`zope.component.getAdapter`, and so on) to use the registry
437     constructed by ``setUp`` as the value it returns from
438     :func:`zope.component.getSiteManager`.  If the
439     :mod:`zope.component` package cannot be imported, or if
440     ``hook_zca`` is ``False``, the hook will not be set.
441
d24055 442     If ``settings`` is not ``None``, it must be a dictionary representing the
2a13b0 443     values passed to a Configurator as its ``settings=`` argument.
d24055 444
MM 445     If ``package`` is ``None`` it will be set to the caller's package. The
446     ``package`` setting in the :class:`pyramid.config.Configurator` will
447     affect any relative imports made via
448     :meth:`pyramid.config.Configurator.include` or
449     :meth:`pyramid.config.Configurator.maybe_dotted`.
2a13b0 450
addf99 451     This function returns an instance of the
aff443 452     :class:`pyramid.config.Configurator` class, which can be
addf99 453     used for further configuration to set up an environment suitable
CM 454     for a unit or integration test.  The ``registry`` attribute
455     attached to the Configurator instance represents the 'current'
456     :term:`application registry`; the same registry will be returned
c81aad 457     by :func:`pyramid.threadlocal.get_current_registry` during the
addf99 458     execution of the test.
d322ac 459     """
cbfafb 460     manager.clear()
d0b398 461     if registry is None:
CM 462         registry = Registry('testing')
d24055 463     if package is None:
MM 464         package = caller_package()
465     config = Configurator(registry=registry, autocommit=autocommit,
466                           package=package)
2a13b0 467     if settings is None:
CM 468         settings = {}
469     if getattr(registry, 'settings', None) is None:
470         config._set_settings(settings)
250c02 471     if hasattr(registry, 'registerUtility'):
CM 472         # Sometimes nose calls us with a non-registry object because
473         # it thinks this function is module test setup.  Likewise,
474         # someone may be passing us an esoteric "dummy" registry, and
475         # the below won't succeed if it doesn't have a registerUtility
476         # method.
121f45 477         config.add_default_response_adapters()
b7e92d 478         config.add_default_renderers()
121f45 479         config.add_default_accept_view_order()
a00621 480         config.add_default_view_predicates()
ceb1f2 481         config.add_default_view_derivers()
9c8ec5 482         config.add_default_route_predicates()
35b0e3 483         config.add_default_tweens()
4b3603 484         config.add_default_security()
c0d4a1 485     config.commit()
bce463 486     global have_zca
CM 487     try:
488         have_zca and hook_zca and config.hook_zca()
338cb9 489     except ImportError: # pragma: no cover
CM 490         # (dont choke on not being able to import z.component)
bce463 491         have_zca = False
bc857e 492     config.begin(request=request)
addf99 493     return config
d0b398 494
CM 495 def tearDown(unhook_zca=True):
b9b3b1 496     """Undo the effects of :func:`pyramid.testing.setUp`.  Use this
addf99 497     function in the ``tearDown`` method of a unit test that uses
c81aad 498     :func:`pyramid.testing.setUp` in its ``setUp`` method.
d0b398 499
CM 500     If the ``unhook_zca`` argument is ``True`` (the default), call
addf99 501     :func:`zope.component.getSiteManager.reset`.  This undoes the
688a93 502     action of :func:`pyramid.testing.setUp` when called with the
addf99 503     argument ``hook_zca=True``.  If :mod:`zope.component` cannot be
688a93 504     imported, ``unhook_zca`` is set to ``False``.
d0b398 505     """
bce463 506     global have_zca
CM 507     if unhook_zca and have_zca:
7696aa 508         try:
CM 509             from zope.component import getSiteManager
510             getSiteManager.reset()
511         except ImportError: # pragma: no cover
bce463 512             have_zca = False
d0b398 513     info = manager.pop()
CM 514     manager.clear()
515     if info is not None:
bc857e 516         registry = info['registry']
CM 517         if hasattr(registry, '__init__') and hasattr(registry, '__name__'):
7ac5aa 518             try:
bc857e 519                 registry.__init__(registry.__name__)
7ac5aa 520             except TypeError:
99fc64 521                 # calling __init__ is largely for the benefit of
CM 522                 # people who want to use the global ZCA registry;
523                 # however maybe somebody's using a registry we don't
524                 # understand, let's not blow up
7ac5aa 525                 pass
17b8bc 526
d0b398 527 def cleanUp(*arg, **kw):
c0d0a3 528     """ An alias for :func:`pyramid.testing.setUp`. """
d24055 529     package = kw.get('package', None)
MM 530     if package is None:
531         package = caller_package()
532         kw['package'] = package
addf99 533     return setUp(*arg, **kw)
59c5df 534
250c02 535 class DummyRendererFactory(object):
CM 536     """ Registered by
d533b1 537     :meth:`pyramid.config.Configurator.testing_add_renderer` as
250c02 538     a dummy renderer factory.  The indecision about what to use as a
CM 539     key (a spec vs. a relative name) is caused by test suites in the
540     wild believing they can register either.  The ``factory`` argument
541     passed to this constructor is usually the *real* template renderer
542     factory, found when ``testing_add_renderer`` is called."""
543     def __init__(self, name, factory):
544         self.name = name
545         self.factory = factory # the "real" renderer factory reg'd previously
546         self.renderers = {}
547
548     def add(self, spec, renderer):
549         self.renderers[spec] = renderer
550         if ':' in spec:
551             package, relative = spec.split(':', 1)
552             self.renderers[relative] = renderer
553
3d9dd0 554     def __call__(self, info):
f5fa3f 555         spec = info.name
250c02 556         renderer = self.renderers.get(spec)
CM 557         if renderer is None:
558             if ':' in spec:
559                 package, relative = spec.split(':', 1)
560                 renderer = self.renderers.get(relative)
561             if renderer is None:
562                 if self.factory:
3d9dd0 563                     renderer = self.factory(info)
250c02 564                 else:
CM 565                     raise KeyError('No testing renderer registered for %r' %
566                                    spec)
567         return renderer
c5f557 568
CR 569
250c02 570 class MockTemplate(object):
CM 571     def __init__(self, response):
572         self._received = {}
573         self.response = response
574     def __getattr__(self, attrname):
575         return self
576     def __getitem__(self, attrname):
577         return self
578     def __call__(self, *arg, **kw):
579         self._received.update(kw)
580         return self.response
35c5a3 581
160865 582 def skip_on(*platforms): # pragma: no  cover
a84e17 583     skip = False
CM 584     for platform in platforms:
585         if skip_on.os_name.startswith(platform):
586             skip = True
160865 587         if platform == 'pypy' and PYPY:
a84e17 588             skip = True
160865 589         if platform == 'py3' and PY3:
a84e17 590             skip = True
25c64c 591
35c5a3 592     def decorator(func):
a84e17 593         if isinstance(func, class_types):
25c64c 594             if skip:
JA 595                 return None
596             else:
597                 return func
a84e17 598         else:
CM 599             def wrapper(*args, **kw):
600                 if skip:
35c5a3 601                     return
a84e17 602                 return func(*args, **kw)
CM 603             wrapper.__name__ = func.__name__
604             wrapper.__doc__ = func.__doc__
605             return wrapper
35c5a3 606     return decorator
CM 607 skip_on.os_name = os.name # for testing
fbbb20 608
BS 609 @contextmanager
610 def testConfig(registry=None,
611         request=None,
612         hook_zca=True,
613         autocommit=True,
614         settings=None):
615     """Returns a context manager for test set up.
616
aaedf5 617     This context manager calls :func:`pyramid.testing.setUp` when
fbbb20 618     entering and :func:`pyramid.testing.tearDown` when exiting.
BS 619
aaedf5 620     All arguments are passed directly to :func:`pyramid.testing.setUp`.
fbbb20 621     If the ZCA is hooked, it will always be un-hooked in tearDown.
BS 622
623     This context manager allows you to write test code like this:
624
625     .. code-block:: python
626         :linenos:
627
628         with testConfig() as config:
629             config.add_route('bar', '/bar/{id}')
630             req = DummyRequest()
b2fb29 631             resp = myview(req)
fbbb20 632     """
BS 633     config = setUp(registry=registry,
634             request=request,
635             hook_zca=hook_zca,
636             autocommit=autocommit,
637             settings=settings)
638     try:
639         yield config
640     finally:
641         tearDown(unhook_zca=hook_zca)