summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pyramid/config/views.py108
-rw-r--r--pyramid/interfaces.py45
-rw-r--r--pyramid/static.py10
-rw-r--r--pyramid/tests/test_config/test_views.py66
4 files changed, 106 insertions, 123 deletions
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index e386bc4e1..67a70145c 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -1,3 +1,4 @@
+import bisect
import inspect
import operator
import os
@@ -1855,18 +1856,7 @@ class ViewsConfiguratorMixin(object):
``Expires`` and ``Cache-Control`` headers for static assets served.
Note that this argument has no effect when the ``name`` is a *url
prefix*. By default, this argument is ``None``, meaning that no
- particular Expires or Cache-Control headers are set in the response,
- unless ``cachebust`` is specified.
-
- The ``cachebust`` keyword argument may be set to cause
- :meth:`~pyramid.request.Request.static_url` to use cache busting when
- generating URLs. See :ref:`cache_busting` for general information
- about cache busting. The value of the ``cachebust`` argument must
- be an object which implements
- :class:`~pyramid.interfaces.ICacheBuster`. If the ``cachebust``
- argument is provided, the default for ``cache_max_age`` is modified
- to be ten years. ``cache_max_age`` may still be explicitly provided
- to override this default.
+ particular Expires or Cache-Control headers are set in the response.
The ``permission`` keyword argument is used to specify the
:term:`permission` required by a user to execute the static view. By
@@ -1946,11 +1936,32 @@ class ViewsConfiguratorMixin(object):
See :ref:`static_assets_section` for more information.
"""
spec = self._make_spec(path)
+ info = self._get_static_info()
+ info.add(self, name, spec, **kw)
+
+ def add_cache_buster(self, path, cachebust):
+ """
+ The ``cachebust`` keyword argument may be set to cause
+ :meth:`~pyramid.request.Request.static_url` to use cache busting when
+ generating URLs. See :ref:`cache_busting` for general information
+ about cache busting. The value of the ``cachebust`` argument must
+ be an object which implements
+ :class:`~pyramid.interfaces.ICacheBuster`. If the ``cachebust``
+ argument is provided, the default for ``cache_max_age`` is modified
+ to be ten years. ``cache_max_age`` may still be explicitly provided
+ to override this default.
+
+ """
+ spec = self._make_spec(path)
+ info = self._get_static_info()
+ info.add_cache_buster(self, spec, cachebust)
+
+ def _get_static_info(self):
info = self.registry.queryUtility(IStaticURLInfo)
if info is None:
info = StaticURLInfo()
self.registry.registerUtility(info, IStaticURLInfo)
- info.add(self, name, spec, **kw)
+ return info
def isexception(o):
if IInterface.providedBy(o):
@@ -1964,26 +1975,18 @@ def isexception(o):
@implementer(IStaticURLInfo)
class StaticURLInfo(object):
- def _get_registrations(self, registry):
- try:
- reg = registry._static_url_registrations
- except AttributeError:
- reg = registry._static_url_registrations = []
- return reg
+ def __init__(self):
+ self.registrations = []
+ self.cache_busters = []
def generate(self, path, request, **kw):
- try:
- registry = request.registry
- except AttributeError: # bw compat (for tests)
- registry = get_current_registry()
- registrations = self._get_registrations(registry)
- for (url, spec, route_name, cachebust) in registrations:
+ for (url, spec, route_name) in self.registrations:
if path.startswith(spec):
subpath = path[len(spec):]
if WIN: # pragma: no cover
subpath = subpath.replace('\\', '/') # windows
- if cachebust:
- subpath, kw = cachebust(subpath, kw)
+ # translate spec into overridden spec and lookup cache buster
+ # to modify subpath, kw
if url is None:
kw['subpath'] = subpath
return request.route_url(route_name, **kw)
@@ -2023,19 +2026,6 @@ class StaticURLInfo(object):
# make sure it ends with a slash
name = name + '/'
- if config.registry.settings.get('pyramid.prevent_cachebust'):
- cb = None
- else:
- cb = extra.pop('cachebust', None)
- if cb:
- def cachebust(subpath, kw):
- subpath_tuple = tuple(subpath.split('/'))
- subpath_tuple, kw = cb.pregenerate(
- spec + subpath, subpath_tuple, kw)
- return '/'.join(subpath_tuple), kw
- else:
- cachebust = None
-
if url_parse(name).netloc:
# it's a URL
# url, spec, route_name
@@ -2044,14 +2034,11 @@ class StaticURLInfo(object):
else:
# it's a view name
url = None
- ten_years = 10 * 365 * 24 * 60 * 60 # more or less
- default = ten_years if cb else None
- cache_max_age = extra.pop('cache_max_age', default)
+ cache_max_age = extra.pop('cache_max_age', None)
# create a view
- cb_match = getattr(cb, 'match', None)
view = static_view(spec, cache_max_age=cache_max_age,
- use_subpath=True, cachebust_match=cb_match)
+ use_subpath=True)
# Mutate extra to allow factory, etc to be passed through here.
# Treat permission specially because we'd like to default to
@@ -2083,7 +2070,7 @@ class StaticURLInfo(object):
)
def register():
- registrations = self._get_registrations(config.registry)
+ registrations = self.registrations
names = [t[0] for t in registrations]
@@ -2092,7 +2079,7 @@ class StaticURLInfo(object):
registrations.pop(idx)
# url, spec, route_name
- registrations.append((url, spec, route_name, cachebust))
+ registrations.append((url, spec, route_name))
intr = config.introspectable('static views',
name,
@@ -2102,3 +2089,30 @@ class StaticURLInfo(object):
intr['spec'] = spec
config.action(None, callable=register, introspectables=(intr,))
+
+ def add_cache_buster(self, config, spec, cachebust):
+ def register():
+ cache_busters = self.cache_busters
+
+ specs = [t[0] for t in cache_busters]
+ if spec in specs:
+ idx = specs.index(spec)
+ cache_busters.pop(idx)
+
+ lengths = [len(t[0]) for t in cache_busters]
+ new_idx = bisect.bisect_left(lengths, len(spec))
+ cache_busters.insert(new_idx, (spec, cachebust))
+
+ intr = config.introspectable('cache busters',
+ spec,
+ 'cache buster for %r' % spec,
+ 'cache buster')
+ intr['cachebust'] = cachebust
+ intr['spec'] = spec
+
+ config.action(None, callable=register, introspectables=(intr,))
+
+ def _find_cache_buster(self, registry, spec):
+ for base_spec, cachebust in self.cache_busters:
+ if base_spec.startswith(spec):
+ pass
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index 90534593c..bdf5bdfbe 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -584,6 +584,9 @@ class IStaticURLInfo(Interface):
def generate(path, request, **kw):
""" Generate a URL for the given path """
+ def add_cache_buster(config, spec, cache_buster):
+ """ Add a new cache buster to a particular set of assets """
+
class IResponseFactory(Interface):
""" A utility which generates a response """
def __call__(request):
@@ -1186,45 +1189,23 @@ class IPredicateList(Interface):
class ICacheBuster(Interface):
"""
- Instances of ``ICacheBuster`` may be provided as arguments to
- :meth:`~pyramid.config.Configurator.add_static_view`. Instances of
- ``ICacheBuster`` provide mechanisms for generating a cache bust token for
- a static asset, modifying a static asset URL to include a cache bust token,
- and, optionally, unmodifying a static asset URL in order to look up an
- asset. See :ref:`cache_busting`.
+ A cache buster modifies the URL generation machinery for
+ :meth:`~pyramid.request.Request.static_url`. See :ref:`cache_busting`.
.. versionadded:: 1.6
"""
- def pregenerate(pathspec, subpath, kw):
+ def __call__(pathspec, subpath, kw):
"""
Modifies a subpath and/or keyword arguments from which a static asset
URL will be computed during URL generation. The ``pathspec`` argument
is the path specification for the resource to be cache busted.
- The ``subpath`` argument is a tuple of path elements that represent the
- portion of the asset URL which is used to find the asset. The ``kw``
- argument is a dict of keywords that are to be passed eventually to
- :meth:`~pyramid.request.Request.route_url` for URL generation. The
- return value should be a two-tuple of ``(subpath, kw)`` which are
- versions of the same arguments modified to include the cache bust token
- in the generated URL.
- """
-
- def match(subpath):
- """
- Performs the logical inverse of
- :meth:`~pyramid.interfaces.ICacheBuster.pregenerate` by taking a
- subpath from a cache busted URL and removing the cache bust token, so
- that :app:`Pyramid` can find the underlying asset.
-
- ``subpath`` is the subpath portion of the URL for an incoming request
- for a static asset. The return value should be the same tuple with the
- cache busting token elided.
-
- If the cache busting scheme in use doesn't specifically modify the path
- portion of the generated URL (e.g. it adds a query string), a method
- which implements this interface may not be necessary. It is
- permissible for an instance of
- :class:`~pyramid.interfaces.ICacheBuster` to omit this method.
+ The ``subpath`` argument is a path of ``/``-delimited segments that
+ represent the portion of the asset URL which is used to find the asset.
+ The ``kw`` argument is a dict of keywords that are to be passed
+ eventually to :meth:`~pyramid.request.Request.static_url` for URL
+ generation. The return value should be a two-tuple of
+ ``(subpath, kw)`` which are versions of the same arguments modified
+ to include the cache bust token in the generated URL.
"""
# configuration phases: a lower phase number means the actions associated
diff --git a/pyramid/static.py b/pyramid/static.py
index c7a5c7ba5..cda98bea4 100644
--- a/pyramid/static.py
+++ b/pyramid/static.py
@@ -179,7 +179,7 @@ class QueryStringCacheBuster(object):
def __init__(self, param='x'):
self.param = param
- def pregenerate(self, pathspec, subpath, kw):
+ def __call__(self, pathspec, subpath, kw):
token = self.tokenize(pathspec)
query = kw.setdefault('_query', {})
if isinstance(query, dict):
@@ -289,8 +289,6 @@ class ManifestCacheBuster(object):
self._mtime = mtime
return self._manifest
- def pregenerate(self, pathspec, subpath, kw):
- path = '/'.join(subpath)
- path = self.manifest.get(path, path)
- new_subpath = path.split('/')
- return (new_subpath, kw)
+ def __call__(self, pathspec, subpath, kw):
+ subpath = self.manifest.get(subpath, subpath)
+ return (subpath, kw)
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
index acfb81962..020ed131d 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -3865,22 +3865,11 @@ class TestStaticURLInfo(unittest.TestCase):
def _makeOne(self):
return self._getTargetClass()()
- def _makeConfig(self, registrations=None):
- config = DummyConfig()
- registry = DummyRegistry()
- if registrations is not None:
- registry._static_url_registrations = registrations
- config.registry = registry
- return config
-
def _makeRequest(self):
request = DummyRequest()
request.registry = DummyRegistry()
return request
- def _assertRegistrations(self, config, expected):
- self.assertEqual(config.registry._static_url_registrations, expected)
-
def test_verifyClass(self):
from pyramid.interfaces import IStaticURLInfo
from zope.interface.verify import verifyClass
@@ -4002,12 +3991,12 @@ class TestStaticURLInfo(unittest.TestCase):
'http://example.com/abc%20def#La%20Pe%C3%B1a')
def test_generate_url_cachebust(self):
- def cachebust(subpath, kw):
+ def cachebust(request, subpath, kw):
kw['foo'] = 'bar'
return 'foo' + '/' + subpath, kw
inst = self._makeOne()
- registrations = [(None, 'package:path/', '__viewname', cachebust)]
- inst._get_registrations = lambda *x: registrations
+ inst.registrations = [(None, 'package:path/', '__viewname', cachebust)]
+ inst.cache_busters = [('package:path/', cachebust)]
request = self._makeRequest()
def route_url(n, **kw):
self.assertEqual(n, '__viewname')
@@ -4016,88 +4005,88 @@ class TestStaticURLInfo(unittest.TestCase):
inst.generate('package:path/abc', request)
def test_add_already_exists(self):
+ config = DummyConfig()
inst = self._makeOne()
- config = self._makeConfig(
- [('http://example.com/', 'package:path/', None)])
+ inst.registrations = [('http://example.com/', 'package:path/', None)]
inst.add(config, 'http://example.com', 'anotherpackage:path')
expected = [
- ('http://example.com/', 'anotherpackage:path/', None, None)]
- self._assertRegistrations(config, expected)
+ ('http://example.com/', 'anotherpackage:path/', None, None)]
+ self.assertEqual(inst.registrations, expected)
def test_add_package_root(self):
+ config = DummyConfig()
inst = self._makeOne()
- config = self._makeConfig()
inst.add(config, 'http://example.com', 'package:')
- expected = [('http://example.com/', 'package:', None, None)]
- self._assertRegistrations(config, expected)
+ expected = [('http://example.com/', 'package:', None, None)]
+ self.assertEqual(inst.registrations, expected)
def test_add_url_withendslash(self):
+ config = DummyConfig()
inst = self._makeOne()
- config = self._makeConfig()
inst.add(config, 'http://example.com/', 'anotherpackage:path')
expected = [
('http://example.com/', 'anotherpackage:path/', None, None)]
- self._assertRegistrations(config, expected)
+ self.assertEqual(inst.registrations, expected)
def test_add_url_noendslash(self):
+ config = DummyConfig()
inst = self._makeOne()
- config = self._makeConfig()
inst.add(config, 'http://example.com', 'anotherpackage:path')
expected = [
('http://example.com/', 'anotherpackage:path/', None, None)]
- self._assertRegistrations(config, expected)
+ self.assertEqual(inst.registrations, expected)
def test_add_url_noscheme(self):
+ config = DummyConfig()
inst = self._makeOne()
- config = self._makeConfig()
inst.add(config, '//example.com', 'anotherpackage:path')
expected = [('//example.com/', 'anotherpackage:path/', None, None)]
- self._assertRegistrations(config, expected)
+ self.assertEqual(inst.registrations, expected)
def test_add_viewname(self):
from pyramid.security import NO_PERMISSION_REQUIRED
from pyramid.static import static_view
- config = self._makeConfig()
+ config = DummyConfig()
inst = self._makeOne()
inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1)
expected = [(None, 'anotherpackage:path/', '__view/', None)]
- self._assertRegistrations(config, expected)
+ 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)
def test_add_viewname_with_route_prefix(self):
- config = self._makeConfig()
+ config = DummyConfig()
config.route_prefix = '/abc'
inst = self._makeOne()
inst.add(config, 'view', 'anotherpackage:path',)
expected = [(None, 'anotherpackage:path/', '__/abc/view/', None)]
- self._assertRegistrations(config, expected)
+ self.assertEqual(inst.registrations, expected)
self.assertEqual(config.route_args, ('__/abc/view/', 'view/*subpath'))
def test_add_viewname_with_permission(self):
- config = self._makeConfig()
+ config = DummyConfig()
inst = self._makeOne()
inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1,
permission='abc')
self.assertEqual(config.view_kw['permission'], 'abc')
def test_add_viewname_with_context(self):
- config = self._makeConfig()
+ config = DummyConfig()
inst = self._makeOne()
inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1,
context=DummyContext)
self.assertEqual(config.view_kw['context'], DummyContext)
def test_add_viewname_with_for_(self):
- config = self._makeConfig()
+ config = DummyConfig()
inst = self._makeOne()
inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1,
for_=DummyContext)
self.assertEqual(config.view_kw['context'], DummyContext)
def test_add_viewname_with_renderer(self):
- config = self._makeConfig()
+ config = DummyConfig()
inst = self._makeOne()
inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1,
renderer='mypackage:templates/index.pt')
@@ -4105,7 +4094,7 @@ class TestStaticURLInfo(unittest.TestCase):
'mypackage:templates/index.pt')
def test_add_cachebust_prevented(self):
- config = self._makeConfig()
+ config = DummyConfig()
config.registry.settings['pyramid.prevent_cachebust'] = True
inst = self._makeOne()
inst.add(config, 'view', 'mypackage:path', cachebust=True)
@@ -4113,7 +4102,7 @@ class TestStaticURLInfo(unittest.TestCase):
self.assertEqual(cachebust, None)
def test_add_cachebust_custom(self):
- config = self._makeConfig()
+ config = DummyConfig()
inst = self._makeOne()
inst.add(config, 'view', 'mypackage:path',
cachebust=DummyCacheBuster('foo'))
@@ -4236,7 +4225,8 @@ class DummyMultiView:
class DummyCacheBuster(object):
def __init__(self, token):
self.token = token
- def pregenerate(self, pathspec, subpath, kw):
+
+ def __call__(self, pathspec, subpath, kw):
kw['x'] = self.token
return subpath, kw