Chris McDonough
2012-02-14 0dd383d5c160460a66cef5fce46a4d4e7c6fe167
Merge branch 'master' into 1.3-branch
7 files modified
176 ■■■■■ changed files
CHANGES.txt 6 ●●●●● patch | view | raw | blame | history
docs/designdefense.rst 4 ●●●● patch | view | raw | blame | history
docs/narr/views.rst 2 ●●● patch | view | raw | blame | history
pyramid/config/util.py 5 ●●●●● patch | view | raw | blame | history
pyramid/config/views.py 32 ●●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_views.py 125 ●●●●● patch | view | raw | blame | history
pyramid/urldispatch.py 2 ●●● patch | view | raw | blame | history
CHANGES.txt
@@ -11,6 +11,12 @@
  http://readthedocs.org/docs/venusian/en/latest/#ignore-scan-argument for
  more information about how to use the ``ignore`` argument to ``scan``.
- Better error messages when a view callable returns a value that cannot be
  converted to a response (for example, when a view callable returns a
  dictionary without a renderer defined, or doesn't return any value at all).
  The error message now contains information about the view callable itself
  as well as the result of calling it.
Dependencies
------------
docs/designdefense.rst
@@ -219,7 +219,7 @@
Using such wrappers, we strive to always hide the ZCA API from application
developers.  Application developers should just never know about the ZCA API:
they should call a Python function with some object germane to the domain as
an argument, and it should returns a result.  A corollary that follows is
an argument, and it should return a result.  A corollary that follows is
that any reader of an application that has been written using :app:`Pyramid`
needn't understand the ZCA API either.
@@ -720,7 +720,7 @@
The :mod:`zope.component`, package on which :app:`Pyramid` depends has
transitive dependencies on several other packages (:mod:`zope.event`, and
:mod:`zope.interface`).  :app:`Pyramid` also has its own direct dependencies,
such as :term:`PasteDeploy`, :term:`Chameleon`, :term:`Mako` :term:`WebOb`,
such as :term:`PasteDeploy`, :term:`Chameleon`, :term:`Mako`, :term:`WebOb`,
:mod:`zope.deprecation` and some of these in turn have their own transitive
dependencies.
docs/narr/views.rst
@@ -30,7 +30,7 @@
View callables are, at the risk of sounding obvious, callable Python
objects. Specifically, view callables can be functions, classes, or instances
that implement an ``__call__`` method (making the instance callable).
that implement a ``__call__`` method (making the instance callable).
View callables must, at a minimum, accept a single argument named
``request``.  This argument represents a :app:`Pyramid` :term:`Request`
pyramid/config/util.py
@@ -6,7 +6,6 @@
from pyramid.interfaces import IActionInfo
from pyramid.compat import (
    string_types,
    bytes_,
    is_nonstr_iter,
    )
@@ -44,7 +43,7 @@
            self._ainfo = []
        info = kw.pop('_info', None)
        # backframes for outer decorators to actionmethods
        backframes = kw.pop('_backframes', 2)
        backframes = kw.pop('_backframes', 2)
        if is_nonstr_iter(info) and len(info) == 4:
            # _info permitted as extract_stack tuple
            info = ActionInfo(*info)
@@ -132,7 +131,7 @@
        request_method = sorted(request_method)
        def request_method_predicate(context, request):
            return request.method in request_method
        text = "request method = %s" % repr(request_method)
        text = "request method = %r" % request_method
        request_method_predicate.__text__ = text
        weights.append(1 << 2)
        predicates.append(request_method_predicate)
pyramid/config/views.py
@@ -55,6 +55,7 @@
from pyramid.static import static_view
from pyramid.threadlocal import get_current_registry
from pyramid.view import render_view_to_response
from pyramid.util import object_description
from pyramid.config.util import (
    DEFAULT_PHASH,
@@ -66,6 +67,12 @@
urljoin = urlparse.urljoin
url_parse = urlparse.urlparse
def view_description(view):
    try:
        return view.__text__
    except AttributeError:
        return object_description(view)
def wraps_view(wrapper):
    def inner(self, view):
@@ -99,7 +106,7 @@
    # "wrapped view"
    for attr in ('__permitted__', '__call_permissive__', '__permission__',
                 '__predicated__', '__predicates__', '__accept__',
                 '__order__'):
                 '__order__', '__text__'):
        try:
            setattr(wrapper, attr, getattr(view, attr))
        except AttributeError:
@@ -343,9 +350,19 @@
            result = view(context, request)
            response = registry.queryAdapterOrSelf(result, IResponse)
            if response is None:
                raise ValueError(
                    'Could not convert view return value "%s" into a '
                    'response object' % (result,))
                if result is None:
                    append = (' You may have forgotten to return a value from '
                              'the view callable.')
                elif isinstance(result, dict):
                    append = (' You may have forgotten to define a renderer in '
                              'the view configuration.')
                else:
                    append = ''
                msg = ('Could not convert return value of the view callable %s '
                      'into a response object. '
                      'The value returned was %r.' + append)
                raise ValueError(msg % (view_description(view), result))
            return response
        return viewresult_to_response
@@ -376,6 +393,8 @@
            mapped_view = self.map_class_requestonly(view)
        else:
            mapped_view = self.map_class_native(view)
        mapped_view.__text__ = 'method %s of %s' % (
            self.attr or '__call__', object_description(view))
        return mapped_view
    def map_nonclass(self, view):
@@ -388,6 +407,11 @@
            mapped_view = self.map_nonclass_requestonly(view)
        elif self.attr:
            mapped_view = self.map_nonclass_attr(view)
        if self.attr is not None:
            mapped_view.__text__ = 'attr %s of %s' % (
                self.attr, object_description(view))
        else:
            mapped_view.__text__ = object_description(view)
        return mapped_view
    def map_class_requestonly(self, view):
pyramid/tests/test_config/test_views.py
@@ -2282,6 +2282,113 @@
        self.config.registry.registerUtility(policy, IAuthenticationPolicy)
        self.config.registry.registerUtility(policy, IAuthorizationPolicy)
    def test_function_returns_non_adaptable(self):
        def view(request):
            return None
        deriver = self._makeOne()
        result = deriver(view)
        self.assertFalse(result is view)
        try:
            result(None, None)
        except ValueError as e:
            self.assertEqual(
                e.args[0],
                'Could not convert return value of the view callable function '
                'pyramid.tests.test_config.test_views.view into a response '
                'object. The value returned was None. You may have forgotten '
                'to return a value from the view callable.'
                )
        else: # pragma: no cover
            raise AssertionError
    def test_function_returns_non_adaptable_dict(self):
        def view(request):
            return {'a':1}
        deriver = self._makeOne()
        result = deriver(view)
        self.assertFalse(result is view)
        try:
            result(None, None)
        except ValueError as e:
            self.assertEqual(
                e.args[0],
                "Could not convert return value of the view callable function "
                "pyramid.tests.test_config.test_views.view into a response "
                "object. The value returned was {'a': 1}. You may have "
                "forgotten to define a renderer in the view configuration."
                )
        else: # pragma: no cover
            raise AssertionError
    def test_instance_returns_non_adaptable(self):
        class AView(object):
            def __call__(self, request):
                return None
        view = AView()
        deriver = self._makeOne()
        result = deriver(view)
        self.assertFalse(result is view)
        try:
            result(None, None)
        except ValueError as e:
            msg = e.args[0]
            self.assertTrue(msg.startswith(
                'Could not convert return value of the view callable object '
                '<pyramid.tests.test_config.test_views.AView object at'))
            self.assertTrue(msg.endswith(
                '> into a response object. The value returned was None. You '
                'may have forgotten to return a value from the view callable.'))
        else: # pragma: no cover
            raise AssertionError
    def test_requestonly_default_method_returns_non_adaptable(self):
        request = DummyRequest()
        class AView(object):
            def __init__(self, request):
                pass
            def __call__(self):
                return None
        deriver = self._makeOne()
        result = deriver(AView)
        self.assertFalse(result is AView)
        try:
            result(None, request)
        except ValueError as e:
            self.assertEqual(
                e.args[0],
                'Could not convert return value of the view callable '
                'method __call__ of '
                'class pyramid.tests.test_config.test_views.AView into a '
                'response object. The value returned was None. You may have '
                'forgotten to return a value from the view callable.'
                )
        else: # pragma: no cover
            raise AssertionError
    def test_requestonly_nondefault_method_returns_non_adaptable(self):
        request = DummyRequest()
        class AView(object):
            def __init__(self, request):
                pass
            def theviewmethod(self):
                return None
        deriver = self._makeOne(attr='theviewmethod')
        result = deriver(AView)
        self.assertFalse(result is AView)
        try:
            result(None, request)
        except ValueError as e:
            self.assertEqual(
                e.args[0],
                'Could not convert return value of the view callable '
                'method theviewmethod of '
                'class pyramid.tests.test_config.test_views.AView into a '
                'response object. The value returned was None. You may have '
                'forgotten to return a value from the view callable.'
                )
        else: # pragma: no cover
            raise AssertionError
    def test_requestonly_function(self):
        response = DummyResponse()
        def view(request):
@@ -3689,6 +3796,24 @@
                 view_attr='attr')
        self.assertEqual(config.view_kw['attr'], 'attr')
class Test_view_description(unittest.TestCase):
    def _callFUT(self, view):
        from pyramid.config.views import view_description
        return view_description(view)
    def test_with_text(self):
        def view(): pass
        view.__text__ = 'some text'
        result = self._callFUT(view)
        self.assertEqual(result, 'some text')
    def test_without_text(self):
        def view(): pass
        result = self._callFUT(view)
        self.assertEqual(result,
                         'function pyramid.tests.test_config.test_views.view')
class DummyRegistry:
    pass
pyramid/urldispatch.py
@@ -14,13 +14,13 @@
    string_types,
    binary_type,
    is_nonstr_iter,
    decode_path_info,
    )
from pyramid.exceptions import URLDecodeError
from pyramid.traversal import (
    quote_path_segment,
    decode_path_info,
    split_path_info,
    )