diff options
| -rw-r--r-- | CHANGES.txt | 13 | ||||
| -rw-r--r-- | pyramid/config/views.py | 95 | ||||
| -rw-r--r-- | pyramid/static.py | 95 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_views.py | 164 | ||||
| -rw-r--r-- | pyramid/tests/test_static.py | 162 | ||||
| -rw-r--r-- | pyramid/tests/test_url.py | 2 |
6 files changed, 268 insertions, 263 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 655fb4956..0a015aa0a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -128,6 +128,9 @@ Internal - Removed the ``_set_security_policies`` method of the Configurator. +- Moved the ``StaticURLInfo`` class from ``pyramid.static`` to + ``pyramid.config.views``. + Deprecations ------------ @@ -135,8 +138,8 @@ Deprecations ``debug_notfound``) are now meant to be prefixed with the prefix ``pyramid.``. For example: ``debug_all`` -> ``pyramid.debug_all``. The old non-prefixed settings will continue to work indefinitely but supplying - them may print a deprecation warning. All scaffolds and tutorials have - been changed to use prefixed settings. + them may eventually print a deprecation warning. All scaffolds and + tutorials have been changed to use prefixed settings. - The ``settings`` dictionary now raises a deprecation warning when you attempt to access its values via ``__getattr__`` instead of @@ -153,7 +156,8 @@ Backwards Incompatibilities single ``callable`` argument (a sequence of callables used to be permitted). If you are passing more than one ``callable`` to ``pyramid.config.Configurator.include``, it will break. You now must now - instead make a separate call to the method for each callable. + instead make a separate call to the method for each callable. This change + was introduced to support the ``route_prefix`` feature of include. Documentation ------------- @@ -180,6 +184,9 @@ Documentation - Added a Logging chapter to the narrative docs (based on the Pylons logging docs, thanks Phil). +- Added a Paste chapter to the narrative docs (moved content from the Project + chapter). + - Added the ``pyramid.interfaces.IDict`` interface representing the methods of a dictionary, for documentation purposes only (IMultiDict and IBeforeRender inherit from it). diff --git a/pyramid/config/views.py b/pyramid/config/views.py index c4c9f1fbc..2d39524ac 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1,4 +1,6 @@ import inspect +from urlparse import urljoin +from urlparse import urlparse from zope.interface import classProvides from zope.interface import implements @@ -29,7 +31,8 @@ from pyramid.exceptions import PredicateMismatch from pyramid.httpexceptions import HTTPForbidden from pyramid.httpexceptions import HTTPNotFound from pyramid.security import NO_PERMISSION_REQUIRED -from pyramid.static import StaticURLInfo +from pyramid.static import static_view +from pyramid.url import route_url from pyramid.view import render_view_to_response from pyramid import renderers @@ -1379,3 +1382,93 @@ def isexception(o): (inspect.isclass(o) and (issubclass(o, Exception))) ) + +class StaticURLInfo(object): + implements(IStaticURLInfo) + + route_url = staticmethod(route_url) # for testing only + + def __init__(self, config): + self.config = config + self.registrations = [] + + def generate(self, path, request, **kw): + for (name, spec, is_url) in self.registrations: + if path.startswith(spec): + subpath = path[len(spec):] + if is_url: + return urljoin(name, subpath) + else: + kw['subpath'] = subpath + return self.route_url(name, request, **kw) + + raise ValueError('No static URL definition matching %s' % path) + + def add(self, name, spec, **extra): + # This feature only allows for the serving of a directory and + # the files contained within, not of a single asset; + # appending a slash here if the spec doesn't have one is + # required for proper prefix matching done in ``generate`` + # (``subpath = path[len(spec):]``). + if not spec.endswith('/'): + spec = spec + '/' + + # we also make sure the name ends with a slash, purely as a + # convenience: a name that is a url is required to end in a + # slash, so that ``urljoin(name, subpath))`` will work above + # when the name is a URL, and it doesn't hurt things for it to + # have a name that ends in a slash if it's used as a route + # name instead of a URL. + if not name.endswith('/'): + # make sure it ends with a slash + name = name + '/' + + names = [ t[0] for t in self.registrations ] + + if name in names: + idx = names.index(name) + self.registrations.pop(idx) + + if urlparse(name)[0]: + # it's a URL + self.registrations.append((name, spec, True)) + else: + # it's a view name + cache_max_age = extra.pop('cache_max_age', None) + # create a view + view = static_view(spec, cache_max_age=cache_max_age, + use_subpath=True) + + # Mutate extra to allow factory, etc to be passed through here. + # Treat permission specially because we'd like to default to + # permissiveness (see docs of config.add_static_view). We need + # to deal with both ``view_permission`` and ``permission`` + # because ``permission`` is used in the docs for add_static_view, + # but ``add_route`` prefers ``view_permission`` + permission = extra.pop('view_permission', None) + if permission is None: + permission = extra.pop('permission', None) + if permission is None: + permission = NO_PERMISSION_REQUIRED + + context = extra.pop('view_context', None) + if context is None: + context = extra.pop('view_for', None) + if context is None: + context = extra.pop('for_', None) + + renderer = extra.pop('view_renderer', None) + if renderer is None: + renderer = extra.pop('renderer', None) + + attr = extra.pop('view_attr', None) + + # register a route using the computed view, permission, and + # pattern, plus any extras passed to us via add_static_view + pattern = "%s*subpath" % name # name already ends with slash + self.config.add_route(name, pattern, **extra) + self.config.add_view(route_name=name, view=view, + permission=permission, context=context, + renderer=renderer, attr=attr) + self.registrations.append((name, spec, False)) + diff --git a/pyramid/static.py b/pyramid/static.py index b1fab066f..1291ae58f 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -1,21 +1,14 @@ import os import pkg_resources -from urlparse import urljoin -from urlparse import urlparse from paste import httpexceptions from paste import request from paste.httpheaders import ETAG from paste.urlparser import StaticURLParser -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.security import NO_PERMISSION_REQUIRED -from pyramid.url import route_url class PackageURLParser(StaticURLParser): """ This probably won't work with zipimported resources """ @@ -84,94 +77,6 @@ class PackageURLParser(StaticURLParser): return '<%s %s:%s at %s>' % (self.__class__.__name__, self.package_name, self.root_resource, id(self)) -class StaticURLInfo(object): - implements(IStaticURLInfo) - - route_url = staticmethod(route_url) # for testing only - - def __init__(self, config): - self.config = config - self.registrations = [] - - def generate(self, path, request, **kw): - for (name, spec, is_url) in self.registrations: - if path.startswith(spec): - subpath = path[len(spec):] - if is_url: - return urljoin(name, subpath) - else: - kw['subpath'] = subpath - return self.route_url(name, request, **kw) - - raise ValueError('No static URL definition matching %s' % path) - - def add(self, name, spec, **extra): - # This feature only allows for the serving of a directory and - # the files contained within, not of a single asset; - # appending a slash here if the spec doesn't have one is - # required for proper prefix matching done in ``generate`` - # (``subpath = path[len(spec):]``). - if not spec.endswith('/'): - spec = spec + '/' - - # we also make sure the name ends with a slash, purely as a - # convenience: a name that is a url is required to end in a - # slash, so that ``urljoin(name, subpath))`` will work above - # when the name is a URL, and it doesn't hurt things for it to - # have a name that ends in a slash if it's used as a route - # name instead of a URL. - if not name.endswith('/'): - # make sure it ends with a slash - name = name + '/' - - names = [ t[0] for t in self.registrations ] - - if name in names: - idx = names.index(name) - self.registrations.pop(idx) - - if urlparse(name)[0]: - # it's a URL - self.registrations.append((name, spec, True)) - else: - # it's a view name - cache_max_age = extra.pop('cache_max_age', None) - # create a view - view = static_view(spec, cache_max_age=cache_max_age, - use_subpath=True) - - # Mutate extra to allow factory, etc to be passed through here. - # Treat permission specially because we'd like to default to - # permissiveness (see docs of config.add_static_view). We need - # to deal with both ``view_permission`` and ``permission`` - # because ``permission`` is used in the docs for add_static_view, - # but ``add_route`` prefers ``view_permission`` - permission = extra.pop('view_permission', None) - if permission is None: - permission = extra.pop('permission', None) - if permission is None: - permission = NO_PERMISSION_REQUIRED - - context = extra.pop('view_context', None) - if context is None: - context = extra.pop('view_for', None) - if context is None: - context = extra.pop('for_', None) - - renderer = extra.pop('view_renderer', None) - if renderer is None: - renderer = extra.pop('renderer', None) - - attr = extra.pop('view_attr', None) - - # register a route using the computed view, permission, and - # pattern, plus any extras passed to us via add_static_view - pattern = "%s*subpath" % name # name already ends with slash - self.config.add_route(name, pattern, **extra) - self.config.add_view(route_name=name, view=view, - permission=permission, context=context, - renderer=renderer, attr=attr) - self.registrations.append((name, spec, False)) class static_view(object): """ An instance of this class is a callable which can act as a diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index d62909603..c9c276368 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -1670,6 +1670,160 @@ class Test_preserve_view_attrs(unittest.TestCase): view2.__predicated__.im_func) +class TestStaticURLInfo(unittest.TestCase): + def _getTargetClass(self): + from pyramid.config.views import StaticURLInfo + return StaticURLInfo + + def _makeOne(self, config): + return self._getTargetClass()(config) + + def test_verifyClass(self): + from pyramid.interfaces import IStaticURLInfo + from zope.interface.verify import verifyClass + verifyClass(IStaticURLInfo, self._getTargetClass()) + + def test_verifyObject(self): + from pyramid.interfaces import IStaticURLInfo + from zope.interface.verify import verifyObject + verifyObject(IStaticURLInfo, self._makeOne(None)) + + def test_ctor(self): + info = self._makeOne(None) + self.assertEqual(info.registrations, []) + self.assertEqual(info.config, None) + + def test_generate_missing(self): + inst = self._makeOne(None) + request = DummyRequest() + self.assertRaises(ValueError, inst.generate, 'path', request) + + def test_generate_registration_miss(self): + inst = self._makeOne(None) + inst.registrations = [('name', 'spec', False), + ('http://example.com/foo/', 'package:path/',True)] + request = DummyRequest() + result = inst.generate('package:path/abc', request) + self.assertEqual(result, 'http://example.com/foo/abc') + + def test_generate_slash_in_name1(self): + inst = self._makeOne(None) + inst.registrations = [('http://example.com/foo/', 'package:path/',True)] + request = DummyRequest() + result = inst.generate('package:path/abc', request) + self.assertEqual(result, 'http://example.com/foo/abc') + + def test_generate_slash_in_name2(self): + inst = self._makeOne(None) + inst.registrations = [('http://example.com/foo/', 'package:path/',True)] + request = DummyRequest() + result = inst.generate('package:path/', request) + self.assertEqual(result, 'http://example.com/foo/') + + def test_generate_route_url(self): + inst = self._makeOne(None) + inst.registrations = [('viewname/', 'package:path/', False)] + def route_url(n, r, **kw): + self.assertEqual(n, 'viewname/') + self.assertEqual(r, request) + self.assertEqual(kw, {'subpath':'abc', 'a':1}) + return 'url' + request = DummyRequest() + inst.route_url = route_url + result = inst.generate('package:path/abc', request, a=1) + self.assertEqual(result, 'url') + + def test_add_already_exists(self): + inst = self._makeOne(None) + inst.registrations = [('http://example.com/', 'package:path/', True)] + inst.add('http://example.com', 'anotherpackage:path') + expected = [('http://example.com/', 'anotherpackage:path/', True)] + self.assertEqual(inst.registrations, expected) + + def test_add_url_withendslash(self): + inst = self._makeOne(None) + inst.add('http://example.com/', 'anotherpackage:path') + expected = [('http://example.com/', 'anotherpackage:path/', True)] + self.assertEqual(inst.registrations, expected) + + def test_add_url_noendslash(self): + inst = self._makeOne(None) + inst.add('http://example.com', 'anotherpackage:path') + expected = [('http://example.com/', 'anotherpackage:path/', True)] + self.assertEqual(inst.registrations, expected) + + def test_add_viewname(self): + from pyramid.security import NO_PERMISSION_REQUIRED + from pyramid.static import static_view + config = DummyConfig() + inst = self._makeOne(config) + inst.add('view', 'anotherpackage:path', cache_max_age=1) + expected = [('view/', 'anotherpackage:path/', False)] + self.assertEqual(inst.registrations, expected) + 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() + inst = self._makeOne(config) + inst.add('view', 'anotherpackage:path', cache_max_age=1, + permission='abc') + self.assertEqual(config.view_kw['permission'], 'abc') + + def test_add_viewname_with_view_permission(self): + config = DummyConfig() + inst = self._makeOne(config) + inst.add('view', 'anotherpackage:path', cache_max_age=1, + view_permission='abc') + self.assertEqual(config.view_kw['permission'], 'abc') + + def test_add_viewname_with_view_context(self): + config = DummyConfig() + inst = self._makeOne(config) + inst.add('view', 'anotherpackage:path', cache_max_age=1, + view_context=DummyContext) + self.assertEqual(config.view_kw['context'], DummyContext) + + def test_add_viewname_with_view_for(self): + config = DummyConfig() + inst = self._makeOne(config) + inst.add('view', 'anotherpackage:path', cache_max_age=1, + view_for=DummyContext) + self.assertEqual(config.view_kw['context'], DummyContext) + + def test_add_viewname_with_for_(self): + config = DummyConfig() + inst = self._makeOne(config) + inst.add('view', 'anotherpackage:path', cache_max_age=1, + for_=DummyContext) + self.assertEqual(config.view_kw['context'], DummyContext) + + def test_add_viewname_with_view_renderer(self): + config = DummyConfig() + inst = self._makeOne(config) + inst.add('view', 'anotherpackage:path', cache_max_age=1, + view_renderer='mypackage:templates/index.pt') + self.assertEqual(config.view_kw['renderer'], + 'mypackage:templates/index.pt') + + def test_add_viewname_with_renderer(self): + config = DummyConfig() + inst = self._makeOne(config) + inst.add('view', 'anotherpackage:path', cache_max_age=1, + renderer='mypackage:templates/index.pt') + self.assertEqual(config.view_kw['renderer'], + 'mypackage:templates/index.pt') + + def test_add_viewname_with_view_attr(self): + config = DummyConfig() + inst = self._makeOne(config) + inst.add('view', 'anotherpackage:path', cache_max_age=1, + view_attr='attr') + self.assertEqual(config.view_kw['attr'], 'attr') + + class DummyRequest: subpath = () matchdict = None @@ -1717,6 +1871,15 @@ class DummySecurityPolicy: def permits(self, context, principals, permission): return self.permitted +class DummyConfig: + def add_route(self, *args, **kw): + self.route_args = args + self.route_kw = kw + + def add_view(self, *args, **kw): + self.view_args = args + self.view_kw = kw + def parse_httpdate(s): import datetime # cannot use %Z, must use literal GMT; Jython honors timezone @@ -1729,4 +1892,3 @@ def assert_similar_datetime(one, two): two_attr = getattr(two, attr) if not one_attr == two_attr: # pragma: no cover raise AssertionError('%r != %r in %s' % (one_attr, two_attr, attr)) - diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py index d698ca4f2..357ec7551 100644 --- a/pyramid/tests/test_static.py +++ b/pyramid/tests/test_static.py @@ -327,168 +327,6 @@ class Test_static_view(unittest.TestCase): self.assertEqual(request.environ['PATH_INFO'], '/sub/path/') self.assertEqual(request.environ['SCRIPT_NAME'], '/path_info') -class TestStaticURLInfo(unittest.TestCase): - def _getTargetClass(self): - from pyramid.static import StaticURLInfo - return StaticURLInfo - - def _makeOne(self, config): - return self._getTargetClass()(config) - - def test_verifyClass(self): - from pyramid.interfaces import IStaticURLInfo - from zope.interface.verify import verifyClass - verifyClass(IStaticURLInfo, self._getTargetClass()) - - def test_verifyObject(self): - from pyramid.interfaces import IStaticURLInfo - from zope.interface.verify import verifyObject - verifyObject(IStaticURLInfo, self._makeOne(None)) - - def test_ctor(self): - info = self._makeOne(None) - self.assertEqual(info.registrations, []) - self.assertEqual(info.config, None) - - def test_generate_missing(self): - inst = self._makeOne(None) - request = DummyRequest() - self.assertRaises(ValueError, inst.generate, 'path', request) - - def test_generate_registration_miss(self): - inst = self._makeOne(None) - inst.registrations = [('name', 'spec', False), - ('http://example.com/foo/', 'package:path/',True)] - request = DummyRequest() - result = inst.generate('package:path/abc', request) - self.assertEqual(result, 'http://example.com/foo/abc') - - def test_generate_slash_in_name1(self): - inst = self._makeOne(None) - inst.registrations = [('http://example.com/foo/', 'package:path/',True)] - request = DummyRequest() - result = inst.generate('package:path/abc', request) - self.assertEqual(result, 'http://example.com/foo/abc') - - def test_generate_slash_in_name2(self): - inst = self._makeOne(None) - inst.registrations = [('http://example.com/foo/', 'package:path/',True)] - request = DummyRequest() - result = inst.generate('package:path/', request) - self.assertEqual(result, 'http://example.com/foo/') - - def test_generate_route_url(self): - inst = self._makeOne(None) - inst.registrations = [('viewname/', 'package:path/', False)] - def route_url(n, r, **kw): - self.assertEqual(n, 'viewname/') - self.assertEqual(r, request) - self.assertEqual(kw, {'subpath':'abc', 'a':1}) - return 'url' - request = DummyRequest() - inst.route_url = route_url - result = inst.generate('package:path/abc', request, a=1) - self.assertEqual(result, 'url') - - def test_add_already_exists(self): - inst = self._makeOne(None) - inst.registrations = [('http://example.com/', 'package:path/', True)] - inst.add('http://example.com', 'anotherpackage:path') - expected = [('http://example.com/', 'anotherpackage:path/', True)] - self.assertEqual(inst.registrations, expected) - - def test_add_url_withendslash(self): - inst = self._makeOne(None) - inst.add('http://example.com/', 'anotherpackage:path') - expected = [('http://example.com/', 'anotherpackage:path/', True)] - self.assertEqual(inst.registrations, expected) - - def test_add_url_noendslash(self): - inst = self._makeOne(None) - inst.add('http://example.com', 'anotherpackage:path') - expected = [('http://example.com/', 'anotherpackage:path/', True)] - self.assertEqual(inst.registrations, expected) - - def test_add_viewname(self): - from pyramid.security import NO_PERMISSION_REQUIRED - from pyramid.static import static_view - config = DummyConfig() - inst = self._makeOne(config) - inst.add('view', 'anotherpackage:path', cache_max_age=1) - expected = [('view/', 'anotherpackage:path/', False)] - self.assertEqual(inst.registrations, expected) - 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() - inst = self._makeOne(config) - inst.add('view', 'anotherpackage:path', cache_max_age=1, - permission='abc') - self.assertEqual(config.view_kw['permission'], 'abc') - - def test_add_viewname_with_view_permission(self): - config = DummyConfig() - inst = self._makeOne(config) - inst.add('view', 'anotherpackage:path', cache_max_age=1, - view_permission='abc') - self.assertEqual(config.view_kw['permission'], 'abc') - - def test_add_viewname_with_view_context(self): - config = DummyConfig() - inst = self._makeOne(config) - inst.add('view', 'anotherpackage:path', cache_max_age=1, - view_context=DummyContext) - self.assertEqual(config.view_kw['context'], DummyContext) - - def test_add_viewname_with_view_for(self): - config = DummyConfig() - inst = self._makeOne(config) - inst.add('view', 'anotherpackage:path', cache_max_age=1, - view_for=DummyContext) - self.assertEqual(config.view_kw['context'], DummyContext) - - def test_add_viewname_with_for_(self): - config = DummyConfig() - inst = self._makeOne(config) - inst.add('view', 'anotherpackage:path', cache_max_age=1, - for_=DummyContext) - self.assertEqual(config.view_kw['context'], DummyContext) - - def test_add_viewname_with_view_renderer(self): - config = DummyConfig() - inst = self._makeOne(config) - inst.add('view', 'anotherpackage:path', cache_max_age=1, - view_renderer='mypackage:templates/index.pt') - self.assertEqual(config.view_kw['renderer'], - 'mypackage:templates/index.pt') - - def test_add_viewname_with_renderer(self): - config = DummyConfig() - inst = self._makeOne(config) - inst.add('view', 'anotherpackage:path', cache_max_age=1, - renderer='mypackage:templates/index.pt') - self.assertEqual(config.view_kw['renderer'], - 'mypackage:templates/index.pt') - - def test_add_viewname_with_view_attr(self): - config = DummyConfig() - inst = self._makeOne(config) - inst.add('view', 'anotherpackage:path', cache_max_age=1, - view_attr='attr') - self.assertEqual(config.view_kw['attr'], 'attr') - -class DummyConfig: - def add_route(self, *args, **kw): - self.route_args = args - self.route_kw = kw - - def add_view(self, *args, **kw): - self.view_args = args - self.view_kw = kw - class DummyStartResponse: def __call__(self, status, headerlist, exc_info=None): self.status = status diff --git a/pyramid/tests/test_url.py b/pyramid/tests/test_url.py index 4a822725c..4d113f0de 100644 --- a/pyramid/tests/test_url.py +++ b/pyramid/tests/test_url.py @@ -384,7 +384,7 @@ class TestURLMethodsMixin(unittest.TestCase): def test_static_url_abspath_integration_with_staticurlinfo(self): import os from pyramid.interfaces import IStaticURLInfo - from pyramid.static import StaticURLInfo + from pyramid.config.views import StaticURLInfo info = StaticURLInfo(self.config) here = os.path.abspath(os.path.dirname(__file__)) info.add('absstatic', here) |
