summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt9
-rw-r--r--docs/api/view.rst12
-rw-r--r--docs/index.rst1
-rw-r--r--repoze/bfg/interfaces.py1
-rw-r--r--repoze/bfg/router.py47
-rw-r--r--repoze/bfg/security.py16
-rw-r--r--repoze/bfg/tests/test_router.py16
-rw-r--r--repoze/bfg/tests/test_view.py284
-rw-r--r--repoze/bfg/view.py71
9 files changed, 408 insertions, 49 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 78bd4bb2f..5fe8c0fdf 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,7 +1,14 @@
+Next release
+
Features
- Event notification is issued after application is created and
- configured.
+ configured (``IWSGIApplicationCreatedEvent``).
+
+ - New API module: ``repoze.bfg.view``. This module contains the functions
+ named ``render_view_to_response``, ``render_view_to_iterable`` and
+ ``is_response``, which are documented in the API docs. These features
+ aid programmatic (non-request-driven) view execution.
0.3.4 (08/28/2008)
diff --git a/docs/api/view.rst b/docs/api/view.rst
new file mode 100644
index 000000000..d7d238154
--- /dev/null
+++ b/docs/api/view.rst
@@ -0,0 +1,12 @@
+.. _view_module:
+
+:mod:`repoze.bfg.view`
+----------------------
+
+.. automodule:: repoze.bfg.view
+
+ .. autofunction:: render_view_to_response
+
+ .. autofunction:: render_view_to_iterable
+
+ .. autofunction:: is_response
diff --git a/docs/index.rst b/docs/index.rst
index b4bbe4398..3d18c115f 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -61,6 +61,7 @@ Per-module :mod:`repoze.bfg` API documentation.
api/template
api/traversal
api/urldispatch
+ api/view
api/wsgi
Sample Applications
diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py
index db2d1622d..6c28c75a7 100644
--- a/repoze/bfg/interfaces.py
+++ b/repoze/bfg/interfaces.py
@@ -1,4 +1,3 @@
-from zope.interface import implements
from zope.interface import Interface
from zope.interface import Attribute
diff --git a/repoze/bfg/router.py b/repoze/bfg/router.py
index 26a313d8a..ab8850110 100644
--- a/repoze/bfg/router.py
+++ b/repoze/bfg/router.py
@@ -1,6 +1,4 @@
from zope.component import getAdapter
-from zope.component import queryMultiAdapter
-from zope.component import queryUtility
from zope.component.event import dispatch
from zope.interface import directlyProvides
@@ -13,14 +11,16 @@ from repoze.bfg.events import NewResponse
from repoze.bfg.events import WSGIApplicationCreatedEvent
from repoze.bfg.interfaces import ITraverserFactory
-from repoze.bfg.interfaces import IView
-from repoze.bfg.interfaces import IViewPermission
-from repoze.bfg.interfaces import ISecurityPolicy
from repoze.bfg.interfaces import IRequest
from repoze.bfg.registry import registry_manager
from repoze.bfg.registry import makeRegistry
+from repoze.bfg.security import Unauthorized
+
+from repoze.bfg.view import is_response
+from repoze.bfg.view import render_view_to_response
+
_marker = ()
class Router:
@@ -43,40 +43,27 @@ class Router:
request.view_name = name
request.subpath = subpath
- security_policy = queryUtility(ISecurityPolicy)
- if security_policy:
- permission = queryMultiAdapter((context, request), IViewPermission,
- name=name)
- if permission is not None:
- if not permission(security_policy):
- app = HTTPUnauthorized()
- app.explanation = repr(permission)
- return app(environ, start_response)
-
- response = queryMultiAdapter((context, request), IView, name=name,
- default=_marker)
- if response is _marker:
+ try:
+ response = render_view_to_response(context, request, name,
+ secure=True)
+ except Unauthorized, why:
+ app = HTTPUnauthorized()
+ app.explanation = str(why)
+ return app(environ, start_response)
+
+ if response is None:
app = HTTPNotFound(request.url)
return app(environ, start_response)
- if not isResponse(response):
- raise ValueError('response was not IResponse: %s' % response)
+ if not is_response(response):
+ raise ValueError('response did not implement IResponse: %r'
+ % response)
dispatch(NewResponse(response))
start_response(response.status, response.headerlist)
return response.app_iter
-def isResponse(ob):
- # response objects aren't obligated to implement a Zope interface,
- # so we do it the hard way
- if ( hasattr(ob, 'app_iter') and hasattr(ob, 'headerlist') and
- hasattr(ob, 'status') ):
- if ( hasattr(ob.app_iter, '__iter__') and
- hasattr(ob.headerlist, '__iter__') and
- isinstance(ob.status, basestring) ) :
- return True
-
def make_app(root_policy, package=None, filename='configure.zcml',
options=None):
""" Create a view registry based on the application's ZCML. and
diff --git a/repoze/bfg/security.py b/repoze/bfg/security.py
index 6535f2608..226cf8664 100644
--- a/repoze/bfg/security.py
+++ b/repoze/bfg/security.py
@@ -203,7 +203,7 @@ class PermitsResult:
self.context_repr = repr(context)
def __str__(self):
- msg = '%s: %r via ace %r in acl %s or principals %r in context %s'
+ msg = '%s: %r via ace %r in acl %r or principals %r in context %s'
msg = msg % (self.__class__.__name__,
self.permission, self.ace, self.acl, self.principals,
self.context_repr)
@@ -219,8 +219,7 @@ class Denied(PermitsResult):
return False
def __eq__(self, other):
- if bool(other) is False:
- return True
+ return bool(other) is False
class Allowed(PermitsResult):
""" An instance of ``Allowed`` is returned by an ACL allow. It
@@ -232,8 +231,7 @@ class Allowed(PermitsResult):
return True
def __eq__(self, other):
- if bool(other) is True:
- return True
+ return bool(other) is True
def flatten(x):
"""flatten(sequence) -> list
@@ -281,4 +279,12 @@ class ViewPermissionFactory(object):
def __call__(self, context, request):
return ViewPermission(context, request, self.permission_name)
+class Unauthorized(Exception):
+ def __init__(self, message='Unauthorized'):
+ self.message = message
+
+ def __str__(self):
+ return str(self.message)
+
+
diff --git a/repoze/bfg/tests/test_router.py b/repoze/bfg/tests/test_router.py
index ed08710f1..a1c2b0cfb 100644
--- a/repoze/bfg/tests/test_router.py
+++ b/repoze/bfg/tests/test_router.py
@@ -154,7 +154,6 @@ class RouterTests(unittest.TestCase, PlacelessSetup):
environ = self._makeEnviron()
self._registerTraverserFactory(traversalfactory, '', None)
self._registerView(view, '', IContext, IRequest)
- app_context = make_appcontext()
router = self._makeOne(rootpolicy, None)
start_response = DummyStartResponse()
result = router(environ, start_response)
@@ -178,7 +177,6 @@ class RouterTests(unittest.TestCase, PlacelessSetup):
self._registerView(view, '', IContext, IRequest)
secpol = DummySecurityPolicy()
self._registerSecurityPolicy(secpol)
- app_context = make_appcontext()
router = self._makeOne(rootpolicy, None)
start_response = DummyStartResponse()
result = router(environ, start_response)
@@ -203,7 +201,6 @@ class RouterTests(unittest.TestCase, PlacelessSetup):
self._registerView(view, '', IContext, IRequest)
self._registerSecurityPolicy(secpol)
self._registerPermission(permissionfactory, '', IContext, IRequest)
- app_context = make_appcontext()
router = self._makeOne(rootpolicy, None)
start_response = DummyStartResponse()
result = router(environ, start_response)
@@ -223,13 +220,15 @@ class RouterTests(unittest.TestCase, PlacelessSetup):
response = DummyResponse()
view = make_view(response)
secpol = DummySecurityPolicy()
- permissionfactory = make_permission_factory(False)
+ from repoze.bfg.security import Denied
+ permissionfactory = make_permission_factory(
+ Denied('ace', 'acl', 'permission', ['principals'], context)
+ )
environ = self._makeEnviron()
self._registerTraverserFactory(traversalfactory, '', None)
self._registerView(view, '', IContext, IRequest)
self._registerSecurityPolicy(secpol)
self._registerPermission(permissionfactory, '', IContext, IRequest)
- app_context = make_appcontext()
router = self._makeOne(rootpolicy, None)
start_response = DummyStartResponse()
result = router(environ, start_response)
@@ -364,13 +363,6 @@ def make_rootpolicy(root):
return root
return rootpolicy
-def make_appcontext():
- from zope.configuration.interfaces import IConfigurationContext
- from zope.interface import directlyProvides
- app_context = DummyContext()
- directlyProvides(app_context, IConfigurationContext)
- return app_context
-
class DummyStartResponse:
status = ()
headers = ()
diff --git a/repoze/bfg/tests/test_view.py b/repoze/bfg/tests/test_view.py
new file mode 100644
index 000000000..3b776b14f
--- /dev/null
+++ b/repoze/bfg/tests/test_view.py
@@ -0,0 +1,284 @@
+import unittest
+
+from zope.component.testing import PlacelessSetup
+
+class BaseTest(PlacelessSetup):
+ def setUp(self):
+ PlacelessSetup.setUp(self)
+
+ def tearDown(self):
+ PlacelessSetup.tearDown(self)
+
+ def _registerView(self, app, name, *for_):
+ import zope.component
+ gsm = zope.component.getGlobalSiteManager()
+ from repoze.bfg.interfaces import IView
+ gsm.registerAdapter(app, for_, IView, name)
+
+ def _registerPermission(self, permission, name, *for_):
+ import zope.component
+ gsm = zope.component.getGlobalSiteManager()
+ from repoze.bfg.interfaces import IViewPermission
+ gsm.registerAdapter(permission, for_, IViewPermission, name)
+
+ def _registerSecurityPolicy(self, secpol):
+ import zope.component
+ gsm = zope.component.getGlobalSiteManager()
+ from repoze.bfg.interfaces import ISecurityPolicy
+ gsm.registerUtility(secpol, ISecurityPolicy)
+
+ def _makeEnviron(self, **extras):
+ environ = {
+ 'wsgi.url_scheme':'http',
+ 'SERVER_NAME':'localhost',
+ 'SERVER_PORT':'8080',
+ 'REQUEST_METHOD':'GET',
+ }
+ environ.update(extras)
+ return environ
+
+class RenderViewToResponseTests(unittest.TestCase, BaseTest):
+ def _getFUT(self):
+ from repoze.bfg.view import render_view_to_response
+ return render_view_to_response
+
+ def test_call_no_view_registered(self):
+ environ = self._makeEnviron()
+ from webob import Request
+ request = Request(environ)
+ context = DummyContext()
+ renderer = self._getFUT()
+ result = renderer(context, request, name='notregistered')
+ self.assertEqual(result, None)
+
+ def test_call_view_registered_secure_permission_disallows(self):
+ context = DummyContext()
+ from zope.interface import Interface
+ from zope.interface import directlyProvides
+ from repoze.bfg.interfaces import IRequest
+ class IContext(Interface):
+ pass
+ directlyProvides(context, IContext)
+ response = DummyResponse()
+ secpol = DummySecurityPolicy()
+ permissionfactory = make_permission_factory(False)
+ view = make_view(response)
+ self._registerView(view, 'registered', IContext, IRequest)
+ self._registerSecurityPolicy(secpol)
+ self._registerPermission(permissionfactory, 'registered', IContext,
+ IRequest)
+ environ = self._makeEnviron()
+ from webob import Request
+ request = Request(environ)
+ directlyProvides(request, IRequest)
+ renderer = self._getFUT()
+ from repoze.bfg.security import Unauthorized
+ self.assertRaises(Unauthorized, renderer, context, request,
+ name='registered', secure=True)
+
+ def test_call_view_registered_secure_permission_allows(self):
+ context = DummyContext()
+ from zope.interface import Interface
+ from zope.interface import directlyProvides
+ from repoze.bfg.interfaces import IRequest
+ class IContext(Interface):
+ pass
+ directlyProvides(context, IContext)
+ response = DummyResponse()
+ secpol = DummySecurityPolicy()
+ permissionfactory = make_permission_factory(True)
+ view = make_view(response)
+ self._registerView(view, 'registered', IContext, IRequest)
+ self._registerSecurityPolicy(secpol)
+ self._registerPermission(permissionfactory, 'registered', IContext,
+ IRequest)
+ environ = self._makeEnviron()
+ from webob import Request
+ request = Request(environ)
+ directlyProvides(request, IRequest)
+ renderer = self._getFUT()
+ response = renderer(context, request, name='registered', secure=True)
+ self.assertEqual(response.status, '200 OK')
+
+ def test_call_view_registered_insecure_permission_disallows(self):
+ context = DummyContext()
+ from zope.interface import Interface
+ from zope.interface import directlyProvides
+ from repoze.bfg.interfaces import IRequest
+ class IContext(Interface):
+ pass
+ directlyProvides(context, IContext)
+ response = DummyResponse()
+ secpol = DummySecurityPolicy()
+ permissionfactory = make_permission_factory(False)
+ view = make_view(response)
+ self._registerView(view, 'registered', IContext, IRequest)
+ self._registerSecurityPolicy(secpol)
+ self._registerPermission(permissionfactory, 'registered', IContext,
+ IRequest)
+ environ = self._makeEnviron()
+ from webob import Request
+ request = Request(environ)
+ directlyProvides(request, IRequest)
+ renderer = self._getFUT()
+ response = renderer(context, request, name='registered', secure=False)
+ self.assertEqual(response.status, '200 OK')
+
+class RenderViewToIterableTests(unittest.TestCase, BaseTest):
+ def _getFUT(self):
+ from repoze.bfg.view import render_view_to_iterable
+ return render_view_to_iterable
+
+ def test_call_no_view_registered(self):
+ environ = self._makeEnviron()
+ from webob import Request
+ request = Request(environ)
+ context = DummyContext()
+ renderer = self._getFUT()
+ result = renderer(context, request, name='notregistered')
+ self.assertEqual(result, None)
+
+ def test_call_view_registered_secure_permission_disallows(self):
+ context = DummyContext()
+ from zope.interface import Interface
+ from zope.interface import directlyProvides
+ from repoze.bfg.interfaces import IRequest
+ class IContext(Interface):
+ pass
+ directlyProvides(context, IContext)
+ response = DummyResponse()
+ secpol = DummySecurityPolicy()
+ permissionfactory = make_permission_factory(False)
+ view = make_view(response)
+ self._registerView(view, 'registered', IContext, IRequest)
+ self._registerSecurityPolicy(secpol)
+ self._registerPermission(permissionfactory, 'registered', IContext,
+ IRequest)
+ environ = self._makeEnviron()
+ from webob import Request
+ request = Request(environ)
+ directlyProvides(request, IRequest)
+ renderer = self._getFUT()
+ from repoze.bfg.security import Unauthorized
+ self.assertRaises(Unauthorized, renderer, context, request,
+ name='registered', secure=True)
+
+ def test_call_view_registered_secure_permission_allows(self):
+ context = DummyContext()
+ from zope.interface import Interface
+ from zope.interface import directlyProvides
+ from repoze.bfg.interfaces import IRequest
+ class IContext(Interface):
+ pass
+ directlyProvides(context, IContext)
+ response = DummyResponse()
+ secpol = DummySecurityPolicy()
+ permissionfactory = make_permission_factory(True)
+ view = make_view(response)
+ self._registerView(view, 'registered', IContext, IRequest)
+ self._registerSecurityPolicy(secpol)
+ self._registerPermission(permissionfactory, 'registered', IContext,
+ IRequest)
+ environ = self._makeEnviron()
+ from webob import Request
+ request = Request(environ)
+ directlyProvides(request, IRequest)
+ renderer = self._getFUT()
+ iterable = renderer(context, request, name='registered', secure=True)
+ self.assertEqual(iterable, ())
+
+ def test_call_view_registered_insecure_permission_disallows(self):
+ context = DummyContext()
+ from zope.interface import Interface
+ from zope.interface import directlyProvides
+ from repoze.bfg.interfaces import IRequest
+ class IContext(Interface):
+ pass
+ directlyProvides(context, IContext)
+ response = DummyResponse()
+ secpol = DummySecurityPolicy()
+ permissionfactory = make_permission_factory(False)
+ view = make_view(response)
+ self._registerView(view, 'registered', IContext, IRequest)
+ self._registerSecurityPolicy(secpol)
+ self._registerPermission(permissionfactory, 'registered', IContext,
+ IRequest)
+ environ = self._makeEnviron()
+ from webob import Request
+ request = Request(environ)
+ directlyProvides(request, IRequest)
+ renderer = self._getFUT()
+ iterable = renderer(context, request, name='registered', secure=False)
+ self.assertEqual(iterable, ())
+
+ def test_call_view_response_doesnt_implement_IResponse(self):
+ context = DummyContext()
+ from zope.interface import Interface
+ from zope.interface import directlyProvides
+ from repoze.bfg.interfaces import IRequest
+ class IContext(Interface):
+ pass
+ directlyProvides(context, IContext)
+ response = 'abc'
+ view = make_view(response)
+ self._registerView(view, 'registered', IContext, IRequest)
+ environ = self._makeEnviron()
+ from webob import Request
+ request = Request(environ)
+ directlyProvides(request, IRequest)
+ renderer = self._getFUT()
+ self.assertRaises(ValueError, renderer, context, request,
+ name='registered', secure=False)
+
+class TestIsResponse(unittest.TestCase):
+ def _getFUT(self):
+ from repoze.bfg.view import is_response
+ return is_response
+
+ def test_is(self):
+ response = DummyResponse()
+ f = self._getFUT()
+ self.assertEqual(f(response), True)
+
+ def test_isnt(self):
+ response = None
+ f = self._getFUT()
+ self.assertEqual(f(response), False)
+
+class DummyContext:
+ pass
+
+def make_view(response):
+ def view(context, request):
+ return response
+ return view
+
+def make_permission_factory(result):
+ class DummyPermissionFactory:
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def __call__(self, secpol):
+ self.__class__.checked_with = secpol
+ return result
+
+ def __repr__(self):
+ return 'permission'
+ return DummyPermissionFactory
+
+def make_appcontext():
+ from zope.configuration.interfaces import IConfigurationContext
+ from zope.interface import directlyProvides
+ app_context = DummyContext()
+ directlyProvides(app_context, IConfigurationContext)
+ return app_context
+
+class DummyResponse:
+ status = '200 OK'
+ headerlist = ()
+ app_iter = ()
+
+class DummySecurityPolicy:
+ pass
+
diff --git a/repoze/bfg/view.py b/repoze/bfg/view.py
new file mode 100644
index 000000000..180c020a0
--- /dev/null
+++ b/repoze/bfg/view.py
@@ -0,0 +1,71 @@
+from zope.component import queryMultiAdapter
+from zope.component import queryUtility
+
+from repoze.bfg.interfaces import ISecurityPolicy
+from repoze.bfg.interfaces import IViewPermission
+from repoze.bfg.interfaces import IView
+from repoze.bfg.security import Unauthorized
+
+def render_view_to_response(context, request, name='', secure=True):
+ """ Render the view named ``name`` against the specified
+ ``context`` and ``request`` to an object implementing
+ ``repoze.bfg.interfaces.IResponse`` or ``None`` if no such view
+ exists. This function will return ``None`` if a corresponding
+ view cannot be found. If ``secure`` is ``True``, and the view is
+ protected by a permission, the permission will be checked before
+ calling the view function. If the permission check disallows view
+ execution (based on the current security policy), a
+ ``repoze.bfg.security.Unauthorized`` exception will be raised; its
+ ``message`` attribute explains why the view access was disallowed.
+ If ``secure`` is ``False``, no permission checking is done."""
+ if secure:
+ security_policy = queryUtility(ISecurityPolicy)
+ if security_policy:
+ permission = queryMultiAdapter((context, request), IViewPermission,
+ name=name)
+ if permission is not None:
+ result = permission(security_policy)
+ if not result:
+ raise Unauthorized(result)
+ return queryMultiAdapter((context, request), IView, name=name)
+
+def render_view_to_iterable(context, request, name='', secure=True):
+ """ Render the view named ``name`` against the specified
+ ``context`` and ``request``, and return an iterable representing
+ the view response's ``app_iter`` (see the interface named
+ ``repoze.bfg.interfaces.IResponse``). This function will return
+ ``None`` if a corresponding view cannot be found. Additionally,
+ this function will raise a ``ValueError`` if a view function is
+ found and called but the view does not return an object which
+ implements ``repoze.bfg.interfaces.IResponse``. You can usually
+ get the string representation of the return value of this function
+ by calling ``''.join(iterable)``. If ``secure`` is ``True``, and
+ the view is protected by a permission, the permission will be
+ checked before calling the view function. If the permission check
+ disallows view execution (based on the current security policy), a
+ ``repoze.bfg.security.Unauthorized`` exception will be raised; its
+ ``message`` attribute explains why the view access was disallowed.
+ If ``secure`` is ``False``, no permission checking is done."""
+ response = render_view_to_response(context, request, name, secure)
+ if response is None:
+ return None
+ if not is_response(response):
+ raise ValueError('response did not implement IResponse: %r' % response)
+ return response.app_iter
+
+def is_response(ob):
+ """ Return True if ``ob`` implements the
+ ``repoze.bfg.interfaces.IResponse`` interface, False if not. Note
+ that this isn't actually a true Zope interface check, it's a
+ duck-typing check, as response objects are not obligated to
+ actually implement a Zope interface."""
+ # response objects aren't obligated to implement a Zope interface,
+ # so we do it the hard way
+ if ( hasattr(ob, 'app_iter') and hasattr(ob, 'headerlist') and
+ hasattr(ob, 'status') ):
+ if ( hasattr(ob.app_iter, '__iter__') and
+ hasattr(ob.headerlist, '__iter__') and
+ isinstance(ob.status, basestring) ) :
+ return True
+ return False
+