On Mon, Nov 04, 2002 at 03:28:16PM +0100, Nicola Larosa wrote: > I didn't expect object traversal to be so... dangerous. Since I've had my own sometimes interesting learning experiences with the publisher's resolution methods recently, I thought I'd offer some comments. Although I have read (well, skimmed, in spots) your whole treatise, I'm going to comment on things as they come up, mostly. I think. :-) Sometimes my fingers have a different agenda; sometimes I am forced to admit they've improved on my original plan. Markets are fire, but writing is learning. Needless to say, I'll be cutting pretty vigorously. > First try. Let's go straight to an object, and then to a method. > > Here is the code: > > _q_exports = ["Folder", "Directory"] > > class Folder: First problem is right here: you're giving Quixote the class object, not an instance of the class. Now, an object's an object (at least until you try to access it), but I'm pretty sure that's not what you intended to do! > _q_exports = ["view"] > > def __init__(self, request): > pass > > def view(self, request): > return "Listing of Folder contents here" > > Here is the URL: > > http://localhost/QuixoTest/Folder/view > > Here is the response: > > error: Server Error: exceptions.TypeError, unbound method view() must be > called with Folder instance as first argument (got HTTPRequest instance > instead): file: /opt/python/lib/python2.2/site-packages/quixote/publish.py > line: 572 > > Well, yes, of course, but how do I do that? :^( First you need to provide an instance of Folder to Quixote. Since you get around to that later on, I'll just sketch one simple hack to provide access to an object's method function without requiring the immediate caller to know about the instance object. First, assume that your view(self, request) is renamed to _view. Then you want something like this: def __init__(self, request): view = lambda r: self._view(r) Or for 2.1 and earlier, without the nested scope change, the usual hack: def __init__(self, request): view = lambda r,s=self: s._view(r) This leaves a bound function taking only the request argument that Quixote will find when it looks for "view" in the instance's __dict__. The trick with the lambda curries the class method to make a new function whose interface matches what Quixote expects in a published function. > Second try. This time we'll use _q_getname to get an object back into the > traversal. > > Here is the code: > > from quixote.errors import TraversalError > > _q_exports = [] > > def _q_getname(request, name): > print "first getname,", name > if name == "Folder": > return Folder(request, name) > elif name == "Directory": > return Directory(request, name) > else: > raise TraversalError, \ > "Unknown path element: %s" % name > > class Folder: > _q_exports = ["view"] > > def __init__(self, request, name): > print "Folder init,", name > > def view(self, request): > print "Folder view" > return "Listing of Folder contents here" > http://localhost/QuixoTest/Folder/view > http://localhost/QuixoTest/Directory/view > > They both work. Anyway, such a usage of _q_getname looks like a kludge to me. I have to admit that I'm puzzled, because this shouldn't work according to the logic of the previous example. :-( Unless to Quixote there *is* a difference between a class object and an instance object... [makes note to review, see if I've forgotten something along these lines.] Anyway, yes, using _q_getname is a bit of a hack here, but the alternative would be to have, say 'class Folder_implementation' and a function like this: def Folder(request): return Folder_implementation(request) THen you should be able to go back to using _q_exports rather than _q_getname to create the proper instance object. > Third try. Now we'll put that Integer demo to use. > > Here is the code of the new Folder class (the global _q_exports and > _q_getname, and the Directory class, are the same as before): > > class Folder: > _q_exports = ["view", "show"] > > def __init__(self, request, name): > print "Folder init,", name > try: > self.idx = int(name) > except ValueError: > self.idx = 0 > pass > > def _q_getname(self, request, name): > print "Folder getname,", name > return Folder(request, name) > > def view(self, request): > print "Folder view" > return "Listing of Folder contents here" > > def show(self, request): > print "Folder show,", self.idx > if self.idx: > return "Contents of File #%s here" % self.idx > else: > return "No File specified" > > Here is the URL: > > http://localhost/QuixoTest/Folder/1/show > > This works, too. Alas, the debug log shows this: > > first getname, Folder > Folder init, Folder This is what you asked it to do: global _q_getname creates an instance of Folder. The next component is processed by that instance's _q_getname: > Folder getname, 1 > Folder init, 1 Again, it has been told to create a new Folder instance, so it does. If it's any consolation, the first Folder instance will be discarded, probably (I'm guessing, haven't grovelled the sources) as soon as the new one is returned to the publisher's parser. > Folder show, 1 > > An unnecessary Folder object has been created, and its init code has tried > to convert the class name to an integer. It would be nice if this could be > avoided. The root of the problem is that you're using the Folder object typr for two different things, but have only defined sensible behavior for one of them. There are several ways to resolve this: I would be inclined to have the global _q_getname (or the simple function Folder as suggested previously) return a Folder_imp object that would have self.path = '' in its __init__. its _q_getname would append the current component to this internal path (add checking for legal cases as desired), and then when it hit a 'view' or 'show' component... well, maybe this doesn't generalize so well that way in general! Anyway, that would be much the same. If you're certain you only want to allow single-component path names that are positive integers, then you could initialize self.idx to zero and use a check that and the current component for legality. > Fourth try. > def _q_getname(self, request, name): > print "Folder getname,", name > try: > self.idx = int(name) > except ValueError: > self.idx = 0 > return self(request, name) > error: Server Error: exceptions.AttributeError, Folder instance has no > __call__ method: file: /home/nl/Araknos/aKab/aKab/Web/__init__.py line: 35 > > Well, well. Python tried to call the self object. Uhm, you *told* it to. 'self(request, name)' > Fifth try. We want to return the self object without calling it, so let's > take away those parameters, unnecessary anyway. We will have to touch the > global _q_getname too, since it creates the Folder object. > def _q_getname(self, request, name): > print "Folder getname,", name > try: > self.idx = int(name) > except ValueError: > self.idx = 0 > return self > first getname, Folder > Folder init > Folder getname, 1 > Folder show, 1 > > That's fine. It wasn't easy, but worth it. I post all this to soothe that > nagging feeling that there's something else so obvious that I can't see it. :^) Since Folder._q_getname is only called for the URL component *past* 'Folder', I would think that the ValueError case would be an error state. Perhaps initialize idx to an illegal value (would -1 work here?), and make Folder._q_getname something like this: try: val = int(name) except: return an error [page]: invalid folder selector if self.idx >= 0: return an error: multiple selectors self.idx = val And presumably you need to check for the missing selector case in view and show. Well, this has been interesting. I'm not sure if this is yet another way to structure a Quixote tree or if it's just a very different view of things. Hope I've been of some help to you! -- Anyone who says you can have a lot of widely dispersed people hack away on a complicated piece of code and avoid total anarchy has never managed a software project. -- Andy Tanenbaum