Skip to content

Instantly share code, notes, and snippets.

@lrowe
Created April 25, 2013 22:49
Show Gist options
  • Save lrowe/5463873 to your computer and use it in GitHub Desktop.
Save lrowe/5463873 to your computer and use it in GitHub Desktop.
This is an attempt at a formal description of Pyramid's view predicate system in order to find possible avenues for optimization.

Pyramid view predicates

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.

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.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment