Ian Bicking wrote:
> Chris McDonough wrote:
> ...
>>> On the other hand, I do understand some motivation for this for 
>>> creating eclectic URL layouts.  But I wonder if there's a better 
>>> way?  In particular, make sure that writing routing code isn't hard, 
>>> so that custom code for different site layouts is easy.  There are at 
>>> least two modest methods in WebOb for this -- req.path_info_peek() 
>>> and req.path_info_pop(), which both get the next segment of the URL.  
>>> Along with req.urlvars (and req.urlargs) there's a way to save the 
>>> captured data without having to instantly inspect the model (unless 
>>> you want to inspect the model).  And domain names can be just as 
>>> valid a thing to parse, and don't really fit any traversal pattern.
>>
>> That blew my stack, sorry.
> 
> Well, maybe an example where graph traversal seems easier is something 
> like a filesystem (or ZODB filesystemish system) with verbs at the end. 
>  E.g., /path/to/file.txt/view, /path/to/file.txt/edit, etc.  You could 
> do something like:
> 
> def route_fs(req):
>     path, verb = req.path_info.rsplit('/', 1)[-1]
>     if not verb:
>         verb = 'index'
>     obj = File(path)
>     return getattr(obj, verb)(req)
> 
> Though I think even better would be something like:
> 
> def route_fs(req):
>     fs = Filesystem()
>     while 1:
>         segment = req.path_info_peek()
>         if segment is None:
>             return fs.view()
>         elif segment == '': # trailing /
>             return fs.index()
>         else:
>             try:
>                 fs = fs[segment]
>             except KeyError:
>                 return fs(req)
>             else:
>                 req.path_info_pop()

View code doesn't typically need to handle traversal in bfg (the framework 
handles it), so that makes both of the examples above more complicated than 
necessary.

Let's pretend the user asks for http://example.com/foo/bar/baz/biz/buz.txt . 
Let's further pretend that when this request comes in that we're traversing the 
follwing graph:

/--
    |
    |-- foo
         |
         ----bar

Here's what happens:

- bfg traverses the root, and attempts to find foo, which it finds.

- bfg traverses foo, and attempts to find bar, which it finds.

- bfg traverses bar, and attempts to find baz, which it does not find ('bar' 
raises a KeyError when asked for baz).

The fact that it does not find "baz" at this point does not signify an error 
condition.  It signifies that:

- the "context" is bar (the context is the last item found during traversal).

- the "view name" is "baz"

- the "subpath" is ['biz', 'buz.txt']

Because it's the "context", bfg examimes "baz" to find out what "type" it is. 
Let's say it finds that the context an IBar type (because "bar" happens to have 
an attribute attached to it that happens to be named __interfaces__ that 
indicates it's an (IBar,) ).

Using the "view name" ("baz") and the type, it asks the "view registry" 
(configured separately, in our case via "configure.zcml") this question:

   - Please find me a "view" (controller in some religions) with the name
     "baz" that can be used for the type IBar.

Let's say it finds no matching view type.  It then returns a NotFound.  The 
request ends.  Everyone is sad.

But!  For this graph:

/--
    |
    |-- foo
         |
         ----bar
              |
              ----baz
                     |
                     biz

The user asks for http://example.com/foo/bar/baz/biz/buz.txt

- bfg traverses foo, and attempts to find bar, which it finds.

- bfg traverses bar, and attempts to find baz, which it finds.

- bfg traverses baz, and attempts to find biz, which it finds.

- bfg traverses biz, and attemtps to find "buz.txt" which it does not find.

The fact that it does not find "biz.txt" at this point does not signify an 
error 
condition.  It signifies that:

- the "context" is biz (the context is the last item found during traversal).

- the "view name" is "buz.txt"

- the "subpath" is the empty list []

Because it's the "context", bfg examimes "biz" to find out what "type" it is. 
Let's say it finds that the context an IBiz type (because "biz" happens to have 
an attribute attached to it that happens to be named __interfaces__ that 
indicates it's an (IBiz,) ).

Using the "view name" ("buz.txt") and the type, it asks the "view registry" 
(configured separately, in our case in "configure.zcml") this question:

   - Please find me a "view" (controller in some religions) with the name
    "buz.txt" that can be used for type IBiz.

Let's say that question is answered "here you go, here'a a bit of code that is 
willing to deal with that case", and returns a view.  It is passed both "biz" 
as 
the "context" and the request.  It returns a response.

> And maybe that's more complicated than simple traversal, I don't know. 
> One thing I was thinking about as a general idea was to look for 
> .wsgi_application on objects, and use that.  Then traversable objects 
> might look like:
> 
> class Filesystem(object):
>     def __init__(self, path):
>         self.path = path
>     wsgi_application = TraverseGetitem('index')
>     @wsgiapp
>     def index(req):
>         ...
>         return resp
>     def __getitem__(self, segment):
>         return self.__class__(os.path.join(self.path, segment))
> 
> class TraverseGetitem(object):
>     def __init__(self, index_meth=None):
>         self.index_meth = index_meth
>     def __get__(self, obj, type=None):
>         if obj is None:
>             return self
>         def app(environ, start_response):
>             req = Request(environ)
>             next = req.path_info_peek()
>             if next:
>                 try:
>                     next = obj[next]
>                 except KeyError:
>                     next = None
>                 else:
>                     req.path_info_pop()
>             else:
>                 next = None
>             if next is None:
>                 next = self.index_meth
>                 if isinstance(next, basestring):
>                     next = getattr(obj, next)
>                 if hasattr(next, '__get__'):
>                     next = next.__get__(obj)
>             if hasattr(next, 'wsgi_application'):
>                 next = next.wsgi_application
>             return next(environ, start_response)
>          return app


That's more or less what the BFG traverser does.  Sort of.  See the 
NaivePublishTraverser class in 
http://svn.repoze.org/repoze.bfg/trunk/repoze/bfg/traversal.py .  That's the 
algorithm.

>>> So I'd be interested in use cases people have where Routes alone 
>>> isn't enough.  I'm sure these exist (I could come up with a few), but 
>>> I'm also pretty sure there's other solutions than object publishing.
>>>
>>>>   - Its equivalent of the publisher doesn't attempt to traverse into
>>>>     *code* stored in the object graph.  All code is on the filesystem.
>>>>     The only thing in the object graph is "model" data.
>>>
>>> How do you deal with distinguishing between model instantiation data, 
>>> and traversal of the models themselves?  E.g., /archive/2008/06/ -- 
>>> /archive probably points to an Archive class or an instance 
>>> (singleton?) of ArchiveManager or something.  /2008 and /06 probably 
>>> translate to datetime(2008, 6) or something along those lines, which 
>>> provide the arguments to instantiate an Archive object, and then you 
>>> call some default method like index or __call__. 
>>
>> It's simpler than that.  We are always traversing model instances.  We 
>> just call __geitem__ the whole way through the model graph until we 
>> either exhaust the path or find an item that doesnt have a __getitem__ 
>> or whose __getitem__ raises a KeyError.  The "next" name becomes the 
>> view name.  Any leftover path elements become a "subpath".  It really 
>> is just block and tackle graph traversal of data, there's nothing 
>> tricky going on.
> 
> The views are code, yes?  How are view names mapped to code?  Presumably 
> you can have a URL ".../view" where the actual function called depends 
> on the type of object you traversed to.  The examples don't make this 
> clear. 


Views are code, yes.  Views are "controllers" in some vocabulary religions. 
View names are mapped to code via the "view registry".  This is the ZCML 
(ripped 
right out of the docs):


<configure xmlns="http://namespaces.zope.org/zope";
     xmlns:bfg="http://namespaces.repoze.org/bfg";>

   <!-- the default view for a MyModel -->
   <bfg:view
       for=".models.IMyModel"
       factory=".views.my_hello_view"
       permission="repoze.view"
       />

   <!-- the templated.html view for a MyModel -->
   <bfg:view
       for=".models.IMyModel"
       factory=".views.my_template_view"
       name="templated.html"
       permission="repoze.view"
       />

</configure>

The "for" and "factory" attributes above point to values that are package 
relative Python dotted-path names.  They indicate a view "type" in "for" (via 
an 
interface) and a "factory" (a view factory) via the dotted name to the actual 
view code.  The "view name" is the name.  The "permission" is a permission 
required to perform the action (if any security policy is defined).

Does this make more sense now?  Say yes even if it doesn't because this took a 
lot of typing. ;-)

- C
_______________________________________________
Repoze-dev mailing list
Repoze-dev@lists.repoze.org
http://lists.repoze.org/listinfo/repoze-dev

Reply via email to