durusmail: quixote-users: Object traversal, a minefield
Object traversal, a minefield
2002-11-04
Object traversal, a minefield
Nicola Larosa
2002-11-04
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




reply