summaryrefslogtreecommitdiff
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
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.
-rw-r--r--CHANGES.txt27
-rw-r--r--docs/narr/static.rst106
-rw-r--r--docs/whatsnew-1.3.rst9
-rw-r--r--repoze/bfg/configuration.py97
-rw-r--r--repoze/bfg/interfaces.py10
-rw-r--r--repoze/bfg/static.py121
-rw-r--r--repoze/bfg/tests/test_configuration.py60
-rw-r--r--repoze/bfg/tests/test_static.py193
-rw-r--r--repoze/bfg/tests/test_url.py79
-rw-r--r--repoze/bfg/tests/test_view.py79
-rw-r--r--repoze/bfg/tests/test_zcml.py14
-rw-r--r--repoze/bfg/url.py42
-rw-r--r--repoze/bfg/view.py69
-rw-r--r--repoze/bfg/zcml.py18
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(