Repository: Pylons/pyramid

Issue:
pyramid.scripting.get_root() triggers AttributeError 
In Pyramid 1.9.1, get_root() triggers an AttributeError on threadlocal_manager:

```
# bin/pshell mything/development.ini 
Python 3.5.2 (default, Nov 23 2017, 16:37:01) 
[GCC 5.4.0 20160609] on linux
Type "help" for more information.

Environment:
  app          The WSGI application.
  registry     Active Pyramid registry.
  request      Active request object.
  root         Root of the default resource tree.
  root_factory Default root factory used to create `root`.

>>> from pyramid.scripting import get_root
>>> x = get_root(app)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/shane/src/pyramidtest/eggs/pyramid-1.9.1-py3.5.egg/pyramid/scripting.py", line 30, in get_root
    app.threadlocal_manager.push(threadlocals)
AttributeError: 'Router' object has no attribute 'threadlocal_manager'
>>>
```


Discussion:
It looks like this is a regression in 1.9 after adding the execution context

Resolving patch:
diff --git a/pyramid/scripting.py b/pyramid/scripting.py
--- a/pyramid/scripting.py
+++ b/pyramid/scripting.py
@@ -8,7 +8,7 @@
 from pyramid.request import Request
 from pyramid.request import apply_request_extensions
 
-from pyramid.threadlocal import manager as threadlocal_manager
+from pyramid.threadlocal import RequestContext
 from pyramid.traversal import DefaultRootFactory
 
 def get_root(app, request=None):
@@ -26,10 +26,11 @@ def get_root(app, request=None):
     registry = app.registry
     if request is None:
         request = _make_request('/', registry)
-    threadlocals = {'registry':registry, 'request':request}
-    app.threadlocal_manager.push(threadlocals)
-    def closer(request=request): # keep request alive via this function default
-        app.threadlocal_manager.pop()
+    request.registry = registry
+    ctx = RequestContext(request)
+    ctx.begin()
+    def closer():
+        ctx.end()
     root = app.root_factory(request)
     return root, closer
 
@@ -87,12 +88,12 @@ def prepare(request=None, registry=None):
     # NB: even though _make_request might have already set registry on
     # request, we reset it in case someone has passed in their own
     # request.
-    request.registry = registry 
-    threadlocals = {'registry':registry, 'request':request}
-    threadlocal_manager.push(threadlocals)
+    request.registry = registry
+    ctx = RequestContext(request)
+    ctx.begin()
     apply_request_extensions(request)
     def closer():
-        threadlocal_manager.pop()
+        ctx.end()
     root_factory = registry.queryUtility(IRootFactory,
                                          default=DefaultRootFactory)
     root = root_factory(request)


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
@@ -8,38 +8,35 @@ def _callFUT(self, app, request=None):
     def _makeRegistry(self):
         return DummyRegistry([DummyFactory])
 
+    def setUp(self):
+        from pyramid.threadlocal import manager
+        self.manager = manager
+        self.default = manager.get()
+
     def test_it_norequest(self):
         registry = self._makeRegistry()
         app = DummyApp(registry=registry)
         root, closer = self._callFUT(app)
-        self.assertEqual(len(app.threadlocal_manager.pushed), 1)
-        pushed = app.threadlocal_manager.pushed[0]
+        self.assertEqual(dummy_root, root)
+        pushed = self.manager.get()
         self.assertEqual(pushed['registry'], registry)
-        self.assertEqual(pushed['request'].registry, app.registry)
-        self.assertEqual(len(app.threadlocal_manager.popped), 0)
+        self.assertEqual(pushed['request'].registry, registry)
+        self.assertEqual(pushed['request'].environ['path'], '/')
         closer()
-        self.assertEqual(len(app.threadlocal_manager.popped), 1)
+        self.assertEqual(self.default, self.manager.get())
 
     def test_it_withrequest(self):
         registry = self._makeRegistry()
         app = DummyApp(registry=registry)
         request = DummyRequest({})
         root, closer = self._callFUT(app, request)
-        self.assertEqual(len(app.threadlocal_manager.pushed), 1)
-        pushed = app.threadlocal_manager.pushed[0]
+        self.assertEqual(dummy_root, root)
+        pushed = self.manager.get()
         self.assertEqual(pushed['registry'], registry)
         self.assertEqual(pushed['request'], request)
-        self.assertEqual(len(app.threadlocal_manager.popped), 0)
+        self.assertEqual(pushed['request'].registry, registry)
         closer()
-        self.assertEqual(len(app.threadlocal_manager.popped), 1)
-
-    def test_it_requestfactory_overridden(self):
-        registry = self._makeRegistry()
-        app = DummyApp(registry=registry)
-        root, closer = self._callFUT(app)
-        self.assertEqual(len(app.threadlocal_manager.pushed), 1)
-        pushed = app.threadlocal_manager.pushed[0]
-        self.assertEqual(pushed['request'].environ['path'], '/')
+        self.assertEqual(self.default, self.manager.get())
 
 class Test_prepare(unittest.TestCase):
     def _callFUT(self, request=None, registry=None):
@@ -206,24 +203,12 @@ def queryUtility(self, iface, default=None): # pragma: no cover
 
 class DummyApp:
     def __init__(self, registry=None):
-        self.threadlocal_manager = DummyThreadLocalManager()
         if registry:
             self.registry = registry
 
     def root_factory(self, environ):
         return dummy_root
 
-class DummyThreadLocalManager:
-    def __init__(self):
-        self.pushed = []
-        self.popped = []
-        
-    def push(self, item):
-        self.pushed.append(item)
-
-    def pop(self):
-        self.popped.append(True)
-        
 class DummyRequest(object):
     matchdict = None
     matched_route = None