Skip to content.

plope

Personal tools
You are here: Home » Members » chrism's Home » View Dispatch Is Not An Adapter Lookup
 
 

View Dispatch Is Not An Adapter Lookup

The traditional Zope worldview that says that view dispatch is an adapter lookup is not optimal.

In a podcast we recorded yesterday, I held forth about the new features of the last two repoze.bfg releases. One of these, new in 1.1, was the idea of "view predicates".

Now, the idea of a view predicate was not mine, it was Malthe Borch's. At the time, I was still wrapped up in the Zope-inspired worldview that a view invocation is (by god) an invocation of a ZCA multiadapter, QED, full-stop. Malthe figured out that it is useful to look outside the idea of adaptation to resolve a request to a view, and as a result I implemented view lookup in terms of view predicates in 1.1; 1.0 lacked this feature. The epiphany is somewhat obvious in hindsight, but at the time, it was not. I was well and truly wrapped around the adaptation axle.

For those who don't know, an "adapter" is a bit of code that converts a set of input objects [I] to an output object O. The simplest physical example of an adapter is the one we know and love: a power plug adapter. Maybe one that converts a US-style two-pronged power plug to a "proper" mains plug like they use in the UK. Other stilted and trivial adapter analogies exist, I'll skip them here. Google for "Zope adapter".

In Zope (and BFG, and Django) terms, a "view" is a bit of code invoked as the result of a particular request. It's called a "controller action" in other framework religions.

In terms of view dispatch, Zope uses a combination of graph traversal and an adapter lookup to find a view. Rather than using some ordered set of URL match tests like many other web frameworks, Zope encodes all knowledge about which view should be invoked in all particular circumstances within a given application as a set of adapter registrations. When a request enters the system, traversal is responsible for finding a "context" object and a "view name". Using this context object, this view name, and the request object it does a "multiadapter" lookup something like this: please find me a view adapter for this kind of context and this kind of request with this name. Ex.: ((context, request), view name) -> view object.

While I'm a big fan of this style of dispatch, real-world requirements stretch its applicability. It was always pretty odd to need to attach an interface object to a request in order to convince the view machinery to do something different in some circumstance; it got truly weird when Zope started to support persistent registries that allowed you to make different view registrations in different contexts. Entire subframeworks of various Zope subsystems and offshoots exist just to manage the results for adapter and utility lookups related to requests and contexts. I fear it may all be for naught.

What Malthe figured out is that view dispatch is not just an adapter lookup. While adapter lookup can help speed things up, there's just not enough value in the adapter lookup machinery to spell all the query axes in terms of interfaces, as required by zope.component. For example, it makes a hell of a lot more sense to register a view callable that will be invoked in a circumstance like so:

  request.method == 'POST'
  'application/json' in request.accept

Than it ever would to register something in terms of interfaces, like Zope makes you do, ala:

  providedBy(request) -> IPostRequest, IAcceptApplicationJSON

I mean, literally, it's just not really possible to anticipate all the interfaces you might need to attach to a request in order to provide a vocabulary that allowed you to compose interfaces in enough combinations for such a system based on interface lookup to work. Even if you could, who would understand it after you created it?

With view predicates in BFG 1.1+, it becomes quite easy to register view callables for very specific circumstances that would be extremely difficult (or maybe impossible) to spell if you treated view lookup as only an adaptation problem. For example, using the containment view predicate in BFG, you can do this:

  <view
    for=".models.Entry"
    name="edit.html"
    containment=".models.Blog"
    />

The containment=".models.Blog" attribute is the important bit. It says "only invoke this view when the context object or any of its parents is a Blog object". This solves a whole raft of problems for UI, where you want to use the same kind of object in two places (an "Entry" object) but you want its edit view to be slightly different in two different "sections" of a site: maybe one view when we're in the Blog section, and another when we're in a Calendar section. This sort of registration is not possible in bare Zope, and adaptation alone, at least in the context of using a single adapter registry, cannot do it.

I don't think we can continue to treat view dispatch in the Zope world as a bare adapter lookup; it's just too limiting. In BFG 1.1+ we do not. Using a pattern much like Zope's we use a multiadapter lookup to find a "view" object. However, the view object that is found is often a "multiview". A multiview is a collection of views with associated predicates. Once a multiview is found and called, it evaluates the predicates associated with each of its constituent view callables (in a reasonably easy to understand order) to find the most specific view callable; then it invokes that to obtain a response.

Essentially we use the adapter lookup machinery only as a speed enhancement in this circumstance. The adapter lookup gets us "in a ballpark" where we can perform fewer predicate evaluations to find "the best" view callable. It would be possible to implement it without doing any adaptation at all, it would just be a lot slower.

Other predicate attributes exist for use in this way: route_name, request_method, request_param, xhr, accept, header, path_info. In the very latest release (1.2a9), there is even a "custom predicate" predicate, which allows you to provide a callable that returns true or false for an arbitrary circumstance so you can create your own predicate logic. These can be combined arbitrarily to specify an extremely granular set of circumstances without any interface foolery.

Personally, I think view predicates are a pure win, and I think their existence demonstrates why bare adaptation is not ideal for view lookup. I might even take that a little further: it would also be useful to be able to look up other things (such as utilities, and other code) using the same set of predicates. If this becomes true, I think the adaptation worldview as "from interface X to interface Y" as a fundamental assumption begins to look pretty anemic.

Created by chrism
Last modified 2009-12-31 10:37 AM

The containment case

Can easily be solved as an adapter lookup.......if we have acquisition of interfaces! :-)

Happy new year!

Generic functions vs. adaptation

These predicates look a lot like they're inspired by generic functions (which are a generalization of adaptation). The new view predicates are great, and the custom view predicates even greater!