diff options
| author | Chris Rossi <chris@archimedeanco.com> | 2014-07-15 14:19:07 -0400 |
|---|---|---|
| committer | Chris Rossi <chris@archimedeanco.com> | 2014-07-15 14:19:07 -0400 |
| commit | 2a1ca8c542e752bdd1de2bfdac0f3365a209c072 (patch) | |
| tree | 5019e33981231c5004867af73de8afe2bd92897d | |
| parent | de2996ddcc7c2ac5c3e59101df0fed1ab832701b (diff) | |
| download | pyramid-2a1ca8c542e752bdd1de2bfdac0f3365a209c072.tar.gz pyramid-2a1ca8c542e752bdd1de2bfdac0f3365a209c072.tar.bz2 pyramid-2a1ca8c542e752bdd1de2bfdac0f3365a209c072.zip | |
I kind of like Raydeo's last idea.
| -rw-r--r-- | pyramid/config/views.py | 62 | ||||
| -rw-r--r-- | pyramid/interfaces.py | 29 | ||||
| -rw-r--r-- | pyramid/static.py | 56 |
3 files changed, 85 insertions, 62 deletions
diff --git a/pyramid/config/views.py b/pyramid/config/views.py index b583b59a0..4b7bdaa81 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1,8 +1,6 @@ -import hashlib import inspect import operator import os -import pkg_resources import warnings from zope.interface import ( @@ -36,7 +34,10 @@ from pyramid.interfaces import ( ) from pyramid import renderers -from pyramid.asset import resolve_asset_spec +from pyramid.static import ( + Md5AssetTokenGenerator, + PathSegmentCacheBuster, +) from pyramid.compat import ( string_types, @@ -1950,19 +1951,14 @@ class StaticURLInfo(object): # make sure it ends with a slash name = name + '/' - cb = extra.pop('cachebust', None) + cb = extra.pop('cachebuster', None) if cb is True: - cb_token, cb_pregen, cb_match = DefaultCacheBuster() - elif cb: - cb_token, cb_pregen, cb_match = cb - else: - cb_token = cb_pregen = cb_match = None - - if cb_token and cb_pregen: + cb = DefaultCacheBuster() + if cb: def cachebuster(subpath, kw): - token = cb_token(spec + subpath) + token = cb.token(spec + subpath) subpath_tuple = tuple(subpath.split('/')) - subpath_tuple, kw = cb_pregen(token, subpath_tuple, kw) + subpath_tuple, kw = cb.pregenerate(token, subpath_tuple, kw) return '/'.join(subpath_tuple), kw else: cachebuster = None @@ -1980,6 +1976,7 @@ class StaticURLInfo(object): cache_max_age = extra.pop('cache_max_age', default) # 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) @@ -2033,41 +2030,6 @@ class StaticURLInfo(object): config.action(None, callable=register, introspectables=(intr,)) - -def _generate_md5(spec): - package, filename = resolve_asset_spec(spec) - md5 = hashlib.md5() - with pkg_resources.resource_stream(package, filename) as stream: - for block in iter(lambda: stream.read(4096), ''): - md5.update(block) - return md5.hexdigest() - - def DefaultCacheBuster(): - token_cache = {} - - def generate_token(pathspec): - # An astute observer will notice that this use of token_cache doesn't - # look particular thread safe. Basic read/write operations on Python - # dicts, however, are atomic, so simply accessing and writing values - # to the dict shouldn't cause a segfault or other catastrophic failure. - # (See: http://effbot.org/pyfaq/what-kinds-of-global-value-mutation-are-thread-safe.htm) - # - # We do have a race condition that could result in the same md5 - # checksum getting computed twice or more times in parallel. Since - # the program would still function just fine if this were to occur, - # the extra overhead of using locks to serialize access to the dict - # seems an unnecessary burden. - # - token = token_cache.get(pathspec) - if not token: - token_cache[pathspec] = token = _generate_md5(pathspec) - return token - - def pregenerate_url(token, subpath, kw): - return (token,) + subpath, kw - - def match_url(subpath): - return subpath[1:] - - return (generate_token, pregenerate_url, match_url) + return PathSegmentCacheBuster(Md5AssetTokenGenerator()) + diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 95aa1d60e..822d1624c 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -1164,8 +1164,12 @@ class IJSONAdapter(Interface): class IPredicateList(Interface): """ Interface representing a predicate list """ -class ICachebustTokenGenerator(Interface): - def __call__(pathspec): +class ICacheBuster(Interface): + """ + A container for functions which implement a cache busting policy for + serving static assets. + """ + def token(pathspec): """ A function which computes and returns a token string used for cache busting. ``pathspec`` is the path specification for the resource to be @@ -1188,13 +1192,12 @@ class ICachebustTokenGenerator(Interface): return cachebust_token """ -class ICachebustURLPregenerator(Interface): - def __call__(token, subpath, kw): + def pregenerate(token, subpath, kw): """ A function which modifies a subpath and/or keyword arguments from which a static asset URL will be computed during URL generation. The ``token`` argument is a token string computed by an instance of - :class:`~pyramid.interfaces.ICachebustTokenGenerator` for a particular + :method:`~pyramid.interfaces.ICacheBuster.token` for a particular asset. 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 @@ -1213,20 +1216,22 @@ class ICachebustURLPregenerator(Interface): return subpath, kw """ -class ICachebustURLMatcher(Interface): - def __call__(subpath): + def match(subpath): """ A function which performs the logical inverse of an - :class:`~pyramid.interfaces.ICacheBustURLPregenerator`, by taking a + :method:`~pyramid.interfaces.ICacheBuster.pregenerate`, by taking a subpath from a cache busted URL and removing the cachebust token, so - that :app:`Pyramid` can find the underlying asset. 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 function which - implements this interface may not be necessary. + 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 function + which implements this interface may not be necessary. It is + permissible for an instance of + :class:`~pyramid.interfaces.ICacheBuster` to omit this function. """ # configuration phases: a lower phase number means the actions associated diff --git a/pyramid/static.py b/pyramid/static.py index 87bbcd34c..92251721e 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +import hashlib import os +import pkg_resources from os.path import ( normcase, @@ -155,3 +157,57 @@ def _secure_path(path_tuple): return None encoded = slash.join(path_tuple) # will be unicode return encoded + +def _generate_md5(spec): + package, filename = resolve_asset_spec(spec) + md5 = hashlib.md5() + with pkg_resources.resource_stream(package, filename) as stream: + for block in iter(lambda: stream.read(4096), ''): + md5.update(block) + return md5.hexdigest() + +def Md5AssetTokenGenerator(): + token_cache = {} + + def generate_token(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 + # to the dict shouldn't cause a segfault or other catastrophic failure. + # (See: http://effbot.org/pyfaq/what-kinds-of-global-value-mutation-are-thread-safe.htm) + # + # We do have a race condition that could result in the same md5 + # checksum getting computed twice or more times in parallel. Since + # the program would still function just fine if this were to occur, + # the extra overhead of using locks to serialize access to the dict + # seems an unnecessary burden. + # + token = token_cache.get(pathspec) + if not token: + token_cache[pathspec] = token = _generate_md5(pathspec) + return token + + return generate_token + +class PathSegmentCacheBuster(object): + + 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): + + def __init__(self, token, param='x'): + self.param = param + self.token = token + + def pregenerate(self, token, subpath, kw): + kw.setdefault('_query', {})[self.param] = token + return subpath, kw + + |
