Chris McDonough
2011-09-07 dc98227171b49237d5236a2fbac0889b7cc07da4
Merge branch 'feature.staticfilegeddon'
2 files added
10 files modified
1077 ■■■■ changed files
CHANGES.txt 14 ●●●●● patch | view | raw | blame | history
TODO.txt 9 ●●●● patch | view | raw | blame | history
pyramid/static.py 197 ●●●●● patch | view | raw | blame | history
pyramid/tests/pkgs/static_abspath/__init__.py 7 ●●●●● patch | view | raw | blame | history
pyramid/tests/pkgs/static_assetspec/__init__.py 3 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_views.py 15 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_integration.py 225 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_static.py 523 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_traversal.py 3 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_view.py 62 ●●●●● patch | view | raw | blame | history
pyramid/traversal.py 3 ●●●● patch | view | raw | blame | history
pyramid/view.py 16 ●●●●● patch | view | raw | blame | history
CHANGES.txt
@@ -10,6 +10,20 @@
- Fixed test suite; on some systems tests would fail due to indeterminate
  test run ordering and a double-push-single-pop of a shared test variable.
- Replaced use of ``paste.urlparser.StaticURLParser`` with a derivative of Chris Rossi's "happy" static file serving code.
Behavior Differences
--------------------
- An ETag header is no longer set when serving a static file.  A
  Last-Modified header is set instead.
- Static file serving no longer supports the ``wsgi.file_wrapper`` extension.
- Instead of returning a ``403 Forbidden`` error when a static file is served
  that cannot be accessed by the Pyramid process' user due to file
  permissions, an IOError (or similar) will be raised.
1.2a5 (2011-09-04)
==================
TODO.txt
@@ -7,6 +7,8 @@
- Consider adding exclog to all scaffolds to print tracebacks to the console
  while the debug toolbar is enabled.
- Add cache_max_age=3600 to add_static_view of all scaffolds.
Nice-to-Have
------------
@@ -78,13 +80,6 @@
- 1.3: Add a default-view-config-params decorator that can be applied to a
  class which names defaults for method-based view_config decorator options.
- 1.3: - Eliminate non-deployment-non-scaffold-related Paste dependency:
  ``paste.urlparser.StaticURLParser`` (cutnpaste or reimplement, possibly
  using chrisrossi's happy stuff as a base).  paste.urlparser/paste.fileapp
  features missing from happy.static: ``wsgi.file_wrapper`` support
  (FileApp.get), 'HEAD' method support (FileApp.get), ETAG and if-none-match
  support (DataApp.get), handling file permission exceptions (FileApp.get),
- 1.3: use zope.registry rather than zope.component.
pyramid/static.py
@@ -1,82 +1,70 @@
import os
import pkg_resources
from os.path import normcase, normpath, join, getmtime, getsize, isdir, exists
from pkg_resources import resource_exists, resource_filename, resource_isdir
import mimetypes
from paste import httpexceptions
from paste import request
from paste.httpheaders import ETAG
from paste.urlparser import StaticURLParser
from repoze.lru import lru_cache
from pyramid.asset import resolve_asset_spec
from pyramid.httpexceptions import HTTPNotFound
from pyramid.httpexceptions import HTTPMovedPermanently
from pyramid.path import caller_package
from pyramid.request import call_app_with_subpath_as_path_info
from pyramid.response import Response
from pyramid.traversal import traversal_path
from pyramid.traversal import quote_path_segment
class PackageURLParser(StaticURLParser):
    """ This probably won't work with zipimported resources """
    def __init__(self, package_name, resource_name, root_resource=None,
                 cache_max_age=None):
        self.package_name = package_name
        self.resource_name = os.path.normpath(resource_name)
        if root_resource is None:
            root_resource = self.resource_name
        self.root_resource = root_resource
        self.cache_max_age = cache_max_age
def init_mimetypes(mimetypes):
    # this is a function so it can be unittested
    if hasattr(mimetypes, 'init'):
        mimetypes.init()
        return True
    return False
    def __call__(self, environ, start_response):
        path_info = environ.get('PATH_INFO', '')
        if not path_info:
            return self.add_slash(environ, start_response)
        if path_info == '/':
            # @@: This should obviously be configurable
            filename = 'index.html'
        else:
            filename = request.path_info_pop(environ)
        resource = os.path.normcase(os.path.normpath(
                    self.resource_name + '/' + filename))
        if not resource.startswith(self.root_resource):
            # Out of bounds
            return self.not_found(environ, start_response)
        if not pkg_resources.resource_exists(self.package_name, resource):
            return self.not_found(environ, start_response)
        if pkg_resources.resource_isdir(self.package_name, resource):
            # @@: Cache?
            return self.__class__(
                self.package_name, resource, root_resource=self.resource_name,
                cache_max_age=self.cache_max_age)(environ, start_response)
        pi = environ.get('PATH_INFO')
        if pi and pi != '/':
            return self.error_extra_path(environ, start_response)
        full = pkg_resources.resource_filename(self.package_name, resource)
        if_none_match = environ.get('HTTP_IF_NONE_MATCH')
        if if_none_match:
            mytime = os.stat(full).st_mtime
            if str(mytime) == if_none_match:
                headers = []
                ETAG.update(headers, mytime)
                start_response('304 Not Modified', headers)
                return [''] # empty body
# See http://bugs.python.org/issue5853 which is a recursion bug
# that seems to effect Python 2.6, Python 2.6.1, and 2.6.2 (a fix
# has been applied on the Python 2 trunk).
init_mimetypes(mimetypes)
        fa = self.make_app(full)
        if self.cache_max_age:
            fa.cache_control(max_age=self.cache_max_age)
        return fa(environ, start_response)
class _FileResponse(Response):
    """
    Serves a static filelike object.
    """
    def __init__(self, path, cache_max_age):
        super(_FileResponse, self).__init__(conditional_response=True)
        self.last_modified = getmtime(path)
        content_type = mimetypes.guess_type(path, strict=False)[0]
        if content_type is None:
            content_type = 'application/octet-stream'
        self.content_type = content_type
        content_length = getsize(path)
        self.app_iter = _FileIter(open(path, 'rb'), content_length)
        # assignment of content_length must come after assignment of app_iter
        self.content_length = content_length
        if cache_max_age is not None:
            self.cache_expires = cache_max_age
    def not_found(self, environ, start_response, debug_message=None):
        comment=('SCRIPT_NAME=%r; PATH_INFO=%r; looking in package %s; '
                 'subdir %s ;debug: %s' % (environ.get('SCRIPT_NAME'),
                                           environ.get('PATH_INFO'),
                                           self.package_name,
                                           self.resource_name,
                                           debug_message or '(none)'))
        exc = httpexceptions.HTTPNotFound(
            'The resource at %s could not be found'
            % request.construct_url(environ),
            comment=comment)
        return exc.wsgi_application(environ, start_response)
class _FileIter(object):
    block_size = 4096 * 64 # (256K)
    def __repr__(self):
        return '<%s %s:%s at %s>' % (self.__class__.__name__, self.package_name,
                                     self.root_resource, id(self))
    def __init__(self, file, size=None):
        self.file = file
        self.size = size
    def __iter__(self):
        return self
    def next(self):
        chunk_size = self.block_size
        if self.size is not None:
            if chunk_size > self.size:
                chunk_size = self.size
            self.size -= chunk_size
        data = self.file.read(chunk_size)
        if not data:
            raise StopIteration
        return data
    def close(self):
        self.file.close()
class static_view(object):
    """ An instance of this class is a callable which can act as a
@@ -120,24 +108,73 @@
         package-relative directory.  However, if the ``root_dir`` is
         absolute, configuration will not be able to
         override the assets it contains.  """
    def __init__(self, root_dir, cache_max_age=3600, package_name=None,
                 use_subpath=False):
                 use_subpath=False, index='index.html'):
        # package_name is for bw compat; it is preferred to pass in a
        # package-relative path as root_dir
        # (e.g. ``anotherpackage:foo/static``).
        self.cache_max_age = cache_max_age
        if package_name is None:
            package_name = caller_package().__name__
        package_name, root_dir = resolve_asset_spec(root_dir, package_name)
        if package_name is None:
            app = StaticURLParser(root_dir, cache_max_age=cache_max_age)
        else:
            app = PackageURLParser(
                package_name, root_dir, cache_max_age=cache_max_age)
        self.app = app
        package_name, docroot = resolve_asset_spec(root_dir, package_name)
        self.use_subpath = use_subpath
        self.package_name = package_name
        self.docroot = docroot
        self.norm_docroot = normcase(normpath(docroot))
        self.index = index
    def __call__(self, context, request):
        if self.use_subpath:
            return call_app_with_subpath_as_path_info(request, self.app)
        return request.get_response(self.app)
            path_tuple = request.subpath
        else:
            path_tuple = traversal_path(request.path_info)
        path = _secure_path(path_tuple)
        if path is None:
            # belt-and-suspenders security; this should never be true
            # unless someone screws up the traversal_path code
            # (request.subpath is computed via traversal_path too)
            return HTTPNotFound('Out of bounds: %s' % request.url)
        if self.package_name: # package resource
            resource_path ='%s/%s' % (self.docroot.rstrip('/'), path)
            if resource_isdir(self.package_name, resource_path):
                if not request.path_url.endswith('/'):
                    return self.add_slash_redirect(request)
                resource_path = '%s/%s' % (resource_path.rstrip('/'),self.index)
            if not resource_exists(self.package_name, resource_path):
                return HTTPNotFound(request.url)
            filepath = resource_filename(self.package_name, resource_path)
        else: # filesystem file
            # os.path.normpath converts / to \ on windows
            filepath = normcase(normpath(join(self.norm_docroot, path)))
            if isdir(filepath):
                if not request.path_url.endswith('/'):
                    return self.add_slash_redirect(request)
                filepath = join(filepath, self.index)
            if not exists(filepath):
                return HTTPNotFound(request.url)
        return _FileResponse(filepath ,self.cache_max_age)
    def add_slash_redirect(self, request):
        url = request.path_url + '/'
        qs = request.query_string
        if qs:
            url = url + '?' + qs
        return HTTPMovedPermanently(url)
@lru_cache(1000)
def _secure_path(path_tuple):
    if '' in path_tuple:
        return None
    for item in path_tuple:
        for val in ['.', '/']:
            if item.startswith(val):
                return None
    return '/'.join([quote_path_segment(x) for x in path_tuple])
pyramid/tests/pkgs/static_abspath/__init__.py
New file
@@ -0,0 +1,7 @@
import os
def includeme(config):
    here =  here = os.path.dirname(__file__)
    fixtures = os.path.normpath(os.path.join(here, '..', '..', 'fixtures'))
    config.add_static_view('/', fixtures)
pyramid/tests/pkgs/static_assetspec/__init__.py
New file
@@ -0,0 +1,3 @@
def includeme(config):
    config.add_static_view('/', 'pyramid.tests:fixtures')
pyramid/tests/test_config/test_views.py
@@ -1415,19 +1415,19 @@
    def test_add_static_view_here_no_utility_registered(self):
        from pyramid.renderers import null_renderer
        from zope.interface import Interface
        from pyramid.static import PackageURLParser
        from pyramid.interfaces import IView
        from pyramid.interfaces import IViewClassifier
        config = self._makeOne(autocommit=True)
        config.add_static_view('static', 'files',
                               renderer=null_renderer)
        config.add_static_view('static', 'files', renderer=null_renderer)
        request_type = self._getRouteRequestIface(config, 'static/')
        self._assertRoute(config, 'static/', 'static/*subpath')
        wrapped = config.registry.adapters.lookup(
            (IViewClassifier, request_type, Interface), IView, name='')
        request = self._makeRequest(config)
        from pyramid.request import Request
        request = Request.blank('/static/minimal.pt')
        request.subpath = ('minimal.pt', )
        result = wrapped(None, request)
        self.assertEqual(result.__class__, PackageURLParser)
        self.assertEqual(result.status, '200 OK')
    def test_add_static_view_package_relative(self):
        from pyramid.interfaces import IStaticURLInfo
@@ -3346,7 +3346,6 @@
        self.assertEqual(config.route_args, ('view/', 'view/*subpath'))
        self.assertEqual(config.view_kw['permission'], NO_PERMISSION_REQUIRED)
        self.assertEqual(config.view_kw['view'].__class__, static_view)
        self.assertEqual(config.view_kw['view'].app.cache_max_age, 1)
    def test_add_viewname_with_permission(self):
        config = DummyConfig()
@@ -3416,10 +3415,6 @@
        self.environ = environ
        self.params = {}
        self.cookies = {}
    def copy(self):
        return self
    def get_response(self, app):
        return app
class DummyContext:
    pass
pyramid/tests/test_integration.py
@@ -41,94 +41,14 @@
            (IViewClassifier, IRequest, INothing), IView, name='')
        self.assertEqual(view.__original_view__, wsgiapptest)
here = os.path.dirname(__file__)
staticapp = static_view(os.path.join(here, 'fixtures'), use_subpath=True)
class TestStaticApp(unittest.TestCase):
    def test_basic(self):
        from webob import Request
        context = DummyContext()
        from StringIO import StringIO
        request = Request({'PATH_INFO':'',
                           'SCRIPT_NAME':'',
                           'SERVER_NAME':'localhost',
                           'SERVER_PORT':'80',
                           'REQUEST_METHOD':'GET',
                           'wsgi.version':(1,0),
                           'wsgi.url_scheme':'http',
                           'wsgi.input':StringIO()})
        request.subpath = ('minimal.pt',)
        result = staticapp(context, request)
        self.assertEqual(result.status, '200 OK')
        self.assertEqual(
            result.body.replace('\r', ''),
            open(os.path.join(here, 'fixtures/minimal.pt'), 'r').read())
    def test_file_in_subdir(self):
        from webob import Request
        context = DummyContext()
        from StringIO import StringIO
        request = Request({'PATH_INFO':'',
                           'SCRIPT_NAME':'',
                           'SERVER_NAME':'localhost',
                           'SERVER_PORT':'80',
                           'REQUEST_METHOD':'GET',
                           'wsgi.version':(1,0),
                           'wsgi.url_scheme':'http',
                           'wsgi.input':StringIO()})
        request.subpath = ('static', 'index.html',)
        result = staticapp(context, request)
        self.assertEqual(result.status, '200 OK')
        self.assertEqual(
            result.body.replace('\r', ''),
            open(os.path.join(here, 'fixtures/static/index.html'), 'r').read())
    def test_redirect_to_subdir(self):
        from webob import Request
        context = DummyContext()
        from StringIO import StringIO
        request = Request({'PATH_INFO':'',
                           'SCRIPT_NAME':'',
                           'SERVER_NAME':'localhost',
                           'SERVER_PORT':'80',
                           'REQUEST_METHOD':'GET',
                           'wsgi.version':(1,0),
                           'wsgi.url_scheme':'http',
                           'wsgi.input':StringIO()})
        request.subpath = ('static',)
        result = staticapp(context, request)
        self.assertEqual(result.status, '301 Moved Permanently')
        self.assertEqual(result.location, 'http://localhost/static/')
    def test_redirect_to_subdir_with_existing_script_name(self):
        from webob import Request
        context = DummyContext()
        from StringIO import StringIO
        request = Request({'PATH_INFO':'/static',
                           'SCRIPT_NAME':'/script_name',
                           'SERVER_NAME':'localhost',
                           'SERVER_PORT':'80',
                           'REQUEST_METHOD':'GET',
                           'wsgi.version':(1,0),
                           'wsgi.url_scheme':'http',
                           'wsgi.input':StringIO()})
        request.subpath = ('static',)
        result = staticapp(context, request)
        self.assertEqual(result.status, '301 Moved Permanently')
        self.assertEqual(result.location,
                         'http://localhost/script_name/static/')
class IntegrationBase(unittest.TestCase):
class IntegrationBase(object):
    root_factory = None
    package = None
    def setUp(self):
        from pyramid.config import Configurator
        config = Configurator(root_factory=self.root_factory,
                              package=self.package)
        config.begin()
        config.include(self.package)
        config.commit()
        app = config.make_wsgi_app()
        from webtest import TestApp
        self.testapp = TestApp(app)
@@ -137,7 +57,124 @@
    def tearDown(self):
        self.config.end()
class TestFixtureApp(IntegrationBase):
here = os.path.dirname(__file__)
class TestStaticAppBase(IntegrationBase):
    def _assertBody(self, body, filename):
        self.assertEqual(
            body.replace('\r', ''),
            open(filename, 'r').read()
            )
    def test_basic(self):
        res = self.testapp.get('/minimal.pt', status=200)
        self._assertBody(res.body, os.path.join(here, 'fixtures/minimal.pt'))
    def test_not_modified(self):
        self.testapp.extra_environ = {
            'HTTP_IF_MODIFIED_SINCE':httpdate(pow(2, 32)-1)}
        res = self.testapp.get('/minimal.pt', status=304)
        self.assertEqual(res.body, '')
    def test_file_in_subdir(self):
        fn = os.path.join(here, 'fixtures/static/index.html')
        res = self.testapp.get('/static/index.html', status=200)
        self._assertBody(res.body, fn)
    def test_directory_noslash_redir(self):
        res = self.testapp.get('/static', status=301)
        self.assertEqual(res.headers['Location'], 'http://localhost/static/')
    def test_directory_noslash_redir_preserves_qs(self):
        res = self.testapp.get('/static?a=1&b=2', status=301)
        self.assertEqual(res.headers['Location'],
                         'http://localhost/static/?a=1&b=2')
    def test_directory_noslash_redir_with_scriptname(self):
        self.testapp.extra_environ = {'SCRIPT_NAME':'/script_name'}
        res = self.testapp.get('/static', status=301)
        self.assertEqual(res.headers['Location'],
                         'http://localhost/script_name/static/')
    def test_directory_withslash(self):
        fn = os.path.join(here, 'fixtures/static/index.html')
        res = self.testapp.get('/static/', status=200)
        self._assertBody(res.body, fn)
    def test_range_inclusive(self):
        self.testapp.extra_environ = {'HTTP_RANGE':'bytes=1-2'}
        res = self.testapp.get('/static/index.html', status=206)
        self.assertEqual(res.body, 'ht')
    def test_range_tilend(self):
        self.testapp.extra_environ = {'HTTP_RANGE':'bytes=-5'}
        res = self.testapp.get('/static/index.html', status=206)
        self.assertEqual(res.body, 'tml>\n')
    def test_range_notbytes(self):
        self.testapp.extra_environ = {'HTTP_RANGE':'kHz=-5'}
        res = self.testapp.get('/static/index.html', status=200)
        self._assertBody(res.body,
                         os.path.join(here, 'fixtures/static/index.html'))
    def test_range_multiple(self):
        res = self.testapp.get('/static/index.html',
                               [('HTTP_RANGE', 'bytes=10-11,11-12')],
                               status=200)
        self._assertBody(res.body,
                         os.path.join(here, 'fixtures/static/index.html'))
    def test_range_oob(self):
        self.testapp.extra_environ = {'HTTP_RANGE':'bytes=1000-1002'}
        self.testapp.get('/static/index.html', status=416)
    def test_notfound(self):
        self.testapp.get('/static/wontbefound.html', status=404)
    def test_oob_doubledot(self):
        self.testapp.get('/static/../../test_integration.py', status=404)
    def test_oob_slash(self):
        self.testapp.get('/%2F/test_integration.py', status=404)
        # XXX pdb this
class TestStaticAppUsingAbsPath(TestStaticAppBase, unittest.TestCase):
    package = 'pyramid.tests.pkgs.static_abspath'
class TestStaticAppUsingAssetSpec(TestStaticAppBase, unittest.TestCase):
    package = 'pyramid.tests.pkgs.static_assetspec'
class TestStaticAppNoSubpath(unittest.TestCase):
    staticapp = static_view(os.path.join(here, 'fixtures'), use_subpath=False)
    def _makeRequest(self, extra):
        from pyramid.request import Request
        from StringIO import StringIO
        kw = {'PATH_INFO':'',
              'SCRIPT_NAME':'',
              'SERVER_NAME':'localhost',
              'SERVER_PORT':'80',
              'REQUEST_METHOD':'GET',
              'wsgi.version':(1,0),
              'wsgi.url_scheme':'http',
              'wsgi.input':StringIO()}
        kw.update(extra)
        request = Request(kw)
        return request
    def _assertBody(self, body, filename):
        self.assertEqual(
            body.replace('\r', ''),
            open(filename, 'r').read()
            )
    def test_basic(self):
        request = self._makeRequest({'PATH_INFO':'/minimal.pt'})
        context = DummyContext()
        result = self.staticapp(context, request)
        self.assertEqual(result.status, '200 OK')
        self._assertBody(result.body, os.path.join(here, 'fixtures/minimal.pt'))
class TestFixtureApp(IntegrationBase, unittest.TestCase):
    package = 'pyramid.tests.pkgs.fixtureapp'
    def test_another(self):
        res = self.testapp.get('/another.html', status=200)
@@ -157,7 +194,7 @@
    def test_protected(self):
        self.testapp.get('/protected.html', status=403)
class TestStaticPermApp(IntegrationBase):
class TestStaticPermApp(IntegrationBase, unittest.TestCase):
    package = 'pyramid.tests.pkgs.staticpermapp'
    root_factory = 'pyramid.tests.pkgs.staticpermapp:RootFactory'
    def test_allowed(self):
@@ -188,7 +225,7 @@
            result.body.replace('\r', ''),
            open(os.path.join(here, 'fixtures/static/index.html'), 'r').read())
class TestCCBug(IntegrationBase):
class TestCCBug(IntegrationBase, unittest.TestCase):
    # "unordered" as reported in IRC by author of
    # http://labs.creativecommons.org/2010/01/13/cc-engine-and-web-non-frameworks/
    package = 'pyramid.tests.pkgs.ccbugapp'
@@ -200,7 +237,7 @@
        res = self.testapp.get('/licenses/1/v1/juri', status=200)
        self.assertEqual(res.body, 'juri')
class TestHybridApp(IntegrationBase):
class TestHybridApp(IntegrationBase, unittest.TestCase):
    # make sure views registered for a route "win" over views registered
    # without one, even though the context of the non-route view may
    # be more specific than the route view.
@@ -243,14 +280,14 @@
        res = self.testapp.get('/error_sub', status=200)
        self.assertEqual(res.body, 'supressed2')
class TestRestBugApp(IntegrationBase):
class TestRestBugApp(IntegrationBase, unittest.TestCase):
    # test bug reported by delijati 2010/2/3 (http://pastebin.com/d4cc15515)
    package = 'pyramid.tests.pkgs.restbugapp'
    def test_it(self):
        res = self.testapp.get('/pet', status=200)
        self.assertEqual(res.body, 'gotten')
class TestForbiddenAppHasResult(IntegrationBase):
class TestForbiddenAppHasResult(IntegrationBase, unittest.TestCase):
    # test that forbidden exception has ACLDenied result attached
    package = 'pyramid.tests.pkgs.forbiddenapp'
    def test_it(self):
@@ -265,7 +302,7 @@
        self.assertTrue(
            result.endswith("for principals ['system.Everyone']"))
class TestViewDecoratorApp(IntegrationBase):
class TestViewDecoratorApp(IntegrationBase, unittest.TestCase):
    package = 'pyramid.tests.pkgs.viewdecoratorapp'
    def _configure_mako(self):
        tmpldir = os.path.join(os.path.dirname(__file__),
@@ -286,7 +323,7 @@
        res = self.testapp.get('/second', status=200)
        self.assertTrue('OK2' in res.body)
class TestViewPermissionBug(IntegrationBase):
class TestViewPermissionBug(IntegrationBase, unittest.TestCase):
    # view_execution_permitted bug as reported by Shane at http://lists.repoze.org/pipermail/repoze-dev/2010-October/003603.html
    package = 'pyramid.tests.pkgs.permbugapp'
    def test_test(self):
@@ -296,7 +333,7 @@
    def test_x(self):
        self.testapp.get('/x', status=403)
class TestDefaultViewPermissionBug(IntegrationBase):
class TestDefaultViewPermissionBug(IntegrationBase, unittest.TestCase):
    # default_view_permission bug as reported by Wiggy at http://lists.repoze.org/pipermail/repoze-dev/2010-October/003602.html
    package = 'pyramid.tests.pkgs.defpermbugapp'
    def test_x(self):
@@ -316,7 +353,7 @@
excroot = {'anexception':AnException(),
           'notanexception':NotAnException()}
class TestExceptionViewsApp(IntegrationBase):
class TestExceptionViewsApp(IntegrationBase, unittest.TestCase):
    package = 'pyramid.tests.pkgs.exceptionviewapp'
    root_factory = lambda *arg: excroot
    def test_root(self):
@@ -475,7 +512,7 @@
        self.assertTrue('Hello' in res.body)
if os.name != 'java': # uses chameleon
    class RendererScanAppTest(IntegrationBase):
    class RendererScanAppTest(IntegrationBase, unittest.TestCase):
        package = 'pyramid.tests.pkgs.rendererscanapp'
        def test_root(self):
            res = self.testapp.get('/one', status=200)
@@ -505,3 +542,7 @@
    def get_response(self, application):
        return application(None, None)
def httpdate(ts):
    import datetime
    ts = datetime.datetime.utcfromtimestamp(ts)
    return ts.strftime("%a, %d %b %Y %H:%M:%S GMT")
pyramid/tests/test_static.py
@@ -1,16 +1,16 @@
import unittest
from pyramid.testing import cleanUp
import datetime
class TestPackageURLParser(unittest.TestCase):
class Test_static_view_use_subpath_False(unittest.TestCase):
    def _getTargetClass(self):
        from pyramid.static import PackageURLParser
        return PackageURLParser
        from pyramid.static import static_view
        return static_view
    def _makeOne(self, *arg, **kw):
        return self._getTargetClass()(*arg, **kw)
    def _makeEnviron(self, **kw):
    def _makeRequest(self, kw=None):
        from pyramid.request import Request
        environ = {
            'wsgi.url_scheme':'http',
            'wsgi.version':(1,0),
@@ -20,332 +20,263 @@
            'SCRIPT_NAME':'',
            'REQUEST_METHOD':'GET',
            }
        environ.update(kw)
        return environ
        if kw is not None:
            environ.update(kw)
        return Request(environ=environ)
    
    def test_ctor_allargs(self):
        import os.path
        inst = self._makeOne('package', 'resource/name', root_resource='root',
                             cache_max_age=100)
        self.assertEqual(inst.package_name, 'package')
        self.assertEqual(inst.resource_name, os.path.join('resource', 'name'))
        self.assertEqual(inst.root_resource, 'root')
        self.assertEqual(inst.cache_max_age, 100)
    def test_ctor_defaultargs(self):
        import os.path
        inst = self._makeOne('package', 'resource/name')
        inst = self._makeOne('package:resource_name')
        self.assertEqual(inst.package_name, 'package')
        self.assertEqual(inst.resource_name, os.path.join('resource', 'name'))
        self.assertEqual(inst.root_resource, os.path.join('resource', 'name'))
        self.assertEqual(inst.cache_max_age, None)
        self.assertEqual(inst.docroot, 'resource_name')
        self.assertEqual(inst.cache_max_age, 3600)
        self.assertEqual(inst.index, 'index.html')
    def test_call_adds_slash_path_info_empty(self):
        environ = self._makeEnviron(PATH_INFO='')
        inst = self._makeOne('pyramid.tests', 'fixtures/static')
        sr = DummyStartResponse()
        response = inst(environ, sr)
        body = response[0]
        self.assertTrue('301 Moved Permanently' in body)
        self.assertTrue('http://example.com:6543/' in body)
        inst = self._makeOne('pyramid.tests:fixtures/static')
        request = self._makeRequest({'PATH_INFO':''})
        context = DummyContext()
        response = inst(context, request)
        response.prepare(request.environ)
        self.assertEqual(response.status, '301 Moved Permanently')
        self.assertTrue('http://example.com:6543/' in response.body)
        
    def test_path_info_slash_means_index_html(self):
        environ = self._makeEnviron()
        inst = self._makeOne('pyramid.tests', 'fixtures/static')
        sr = DummyStartResponse()
        response = inst(environ, sr)
        body = response[0]
        self.assertTrue('<html>static</html>' in body)
        inst = self._makeOne('pyramid.tests:fixtures/static')
        request = self._makeRequest()
        context = DummyContext()
        response = inst(context, request)
        self.assertTrue('<html>static</html>' in response.body)
    def test_resource_out_of_bounds(self):
        environ = self._makeEnviron()
        inst = self._makeOne('pyramid.tests', 'fixtures/static')
        inst.root_resource = 'abcdef'
        sr = DummyStartResponse()
        response = inst(environ, sr)
        body = response[0]
        self.assertTrue('404 Not Found' in body)
        self.assertTrue('http://example.com:6543/' in body)
        inst = self._makeOne('pyramid.tests:fixtures/static')
        request = self._makeRequest({'PATH_INFO':'/subdir/../../minimal.pt'})
        context = DummyContext()
        response = inst(context, request)
        self.assertEqual(response.status, '404 Not Found')
    def test_resource_doesnt_exist(self):
        environ = self._makeEnviron(PATH_INFO='/notthere')
        inst = self._makeOne('pyramid.tests', 'fixtures/static')
        sr = DummyStartResponse()
        response = inst(environ, sr)
        body = response[0]
        self.assertTrue('404 Not Found' in body)
        self.assertTrue('http://example.com:6543/' in body)
        inst = self._makeOne('pyramid.tests:fixtures/static')
        request = self._makeRequest({'PATH_INFO':'/notthere'})
        context = DummyContext()
        response = inst(context, request)
        self.assertEqual(response.status, '404 Not Found')
    def test_resource_isdir(self):
        environ = self._makeEnviron(PATH_INFO='/subdir/')
        inst = self._makeOne('pyramid.tests', 'fixtures/static')
        sr = DummyStartResponse()
        response = inst(environ, sr)
        body = response[0]
        self.assertTrue('<html>subdir</html>' in body)
        inst = self._makeOne('pyramid.tests:fixtures/static')
        request = self._makeRequest({'PATH_INFO':'/subdir/'})
        context = DummyContext()
        response = inst(context, request)
        self.assertTrue('<html>subdir</html>' in response.body)
    def test_resource_is_file(self):
        environ = self._makeEnviron(PATH_INFO='/index.html')
        inst = self._makeOne('pyramid.tests', 'fixtures/static')
        sr = DummyStartResponse()
        response = inst(environ, sr)
        body = response[0]
        self.assertTrue('<html>static</html>' in body)
    def test_resource_has_extra_path_info(self):
        environ = self._makeEnviron(PATH_INFO='/static/index.html/more')
        inst = self._makeOne('pyramid.tests', 'fixtures')
        sr = DummyStartResponse()
        response = inst(environ, sr)
        body = response[0]
        self.assertTrue("The trailing path '/more' is not allowed" in body)
        inst = self._makeOne('pyramid.tests:fixtures/static')
        request = self._makeRequest({'PATH_INFO':'/index.html'})
        context = DummyContext()
        response = inst(context, request)
        self.assertTrue('<html>static</html>' in response.body)
    def test_resource_is_file_with_cache_max_age(self):
        environ = self._makeEnviron(PATH_INFO='/index.html')
        inst = self._makeOne('pyramid.tests', 'fixtures/static',
                             cache_max_age=600)
        sr = DummyStartResponse()
        response = inst(environ, sr)
        body = response[0]
        self.assertTrue('<html>static</html>' in body)
        self.assertEqual(len(sr.headerlist), 8)
        header_names = [ x[0] for x in sr.headerlist ]
        inst = self._makeOne('pyramid.tests:fixtures/static', cache_max_age=600)
        request = self._makeRequest({'PATH_INFO':'/index.html'})
        context = DummyContext()
        response = inst(context, request)
        self.assertTrue('<html>static</html>' in response.body)
        self.assertEqual(len(response.headerlist), 5)
        header_names = [ x[0] for x in response.headerlist ]
        header_names.sort()
        self.assertEqual(header_names,
                         ['Accept-Ranges', 'Cache-Control',
                          'Content-Length', 'Content-Range',
                          'Content-Type', 'ETag', 'Expires', 'Last-Modified'])
                         ['Cache-Control', 'Content-Length', 'Content-Type',
                          'Expires', 'Last-Modified'])
    def test_resource_is_file_with_no_cache_max_age(self):
        environ = self._makeEnviron(PATH_INFO='/index.html')
        inst = self._makeOne('pyramid.tests', 'fixtures/static')
        sr = DummyStartResponse()
        response = inst(environ, sr)
        body = response[0]
        self.assertTrue('<html>static</html>' in body)
        self.assertEqual(len(sr.headerlist), 6)
        header_names = [ x[0] for x in sr.headerlist ]
        inst = self._makeOne('pyramid.tests:fixtures/static',
                             cache_max_age=None)
        request = self._makeRequest({'PATH_INFO':'/index.html'})
        context = DummyContext()
        response = inst(context, request)
        self.assertTrue('<html>static</html>' in response.body)
        self.assertEqual(len(response.headerlist), 3)
        header_names = [ x[0] for x in response.headerlist ]
        header_names.sort()
        self.assertEqual(header_names,
                         ['Accept-Ranges', 'Content-Length', 'Content-Range',
                          'Content-Type', 'ETag', 'Last-Modified'])
        self.assertEqual(
            header_names,
            ['Content-Length', 'Content-Type', 'Last-Modified'])
    def test_with_root_resource(self):
        environ = self._makeEnviron(PATH_INFO='/static/index.html')
        inst = self._makeOne('pyramid.tests', 'fixtures',
                             root_resource='fixtures/static')
        sr = DummyStartResponse()
        response = inst(environ, sr)
        body = response[0]
        self.assertTrue('<html>static</html>' in body)
    def test_if_none_match(self):
        class DummyEq(object):
            def __eq__(self, other):
                return True
        dummy_eq = DummyEq()
        environ = self._makeEnviron(HTTP_IF_NONE_MATCH=dummy_eq)
        inst = self._makeOne('pyramid.tests', 'fixtures/static')
        sr = DummyStartResponse()
        response = inst(environ, sr)
        self.assertEqual(len(sr.headerlist), 1)
        self.assertEqual(sr.status, '304 Not Modified')
        self.assertEqual(sr.headerlist[0][0], 'ETag')
        self.assertEqual(response[0], '')
    def test_if_none_match_miss(self):
        class DummyEq(object):
            def __eq__(self, other):
                return False
        dummy_eq = DummyEq()
        environ = self._makeEnviron(HTTP_IF_NONE_MATCH=dummy_eq)
        inst = self._makeOne('pyramid.tests', 'fixtures/static')
        sr = DummyStartResponse()
        inst(environ, sr)
        self.assertEqual(len(sr.headerlist), 6)
        self.assertEqual(sr.status, '200 OK')
    def test_repr(self):
        import os.path
        inst = self._makeOne('pyramid.tests', 'fixtures/static')
        self.assertTrue(
            repr(inst).startswith(
            '<PackageURLParser pyramid.tests:%s at'
                % os.path.join('fixtures', 'static')))
    def test_resource_notmodified(self):
        inst = self._makeOne('pyramid.tests:fixtures/static')
        request = self._makeRequest({'PATH_INFO':'/index.html'})
        request.if_modified_since = pow(2, 32) -1
        context = DummyContext()
        response = inst(context, request)
        start_response = DummyStartResponse()
        app_iter = response(request.environ, start_response)
        self.assertEqual(start_response.status, '304 Not Modified')
        self.assertEqual(list(app_iter), [])
    def test_not_found(self):
        inst = self._makeOne('pyramid.tests', 'fixtures/static')
        environ = self._makeEnviron()
        sr = DummyStartResponse()
        response = inst.not_found(environ, sr, 'debug_message')
        body = response[0]
        self.assertTrue('404 Not Found' in body)
        self.assertEqual(sr.status, '404 Not Found')
        inst = self._makeOne('pyramid.tests:fixtures/static')
        request = self._makeRequest({'PATH_INFO':'/notthere.html'})
        context = DummyContext()
        response = inst(context, request)
        self.assertEqual(response.status, '404 Not Found')
class Test_static_view(unittest.TestCase):
    def setUp(self):
        cleanUp()
    def tearDown(self):
        cleanUp()
class Test_static_view_use_subpath_True(unittest.TestCase):
    def _getTargetClass(self):
        from pyramid.static import static_view
        return static_view
    def _makeOne(self, path, package_name=None, use_subpath=False):
        return self._getTargetClass()(path, package_name=package_name,
                                      use_subpath=use_subpath)
    def _makeEnviron(self, **extras):
    def _makeOne(self, *arg, **kw):
        kw['use_subpath'] = True
        return self._getTargetClass()(*arg, **kw)
    def _makeRequest(self, kw=None):
        from pyramid.request import Request
        environ = {
            'wsgi.url_scheme':'http',
            'wsgi.version':(1,0),
            'SERVER_NAME':'localhost',
            'SERVER_PORT':'8080',
            'SERVER_NAME':'example.com',
            'SERVER_PORT':'6543',
            'PATH_INFO':'/',
            'SCRIPT_NAME':'',
            'REQUEST_METHOD':'GET',
            }
        environ.update(extras)
        return environ
    def test_abspath_subpath(self):
        import os.path
        path = os.path.dirname(__file__)
        view = self._makeOne(path, use_subpath=True)
        context = DummyContext()
        request = DummyRequest()
        request.subpath = ['__init__.py']
        request.environ = self._makeEnviron()
        response = view(context, request)
        self.assertEqual(request.copied, True)
        self.assertEqual(response.directory, os.path.normcase(path))
    def test_relpath_subpath(self):
        path = 'fixtures'
        view = self._makeOne(path, use_subpath=True)
        context = DummyContext()
        request = DummyRequest()
        request.subpath = ['__init__.py']
        request.environ = self._makeEnviron()
        response = view(context, request)
        self.assertEqual(request.copied, True)
        self.assertEqual(response.root_resource, 'fixtures')
        self.assertEqual(response.resource_name, 'fixtures')
        self.assertEqual(response.package_name, 'pyramid.tests')
        self.assertEqual(response.cache_max_age, 3600)
    def test_relpath_notsubpath(self):
        path = 'fixtures'
        view = self._makeOne(path)
        context = DummyContext()
        request = DummyRequest()
        request.subpath = ['__init__.py']
        request.environ = self._makeEnviron()
        response = view(context, request)
        self.assertTrue(not hasattr(request, 'copied'))
        self.assertEqual(response.root_resource, 'fixtures')
        self.assertEqual(response.resource_name, 'fixtures')
        self.assertEqual(response.package_name, 'pyramid.tests')
        self.assertEqual(response.cache_max_age, 3600)
    def test_relpath_withpackage_subpath(self):
        view = self._makeOne('another:fixtures', use_subpath=True)
        context = DummyContext()
        request = DummyRequest()
        request.subpath = ['__init__.py']
        request.environ = self._makeEnviron()
        response = view(context, request)
        self.assertEqual(request.copied, True)
        self.assertEqual(response.root_resource, 'fixtures')
        self.assertEqual(response.resource_name, 'fixtures')
        self.assertEqual(response.package_name, 'another')
        self.assertEqual(response.cache_max_age, 3600)
    def test_relpath_withpackage_name_subpath(self):
        view = self._makeOne('fixtures', package_name='another',
                             use_subpath=True)
        context = DummyContext()
        request = DummyRequest()
        request.subpath = ['__init__.py']
        request.environ = self._makeEnviron()
        response = view(context, request)
        self.assertEqual(request.copied, True)
        self.assertEqual(response.root_resource, 'fixtures')
        self.assertEqual(response.resource_name, 'fixtures')
        self.assertEqual(response.package_name, 'another')
        self.assertEqual(response.cache_max_age, 3600)
    def test_no_subpath_preserves_path_info_and_script_name_subpath(self):
        view = self._makeOne('fixtures', package_name='another',
                             use_subpath=True)
        context = DummyContext()
        request = DummyRequest()
        request.subpath = ()
        request.environ = self._makeEnviron(PATH_INFO='/path_info',
                                            SCRIPT_NAME='/script_name')
        view(context, request)
        self.assertEqual(request.copied, True)
        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_subpath(self):
        view = self._makeOne('fixtures', package_name='another',
                             use_subpath=True)
        context = DummyContext()
        request = DummyRequest()
        request.subpath = ('subpath',)
        request.environ = self._makeEnviron(PATH_INFO='/path_info/subpath/')
        view(context, request)
        self.assertEqual(request.copied, True)
        self.assertEqual(request.environ['PATH_INFO'], '/subpath/')
        self.assertEqual(request.environ['SCRIPT_NAME'], '/path_info')
    def test_with_subpath_original_script_name_preserved(self):
        view = self._makeOne('fixtures', package_name='another',
                             use_subpath=True)
        context = DummyContext()
        request = DummyRequest()
        request.subpath = ('subpath',)
        request.environ = self._makeEnviron(PATH_INFO='/path_info/subpath/',
                                            SCRIPT_NAME='/scriptname')
        view(context, request)
        self.assertEqual(request.copied, True)
        self.assertEqual(request.environ['PATH_INFO'], '/subpath/')
        self.assertEqual(request.environ['SCRIPT_NAME'],
                         '/scriptname/path_info')
    def test_with_subpath_new_script_name_fixes_trailing_slashes(self):
        view = self._makeOne('fixtures', package_name='another',
                             use_subpath=True)
        context = DummyContext()
        request = DummyRequest()
        request.subpath = ('sub', 'path')
        request.environ = self._makeEnviron(PATH_INFO='/path_info//sub//path//')
        view(context, request)
        self.assertEqual(request.copied, True)
        self.assertEqual(request.environ['PATH_INFO'], '/sub/path/')
        self.assertEqual(request.environ['SCRIPT_NAME'], '/path_info')
class DummyStartResponse:
    def __call__(self, status, headerlist, exc_info=None):
        self.status = status
        self.headerlist = headerlist
        self.exc_info = exc_info
        if kw is not None:
            environ.update(kw)
        return Request(environ=environ)
    
    def test_ctor_defaultargs(self):
        inst = self._makeOne('package:resource_name')
        self.assertEqual(inst.package_name, 'package')
        self.assertEqual(inst.docroot, 'resource_name')
        self.assertEqual(inst.cache_max_age, 3600)
        self.assertEqual(inst.index, 'index.html')
    def test_call_adds_slash_path_info_empty(self):
        inst = self._makeOne('pyramid.tests:fixtures/static')
        request = self._makeRequest({'PATH_INFO':''})
        request.subpath = ()
        context = DummyContext()
        response = inst(context, request)
        response.prepare(request.environ)
        self.assertEqual(response.status, '301 Moved Permanently')
        self.assertTrue('http://example.com:6543/' in response.body)
    def test_path_info_slash_means_index_html(self):
        inst = self._makeOne('pyramid.tests:fixtures/static')
        request = self._makeRequest()
        request.subpath = ()
        context = DummyContext()
        response = inst(context, request)
        self.assertTrue('<html>static</html>' in response.body)
    def test_resource_out_of_bounds(self):
        inst = self._makeOne('pyramid.tests:fixtures/static')
        request = self._makeRequest()
        request.subpath = ('subdir', '..', '..', 'minimal.pt')
        context = DummyContext()
        response = inst(context, request)
        self.assertEqual(response.status, '404 Not Found')
    def test_resource_doesnt_exist(self):
        inst = self._makeOne('pyramid.tests:fixtures/static')
        request = self._makeRequest()
        request.subpath = ('notthere,')
        context = DummyContext()
        response = inst(context, request)
        self.assertEqual(response.status, '404 Not Found')
    def test_resource_isdir(self):
        inst = self._makeOne('pyramid.tests:fixtures/static')
        request = self._makeRequest()
        request.subpath = ('subdir',)
        context = DummyContext()
        response = inst(context, request)
        self.assertTrue('<html>subdir</html>' in response.body)
    def test_resource_is_file(self):
        inst = self._makeOne('pyramid.tests:fixtures/static')
        request = self._makeRequest()
        request.subpath = ('index.html',)
        context = DummyContext()
        response = inst(context, request)
        self.assertTrue('<html>static</html>' in response.body)
    def test_resource_is_file_with_cache_max_age(self):
        inst = self._makeOne('pyramid.tests:fixtures/static', cache_max_age=600)
        request = self._makeRequest()
        request.subpath = ('index.html',)
        context = DummyContext()
        response = inst(context, request)
        self.assertTrue('<html>static</html>' in response.body)
        self.assertEqual(len(response.headerlist), 5)
        header_names = [ x[0] for x in response.headerlist ]
        header_names.sort()
        self.assertEqual(header_names,
                         ['Cache-Control', 'Content-Length', 'Content-Type',
                          'Expires', 'Last-Modified'])
    def test_resource_is_file_with_no_cache_max_age(self):
        inst = self._makeOne('pyramid.tests:fixtures/static',
                             cache_max_age=None)
        request = self._makeRequest()
        request.subpath = ('index.html',)
        context = DummyContext()
        response = inst(context, request)
        self.assertTrue('<html>static</html>' in response.body)
        self.assertEqual(len(response.headerlist), 3)
        header_names = [ x[0] for x in response.headerlist ]
        header_names.sort()
        self.assertEqual(
            header_names,
            ['Content-Length', 'Content-Type', 'Last-Modified'])
    def test_resource_notmodified(self):
        inst = self._makeOne('pyramid.tests:fixtures/static')
        request = self._makeRequest()
        request.if_modified_since = pow(2, 32) -1
        request.subpath = ('index.html',)
        context = DummyContext()
        response = inst(context, request)
        start_response = DummyStartResponse()
        app_iter = response(request.environ, start_response)
        self.assertEqual(start_response.status, '304 Not Modified')
        self.assertEqual(list(app_iter), [])
    def test_not_found(self):
        inst = self._makeOne('pyramid.tests:fixtures/static')
        request = self._makeRequest()
        request.subpath = ('notthere.html',)
        context = DummyContext()
        response = inst(context, request)
        self.assertEqual(response.status, '404 Not Found')
class Test_patch_mimetypes(unittest.TestCase):
    def _callFUT(self, module):
        from pyramid.static import init_mimetypes
        return init_mimetypes(module)
    def test_has_init(self):
        class DummyMimetypes(object):
            def init(self):
                self.initted = True
        module = DummyMimetypes()
        result = self._callFUT(module)
        self.assertEqual(result, True)
        self.assertEqual(module.initted, True)
    def test_missing_init(self):
        class DummyMimetypes(object):
            pass
        module = DummyMimetypes()
        result = self._callFUT(module)
        self.assertEqual(result, False)
class DummyContext:
    pass
class DummyRequest:
    def __init__(self, environ=None):
        if environ is None:
            environ = {}
        self.environ = environ
    def get_response(self, application):
        return application
    def copy(self):
        self.copied = True
        return self
class DummyStartResponse:
    status = ()
    headers = ()
    def __call__(self, status, headers):
        self.status = status
        self.headers = headers
pyramid/tests/test_traversal.py
@@ -19,6 +19,9 @@
    def test_twodots(self):
        self.assertEqual(self._callFUT('foo/../bar'), (u'bar',))
    def test_twodots_at_start(self):
        self.assertEqual(self._callFUT('../../bar'), (u'bar',))
    def test_element_urllquoted(self):
        self.assertEqual(self._callFUT('/foo/space%20thing/bar'),
                         (u'foo', u'space thing', u'bar'))
pyramid/tests/test_view.py
@@ -548,27 +548,6 @@
        result = self._callFUT(context, request)
        self.assertEqual(result, 'abc')
class Test_patch_mimetypes(unittest.TestCase):
    def _callFUT(self, module):
        from pyramid.view import init_mimetypes
        return init_mimetypes(module)
    def test_has_init(self):
        class DummyMimetypes(object):
            def init(self):
                self.initted = True
        module = DummyMimetypes()
        result = self._callFUT(module)
        self.assertEqual(result, True)
        self.assertEqual(module.initted, True)
    def test_missing_init(self):
        class DummyMimetypes(object):
            pass
        module = DummyMimetypes()
        result = self._callFUT(module)
        self.assertEqual(result, False)
class Test_static(unittest.TestCase):
    def setUp(self):
        from zope.deprecation import __show__
@@ -578,38 +557,14 @@
        from zope.deprecation import __show__
        __show__.on()
    def _getTargetClass(self):
    def _makeOne(self, path, package_name):
        from pyramid.view import static
        return static
    def _makeOne(self, path, package_name=None):
        return self._getTargetClass()(path, package_name=package_name)
        return static(path, package_name)
        
    def _makeEnviron(self, **extras):
        environ = {
            'wsgi.url_scheme':'http',
            'wsgi.version':(1,0),
            'SERVER_NAME':'localhost',
            'SERVER_PORT':'8080',
            'REQUEST_METHOD':'GET',
            }
        environ.update(extras)
        return environ
    def test_relpath_subpath(self):
    def test_it(self):
        path = 'fixtures'
        view = self._makeOne(path)
        context = DummyContext()
        request = DummyRequest()
        request.subpath = ['__init__.py']
        request.environ = self._makeEnviron()
        response = view(context, request)
        self.assertEqual(request.copied, True)
        self.assertEqual(response.root_resource, 'fixtures')
        self.assertEqual(response.resource_name, 'fixtures')
        self.assertEqual(response.package_name, 'pyramid.tests')
        self.assertEqual(response.cache_max_age, 3600)
        view = self._makeOne(path, None)
        self.assertEqual(view.docroot, 'fixtures')
class ExceptionResponse(Exception):
    status = '404 Not Found'
@@ -632,13 +587,6 @@
            environ = {}
        self.environ = environ
        
    def get_response(self, application):
        return application
    def copy(self):
        self.copied = True
        return self
from pyramid.interfaces import IResponse
from zope.interface import implements
pyramid/traversal.py
@@ -479,7 +479,8 @@
        if not segment or segment=='.':
            continue
        elif segment == '..':
            del clean[-1]
            if clean:
                del clean[-1]
        else:
            try:
                segment = segment.decode('utf-8')
pyramid/view.py
@@ -1,4 +1,3 @@
import mimetypes
import venusian
from zope.interface import providedBy
@@ -13,21 +12,6 @@
from pyramid.path import caller_package
from pyramid.static import static_view
from pyramid.threadlocal import get_current_registry
def init_mimetypes(mimetypes):
    # this is a function so it can be unittested
    if hasattr(mimetypes, 'init'):
        mimetypes.init()
        return True
    return False
# See http://bugs.python.org/issue5853 which is a recursion bug
# that seems to effect Python 2.6, Python 2.6.1, and 2.6.2 (a fix
# has been applied on the Python 2 trunk).  This workaround should
# really be in Paste if anywhere, but it's easiest to just do it
# here and get it over with to avoid needing to deal with any
# fallout.
init_mimetypes(mimetypes)
_marker = object()