pyTivo
[pyTivo/krkeegan.git] / Cheetah / NameMapper.py
blob2d6d95314e06aae0b1114db85cd1c5828caafd50
1 #!/usr/bin/env python
2 # $Id: NameMapper.py,v 1.29 2006/01/15 20:27:42 tavis_rudd Exp $
4 """This module supports Cheetah's optional NameMapper syntax.
6 Overview
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
33 customer information:
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)
40 --OR--
41 $customers()[$ID].address()['city']
42 --OR--
43 $customers()[$ID].address().city
44 --OR--
45 $customers()[$ID].address.city
46 --OR--
47 $customers()[$ID].address.city
48 --OR--
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.
61 Details
62 ================================================================================
63 The parenthesized letters below correspond to the aims in the second paragraph.
65 DICTIONARY ACCESS (a)
66 ---------------------
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]*'
80 AUTOCALLING (b,d)
81 -----------------
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
87 $a.b
88 is equivalent to
89 $a.b()
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.
95 Further notes:
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)
113 --------------------
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.
134 Meta-Data
135 ================================================================================
136 Authors: Tavis Rudd <tavis@damnsimple.com>,
137 Chuck Esterbrook <echuck@mindspring.com>
138 Version: $Revision: 1.29 $
139 Start Date: 2001/04/03
140 Last Revision Date: $Date: 2006/01/15 20:27:42 $
142 from __future__ import generators
143 __author__ = "Tavis Rudd <tavis@damnsimple.com>," +\
144 "\nChuck Esterbrook <echuck@mindspring.com>"
145 __revision__ = "$Revision: 1.29 $"[11:-2]
146 import types
147 from types import StringType, InstanceType, ClassType, TypeType
148 from pprint import pformat
149 import inspect
151 _INCLUDE_NAMESPACE_REPR_IN_NOTFOUND_EXCEPTIONS = False
152 _ALLOW_WRAPPING_OF_NOTFOUND_EXCEPTIONS = True
153 __all__ = ['NotFound',
154 'hasKey',
155 'valueForKey',
156 'valueForName',
157 'valueFromSearchList',
158 'valueFromFrameOrSearchList',
159 'valueFromFrame',
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
166 ## the C versions.
168 class NotFound(LookupError):
169 pass
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:
179 raise
180 else:
181 excStr = exc.args[0]
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 excString += ' in the namespace %s'%pformat(namespace)
186 exc.args = (excStr,)
187 raise
189 def hasKey(obj, key):
190 """Determine if 'obj' has 'key' """
191 if hasattr(obj,'has_key') and obj.has_key(key):
192 return True
193 elif hasattr(obj, key):
194 return True
195 else:
196 return False
198 def valueForKey(obj, key):
199 if hasattr(obj, 'has_key') and obj.has_key(key):
200 return obj[key]
201 elif hasattr(obj, key):
202 return getattr(obj, key)
203 else:
204 _raiseNotFoundException(key, obj)
206 def _valueForName(obj, name, executeCallables=False):
207 nameChunks=name.split('.')
208 for i in range(len(nameChunks)):
209 key = nameChunks[i]
210 if hasattr(obj, 'has_key') and obj.has_key(key):
211 nextObj = obj[key]
212 elif hasattr(obj, key):
213 nextObj = getattr(obj, key)
214 else:
215 _raiseNotFoundException(key, obj)
217 if (executeCallables and callable(nextObj)
218 and (type(nextObj) not in (InstanceType, ClassType))):
219 obj = nextObj()
220 else:
221 obj = nextObj
222 return obj
224 def valueForName(obj, name, executeCallables=False):
225 try:
226 return _valueForName(obj, name, executeCallables)
227 except NotFound, e:
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
240 if searchList:
241 for namespace in searchList:
242 yield namespace
243 yield callerFrame.f_globals
244 yield __builtins__
246 def valueFromFrameOrSearchList(searchList, name, executeCallables=False,
247 frame=None):
248 def __valueForName():
249 try:
250 return _valueForName(namespace, name, executeCallables=executeCallables)
251 except NotFound, e:
252 _wrapNotFoundException(e, fullName=name, namespace=searchList)
253 try:
254 if not frame:
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)
260 finally:
261 del frame
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
267 try:
268 if not frame:
269 frame = inspect.stack()[1][0]
270 return valueFromFrameOrSearchList(searchList=None,
271 name=name,
272 executeCallables=executeCallables,
273 frame=frame)
274 finally:
275 del frame
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):
282 return False
283 try:
284 valueForName(obj, name)
285 return True
286 except NotFound:
287 return False
288 try:
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
292 C_VERSION = True
293 except:
294 C_VERSION = False
296 ##################################################
297 ## CLASSES
299 class Mixin:
300 """@@ document me"""
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 ##
310 def example():
311 class A(Mixin):
312 classVar = 'classVar val'
313 def method(self,arg='method 1 default arg'):
314 return arg
316 def method2(self, arg='meth 2 default arg'):
317 return {'item1':arg}
319 def method3(self, arg='meth 3 default'):
320 return arg
322 class B(A):
323 classBvar = 'classBvar val'
325 a = A()
326 a.one = 'valueForOne'
327 def function(whichOne='default'):
328 values = {
329 'default': 'default output',
330 'one': 'output option one',
331 'two': 'output option two'
333 return values[whichOne]
335 a.dic = {
336 'func': function,
337 'method': a.method3,
338 'item': 'itemval',
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__':
352 example()