Repository: Pylons/pyramid

Issue:
make pyramid.paster.bootstrap into a context manager
This would just improve the API such that users could automatically call the closer in a reliable way.

``` python
from pyramid.paster import bootstrap

with bootstrap('development.ini') as env:
    req = env['request']
```

This change would also affect `pyramid.scripting.prepare` which is what `bootstrap` uses under the hood to construct the `env`.



Discussion:

Resolving patch:
diff --git a/pyramid/paster.py b/pyramid/paster.py
--- a/pyramid/paster.py
+++ b/pyramid/paster.py
@@ -129,8 +129,22 @@ def bootstrap(config_uri, request=None, options=None):
     {'http_port': 8080} and then use %(http_port)s in the
     config file.
 
+    This function may be used as a context manager to call the ``closer``
+    automatically:
+
+    .. code-block:: python
+
+       with bootstrap('development.ini') as env:
+           request = env['request']
+           # ...
+
     See :ref:`writing_a_script` for more information about how to use this
     function.
+
+    .. versionchanged:: 1.8
+
+       Added the ability to use the return value as a context manager.
+
     """
     app = get_app(config_uri, options=options)
     env = prepare(request)
diff --git a/pyramid/scripting.py b/pyramid/scripting.py
--- a/pyramid/scripting.py
+++ b/pyramid/scripting.py
@@ -56,12 +56,25 @@ def prepare(request=None, registry=None):
     ``root`` returned is the application's root resource object.  The
     ``closer`` returned is a callable (accepting no arguments) that
     should be called when your scripting application is finished
-    using the root.  ``registry`` is the registry object passed or
-    the last registry loaded into
-    :attr:`pyramid.config.global_registries` if no registry is passed.
+    using the root.  ``registry`` is the resolved registry object.
     ``request`` is the request object passed or the constructed request
     if no request is passed.  ``root_factory`` is the root factory used
     to construct the root.
+
+    This function may be used as a context manager to call the ``closer``
+    automatically:
+
+    .. code-block:: python
+
+       registry = config.registry
+       with prepare(registry) as env:
+           request = env['request']
+           # ...
+
+    .. versionchanged:: 1.8
+
+       Added the ability to use the return value as a context manager.
+
     """
     if registry is None:
         registry = getattr(request, 'registry', global_registries.last)
@@ -85,8 +98,20 @@ def closer():
     root = root_factory(request)
     if getattr(request, 'context', None) is None:
         request.context = root
-    return {'root':root, 'closer':closer, 'registry':registry,
-            'request':request, 'root_factory':root_factory}
+    return AppEnvironment(
+        root=root,
+        closer=closer,
+        registry=registry,
+        request=request,
+        root_factory=root_factory,
+    )
+
+class AppEnvironment(dict):
+    def __enter__(self):
+        return self
+
+    def __exit__(self, type, value, traceback):
+        self['closer']()
 
 def _make_request(path, registry=None):
     """ Return a :meth:`pyramid.request.Request` object anchored at a


Test patch:
diff --git a/pyramid/tests/test_scripting.py b/pyramid/tests/test_scripting.py
--- a/pyramid/tests/test_scripting.py
+++ b/pyramid/tests/test_scripting.py
@@ -134,6 +134,27 @@ def test_it_with_extensions(self):
         root, closer = info['root'], info['closer']
         closer()
 
+    def test_it_is_a_context_manager(self):
+        request = DummyRequest({})
+        registry = request.registry = self._makeRegistry()
+        closer_called = [False]
+        with self._callFUT(request=request) as info:
+            root, request = info['root'], info['request']
+            pushed = self.manager.get()
+            self.assertEqual(pushed['request'], request)
+            self.assertEqual(pushed['registry'], registry)
+            self.assertEqual(pushed['request'].registry, registry)
+            self.assertEqual(root.a, (request,))
+            orig_closer = info['closer']
+            def closer():
+                orig_closer()
+                closer_called[0] = True
+            info['closer'] = closer
+        self.assertTrue(closer_called[0])
+        self.assertEqual(self.default, self.manager.get())
+        self.assertEqual(request.context, root)
+        self.assertEqual(request.registry, registry)
+
 class Test__make_request(unittest.TestCase):
     def _callFUT(self, path='/', registry=None):
         from pyramid.scripting import _make_request