summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2010-12-09 04:04:39 -0500
committerChris McDonough <chrism@plope.com>2010-12-09 04:04:39 -0500
commite8edd5ff7157c9a11b6478702c1e45bb46f11344 (patch)
tree98ba7a2d4235fcbb86b67c187f84cc39fd6d0add
parentf360d9e6d9689dfe92a950e97e7e19c414655997 (diff)
downloadpyramid-e8edd5ff7157c9a11b6478702c1e45bb46f11344.tar.gz
pyramid-e8edd5ff7157c9a11b6478702c1e45bb46f11344.tar.bz2
pyramid-e8edd5ff7157c9a11b6478702c1e45bb46f11344.zip
- Add a ``handler`` ZCML directive. This directive does the same thing as
``pyramid.configuration.add_handler``.
-rw-r--r--CHANGES.txt3
-rw-r--r--TODO.txt5
-rw-r--r--docs/narr/declarative.rst51
-rw-r--r--docs/narr/handlers.rst6
-rw-r--r--docs/zcml.rst1
-rw-r--r--docs/zcml/handler.rst158
-rw-r--r--pyramid/includes/meta.zcml6
-rw-r--r--pyramid/tests/test_zcml.py80
-rw-r--r--pyramid/zcml.py79
9 files changed, 374 insertions, 15 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 2a968daf9..2adec52c6 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -4,6 +4,9 @@ Next release
Features
--------
+- Add a ``handler`` ZCML directive. This directive does the same thing as
+ ``pyramid.configuration.add_handler``.
+
- A new module named ``pyramid.config`` was added. It subsumes the duties of
the older ``pyramid.configuration`` module.
diff --git a/TODO.txt b/TODO.txt
index 0033eb3b3..194452f2c 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -4,11 +4,6 @@ Pyramid TODOs
Must-Have (before 1.0)
----------------------
-- Add a ``handler`` ZCML directive. This implies some slightly dicey
- refactoring of the configurator to allow it to generate ZCML
- "discriminators" for views and routes, that could be implemented in terms
- of "twophase configuration" in "should have" below.
-
- Provide a .flash API on session object.
- Use ``@register_view`` instead of ``@view_config`` and change view docs to
diff --git a/docs/narr/declarative.rst b/docs/narr/declarative.rst
index 31d6fc575..99c5a75ac 100644
--- a/docs/narr/declarative.rst
+++ b/docs/narr/declarative.rst
@@ -644,12 +644,11 @@ See :ref:`view_directive` for complete ZCML directive documentation.
Configuring a Route via ZCML
----------------------------
-Instead of using the imperative
-:meth:`pyramid.config.Configurator.add_route` method to add a new
-route, you can alternately use :term:`ZCML`. :ref:`route_directive`
-statements in a :term:`ZCML` file used by your application is a sign that
-you're using :term:`URL dispatch`. For example, the following :term:`ZCML
-declaration` causes a route to be added to the application.
+Instead of using the imperative :meth:`pyramid.config.Configurator.add_route`
+method to add a new route, you can alternately use :term:`ZCML`.
+:ref:`route_directive` statements in a :term:`ZCML` file. For example, the
+following :term:`ZCML declaration` causes a route to be added to the
+application.
.. code-block:: xml
:linenos:
@@ -679,6 +678,46 @@ is the order that they appear relative to each other in the ZCML file.
See :ref:`route_directive` for full ``route`` ZCML directive
documentation.
+.. _zcml_handler_configuration:
+
+Configuring a Handler via ZCML
+------------------------------
+
+Instead of using the imperative
+:meth:`pyramid.config.Configurator.add_handler` method to add a new
+route, you can alternately use :term:`ZCML`. :ref:`handler_directive`
+statements in a :term:`ZCML` file used by your application is a sign that
+you're using :term:`URL dispatch`. For example, the following :term:`ZCML
+declaration` causes a route to be added to the application.
+
+.. code-block:: xml
+ :linenos:
+
+ <handler
+ route_name="myroute"
+ pattern="/prefix/{action}"
+ handler=".handlers.MyHandler"
+ />
+
+.. note::
+
+ Values prefixed with a period (``.``) within the values of ZCML attributes
+ such as the ``handler`` attribute of a ``handler`` directive mean
+ "relative to the Python package directory in which this :term:`ZCML` file
+ is stored". So if the above ``handler`` declaration was made inside a
+ ``configure.zcml`` file that lived in the ``hello`` package, you could
+ replace the relative ``.views.MyHandler`` with the absolute
+ ``hello.views.MyHandler`` Either the relative or absolute form is
+ functionally equivalent. It's often useful to use the relative form, in
+ case your package's name changes. It's also shorter to type.
+
+The order that the routes attached to handlers are evaluated when declarative
+configuration is used is the order that they appear relative to each other in
+the ZCML file.
+
+See :ref:`handler_directive` for full ``handler`` ZCML directive
+documentation.
+
.. index::
triple: view; zcml; static resource
diff --git a/docs/narr/handlers.rst b/docs/narr/handlers.rst
index 49dd73f7f..9a5267b2a 100644
--- a/docs/narr/handlers.rst
+++ b/docs/narr/handlers.rst
@@ -86,6 +86,12 @@ This will result one of the methods that are configured for the ``action`` of
handler class not named 'index' might be called if they were configured to be
called when the ``action`` name is 'index' as will be seen below.
+.. note::
+
+ Handler configuration may also be added to the system via :term:`ZCML` (see
+ :ref:`zcml_handler_configuration`).
+
+.. _using_add_handler:
Using :meth:`~pyramid.config.Configurator.add_handler`
-------------------------------------------------------------
diff --git a/docs/zcml.rst b/docs/zcml.rst
index 60bbbf574..5913f9460 100644
--- a/docs/zcml.rst
+++ b/docs/zcml.rst
@@ -16,6 +16,7 @@ documentation is organized alphabetically by directive name.
zcml/configure
zcml/default_permission
zcml/forbidden
+ zcml/handler
zcml/include
zcml/localenegotiator
zcml/notfound
diff --git a/docs/zcml/handler.rst b/docs/zcml/handler.rst
new file mode 100644
index 000000000..301bf7895
--- /dev/null
+++ b/docs/zcml/handler.rst
@@ -0,0 +1,158 @@
+.. _handler_directive:
+
+``handler``
+-----------
+
+The ``handler`` directive adds the configuration of a :term:`view handler` to
+the :term:`application registry`.
+
+Attributes
+~~~~~~~~~~
+
+``route_name``
+ The name of the route, e.g. ``myroute``. This attribute is required. It
+ must be unique among all defined handler and route names in a given
+ configuration.
+
+``pattern``
+ The pattern of the route e.g. ``ideas/{idea}``. This attribute is
+ required. See :ref:`route_pattern_syntax` for information about the syntax
+ of route patterns. The name ``{action}`` is treated specially in handler
+ patterns. See :ref:`using_add_handler` for a discussion of how
+ ``{action}`` in handler patterns is treated.
+
+``action``
+ If the action name is not specified in the ``pattern``, use this name as the
+ handler action (method name).
+
+``factory``
+ The :term:`dotted Python name` to a function that will generate a
+ :app:`Pyramid` context object when the associated route matches.
+ e.g. ``mypackage.models.MyFactoryClass``. If this argument is not
+ specified, a default root factory will be used.
+
+``xhr``
+ This value should be either ``True`` or ``False``. If this value is
+ specified and is ``True``, the :term:`request` must possess an
+ ``HTTP_X_REQUESTED_WITH`` (aka ``X-Requested-With``) header for this
+ route to match. This is useful for detecting AJAX requests issued
+ from jQuery, Prototype and other Javascript libraries. If this
+ predicate returns false, route matching continues.
+
+``traverse``
+ If you would like to cause the :term:`context` to be something other
+ than the :term:`root` object when this route matches, you can spell
+ a traversal pattern as the ``traverse`` argument. This traversal
+ pattern will be used as the traversal path: traversal will begin at
+ the root object implied by this route (either the global root, or
+ the object returned by the ``factory`` associated with this route).
+
+ The syntax of the ``traverse`` argument is the same as it is for
+ ``pattern``. For example, if the ``pattern`` provided to the
+ ``route`` directive is ``articles/{article}/edit``, and the
+ ``traverse`` argument provided to the ``route`` directive is
+ ``/{article}``, when a request comes in that causes the route to
+ match in such a way that the ``article`` match value is '1' (when
+ the request URI is ``/articles/1/edit``), the traversal path will be
+ generated as ``/1``. This means that the root object's
+ ``__getitem__`` will be called with the name ``1`` during the
+ traversal phase. If the ``1`` object exists, it will become the
+ :term:`context` of the request. :ref:`traversal_chapter` has more
+ information about traversal.
+
+ If the traversal path contains segment marker names which are not
+ present in the ``pattern`` argument, a runtime error will occur.
+ The ``traverse`` pattern should not contain segment markers that do
+ not exist in the ``pattern``.
+
+ A similar combining of routing and traversal is available when a
+ route is matched which contains a ``*traverse`` remainder marker in
+ its ``pattern`` (see :ref:`using_traverse_in_a_route_pattern`). The
+ ``traverse`` argument to the ``route`` directive allows you to
+ associate route patterns with an arbitrary traversal path without
+ using a a ``*traverse`` remainder marker; instead you can use other
+ match information.
+
+ Note that the ``traverse`` argument to the ``handler`` directive is
+ ignored when attached to a route that has a ``*traverse`` remainder
+ marker in its pattern.
+
+``request_method``
+ A string representing an HTTP method name, e.g. ``GET``, ``POST``,
+ ``HEAD``, ``DELETE``, ``PUT``. If this argument is not specified,
+ this route will match if the request has *any* request method. If
+ this predicate returns false, route matching continues.
+
+``path_info``
+ The value of this attribute represents a regular expression pattern
+ that will be tested against the ``PATH_INFO`` WSGI environment
+ variable. If the regex matches, this predicate will be true. If
+ this predicate returns false, route matching continues.
+
+``request_param``
+ This value can be any string. A view declaration with this
+ attribute ensures that the associated route will only match when the
+ request has a key in the ``request.params`` dictionary (an HTTP
+ ``GET`` or ``POST`` variable) that has a name which matches the
+ supplied value. If the value supplied to the attribute has a ``=``
+ sign in it, e.g. ``request_params="foo=123"``, then the key
+ (``foo``) must both exist in the ``request.params`` dictionary, and
+ the value must match the right hand side of the expression (``123``)
+ for the route to "match" the current request. If this predicate
+ returns false, route matching continues.
+
+``header``
+ The value of this attribute represents an HTTP header name or a
+ header name/value pair. If the value contains a ``:`` (colon), it
+ will be considered a name/value pair (e.g. ``User-Agent:Mozilla/.*``
+ or ``Host:localhost``). The *value* of an attribute that represent
+ a name/value pair should be a regular expression. If the value does
+ not contain a colon, the entire value will be considered to be the
+ header name (e.g. ``If-Modified-Since``). If the value evaluates to
+ a header name only without a value, the header specified by the name
+ must be present in the request for this predicate to be true. If
+ the value evaluates to a header name/value pair, the header
+ specified by the name must be present in the request *and* the
+ regular expression specified as the value must match the header
+ value. Whether or not the value represents a header name or a
+ header name/value pair, the case of the header name is not
+ significant. If this predicate returns false, route matching
+ continues.
+
+``accept``
+ The value of this attribute represents a match query for one or more
+ mimetypes in the ``Accept`` HTTP request header. If this value is
+ specified, it must be in one of the following forms: a mimetype
+ match token in the form ``text/plain``, a wildcard mimetype match
+ token in the form ``text/*`` or a match-all wildcard mimetype match
+ token in the form ``*/*``. If any of the forms matches the
+ ``Accept`` header of the request, this predicate will be true. If
+ this predicate returns false, route matching continues.
+
+``custom_predicates``
+ This value should be a sequence of references to custom predicate
+ callables. Use custom predicates when no set of predefined
+ predicates does what you need. Custom predicates can be combined
+ with predefined predicates as necessary. Each custom predicate
+ callable should accept two arguments: ``info`` and ``request``
+ and should return either ``True`` or ``False`` after doing arbitrary
+ evaluation of the info and/or the request. If all custom and
+ non-custom predicate callables return ``True`` the associated route
+ will be considered viable for a given request. If any predicate
+ callable returns ``False``, route matching continues. Note that the
+ value ``info`` passed to a custom route predicate is a dictionary
+ containing matching information; see :ref:`custom_route_predicates`
+ for more information about ``info``.
+
+
+Alternatives
+~~~~~~~~~~~~
+
+You can also add a :term:`route configuration` via:
+
+- Using the :meth:`pyramid.config.Configurator.add_handler` method.
+
+See Also
+~~~~~~~~
+
+See also :ref:`handlers_chapter`.
diff --git a/pyramid/includes/meta.zcml b/pyramid/includes/meta.zcml
index bd16c3ea6..58af814ef 100644
--- a/pyramid/includes/meta.zcml
+++ b/pyramid/includes/meta.zcml
@@ -35,6 +35,12 @@
/>
<meta:directive
+ name="handler"
+ schema="pyramid.zcml.IHandlerDirective"
+ handler="pyramid.zcml.handler"
+ />
+
+ <meta:directive
name="resource"
schema="pyramid.zcml.IResourceDirective"
handler="pyramid.zcml.resource"
diff --git a/pyramid/tests/test_zcml.py b/pyramid/tests/test_zcml.py
index 33a67873f..8bc7c3eac 100644
--- a/pyramid/tests/test_zcml.py
+++ b/pyramid/tests/test_zcml.py
@@ -639,6 +639,86 @@ class TestRouteDirective(unittest.TestCase):
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)
diff --git a/pyramid/zcml.py b/pyramid/zcml.py
index 07dc3f0b5..eb7c11d10 100644
--- a/pyramid/zcml.py
+++ b/pyramid/zcml.py
@@ -178,13 +178,11 @@ def view(
_view = view # for directives that take a view arg
-class IRouteDirective(Interface):
+
+class IRouteLikeDirective(Interface):
""" The interface for the ``route`` ZCML directive
"""
- name = TextLine(title=u'name', required=True)
pattern = TextLine(title=u'pattern', required=False)
- # alias for pattern
- path = TextLine(title=u'path', required=False)
factory = GlobalObject(title=u'context factory', required=False)
view = GlobalObject(title=u'view', required=False)
@@ -224,6 +222,11 @@ class IRouteDirective(Interface):
)
use_global_views = Bool(title=u'use_global_views', required=False)
+class IRouteDirective(IRouteLikeDirective):
+ name = TextLine(title=u'name', required=True)
+ # alias for pattern
+ path = TextLine(title=u'path', required=False)
+
def route(_context,
name,
pattern=None,
@@ -287,6 +290,74 @@ def route(_context,
traverse=traverse,
)
+class IHandlerDirective(IRouteLikeDirective):
+ route_name = TextLine(title=u'route_name', required=True)
+ for_ = 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"",