diff options
| author | Chris McDonough <chrism@agendaless.com> | 2010-07-02 01:47:47 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2010-07-02 01:47:47 +0000 |
| commit | b29429e470c093573f3735b0dbf09d42c29cfe4d (patch) | |
| tree | 8dfa266de6bd0f10a80d9654fe3ddf3a7897c467 | |
| parent | 78a659d76e5bbb7544212174f010c1f50f8bcbe6 (diff) | |
| download | pyramid-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.
| -rw-r--r-- | CHANGES.txt | 27 | ||||
| -rw-r--r-- | docs/narr/static.rst | 106 | ||||
| -rw-r--r-- | docs/whatsnew-1.3.rst | 9 | ||||
| -rw-r--r-- | repoze/bfg/configuration.py | 97 | ||||
| -rw-r--r-- | repoze/bfg/interfaces.py | 10 | ||||
| -rw-r--r-- | repoze/bfg/static.py | 121 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_configuration.py | 60 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_static.py | 193 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_url.py | 79 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_view.py | 79 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_zcml.py | 14 | ||||
| -rw-r--r-- | repoze/bfg/url.py | 42 | ||||
| -rw-r--r-- | repoze/bfg/view.py | 69 | ||||
| -rw-r--r-- | repoze/bfg/zcml.py | 18 |
14 files changed, 634 insertions, 290 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index d56f51e4e..28322d42f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -48,6 +48,23 @@ Features add_route('ym', '/:year/:month', custom_predicates=(twenty_ten,)) add_route('ymd', '/:year/:month:/day', custom_predicates=(twenty_ten,)) +- 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. Documentation ------------- @@ -68,6 +85,16 @@ Documentation - A section named ``Custom Route Predicates`` was added to the URL Dispatch narrative chapter. +- The Static Resources chapter has been updated to mention using + ``static_url`` to generate URLs to external webservers. + +Internal +-------- + +- Removed ``repoze.bfg.static.StaticURLFactory`` in favor of a new + abstraction revolving around the (still-internal) + ``repoze.bfg.static.StaticURLInfo`` helper class. + 1.3a3 (2010-05-01) ================== diff --git a/docs/narr/static.rst b/docs/narr/static.rst index ad42f1b79..d33831741 100644 --- a/docs/narr/static.rst +++ b/docs/narr/static.rst @@ -21,9 +21,17 @@ resources such as JavaScript and CSS files. This mechanism makes static files available at a name relative to the application root URL, e.g. ``/static``. +Note that the ``path`` provided to ``static`` may be a fully qualified +:term:`resource specification`, a package-relative path, or an +*absolute path*. The ``path`` with the value ``a/b/c/static`` of a +``static`` directive in a ZCML file that resides in the "mypackage" +package will resolve to a package-qualified resource such as +``some_package:a/b/c/static``. + Here's an example of a ``static`` ZCML directive that will serve files up under the ``/static`` URL from the ``/var/www/static`` directory of -the computer which runs the :mod:`repoze.bfg` application. +the computer which runs the :mod:`repoze.bfg` application using an +absolute path. .. code-block:: xml :linenos: @@ -35,7 +43,8 @@ the computer which runs the :mod:`repoze.bfg` application. Here's an example of a ``static`` directive that will serve files up under the ``/static`` URL from the ``a/b/c/static`` directory of the -Python package named ``some_package``. +Python package named ``some_package`` using a fully qualified +:term:`resource specification`. .. code-block:: xml :linenos: @@ -47,7 +56,8 @@ Python package named ``some_package``. Here's an example of a ``static`` directive that will serve files up under the ``/static`` URL from the ``static`` directory of the Python -package in which the ``configure.zcml`` file lives. +package in which the ``configure.zcml`` file lives using a +package-relative path. .. code-block:: xml :linenos: @@ -57,23 +67,53 @@ package in which the ``configure.zcml`` file lives. path="static" /> -When you place your static files on the filesystem in the directory -represented as the ``path`` of the directive, you should be able -to view the static files in this directory via a browser at URLs -prefixed with the directive's ``name``. For instance if the -``static`` directive's ``name`` is ``static`` and the static -directive's ``path`` is ``/path/to/static``, -``http://localhost:6543/static/foo.js`` will return the file -``/path/to/static/dir/foo.js``. The static directory may contain -subdirectories recursively, and any subdirectories may hold files; -these will be resolved by the static view as you would expect. - -See :ref:`static_directive` for detailed information about the -``static`` ZCML directive. - .. note:: The :ref:`static_directive` ZCML directive is new in :mod:`repoze.bfg` 1.1. +Whether you use for ``path`` a fully qualified resource specification, +an absolute path, or a package-relative path, When you place your +static files on the filesystem in the directory represented as the +``path`` of the directive, you will then be able to view the static +files in this directory via a browser at URLs prefixed with the +directive's ``name``. For instance if the ``static`` directive's +``name`` is ``static`` and the static directive's ``path`` is +``/path/to/static``, ``http://localhost:6543/static/foo.js`` will +return the file ``/path/to/static/dir/foo.js``. The static directory +may contain subdirectories recursively, and any subdirectories may +hold files; these will be resolved by the static view as you would +expect. + +While the ``path`` argument can be a number of different things, the +``name`` argument of the ``static`` ZCML directive can also be one of +a number of things: a *view name* or a *URL*. The above examples have +shown usage of the ``name`` argument as a view name. When ``name`` is +a *URL* (or any string with a slash (``/``) in it), static resources +can be served from an external webserver. In this mode, the ``name`` +is used as the URL prefix when generating a URL using +:func:`repoze.bfg.url.static_url`. + +For example, the ``static`` ZCML directive may be fed a ``name`` +argument which is ``http://example.com/images``: + +.. code-block:: xml + :linenos: + + <static + name="http://example.com/images" + path="mypackage:images" + /> + +Because the ``static`` ZCML directive is provided with a ``name`` +argument that is the URL prefix ``http://example.com/images``, +subsequent calls to :func:`repoze.bfg.url.static_url` with paths that +start with the ``path`` argument passed to +:meth:`repoze.bfg.configuration.Configurator.add_static_view` will +generate a URL something like ``http://example.com/logo.png``. The +external webserver listening on ``example.com`` must be itself +configured to respond properly to such a request. The +:func:`repoze.bfg.url.static_url` API is discussed in more detail +later in this chapter. + The :meth:`repoze.bfg.configuration.Configurator.add_static_view` method offers an imperative equivalent to the ``static`` ZCML directive. Use of the ``add_static_view`` imperative configuration @@ -96,6 +136,9 @@ static resource directory, a special helper API named URL for a package resource that lives in one of the directories named by the static registration ``path`` attribute. +.. note:: The :func:`repoze.bfg.url.static_url` API is new in + :mod:`repoze.bfg` 1.1. + For example, let's assume you create a set of ``static`` declarations in ZCML like so: @@ -148,8 +191,33 @@ rather than constructing static URLs "by hand" is that if you need to change the ``name`` of a static URL declaration in ZCML, the generated URLs will continue to resolve properly after the rename. -.. note:: The :func:`repoze.bfg.url.static_url` API is new in - :mod:`repoze.bfg` 1.1. +URLs may also be generated by :func:`repoze.bfg.url.static_url` to +static resources that live *outside* the :mod:`repoze.bfg` +application. This will happen when the ``name`` argument provided to +the ``static`` ZCML directive or the +:meth:`repoze.bfg.configuration.Configurator.add_static_view` API +associated with the path fed to :func:`repoze.bfg.url.static_url` is a +*URL* instead of a view name. For example, the ``name`` argument +given to either the ZCML directive or the configurator API may be +``http://example.com`` while the the ``path`` given may be +``mypackage:images``: + +.. code-block:: xml + :linenos: + + <static + name="static1" + path="mypackage:images" + /> + +Under such a configuration, the URL generated by ``static_url`` for +resources which begin with ``mypackage:images`` will be prefixed with +``http://example.com/images``: + +.. code-block:: python + + static_url('mypackage:images/logo.png', request) + # -> http://example.com/images/logo.png .. index:: single: static resource view diff --git a/docs/whatsnew-1.3.rst b/docs/whatsnew-1.3.rst index 17be94c9d..6a20938a8 100644 --- a/docs/whatsnew-1.3.rst +++ b/docs/whatsnew-1.3.rst @@ -140,6 +140,15 @@ Minor Feature Additions :ref:`custom_route_predicates`. In prior versions, the ``info`` argument was always ``None``. +- It is now possible to use a URL as the ``name`` argument fed to + :meth:`repoze.bfg.configuration.Configurator.add_static_view`. When + the name argument is a URL, the :func:`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. + Backwards Incompatibilites -------------------------- diff --git a/repoze/bfg/configuration.py b/repoze/bfg/configuration.py index 869e8c2c2..60fe4627e 100644 --- a/repoze/bfg/configuration.py +++ b/repoze/bfg/configuration.py @@ -34,6 +34,7 @@ from repoze.bfg.interfaces import IRouteRequest from repoze.bfg.interfaces import IRoutesMapper from repoze.bfg.interfaces import ISecuredView from repoze.bfg.interfaces import ISettings +from repoze.bfg.interfaces import IStaticURLInfo from repoze.bfg.interfaces import ITemplateRenderer from repoze.bfg.interfaces import ITranslationDirectories from repoze.bfg.interfaces import ITraverser @@ -59,7 +60,7 @@ from repoze.bfg.request import route_request_iface from repoze.bfg.resource import PackageOverrides from repoze.bfg.resource import resolve_resource_spec from repoze.bfg.settings import Settings -from repoze.bfg.static import StaticRootFactory +from repoze.bfg.static import StaticURLInfo from repoze.bfg.threadlocal import get_current_registry from repoze.bfg.threadlocal import get_current_request from repoze.bfg.threadlocal import manager @@ -68,7 +69,6 @@ from repoze.bfg.traversal import DefaultRootFactory from repoze.bfg.traversal import find_interface from repoze.bfg.urldispatch import RoutesMapper from repoze.bfg.view import render_view_to_response -from repoze.bfg.view import static from repoze.bfg.view import default_notfound_view from repoze.bfg.view import default_forbidden_view @@ -1454,27 +1454,100 @@ class Configurator(object): ctranslate = ChameleonTranslate(translator) self.registry.registerUtility(ctranslate, IChameleonTranslate) - def add_static_view(self, name, path, cache_max_age=3600, _info=u''): - """ Add a view used to render static resources to the current - configuration state. + def add_static_view(self, name, path, **kw): + """ Add a view used to render static resources such as images + and CSS files. The ``name`` argument is a string representing :term:`view - name` of the view which is registered. + name` of the view which is registered. It may alternately be + a *url prefix*. The ``path`` argument is the path on disk where the static files reside. This can be an absolute path, a package-relative path, or a :term:`resource specification`. + The ``cache_max_age`` keyword argument is input to set the + ``Expires`` and ``Cache-Control`` headers for static resources + served. Note that this argument has no effect when the + ``name`` is a *url prefix*. + + Usage + ----- + + The ``add_static_view`` function is typically used in + conjunction with the :func:`repoze.bfg.url.static_url` + function. ``add_static_view`` adds a view which renders a + static resource when some URL is visited; + :func:`repoze.bfg.url.static_url` generates a URL to that + resource. + + The ``name`` argument to ``add_static_view`` is usually a + :term:`view name`. When this is the case, the + :func:`repoze.bfg.url.static_url` API will generate a URL + which points to a BFG view, which will serve up a set of + resources that live in the package itself. For example: + + .. code-block:: python + + add_static_view('images', 'mypackage:images/') + + Code that registers such a view can generate URLs to the view + via :func:`repoze.bfg.url.static_url`: + + .. code-block:: python + + static_url('mypackage:images/logo.png', request) + + When ``add_static_view`` is called with a ``name`` argument + that represents a simple view name, as it is above, subsequent + calls to :func:`repoze.bfg.url.static_url` with paths that + start with the ``path`` argument passed to ``add_static_view`` + will generate a URL something like ``http://<BFG app + URL>/images/logo.png``, which will cause the ``logo.png`` file + in the ``images`` subdirectory of the ``mypackage`` package to + be served. + + ``add_static_view`` can alternately be used with a ``name`` + argument which is a *URL*, causing static resources to be + served from an external webserver. This happens when the + ``name`` argument is a URL (detected as any string with a + slash in it). In this mode, the ``name`` is used as the URL + prefix when generating a URL using + :func:`repoze.bfg.url.static_url`. For example, if + ``add_static_view`` is called like so: + + .. code-block:: python + + add_static_view('http://example.com/images', 'mypackage:images/') + + Subsequently, the URLs generated by + :func:`repoze.bfg.url.static_url` for that static view will be + prefixed with ``http://example.com/images``: + + .. code-block:: python + + static_url('mypackage:images/logo.png', request) + + When ``add_static_view`` is called with a ``name`` argument + that is the URL prefix ``http://example.com/images``, + subsequent calls to :func:`repoze.bfg.url.static_url` with + paths that start with the ``path`` argument passed to + ``add_static_view`` will generate a URL something like + ``http://example.com/logo.png``. The external webserver + listening on ``example.com`` must be itself configured to + respond properly to such a request. + See :ref:`static_resources_section` for more information. """ spec = self._make_spec(path) - view = static(spec, cache_max_age=cache_max_age) - self.add_route(name, "%s*subpath" % name, view=view, - view_for=StaticRootFactory, - factory=StaticRootFactory(spec), - _info=_info) - # testing API + info = self.registry.queryUtility(IStaticURLInfo) + if info is None: + info = StaticURLInfo(self) + self.registry.registerUtility(info, IStaticURLInfo) + info.add(name, spec, **kw) + + # testing API def testing_securitypolicy(self, userid=None, groupids=(), permissive=True): """Unit/integration testing helper: Registers a pair of faux diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py index 09b639525..814e558f6 100644 --- a/repoze/bfg/interfaces.py +++ b/repoze/bfg/interfaces.py @@ -29,8 +29,7 @@ class IWSGIApplicationCreatedEvent(Interface): class IRequest(Interface): """ Request type interface attached to all request objects """ -# for exception view lookups -IRequest.combined = IRequest +IRequest.combined = IRequest # for exception view lookups class IRouteRequest(Interface): """ *internal only* interface used as in a utility lookup to find @@ -72,6 +71,13 @@ class IAuthorizationPolicy(Interface): def principals_allowed_by_permission(context, permission): """ Return a set of principal identifiers allowed by the permission """ +class IStaticURLInfo(Interface): + """ A policy for generating URLs to static resources """ + def add(name, spec, **extra): + """ Add a new static info registration """ + + def generate(path, request, **kw): + """ Generate a URL for the given path """ class IResponseFactory(Interface): """ A utility which generates a response factory """ 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) diff --git a/repoze/bfg/tests/test_configuration.py b/repoze/bfg/tests/test_configuration.py index 16b590cdd..4762ed416 100644 --- a/repoze/bfg/tests/test_configuration.py +++ b/repoze/bfg/tests/test_configuration.py @@ -1699,59 +1699,52 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(overrides.inserted, [('path', 'opackage', 'oprefix')]) self.assertEqual(overrides.package, package) - def test_add_static_view_here_relative(self): + def test_add_static_here_no_utility_registered(self): from repoze.bfg.static import PackageURLParser from zope.interface import implementedBy - from repoze.bfg.static import StaticRootFactory + from repoze.bfg.static import StaticURLInfo from repoze.bfg.interfaces import IView from repoze.bfg.interfaces import IViewClassifier config = self._makeOne() config.add_static_view('static', 'fixtures/static') request_type = self._getRouteRequestIface(config, 'static') route = self._assertRoute(config, 'static', 'static*subpath') - self.assertEqual(route.factory.__class__, StaticRootFactory) - iface = implementedBy(StaticRootFactory) + self.assertEqual(route.factory.__class__, type(lambda x: x)) + iface = implementedBy(StaticURLInfo) wrapped = config.registry.adapters.lookup( (IViewClassifier, request_type, iface), IView, name='') request = self._makeRequest(config) self.assertEqual(wrapped(None, request).__class__, PackageURLParser) def test_add_static_view_package_relative(self): - from repoze.bfg.static import PackageURLParser - from zope.interface import implementedBy - from repoze.bfg.static import StaticRootFactory - from repoze.bfg.interfaces import IView - from repoze.bfg.interfaces import IViewClassifier + from repoze.bfg.interfaces import IStaticURLInfo + info = DummyStaticURLInfo() config = self._makeOne() + config.registry.registerUtility(info, IStaticURLInfo) config.add_static_view('static', 'repoze.bfg.tests:fixtures/static') - request_type = self._getRouteRequestIface(config, 'static') - route = self._assertRoute(config, 'static', 'static*subpath') - self.assertEqual(route.factory.__class__, StaticRootFactory) - iface = implementedBy(StaticRootFactory) - wrapped = config.registry.adapters.lookup( - (IViewClassifier, request_type, iface), IView, name='') - request = self._makeRequest(config) - self.assertEqual(wrapped(None, request).__class__, PackageURLParser) + self.assertEqual(info.added, + [('static', 'repoze.bfg.tests:fixtures/static', {})]) + + def test_add_static_view_package_here_relative(self): + from repoze.bfg.interfaces import IStaticURLInfo + info = DummyStaticURLInfo() + config = self._makeOne() + config.registry.registerUtility(info, IStaticURLInfo) + config.add_static_view('static', 'fixtures/static') + self.assertEqual(info.added, + [('static', 'repoze.bfg.tests:fixtures/static', {})]) def test_add_static_view_absolute(self): - from paste.urlparser import StaticURLParser import os - from zope.interface import implementedBy - from repoze.bfg.static import StaticRootFactory - from repoze.bfg.interfaces import IView - from repoze.bfg.interfaces import IViewClassifier + from repoze.bfg.interfaces import IStaticURLInfo + info = DummyStaticURLInfo() config = self._makeOne() + config.registry.registerUtility(info, IStaticURLInfo) here = os.path.dirname(__file__) static_path = os.path.join(here, 'fixtures', 'static') config.add_static_view('static', static_path) - request_type = self._getRouteRequestIface(config, 'static') - route = self._assertRoute(config, 'static', 'static*subpath') - self.assertEqual(route.factory.__class__, StaticRootFactory) - iface = implementedBy(StaticRootFactory) - wrapped = config.registry.adapters.lookup( - (IViewClassifier, request_type, iface), IView, name='') - request = self._makeRequest(config) - self.assertEqual(wrapped(None, request).__class__, StaticURLParser) + self.assertEqual(info.added, + [('static', static_path, {})]) def test_set_notfound_view(self): from zope.interface import implementedBy @@ -3661,3 +3654,10 @@ class DummyFactory(object): class DummyEvent: implements(IDummy) +class DummyStaticURLInfo: + def __init__(self): + self.added = [] + + def add(self, name, spec, **kw): + self.added.append((name, spec, kw)) + diff --git a/repoze/bfg/tests/test_static.py b/repoze/bfg/tests/test_static.py index 355afac2a..f857f63a8 100644 --- a/repoze/bfg/tests/test_static.py +++ b/repoze/bfg/tests/test_static.py @@ -1,4 +1,5 @@ import unittest +from repoze.bfg.testing import cleanUp class TestPackageURLParser(unittest.TestCase): def _getTargetClass(self): @@ -148,17 +149,195 @@ class TestPackageURLParser(unittest.TestCase): self.failUnless('404 Not Found' in body) self.assertEqual(sr.status, '404 Not Found') -class TestStaticRootFactory(unittest.TestCase): - def test_it(self): - from repoze.bfg.static import StaticRootFactory - factory = StaticRootFactory('abc') - self.assertEqual(factory.spec, 'abc') - self.assertEqual(factory({}), factory) +class TestStaticView(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def _getTargetClass(self): + from repoze.bfg.view import static + return static + + def _makeOne(self, path, package_name=None): + return self._getTargetClass()(path, package_name=package_name) + + def _makeEnviron(self, **extras): + environ = { + 'wsgi.url_scheme':'http', + 'wsgi.version':(1,0), + 'SERVER_NAME':'localhost', + 'SERVER_PORT':'8080', + 'REQUEST_METHOD':'GET', + } + environ.update(extras) + return environ + + def test_abspath(self): + import os + path = os.path.dirname(__file__) + view = self._makeOne(path) + context = DummyContext() + request = DummyRequest() + request.subpath = ['__init__.py'] + request.environ = self._makeEnviron() + response = view(context, request) + self.assertEqual(request.copied, True) + self.assertEqual(response.directory, path) + + def test_relpath(self): + path = 'fixtures' + view = self._makeOne(path) + context = DummyContext() + request = DummyRequest() + request.subpath = ['__init__.py'] + request.environ = self._makeEnviron() + response = view(context, request) + self.assertEqual(request.copied, True) + 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): + view = self._makeOne('another:fixtures') + context = DummyContext() + request = DummyRequest() + request.subpath = ['__init__.py'] + request.environ = self._makeEnviron() + response = view(context, request) + self.assertEqual(request.copied, True) + 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) + + def test_relpath_withpackage_name(self): + view = self._makeOne('fixtures', package_name='another') + context = DummyContext() + request = DummyRequest() + request.subpath = ['__init__.py'] + request.environ = self._makeEnviron() + response = view(context, request) + self.assertEqual(request.copied, True) + 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 TestStaticURLInfo(unittest.TestCase): + def _getTargetClass(self): + from repoze.bfg.static import StaticURLInfo + return StaticURLInfo + + def _makeOne(self, config): + return self._getTargetClass()(config) + + def test_verifyClass(self): + from repoze.bfg.interfaces import IStaticURLInfo + from zope.interface.verify import verifyClass + verifyClass(IStaticURLInfo, self._getTargetClass()) + + def test_verifyObject(self): + from repoze.bfg.interfaces import IStaticURLInfo + from zope.interface.verify import verifyObject + verifyObject(IStaticURLInfo, self._makeOne(None)) + + def test_ctor(self): + info = self._makeOne(None) + self.assertEqual(info.registrations, []) + self.assertEqual(info.config, None) + + def test_generate_missing(self): + inst = self._makeOne(None) + request = DummyRequest() + self.assertRaises(ValueError, inst.generate, 'path', request) + + def test_generate_slash_in_name1(self): + inst = self._makeOne(None) + inst.registrations = [('http://example.com/foo/', 'package:path')] + request = DummyRequest() + result = inst.generate('package:path/abc', request) + self.assertEqual(result, 'http://example.com/foo/abc') + + def test_generate_slash_in_name2(self): + inst = self._makeOne(None) + inst.registrations = [('http://example.com/foo/', 'package:path')] + request = DummyRequest() + result = inst.generate('package:path', request) + self.assertEqual(result, 'http://example.com/foo/') + + def test_generate_route_url(self): + inst = self._makeOne(None) + inst.registrations = [('viewname', 'package:path')] + def route_url(n, r, **kw): + self.assertEqual(n, 'viewname') + self.assertEqual(r, request) + self.assertEqual(kw, {'subpath':'/abc', 'a':1}) + return 'url' + request = DummyRequest() + inst.route_url = route_url + result = inst.generate('package:path/abc', request, a=1) + self.assertEqual(result, 'url') + + def test_add_already_exists(self): + inst = self._makeOne(None) + inst.registrations = [('http://example.com/', 'package:path')] + inst.add('http://example.com/', 'anotherpackage:path') + expected = [('http://example.com/', 'anotherpackage:path')] + self.assertEqual(inst.registrations, expected) + + def test_add_url_withendslash(self): + inst = self._makeOne(None) + inst.add('http://example.com/', 'anotherpackage:path') + expected = [('http://example.com/', 'anotherpackage:path')] + self.assertEqual(inst.registrations, expected) + + def test_add_url_noendslash(self): + inst = self._makeOne(None) + inst.add('http://example.com', 'anotherpackage:path') + expected = [('http://example.com/', 'anotherpackage:path')] + self.assertEqual(inst.registrations, expected) + + def test_add_viewname(self): + from repoze.bfg.static import static_view + class Config: + def add_route(self, *arg, **kw): + self.arg = arg + self.kw = kw + config = Config() + inst = self._makeOne(config) + inst.add('view', 'anotherpackage:path', cache_max_age=1) + expected = [('view', 'anotherpackage:path')] + self.assertEqual(inst.registrations, expected) + self.assertEqual(config.arg, ('view', 'view*subpath')) + self.assertEqual(config.kw['_info'], None) + self.assertEqual(config.kw['view_for'], self._getTargetClass()) + self.assertEqual(config.kw['factory'](), inst) + self.assertEqual(config.kw['view'].__class__, static_view) + self.assertEqual(config.kw['view'].app.cache_max_age, 1) + self.assertEqual(inst.registrations, expected) class DummyStartResponse: def __call__(self, status, headerlist, exc_info=None): self.status = status self.headerlist = headerlist self.exc_info = exc_info - +class DummyContext: + pass + +class DummyRequest: + def __init__(self, environ=None): + if environ is None: + environ = {} + self.environ = environ + + def get_response(self, application): + return application + + def copy(self): + self.copied = True + return self + diff --git a/repoze/bfg/tests/test_url.py b/repoze/bfg/tests/test_url.py index 0f5050cd4..e38c6a8eb 100644 --- a/repoze/bfg/tests/test_url.py +++ b/repoze/bfg/tests/test_url.py @@ -187,6 +187,14 @@ class TestRouteUrl(unittest.TestCase): self.assertEqual(mapper.kw, {}) # shouldnt have anchor/query self.assertEqual(result, 'http://example.com:5432?name=some_name') + def test_with_app_url(self): + from repoze.bfg.interfaces import IRoutesMapper + request = _makeRequest() + mapper = DummyRoutesMapper(result='/1/2/3') + request.registry.registerUtility(mapper, IRoutesMapper) + result = self._callFUT('flub', request, _app_url='http://example2.com') + self.assertEqual(result, 'http://example2.com/1/2/3') + class TestStaticUrl(unittest.TestCase): def setUp(self): cleanUp() @@ -198,54 +206,45 @@ class TestStaticUrl(unittest.TestCase): from repoze.bfg.url import static_url return static_url(*arg, **kw) - def test_notfound(self): - from repoze.bfg.interfaces import IRoutesMapper + def test_staticurlinfo_notfound(self): request = _makeRequest() - mapper = DummyRoutesMapper(result='/1/2/3') - request.registry.registerUtility(mapper, IRoutesMapper) self.assertRaises(ValueError, self._callFUT, 'static/foo.css', request) def test_abspath(self): - from repoze.bfg.interfaces import IRoutesMapper request = _makeRequest() - mapper = DummyRoutesMapper(result='/1/2/3') - request.registry.registerUtility(mapper, IRoutesMapper) self.assertRaises(ValueError, self._callFUT, '/static/foo.css', request) def test_found_rel(self): - from repoze.bfg.interfaces import IRoutesMapper - from repoze.bfg.static import StaticRootFactory + from repoze.bfg.interfaces import IStaticURLInfo request = _makeRequest() - factory = StaticRootFactory('repoze.bfg.tests:fixtures') - routes = [DummyRoute('name', factory=factory)] - mapper = DummyRoutesMapper(result='/1/2/3', routes = routes) - request.registry.registerUtility(mapper, IRoutesMapper) - url = self._callFUT('fixtures/minimal.pt', request) - self.assertEqual(url, 'http://example.com:5432/1/2/3') + info = DummyStaticURLInfo('abc') + request.registry.registerUtility(info, IStaticURLInfo) + result = self._callFUT('static/foo.css', request) + self.assertEqual(result, 'abc') + self.assertEqual(info.args, + ('repoze.bfg.tests:static/foo.css', request, {}) ) def test_found_abs(self): - from repoze.bfg.interfaces import IRoutesMapper - from repoze.bfg.static import StaticRootFactory + from repoze.bfg.interfaces import IStaticURLInfo request = _makeRequest() - factory = StaticRootFactory('repoze.bfg.tests:fixtures') - routes = [DummyRoute('name', factory=factory)] - mapper = DummyRoutesMapper(result='/1/2/3', routes = routes) - request.registry.registerUtility(mapper, IRoutesMapper) - url = self._callFUT('repoze.bfg.tests:fixtures/minimal.pt', request) - self.assertEqual(url, 'http://example.com:5432/1/2/3') + info = DummyStaticURLInfo('abc') + request.registry.registerUtility(info, IStaticURLInfo) + result = self._callFUT('repoze.bfg.tests:static/foo.css', request) + self.assertEqual(result, 'abc') + self.assertEqual(info.args, + ('repoze.bfg.tests:static/foo.css', request, {}) ) def test_found_abs_no_registry_on_request(self): from repoze.bfg.threadlocal import get_current_registry - from repoze.bfg.interfaces import IRoutesMapper - from repoze.bfg.static import StaticRootFactory - factory = StaticRootFactory('repoze.bfg.tests:fixtures') - routes = [DummyRoute('name', factory=factory)] - mapper = DummyRoutesMapper(result='/1/2/3', routes = routes) - registry = get_current_registry() - registry.registerUtility(mapper, IRoutesMapper) + from repoze.bfg.interfaces import IStaticURLInfo request = DummyRequest() - url = self._callFUT('repoze.bfg.tests:fixtures/minimal.pt', request) - self.assertEqual(url, 'http://example.com:5432/1/2/3') + registry = get_current_registry() + info = DummyStaticURLInfo('abc') + registry.registerUtility(info, IStaticURLInfo) + result = self._callFUT('repoze.bfg.tests:static/foo.css', request) + self.assertEqual(result, 'abc') + self.assertEqual(info.args, + ('repoze.bfg.tests:static/foo.css', request, {}) ) class DummyContext(object): def __init__(self, next=None): @@ -264,20 +263,12 @@ class DummyRoutesMapper: self.result = result self.routes = routes - def get_routes(self): - return self.routes - def generate(self, *route_args, **kw): self.kw = kw if self.raise_exc: raise self.raise_exc return self.result -class DummyRoute: - def __init__(self, name, factory=None): - self.name = name - self.factory = factory - def _makeRequest(environ=None): from repoze.bfg.registry import Registry request = DummyRequest(environ) @@ -285,3 +276,11 @@ def _makeRequest(environ=None): return request +class DummyStaticURLInfo: + def __init__(self, result): + self.result = result + + def generate(self, path, request, **kw): + self.args = path, request, kw + return self.result + diff --git a/repoze/bfg/tests/test_view.py b/repoze/bfg/tests/test_view.py index eb574cf11..bb178029c 100644 --- a/repoze/bfg/tests/test_view.py +++ b/repoze/bfg/tests/test_view.py @@ -206,72 +206,6 @@ class TestIsResponse(unittest.TestCase): response.status = None self.assertEqual(self._callFUT(response), False) -class TestStaticView(BaseTest, unittest.TestCase): - def setUp(self): - cleanUp() - - def tearDown(self): - cleanUp() - - def _getTargetClass(self): - from repoze.bfg.view import static - return static - - def _makeOne(self, path, package_name=None): - return self._getTargetClass()(path, package_name=package_name) - - def test_abspath(self): - import os - path = os.path.dirname(__file__) - view = self._makeOne(path) - context = DummyContext() - request = DummyRequest() - request.subpath = ['__init__.py'] - request.environ = self._makeEnviron() - response = view(context, request) - self.assertEqual(request.copied, True) - self.assertEqual(response.directory, path) - - def test_relpath(self): - path = 'fixtures' - view = self._makeOne(path) - context = DummyContext() - request = DummyRequest() - request.subpath = ['__init__.py'] - request.environ = self._makeEnviron() - response = view(context, request) - self.assertEqual(request.copied, True) - 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): - view = self._makeOne('another:fixtures') - context = DummyContext() - request = DummyRequest() - request.subpath = ['__init__.py'] - request.environ = self._makeEnviron() - response = view(context, request) - self.assertEqual(request.copied, True) - 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) - - def test_relpath_withpackage_name(self): - view = self._makeOne('fixtures', package_name='another') - context = DummyContext() - request = DummyRequest() - request.subpath = ['__init__.py'] - request.environ = self._makeEnviron() - response = view(context, request) - self.assertEqual(request.copied, True) - 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): cleanUp() @@ -521,19 +455,6 @@ class AppendSlashNotFoundView(BaseTest, unittest.TestCase): class DummyContext: pass -class DummyRequest: - def __init__(self, environ=None): - if environ is None: - environ = {} - self.environ = environ - - def get_response(self, application): - return application - - def copy(self): - self.copied = True - return self - def make_view(response): def view(context, request): return response diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py index 0c8cccb06..d7282551d 100644 --- a/repoze/bfg/tests/test_zcml.py +++ b/repoze/bfg/tests/test_zcml.py @@ -683,11 +683,11 @@ class TestStaticDirective(unittest.TestCase): from repoze.bfg.zcml import static return static(*arg, **kw) - def test_it(self): + def test_it_with_slash(self): from repoze.bfg.static import PackageURLParser from repoze.bfg.threadlocal import get_current_registry from zope.interface import implementedBy - from repoze.bfg.static import StaticRootFactory + from repoze.bfg.static import StaticURLInfo from repoze.bfg.interfaces import IView from repoze.bfg.interfaces import IViewClassifier from repoze.bfg.interfaces import IRouteRequest @@ -701,9 +701,8 @@ class TestStaticDirective(unittest.TestCase): route_action = actions[0] discriminator = route_action['discriminator'] - self.assertEqual(discriminator, - ('route', 'name', False, None, None, None, None, None)) - route_action['callable'](*route_action['args']) + self.assertEqual(discriminator, ('static', 'name')) + route_action['callable'](*route_action['args'], **route_action['kw']) mapper = reg.getUtility(IRoutesMapper) routes = mapper.get_routes() self.assertEqual(len(routes), 1) @@ -712,16 +711,15 @@ class TestStaticDirective(unittest.TestCase): view_action = actions[1] discriminator = view_action['discriminator'] - self.assertEqual(discriminator[:3], ('view', StaticRootFactory, '')) + self.assertEqual(discriminator[:3], ('view', StaticURLInfo, '')) self.assertEqual(discriminator[4], IView) - iface = implementedBy(StaticRootFactory) + iface = implementedBy(StaticURLInfo) request_type = reg.getUtility(IRouteRequest, 'name') view = reg.adapters.lookup( (IViewClassifier, request_type, iface), IView, name='') request = DummyRequest() self.assertEqual(view(None, request).__class__, PackageURLParser) - class TestResourceDirective(unittest.TestCase): def setUp(self): testing.setUp() diff --git a/repoze/bfg/url.py b/repoze/bfg/url.py index 15dabaa61..e6a693d8a 100644 --- a/repoze/bfg/url.py +++ b/repoze/bfg/url.py @@ -6,10 +6,10 @@ from repoze.lru import lru_cache from repoze.bfg.interfaces import IContextURL from repoze.bfg.interfaces import IRoutesMapper +from repoze.bfg.interfaces import IStaticURLInfo from repoze.bfg.encode import urlencode from repoze.bfg.path import caller_package -from repoze.bfg.static import StaticRootFactory from repoze.bfg.threadlocal import get_current_registry from repoze.bfg.traversal import TraversalContextURL from repoze.bfg.traversal import quote_path_segment @@ -84,6 +84,17 @@ def route_url(route_name, request, *elements, **kw): element will always follow the query element, e.g. ``http://example.com?foo=1#bar``. + If a keyword ``_app_url`` is present, it 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``. If + ``_app_url`` is not specified, the result of + ``request.application_url`` will be used as the prefix (the + default). + This function raises a :exc:`KeyError` if the URL cannot be generated due to missing replacement names. Extra replacement names are ignored. @@ -96,6 +107,7 @@ def route_url(route_name, request, *elements, **kw): anchor = '' qs = '' + app_url = None if '_query' in kw: qs = '?' + urlencode(kw.pop('_query'), doseq=True) @@ -106,6 +118,9 @@ def route_url(route_name, request, *elements, **kw): anchor = anchor.encode('utf-8') anchor = '#' + anchor + if '_app_url' in kw: + app_url = kw.pop('_app_url') + path = mapper.generate(route_name, kw) # raises KeyError if generate fails if elements: @@ -115,7 +130,13 @@ def route_url(route_name, request, *elements, **kw): else: suffix = '' - return request.application_url + path + suffix + qs + anchor + if app_url is None: + # we only defer lookup of application_url until here because + # it's somewhat expensive; we won't need to do it if we've + # been passed _app_url + app_url = request.application_url + + return app_url + path + suffix + qs + anchor def model_url(model, request, *elements, **kw): """ @@ -281,18 +302,11 @@ def static_url(path, request, **kw): except AttributeError: reg = get_current_registry() # b/c - mapper = reg.getUtility(IRoutesMapper) - routes = mapper.get_routes() - - for route in routes: - factory = route.factory - if factory.__class__ is StaticRootFactory: - if path.startswith(factory.spec): - subpath = path[len(factory.spec):] - kw['subpath'] = subpath - return route_url(route.name, request, **kw) - - raise ValueError('No static URL definition matching %s' % path) + info = reg.queryUtility(IStaticURLInfo) + if info is None: + raise ValueError('No static URL definition matching %s' % path) + + return info.generate(path, request, **kw) @lru_cache(1000) def _join_elements(elements): diff --git a/repoze/bfg/view.py b/repoze/bfg/view.py index 1ab966be2..7a853eb28 100644 --- a/repoze/bfg/view.py +++ b/repoze/bfg/view.py @@ -17,8 +17,6 @@ from webob.exc import HTTPFound import venusian -from paste.urlparser import StaticURLParser - from zope.deprecation import deprecated from zope.interface import providedBy @@ -27,11 +25,9 @@ from repoze.bfg.interfaces import IRoutesMapper from repoze.bfg.interfaces import IView from repoze.bfg.interfaces import IViewClassifier -from repoze.bfg.path import caller_package from repoze.bfg.path import package_path -from repoze.bfg.resource import resolve_resource_spec from repoze.bfg.resource import resource_spec_from_abspath -from repoze.bfg.static import PackageURLParser +from repoze.bfg.static import static_view as static # B/C from repoze.bfg.threadlocal import get_current_registry # b/c imports @@ -51,6 +47,8 @@ deprecated('NotFound', "repoze.bfg.exceptions import NotFound')", ) +static = static # dont yet deprecate this (ever?) + _marker = object() def render_view_to_response(context, request, name='', secure=True): @@ -166,67 +164,6 @@ def is_response(ob): return True return False -class static(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, 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) - class bfg_view(object): """ A function, class or method :term:`decorator` which allows a developer to create view registrations nearer to a :term:`view diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py index 3e8bb0c50..d55dd1d1d 100644 --- a/repoze/bfg/zcml.py +++ b/repoze/bfg/zcml.py @@ -32,7 +32,7 @@ from repoze.bfg.exceptions import NotFound from repoze.bfg.exceptions import Forbidden from repoze.bfg.request import route_request_iface from repoze.bfg.resource import resource_spec_from_abspath -from repoze.bfg.static import StaticRootFactory +from repoze.bfg.static import StaticURLInfo from repoze.bfg.threadlocal import get_current_registry ###################### directives ########################## @@ -558,17 +558,19 @@ def static(_context, name, path, cache_max_age=3600): config = Configurator(reg, package=_context.package) _context.action( - discriminator = ('route', name, False, None, None, None, None, None), + discriminator=('static', name), callable=config.add_static_view, - args = (name, path, cache_max_age, _context.info), + args = (name, path), + kw = {'cache_max_age':cache_max_age, '_info':_context.info}, ) - _context.action( - discriminator = ( - 'view', StaticRootFactory, '', None, IView, None, None, None, - name, None, None, None, None, None, + if not '/' in name: + _context.action( + discriminator = ( + 'view', StaticURLInfo, '', None, IView, None, None, None, + name, None, None, None, None, None, + ) ) - ) class IScanDirective(Interface): package = GlobalObject( |
