summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Merickel <michael@merickel.org>2015-12-14 00:17:39 -0600
committerMichael Merickel <michael@merickel.org>2015-12-14 00:17:39 -0600
commit4d19b84cb8134a0e7f030064e5d944defaa6970a (patch)
treef7015cbf1071d131594cb3b58e76cdb4edac90bb
parent4350699a208dc9304ae8c8dd165251f227ff5189 (diff)
downloadpyramid-4d19b84cb8134a0e7f030064e5d944defaa6970a.tar.gz
pyramid-4d19b84cb8134a0e7f030064e5d944defaa6970a.tar.bz2
pyramid-4d19b84cb8134a0e7f030064e5d944defaa6970a.zip
new default behavior matches virtual specs, old behavior hidden behind explicit=True
-rw-r--r--pyramid/config/views.py67
-rw-r--r--pyramid/interfaces.py26
-rw-r--r--pyramid/static.py10
-rw-r--r--pyramid/tests/test_config/test_views.py69
-rw-r--r--pyramid/tests/test_static.py2
5 files changed, 123 insertions, 51 deletions
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index 1fcdcb136..759276351 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -1,4 +1,3 @@
-import bisect
import inspect
import posixpath
import operator
@@ -1941,7 +1940,7 @@ class ViewsConfiguratorMixin(object):
info = self._get_static_info()
info.add(self, name, spec, **kw)
- def add_cache_buster(self, path, cachebust):
+ def add_cache_buster(self, path, cachebust, explicit=False):
"""
Add a cache buster to a set of files on disk.
@@ -1956,10 +1955,16 @@ class ViewsConfiguratorMixin(object):
be an object which implements
:class:`~pyramid.interfaces.ICacheBuster`.
+ If ``explicit`` is set to ``True`` then the ``path`` for the cache
+ buster will be matched based on the ``rawspec`` instead of the
+ ``pathspec`` as defined in the
+ :class:`~pyramid.interfaces.ICacheBuster` interface.
+ Default: ``False``.
+
"""
spec = self._make_spec(path)
info = self._get_static_info()
- info.add_cache_buster(self, spec, cachebust)
+ info.add_cache_buster(self, spec, cachebust, explicit=explicit)
def _get_static_info(self):
info = self.registry.queryUtility(IStaticURLInfo)
@@ -1992,7 +1997,7 @@ class StaticURLInfo(object):
subpath = subpath.replace('\\', '/') # windows
if self.cache_busters:
subpath, kw = self._bust_asset_path(
- request.registry, spec, subpath, kw)
+ request, spec, subpath, kw)
if url is None:
kw['subpath'] = subpath
return request.route_url(route_name, **kw)
@@ -2096,7 +2101,7 @@ class StaticURLInfo(object):
config.action(None, callable=register, introspectables=(intr,))
- def add_cache_buster(self, config, spec, cachebust):
+ def add_cache_buster(self, config, spec, cachebust, explicit=False):
if config.registry.settings.get('pyramid.prevent_cachebust'):
return
@@ -2112,29 +2117,46 @@ class StaticURLInfo(object):
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)
+ # find duplicate cache buster (old_idx)
+ # and insertion location (new_idx)
+ new_idx, old_idx = len(cache_busters), None
+ for idx, (spec_, cb_, explicit_) in enumerate(cache_busters):
+ # if we find an identical (spec, explicit) then use it
+ if spec == spec_ and explicit == explicit_:
+ old_idx = new_idx = idx
+ break
+
+ # past all explicit==False specs then add to the end
+ elif not explicit and explicit_:
+ new_idx = idx
+ break
+
+ # explicit matches and spec is shorter
+ elif explicit == explicit_ and len(spec) < len(spec_):
+ new_idx = idx
+ break
- lengths = [len(t[0]) for t in cache_busters]
- new_idx = bisect.bisect_left(lengths, len(spec))
- cache_busters.insert(new_idx, (spec, cachebust))
+ if old_idx is not None:
+ cache_busters.pop(old_idx)
+ cache_busters.insert(new_idx, (spec, cachebust, explicit))
intr = config.introspectable('cache busters',
spec,
'cache buster for %r' % spec,
'cache buster')
intr['cachebust'] = cachebust
- intr['spec'] = spec
+ intr['path'] = spec
+ intr['explicit'] = explicit
config.action(None, callable=register, introspectables=(intr,))
- def _bust_asset_path(self, registry, spec, subpath, kw):
+ def _bust_asset_path(self, request, spec, subpath, kw):
+ registry = request.registry
pkg_name, pkg_subpath = resolve_asset_spec(spec)
rawspec = None
if pkg_name is not None:
+ pathspec = '{0}:{1}{2}'.format(pkg_name, pkg_subpath, subpath)
overrides = registry.queryUtility(IPackageOverrides, name=pkg_name)
if overrides is not None:
resource_name = posixpath.join(pkg_subpath, subpath)
@@ -2145,14 +2167,19 @@ class StaticURLInfo(object):
rawspec = '{0}:{1}'.format(source.pkg_name, rawspec)
break
- if rawspec is None:
- rawspec = '{0}:{1}{2}'.format(pkg_name, pkg_subpath, subpath)
+ else:
+ pathspec = pkg_subpath + subpath
if rawspec is None:
- rawspec = pkg_subpath + subpath
+ rawspec = pathspec
- for base_spec, cachebust in reversed(self.cache_busters):
- if rawspec.startswith(base_spec):
- subpath, kw = cachebust(rawspec, subpath, kw)
+ kw['pathspec'] = pathspec
+ kw['rawspec'] = rawspec
+ for spec_, cachebust, explicit in reversed(self.cache_busters):
+ if (
+ (explicit and rawspec.startswith(spec_)) or
+ (not explicit and pathspec.startswith(spec_))
+ ):
+ subpath, kw = cachebust(request, subpath, kw)
break
return subpath, kw
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index 153fdad03..bbdc5121d 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -1194,11 +1194,11 @@ class ICacheBuster(Interface):
.. versionadded:: 1.6
"""
- def __call__(pathspec, subpath, kw):
+ def __call__(request, 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.
+ URL will be computed during URL generation.
+
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
@@ -1209,10 +1209,22 @@ class ICacheBuster(Interface):
should be modified to include the cache bust token in the generated
URL.
- The ``pathspec`` refers to original location of the file, ignoring any
- calls to :meth:`pyramid.config.Configurator.override_asset`. For
- example, with a call ``request.static_url('myapp:static/foo.png'), the
- ``pathspec`` may be ``themepkg:bar.png``, assuming a call to
+ The ``kw`` dictionary contains extra arguments passed to
+ :meth:`~pyramid.request.Request.static_url` as well as some extra
+ items that may be usful including:
+
+ - ``pathspec`` is the path specification for the resource
+ to be cache busted.
+
+ - ``rawspec`` is the original location of the file, ignoring
+ any calls to :meth:`pyramid.config.Configurator.override_asset`.
+
+ The ``pathspec`` and ``rawspec`` values are only different in cases
+ where an asset has been mounted into a virtual location using
+ :meth:`pyramid.config.Configurator.override_asset`. For example, with
+ a call to ``request.static_url('myapp:static/foo.png'), the
+ ``pathspec`` is ``myapp:static/foo.png`` whereas the ``rawspec`` may
+ be ``themepkg:bar.png``, assuming a call to
``config.override_asset('myapp:static/foo.png', 'themepkg:bar.png')``.
"""
diff --git a/pyramid/static.py b/pyramid/static.py
index 9559cd881..4054d5be0 100644
--- a/pyramid/static.py
+++ b/pyramid/static.py
@@ -172,15 +172,15 @@ class QueryStringCacheBuster(object):
to the query string and defaults to ``'x'``.
To use this class, subclass it and provide a ``tokenize`` method which
- accepts a ``pathspec`` and returns a token.
+ accepts ``request, pathspec, kw`` and returns a token.
.. versionadded:: 1.6
"""
def __init__(self, param='x'):
self.param = param
- def __call__(self, pathspec, subpath, kw):
- token = self.tokenize(pathspec)
+ def __call__(self, request, subpath, kw):
+ token = self.tokenize(request, subpath, kw)
query = kw.setdefault('_query', {})
if isinstance(query, dict):
query[self.param] = token
@@ -205,7 +205,7 @@ class QueryStringConstantCacheBuster(QueryStringCacheBuster):
super(QueryStringConstantCacheBuster, self).__init__(param=param)
self._token = token
- def tokenize(self, pathspec):
+ def tokenize(self, request, subpath, kw):
return self._token
class ManifestCacheBuster(object):
@@ -290,6 +290,6 @@ class ManifestCacheBuster(object):
self._mtime = mtime
return self._manifest
- def __call__(self, pathspec, subpath, kw):
+ def __call__(self, request, 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 eda8d8b05..e89d43c9a 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -3978,13 +3978,15 @@ class TestStaticURLInfo(unittest.TestCase):
return 'foo' + '/' + subpath, kw
inst = self._makeOne()
inst.registrations = [(None, 'package:path/', '__viewname')]
- inst.cache_busters = [('package:path/', cachebust)]
+ inst.cache_busters = [('package:path/', cachebust, False)]
request = self._makeRequest()
called = [False]
def route_url(n, **kw):
called[0] = True
self.assertEqual(n, '__viewname')
- self.assertEqual(kw, {'subpath': 'foo/abc', 'foo': 'bar'})
+ self.assertEqual(kw, {'subpath': 'foo/abc', 'foo': 'bar',
+ 'pathspec': 'package:path/abc',
+ 'rawspec': 'package:path/abc'})
request.route_url = route_url
inst.generate('package:path/abc', request)
self.assertTrue(called[0])
@@ -3996,13 +3998,15 @@ class TestStaticURLInfo(unittest.TestCase):
return 'foo' + '/' + subpath, kw
inst = self._makeOne()
inst.registrations = [(None, here, '__viewname')]
- inst.cache_busters = [(here, cachebust)]
+ inst.cache_busters = [(here, cachebust, False)]
request = self._makeRequest()
called = [False]
def route_url(n, **kw):
called[0] = True
self.assertEqual(n, '__viewname')
- self.assertEqual(kw, {'subpath': 'foo/abc', 'foo': 'bar'})
+ self.assertEqual(kw, {'subpath': 'foo/abc', 'foo': 'bar',
+ 'pathspec': here + 'abc',
+ 'rawspec': here + 'abc'})
request.route_url = route_url
inst.generate(here + 'abc', request)
self.assertTrue(called[0])
@@ -4011,13 +4015,15 @@ class TestStaticURLInfo(unittest.TestCase):
def fake_cb(*a, **kw): raise AssertionError
inst = self._makeOne()
inst.registrations = [(None, 'package:path/', '__viewname')]
- inst.cache_busters = [('package:path2/', fake_cb)]
+ inst.cache_busters = [('package:path2/', fake_cb, False)]
request = self._makeRequest()
called = [False]
def route_url(n, **kw):
called[0] = True
self.assertEqual(n, '__viewname')
- self.assertEqual(kw, {'subpath': 'abc'})
+ self.assertEqual(kw, {'subpath': 'abc',
+ 'pathspec': 'package:path/abc',
+ 'rawspec': 'package:path/abc'})
request.route_url = route_url
inst.generate('package:path/abc', request)
self.assertTrue(called[0])
@@ -4025,17 +4031,22 @@ class TestStaticURLInfo(unittest.TestCase):
def test_generate_url_cachebust_with_overrides(self):
config = testing.setUp()
try:
+ request = testing.DummyRequest()
config.add_static_view('static', 'path')
config.override_asset(
'pyramid.tests.test_config:path/',
'pyramid.tests.test_config:other_path/')
- def cb(pathspec, subpath, kw):
- kw['_query'] = {'x': 'foo'}
- return subpath, kw
- config.add_cache_buster('other_path', cb)
- request = testing.DummyRequest()
+ def cb(val):
+ def cb_(request, subpath, kw):
+ kw['_query'] = {'x': val}
+ return subpath, kw
+ return cb_
+ config.add_cache_buster('path', cb('foo'))
result = request.static_url('path/foo.png')
self.assertEqual(result, 'http://example.com/static/foo.png?x=foo')
+ config.add_cache_buster('other_path', cb('bar'), explicit=True)
+ result = request.static_url('path/foo.png')
+ self.assertEqual(result, 'http://example.com/static/foo.png?x=bar')
finally:
testing.tearDown()
@@ -4138,7 +4149,7 @@ class TestStaticURLInfo(unittest.TestCase):
inst = self._makeOne()
inst.add_cache_buster(config, 'mypackage:path', DummyCacheBuster('foo'))
cachebust = inst.cache_busters[-1][1]
- subpath, kw = cachebust('mypackage:some/path', 'some/path', {})
+ subpath, kw = cachebust(None, 'some/path', {})
self.assertEqual(subpath, 'some/path')
self.assertEqual(kw['x'], 'foo')
@@ -4148,7 +4159,7 @@ class TestStaticURLInfo(unittest.TestCase):
inst = self._makeOne()
cb = DummyCacheBuster('foo')
inst.add_cache_buster(config, here, cb)
- self.assertEqual(inst.cache_busters, [(here + '/', cb)])
+ self.assertEqual(inst.cache_busters, [(here + '/', cb, False)])
def test_add_cachebuster_overwrite(self):
config = DummyConfig()
@@ -4158,17 +4169,39 @@ class TestStaticURLInfo(unittest.TestCase):
inst.add_cache_buster(config, 'mypackage:path/', cb1)
inst.add_cache_buster(config, 'mypackage:path', cb2)
self.assertEqual(inst.cache_busters,
- [('mypackage:path/', cb2)])
+ [('mypackage:path/', cb2, False)])
+
+ def test_add_cachebuster_overwrite_explicit(self):
+ config = DummyConfig()
+ inst = self._makeOne()
+ cb1 = DummyCacheBuster('foo')
+ cb2 = DummyCacheBuster('bar')
+ inst.add_cache_buster(config, 'mypackage:path/', cb1)
+ inst.add_cache_buster(config, 'mypackage:path', cb2, True)
+ self.assertEqual(inst.cache_busters,
+ [('mypackage:path/', cb1, False),
+ ('mypackage:path/', cb2, True)])
def test_add_cachebuster_for_more_specific_path(self):
config = DummyConfig()
inst = self._makeOne()
cb1 = DummyCacheBuster('foo')
cb2 = DummyCacheBuster('bar')
+ cb3 = DummyCacheBuster('baz')
+ cb4 = DummyCacheBuster('xyz')
+ cb5 = DummyCacheBuster('w')
inst.add_cache_buster(config, 'mypackage:path', cb1)
- inst.add_cache_buster(config, 'mypackage:path/sub', cb2)
- self.assertEqual(inst.cache_busters,
- [('mypackage:path/', cb1), ('mypackage:path/sub/', cb2)])
+ inst.add_cache_buster(config, 'mypackage:path/sub', cb2, True)
+ inst.add_cache_buster(config, 'mypackage:path/sub/other', cb3)
+ inst.add_cache_buster(config, 'mypackage:path/sub/other', cb4, True)
+ inst.add_cache_buster(config, 'mypackage:path/sub/less', cb5, True)
+ self.assertEqual(
+ inst.cache_busters,
+ [('mypackage:path/', cb1, False),
+ ('mypackage:path/sub/other/', cb3, False),
+ ('mypackage:path/sub/', cb2, True),
+ ('mypackage:path/sub/less/', cb5, True),
+ ('mypackage:path/sub/other/', cb4, True)])
class Test_view_description(unittest.TestCase):
def _callFUT(self, view):
@@ -4293,7 +4326,7 @@ class DummyCacheBuster(object):
def __init__(self, token):
self.token = token
- def __call__(self, pathspec, subpath, kw):
+ def __call__(self, request, subpath, kw):
kw['x'] = self.token
return subpath, kw
diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py
index 73f242add..2ca86bc44 100644
--- a/pyramid/tests/test_static.py
+++ b/pyramid/tests/test_static.py
@@ -383,7 +383,7 @@ class TestQueryStringConstantCacheBuster(unittest.TestCase):
def test_token(self):
fut = self._makeOne().tokenize
- self.assertEqual(fut('whatever'), 'foo')
+ self.assertEqual(fut(None, 'whatever', None), 'foo')
def test_it(self):
fut = self._makeOne()