Steve Piercy
2017-09-23 92b4e7e79fae9298dfdef29d6d26b17a0e8855e3
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.
b7e92d 477         config.add_default_renderers()
a00621 478         config.add_default_view_predicates()
ceb1f2 479         config.add_default_view_derivers()
9c8ec5 480         config.add_default_route_predicates()
35b0e3 481         config.add_default_tweens()
c0d4a1 482     config.commit()
bce463 483     global have_zca
CM 484     try:
485         have_zca and hook_zca and config.hook_zca()
338cb9 486     except ImportError: # pragma: no cover
CM 487         # (dont choke on not being able to import z.component)
bce463 488         have_zca = False
bc857e 489     config.begin(request=request)
addf99 490     return config
d0b398 491
CM 492 def tearDown(unhook_zca=True):
b9b3b1 493     """Undo the effects of :func:`pyramid.testing.setUp`.  Use this
addf99 494     function in the ``tearDown`` method of a unit test that uses
c81aad 495     :func:`pyramid.testing.setUp` in its ``setUp`` method.
d0b398 496
CM 497     If the ``unhook_zca`` argument is ``True`` (the default), call
addf99 498     :func:`zope.component.getSiteManager.reset`.  This undoes the
688a93 499     action of :func:`pyramid.testing.setUp` when called with the
addf99 500     argument ``hook_zca=True``.  If :mod:`zope.component` cannot be
688a93 501     imported, ``unhook_zca`` is set to ``False``.
d0b398 502     """
bce463 503     global have_zca
CM 504     if unhook_zca and have_zca:
7696aa 505         try:
CM 506             from zope.component import getSiteManager
507             getSiteManager.reset()
508         except ImportError: # pragma: no cover
bce463 509             have_zca = False
d0b398 510     info = manager.pop()
CM 511     manager.clear()
512     if info is not None:
bc857e 513         registry = info['registry']
CM 514         if hasattr(registry, '__init__') and hasattr(registry, '__name__'):
7ac5aa 515             try:
bc857e 516                 registry.__init__(registry.__name__)
7ac5aa 517             except TypeError:
99fc64 518                 # calling __init__ is largely for the benefit of
CM 519                 # people who want to use the global ZCA registry;
520                 # however maybe somebody's using a registry we don't
521                 # understand, let's not blow up
7ac5aa 522                 pass
17b8bc 523
d0b398 524 def cleanUp(*arg, **kw):
c0d0a3 525     """ An alias for :func:`pyramid.testing.setUp`. """
d24055 526     package = kw.get('package', None)
MM 527     if package is None:
528         package = caller_package()
529         kw['package'] = package
addf99 530     return setUp(*arg, **kw)
59c5df 531
250c02 532 class DummyRendererFactory(object):
CM 533     """ Registered by
d533b1 534     :meth:`pyramid.config.Configurator.testing_add_renderer` as
250c02 535     a dummy renderer factory.  The indecision about what to use as a
CM 536     key (a spec vs. a relative name) is caused by test suites in the
537     wild believing they can register either.  The ``factory`` argument
538     passed to this constructor is usually the *real* template renderer
539     factory, found when ``testing_add_renderer`` is called."""
540     def __init__(self, name, factory):
541         self.name = name
542         self.factory = factory # the "real" renderer factory reg'd previously
543         self.renderers = {}
544
545     def add(self, spec, renderer):
546         self.renderers[spec] = renderer
547         if ':' in spec:
548             package, relative = spec.split(':', 1)
549             self.renderers[relative] = renderer
550
3d9dd0 551     def __call__(self, info):
f5fa3f 552         spec = info.name
250c02 553         renderer = self.renderers.get(spec)
CM 554         if renderer is None:
555             if ':' in spec:
556                 package, relative = spec.split(':', 1)
557                 renderer = self.renderers.get(relative)
558             if renderer is None:
559                 if self.factory:
3d9dd0 560                     renderer = self.factory(info)
250c02 561                 else:
CM 562                     raise KeyError('No testing renderer registered for %r' %
563                                    spec)
564         return renderer
c5f557 565
CR 566
250c02 567 class MockTemplate(object):
CM 568     def __init__(self, response):
569         self._received = {}
570         self.response = response
571     def __getattr__(self, attrname):
572         return self
573     def __getitem__(self, attrname):
574         return self
575     def __call__(self, *arg, **kw):
576         self._received.update(kw)
577         return self.response
35c5a3 578
160865 579 def skip_on(*platforms): # pragma: no  cover
a84e17 580     skip = False
CM 581     for platform in platforms:
582         if skip_on.os_name.startswith(platform):
583             skip = True
160865 584         if platform == 'pypy' and PYPY:
a84e17 585             skip = True
160865 586         if platform == 'py3' and PY3:
a84e17 587             skip = True
25c64c 588
35c5a3 589     def decorator(func):
a84e17 590         if isinstance(func, class_types):
25c64c 591             if skip:
JA 592                 return None
593             else:
594                 return func
a84e17 595         else:
CM 596             def wrapper(*args, **kw):
597                 if skip:
35c5a3 598                     return
a84e17 599                 return func(*args, **kw)
CM 600             wrapper.__name__ = func.__name__
601             wrapper.__doc__ = func.__doc__
602             return wrapper
35c5a3 603     return decorator
CM 604 skip_on.os_name = os.name # for testing
fbbb20 605
BS 606 @contextmanager
607 def testConfig(registry=None,
608         request=None,
609         hook_zca=True,
610         autocommit=True,
611         settings=None):
612     """Returns a context manager for test set up.
613
aaedf5 614     This context manager calls :func:`pyramid.testing.setUp` when
fbbb20 615     entering and :func:`pyramid.testing.tearDown` when exiting.
BS 616
aaedf5 617     All arguments are passed directly to :func:`pyramid.testing.setUp`.
fbbb20 618     If the ZCA is hooked, it will always be un-hooked in tearDown.
BS 619
620     This context manager allows you to write test code like this:
621
622     .. code-block:: python
623         :linenos:
624
625         with testConfig() as config:
626             config.add_route('bar', '/bar/{id}')
627             req = DummyRequest()
7c566c 628             resp = myview(req)
fbbb20 629     """
BS 630     config = setUp(registry=registry,
631             request=request,
632             hook_zca=hook_zca,
633             autocommit=autocommit,
634             settings=settings)
635     try:
636         yield config
637     finally:
638         tearDown(unhook_zca=hook_zca)