diff options
| -rw-r--r-- | docs/api/static.rst | 2 | ||||
| -rw-r--r-- | docs/narr/assets.rst | 41 | ||||
| -rw-r--r-- | pyramid/config/views.py | 8 | ||||
| -rw-r--r-- | pyramid/static.py | 66 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_views.py | 20 | ||||
| -rw-r--r-- | pyramid/tests/test_static.py | 46 |
6 files changed, 87 insertions, 96 deletions
diff --git a/docs/api/static.rst b/docs/api/static.rst index 8ea2fff75..de5bcabda 100644 --- a/docs/api/static.rst +++ b/docs/api/static.rst @@ -14,5 +14,3 @@ .. autoclass:: QueryStringCacheBuster :members: - - .. autofunction:: Md5AssetTokenGenerator diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index 642211f5b..7987d03a6 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -370,28 +370,18 @@ equivalent to: .. code-block:: python :linenos: - from pyramid.static import ( - Md5AssetTokenGenerator, - PathSegmentCacheBuster) + from pyramid.static import PathSegmentCacheBuster # config is an instance of pyramid.config.Configurator - cachebuster = PathSegmentCacheBuster(Md5AssetTokenGenerator()) config.add_static_view(name='static', path='mypackage:folder/static', - cachebuster=cachebuster) + cachebuster=PathSegmentCacheBuster()) :app:`Pyramid` includes two ready to use cache buster implementations: :class:`~pyramid.static.PathSegmentCacheBuster`, which inserts an asset token in the path portion of the asset's URL, and :class:`~pyramid.static.QueryStringCacheBuster`, which adds an asset token to -the query string of the asset's URL. Both of these classes have constructors -which accept a token generator function as an argument, allowing for the way a -token is generated to be decoupled from the way it is inserted into a URL. -:app:`Pyramid` provides a single asset token generator, -:meth:`~pyramid.static.Md5AssetTokenGenerator`. - -In order to implement your own cache buster, see the -:class:`~pyramid.interfaces.ICacheBuster` interface and the existing -implementations in the :mod:`~pyramid.static` module. +the query string of the asset's URL. Both of these classes generate md5 +checksums as asset tokens. .. note:: @@ -400,6 +390,29 @@ implementations in the :mod:`~pyramid.static` module. :class:`~pyramid.static.PathSegementCacheBuster` to :class:`~pyramid.static.QueryStringCacheBuster`. +In order to implement your own cache buster, you can write your own class from +scratch which implements the :class:`~pyramid.interfaces.ICacheBuster` +interface. Alternatively you may choose to subclass one of the existing +implementations. One of the most likely scenarios is you'd want to change the +way the asset token is generated. To do this just subclass an existing +implementation and replace the :meth:`~pyramid.interfaces.ICacheBuster.token` +method. Here is an example which just uses a global setting for the asset +token: + +.. code-block:: python + :linenos: + + from pyramid.static import PathSegmentCacheBuster + + class MyCacheBuster(PathSegmentCacheBuster): + + def __init__(self, config): + # config is an instance of pyramid.config.Configurator + self._token = config.registry.settings['myapp.cachebust_token'] + + def token(self, pathspec): + return self._token + .. index:: single: static assets view diff --git a/pyramid/config/views.py b/pyramid/config/views.py index d74ecfadb..f186a44ae 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1897,11 +1897,7 @@ def isexception(o): @implementer(IStaticURLInfo) class StaticURLInfo(object): # Indirection for testing - _default_cachebuster = staticmethod(PathSegmentCacheBuster) - _default_asset_token_generator = staticmethod(Md5AssetTokenGenerator) - - def _make_default_cachebuster(self): - return self._default_cachebuster(self._default_asset_token_generator()) + _default_cachebuster = PathSegmentCacheBuster def _get_registrations(self, registry): try: @@ -1964,7 +1960,7 @@ class StaticURLInfo(object): cb = extra.pop('cachebuster', None) if cb is True: - cb = self._make_default_cachebuster() + cb = self._default_cachebuster() if cb: def cachebuster(subpath, kw): token = cb.token(spec + subpath) diff --git a/pyramid/static.py b/pyramid/static.py index ab9d47aa5..34fc3f55c 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -165,16 +165,16 @@ def _generate_md5(spec): md5.update(block) return md5.hexdigest() -def Md5AssetTokenGenerator(): +class Md5AssetTokenGenerator(object): """ - A factory method which returns a function that implements - :meth:`~pyramid.interfaces.ICacheBuster.token`. The function computes and - returns md5 checksums for static assets, caching them in memory for speedy - retrieval on subsequent calls. + A mixin class which provides an implementation of + :meth:`~pyramid.interfaces.ICacheBuster.target` which generates an md5 + checksum token for an asset, caching it for subsequent calls. """ - token_cache = {} + def __init__(self): + self.token_cache = {} - def generate_token(pathspec): + def token(self, pathspec): # An astute observer will notice that this use of token_cache doesn't # look particularly thread safe. Basic read/write operations on Python # dicts, however, are atomic, so simply accessing and writing values @@ -187,64 +187,36 @@ def Md5AssetTokenGenerator(): # the extra overhead of using locks to serialize access to the dict # seems an unnecessary burden. # - token = token_cache.get(pathspec) + token = self.token_cache.get(pathspec) if not token: - token_cache[pathspec] = token = _generate_md5(pathspec) + self.token_cache[pathspec] = token = _generate_md5(pathspec) return token - return generate_token - -class PathSegmentCacheBuster(object): +class PathSegmentCacheBuster(Md5AssetTokenGenerator): """ An implementation of :class:`~pyramid.interfaces.ICacheBuster` which - inserts a token for cache busting in the path portion of an asset URL. - - The ``token`` argument should be an implementation of - :meth:`~pyramid.interfaces.ICacheBuster.token`. For example, to use - this cache buster with an md5 token generator: - - .. code-block:: python - :linenos: - - from pyramid.static import ( - Md5AssetTokenGenerator, - PathSegmentCacheBuster) - - cachebuster = PathSegmentCacheBuster(Md5AssetTokenGenerator()) + inserts an md5 checksum token for cache busting in the path portion of an + asset URL. Generated md5 checksums are cached in order to speed up + subsequent calls. """ - def __init__(self, token): - self.token = token - def pregenerate(self, token, subpath, kw): return (token,) + subpath, kw def match(self, subpath): return subpath[1:] -class QueryStringCacheBuster(object): +class QueryStringCacheBuster(Md5AssetTokenGenerator): """ - An implementation of :class:`~pyramid.interfaces.ICacheBuster` which - adds a token for cache busting in the query string of an asset URL. - - The ``token`` argument should be an implementation of - :meth:`~pyramid.interfaces.ICacheBuster.token`. For example, to use - this cache buster with an md5 token generator: - - .. code-block:: python - :linenos: - - from pyramid.static import ( - Md5AssetTokenGenerator, - PathSegmentCacheBuster) - - cachebuster = QueryStringCacheBuster(Md5AssetTokenGenerator()) + An implementation of :class:`~pyramid.interfaces.ICacheBuster` which adds a + token for cache busting in the query string of an asset URL. Generated md5 + checksums are cached in order to speed up subsequent calls. The optional ``param`` argument determines the name of the parameter added to the query string and defaults to ``'x'``. """ - def __init__(self, token, param='x'): + def __init__(self, param='x'): + super(QueryStringCacheBuster, self).__init__() self.param = param - self.token = token def pregenerate(self, token, subpath, kw): query = kw.setdefault('_query', {}) diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index 0b81f5a6f..10a2f6f53 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -3944,19 +3944,14 @@ class TestStaticURLInfo(unittest.TestCase): def test_add_cachebust_default(self): config = self._makeConfig() inst = self._makeOne() - inst._default_asset_token_generator = lambda: lambda pathspec: 'foo' + inst._default_cachebuster = DummyCacheBuster inst.add(config, 'view', 'mypackage:path', cachebuster=True) cachebuster = config.registry._static_url_registrations[0][3] - subpath, _ = cachebuster('some/path', None) - self.assertEqual(subpath, 'foo/some/path') + subpath, kw = cachebuster('some/path', {}) + self.assertEqual(subpath, 'some/path') + self.assertEqual(kw['x'], 'foo') def test_add_cachebust_custom(self): - class DummyCacheBuster(object): - def token(self, pathspec): - return 'foo' - def pregenerate(self, token, subpath, kw): - kw['x'] = token - return subpath, kw config = self._makeConfig() inst = self._makeOne() inst.add(config, 'view', 'mypackage:path', @@ -4071,6 +4066,13 @@ class DummyMultiView: def __permitted__(self, context, request): """ """ +class DummyCacheBuster(object): + def token(self, pathspec): + return 'foo' + def pregenerate(self, token, subpath, kw): + kw['x'] = token + return subpath, kw + def parse_httpdate(s): import datetime # cannot use %Z, must use literal GMT; Jython honors timezone diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py index f7b580df2..6ae9b13db 100644 --- a/pyramid/tests/test_static.py +++ b/pyramid/tests/test_static.py @@ -369,29 +369,33 @@ class Test_static_view_use_subpath_True(unittest.TestCase): self.assertRaises(HTTPNotFound, inst, context, request) class TestMd5AssetTokenGenerator(unittest.TestCase): + _fspath = None + + @property + def fspath(self): + if self._fspath: + return self._fspath - def setUp(self): import os import tempfile - self.tmp = tempfile.mkdtemp() - self.fspath = os.path.join(self.tmp, 'test.txt') - - def tearDown(self): import shutil - shutil.rmtree(self.tmp) + tmp = tempfile.mkdtemp() + self.addCleanup(lambda: shutil.rmtree(tmp)) + self._fspath = os.path.join(tmp, 'test.txt') + return self._fspath def _makeOne(self): - from pyramid.static import Md5AssetTokenGenerator as unit - return unit() + from pyramid.static import Md5AssetTokenGenerator as cls + return cls() def test_package_resource(self): - fut = self._makeOne() + fut = self._makeOne().token expected = '76d653a3a044e2f4b38bb001d283e3d9' token = fut('pyramid.tests:fixtures/static/index.html') self.assertEqual(token, expected) def test_filesystem_resource(self): - fut = self._makeOne() + fut = self._makeOne().token expected = 'd5155f250bef0e9923e894dbc713c5dd' with open(self.fspath, 'w') as f: f.write("Are we rich yet?") @@ -399,7 +403,7 @@ class TestMd5AssetTokenGenerator(unittest.TestCase): self.assertEqual(token, expected) def test_cache(self): - fut = self._makeOne() + fut = self._makeOne().token expected = 'd5155f250bef0e9923e894dbc713c5dd' with open(self.fspath, 'w') as f: f.write("Are we rich yet?") @@ -415,8 +419,10 @@ class TestMd5AssetTokenGenerator(unittest.TestCase): class TestPathSegmentCacheBuster(unittest.TestCase): def _makeOne(self): - from pyramid.static import PathSegmentCacheBuster as unit - return unit(lambda pathspec: 'foo') + from pyramid.static import PathSegmentCacheBuster as cls + inst = cls() + inst.token = lambda pathspec: 'foo' + return inst def test_token(self): fut = self._makeOne().token @@ -432,9 +438,14 @@ class TestPathSegmentCacheBuster(unittest.TestCase): class TestQueryStringCacheBuster(unittest.TestCase): - def _makeOne(self): - from pyramid.static import QueryStringCacheBuster as unit - return unit(lambda pathspec: 'foo') + def _makeOne(self, param=None): + from pyramid.static import QueryStringCacheBuster as cls + if param: + inst = cls(param) + else: + inst = cls() + inst.token = lambda pathspec: 'foo' + return inst def test_token(self): fut = self._makeOne().token @@ -447,8 +458,7 @@ class TestQueryStringCacheBuster(unittest.TestCase): (('bar',), {'_query': {'x': 'foo'}})) def test_pregenerate_change_param(self): - from pyramid.static import QueryStringCacheBuster as unit - fut = unit(lambda pathspec: 'foo', 'y').pregenerate + fut = self._makeOne('y').pregenerate self.assertEqual( fut('foo', ('bar',), {}), (('bar',), {'_query': {'y': 'foo'}})) |
