summaryrefslogtreecommitdiff
path: root/repoze/bfg/static.py
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2010-07-02 01:47:47 +0000
committerChris McDonough <chrism@agendaless.com>2010-07-02 01:47:47 +0000
commitb29429e470c093573f3735b0dbf09d42c29cfe4d (patch)
tree8dfa266de6bd0f10a80d9654fe3ddf3a7897c467 /repoze/bfg/static.py
parent78a659d76e5bbb7544212174f010c1f50f8bcbe6 (diff)
downloadpyramid-b29429e470c093573f3735b0dbf09d42c29cfe4d.tar.gz
pyramid-b29429e470c093573f3735b0dbf09d42c29cfe4d.tar.bz2
pyramid-b29429e470c093573f3735b0dbf09d42c29cfe4d.zip
- The ``repoze.bfg.url.route_url`` API has changed. If a keyword
``_app_url`` is present in the arguments passed to ``route_url``, this value will be used as the protocol/hostname/port/leading path prefix of the generated URL. For example, using an ``_app_url`` of ``http://example.com:8080/foo`` would cause the URL ``http://example.com:8080/foo/fleeb/flub`` to be returned from this function if the expansion of the route pattern associated with the ``route_name`` expanded to ``/fleeb/flub``. - It is now possible to use a URL as the ``name`` argument fed to ``repoze.bfg.configuration.Configurator.add_static_view``. When the name argument is a URL, the ``repoze.bfg.url.static_url`` API will generate join this URL (as a prefix) to a path including the static file name. This makes it more possible to put static media on a separate webserver for production, while keeping static media package-internal and served by the development webserver during development.
Diffstat (limited to 'repoze/bfg/static.py')
-rw-r--r--repoze/bfg/static.py121
1 files changed, 116 insertions, 5 deletions
diff --git a/repoze/bfg/static.py b/repoze/bfg/static.py
index 6261a1fb3..f2dfa7897 100644
--- a/repoze/bfg/static.py
+++ b/repoze/bfg/static.py
@@ -1,11 +1,19 @@
import os
import pkg_resources
+from urlparse import urljoin
from paste import httpexceptions
from paste import request
from paste.httpheaders import ETAG
from paste.urlparser import StaticURLParser
+from zope.interface import implements
+
+from repoze.bfg.interfaces import IStaticURLInfo
+from repoze.bfg.path import caller_package
+from repoze.bfg.resource import resolve_resource_spec
+from repoze.bfg.url import route_url
+
class PackageURLParser(StaticURLParser):
""" This probably won't work with zipimported resources """
def __init__(self, package_name, resource_name, root_resource=None,
@@ -76,10 +84,113 @@ class PackageURLParser(StaticURLParser):
return '<%s %s:%s at %s>' % (self.__class__.__name__, self.package_name,
self.root_resource, id(self))
-class StaticRootFactory:
- def __init__(self, spec):
- self.spec = spec
+class StaticURLInfo(object):
+ implements(IStaticURLInfo)
+
+ route_url = staticmethod(route_url) # for testing only
+
+ def __init__(self, config):
+ self.config = config
+ self.registrations = []
+
+ def generate(self, path, request, **kw):
+ for (name, spec) in self.registrations:
+ if path.startswith(spec):
+ subpath = path[len(spec):]
+ if '/' in name:
+ return urljoin(name, subpath[1:])
+ else:
+ kw['subpath'] = subpath
+ return self.route_url(name, request, **kw)
+
+ raise ValueError('No static URL definition matching %s' % path)
+
+ def add(self, name, spec, **extra):
+ names = [ t[0] for t in self.registrations ]
+ if name in names:
+ idx = names.index(name)
+ self.registrations.pop(idx)
+
+ if '/' in name:
+ # it's a URL
+ if not name.endswith('/'):
+ # make sure it ends with a slash
+ name = name + '/'
+ else:
+ # it's a view name
+ _info = extra.pop('_info', None)
+ cache_max_age = extra.pop('cache_max_age', None)
+ view = static_view(spec, cache_max_age=cache_max_age)
+ # register a route using this view
+ self.config.add_route(
+ name,
+ "%s*subpath" % name,
+ view=view,
+ view_for=self.__class__,
+ factory=lambda *x: self,
+ _info=_info
+ )
+
+ self.registrations.append((name, spec))
+
+class static_view(object):
+ """ An instance of this class is a callable which can act as a
+ :mod:`repoze.bfg` :term:`view callable`; this view will serve
+ static files from a directory on disk based on the ``root_dir``
+ you provide to its constructor.
+
+ The directory may contain subdirectories (recursively); the static
+ view implementation will descend into these directories as
+ necessary based on the components of the URL in order to resolve a
+ path into a response.
+
+ You may pass an absolute or relative filesystem path or a
+ :term:`resource specification` representing the directory
+ containing static files as the ``root_dir`` argument to this
+ class' constructor.
+
+ If the ``root_dir`` path is relative, and the ``package_name``
+ argument is ``None``, ``root_dir`` will be considered relative to
+ the directory in which the Python file which *calls* ``static``
+ resides. If the ``package_name`` name argument is provided, and a
+ relative ``root_dir`` is provided, the ``root_dir`` will be
+ considered relative to the Python :term:`package` specified by
+ ``package_name`` (a dotted path to a Python package).
+
+ ``cache_max_age`` influences the ``Expires`` and ``Max-Age``
+ response headers returned by the view (default is 3600 seconds or
+ five minutes).
+
+ .. note:: If the ``root_dir`` is relative to a :term:`package`, or
+ is a :term:`resource specification` the :mod:`repoze.bfg`
+ ``resource`` ZCML directive or
+ :class:`repoze.bfg.configuration.Configurator` method can be
+ used to override resources within the named ``root_dir``
+ package-relative directory. However, if the ``root_dir`` is
+ absolute, the ``resource`` directive will not be able to
+ override the resources it contains. """
+
+ def __init__(self, root_dir, cache_max_age=3600, package_name=None):
+ # package_name is for bw compat; it is preferred to pass in a
+ # package-relative path as root_dir
+ # (e.g. ``anotherpackage:foo/static``).
+ caller_package_name = caller_package().__name__
+ package_name = package_name or caller_package_name
+ package_name, root_dir = resolve_resource_spec(root_dir, package_name)
+ if package_name is None:
+ app = StaticURLParser(root_dir, cache_max_age=cache_max_age)
+ else:
+ app = PackageURLParser(
+ package_name, root_dir, cache_max_age=cache_max_age)
+ self.app = app
- def __call__(self, environ):
- return self
+ def __call__(self, context, request):
+ subpath = '/'.join(request.subpath)
+ request_copy = request.copy()
+ # Fix up PATH_INFO to get rid of everything but the "subpath"
+ # (the actual path to the file relative to the root dir).
+ request_copy.environ['PATH_INFO'] = '/' + subpath
+ # Zero out SCRIPT_NAME for good measure.
+ request_copy.environ['SCRIPT_NAME'] = ''
+ return request_copy.get_response(self.app)