Welcome, guest | Sign In | My Account | Store | Cart

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, 118 lines
  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

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.

1 comment

Jim Eggleston 17 years, 10 months ago  # | flag

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.

Created by Costas Malamas on Mon, 22 May 2006 (PSF)
Python recipes (4591)
Costas Malamas's recipes (1)

Required Modules

Other Information and Tasks