Chris McDonough
2011-01-16 e333c2c2236cbfd11809ee393aa71be1b4846d88
Remove configurator.add_handler, handler-related functions and methods from pyramid.view, handler ZCML directive.  This functionality is to be moved to a "pyramid_handlers" package.

Fix add_directive to properly persist directives across configurator creations.
10 files modified
743 ■■■■■ changed files
CHANGES.txt 18 ●●●●● patch | view | raw | blame | history
docs/api/config.rst 2 ●●●●● patch | view | raw | blame | history
docs/zcml.rst 1 ●●●● patch | view | raw | blame | history
pyramid/config.py 166 ●●●●● patch | view | raw | blame | history
pyramid/includes/meta.zcml 6 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_config.py 369 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_view.py 22 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_zcml.py 80 ●●●●● patch | view | raw | blame | history
pyramid/view.py 11 ●●●●● patch | view | raw | blame | history
pyramid/zcml.py 68 ●●●●● patch | view | raw | blame | history
CHANGES.txt
@@ -7,6 +7,24 @@
- URL Dispatch properly handles a '.*' or '*' appearing in a regex match
  when used inside brackets. Resolve Issue #90.
Backwards Incompatibilities
---------------------------
- The ``add_handler`` method of a Configurator has been removed from the
  Pyramid core.  Handlers are now a feature of the ``pyramid_handlers``
  package, which can be downloaded from PyPI.  Documentation for the package
  should be available via http://pylonsproject.org, which describes how to
  get this method back after depending upon ``pyramid_handlers`` as an
  ``install_requires`` dependency.
- The ``pyramid.view.action`` decorator has been removed from the Pyramid
  core.  Handlers are now a feature of the ``pyramid_handlers`` package.  It
  should now be imported from ``pyramid_handlers`` e.g. ``from
  pyramid_handlers import action``.
- The ``handler`` ZCML directive has been removed.  It is now a feature of
  the ``pyramid_handlers`` package.
Features
--------
docs/api/config.rst
@@ -48,8 +48,6 @@
     .. automethod:: add_translation_dirs
     .. automethod:: add_handler
     .. automethod:: add_view
     .. automethod:: derive_view
docs/zcml.rst
@@ -17,7 +17,6 @@
   zcml/configure
   zcml/default_permission
   zcml/forbidden
   zcml/handler
   zcml/include
   zcml/localenegotiator
   zcml/notfound
pyramid/config.py
@@ -303,9 +303,6 @@
                session_factory=session_factory,
                default_view_mapper=default_view_mapper,
                )
        if hasattr(registry, '_directives'):
            for name, directive in registry._directives.items():
                self.add_directive(name, directive)
    def _set_settings(self, mapping):
        settings = Settings(mapping or {})
@@ -599,9 +596,16 @@
        if not hasattr(self.registry, '_directives'):
            self.registry._directives = {}
        self.registry._directives[name] = c
    def __getattr__(self, name):
        # allow directive extension names to work
        directives = getattr(self.registry, '_directives', {})
        c = directives.get(name)
        if c is None:
            raise AttributeError(name)
        c = action_method(c)
        m = types.MethodType(c, self, self.__class__)
        setattr(self, name, m)
        return m
    @classmethod
    def with_context(cls, context):
@@ -955,136 +959,6 @@
            lock.release()
            self.manager.pop()
        return registry
    @action_method
    def add_handler(self, route_name, pattern, handler, action=None, **kw):
        """ Add a Pylons-style view handler.  This function adds a
        route and some number of views based on a handler object
        (usually a class).
        ``route_name`` is the name of the route (to be used later in
        URL generation).
        ``pattern`` is the matching pattern,
        e.g. ``'/blog/{action}'``.  ``pattern`` may be ``None``, in
        which case the pattern of an existing route named the same as
        ``route_name`` is used.  If ``pattern`` is ``None`` and no
        route named ``route_name`` exists, a ``ConfigurationError`` is
        raised.
        ``handler`` is a dotted name of (or direct reference to) a
        Python handler class,
        e.g. ``'my.package.handlers.MyHandler'``.
        If ``{action}`` or ``:action`` is in
        the pattern, the exposed methods of the handler will be used
        as views.
        If ``action`` is passed, it will be considered the method name
        of the handler to use as a view.
        Passing both ``action`` and having an ``{action}`` in the
        route pattern is disallowed.
        Any extra keyword arguments are passed along to ``add_route``.
        See :ref:`views_chapter` for more explanatory documentation.
        This method returns the result of add_route."""
        handler = self.maybe_dotted(handler)
        if pattern is not None:
            route = self.add_route(route_name, pattern, **kw)
        else:
            mapper = self.get_routes_mapper()
            route = mapper.get_route(route_name)
            if route is None:
                raise ConfigurationError(
                    'The "pattern" parameter may only be "None" when a route '
                    'with the route_name argument was previously registered. '
                    'No such route named %r exists' % route_name)
            pattern = route.pattern
        action_decorator = getattr(handler, '__action_decorator__', None)
        if action_decorator is not None:
            if hasattr(action_decorator, 'im_self'):
                # instance methods have an im_self == None
                # classmethods have an im_self == cls
                # staticmethods have no im_self
                # instances have no im_self
                if action_decorator.im_self is not handler:
                    raise ConfigurationError(
                        'The "__action_decorator__" attribute of a handler '
                        'must not be an instance method (must be a '
                        'staticmethod, classmethod, function, or an instance '
                        'which is a callable')
        path_has_action = ':action' in pattern or '{action}' in pattern
        if action and path_has_action:
            raise ConfigurationError(
                'action= (%r) disallowed when an action is in the route '
                'path %r' % (action, pattern))
        if path_has_action:
            autoexpose = getattr(handler, '__autoexpose__', r'[A-Za-z]+')
            if autoexpose:
                try:
                    autoexpose = re.compile(autoexpose).match
                except (re.error, TypeError), why:
                    raise ConfigurationError(why[0])
            for method_name, method in inspect.getmembers(
                handler, inspect.ismethod):
                configs = getattr(method, '__exposed__', [])
                if autoexpose and not configs:
                    if autoexpose(method_name):
                        configs = [{}]
                for expose_config in configs:
                    # we don't want to mutate any dict in __exposed__,
                    # so we copy each
                    view_args = expose_config.copy()
                    action = view_args.pop('name', method_name)
                    preds = list(view_args.pop('custom_predicates', []))
                    preds.append(ActionPredicate(action))
                    view_args['custom_predicates'] = preds
                    self.add_view(view=handler, attr=method_name,
                                  route_name=route_name,
                                  decorator=action_decorator, **view_args)
        else:
            method_name = action
            if method_name is None:
                method_name = '__call__'
            # Scan the controller for any other methods with this action name
            for meth_name, method in inspect.getmembers(
                handler, inspect.ismethod):
                configs = getattr(method, '__exposed__', [{}])
                for expose_config in configs:
                    # Don't re-register the same view if this method name is
                    # the action name
                    if meth_name == action:
                        continue
                    # We only reg a view if the name matches the action
                    if expose_config.get('name') != method_name:
                        continue
                    # we don't want to mutate any dict in __exposed__,
                    # so we copy each
                    view_args = expose_config.copy()
                    del view_args['name']
                    self.add_view(view=handler, attr=meth_name,
                                  route_name=route_name,
                                  decorator=action_decorator, **view_args)
            # Now register the method itself
            method = getattr(handler, method_name, None)
            configs = getattr(method, '__exposed__', [{}])
            for expose_config in configs:
                self.add_view(view=handler, attr=action, route_name=route_name,
                              decorator=action_decorator, **expose_config)
        return route
    @action_method
    def add_view(self, view=None, name="", for_=None, permission=None,
@@ -3117,30 +2991,6 @@
            return True
    return False
class ActionPredicate(object):
    action_name = 'action'
    def __init__(self, action):
        self.action = action
        try:
            self.action_re = re.compile(action + '$')
        except (re.error, TypeError), why:
            raise ConfigurationError(why[0])
    def __call__(self, context, request):
        matchdict = request.matchdict
        if matchdict is None:
            return False
        action = matchdict.get(self.action_name)
        if action is None:
            return False
        return bool(self.action_re.match(action))
    def __hash__(self):
        # allow this predicate's phash to be compared as equal to
        # others that share the same action name
        return hash(self.action)
class PyramidConfigurationMachine(ConfigurationMachine):
    autocommit = False
pyramid/includes/meta.zcml
@@ -35,12 +35,6 @@
        />
    <meta:directive
        name="handler"
        schema="pyramid.zcml.IHandlerDirective"
        handler="pyramid.zcml.handler"
        />
    <meta:directive
        name="asset"
        schema="pyramid.zcml.IAssetDirective"
        handler="pyramid.zcml.asset"
pyramid/tests/test_config.py
@@ -1875,278 +1875,6 @@
        request = self._makeRequest(config)
        self.assertEqual(view(None, request), 'OK')
    def test_add_handler_action_in_route_pattern(self):
        config = self._makeOne(autocommit=True)
        views = []
        def dummy_add_view(**kw):
            views.append(kw)
        config.add_view = dummy_add_view
        config.add_handler('name', '/:action', DummyHandler)
        self._assertRoute(config, 'name', '/:action', 0)
        self.assertEqual(len(views), 2)
        view = views[0]
        preds = view['custom_predicates']
        self.assertEqual(len(preds), 1)
        pred = preds[0]
        request = DummyRequest()
        self.assertEqual(pred(None, request), False)
        request.matchdict = {'action':'action1'}
        self.assertEqual(pred(None, request), True)
        self.assertEqual(view['route_name'], 'name')
        self.assertEqual(view['attr'], 'action1')
        self.assertEqual(view['view'], DummyHandler)
        view = views[1]
        preds = view['custom_predicates']
        self.assertEqual(len(preds), 1)
        pred = preds[0]
        request = DummyRequest()
        self.assertEqual(pred(None, request), False)
        request.matchdict = {'action':'action2'}
        self.assertEqual(pred(None, request), True)
        self.assertEqual(view['route_name'], 'name')
        self.assertEqual(view['attr'], 'action2')
        self.assertEqual(view['view'], DummyHandler)
    def test_add_handler_with_view_overridden_autoexpose_None(self):
        config = self._makeOne(autocommit=True)
        views = []
        def dummy_add_view(**kw):
            views.append(kw) # pragma: no cover
        config.add_view = dummy_add_view
        class MyView(DummyHandler):
            __autoexpose__ = None
        config.add_handler('name', '/:action', MyView)
        self._assertRoute(config, 'name', '/:action', 0)
        self.assertEqual(len(views), 0)
    def test_add_handler_with_view_overridden_autoexpose_broken_regex1(self):
        from pyramid.exceptions import ConfigurationError
        config = self._makeOne()
        def dummy_add_view(**kw):
            """ """
        config.add_view = dummy_add_view
        class MyView(DummyHandler):
            __autoexpose__ = 1
        self.assertRaises(ConfigurationError, config.add_handler,
                          'name', '/{action}', MyView)
    def test_add_handler_with_view_overridden_autoexpose_broken_regex2(self):
        from pyramid.exceptions import ConfigurationError
        config = self._makeOne()
        def dummy_add_view(**kw):
            """ """
        config.add_view = dummy_add_view
        class MyView(DummyHandler):
            __autoexpose__ = 'a\\'
        self.assertRaises(ConfigurationError, config.add_handler,
                          'name', '/{action}', MyView)
    def test_add_handler_with_view_method_has_expose_config(self):
        config = self._makeOne(autocommit=True)
        views = []
        def dummy_add_view(**kw):
            views.append(kw)
        config.add_view = dummy_add_view
        class MyView(object):
            def action(self): # pragma: no cover
                return 'response'
            action.__exposed__ = [{'custom_predicates':(1,)}]
        config.add_handler('name', '/:action', MyView)
        self._assertRoute(config, 'name', '/:action', 0)
        self.assertEqual(len(views), 1)
        view = views[0]
        preds = view['custom_predicates']
        self.assertEqual(len(preds), 2)
        self.assertEqual(view['route_name'], 'name')
        self.assertEqual(view['attr'], 'action')
        self.assertEqual(view['view'], MyView)
    def test_add_handler_with_view_method_has_expose_config_with_action(self):
        config = self._makeOne(autocommit=True)
        views = []
        def dummy_add_view(**kw):
            views.append(kw)
        config.add_view = dummy_add_view
        class MyView(object):
            def action(self): # pragma: no cover
                return 'response'
            action.__exposed__ = [{'name':'action3000'}]
        config.add_handler('name', '/:action', MyView)
        self._assertRoute(config, 'name', '/:action', 0)
        self.assertEqual(len(views), 1)
        view = views[0]
        preds = view['custom_predicates']
        self.assertEqual(len(preds), 1)
        pred = preds[0]
        request = DummyRequest()
        self.assertEqual(pred(None, request), False)
        request.matchdict = {'action':'action3000'}
        self.assertEqual(pred(None, request), True)
        self.assertEqual(view['route_name'], 'name')
        self.assertEqual(view['attr'], 'action')
        self.assertEqual(view['view'], MyView)
    def test_add_handler_with_view_method_has_expose_config_with_action_regex(
        self):
        config = self._makeOne(autocommit=True)
        views = []
        def dummy_add_view(**kw):
            views.append(kw)
        config.add_view = dummy_add_view
        class MyView(object):
            def action(self): # pragma: no cover
                return 'response'
            action.__exposed__ = [{'name':'^action3000$'}]
        config.add_handler('name', '/:action', MyView)
        self._assertRoute(config, 'name', '/:action', 0)
        self.assertEqual(len(views), 1)
        view = views[0]
        preds = view['custom_predicates']
        self.assertEqual(len(preds), 1)
        pred = preds[0]
        request = DummyRequest()
        self.assertEqual(pred(None, request), False)
        request.matchdict = {'action':'action3000'}
        self.assertEqual(pred(None, request), True)
        self.assertEqual(view['route_name'], 'name')
        self.assertEqual(view['attr'], 'action')
        self.assertEqual(view['view'], MyView)
    def test_add_handler_with_action_decorator(self):
        config = self._makeOne(autocommit=True)
        views = []
        def dummy_add_view(**kw):
            views.append(kw)
        config.add_view = dummy_add_view
        class MyHandler(object):
            @classmethod
            def __action_decorator__(cls, fn): # pragma: no cover
                return fn
            def action(self): # pragma: no cover
                return 'response'
        config.add_handler('name', '/{action}', MyHandler)
        self.assertEqual(len(views), 1)
        self.assertEqual(views[0]['decorator'], MyHandler.__action_decorator__)
    def test_add_handler_with_action_decorator_fail_on_instancemethod(self):
        config = self._makeOne(autocommit=True)
        class MyHandler(object):
            def __action_decorator__(self, fn): # pragma: no cover
                return fn
            def action(self): # pragma: no cover
                return 'response'
        from pyramid.exceptions import ConfigurationError
        self.assertRaises(ConfigurationError, config.add_handler,
                          'name', '/{action}', MyHandler)
    def test_add_handler_doesnt_mutate_expose_dict(self):
        config = self._makeOne(autocommit=True)
        views = []
        def dummy_add_view(**kw):
            views.append(kw)
        config.add_view = dummy_add_view
        exposed = [{'name':'^action3000$'}]
        class MyView(object):
            def action(self): # pragma: no cover
                return 'response'
            action.__exposed__ = exposed
        config.add_handler('name', '/{action}', MyView)
        self.assertEqual(exposed[0], {'name':'^action3000$'}) # not mutated
    def test_add_handler_with_action_and_action_in_path(self):
        from pyramid.exceptions import ConfigurationError
        config = self._makeOne()
        self.assertRaises(ConfigurationError, config.add_handler,
                          'name', '/{action}', DummyHandler, action='abc')
    def test_add_handler_with_explicit_action(self):
        config = self._makeOne(autocommit=True)
        class DummyHandler(object):
            def index(self): pass
            index.__exposed__ = [{'a':'1'}]
        views = []
        def dummy_add_view(**kw):
            views.append(kw)
        config.add_view = dummy_add_view
        config.add_handler('name', '/abc', DummyHandler, action='index')
        self.assertEqual(len(views), 1)
        view = views[0]
        self.assertEqual(view['a'], '1')
        self.assertEqual(view['attr'], 'index')
        self.assertEqual(view['route_name'], 'name')
        self.assertEqual(view['view'], DummyHandler)
    def test_add_handler_with_implicit_action(self):
        config = self._makeOne(autocommit=True)
        class DummyHandler(object):
            def __call__(self): pass
            __call__.__exposed__ = [{'a':'1'}]
        views = []
        def dummy_add_view(**kw):
            views.append(kw)
        config.add_view = dummy_add_view
        config.add_handler('name', '/abc', DummyHandler)
        self.assertEqual(len(views), 1)
        view = views[0]
        self.assertEqual(view['a'], '1')
        self.assertEqual(view['attr'], None)
        self.assertEqual(view['route_name'], 'name')
        self.assertEqual(view['view'], DummyHandler)
    def test_add_handler_with_multiple_action(self):
        config = self._makeOne(autocommit=True)
        class DummyHandler(object):
            def index(self): pass
            def create(self): pass
            create.__exposed__ = [{'name': 'index'}]
        views = []
        def dummy_add_view(**kw):
            views.append(kw)
        config.add_view = dummy_add_view
        config.add_handler('name', '/abc', DummyHandler, action='index')
        self.assertEqual(len(views), 2)
        view = views[0]
        self.assertEqual(view['attr'], 'create')
        self.assertEqual(view['route_name'], 'name')
        self.assertEqual(view['view'], DummyHandler)
        view = views[1]
        self.assertEqual(view['attr'], 'index')
    def test_add_handler_string(self):
        import pyramid
        views = []
        config = self._makeOne(autocommit=True)
        def dummy_add_view(**kw):
            views.append(kw)
        config.add_view = dummy_add_view
        config.add_handler('name', '/abc', 'pyramid')
        self.assertEqual(len(views), 1)
        view = views[0]
        self.assertEqual(view['view'], pyramid)
    def test_add_handler_pattern_None_no_previous_route(self):
        from pyramid.exceptions import ConfigurationError
        config = self._makeOne()
        self.assertRaises(ConfigurationError, config.add_handler,
                          'name', None, 'pyramid')
    def test_add_handler_pattern_None_with_previous_route(self):
        import pyramid
        config = self._makeOne(autocommit=True)
        config.add_route('name', ':def')
        views = []
        def dummy_add_view(**kw):
            views.append(kw)
        config.add_view = dummy_add_view
        config.add_route = None # shouldn't be called
        config.add_handler('name', None, 'pyramid')
        self.assertEqual(len(views), 1)
        view = views[0]
        self.assertEqual(view['view'], pyramid)
    def _assertRoute(self, config, name, path, num_predicates=0):
        from pyramid.interfaces import IRoutesMapper
        mapper = config.registry.getUtility(IRoutesMapper)
@@ -3188,24 +2916,6 @@
        else: # pragma: no cover
            raise AssertionError
    def test_conflict_add_handler(self):
        class AHandler(object):
            def aview(self): pass
        from zope.configuration.config import ConfigurationConflictError
        config = self._makeOne()
        config.add_handler('h1', '/h1', handler=AHandler)
        config.add_handler('h1', '/h1', handler=AHandler)
        try:
            config.commit()
        except ConfigurationConflictError, why:
            c1, c2, c3, c4 = self._conflictFunctions(why)
            self.assertEqual(c1, 'test_conflict_add_handler')
            self.assertEqual(c2, 'test_conflict_add_handler')
            self.assertEqual(c3, 'test_conflict_add_handler')
            self.assertEqual(c3, 'test_conflict_add_handler')
        else: # pragma: no cover
            raise AssertionError
    def test_scan_conflict(self):
        from zope.configuration.config import ConfigurationConflictError
        from pyramid.tests import selfscanapp
@@ -3232,6 +2942,24 @@
        for conflict in conflicts:
            for confinst in conflict:
                yield confinst[2]
    def test___getattr__missing_when_directives_exist(self):
        config = self._makeOne()
        directives = {}
        config.registry._directives = directives
        self.assertRaises(AttributeError, config.__getattr__, 'wontexist')
    def test___getattr__missing_when_directives_dont_exist(self):
        config = self._makeOne()
        self.assertRaises(AttributeError, config.__getattr__, 'wontexist')
    def test___getattr__matches(self):
        config = self._makeOne()
        def foo(config): pass
        directives = {'foo':foo}
        config.registry._directives = directives
        foo_meth = config.foo
        self.failUnless(foo_meth.im_func.__docobj__ is foo)
class TestConfigurator_add_directive(unittest.TestCase):
@@ -4836,57 +4564,6 @@
            pass
        self.assertEqual(self._callFUT(ISubException), True)
class TestActionPredicate(unittest.TestCase):
    def _getTargetClass(self):
        from pyramid.config import ActionPredicate
        return ActionPredicate
    def _makeOne(self, action='myaction'):
        return self._getTargetClass()(action)
    def test_bad_action_regex_string(self):
        from pyramid.exceptions import ConfigurationError
        cls = self._getTargetClass()
        self.assertRaises(ConfigurationError, cls, '[a-z')
    def test_bad_action_regex_None(self):
        from pyramid.exceptions import ConfigurationError
        cls = self._getTargetClass()
        self.assertRaises(ConfigurationError, cls, None)
    def test___call__no_matchdict(self):
        pred = self._makeOne()
        request = DummyRequest()
        self.assertEqual(pred(None, request), False)
    def test___call__no_action_in_matchdict(self):
        pred = self._makeOne()
        request = DummyRequest()
        request.matchdict = {}
        self.assertEqual(pred(None, request), False)
    def test___call__action_does_not_match(self):
        pred = self._makeOne()
        request = DummyRequest()
        request.matchdict = {'action':'notmyaction'}
        self.assertEqual(pred(None, request), False)
    def test___call__action_matches(self):
        pred = self._makeOne()
        request = DummyRequest()
        request.matchdict = {'action':'myaction'}
        self.assertEqual(pred(None, request), True)
    def test___hash__(self):
        pred1 = self._makeOne()
        pred2 = self._makeOne()
        pred3 = self._makeOne(action='notthesame')
        self.assertEqual(hash(pred1), hash(pred2))
        self.assertNotEqual(hash(pred1), hash(pred3))
        self.assertNotEqual(hash(pred2), hash(pred3))
class DummyRequest:
    subpath = ()
    matchdict = None
@@ -5024,16 +4701,6 @@
def dummyfactory(request):
    """ """
class DummyHandler(object): # pragma: no cover
    def __init__(self, request):
        self.request = request
    def action1(self):
        return 'response 1'
    def action2(self):
        return 'response 2'
def dummy_include(config):
    config.action('discrim', None, config.package)
pyramid/tests/test_view.py
@@ -429,28 +429,6 @@
        result = self._callFUT(context, request)
        self.assertEqual(result, 'abc')
class Test_action(unittest.TestCase):
    def _makeOne(self, **kw):
        from pyramid.view import action
        return action(**kw)
    def test_call_no_previous__exposed__(self):
        inst = self._makeOne(a=1, b=2)
        def wrapped():
            """ """
        result = inst(wrapped)
        self.failUnless(result is wrapped)
        self.assertEqual(result.__exposed__, [{'a':1, 'b':2}])
    def test_call_with_previous__exposed__(self):
        inst = self._makeOne(a=1, b=2)
        def wrapped():
            """ """
        wrapped.__exposed__ = [None]
        result = inst(wrapped)
        self.failUnless(result is wrapped)
        self.assertEqual(result.__exposed__, [None, {'a':1, 'b':2}])
class ExceptionResponse(Exception):
    status = '404 Not Found'
    app_iter = ['Not Found']
pyramid/tests/test_zcml.py
@@ -639,86 +639,6 @@
        context = self.config._ctx
        self.assertRaises(ConfigurationError, self._callFUT, context, 'name')
class TestHandlerDirective(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp(autocommit=False)
        self.config._ctx = self.config._make_context()
    def tearDown(self):
        testing.tearDown()
    def _callFUT(self, *arg, **kw):
        from pyramid.zcml import handler
        return handler(*arg, **kw)
    def _assertRoute(self, name, pattern, num_predicates=0):
        from pyramid.interfaces import IRoutesMapper
        reg = self.config.registry
        mapper = reg.getUtility(IRoutesMapper)
        routes = mapper.get_routes()
        route = routes[0]
        self.assertEqual(len(routes), 1)
        self.assertEqual(route.name, name)
        self.assertEqual(route.pattern, pattern)
        self.assertEqual(len(routes[0].predicates), num_predicates)
        return route
    def test_it(self):
        from pyramid.view import action
        from zope.interface import Interface
        from pyramid.interfaces import IView
        from pyramid.interfaces import IViewClassifier
        from pyramid.interfaces import IRouteRequest
        reg = self.config.registry
        context = self.config._ctx
        class Handler(object): # pragma: no cover
            def __init__(self, request):
                self.request = request
            action(renderer='json')
            def one(self):
                return 'OK'
            action(renderer='json')
            def two(self):
                return 'OK'
        self._callFUT(context, 'name', '/:action', Handler)
        actions = extract_actions(context.actions)
        self.assertEqual(len(actions), 3)
        route_action = actions[0]
        route_discriminator = route_action['discriminator']
        self.assertEqual(route_discriminator,
                         ('route', 'name', False, None, None, None, None,None))
        self._assertRoute('name', '/:action')
        view_action = actions[1]
        request_type = reg.getUtility(IRouteRequest, 'name')
        view_discriminator = view_action['discriminator']
        discrim = ('view', None, '', None, IView, None, None, None, 'name',
                   'one', False, None, None, None)
        self.assertEqual(view_discriminator[:14], discrim)
        view_action['callable'](*view_action['args'], **view_action['kw'])
        view_action = actions[2]
        request_type = reg.getUtility(IRouteRequest, 'name')
        view_discriminator = view_action['discriminator']
        discrim = ('view', None, '', None, IView, None, None, None, 'name',
                   'two', False, None, None, None)
        self.assertEqual(view_discriminator[:14], discrim)
        view_action['callable'](*view_action['args'], **view_action['kw'])
        wrapped = reg.adapters.lookup(
            (IViewClassifier, request_type, Interface), IView, name='')
        self.failUnless(wrapped)
    def test_pattern_is_None(self):
        from pyramid.exceptions import ConfigurationError
        context = self.config._ctx
        class Handler(object):
            pass
        self.assertRaises(ConfigurationError, self._callFUT,
                          context, 'name', None, Handler)
class TestStaticDirective(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp(autocommit=False)
pyramid/view.py
@@ -529,14 +529,3 @@
"""
class action(object):
    def __init__(self, **kw):
        self.kw = kw
    def __call__(self, wrapped):
        if hasattr(wrapped, '__exposed__'):
            wrapped.__exposed__.append(self.kw)
        else:
            wrapped.__exposed__ = [self.kw]
        return wrapped
pyramid/zcml.py
@@ -296,74 +296,6 @@
        traverse=traverse,
        )
class IHandlerDirective(IRouteLikeDirective):
    route_name = TextLine(title=u'route_name', required=True)
    handler = GlobalObject(title=u'handler', required=True)
    action = TextLine(title=u"action", required=False)
def handler(_context,
            route_name,
            pattern,
            handler,
            action=None,
            view=None,
            view_for=None,
            permission=None,
            factory=None,
            for_=None,
            header=None,
            xhr=False,
            accept=None,
            path_info=None,
            request_method=None,
            request_param=None,
            custom_predicates=(),
            view_permission=None,
            view_attr=None,
            renderer=None,
            view_renderer=None,
            view_context=None,
            traverse=None,
            use_global_views=False):
    """ Handle ``handler`` ZCML directives
    """
    # the strange ordering of the request kw args above is for b/w
    # compatibility purposes.
    # these are route predicates; if they do not match, the next route
    # in the routelist will be tried
    if view_context is None:
        view_context = view_for or for_
    view_permission = view_permission or permission
    view_renderer = view_renderer or renderer
    if pattern is None:
        raise ConfigurationError('handler directive must include a "pattern"')
    config = Configurator.with_context(_context)
    config.add_handler(
        route_name,
        pattern,
        handler,
        action=action,
        factory=factory,
        header=header,
        xhr=xhr,
        accept=accept,
        path_info=path_info,
        request_method=request_method,
        request_param=request_param,
        custom_predicates=custom_predicates,
        view=view,
        view_context=view_context,
        view_permission=view_permission,
        view_renderer=view_renderer,
        view_attr=view_attr,
        use_global_views=use_global_views,
        traverse=traverse,
        )
class ISystemViewDirective(Interface):
    view = GlobalObject(
        title=u"",