summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Rossi <chris@archimedeanco.com>2014-07-15 14:19:07 -0400
committerChris Rossi <chris@archimedeanco.com>2014-07-15 14:19:07 -0400
commit2a1ca8c542e752bdd1de2bfdac0f3365a209c072 (patch)
tree5019e33981231c5004867af73de8afe2bd92897d
parentde2996ddcc7c2ac5c3e59101df0fed1ab832701b (diff)
downloadpyramid-2a1ca8c542e752bdd1de2bfdac0f3365a209c072.tar.gz
pyramid-2a1ca8c542e752bdd1de2bfdac0f3365a209c072.tar.bz2
pyramid-2a1ca8c542e752bdd1de2bfdac0f3365a209c072.zip
I kind of like Raydeo's last idea.
-rw-r--r--pyramid/config/views.py62
-rw-r--r--pyramid/interfaces.py29
-rw-r--r--pyramid/static.py56
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
+
+