summaryrefslogtreecommitdiff
path: root/repoze
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2009-06-30 21:02:00 +0000
committerChris McDonough <chrism@agendaless.com>2009-06-30 21:02:00 +0000
commitd809ac74d19342bcc84e4fe043697709b2001cc0 (patch)
tree9973b531b90336bf6ecf3811aa3b9ba753f4e6d8 /repoze
parentf9e73ea5a9d5a4210e3a346aa2f9483d70d22ab9 (diff)
downloadpyramid-d809ac74d19342bcc84e4fe043697709b2001cc0.tar.gz
pyramid-d809ac74d19342bcc84e4fe043697709b2001cc0.tar.bz2
pyramid-d809ac74d19342bcc84e4fe043697709b2001cc0.zip
- Add a ``reload_resources`` configuration file setting (aka the
``BFG_RELOAD_RESOURCES`` environment variable). When this is set to true, the server never needs to be restarted when moving files between directory resource overrides (esp. for templates currently). - Add a ``reload_all`` configuration file setting (aka the ``BFG_RELOAD_ALL`` environment variable) that implies both ``reload_resources`` and ``reload_templates``. - The ``static`` helper view class now uses a ``PackageURLParser`` in order to allow for the overriding of static resources (CSS / logo files, etc) using the ``resource`` ZCML directive. The ``PackageURLParser`` class was added to a (new) ``static`` module in BFG; it is a subclass of the ``StaticURLParser`` class in ``paste.urlparser``. - The ``repoze.bfg.templating.renderer_from_cache`` function now checks for the ``reload_resources`` setting; if it's true, it does not register a template renderer (it won't use the registry as a template renderer cache). - Add ``pkg_resources`` to the glossary. - Update the "Environment" docs to note the existence of ``reload_resources`` and ``reload_all``. - Use a colon instead of a tab as the separator between package name and relpath to form the "spec" when register a ITemplateRenderer.
Diffstat (limited to 'repoze')
-rw-r--r--repoze/bfg/settings.py9
-rw-r--r--repoze/bfg/static.py78
-rw-r--r--repoze/bfg/templating.py19
-rw-r--r--repoze/bfg/tests/fixtures/static/index.html1
-rw-r--r--repoze/bfg/tests/fixtures/static/subdir/index.html1
-rw-r--r--repoze/bfg/tests/test_settings.py36
-rw-r--r--repoze/bfg/tests/test_static.py157
-rw-r--r--repoze/bfg/tests/test_templating.py31
-rw-r--r--repoze/bfg/tests/test_view.py26
-rw-r--r--repoze/bfg/view.py51
10 files changed, 384 insertions, 25 deletions
diff --git a/repoze/bfg/settings.py b/repoze/bfg/settings.py
index df2a5e99e..08607f756 100644
--- a/repoze/bfg/settings.py
+++ b/repoze/bfg/settings.py
@@ -40,6 +40,9 @@ def get_options(kw, environ=os.environ):
config_debug_all = kw.get('debug_all', '')
effective_debug_all = asbool(eget('BFG_DEBUG_ALL',
config_debug_all))
+ config_reload_all = kw.get('reload_all', '')
+ effective_reload_all = asbool(eget('BFG_RELOAD_ALL',
+ config_reload_all))
config_debug_auth = kw.get('debug_authorization', '')
effective_debug_auth = asbool(eget('BFG_DEBUG_AUTHORIZATION',
config_debug_auth))
@@ -49,10 +52,14 @@ def get_options(kw, environ=os.environ):
config_reload_templates = kw.get('reload_templates', '')
effective_reload_templates = asbool(eget('BFG_RELOAD_TEMPLATES',
config_reload_templates))
+ config_reload_resources = kw.get('reload_resources', '')
+ effective_reload_resources = asbool(eget('BFG_RELOAD_RESOURCES',
+ config_reload_resources))
update = {
'debug_authorization': effective_debug_all or effective_debug_auth,
'debug_notfound': effective_debug_all or effective_debug_notfound,
- 'reload_templates': effective_reload_templates,
+ 'reload_templates': effective_reload_all or effective_reload_templates,
+ 'reload_resources':effective_reload_all or effective_reload_resources,
}
kw.update(update)
diff --git a/repoze/bfg/static.py b/repoze/bfg/static.py
new file mode 100644
index 000000000..01ce98a30
--- /dev/null
+++ b/repoze/bfg/static.py
@@ -0,0 +1,78 @@
+import os
+import pkg_resources
+
+from paste.httpheaders import ETAG
+from paste.urlparser import StaticURLParser
+from paste import httpexceptions
+from paste import request
+
+class PackageURLParser(StaticURLParser):
+ """ This probably won't work with zipimported resources """
+ def __init__(self, package_name, resource_name, root_resource=None,
+ cache_max_age=None):
+ self.package_name = package_name
+ self.resource_name = os.path.normpath(resource_name)
+ if root_resource is None:
+ root_resource = self.resource_name
+ self.root_resource = root_resource
+ self.cache_max_age = cache_max_age
+
+ def __call__(self, environ, start_response):
+ path_info = environ.get('PATH_INFO', '')
+ if not path_info:
+ return self.add_slash(environ, start_response)
+ if path_info == '/':
+ # @@: This should obviously be configurable
+ filename = 'index.html'
+ else:
+ filename = request.path_info_pop(environ)
+ resource = os.path.normcase(os.path.normpath(
+ self.resource_name + '/' + filename))
+ if ( (self.root_resource is not None) and
+ (not resource.startswith(self.root_resource)) ):
+ # Out of bounds
+ return self.not_found(environ, start_response)
+ if not pkg_resources.resource_exists(self.package_name, resource):
+ return self.not_found(environ, start_response)
+ if pkg_resources.resource_isdir(self.package_name, resource):
+ # @@: Cache?
+ child_root = (self.root_resource is not None and
+ self.root_resource or self.resource_name)
+ return self.__class__(
+ self.package_name, resource, root_resource=child_root,
+ cache_max_age=self.cache_max_age)(environ, start_response)
+ if (environ.get('PATH_INFO')
+ and environ.get('PATH_INFO') != '/'): # pragma: no cover
+ return self.error_extra_path(environ, start_response)
+ full = pkg_resources.resource_filename(self.package_name, resource)
+ if_none_match = environ.get('HTTP_IF_NONE_MATCH')
+ if if_none_match:
+ mytime = os.stat(full).st_mtime
+ if str(mytime) == if_none_match:
+ headers = []
+ ETAG.update(headers, mytime)
+ start_response('304 Not Modified', headers)
+ return [''] # empty body
+
+ fa = self.make_app(full)
+ if self.cache_max_age:
+ fa.cache_control(max_age=self.cache_max_age)
+ return fa(environ, start_response)
+
+ def not_found(self, environ, start_response, debug_message=None):
+ comment=('SCRIPT_NAME=%r; PATH_INFO=%r; looking in package %s; '
+ 'subdir %s ;debug: %s' % (environ.get('SCRIPT_NAME'),
+ environ.get('PATH_INFO'),
+ self.package_name,
+ self.resource_name,
+ debug_message or '(none)'))
+ exc = httpexceptions.HTTPNotFound(
+ 'The resource at %s could not be found'
+ % request.construct_url(environ),
+ comment=comment)
+ return exc.wsgi_application(environ, start_response)
+
+ def __repr__(self):
+ return '<%s %s:%s at %s>' % (self.__class__.__name__, self.package_name,
+ self.root_resource, id(self))
+
diff --git a/repoze/bfg/templating.py b/repoze/bfg/templating.py
index a81726bb9..3287f0808 100644
--- a/repoze/bfg/templating.py
+++ b/repoze/bfg/templating.py
@@ -1,9 +1,12 @@
+import os
import pkg_resources
+
from zope.component import queryUtility
-from repoze.bfg.interfaces import ITemplateRenderer
from zope.component import getSiteManager
+
+from repoze.bfg.interfaces import ITemplateRenderer
from repoze.bfg.path import caller_package
-import os
+from repoze.bfg.settings import get_settings
def renderer_from_cache(path, factory, level=3, **kw):
if os.path.isabs(path):
@@ -21,7 +24,7 @@ def renderer_from_cache(path, factory, level=3, **kw):
# 'path' is a relative filename
package = caller_package(level=level)
spec = (package.__name__, path)
- utility_name = '%s\t%s' % spec # utility name must be a string :-(
+ utility_name = '%s:%s' % spec # utility name must be a string
renderer = queryUtility(ITemplateRenderer, name=utility_name)
if renderer is None:
# service unit tests here by trying the relative path
@@ -29,11 +32,15 @@ def renderer_from_cache(path, factory, level=3, **kw):
renderer = queryUtility(ITemplateRenderer, name=path)
if renderer is None:
if not pkg_resources.resource_exists(*spec):
- raise ValueError('Missing template resource: %s:%s' % spec)
+ raise ValueError('Missing template resource: %s' % utility_name)
abspath = pkg_resources.resource_filename(*spec)
renderer = factory(abspath, **kw)
- sm = getSiteManager()
- sm.registerUtility(renderer, ITemplateRenderer, name=utility_name)
+ settings = get_settings()
+ if (not settings) or (not settings.get('reload_resources')):
+ # cache the template
+ sm = getSiteManager()
+ sm.registerUtility(renderer, ITemplateRenderer,
+ name=utility_name)
return renderer
diff --git a/repoze/bfg/tests/fixtures/static/index.html b/repoze/bfg/tests/fixtures/static/index.html
new file mode 100644
index 000000000..6498787a5
--- /dev/null
+++ b/repoze/bfg/tests/fixtures/static/index.html
@@ -0,0 +1 @@
+<html>static</html>
diff --git a/repoze/bfg/tests/fixtures/static/subdir/index.html b/repoze/bfg/tests/fixtures/static/subdir/index.html
new file mode 100644
index 000000000..bb84fad04
--- /dev/null
+++ b/repoze/bfg/tests/fixtures/static/subdir/index.html
@@ -0,0 +1 @@
+<html>subdir</html>
diff --git a/repoze/bfg/tests/test_settings.py b/repoze/bfg/tests/test_settings.py
index 903a23f5d..8319a302d 100644
--- a/repoze/bfg/tests/test_settings.py
+++ b/repoze/bfg/tests/test_settings.py
@@ -61,6 +61,42 @@ class TestGetOptions(unittest.TestCase):
{'BFG_RELOAD_TEMPLATES':'1'})
self.assertEqual(result['reload_templates'], True)
+ def test_reload_resources(self):
+ result = self._callFUT({})
+ self.assertEqual(result['reload_resources'], False)
+ result = self._callFUT({'reload_resources':'false'})
+ self.assertEqual(result['reload_resources'], False)
+ result = self._callFUT({'reload_resources':'t'})
+ self.assertEqual(result['reload_resources'], True)
+ result = self._callFUT({'reload_resources':'1'})
+ self.assertEqual(result['reload_resources'], True)
+ result = self._callFUT({}, {'BFG_RELOAD_RESOURCES':'1'})
+ self.assertEqual(result['reload_resources'], True)
+ result = self._callFUT({'reload_resources':'false'},
+ {'BFG_RELOAD_RESOURCES':'1'})
+ self.assertEqual(result['reload_resources'], True)
+
+ def test_reload_all(self):
+ result = self._callFUT({})
+ self.assertEqual(result['reload_templates'], False)
+ self.assertEqual(result['reload_resources'], False)
+ result = self._callFUT({'reload_all':'false'})
+ self.assertEqual(result['reload_templates'], False)
+ self.assertEqual(result['reload_resources'], False)
+ result = self._callFUT({'reload_all':'t'})
+ self.assertEqual(result['reload_templates'], True)
+ self.assertEqual(result['reload_resources'], True)
+ result = self._callFUT({'reload_all':'1'})
+ self.assertEqual(result['reload_templates'], True)
+ self.assertEqual(result['reload_resources'], True)
+ result = self._callFUT({}, {'BFG_RELOAD_ALL':'1'})
+ self.assertEqual(result['reload_templates'], True)
+ self.assertEqual(result['reload_resources'], True)
+ result = self._callFUT({'reload_all':'false'},
+ {'BFG_RELOAD_ALL':'1'})
+ self.assertEqual(result['reload_templates'], True)
+ self.assertEqual(result['reload_resources'], True)
+
def test_debug_authorization(self):
result = self._callFUT({})
self.assertEqual(result['debug_authorization'], False)
diff --git a/repoze/bfg/tests/test_static.py b/repoze/bfg/tests/test_static.py
new file mode 100644
index 000000000..7c7b5627c
--- /dev/null
+++ b/repoze/bfg/tests/test_static.py
@@ -0,0 +1,157 @@
+import unittest
+
+class TestPackageURLParser(unittest.TestCase):
+ def _getTargetClass(self):
+ from repoze.bfg.static import PackageURLParser
+ return PackageURLParser
+
+ def _makeOne(self, *arg, **kw):
+ return self._getTargetClass()(*arg, **kw)
+
+
+ def _makeEnviron(self, **kw):
+ environ = {
+ 'wsgi.url_scheme':'http',
+ 'wsgi.version':(1,0),
+ 'SERVER_NAME':'example.com',
+ 'SERVER_PORT':'6543',
+ 'PATH_INFO':'/',
+ 'SCRIPT_NAME':'',
+ 'REQUEST_METHOD':'GET',
+ }
+ environ.update(kw)
+ return environ
+
+ def test_ctor_allargs(self):
+ inst = self._makeOne('package', 'resource/name', root_resource='root',
+ cache_max_age=100)
+ self.assertEqual(inst.package_name, 'package')
+ self.assertEqual(inst.resource_name, 'resource/name')
+ self.assertEqual(inst.root_resource, 'root')
+ self.assertEqual(inst.cache_max_age, 100)
+
+ def test_ctor_defaultargs(self):
+ inst = self._makeOne('package', 'resource/name')
+ self.assertEqual(inst.package_name, 'package')
+ self.assertEqual(inst.resource_name, 'resource/name')
+ self.assertEqual(inst.root_resource, 'resource/name')
+ self.assertEqual(inst.cache_max_age, None)
+
+ def test_call_adds_slash_path_info_empty(self):
+ environ = self._makeEnviron(PATH_INFO='')
+ inst = self._makeOne('repoze.bfg.tests', 'fixtures/static')
+ sr = DummyStartResponse()
+ response = inst(environ, sr)
+ body = response[0]
+ self.failUnless('301 Moved Permanently' in body)
+ self.failUnless('http://example.com:6543/' in body)
+
+ def test_path_info_slash_means_index_html(self):
+ environ = self._makeEnviron()
+ inst = self._makeOne('repoze.bfg.tests', 'fixtures/static')
+ sr = DummyStartResponse()
+ response = inst(environ, sr)
+ body = response[0]
+ self.failUnless('<html>static</html>' in body)
+
+ def test_resource_out_of_bounds(self):
+ environ = self._makeEnviron()
+ inst = self._makeOne('repoze.bfg.tests', 'fixtures/static')
+ inst.root_resource = 'abcdef'
+ sr = DummyStartResponse()
+ response = inst(environ, sr)
+ body = response[0]
+ self.failUnless('404 Not Found' in body)
+ self.failUnless('http://example.com:6543/' in body)
+
+ def test_resource_doesnt_exist(self):
+ environ = self._makeEnviron(PATH_INFO='/notthere')
+ inst = self._makeOne('repoze.bfg.tests', 'fixtures/static')
+ sr = DummyStartResponse()
+ response = inst(environ, sr)
+ body = response[0]
+ self.failUnless('404 Not Found' in body)
+ self.failUnless('http://example.com:6543/' in body)
+
+ def test_resource_isdir(self):
+ environ = self._makeEnviron(PATH_INFO='/subdir/')
+ inst = self._makeOne('repoze.bfg.tests', 'fixtures/static')
+ sr = DummyStartResponse()
+ response = inst(environ, sr)
+ body = response[0]
+ self.failUnless('<html>subdir</html>' in body)
+
+ def test_resource_is_file(self):
+ environ = self._makeEnviron(PATH_INFO='/index.html')
+ inst = self._makeOne('repoze.bfg.tests', 'fixtures/static')
+ sr = DummyStartResponse()
+ response = inst(environ, sr)
+ body = response[0]
+ self.failUnless('<html>static</html>' in body)
+
+ def test_resource_is_file_with_cache_max_age(self):
+ environ = self._makeEnviron(PATH_INFO='/index.html')
+ inst = self._makeOne('repoze.bfg.tests', 'fixtures/static',
+ cache_max_age=600)
+ sr = DummyStartResponse()
+ response = inst(environ, sr)
+ body = response[0]
+ self.failUnless('<html>static</html>' in body)
+ self.assertEqual(len(sr.headerlist), 8)
+ header_names = [ x[0] for x in sr.headerlist ]
+ header_names.sort()
+ self.assertEqual(header_names,
+ ['Accept-Ranges', 'Cache-Control',
+ 'Content-Length', 'Content-Range',
+ 'Content-Type', 'ETag', 'Expires', 'Last-Modified'])
+
+ def test_resource_is_file_with_no_cache_max_age(self):
+ environ = self._makeEnviron(PATH_INFO='/index.html')
+ inst = self._makeOne('repoze.bfg.tests', 'fixtures/static')
+ sr = DummyStartResponse()
+ response = inst(environ, sr)
+ body = response[0]
+ self.failUnless('<html>static</html>' in body)
+ self.assertEqual(len(sr.headerlist), 6)
+ header_names = [ x[0] for x in sr.headerlist ]
+ header_names.sort()
+ self.assertEqual(header_names,
+ ['Accept-Ranges', 'Content-Length', 'Content-Range',
+ 'Content-Type', 'ETag', 'Last-Modified'])
+
+ def test_if_none_match(self):
+ class DummyEq(object):
+ def __eq__(self, other):
+ return True
+ dummy_eq = DummyEq()
+ environ = self._makeEnviron(HTTP_IF_NONE_MATCH=dummy_eq)
+ inst = self._makeOne('repoze.bfg.tests', 'fixtures/static')
+ sr = DummyStartResponse()
+ response = inst(environ, sr)
+ self.assertEqual(len(sr.headerlist), 1)
+ self.assertEqual(sr.status, '304 Not Modified')
+ self.assertEqual(sr.headerlist[0][0], 'ETag')
+ self.assertEqual(response[0], '')
+
+ def test_repr(self):
+ inst = self._makeOne('repoze.bfg.tests', 'fixtures/static')
+ self.failUnless(
+ repr(inst).startswith(
+ '<PackageURLParser repoze.bfg.tests:fixtures/static at'))
+
+ def test_not_found(self):
+ inst = self._makeOne('repoze.bfg.tests', 'fixtures/static')
+ environ = self._makeEnviron()
+ sr = DummyStartResponse()
+ response = inst.not_found(environ, sr, 'debug_message')
+ body = response[0]
+ self.failUnless('404 Not Found' in body)
+ self.assertEqual(sr.status, '404 Not Found')
+
+class DummyStartResponse:
+ def __call__(self, status, headerlist, exc_info=None):
+ self.status = status
+ self.headerlist = headerlist
+ self.exc_info = exc_info
+
+
diff --git a/repoze/bfg/tests/test_templating.py b/repoze/bfg/tests/test_templating.py
index d413acc2e..361cf6f7e 100644
--- a/repoze/bfg/tests/test_templating.py
+++ b/repoze/bfg/tests/test_templating.py
@@ -57,7 +57,7 @@ class TestRendererFromCache(unittest.TestCase):
from repoze.bfg import tests
module_name = tests.__name__
relpath = 'test_templating.py'
- spec = '%s\t%s' % (module_name, relpath)
+ spec = '%s:%s' % (module_name, relpath)
renderer = {}
testing.registerUtility(renderer, ITemplateRenderer, name=spec)
result = self._callFUT('test_templating.py', None)
@@ -68,7 +68,6 @@ class TestRendererFromCache(unittest.TestCase):
from repoze.bfg.tests import test_templating
module_name = test_templating.__name__
relpath = 'test_templating.py'
- spec = '%s\t%s' % (module_name, relpath)
renderer = {}
factory = DummyFactory(renderer)
result = self._callFUT('test_templating.py', factory)
@@ -79,6 +78,34 @@ class TestRendererFromCache(unittest.TestCase):
self.assertEqual(factory.path, path)
self.assertEqual(factory.kw, {})
+ def test_relpath_notyetregistered_reload_resources_true(self):
+ from zope.component import queryUtility
+ from repoze.bfg.interfaces import ISettings
+ from repoze.bfg.interfaces import ITemplateRenderer
+ settings = {'reload_resources':True}
+ testing.registerUtility(settings, ISettings)
+ renderer = {}
+ factory = DummyFactory(renderer)
+ result = self._callFUT('test_templating.py', factory)
+ self.failUnless(result is renderer)
+ spec = '%s:%s' % ('repoze.bfg.tests', 'test_templating.py')
+ self.assertEqual(queryUtility(ITemplateRenderer, name=spec),
+ None)
+
+ def test_relpath_notyetregistered_reload_resources_false(self):
+ from zope.component import queryUtility
+ from repoze.bfg.interfaces import ISettings
+ from repoze.bfg.interfaces import ITemplateRenderer
+ settings = {'reload_resources':False}
+ testing.registerUtility(settings, ISettings)
+ renderer = {}
+ factory = DummyFactory(renderer)
+ result = self._callFUT('test_templating.py', factory)
+ self.failUnless(result is renderer)
+ spec = '%s:%s' % ('repoze.bfg.tests', 'test_templating.py')
+ self.assertNotEqual(queryUtility(ITemplateRenderer, name=spec),
+ None)
+
class DummyFactory:
def __init__(self, renderer):
self.renderer = renderer
diff --git a/repoze/bfg/tests/test_view.py b/repoze/bfg/tests/test_view.py
index 55e9ecffa..5b870fa7d 100644
--- a/repoze/bfg/tests/test_view.py
+++ b/repoze/bfg/tests/test_view.py
@@ -317,8 +317,8 @@ class TestStaticView(unittest.TestCase, BaseTest):
from repoze.bfg.view import static
return static
- def _makeOne(self, path):
- return self._getTargetClass()(path)
+ def _makeOne(self, path, package_name=None):
+ return self._getTargetClass()(path, package_name=package_name)
def test_abspath(self):
import os
@@ -343,8 +343,26 @@ class TestStaticView(unittest.TestCase, BaseTest):
response = view(context, request)
self.assertEqual(request.copied, True)
here = os.path.abspath(os.path.dirname(__file__))
- abspath = os.path.join(here, 'fixtures')
- self.assertEqual(response.directory, abspath)
+ self.assertEqual(response.root_resource, 'fixtures')
+ self.assertEqual(response.resource_name, 'fixtures')
+ self.assertEqual(response.package_name, 'repoze.bfg.tests')
+ self.assertEqual(response.cache_max_age, 3600)
+
+ def test_relpath_withpackage(self):
+ import os
+ path = 'fixtures'
+ view = self._makeOne(path, package_name='another')
+ context = DummyContext()
+ request = DummyRequest()
+ request.subpath = ['__init__.py']
+ request.environ = self._makeEnviron()
+ response = view(context, request)
+ self.assertEqual(request.copied, True)
+ here = os.path.abspath(os.path.dirname(__file__))
+ self.assertEqual(response.root_resource, 'fixtures')
+ self.assertEqual(response.resource_name, 'fixtures')
+ self.assertEqual(response.package_name, 'another')
+ self.assertEqual(response.cache_max_age, 3600)
class TestBFGViewDecorator(unittest.TestCase):
def setUp(self):
diff --git a/repoze/bfg/view.py b/repoze/bfg/view.py
index 0b1f09837..2c0e3efdc 100644
--- a/repoze/bfg/view.py
+++ b/repoze/bfg/view.py
@@ -1,13 +1,17 @@
+import os
import inspect
from paste.urlparser import StaticURLParser
+
from zope.component import queryMultiAdapter
from zope.deprecation import deprecated
from repoze.bfg.interfaces import IView
from repoze.bfg.path import caller_path
+from repoze.bfg.path import caller_package
from repoze.bfg.security import view_execution_permitted
from repoze.bfg.security import Unauthorized
+from repoze.bfg.static import PackageURLParser
deprecated('view_execution_permitted',
"('from repoze.bfg.view import view_execution_permitted' is now "
@@ -106,25 +110,48 @@ def is_response(ob):
class static(object):
""" An instance of this class is a callable which can act as a BFG
view; 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
+ 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 to the
directory containing static files directory to the constructor as
- the ``root_dir`` argument. If the path is relative, it will be
- considered relative to the directory in which the Python file
- which calls ``static`` resides. ``cache_max_age`` influences the
- Expires and Max-Age response headers returned by the view (default
- is 3600 seconds or five minutes). ``level`` influences how
- relative directories are resolved (the number of hops in the call
- stack), not used very often.
+ the ``root_dir`` argument.
+
+ If the path is relative, and the ``package`` argument is ``None``,
+ it will be considered relative to the directory in which the
+ Python file which calls ``static`` resides. If the ``package``
+ name argument is provided, and a relative ``root_dir`` is
+ provided, the ``root_dir`` will be considered relative to the
+ Python 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). ``level`` influences how relative directories are
+ resolved (the number of hops in the call stack), not used very
+ often.
+
+ .. note:: If the ``root_dir`` is relative to a package, the BFG
+ ``resource`` ZCML directive 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, level=2):
- root_dir = caller_path(root_dir, level=level)
- self.app = StaticURLParser(root_dir, cache_max_age=cache_max_age)
+ def __init__(self, root_dir, cache_max_age=3600, level=2,
+ package_name=None):
+ if os.path.isabs(root_dir):
+ root_dir = caller_path(root_dir, level=level)
+ self.app = StaticURLParser(root_dir, cache_max_age=cache_max_age)
+ else:
+ if package_name is None:
+ package_name = caller_package().__name__
+ self.app = PackageURLParser(package_name, root_dir,
+ cache_max_age=cache_max_age)
def __call__(self, context, request):
subpath = '/'.join(request.subpath)