summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-04-21 23:43:07 -0400
committerChris McDonough <chrism@plope.com>2011-04-21 23:43:07 -0400
commit990efe90b5296f4f3d140b5a5fad9cf89834c47b (patch)
tree2f12f9cdacf4237b347be53cc93a0544e1ebae4c
parentf6799bbb6e9aca55f344f5d7f49f8fcb7af13db4 (diff)
parentba0a5f88d916d97fe52540a535b8b5520815201a (diff)
downloadpyramid-990efe90b5296f4f3d140b5a5fad9cf89834c47b.tar.gz
pyramid-990efe90b5296f4f3d140b5a5fad9cf89834c47b.tar.bz2
pyramid-990efe90b5296f4f3d140b5a5fad9cf89834c47b.zip
Merge branch 'scriptname_fixes'
-rw-r--r--CHANGES.txt13
-rw-r--r--pyramid/request.py52
-rw-r--r--pyramid/static.py43
-rw-r--r--pyramid/tests/test_integration.py20
-rw-r--r--pyramid/tests/test_request.py67
-rw-r--r--pyramid/tests/test_static.py9
-rw-r--r--pyramid/tests/test_wsgi.py48
-rw-r--r--pyramid/tests/wsgiapp2app/__init__.py17
-rw-r--r--pyramid/wsgi.py40
9 files changed, 204 insertions, 105 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index c3bdd54ef..c0108ff3a 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -140,6 +140,13 @@ Bug Fixes
- Redirects issued by a static view did not take into account any existing
``SCRIPT_NAME`` (such as one set by a url mapping composite). Now they do.
+- The ``pyramid.wsgi.wsgiapp2`` decorator did not take into account the
+ ``SCRIPT_NAME`` in the origin request.
+
+- The ``pyramid.wsgi.wsgiapp2`` decorator effectively only worked when it
+ decorated a view found via traversal; it ignored the ``PATH_INFO`` that was
+ part of a url-dispatch-matched view.
+
Deprecations
------------
@@ -173,6 +180,12 @@ Behavior Changes
renderer changes the content type (to ``application/json`` or
``text/plain`` for JSON and string renderers respectively).
+- The ``pyramid.wsgi.wsgiapp2`` now uses a slightly different method of
+ figuring out how to "fix" ``SCRIPT_NAME`` and ``PATH_INFO`` for the
+ downstream application. As a result, those values may differ slightly from
+ the perspective of the downstream application (for example, ``SCRIPT_NAME``
+ will now never possess a trailing slash).
+
Dependencies
------------
diff --git a/pyramid/request.py b/pyramid/request.py
index 9d2b9344b..0fe8b9379 100644
--- a/pyramid/request.py
+++ b/pyramid/request.py
@@ -10,6 +10,7 @@ from pyramid.interfaces import IResponseFactory
from pyramid.exceptions import ConfigurationError
from pyramid.decorator import reify
from pyramid.response import Response
+from pyramid.traversal import quote_path_segment
from pyramid.url import resource_url
from pyramid.url import route_url
from pyramid.url import static_url
@@ -393,3 +394,54 @@ def add_global_response_headers(request, headerlist):
response.headerlist.append((k, v))
request.add_response_callback(add_headers)
+def call_app_with_subpath_as_path_info(request, app):
+ # Copy the request. Use the source request's subpath (if it exists) as
+ # the new request's PATH_INFO. Set the request copy's SCRIPT_NAME to the
+ # prefix before the subpath. Call the application with the new request
+ # and return a response.
+ #
+ # Postconditions:
+ # - SCRIPT_NAME and PATH_INFO are empty or start with /
+ # - At least one of SCRIPT_NAME or PATH_INFO are set.
+ # - SCRIPT_NAME is not '/' (it should be '', and PATH_INFO should
+ # be '/').
+
+ environ = request.environ
+ script_name = environ.get('SCRIPT_NAME', '')
+ path_info = environ.get('PATH_INFO', '/')
+ subpath = list(getattr(request, 'subpath', ()))
+
+ new_script_name = ''
+
+ # compute new_path_info
+ new_path_info = '/' + '/'.join([x.encode('utf-8') for x in subpath])
+
+ if new_path_info != '/': # don't want a sole double-slash
+ if path_info != '/': # if orig path_info is '/', we're already done
+ if path_info.endswith('/'):
+ # readd trailing slash stripped by subpath (traversal)
+ # conversion
+ new_path_info += '/'
+
+ # compute new_script_name
+ workback = (script_name + path_info).split('/')
+
+ tmp = []
+ while workback:
+ if tmp == subpath:
+ break
+ el = workback.pop()
+ if el:
+ tmp.insert(0, el.decode('utf-8'))
+
+ # strip all trailing slashes from workback to avoid appending undue slashes
+ # to end of script_name
+ while workback and (workback[-1] == ''):
+ workback = workback[:-1]
+
+ new_script_name = '/'.join(workback)
+
+ new_request = request.copy()
+ new_request.environ['SCRIPT_NAME'] = new_script_name
+ new_request.environ['PATH_INFO'] = new_path_info
+ return new_request.get_response(app)
diff --git a/pyramid/static.py b/pyramid/static.py
index 223652768..170b027f1 100644
--- a/pyramid/static.py
+++ b/pyramid/static.py
@@ -13,6 +13,7 @@ from zope.interface import implements
from pyramid.asset import resolve_asset_spec
from pyramid.interfaces import IStaticURLInfo
from pyramid.path import caller_package
+from pyramid.request import call_app_with_subpath_as_path_info
from pyramid.url import route_url
class PackageURLParser(StaticURLParser):
@@ -208,44 +209,4 @@ class static_view(object):
self.app = app
def __call__(self, context, request):
- # Point PATH_INFO to the static file/dir path; point SCRIPT_NAME
- # to the prefix before it.
-
- # Postconditions:
- # - SCRIPT_NAME and PATH_INFO are empty or start with /
- # - At least one of SCRIPT_NAME or PATH_INFO are set.
- # - SCRIPT_NAME is not '/' (it should be '', and PATH_INFO should
- # be '/').
-
- request_copy = request.copy()
- script_name = request_copy.environ.get('SCRIPT_NAME', '')
- path_info = request_copy.environ.get('PATH_INFO', '/')
-
- new_script_name = script_name
- new_path_info = path_info
-
- subpath = list(request.subpath)
-
- if subpath:
- # compute new_path_info
- new_path_info = '/' + '/'.join(subpath)
- if path_info.endswith('/'):
- # readd trailing slash stripped by subpath (traversal)
- # conversion
- new_path_info += '/'
-
- # compute new_script_name
- tmp = []
- workback = (script_name + path_info).split('/')
- while workback:
- el = workback.pop()
- if el:
- tmp.insert(0, el)
- if tmp == subpath:
- new_script_name = '/'.join(workback)
- break
-
- request_copy.environ['SCRIPT_NAME'] = new_script_name
- request_copy.environ['PATH_INFO'] = new_path_info
-
- return request_copy.get_response(self.app)
+ return call_app_with_subpath_as_path_info(request, self.app)
diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py
index 1f9484279..dd77d3aec 100644
--- a/pyramid/tests/test_integration.py
+++ b/pyramid/tests/test_integration.py
@@ -104,7 +104,7 @@ class TestStaticApp(unittest.TestCase):
from webob import Request
context = DummyContext()
from StringIO import StringIO
- request = Request({'PATH_INFO':'',
+ request = Request({'PATH_INFO':'/static',
'SCRIPT_NAME':'/script_name',
'SERVER_NAME':'localhost',
'SERVER_PORT':'80',
@@ -112,7 +112,7 @@ class TestStaticApp(unittest.TestCase):
'wsgi.version':(1,0),
'wsgi.url_scheme':'http',
'wsgi.input':StringIO()})
- request.subpath = ['static']
+ request.subpath = ('static',)
result = staticapp(context, request)
self.assertEqual(result.status, '301 Moved Permanently')
self.assertEqual(result.location,
@@ -390,6 +390,22 @@ class SelfScanAppTest(unittest.TestCase):
res = self.testapp.get('/two', status=200)
self.assertTrue('two' in res.body)
+class WSGIApp2AppTest(unittest.TestCase):
+ def setUp(self):
+ from pyramid.tests.wsgiapp2app import main
+ config = main()
+ app = config.make_wsgi_app()
+ from webtest import TestApp
+ self.testapp = TestApp(app)
+ self.config = config
+
+ def tearDown(self):
+ self.config.end()
+
+ def test_hello(self):
+ res = self.testapp.get('/hello', status=200)
+ self.assertTrue('Hello' in res.body)
+
class DummyContext(object):
pass
diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py
index 66a451220..60d59ece6 100644
--- a/pyramid/tests/test_request.py
+++ b/pyramid/tests/test_request.py
@@ -325,6 +325,66 @@ class Test_add_global_response_headers(unittest.TestCase):
request.response_callbacks[0](None, response)
self.assertEqual(response.headerlist, [('c', 1)] )
+class Test_call_app_with_subpath_as_path_info(unittest.TestCase):
+ def _callFUT(self, request, app):
+ from pyramid.request import call_app_with_subpath_as_path_info
+ return call_app_with_subpath_as_path_info(request, app)
+
+ def test_it_all_request_and_environment_data_missing(self):
+ request = DummyRequest({})
+ response = self._callFUT(request, 'app')
+ self.assertTrue(request.copied)
+ self.assertEqual(response, 'app')
+ self.assertEqual(request.environ['SCRIPT_NAME'], '')
+ self.assertEqual(request.environ['PATH_INFO'], '/')
+
+ def test_it_with_subpath_and_path_info(self):
+ request = DummyRequest({'PATH_INFO':'/hello'})
+ request.subpath = ('hello',)
+ response = self._callFUT(request, 'app')
+ self.assertTrue(request.copied)
+ self.assertEqual(response, 'app')
+ self.assertEqual(request.environ['SCRIPT_NAME'], '')
+ self.assertEqual(request.environ['PATH_INFO'], '/hello')
+
+ def test_it_with_subpath_and_path_info_path_info_endswith_slash(self):
+ request = DummyRequest({'PATH_INFO':'/hello/'})
+ request.subpath = ('hello',)
+ response = self._callFUT(request, 'app')
+ self.assertTrue(request.copied)
+ self.assertEqual(response, 'app')
+ self.assertEqual(request.environ['SCRIPT_NAME'], '')
+ self.assertEqual(request.environ['PATH_INFO'], '/hello/')
+
+ def test_it_with_subpath_and_path_info_extra_script_name(self):
+ request = DummyRequest({'PATH_INFO':'/hello', 'SCRIPT_NAME':'/script'})
+ request.subpath = ('hello',)
+ response = self._callFUT(request, 'app')
+ self.assertTrue(request.copied)
+ self.assertEqual(response, 'app')
+ self.assertEqual(request.environ['SCRIPT_NAME'], '/script')
+ self.assertEqual(request.environ['PATH_INFO'], '/hello')
+
+ def test_it_with_extra_slashes_in_path_info(self):
+ request = DummyRequest({'PATH_INFO':'//hello/',
+ 'SCRIPT_NAME':'/script'})
+ request.subpath = ('hello',)
+ response = self._callFUT(request, 'app')
+ self.assertTrue(request.copied)
+ self.assertEqual(response, 'app')
+ self.assertEqual(request.environ['SCRIPT_NAME'], '/script')
+ self.assertEqual(request.environ['PATH_INFO'], '/hello/')
+
+ def test_subpath_path_info_and_script_name_have_utf8(self):
+ la = 'La Pe\xc3\xb1a'
+ request = DummyRequest({'PATH_INFO':'/'+la, 'SCRIPT_NAME':'/'+la})
+ request.subpath = (unicode(la, 'utf-8'), )
+ response = self._callFUT(request, 'app')
+ self.assertTrue(request.copied)
+ self.assertEqual(response, 'app')
+ self.assertEqual(request.environ['SCRIPT_NAME'], '/' + la)
+ self.assertEqual(request.environ['PATH_INFO'], '/' + la)
+
class DummyRequest:
def __init__(self, environ=None):
if environ is None:
@@ -334,6 +394,13 @@ class DummyRequest:
def add_response_callback(self, callback):
self.response_callbacks = [callback]
+ def get_response(self, app):
+ return app
+
+ def copy(self):
+ self.copied = True
+ return self
+
class DummyResponse:
def __init__(self):
self.headerlist = []
diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py
index b9d464bf1..588dc32e2 100644
--- a/pyramid/tests/test_static.py
+++ b/pyramid/tests/test_static.py
@@ -268,8 +268,9 @@ class Test_static_view(unittest.TestCase):
SCRIPT_NAME='/script_name')
view(context, request)
self.assertEqual(request.copied, True)
- self.assertEqual(request.environ['PATH_INFO'], '/path_info')
- self.assertEqual(request.environ['SCRIPT_NAME'], '/script_name')
+ self.assertEqual(request.environ['PATH_INFO'], '/')
+ self.assertEqual(request.environ['SCRIPT_NAME'],
+ '/script_name/path_info')
def test_with_subpath_path_info_ends_with_slash(self):
view = self._makeOne('fixtures', package_name='another')
@@ -295,7 +296,7 @@ class Test_static_view(unittest.TestCase):
self.assertEqual(request.environ['SCRIPT_NAME'],
'/scriptname/path_info')
- def test_with_subpath_new_script_name_fixes_trailing_double_slashes(self):
+ def test_with_subpath_new_script_name_fixes_trailing_slashes(self):
view = self._makeOne('fixtures', package_name='another')
context = DummyContext()
request = DummyRequest()
@@ -304,7 +305,7 @@ class Test_static_view(unittest.TestCase):
view(context, request)
self.assertEqual(request.copied, True)
self.assertEqual(request.environ['PATH_INFO'], '/sub/path/')
- self.assertEqual(request.environ['SCRIPT_NAME'], '/path_info/')
+ self.assertEqual(request.environ['SCRIPT_NAME'], '/path_info')
class TestStaticURLInfo(unittest.TestCase):
def _getTargetClass(self):
diff --git a/pyramid/tests/test_wsgi.py b/pyramid/tests/test_wsgi.py
index f63667352..06bcf1cb2 100644
--- a/pyramid/tests/test_wsgi.py
+++ b/pyramid/tests/test_wsgi.py
@@ -20,11 +20,9 @@ class WSGIApp2Tests(unittest.TestCase):
def test_decorator_with_subpath_and_view_name(self):
context = DummyContext()
request = DummyRequest()
- request.traversed = ['a', 'b']
- request.virtual_root_path = ['a']
- request.subpath = ['subpath']
- request.view_name = 'view_name'
- request.environ = {'SCRIPT_NAME':'/foo'}
+ request.subpath = ('subpath',)
+ request.environ = {'SCRIPT_NAME':'/foo',
+ 'PATH_INFO':'/b/view_name/subpath'}
decorator = self._callFUT(dummyapp)
response = decorator(context, request)
self.assertEqual(response, dummyapp)
@@ -34,11 +32,8 @@ class WSGIApp2Tests(unittest.TestCase):
def test_decorator_with_subpath_no_view_name(self):
context = DummyContext()
request = DummyRequest()
- request.traversed = ['a', 'b']
- request.virtual_root_path = ['a']
- request.subpath = ['subpath']
- request.view_name = ''
- request.environ = {'SCRIPT_NAME':'/foo'}
+ request.subpath = ('subpath',)
+ request.environ = {'SCRIPT_NAME':'/foo', 'PATH_INFO':'/b/subpath'}
decorator = self._callFUT(dummyapp)
response = decorator(context, request)
self.assertEqual(response, dummyapp)
@@ -48,11 +43,8 @@ class WSGIApp2Tests(unittest.TestCase):
def test_decorator_no_subpath_with_view_name(self):
context = DummyContext()
request = DummyRequest()
- request.traversed = ['a', 'b']
- request.virtual_root_path = ['a']
- request.subpath = []
- request.view_name = 'view_name'
- request.environ = {'SCRIPT_NAME':'/foo'}
+ request.subpath = ()
+ request.environ = {'SCRIPT_NAME':'/foo', 'PATH_INFO':'/b/view_name'}
decorator = self._callFUT(dummyapp)
response = decorator(context, request)
self.assertEqual(response, dummyapp)
@@ -62,11 +54,8 @@ class WSGIApp2Tests(unittest.TestCase):
def test_decorator_traversed_empty_with_view_name(self):
context = DummyContext()
request = DummyRequest()
- request.traversed = []
- request.virtual_root_path = []
- request.subpath = []
- request.view_name = 'view_name'
- request.environ = {'SCRIPT_NAME':'/foo'}
+ request.subpath = ()
+ request.environ = {'SCRIPT_NAME':'/foo', 'PATH_INFO':'/view_name'}
decorator = self._callFUT(dummyapp)
response = decorator(context, request)
self.assertEqual(response, dummyapp)
@@ -76,11 +65,8 @@ class WSGIApp2Tests(unittest.TestCase):
def test_decorator_traversed_empty_no_view_name(self):
context = DummyContext()
request = DummyRequest()
- request.traversed = []
- request.virtual_root_path = []
- request.subpath = []
- request.view_name = ''
- request.environ = {'SCRIPT_NAME':'/foo'}
+ request.subpath = ()
+ request.environ = {'SCRIPT_NAME':'/foo', 'PATH_INFO':'/'}
decorator = self._callFUT(dummyapp)
response = decorator(context, request)
self.assertEqual(response, dummyapp)
@@ -90,11 +76,8 @@ class WSGIApp2Tests(unittest.TestCase):
def test_decorator_traversed_empty_no_view_name_no_script_name(self):
context = DummyContext()
request = DummyRequest()
- request.traversed = []
- request.virtual_root_path = []
- request.subpath = []
- request.view_name = ''
- request.environ = {'SCRIPT_NAME':''}
+ request.subpath = ()
+ request.environ = {'SCRIPT_NAME':'', 'PATH_INFO':'/'}
decorator = self._callFUT(dummyapp)
response = decorator(context, request)
self.assertEqual(response, dummyapp)
@@ -110,3 +93,8 @@ class DummyContext:
class DummyRequest:
def get_response(self, application):
return application
+
+ def copy(self):
+ self.copied = True
+ return self
+
diff --git a/pyramid/tests/wsgiapp2app/__init__.py b/pyramid/tests/wsgiapp2app/__init__.py
new file mode 100644
index 000000000..0880556ef
--- /dev/null
+++ b/pyramid/tests/wsgiapp2app/__init__.py
@@ -0,0 +1,17 @@
+from pyramid.view import view_config
+from pyramid.wsgi import wsgiapp2
+
+@view_config(name='hello', renderer='string')
+@wsgiapp2
+def hello(environ, start_response):
+ assert environ['PATH_INFO'] == '/'
+ assert environ['SCRIPT_NAME'] == '/hello'
+ response_headers = [('Content-Type', 'text/plain')]
+ start_response('200 OK', response_headers)
+ return ['Hello!']
+
+def main():
+ from pyramid.config import Configurator
+ c = Configurator()
+ c.scan()
+ return c
diff --git a/pyramid/wsgi.py b/pyramid/wsgi.py
index e988a000e..e4c61ff63 100644
--- a/pyramid/wsgi.py
+++ b/pyramid/wsgi.py
@@ -1,5 +1,5 @@
from pyramid.compat import wraps
-from pyramid.traversal import quote_path_segment
+from pyramid.request import call_app_with_subpath_as_path_info
def wsgiapp(wrapped):
""" Decorator to turn a WSGI application into a :app:`Pyramid`
@@ -31,7 +31,7 @@ def wsgiapp(wrapped):
"""
def decorator(context, request):
return request.get_response(wrapped)
- return wraps(wrapped)(decorator) # grokkability
+ return wraps(wrapped)(decorator)
def wsgiapp2(wrapped):
""" Decorator to turn a WSGI application into a :app:`Pyramid`
@@ -56,31 +56,15 @@ def wsgiapp2(wrapped):
config.add_view(hello_world, name='hello_world.txt')
The ``wsgiapp2`` decorator will convert the result of the WSGI
- application to a Response and return it to :app:`Pyramid` as if
- the WSGI app were a :app:`Pyramid` view. The ``SCRIPT_NAME``
- and ``PATH_INFO`` values present in the WSGI environment are fixed
- up before the application is invoked. """
+ application to a Response and return it to :app:`Pyramid` as if the WSGI
+ app were a :app:`Pyramid` view. The ``SCRIPT_NAME`` and ``PATH_INFO``
+ values present in the WSGI environment are fixed up before the
+ application is invoked. In particular, a new WSGI environment is
+ generated, and the :term:`subpath` of the request passed to ``wsgiapp2``
+ is used as the new request's ``PATH_INFO`` and everything preceding the
+ subpath is used as the ``SCRIPT_NAME``. The new environment is passed to
+ the downstream WSGI application."""
def decorator(context, request):
- traversed = request.traversed
- vroot_path = request.virtual_root_path
- if not vroot_path:
- vroot_path = ()
- view_name = request.view_name
- subpath = request.subpath
- if not subpath:
- subpath = ()
- script_tuple = traversed[len(vroot_path):]
- script_list = [ quote_path_segment(name) for name in script_tuple ]
- if view_name:
- script_list.append(quote_path_segment(view_name))
- script_name = '/' + '/'.join(script_list)
- path_list = [ quote_path_segment(name) for name in subpath ]
- path_info = '/' + '/'.join(path_list)
- request.environ['PATH_INFO'] = path_info
- script_name = request.environ['SCRIPT_NAME'] + script_name
- if script_name.endswith('/'):
- script_name = script_name[:-1]
- request.environ['SCRIPT_NAME'] = script_name
- return request.get_response(wrapped)
- return wraps(wrapped)(decorator) # grokkability
+ return call_app_with_subpath_as_path_info(request, wrapped)
+ return wraps(wrapped)(decorator)