2 # $Id: NameMapper.py,v 1.30 2007/04/03 01:58:20 tavis_rudd Exp $
4 """This module supports Cheetah's optional NameMapper syntax.
7 ================================================================================
9 NameMapper provides a simple syntax for accessing Python data structures,
10 functions, and methods from Cheetah. It's called NameMapper because it 'maps'
11 simple 'names' in Cheetah templates to possibly more complex syntax in Python.
13 Its purpose is to make working with Cheetah easy for non-programmers.
14 Specifically, non-programmers using Cheetah should NOT need to be taught (a)
15 what the difference is between an object and a dictionary, (b) what functions
16 and methods are, and (c) what 'self' is. A further aim (d) is to buffer the
17 code in Cheetah templates from changes in the implementation of the Python data
18 structures behind them.
20 Consider this scenario:
22 You are building a customer information system. The designers with you want to
23 use information from your system on the client's website --AND-- they want to
24 understand the display code and so they can maintian it themselves.
26 You write a UI class with a 'customers' method that returns a dictionary of all
27 the customer objects. Each customer object has an 'address' method that returns
28 the a dictionary with information about the customer's address. The designers
29 want to be able to access that information.
31 Using PSP, the display code for the website would look something like the
32 following, assuming your servlet subclasses the class you created for managing
35 <%= self.customer()[ID].address()['city'] %> (42 chars)
37 Using Cheetah's NameMapper syntax it could be any of the following:
39 $self.customers()[$ID].address()['city'] (39 chars)
41 $customers()[$ID].address()['city']
43 $customers()[$ID].address().city
45 $customers()[$ID].address.city
47 $customers()[$ID].address.city
49 $customers[$ID].address.city (27 chars)
52 Which of these would you prefer to explain to the designers, who have no
53 programming experience? The last form is 15 characters shorter than the PSP
54 and, conceptually, is far more accessible. With PHP or ASP, the code would be
55 even messier than the PSP
57 This is a rather extreme example and, of course, you could also just implement
58 '$getCustomer($ID).city' and obey the Law of Demeter (search Google for more on that).
59 But good object orientated design isn't the point here.
62 ================================================================================
63 The parenthesized letters below correspond to the aims in the second paragraph.
68 NameMapper allows access to items in a dictionary using the same dotted notation
69 used to access object attributes in Python. This aspect of NameMapper is known
70 as 'Unified Dotted Notation'.
72 For example, with Cheetah it is possible to write:
73 $customers()['kerr'].address() --OR-- $customers().kerr.address()
74 where the second form is in NameMapper syntax.
76 This only works with dictionary keys that are also valid python identifiers:
77 regex = '[a-zA-Z_][a-zA-Z_0-9]*'
83 NameMapper automatically detects functions and methods in Cheetah $vars and calls
84 them if the parentheses have been left off.
86 For example if 'a' is an object, 'b' is a method
91 If b returns a dictionary, then following variations are possible
92 $a.b.c --OR-- $a.b().c --OR-- $a.b()['c']
93 where 'c' is a key in the dictionary that a.b() returns.
96 * NameMapper autocalls the function or method without any arguments. Thus
97 autocalling can only be used with functions or methods that either have no
98 arguments or have default values for all arguments.
100 * NameMapper only autocalls functions and methods. Classes and callable object instances
101 will not be autocalled.
103 * Autocalling can be disabled using Cheetah's 'useAutocalling' setting.
105 LEAVING OUT 'self' (c,d)
106 ------------------------
108 NameMapper makes it possible to access the attributes of a servlet in Cheetah
109 without needing to include 'self' in the variable names. See the NAMESPACE
110 CASCADING section below for details.
112 NAMESPACE CASCADING (d)
116 Implementation details
117 ================================================================================
119 * NameMapper's search order is dictionary keys then object attributes
121 * NameMapper.NotFound is raised if a value can't be found for a name.
123 Performance and the C version
124 ================================================================================
126 Cheetah comes with both a C version and a Python version of NameMapper. The C
127 version is significantly faster and the exception tracebacks are much easier to
128 read. It's still slower than standard Python syntax, but you won't notice the
129 difference in realistic usage scenarios.
131 Cheetah uses the optimized C version (_namemapper.c) if it has
132 been compiled or falls back to the Python version if not.
135 ================================================================================
136 Authors: Tavis Rudd <tavis@damnsimple.com>,
137 Chuck Esterbrook <echuck@mindspring.com>
138 Version: $Revision: 1.30 $
139 Start Date: 2001/04/03
140 Last Revision Date: $Date: 2007/04/03 01:58:20 $
142 from __future__
import generators
143 __author__
= "Tavis Rudd <tavis@damnsimple.com>," +\
144 "\nChuck Esterbrook <echuck@mindspring.com>"
145 __revision__
= "$Revision: 1.30 $"[11:-2]
147 from types
import StringType
, InstanceType
, ClassType
, TypeType
148 from pprint
import pformat
151 _INCLUDE_NAMESPACE_REPR_IN_NOTFOUND_EXCEPTIONS
= False
152 _ALLOW_WRAPPING_OF_NOTFOUND_EXCEPTIONS
= True
153 __all__
= ['NotFound',
157 'valueFromSearchList',
158 'valueFromFrameOrSearchList',
163 ## N.B. An attempt is made at the end of this module to import C versions of
164 ## these functions. If _namemapper.c has been compiled succesfully and the
165 ## import goes smoothly, the Python versions defined here will be replaced with
168 class NotFound(LookupError):
171 def _raiseNotFoundException(key
, namespace
):
172 excString
= "cannot find '%s'"%key
173 if _INCLUDE_NAMESPACE_REPR_IN_NOTFOUND_EXCEPTIONS
:
174 excString
+= ' in the namespace %s'%pformat(namespace
)
175 raise NotFound(excString
)
177 def _wrapNotFoundException(exc
, fullName
, namespace
):
178 if not _ALLOW_WRAPPING_OF_NOTFOUND_EXCEPTIONS
:
182 if excStr
.find('while searching')==-1: # only wrap once!
183 excStr
+=" while searching for '%s'"%fullName
184 if _INCLUDE_NAMESPACE_REPR_IN_NOTFOUND_EXCEPTIONS
:
185 excStr
+= ' in the namespace %s'%pformat(namespace
)
189 def hasKey(obj
, key
):
190 """Determine if 'obj' has 'key' """
191 if hasattr(obj
,'has_key') and obj
.has_key(key
):
193 elif hasattr(obj
, key
):
198 def valueForKey(obj
, key
):
199 if hasattr(obj
, 'has_key') and obj
.has_key(key
):
201 elif hasattr(obj
, key
):
202 return getattr(obj
, key
)
204 _raiseNotFoundException(key
, obj
)
206 def _valueForName(obj
, name
, executeCallables
=False):
207 nameChunks
=name
.split('.')
208 for i
in range(len(nameChunks
)):
210 if hasattr(obj
, 'has_key') and obj
.has_key(key
):
212 elif hasattr(obj
, key
):
213 nextObj
= getattr(obj
, key
)
215 _raiseNotFoundException(key
, obj
)
217 if (executeCallables
and callable(nextObj
)
218 and (type(nextObj
) not in (InstanceType
, ClassType
))):
224 def valueForName(obj
, name
, executeCallables
=False):
226 return _valueForName(obj
, name
, executeCallables
)
228 _wrapNotFoundException(e
, fullName
=name
, namespace
=obj
)
230 def valueFromSearchList(searchList
, name
, executeCallables
=False):
231 key
= name
.split('.')[0]
232 for namespace
in searchList
:
233 if hasKey(namespace
, key
):
234 return _valueForName(namespace
, name
,
235 executeCallables
=executeCallables
)
236 _raiseNotFoundException(key
, searchList
)
238 def _namespaces(callerFrame
, searchList
=None):
239 yield callerFrame
.f_locals
241 for namespace
in searchList
:
243 yield callerFrame
.f_globals
246 def valueFromFrameOrSearchList(searchList
, name
, executeCallables
=False,
248 def __valueForName():
250 return _valueForName(namespace
, name
, executeCallables
=executeCallables
)
252 _wrapNotFoundException(e
, fullName
=name
, namespace
=searchList
)
255 frame
= inspect
.stack()[1][0]
256 key
= name
.split('.')[0]
257 for namespace
in _namespaces(frame
, searchList
):
258 if hasKey(namespace
, key
): return __valueForName()
259 _raiseNotFoundException(key
, searchList
)
263 def valueFromFrame(name
, executeCallables
=False, frame
=None):
264 # @@TR consider implementing the C version the same way
265 # at the moment it provides a seperate but mirror implementation
266 # to valueFromFrameOrSearchList
269 frame
= inspect
.stack()[1][0]
270 return valueFromFrameOrSearchList(searchList
=None,
272 executeCallables
=executeCallables
,
277 def hasName(obj
, name
):
278 #Not in the C version
279 """Determine if 'obj' has the 'name' """
280 key
= name
.split('.')[0]
281 if not hasKey(obj
, key
):
284 valueForName(obj
, name
)
289 from _namemapper
import NotFound
, valueForKey
, valueForName
, \
290 valueFromSearchList
, valueFromFrameOrSearchList
, valueFromFrame
291 # it is possible with Jython or Windows, for example, that _namemapper.c hasn't been compiled
296 ##################################################
301 def valueForName(self
, name
):
302 return valueForName(self
, name
)
304 def valueForKey(self
, key
):
305 return valueForKey(self
, key
)
307 ##################################################
308 ## if run from the command line ##
312 classVar
= 'classVar val'
313 def method(self
,arg
='method 1 default arg'):
316 def method2(self
, arg
='meth 2 default arg'):
319 def method3(self
, arg
='meth 3 default'):
323 classBvar
= 'classBvar val'
326 a
.one
= 'valueForOne'
327 def function(whichOne
='default'):
329 'default': 'default output',
330 'one': 'output option one',
331 'two': 'output option two'
333 return values
[whichOne
]
339 'subDict': {'nestedMethod':a
.method3
}
341 b
= 'this is local b'
343 print valueForKey(a
.dic
,'subDict')
344 print valueForName(a
, 'dic.item')
345 print valueForName(vars(), 'b')
346 print valueForName(__builtins__
, 'dir')()
347 print valueForName(vars(), 'a.classVar')
348 print valueForName(vars(), 'a.dic.func', executeCallables
=True)
349 print valueForName(vars(), 'a.method2.item1', executeCallables
=True)
351 if __name__
== '__main__':