summaryrefslogtreecommitdiff
path: root/docs/narr/assets.rst
diff options
context:
space:
mode:
Diffstat (limited to 'docs/narr/assets.rst')
-rw-r--r--docs/narr/assets.rst244
1 files changed, 235 insertions, 9 deletions
diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst
index b0a8d18b0..d6bc8cbb8 100644
--- a/docs/narr/assets.rst
+++ b/docs/narr/assets.rst
@@ -276,15 +276,238 @@ to put static media on a separate webserver during production (if the
``name`` argument to :meth:`~pyramid.config.Configurator.add_static_view` is
a URL), while keeping static media package-internal and served by the
development webserver during development (if the ``name`` argument to
-:meth:`~pyramid.config.Configurator.add_static_view` is a URL prefix). To
-create such a circumstance, we suggest using the
-:attr:`pyramid.registry.Registry.settings` API in conjunction with a setting
-in the application ``.ini`` file named ``media_location``. Then set the
-value of ``media_location`` to either a prefix or a URL depending on whether
-the application is being run in development or in production (use a different
-``.ini`` file for production than you do for development). This is just a
-suggestion for a pattern; any setting name other than ``media_location``
-could be used.
+:meth:`~pyramid.config.Configurator.add_static_view` is a URL prefix).
+
+For example, we may define a :ref:`custom setting <adding_a_custom_setting>`
+named ``media_location`` which we can set to an external URL in production
+when our assets are hosted on a CDN.
+
+.. code-block:: python
+ :linenos:
+
+ media_location = settings.get('media_location', 'static')
+
+ config = Configurator(settings=settings)
+ config.add_static_view(path='myapp:static', name=media_location)
+
+Now we can optionally define the setting in our ini file:
+
+.. code-block:: ini
+ :linenos:
+
+ # production.ini
+ [app:main]
+ use = egg:myapp#main
+
+ media_location = http://static.example.com/
+
+It is also possible to serve assets that live outside of the source by
+referring to an absolute path on the filesystem. There are two ways to
+accomplish this.
+
+First, :meth:`~pyramid.config.Configurator.add_static_view`
+supports taking an absolute path directly instead of an asset spec. This works
+as expected, looking in the file or folder of files and serving them up at
+some URL within your application or externally. Unfortunately, this technique
+has a drawback that it is not possible to use the
+:meth:`~pyramid.request.Request.static_url` method to generate URLs, since it
+works based on an asset spec.
+
+The second approach, available in Pyramid 1.6+, uses the asset overriding
+APIs described in the :ref:`overriding_assets_section` section. It is then
+possible to configure a "dummy" package which then serves its file or folder
+from an absolute path.
+
+.. code-block:: python
+
+ config.add_static_view(path='myapp:static_images', name='static')
+ config.override_asset(to_override='myapp:static_images/',
+ override_with='/abs/path/to/images/')
+
+From this configuration it is now possible to use
+:meth:`~pyramid.request.Request.static_url` to generate URLs to the data
+in the folder by doing something like
+``request.static_url('myapp:static_images/foo.png')``. While it is not
+necessary that the ``static_images`` file or folder actually exist in the
+``myapp`` package, it is important that the ``myapp`` portion points to a
+valid package. If the folder does exist then the overriden folder is given
+priority if the file's name exists in both locations.
+
+.. index::
+ single: Cache Busting
+
+.. _cache_busting:
+
+Cache Busting
+-------------
+
+.. versionadded:: 1.6
+
+In order to maximize performance of a web application, you generally want to
+limit the number of times a particular client requests the same static asset.
+Ideally a client would cache a particular static asset "forever", requiring
+it to be sent to the client a single time. The HTTP protocol allows you to
+send headers with an HTTP response that can instruct a client to cache a
+particular asset for an amount of time. As long as the client has a copy of
+the asset in its cache and that cache hasn't expired, the client will use the
+cached copy rather than request a new copy from the server. The drawback to
+sending cache headers to the client for a static asset is that at some point
+the static asset may change, and then you'll want the client to load a new copy
+of the asset. Under normal circumstances you'd just need to wait for the
+client's cached copy to expire before they get the new version of the static
+resource.
+
+A commonly used workaround to this problem is a technique known as "cache
+busting". Cache busting schemes generally involve generating a URL for a
+static asset that changes when the static asset changes. This way headers can
+be sent along with the static asset instructing the client to cache the asset
+for a very long time. When a static asset is changed, the URL used to refer to
+it in a web page also changes, so the client sees it as a new resource and
+requests a copy, regardless of any caching policy set for the resource's old
+URL.
+
+:app:`Pyramid` can be configured to produce cache busting URLs for static
+assets by passing the optional argument, ``cachebust`` to
+:meth:`~pyramid.config.Configurator.add_static_view`:
+
+.. code-block:: python
+ :linenos:
+
+ # config is an instance of pyramid.config.Configurator
+ config.add_static_view(name='static', path='mypackage:folder/static',
+ cachebust=True)
+
+Setting the ``cachebust`` argument instructs :app:`Pyramid` to use a cache
+busting scheme which adds the md5 checksum for a static asset as a path segment
+in the asset's URL:
+
+.. code-block:: python
+ :linenos:
+
+ js_url = request.static_url('mypackage:folder/static/js/myapp.js')
+ # Returns: 'http://www.example.com/static/c9658b3c0a314a1ca21e5988e662a09e/js/myapp.js`
+
+When the asset changes, so will its md5 checksum, and therefore so will its
+URL. Supplying the ``cachebust`` argument also causes the static view to set
+headers instructing clients to cache the asset for ten years, unless the
+``max_cache_age`` argument is also passed, in which case that value is used.
+
+.. note::
+
+ md5 checksums are cached in RAM so if you change a static resource without
+ restarting your application, you may still generate URLs with a stale md5
+ checksum.
+
+Disabling the Cache Buster
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It can be useful in some situations (e.g. development) to globally disable all
+configured cache busters without changing calls to
+:meth:`~pyramid.config.Configurator.add_static_view`. To do this set the
+``PYRAMID_PREVENT_CACHEBUST`` environment variable or the
+``pyramid.prevent_cachebust`` configuration value to a true value.
+
+Customizing the Cache Buster
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Revisiting from the previous section:
+
+.. code-block:: python
+ :linenos:
+
+ # config is an instance of pyramid.config.Configurator
+ config.add_static_view(name='static', path='mypackage:folder/static',
+ cachebust=True)
+
+Setting ``cachebust`` to ``True`` instructs :app:`Pyramid` to use a default
+cache busting implementation that should work for many situations. The
+``cachebust`` may be set to any object that implements the interface,
+:class:`~pyramid.interfaces.ICacheBuster`. The above configuration is exactly
+equivalent to:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.static import PathSegmentMd5CacheBuster
+
+ # config is an instance of pyramid.config.Configurator
+ config.add_static_view(name='static', path='mypackage:folder/static',
+ cachebust=PathSegmentMd5CacheBuster())
+
+:app:`Pyramid` includes a handful of ready to use cache buster implementations:
+:class:`~pyramid.static.PathSegmentMd5CacheBuster`, which inserts an md5
+checksum token in the path portion of the asset's URL,
+:class:`~pyramid.static.QueryStringMd5CacheBuster`, which adds an md5 checksum
+token to the query string of the asset's URL, and
+:class:`~pyramid.static.QueryStringConstantCacheBuster`, which adds an
+arbitrary token you provide to the query string of the asset's URL.
+
+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 either
+:class:`~pyramid.static.PathSegmentCacheBuster` or
+:class:`~pyramid.static.QueryStringCacheBuster` and define a
+``tokenize(pathspec)`` method. Here is an example which just uses Git to get
+the hash of the currently checked out code:
+
+.. code-block:: python
+ :linenos:
+
+ import os
+ import subprocess
+ from pyramid.static import PathSegmentCacheBuster
+
+ class GitCacheBuster(PathSegmentCacheBuster):
+ """
+ Assuming your code is installed as a Git checkout, as opposed to as an
+ egg from an egg repository like PYPI, you can use this cachebuster to
+ get the current commit's SHA1 to use as the cache bust token.
+ """
+ def __init__(self):
+ here = os.path.dirname(os.path.abspath(__file__))
+ self.sha1 = subprocess.check_output(
+ ['git', 'rev-parse', 'HEAD'],
+ cwd=here).strip()
+
+ def tokenize(self, pathspec):
+ return self.sha1
+
+Choosing a Cache Buster
+~~~~~~~~~~~~~~~~~~~~~~~
+
+The default cache buster implementation,
+:class:`~pyramid.static.PathSegmentMd5CacheBuster`, works very well assuming
+that you're using :app:`Pyramid` to serve your static assets. The md5 checksum
+is fine grained enough that browsers should only request new versions of
+specific assets that have changed. Many caching HTTP proxies will fail to
+cache a resource if the URL contains a query string. In general, therefore,
+you should prefer a cache busting strategy which modifies the path segment to
+a strategy which adds a query string.
+
+It is possible, however, that your static assets are being served by another
+web server or externally on a CDN. In these cases modifying the path segment
+for a static asset URL would cause the external service to fail to find the
+asset, causing your customer to get a 404. In these cases you would need to
+fall back to a cache buster which adds a query string. It is even possible
+that there isn't a copy of your static assets available to the :app:`Pyramid`
+application, so a cache busting implementation that generates md5 checksums
+would fail since it can't access the assets. In such a case,
+:class:`~pyramid.static.QueryStringConstantCacheBuster` is a reasonable
+fallback. The following code would set up a cachebuster that just uses the
+time at start up as a cachebust token:
+
+.. code-block:: python
+ :linenos:
+
+ import time
+ from pyramid.static import QueryStringConstantCacheBuster
+
+ config.add_static_view(
+ name='http://mycdn.example.com/',
+ path='mypackage:static',
+ cachebust=QueryStringConstantCacheBuster(str(time.time())))
.. index::
single: static assets view
@@ -526,3 +749,6 @@ files. Any software which uses the
:func:`pkg_resources.get_resource_string` APIs will obtain an overridden file
when an override is used.
+As of Pyramid 1.6, it is also possible to override an asset by supplying an
+absolute path to a file or directory. This may be useful if the assets are
+not distributed as part of a Python package.