new nevow know-how needs...;-)

Hi all -- I'm rather IRC-allergic, don't particularly love mailing lists, but apparently gmane should let me access this mlist via my newsreader (much, much better from my POV), so, let's see. With Rikard Anglerud, and help from Anders Hammarquist (aka Iko), we're developing a few web applications, and eventually a new framework for web applications, with/for AB Strakt's "CAPS" (see www.strakt.com), and we're basing our work entirely on nevow and other parts of twisted (CAPS already uses twisted for all communication among its layers, so the new approach already works much more smoothly than our "previous generation" web-apps based on Webware, IMHO). Over the last few days we moved our stuff from nevow's previous generation (the one that's part of quotient) to nevow's new, stand-alone incarnation. The amount of incompatible changes was staggering (serves us right for working with a new, hotly-developing framework, of course;-), but they do seem to be for the better, and we do have everything working again now (and having nevow as a stand-alone package does make the task of packaging it up with the rest of what CAPS needs _way_ easier, of course). I currently have _one_ serious problem understanding what's going on (or supposed to go on): nevow:data="whatever" directives. I don't understand the rules regarding WHEN this causes a lookup (e.g. of 'data_whatever' on a rend.Page subclass), WHAT it looks things up when it does do a lookup, WHEN it calls method 'data_whatever' thereafter, and with what arguments. Basically, I must be missing something important here -- it mostly feels that once I've used a nevow:data= directive, I lose control of what's going to happen and get nasty surprises. So, I'm gradually moving to using nevow.render= directives instead -- mixing the "model" aspect (getting the data on which rendering is to be based) with the "view" one (rendering it). Not nice, but with nevow.render I _do_ appear to have complete control, or maybe better expressed, _understanding_, of what's gonna happen... So, perhaps somebody can help me understand and use data directives in a more appropriate way? Thanks in advance! Alex

On Mar 1, 2004, at 6:02 AM, Alex Martelli wrote:
I currently have _one_ serious problem understanding what's going on (or supposed to go on): nevow:data="whatever" directives. I don't understand the rules regarding WHEN this causes a lookup (e.g. of 'data_whatever' on a rend.Page subclass), WHAT it looks things up when it does do a lookup, WHEN it calls method 'data_whatever' thereafter, and with what arguments. Basically, I must be missing something important here -- it mostly feels that once I've used a nevow:data= directive, I lose control of what's going to happen and get nasty surprises. So, I'm gradually moving to using nevow.render= directives instead -- mixing the "model" aspect (getting the data on which rendering is to be based) with the "view" one (rendering it). Not nice, but with nevow.render I _do_ appear to have complete control, or maybe better expressed, _understanding_, of what's gonna happen...
I just totally changed how this worked today. I'll describe what *now* happens, which is different from what used to happen: def data_foo(context, data): return 4 def render_foo(context, data): return context.tag['Hi: '+data] span(data=data_foo, renderer=render_foo) This will first call data_foo, returning '4'. data_foo's context will be the context of the span (context.tag is the span tag), without its IData remembered yet (thus, the data argument will be whatever the containing data is, in most cases, the rend.Page instance. Then, render_foo will be called, with the tag's context, again, but now with the result of data_foo's result (4) as 'data'. Thus, this will result in the string "<span>Hi: 4</span>". Now, take another example: number = 0 def data_count(context, data): global number number = number + 1 return number span(data=data_count)[str, ' ', str, ' ', str], ' ', span(data=data_count)[str, ' ', str, ' ', str] This will result in the string "<span>1 1 1</span> <span>2 2 2</span>". Note that returning deferreds from the data_ is now supported. In detail, the way this works: nevow.flat.flatstan.TagRenderer calls (quite paraphrased) "context.remember(convertToData(data, context), IData)" when it first sees a tag, right before it calls the function specified by the render= attribute. convertToData causes functions to be called, 'directive' statements to be looked up, and deferreds to be handled. Then, when the actual rendering function is called, it does context.locate(IData), which finds the previously remembered data, and passes it directly (no further fiddling is done!) to the data argument of the renderer. If you use a directive('name') form in a data=, this causes the current data to be adapted to IContainer before calling .get on it. There are currently adapters for dict, list, and tuple for this. Thus, def data_foo(context, data): return defer.succeed({'foo':1, 'bar':2}) span(data=data_foo)[span(data=directive('foo'))[str], span(data=directive('bar')[str]] Should work as expected, and call data_foo *once*, and produce "<span><span>1</span><span>2</span></span>". Hope this helps, James

James Y Knight wrote: ...
I just totally changed how this worked today. I'll describe what *now* happens, which is different from what used to happen:
def data_foo(context, data): return 4 def render_foo(context, data): return context.tag['Hi: '+data] span(data=data_foo, renderer=render_foo)
This will first call data_foo, returning '4'. data_foo's context will be the context of the span (context.tag is the span tag), without its IData remembered yet (thus, the data argument will be whatever the containing data is, in most cases, the rend.Page instance.
Then, render_foo will be called, with the tag's context, again, but now with the result of data_foo's result (4) as 'data'. Thus, this will result in the string "<span>Hi: 4</span>".
Wonderful! This new behavior, as you describe it, perfectly matches my intuition about "what _should_ happen".
Now, take another example: number = 0 def data_count(context, data): global number number = number + 1 return number
span(data=data_count)[str, ' ', str, ' ', str], ' ', span(data=data_count)[str, ' ', str, ' ', str]
This will result in the string "<span>1 1 1</span> <span>2 2 2</span>".
One more thing that feels just right -- two "calls" (data directives that mention data_count), two "executions" (actual calls to the function).
Note that returning deferreds from the data_ is now supported. In
Again, wonderful! I'll be able to dismantle my pesky "fake renderers" that basically dealt with data being intrinsically deferred.
detail, the way this works: nevow.flat.flatstan.TagRenderer calls (quite paraphrased) "context.remember(convertToData(data, context), IData)" when it first sees a tag, right before it calls the function specified by the render= attribute.
convertToData causes functions to be called, 'directive' statements to be looked up, and deferreds to be handled. Then, when the actual rendering function is called, it does context.locate(IData), which finds the previously remembered data, and passes it directly (no further fiddling is done!) to the data argument of the renderer.
Superb. Feels simple, direct, and predictable.
If you use a directive('name') form in a data=, this causes the current data to be adapted to IContainer before calling .get on it. There are currently adapters for dict, list, and tuple for this.
Hmmm, yes, I had to write an adapter to IContainer for my application's "business object" class just to redirect data lookups back to the resource, because this kind of thing just seemed to happen "when it felt like it":-).
Thus, def data_foo(context, data): return defer.succeed({'foo':1, 'bar':2})
span(data=data_foo)[span(data=directive('foo'))[str], span(data=directive('bar')[str]]
Should work as expected, and call data_foo *once*, and produce "<span><span>1</span><span>2</span></span>".
Hope this helps, James
Sure does! Thanks James. The one aspect that's still a bit obscure to me is, how is this distinction between "data=data_foo" (where I want to use function or method named data_foo as the data supplier) and "data=directive('bar')" (where I want the IContainer adaptation and call to .get) distinguished, _when the HTML comes from an on-disk template, rather than from a stan expression in my code_? My application is thoroughly based on on-disk templates -- I don't code much stan stuff at all, except basically for quick and dirty prototyping of stuff that's destined to move to an on-disk template soon anyway. So, won't every HTML construct coming from a template of some form such as: <span nevow:data="bar"> etc, etc </span> end up "compiled" into a "span(data=directive('bar'))[etc, etc]" anyway, leading me right back into subtle issues of "WHERE is this 'bar' going to be looked up, depending on phase of the moon etc"...? Or are there two different ways I should code that '<span nevow:data="bar">' in the on-disk template depending on whether I mean one thing or the other? Anyway, if I understand correctly, I'll now get this "new behavior" with just a cvs up on the (stand-alone) Nevow CVS, which I intend to do at once, so I can experiment. But, if I can get some explanation of how things are designed to work, this might help. Perhaps as small a change as "if the current IData remembrance is not adaptable to IContainer [and perhaps also if it is, but its .get raises?], just back off to 'external' IData remembrances until a satisfactory one is found" would be sufficient for my purposes, and -- if you can confirm there's no other solution that better matches the current intent of the code -- I could experiment with that kind of approach. Thanks again, Alex

On Mar 5, 2004, at 6:15 AM, Alex Martelli wrote:
So, won't every HTML construct coming from a template of some form such as:
<span nevow:data="bar"> etc, etc </span>
end up "compiled" into a "span(data=directive('bar'))[etc, etc]" anyway, leading me right back into subtle issues of "WHERE is this 'bar' going to be looked up, depending on phase of the moon etc"...? Or are there two different ways I should code that '<span nevow:data="bar">' in the on-disk template depending on whether I mean one thing or the other?
Welll, it doesn't depend on the phase of the moon. directives always get looked up on the first data it finds above it in the tree. I'm not sure exactly what your question is, but, is it the lack of being able to go back up the tree? e.g.: class MyPage: def data_foo(context, data): return {'bar':2, 'baz':3} def data_other(context, data): return 1 docFactory = xmlstr(""" <html> <span data="foo"> <span data="bar" render="string">This works fine.</span> <span data="other" render="string">This won't work because 'other' isn't a key in {'bar':2, 'baz':3}.</span> </span> </html>""")
Anyway, if I understand correctly, I'll now get this "new behavior" with just a cvs up on the (stand-alone) Nevow CVS, which I intend to do at once, so I can experiment.
Yep.
But, if I can get some explanation of how things are designed to work, this might help. Perhaps as small a change as "if the current IData remembrance is not adaptable to IContainer [and perhaps also if it is, but its .get raises?], just back off to 'external' IData remembrances until a satisfactory one is found" would be sufficient for my purposes, and -- if you can confirm there's no other solution that better matches the current intent of the code -- I could experiment with that kind of approach.
I dont think that's the right solution. Too magical. And, what if your data *is* a container but you still want to get to something outside it? One idea that's been proposed and that makes a lot of sense to me is a "../" syntax, so you could say data="../other" or "/other" in the example above. It just hasn't been implemented yet. James

James Y Knight wrote:
On Mar 5, 2004, at 6:15 AM, Alex Martelli wrote:
So, won't every HTML construct coming from a template of some form such as:
<span nevow:data="bar"> etc, etc </span>
end up "compiled" into a "span(data=directive('bar'))[etc, etc]" anyway, leading me right back into subtle issues of "WHERE is this 'bar' going to be looked up, depending on phase of the moon etc"...? Or are there two different ways I should code that '<span nevow:data="bar">' in the on-disk template depending on whether I mean one thing or the other?
Welll, it doesn't depend on the phase of the moon. directives always get looked up on the first data it finds above it in the tree. I'm not sure exactly what your question is, but, is it the lack of being able to go back up the tree? e.g.:
I guess, essentiallly, that's it -- except that in my experience with the previous mechanism it appeared to me that sometimes (unpredictably) the lookup happened on the IData set by a "SISTER" tag, rather than on the data set by a "mother" / "grandmother" one. If now the lookup is always strictly "upwards" (towards the root of the tree without exceptions), that, at least, is progress, even though I'll still have to do lots of data fetching in renderers (mixing view and model). Basically, assuming that's how it always works now, I have to consider that putting a data fetch in a data directive "poisons the well" for a whole subtree -- there is no "going back", ever. If there is any possibility at all that the templates-author (a web designer separate from the programmer who codes the logic behind the templates) may want to get to such other data suppliers, "hidden" by the current mechanism, then I have to code render methods that know how to get their own data.
class MyPage: def data_foo(context, data): return {'bar':2, 'baz':3} def data_other(context, data): return 1 docFactory = xmlstr(""" <html> <span data="foo"> <span data="bar" render="string">This works fine.</span> <span data="other" render="string">This won't work because 'other' isn't a key in {'bar':2, 'baz':3}.</span> </span> </html>""")
Right. calling .get("other") on that dictionary returns None, and, that's it: the situation becomes irretrievable in the current scheme (even though in a much clearer way than it was in the previous one).
Anyway, if I understand correctly, I'll now get this "new behavior" with just a cvs up on the (stand-alone) Nevow CVS, which I intend to do at once, so I can experiment.
Yep.
I have indeed experimented, with my adapter still in place and print statements in said adapter to track when it's called; the number of occurrences has dropped from many dozens to just a couple in a typical rendering pass -- real progress, but still a LITTLE to go.
as "if the current IData remembrance is not adaptable to IContainer [and perhaps also if it is, but its .get raises?], just back off to 'external' IData remembrances until a satisfactory one is found" would be ... I dont think that's the right solution. Too magical. And, what if your data *is* a container but you still want to get to something outside it? One idea that's been proposed and that makes a lot of sense to me is a "../" syntax, so you could say data="../other" or "/other" in the example above. It just hasn't been implemented yet.
It seems to me that, while a syntax to explicitly access "overridden names" (of data suppliers) is helpful in some corner cases, the fundamental mechanism should still be "if a name has not been overridden it's still accessible" -- by analogy with OO programming, and also with CSS mechanics (the latter may be more accessible to my target audience -- template authors who are fundamentally web designers, rather than programmers). So, basically, I'm thinking of IData remembrances as being "in a stack". When a data directive lookup happens, it should walk up the stack, trying, for each different IData remembrance it meets, in order, to perform: new_data = IContainer(said_data).get('new_name') Instead of giving up if this raises, or binds new_data to None, such failures, it seems to me, should just mean one keeps walking up the stack. I don't see this as 'too magical' any more than I consider OO name lookups (and in a single-inheritance scenario, too, the very simplest!) to be so (CSS is more complicated and thus might be 'too magical' for some, yet web designers are mastering THAT complicated name-lookup scenario already...). Guess I'll try to play with these concepts in my copious spare time and see if I can grasp enough of the remembrance and lookup mechanisms to implement a sandbox version of this. Any suggestions of where to focus in the nevow sources? Thanks, Alex

On Mar 6, 2004, at 3:07 AM, Alex Martelli wrote:
If there is any possibility at all that the templates-author (a web designer separate from the programmer who codes the logic behind the templates) may want to get to such other data suppliers, "hidden" by the current mechanism, then I have to code render methods that know how to get their own data.
You could also write a render method that retrieves the root data (the page), and remembers it as IData. Then any data directive inside will use the Page as the container and call one of its data_ methods. I would consider this a horrible hack, but it will work; a path solution for directive() would be much cleaner.
I have indeed experimented, with my adapter still in place and print statements in said adapter to track when it's called; the number of occurrences has dropped from many dozens to just a couple in a typical rendering pass -- real progress, but still a LITTLE to go.
I'm not sure I understand. The function will be called exactly once per render of the associated tag. Anything else is a bug.
It seems to me that, while a syntax to explicitly access "overridden names" (of data suppliers) is helpful in some corner cases, the fundamental mechanism should still be "if a name has not been overridden it's still accessible" -- by analogy with OO programming, and also with CSS mechanics (the latter may be more accessible to my target audience -- template authors who are fundamentally web designers, rather than programmers).
In programming, the set of local names is generally not dynamic. With data, like this, it is. Think of the result of a data_users. The user names can be anything. It would be extremely dangerous to do '<span data="usernames"><span data="title">' expecting the page's data_title method to be called -- what if a user was later created with username 'title'?
new_data = IContainer(said_data).get('new_name')
Minor correction -- that is IContainer(said_data).child('new_name').
Guess I'll try to play with these concepts in my copious spare time and see if I can grasp enough of the remembrance and lookup mechanisms to implement a sandbox version of this. Any suggestions of where to focus in the nevow sources?
The policy is implemented completely in the short function "nevow.accessors.DirectiveAccessor". You could use things like context.locate(inevow.IData, depth=numDotDots+1) to search go up the context stack, or context.locate(inevow.IResource) to support an initial '/'. James

James Y Knight wrote:
... It would be extremely dangerous to do '<span data="usernames"><span data="title">' expecting the page's data_title method to be called -- what if a user was later created with username 'title'?
Aren't you confusing variable names with values? I would think what you cite here would only be a problem if what was called "usernames" was later called "title" -- which would be a problem in any event. Steve

On Mar 6, 2004, at 3:57 PM, Stephen Waterbury wrote:
Aren't you confusing variable names with values? I would think what you cite here would only be a problem if what was called "usernames" was later called "title" -- which would be a problem in any event.
No -- given a page with the following functions: def data_users(context, data): return {'foom': 'James Knight", 'mg': 'Matt Goodall', 'title': 'Bogus User'} def data_title(context, data): return "My Title" You ask it to render this: <span data="users"><span data="title">.....</span></span> First, it will lookup the child 'users' from the current data (the Page instance). This will be the function data_users, which will be called, and return the dictionary. Now, the current data is the abovementioned dict. Now, it looks up the child 'title' from the current data (the dict). This will return 'Bogus User', which is not at all what you wanted! James

James Y Knight wrote:
On Mar 6, 2004, at 3:57 PM, Stephen Waterbury wrote:
Aren't you confusing variable names with values? I would think what you cite here would only be a problem if what was called "usernames" was later called "title" -- which would be a problem in any event.
No -- given a page with the following functions:
def data_users(context, data): return {'foom': 'James Knight", 'mg': 'Matt Goodall', 'title': 'Bogus User'} def data_title(context, data): return "My Title"
You ask it to render this: <span data="users"><span data="title">.....</span></span>
First, it will lookup the child 'users' from the current data (the Page instance). This will be the function data_users, which will be called, and return the dictionary. Now, the current data is the abovementioned dict. Now, it looks up the child 'title' from the current data (the dict). This will return 'Bogus User', which is not at all what you wanted!
Thanks, James -- I see now, and I appreciate your patience! :) I must say that for me this is rather non-intuitive. Cheers, Steve

On Mar 6, 2004, at 5:03 PM, Stephen Waterbury wrote:
Thanks, James -- I see now, and I appreciate your patience! :) I must say that for me this is rather non-intuitive.
Well, how would you like it to work? It needs to support contextual data. Consider the following (actually working) example, which in my experience is more common: from nevow import inevow, rend class User: __implements__ = inevow.IContainer allowableAttrs = ('name', 'password') def __init__(self, name, password): self.name=name self.password=password def child(self, context, name): if name in self.allowableAttrs: return getattr(self, name) class MyPage(rend.Page): def data_users(self, context, data): return (User('James Knight', 'twiddle'), User('Matt Goodall', 'twoddle'), User('Bogus User', '1')) def data_title(self, context, data): return "My Title" docFactory = rend.xmlstr( """<ul nevow:data="users" nevow:render="sequence"> <li nevow:pattern="item"> Name: <span nevow:data="name" nevow:render="string" /> Password: <span nevow:data="password" nevow:render="string" /> </li> </ul>""") from twisted.application import service,internet from nevow import appserver application = service.Application("disktemplates") site = appserver.NevowSite(MyPage()) internet.TCPServer(8080, site).setServiceParent(application)
participants (3)
-
Alex Martelli
-
James Y Knight
-
Stephen Waterbury