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