ActiveState Code

Recipe 496730: QuackTemplate.Wrapper


QuackTemplate.Wrapper is a template helper class: it takes arbitrary Python objects and uses__gettitem__ so that the wrapped object can be passed into the standard Python string substitution mechanism. It can follow method calls, dict keys and list members (to some extent).

Python
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
"""
A quick-and-dirty template helper, using Python's "duck"-typing approach.

Usage: Wrap an object in QuackTemplate.Wrapper, and use it with a normal
       Python template, like so:
      
       print "%(attribute)s" % Wrapper(myObj)

       See __main__ below for test cases and examples.

       NB: QuackTemplate can follow encapsulated objects, lists and dicts,
           but it can only call methods WITH ZERO arguments.

Costas Malamas 2006.02.22
"""
import types

class Wrapper:
   def __init__(self, obj):
      self.obj = obj

   def __repr__(self):
      return str(self.obj)

   def __getitem__(self, key):
      #
      # Null key, same as repr of self
      #
      if not key:
         return str(self)
      #
      # If there is a dereference, split the call
      #
      rem = None
      idx = None
      if "." in key:
         key, rem = key.split('.', 1) 
      if '#' in key:
         key, idx = key.split('#', 1)
      try:
         result = getattr(self.obj, key)
      except AttributeError:
         #
         # Allow for last-minute additions to wrappers; if a key doesn't 
         # exist in the wrapped object, first test ourselves, then delegate
         #
         if self.__dict__.get(key):
            result = self.__dict__[key]
         else:
            try:
               result = self.obj[key]
            except:
               raise Exception, "Key '%s' (remainder: '%s') not found in object of type %s!" %\
                  (key, rem, self.obj.__class__)
      except Exception, mesg:
         print "Wrapper %s with key %s and remainder %s" % (str(self), key, rem)
         print mesg
         raise Exception

      #
      # If calling a member instance, delegate to it!
      #
      if type(result) == types.InstanceType:
         return Wrapper(result)[rem]
      #
      # A method could return either another object
      # or a result, so delegate the interim result
      #
      elif type(result) == types.MethodType:
         interim = result()
         return Wrapper(interim)[rem]
      elif idx and type(result) == types.ListType:
         return Wrapper(result[int(idx)])[rem]
      elif rem:
         return Wrapper(result[rem])
      return Wrapper(result)[rem]

if __name__ == "__main__":
   class Test:
      def __init__(self):
         self.var    = "a String!"
         self.intvar = 20
         self.data = [1, 2]
         self.lut = {"key" : 999}

      def get(self):
         return "from get()!"

      def factory(self):
         return Test()

      def __repr__(self):
         return "A Test instance!"

   z = Test()
   z.child = Test()
   z.child.child = Test()
   z.child.children = [Test(), Test()]
   wz = Wrapper(z)
   zz = Wrapper(Test())
   wz.whatever = Wrapper(zz)

   print "String attribute: %(var)s" % wz
   print "Integer attribute: %(intvar)s" % wz
   print "Method call: %(get)s" % wz
   print "Attribute of a child: %(child.var)s" % wz
   print "Method of a grand-child: %(child.child.get)s" % wz
   print "Method of a generated object: %(child.factory.get)s" % wz
   print "Repr of a generated object: %(child.child)s" % wz
   print "Item of an encaps'ed list object: %(child.data#1)s" % wz
   print "Value of a generated object's dict: %(child.lut.key)s" % wz
   print "Method of an encaps'ed obj in a list: %(child.children#1.get)s" % wz
   print "Dynamic addition to a wrapper: %(whatever.get)s" % wz

   x = {"one": Wrapper(Test()), "two" : 4}
   wx = Wrapper(x)
   print "Object method in a dict: %(one.get)s" % wx
   print "Value in a dict: %(two)s" % wx

Discussion

I've found that my Python-based templates keep growing and growing and at some point there's just too many things I'd like to pass in for template substitution. So, this light-weight wrapper is just a convenient intermediary for sending large objects into standard Python string substitution.

Comments

  1. 1. At 2:37 p.m. on 22 may 2006, Jim Eggleston said:

    Great solution to something I often want to do. I often wish objects would work with string formats. I normally add __getitem__ to my own classes, but it is a nuisance overriding other people's classes just to make them work with string formats. Your class solves the problem nicely.

Sign in to comment