Thanks David!

And also (thanks mostly to Reed O'Brien) I came to a resolution for the issues 
I 
wrote down in the email.  In short, I don't think anything needs to change from 
how things are on the trunk.

In excruciating detail however:

- Both <route> and <view> statements will continue to exist.

- I will create an advanced topics chapter for explaining how
   traversal and routing work together.

- <route> statements will require relative ordering.  I will explain
   this in advanced routes+traversal documentation by placing them
   in a separate routes.zcml file that has a "must be ordered!" comment
   and via narrative explanation.

- <view> statements will (optionally) name a route by its name
   via a route_name attribute.  I will explain that these do not
   need to be ordered by placing them in a separate views.zcml
   file within advanced routes+traversal documentation.

- We won't implement a <route><view/><view/></route> pattern for
   now (a route tag that contains associated views).  We'll keep
   this in our pocket for later should the need arise.

- C


On 6/16/09 7:15 PM, David Pratt wrote:
> Wow. I think you have done a pretty decent job of explaining it here
> Chris. I'd include your explanation, but as you say include it as an
> advanced concept. I have used both routing methods in same app and I am
> currently working with bfg 0.6.9. When I update my work, which will be
> soon, will be having to dig into this a bit more. I am glad you have
> documented this here at the very least and see value in putting it in
> the docs if not already there. Might be that folks won't get it but
> those that take time to understand both routing methods will find value
> and possibilities in what you have done. Many thanks.
>
> Regards,
> David
>
>
> On 11-Jun-09, at 11:10 PM, Chris McDonough wrote:
>
>> 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
>

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

Reply via email to