Repository: Pylons/pyramid

Issue:
request object in pviews misses custom request methods
Here’s the setup:
- myapp includes mylib
- `mylib.includeme` calls `add_request_method('user_service_client', get_user_service_client, reify=True)`
- myapp uses traversal; there is a UserCollection with a `__getitem__` method that uses request.user_service_client to see if the user ID is found in our user service.
- under pviews, request.user_service_client does not exist, causing traversal and pviews to fail.

After some interesting diving into pyramid.scripting, pyramid.bootstrap and pyramid.scripts.pviews, I found that bootstrap creates a request object and sets the custom attibutes on it (using the IRequestExtensions utility and _set_extensions method), but PViewsCommand._find_view creates a fresh request object.  Thus, my custom request property is not defined when my resources get this request object.

I changed _find_view to take the request object from bootstrap (it already takes the registry) and my pviews command worked.  21 tests failed because of the changed signature; I don’t know if not creating a fresh request will cause issues for other pviews usecases, but unless someone objects, I’ll make a pull request with this change and fixed tests.



Discussion:
I lied: I noticed that pviews did not fail with AttributeError anymore, but I was not paying attention to the output: the matched resource was the root instead of the expected class three traversal levels down, so the fix is definitely more complicated than what I said.

Alternate fix idea: when the new request object is created in pviews, see if IRequestExtensions is registered and if yes, apply it to the new request.  I’ll try that.


Resolving patch:
diff --git a/pyramid/scripts/pviews.py b/pyramid/scripts/pviews.py
--- a/pyramid/scripts/pviews.py
+++ b/pyramid/scripts/pviews.py
@@ -4,6 +4,7 @@
 
 from pyramid.interfaces import IMultiView
 from pyramid.paster import bootstrap
+from pyramid.request import Request
 from pyramid.scripts.common import parse_vars
 
 def main(argv=sys.argv, quiet=False):
@@ -52,7 +53,7 @@ def _find_multi_routes(self, mapper, request):
                 infos.append(info)
         return infos
 
-    def _find_view(self, url, registry):
+    def _find_view(self, request):
         """
         Accept ``url`` and ``registry``; create a :term:`request` and
         find a :app:`Pyramid` view based on introspection of :term:`view
@@ -63,22 +64,19 @@ def _find_view(self, url, registry):
         from pyramid.interfaces import IRequest
         from pyramid.interfaces import IRootFactory
         from pyramid.interfaces import IRouteRequest
-        from pyramid.interfaces import IRequestFactory
         from pyramid.interfaces import IRoutesMapper
         from pyramid.interfaces import IView
         from pyramid.interfaces import IViewClassifier
         from pyramid.interfaces import ITraverser
-        from pyramid.request import Request
         from pyramid.traversal import DefaultRootFactory
         from pyramid.traversal import ResourceTreeTraverser
 
+        registry = request.registry
         q = registry.queryUtility
         root_factory = q(IRootFactory, default=DefaultRootFactory)
         routes_mapper = q(IRoutesMapper)
-        request_factory = q(IRequestFactory, default=Request)
 
         adapters = registry.adapters
-        request = None
 
         @implementer(IMultiView)
         class RoutesMultiView(object):
@@ -111,20 +109,9 @@ def __init__(self, infos, context_iface, root_factory, request):
                             view.__view_attr__ = ''
                         self.views.append((None, view, None))
 
-
-        # create the request
-        environ = {
-            'wsgi.url_scheme':'http',
-            'SERVER_NAME':'localhost',
-            'SERVER_PORT':'8080',
-            'REQUEST_METHOD':'GET',
-            'PATH_INFO':url,
-            }
-        request = request_factory(environ)
         context = None
         routes_multiview = None
         attrs = request.__dict__
-        attrs['registry'] = registry
         request_iface = IRequest
 
         # find the root object
@@ -236,9 +223,10 @@ def run(self):
 
         if not url.startswith('/'):
             url = '/%s' % url
-        env = self.bootstrap[0](config_uri, options=parse_vars(self.args[2:]))
-        registry = env['registry']
-        view = self._find_view(url, registry)
+        request = Request.blank(url)
+        env = self.bootstrap[0](config_uri, options=parse_vars(self.args[2:]),
+                                request=request)
+        view = self._find_view(request)
         self.out('')
         self.out("URL = %s" % url)
         self.out('')
@@ -257,5 +245,6 @@ def run(self):
             else:
                 self.out("    Not found.")
         self.out('')
+        env['closer']()
         return 0
 


Test patch:
diff --git a/pyramid/tests/test_scripts/dummy.py b/pyramid/tests/test_scripts/dummy.py
--- a/pyramid/tests/test_scripts/dummy.py
+++ b/pyramid/tests/test_scripts/dummy.py
@@ -146,10 +146,13 @@ def __init__(self, app=None, registry=None, request=None, root=None,
     def __call__(self, *a, **kw):
         self.a = a
         self.kw = kw
+        registry = kw.get('registry', self.registry)
+        request = kw.get('request', self.request)
+        request.registry = registry
         return {
             'app': self.app,
-            'registry': self.registry,
-            'request': self.request,
+            'registry': registry,
+            'request': request,
             'root': self.root,
             'root_factory': self.root_factory,
             'closer': self.closer,
diff --git a/pyramid/tests/test_scripts/test_pviews.py b/pyramid/tests/test_scripts/test_pviews.py
--- a/pyramid/tests/test_scripts/test_pviews.py
+++ b/pyramid/tests/test_scripts/test_pviews.py
@@ -12,6 +12,12 @@ def _makeOne(self, registry=None):
         cmd.args = ('/foo/bar/myapp.ini#myapp',)
         return cmd
 
+    def _makeRequest(self, url, registry):
+        from pyramid.request import Request
+        request = Request.blank('/a')
+        request.registry = registry
+        return request
+
     def _register_mapper(self, registry, routes):
         from pyramid.interfaces import IRoutesMapper
         mapper = dummy.DummyMapper(*routes)
@@ -22,7 +28,8 @@ def test__find_view_no_match(self):
         registry = Registry()
         self._register_mapper(registry, [])
         command = self._makeOne(registry)
-        result = command._find_view('/a', registry)
+        request = self._makeRequest('/a', registry)
+        result = command._find_view(request)
         self.assertEqual(result, None)
 
     def test__find_view_no_match_multiview_registered(self):
@@ -45,7 +52,8 @@ class View1(object):
                                  IMultiView)
         self._register_mapper(registry, [])
         command = self._makeOne(registry=registry)
-        result = command._find_view('/x', registry)
+        request = self._makeRequest('/x', registry)
+        result = command._find_view(request)
         self.assertEqual(result, None)
 
     def test__find_view_traversal(self):
@@ -65,7 +73,8 @@ def view1(): pass
                                  IView, name='a')
         self._register_mapper(registry, [])
         command = self._makeOne(registry=registry)
-        result = command._find_view('/a', registry)
+        request = self._makeRequest('/a', registry)
+        result = command._find_view(request)
         self.assertEqual(result, view1)
 
     def test__find_view_traversal_multiview(self):
@@ -89,7 +98,8 @@ class View1(object):
                                  IMultiView, name='a')
         self._register_mapper(registry, [])
         command = self._makeOne(registry=registry)
-        result = command._find_view('/a', registry)
+        request = self._makeRequest('/a', registry)
+        result = command._find_view(request)
         self.assertEqual(result, view)
 
     def test__find_view_route_no_multiview(self):
@@ -117,7 +127,8 @@ def __init__(self, request):
                   dummy.DummyRoute('b', '/b', factory=Factory)]
         self._register_mapper(registry, routes)
         command = self._makeOne(registry=registry)
-        result = command._find_view('/a', registry)
+        request = self._makeRequest('/a', registry)
+        result = command._find_view(request)
         self.assertEqual(result, view)
 
     def test__find_view_route_multiview_no_view_registered(self):
@@ -147,7 +158,8 @@ def __init__(self, request):
                   dummy.DummyRoute('b', '/a', matchdict={})]
         self._register_mapper(registry, routes)
         command = self._makeOne(registry=registry)
-        result = command._find_view('/a', registry)
+        request = self._makeRequest('/a', registry)
+        result = command._find_view(request)
         self.assertTrue(IMultiView.providedBy(result))
 
     def test__find_view_route_multiview(self):
@@ -185,7 +197,8 @@ def __init__(self, request):
                   dummy.DummyRoute('b', '/a', matchdict={})]
         self._register_mapper(registry, routes)
         command = self._makeOne(registry=registry)
-        result = command._find_view('/a', registry)
+        request = self._makeRequest('/a', registry)
+        result = command._find_view(request)
         self.assertTrue(IMultiView.providedBy(result))
         self.assertEqual(len(result.views), 2)
         self.assertTrue((None, view1, None) in result.views)
@@ -228,7 +241,7 @@ def test_views_command_not_found(self):
         command = self._makeOne(registry=registry)
         L = []
         command.out = L.append
-        command._find_view = lambda arg1, arg2: None
+        command._find_view = lambda arg1: None
         command.args = ('/foo/bar/myapp.ini#myapp', '/a')
         result = command.run()
         self.assertEqual(result, 0)
@@ -241,7 +254,7 @@ def test_views_command_not_found_url_starts_without_slash(self):
         command = self._makeOne(registry=registry)
         L = []
         command.out = L.append
-        command._find_view = lambda arg1, arg2: None
+        command._find_view = lambda arg1: None
         command.args = ('/foo/bar/myapp.ini#myapp', 'a')
         result = command.run()
         self.assertEqual(result, 0)
@@ -255,7 +268,7 @@ def test_views_command_single_view_traversal(self):
         L = []
         command.out = L.append
         view = dummy.DummyView(context='context', view_name='a')
-        command._find_view = lambda arg1, arg2: view
+        command._find_view = lambda arg1: view
         command.args = ('/foo/bar/myapp.ini#myapp', '/a')
         result = command.run()
         self.assertEqual(result, 0)
@@ -273,7 +286,7 @@ def test_views_command_single_view_function_traversal(self):
         command.out = L.append
         def view(): pass
         view.__request_attrs__ = {'context': 'context', 'view_name': 'a'}
-        command._find_view = lambda arg1, arg2: view
+        command._find_view = lambda arg1: view
         command.args = ('/foo/bar/myapp.ini#myapp', '/a')
         result = command.run()
         self.assertEqual(result, 0)
@@ -291,7 +304,7 @@ def test_views_command_single_view_traversal_with_permission(self):
         command.out = L.append
         view = dummy.DummyView(context='context', view_name='a')
         view.__permission__ = 'test'
-        command._find_view = lambda arg1, arg2: view
+        command._find_view = lambda arg1: view
         command.args = ('/foo/bar/myapp.ini#myapp', '/a')
         result = command.run()
         self.assertEqual(result, 0)
@@ -312,7 +325,7 @@ def predicate(): pass
         predicate.text = lambda *arg: "predicate = x"
         view = dummy.DummyView(context='context', view_name='a')
         view.__predicates__ = [predicate]
-        command._find_view = lambda arg1, arg2: view
+        command._find_view = lambda arg1: view
         command.args = ('/foo/bar/myapp.ini#myapp', '/a')
         result = command.run()
         self.assertEqual(result, 0)
@@ -332,7 +345,7 @@ def test_views_command_single_view_route(self):
         route = dummy.DummyRoute('a', '/a', matchdict={})
         view = dummy.DummyView(context='context', view_name='a',
                          matched_route=route, subpath='')
-        command._find_view = lambda arg1, arg2: view
+        command._find_view = lambda arg1: view
         command.args = ('/foo/bar/myapp.ini#myapp', '/a')
         result = command.run()
         self.assertEqual(result, 0)
@@ -360,7 +373,7 @@ def test_views_command_multi_view_nested(self):
                                           view_name='a1')
         multiview2 = dummy.DummyMultiView(multiview1, context='context',
                                     view_name='a')
-        command._find_view = lambda arg1, arg2: multiview2
+        command._find_view = lambda arg1: multiview2
         command.args = ('/foo/bar/myapp.ini#myapp', '/a')
         result = command.run()
         self.assertEqual(result, 0)
@@ -383,7 +396,7 @@ def predicate(): pass
         route = dummy.DummyRoute('a', '/a', matchdict={}, predicate=predicate)
         view = dummy.DummyView(context='context', view_name='a',
                          matched_route=route, subpath='')
-        command._find_view = lambda arg1, arg2: view
+        command._find_view = lambda arg1: view
         command.args = ('/foo/bar/myapp.ini#myapp', '/a')
         result = command.run()
         self.assertEqual(result, 0)
@@ -409,7 +422,7 @@ def test_views_command_multiview(self):
         view.__name__ = 'view'
         view.__view_attr__ = 'call'
         multiview = dummy.DummyMultiView(view, context='context', view_name='a')
-        command._find_view = lambda arg1, arg2: multiview
+        command._find_view = lambda arg1: multiview
         command.args = ('/foo/bar/myapp.ini#myapp', '/a')
         result = command.run()
         self.assertEqual(result, 0)
@@ -430,7 +443,7 @@ def test_views_command_multiview_with_permission(self):
         view.__view_attr__ = 'call'
         view.__permission__ = 'test'
         multiview = dummy.DummyMultiView(view, context='context', view_name='a')
-        command._find_view = lambda arg1, arg2: multiview
+        command._find_view = lambda arg1: multiview
         command.args = ('/foo/bar/myapp.ini#myapp', '/a')
         result = command.run()
         self.assertEqual(result, 0)
@@ -454,7 +467,7 @@ def predicate(): pass
         view.__view_attr__ = 'call'
         view.__predicates__ = [predicate]
         multiview = dummy.DummyMultiView(view, context='context', view_name='a')
-        command._find_view = lambda arg1, arg2: multiview
+        command._find_view = lambda arg1: multiview
         command.args = ('/foo/bar/myapp.ini#myapp', '/a')
         result = command.run()
         self.assertEqual(result, 0)