So now that this work is done, I'm having some major problems
explaining its finer points in documentation.  I'm a bit worried that
I'll not explain it satisfactorily, and that will cause support and
adoption issues later.  Sorry about writing the novel below.  I don't
really expect anybody to read this, much less reply, but maybe the act
of writing it will help me think about how to make changes that will
simplify things a bit.

On the BFG trunk, nothing much is different than BFG 0.8+ if you don't
try to use *both* routes and traversal within the same application.
In fact, almost all existing applications will run unchanged.  The
only ones that won't run unchanged are those that make use of a routes
"context factory".

An application that uses Routes exclusively to map URLs to code will
still have declarations like this:

   <route
     path=":foo/:bar"
     name="foobar"
     view=".views.foobar"
     />

   <route
     path=":baz/:buz"
     name="bazbuz"
     view=".views.bazbuz"
     />

In other words, each route typically corresponds with a single view
function, and when the route is matched during a request, the view
attached to it is invoked.  Typically, applications that use only URL
dispatch won't have any "view" statements in them.  Simple enough.

Under the hood, up until 0.9.1, when such route statements were
executed, we'd register a view for a special "IRoutesContext"
interface as the context interface and IRequest as a request interface
using the route name as the view name.  On the BFG trunk, however, we
ditched the "IRoutesContext" interface because we disused the concept
of "context factories" in favor of unifying the idea of a "root
factory" as something both traversal and URL dispatch can use.  So
instead when we run into view declarations like the above on the
trunk, we register a view for each route for the context interface
``None`` (implying any context) and a route-statement-specific
(dynamically-constructed) request interface type using the empty
string as the view name (implying the default view).  In either case,
the point is to make it so that the named view will only be called
when the route it's attached to actually matches, and in the simplest
case they are logically equivalent.

As before, an application that uses *traversal* exclusively to map
URLs to code just won't have any "route" declarations.  Instead, its
ZCML (or bfg_view decorators) will imply declarations that look like
this:

   <view
     name="foobar"
     view=".views.foobar"
     />

   <view
     name="bazbuz"
     view=".views.bazbuz"
     />

The above view statements register a view using the context interface
``None``, the IRequest request interface with a name matching the
name= argument.  The "foobar" view above will match the URL
/a/b/c/foobar or /foobar, etc, assuming that no view is named "a",
"b", or "c" during traversal.  Nothing about this has changed since,
well, forever.

No example applications that use both <route> and <view> declarations
within the same application really exist, but this has always been
possible, and it still is.  It works exactly how it did in 0.8+:

   <route
     path=":foo/:bar"
     name="foobar"
     view=".views.foobar"
     />

   <view
     name="bazbuz"
     view=".views.bazbuz
     />

In all versions of BFG after 0.8 (including the trunk), this will
register a ".views.foobar" view that will be invoked when the url
matches ":foo/:bar" and will register a view named "bazbuz" against
any context/request interface pair that will be invoked when no routes
match and the URL resolves the view name "bazbuz".

So far so good.

The shit starts to hit the fan when I try to explain how to use these
two concepts *together* in more interesting ways. The trunk
unification effort has made this possible.  Here is the catalog of
horrors.

1.  The "view" declaration has grown a "route_name" attribute.

On the trunk, the "view" declaration has sprouted a "route_name"
attribute.  It's meant to associate a particular view declaration with
a route, using the route's name, in order to indicate that the view
should *only be invoked when the route matches*.  For example:

   <route
     path="/abc"
     name="abc"
     view=".views.abc"
     />

   <view
     name="bazbuz"
     view=".views.bazbuz"
     route_name="abc"
     />

The above <view> declaration is completely useless, because the view
name will never be matched when the route it references matches.  Only
the view associated with the route itself (".views.abc") will ever be
invoked when the route matches, because the default view is always
invoked when a route matches and when no post-match traversal is
performed.  But, if you add a special token to the route's "path"
named "*traverse" that matches a path remainder, associating a <view>
statement with a <route> statement starts to make a bit more sense:

   <route
     path="/abc/*traverse"
     name="abc"
     view=".views.abc"
     />

   <view
     name="bazbuz"
     view=".views.bazbuz"
     route_name="abc"
     />

Under this circumstance, traversal is performed *after* the route
matches.  So a url like "/abc/bazbuz" (and potentially
"/abc/def/ghi/bazbuz") might be matched by the "bazbuz" view
declaration above, at least if the default root factory was willing to
traverse intermediate names.  The traversal path, respectively, for
each example I just mentioned, is "bazbuz" and "def/ghi/bazbuz".

It's pretty difficult to explain traversal in general.  People who
choose to use routes exclusively as a by-god framework choice just
don't care about traversal, and they never will.  I don't really care
too much about trying to explain traversal to these folks; they can
just use route statements without any "*traverse" token in the path
pattern, and they'll be quite happy.

But trying to explain traversal-after-route-match to people who
understand both concepts is difficult, because combining the two
concepts seems to break a law of "the magical number seven plus or
minus 2"
(http://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two),
at least for me.  These people need to understand 1) URL pattern
matching, 2) root factories and 3) the traversal algorithm, and the
interactions between all of them.  I'm not sure there is (or should
be) a solution to this, but it's definitely an advanced concept.  I
just don't know if I can explain it adequately in narrative
documentation to even bother.

Another hard thing to explain about the "route_name" attribute and
traversal-after-route-match even to people who do understand
traversal: a view that *doesn't* spell the route name won't match when
the route matches, even if it's defined in ZCML and seems like it
would otherwise. For example, in the below example, the "bazbuz" view
will never be invoked when the "abc" route matches even if the URL
ends with "bazbuz" and everything else indicates it would match:

   <route
     path="/abc/*traverse"
     name="abc"
     view=".views.abc"
     />

   <view
     name="bazbuz"
     view=".views.bazbuz"
     />

The "bazbuz" view won't match when the URL is "/abc/bazbuz" because
its declaration doesn't match: the "route_name" attribute is missing.
I'm thinking of changing this so that it *will* match.  This implies
deriving the route-specific request interface classes from
non-route-specific request interface classes, which isn't very hard to
do.  I just don't know that one is clearly better than the other,
really.

2. The "route" declaration's "view" attribute is now optional.

It's now possible to define a <route> statement that has no "view"
attribute.  In 0.8 - 0.9.1, this was not possible.

   <route
     path="/abc"
     name="abc"
     />

By itself, the above route statement is useless.  It will cause a
match when a request is processed, but since it isn't associated with
any view, a not found error will be returned unconditionally.
However, when you couple it with one or more views, it begins to make
sense:

   <route
     path="/abc"
     name="abc"
     />

   <view
     name=""
     view=".views.abc"
     route_name="abc"
     />

The above pair of declarations is actually logically equivalent to:

   <route
     path="/abc"
     name="abc"
     view=".views.abc"
     />

The reason we allow for the first (more verbose) form (and routes with
no "view" attribute) is to allow traversal to match views after
a routematch, ala:

   <route
     path="/abc/*traverse"
     name="abc"
     />

   <view
     name=""
     view=".views.abc"
     route_name="abc"
     />

   <view
     name="def"
     view=".views.def"
     route_name="abc"
     />

   <view
     name="ghi"
     view=".views.ghi"
     route_name="abc"
     />

We *could* maybe make this a bit more obvious by making <view>
declarations that are meant to match only a particular <route>
declarations into subdirectives or the <route>, ala:

   <route
     path="/abc/*traverse"
     name="abc"
     >

     <view
       name="def"
       view=".views.def"
       />

     <view
       name="ghi"
       view=".views.ghi"
       />

   </route>

.. but this would mean that people couldn't extend applications which
used post-routematch traversal with extra views within a separate ZCML
file.  I'm a bit loath to do *both* (the subdirective and the
route_name attribute).

3.  Route declarations need to come *before* view declarations which
name them in ZCML.

Currently, due to implementation vagaries, <route> directives that are
referred to by the ``route_name`` of <view> directives must *precede*
the view directive.  For example, this will work:

   <route
     path="/abc/*traverse"
     name="abc"
     />

   <view
     name=""
     view=".views.abc"
     route_name="abc"
     />

But this will raise an error at parse time:

   <view
     name=""
     view=".views.abc"
     route_name="abc"
     />

   <route
     path="/abc/*traverse"
     name="abc"
     />

I can probably solve this with enough elbow grease, but maybe not
soon.

4. Route statements need to be ordered relative to each other; view
statements don't.

<route> statement ordering is very important, because routes are
evaluated in a specific order, unlike traversal, which depends on
emergent behavior rather than an ordered list of directives.  It's
difficult to explain why this is the case.

5.  The "route" declaration can mention a "factory"

This has always been the case, but on the trunk, the "factory"
mentioned by a route statement now implies a "root factory", meaning
that it can potentially return something that can be traversed after a
route is matched.  For example, the following route declaration names
a factory:

   <route
    factory=".models.root_factory"
    path="/abc/*traverse"
    name="abc"
    />

The factory in .models.root_factory might look like so:

class Root:
     def __getitem__(self, name):
         if name == 'self':
             return self

def root_factory(environ):
     return Root()

The URL "/abc/foo" would try to invoke the "foo" view using the root
object as the view's context.  However, "/abc/self" would try to
invoke the default view against the root object after its __getitem__
had been called once.

This is really just an explaining-traversal problem I suppose

---

So... catalog of horrors over.  In conclusion....

One idea I had while writing this is to just deprecate the <route>
statement entirely and instead have only <view> directives, ala:

   <view
     route=":abc/:def"
     name=""
     view=".views.abc"
     />

   <view
     route=":abc/:def"
     name="foobar"
     view=".views.foobar"
     />

   <view
     route=":ghi/:jkl"
     name=""
     view=".views.abc"
     />

It would mean that <view> statements would grow a bunch of bullshit to
support all the extra "route" attributes, and it would make it
impossible to use the bfg_view decorator in complete symmetry with the
<view> ZCML declaration, because code definition order is usually
radically different than ZCML directive ordering (code definition
ordering is unimportant).  But it might be "an answer" to reducing
some complexity.



On 6/10/09 11:21 PM, Chris McDonough wrote:
> This work has now been done and merged into the trunk.  See
> http://svn.repoze.org/repoze.bfg/trunk/CHANGES.txt for more info.  I'll 
> probably
> release an alpha soon into the BFG "dev" index, maybe numbered something like
> "0.9.5" or so.
>
> - C
>
> On 6/5/09 11:33 AM, Chris McDonough wrote:
>> Paul and Tres recently taught a repoze.bfg tutorial at the Plone
>> Symposium at Penn State.  Tres mentioned to me that, by the reactions
>> of the tutorial attendees, he thought having two separate-but-equal
>> ways to do URL-to-code mapping (traversal vs. url dispatch/aka routes)
>> was too confusing.  He then suggested an alternative.
>>
>> Currently, a configured repoze.bfg application is in one of three
>> modes:
>>
>> - traversal-only, when a "root factory" is used but no routes are
>>      configured
>>
>> - routes-only, when routes are confgured but no root factory is used.
>>
>> - a hybrid model where if a route can't be matched, the system falls
>>      back to traversal from a single root.  In this mode, both a root
>>      factory and routes are configured.
>>
>> Tres' suggestion was essentially to cause BFG to always operate in a
>> hybrid mode, where a "root factory" was used to generate the context
>> object for views even when they are found via a route match instead of
>> via traversal.
>>
>> For example, let's say we had a root factory that looked like this:
>>
>>        class Root:
>>            pass
>>
>>        root = Root()
>>
>>        def root_factory(environ):
>>            return root
>>
>> .. and we configure it in to our BFG application like so:
>>
>> from repoze.bfg.router import make_app
>> from myapp.models import root_factory
>>
>> return make_app(root_factory)
>>
>> Currently the above "root_factory" callback is only used when the URL
>> is matched as a result of traversal.  But when any<route>   matches, it
>> is ignored.  Instead, when a<route>   matches:
>>
>> - If there's a "factory=" attribute on the route declaration, it names
>>      a "context factory".  The context factory is called when this route
>>      matches.
>>
>> - If there's no "factory=" attribute on the route declaration, a
>>      default routes context factory is used.
>>
>> But BFG never calls a "root factory" for an object matched via a route.
>>
>> In a system that operated under Tres' model, we'd essentially take
>> away the difference between a "root factory" and a "context factory".
>> Instead:
>>
>> - Each route will match a URL pattern.
>>
>> - If a route's URL pattern is matched on ingress, if the route has a
>>      "factory" attribute, the factory will be assumed to be a "root
>>      factory", and it will return a context object appropriate for that
>>      route.  If the route does *not* have a "factory" attribute, the
>>      "default root factory" would be used to compose the context.
>>
>> - There would be a "default root factory", used when no supplied route
>>      matches or when no "factory=" attribute was supplied along with a
>>      route statement.  What this boils down to is that the syste will
>>      have a "default route" will match any URL, but will be last in the
>>      route check ordering.  The default route will always use the
>>      "default root factory" as its factory.
>>
>> Benefits:
>>
>> - Makes the difference between an application that uses routes and one
>>      that doesn't far less pronounced.  Essentially, this change unifies
>>      the two models.
>>
>> - Adds the ability to do traversal through some set of names *after* a
>>      route is matched.  We'd allow some special signifier to be placed
>>      within a route path, ala "/foo/bar/*subpath"; we'd resolve the root
>>      related to "/foo/bar", then just traverse with the path info
>>      captured in "subpath".
>>
>> Risks:
>>
>> - If a "factory" is specified on a route, it will need to point at a
>>      function that had the same call/response convention as a traversal
>>      root factory.  This will break code.  "Context factories" accept
>>      key/value pairs assumed to be items that matched in the URL match.
>>      These would cease working, and would need to be rewritten as root
>>      factories, which accept a WSGI environment.
>>
>> - URL generation may become more difficult and costly.
>>
>> I'm apt to do this for 1.0, even at the risk of breaking code, because it 
>> does
>> nicely unify the traversal vs. routes story, which is definitely the
>> most up-in-the-air part of BFG today.
>>
>> Anybody have any objections?
>>
>> - C
>> _______________________________________________
>> Repoze-dev mailing list
>> Repoze-dev@lists.repoze.org
>> http://lists.repoze.org/listinfo/repoze-dev
>>
>
> _______________________________________________
> Repoze-dev mailing list
> Repoze-dev@lists.repoze.org
> http://lists.repoze.org/listinfo/repoze-dev
>

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

Reply via email to