[Welcome] [TitleIndex] [WordIndex

Note that a simpler and more flexible approach is described in CustomPathTraversal.

The TailEater was the result of getting my head around Quixote's insistence on viewing the UrlAsInterface. It's one of the things I enjoy about using Quixote, and after getting that made me realize that dates were arguments, and arguments belong at the end, well, then I needed something to consume those trailing arguments. Doing it once with special-case _q_lookup()s cured me of that real fast, and the result was something that I didn't call TailEater, but now think I ought to have.

   1 from quixote.directory import Directory
   2 
   3 class TailEater(Directory):
   4     _q_exports = ['']
   5 
   6     def __init__(self, handler, first_component=None):
   7         self.handler = handler
   8         self.args = [first_component] if first_component is not None else []
   9 
  10     def _q_index(self):
  11         return self.handler.make_page(self.args)
  12 
  13     def _q_lookup(self, component):
  14         self.args.append(component)
  15         return self
  16 

TailEater is invoked at the point where the function selector has been parsed. For example, at the selector in http://norwegian-blue.com/journal/amk/2003/11/11 we know that the result is going to be a journal page. Whose, and for what date(s), are the arguments that follow the selector. That may not be the best form in all cases, but when it is this works nicely.

One catch is that this is most conveniently invoked (by creating and returning an instance of TailEater to Quixote's traverser) from a _q_lookup. It should be easy to use it in a Directory as well with the help of a decorator.

   1 from quixote.errors import TraversalError
   2 
   3 def tail_eater(func):
   4     def wrapper(directory):
   5         class Handler:
   6             def make_page(self, args):
   7                 if len(args)+1 != func.func_code.co_argcount:
   8                     raise TraversalError
   9                 return func(directory, *args)
  10         return TailEater(Handler())
  11     return property(wrapper)
  12 
  13 class Root(Directory):
  14     _q_exports = ['journal']
  15 
  16     @tail_eater
  17     def journal(self, journal, year, month, day):
  18         ...
  19 


Why was it important for handler to be an instance object rather than a function?

Not so much important as useful. In the original design, though not in the form it had changed to when I first wrote this up for the wiki, I needed some additional state that was conveniently captured in the handler object. That's come back in another project that's just getting started, so I think this is a keeper. MartinManey

The first_component initializer is probably NOT part of the concept, but rather an artifact of the way my original version evolved.

Maybe, maybe not. In the new work I've changed the design a trifle, and that's now an optional argument to the initializer. If it is not given (or is None, the default), then the collected list does not include an item for the component which caused TailEater to start collecting. Although this does introduce the possibility that the invoker and the handler can disagree about whether the invoking component is recorded int he list, I consider that a small price for not having to deal with an unwanted inkoking component or needing to deal with it differently, depending on whether it's actually useful in a given design. I hope that some work currently begining will give me an opportunity to test this theory. MartinManey


2010-09-22 22:14