From d61024a2a7f625dd61b5face58f4190c9e233718 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 30 Jun 2010 18:44:09 +0000 Subject: - A section named ``Custom Predicates`` was added to the URL Dispatch narrative chapter. --- docs/narr/urldispatch.rst | 100 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 571388929..68cbaeb29 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -571,6 +571,105 @@ represent neither predicates nor view configuration information. try to fall back to using a view that otherwise matches the context, request, and view name (but does not match the route name predicate). +Custom Predicates +~~~~~~~~~~~~~~~~~ + +Each of the predicate callables fed to the ``custom_predicates`` +argument of :meth:`repoze.bfg.configuration.Configurator.add_route` or +the ``custom_predicates`` ZCML attribute must be a callable accepting +two arguments. The first argument passed to a custom predicate is a +dictionary conventionally named ``info`. The second argument is the +current :term:`request` object. + +The ``info`` dictionary has a number of contained values: ``match`` is +a dictionary: it represents the arguments matched in the URL by the +route. ``route`` is an object representing the route which was +matched. + +``info['match']`` is useful when predicates need access to the route +match. For example: + +.. code-block:: python + + def any_of(segment_name, *allowed): + def predicate(info, request): + if info['match'][segment_name] in allowed: + return True + return predicate + + num_one_two_or_three = any_of('num, 'one', 'two', 'three') + + config.add_route('num', '/:num', + custom_predicates=(num_one_two_or_three,)) + +The above ``any_of`` function generates a predicate which ensures that +the match value named ``segment_name`` is in the set of allowable +values represented by ``allowed``. We use this ``any_of`` function to +generate a predicate function named ``num_one_two_or_three``, which +ensures that the ``num`` segment is one of the values ``one``, +``two``, or ``three`` , and use the result as a custom predicate by +feeding it inside a tuple to the ``custom_predicates`` argument to +:meth:`repoze.configuration.Configurator.add_route`. + +A custom route predicate may also *modify* the ``match`` dictionary. +For instance, a predicate might do some type conversion of values: + +.. code-block:: python + + def integers(*segment_names): + def predicate(info, request): + match = info['match'] + for segment_name in segment_names: + try: + match[segment_name] = int(match[segment_name]) + except (TypeError, ValueError): + pass + return predicate + + ymd_to_int = integers('year', 'month', 'day') + + config.add_route('num', '/:year/:month/:day', + custom_predicates=(ymd_to_int,)) + +The ``match`` dictionary passed within ``info`` to each predicate +attached to a route will be the same dictionary. Therefore, when +registering a custom predicate which modifies the ``match`` dict, the +code registering the predicate should usually arrange for the +predicate to be the *last* custom predicate in the custom predicate +list. Otherwise, custom predicates which fire subsequent to the +predicate which performs the ``match`` modification will receive the +*modified* match dictionary. + +.. warning:: + + It is a poor idea to rely on ordering of custom predicates to build + some conversion pipeline, where one predicate depends on the side + effect of another. For instance, it's a poor idea to register two + custom predicates, one which handles conversion of a value to an + int, the next which handles conversion of that integer to some + custom object. Just do all that in a single custom predicate. + +The ``route`` object in the ``info`` dict is an object that has two +useful attributes: ``name`` and ``path``. The ``name`` attribute is +the route name. The ``path`` attribute is the route pattern. An +example of using the route in a set of route predicates: + +.. code-block:: python + :linenos: + + def twenty_ten(info, request): + if info['route'].name in ('ymd', 'ym', 'y'): + return info['match']['year'] == '2010' + + config.add_route('y', '/:year', custom_predicates=(twenty_ten,)) + config.add_route('ym', '/:year/:month', custom_predicates=(twenty_ten,)) + config.add_route('ymd', '/:year/:month:/day', + custom_predicates=(twenty_ten,)) + +The above predicate, when added to a number of route configurations +ensures that the year match argument is '2010' if and only if the +route name is 'ymd', 'ym', or 'y'. + Route Matching -------------- @@ -606,7 +705,6 @@ If no route matches after all route patterns are exhausted, :mod:`repoze.bfg` falls back to :term:`traversal` to do :term:`context finding` and :term:`view lookup`. - .. index:: single: matchdict -- cgit v1.2.3