This is an attempt at a formal description of Pyramid's view predicate
system in order to find possible avenues for optimization.
It was prompted by concern over the approach currently taken to fix #768_.

An alternative implementation approach is suggested using adapter
registry subscribers.

.. _#768: https://github.com/Pylons/pyramid/pull/786


View registration
=================

View are registered with a number of predicates (name, route_name,
context, request_method, permission, etc...) which determine when the
view is applicable.

Internally, some predicates receive special handling (name,
route_name, context) but this is purely an optimization and
semantically they should be considered the same.

We should distinguish between two types of view predicates:

*Pure predicates*
    Simply extract distinguishing characteristics from the request,
e.g. *request_method*.
    Pure predicates have no side-effects, though they are only
guaranteed stable for the duration of a request (e.g. the *check_csrf*
predicate counts even though the token it checks may be invalid in a
subsequent request.)

*Impure predicates*

    Make assertions about the request (e.g. validating the body of a
request) and may raise an error when called.
    This might be thought an abuse of the predicate system and should
perhaps be transformed to decorators during view configuration.
    It should not be possible to register both a validated and
unvalidated view if the validation decorator raises an error.

For the purposes of this document, only pure predicates will be considered.


Finding the view
================

When a request is handled, the matching *route* is found (may be the
default route), the route's root is traversed to find the *context*
and the *view name* is extracted.

The first view where all predicates match is used or a 404 is raised.


View sort order
===============

Views are sorted by predicate value in predicate order on registration.


Predicate order
---------------

Predicate order matters because multiple candidate views may match a request.
When a request may match multiple values for a predicate (an unset
predicate, or a context specification)
Then the order they are tested determines which view is use.

Where a predicate is not supplied in view registration it is
considered *unset*, matching all requests.

    # request_method sorts before context
    (name='display', request_method='GET', context=any) -> view1
    (name='display', request_method=unset, context=Item) -> view2

    # context sorts before request_method
    (name='display', request_method=unset, context=Item) -> view2
    (name='display', request_method='GET', context=any) -> view1

Only when the values for a predicate are fully disctinct (e.g. view
name, which defaults to blank and cannot be left unset) is the
predicate order irrelevant.


Predicate value order
---------------------

Within a predicate, views are ordered by predicate value.
Predicate values are ordered by specificity, so a view registration
with an unset predicate value sorts last among that predicate's
values.

More complex specificity sorting rules apply to the *context* predicate.
These are sorted by zope.interface `specification resolution order`_,
a subclass sorts before its superclass.

The specificity of a *route_name* value depends on whether the route
was registered with *use_global_views*.
When the ``use_global_views=True``, both global and route_name
specific views are considered.
This is achieved by including the global `IRequest` in the route
specific request interface's bases.

.. specification resolution order:
http://docs.zope.org/zope.interface/api.html#ISpecification.__sro__


Scope for optimization
======================

Except for the *route_name*, *view name* and *context*, predicates are
currently tested against each candidate view.

A multi-adapter lookup is currently used to find the MultiView with
list of predicated views registrations for the route, view name and
context.


Could the component registry be used?
-------------------------------------

Lets assume that all predicate values were possible to map to
interfaces (like the route request_iface.)

Only the most specific adapter is returned from the adapters.lookup
call, so all predicates would need to be precalculated if we wanted to
use the adapters.lookup for finding the view instead of using the
MultiView.

Subscribers are similar to adapters but all matching subscribers
rather than only the most specific are returned from a lookup. They
appear to be returned in order of specificity (with most specific
last), though that does not appear to be documented in zope.interface.

Using subscribers, predicates could be registered as either eager or lazy.

*Eager predicates*
    Have their values calculated once before view lookup.

*Lazy predicates*
    Have their values calculate per view, as they are currently in the
MultiView.

The adapter registery would be queried for subscribers with the values
from the eager predicates::

        predicate_ifaces = (IViewClassifier, request.request_iface,
                            context_iface, request_method_iface,  # ...
                            )
        candidates = adapters.subscriptions(predicate_ifaces, IView,
name=view_name)

        for view_callable in reversed(candidates):
            try:
                result = view_callable(context, request)
            except PredicateMismatch:
                continue
            else:
                break
        else:
            raise NotFound

Views with lazy predicates would be wrapped much as they currently are
in a MultiView (although it would only need to handle a single view
rather than multiple.)

To preserve predicate sort order, lazy predicates could be included in
the subscriptions lookup, but would be registered with None so they
apply to all requests.

-- 
You received this message because you are subscribed to the Google Groups 
"pylons-devel" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to pylons-devel+unsubscr...@googlegroups.com.
To post to this group, send email to pylons-devel@googlegroups.com.
Visit this group at http://groups.google.com/group/pylons-devel?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to