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 >>> Repozefirstname.lastname@example.org >>> http://lists.repoze.org/listinfo/repoze-dev >>> >> >> _______________________________________________ >> Repoze-dev mailing list >> Repozeemail@example.com >> http://lists.repoze.org/listinfo/repoze-dev >> > > _______________________________________________ > Repoze-dev mailing list > Repozefirstname.lastname@example.org > http://lists.repoze.org/listinfo/repoze-dev _______________________________________________ Repoze-dev mailing list Repozeemail@example.com http://lists.repoze.org/listinfo/repoze-dev