I didn't expect object traversal to be so... dangerous. If I'm still here to
talk about it :^) , that's only because of this thread (bless those mailing
list archives!):
http://mail.mems-exchange.org/pipermail/quixote-users/2002-January/000222.html
I will expose the various stages of my tribulations by means of a silly,
contrived example, with working (or not) code.
Let's say we have two object, Folder and Directory, handling similar file
system data. Both objects will have a "view" method, listing the contents of
the self object. The Folder object will also have a "show" method, showing
the contents of a File object whose index we will give in the request path.
***
First try. Let's go straight to an object, and then to a method.
Here is the code:
_q_exports = ["Folder", "Directory"]
class Folder:
_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? :^(
***
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"
class Directory:
_q_exports = ["view"]
def __init__(self, request, name):
print "Directory init,", name
def view(self, request):
print "Directory view"
return "Listing of Directory contents here"
Here are the URLs:
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.
***
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
Folder getname, 1
Folder init, 1
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.
***
Fourth try. To avoid it, we move the computation of the index to the
_q_getname function, and make it return the self object.
Here is the code (only the __init__ and the _q_getname methods, the rest is
the same):
class Folder: # just part of it
def __init__(self, request, name):
print "Folder init,", name
pass
def _q_getname(self, request, name):
print "Folder getname,", name
try:
self.idx = int(name)
except ValueError:
self.idx = 0
return self(request, name)
The URL is the same:
http://localhost/QuixoTest/Folder/1/show
Here is the response:
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.
***
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.
Here is the whole code, again:
from quixote.errors import TraversalError
_q_exports = []
def _q_getname(request, name):
print "\nfirst getname,", name
if name == "Folder":
return Folder()
elif name == "Directory":
return Directory()
else:
raise TraversalError, \
"Unknown path element: %s" % name
class Folder:
_q_exports = ["view", "show"]
def __init__(self):
print "Folder init"
def _q_getname(self, request, name):
print "Folder getname,", name
try:
self.idx = int(name)
except ValueError:
self.idx = 0
return self
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"
class Directory:
_q_exports = ["view"]
def __init__(self):
print "Directory init"
def view(self, request):
print "Directory view"
return "Listing of Directory contents here"
Same URL:
http://localhost/QuixoTest/Folder/1/show
And here is the debug log:
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. :^)
In any case, I feel that something about all this should go into the docs,
adequately amended of my mistakes and misunderstandings.
--
"We should forget about small efficiencies, about 97% of the time.
Premature optimization is the root of all evil." Donald Knuth
Nicola Larosa - nico@tekNico.net