Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 367 lines (297 sloc) 12.376 kb
a0c6979 @rtyler Prune unnecessary 'types' import in Namemapper.py
rtyler authored
1 #!/usr/bin/env python
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
2 """This module supports Cheetah's optional NameMapper syntax.
32567d8 Initial revision
tavis_rudd authored
3
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
4 Overview
32567d8 Initial revision
tavis_rudd authored
5 ================================================================================
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
6
7 NameMapper provides a simple syntax for accessing Python data structures,
8 functions, and methods from Cheetah. It's called NameMapper because it 'maps'
9 simple 'names' in Cheetah templates to possibly more complex syntax in Python.
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
10
11 Its purpose is to make working with Cheetah easy for non-programmers.
12 Specifically, non-programmers using Cheetah should NOT need to be taught (a)
13 what the difference is between an object and a dictionary, (b) what functions
14 and methods are, and (c) what 'self' is. A further aim (d) is to buffer the
15 code in Cheetah templates from changes in the implementation of the Python data
16 structures behind them.
17
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
18 Consider this scenario:
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
19
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
20 You are building a customer information system. The designers with you want to
21 use information from your system on the client's website --AND-- they want to
22 understand the display code and so they can maintian it themselves.
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
23
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
24 You write a UI class with a 'customers' method that returns a dictionary of all
25 the customer objects. Each customer object has an 'address' method that returns
26 the a dictionary with information about the customer's address. The designers
27 want to be able to access that information.
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
28
29 Using PSP, the display code for the website would look something like the
30 following, assuming your servlet subclasses the class you created for managing
31 customer information:
32
33 <%= self.customer()[ID].address()['city'] %> (42 chars)
34
35 Using Cheetah's NameMapper syntax it could be any of the following:
36
37 $self.customers()[$ID].address()['city'] (39 chars)
6a42520 @rtyler Tidying up whitespace
rtyler authored
38 --OR--
39 $customers()[$ID].address()['city']
40 --OR--
41 $customers()[$ID].address().city
42 --OR--
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
43 $customers()[$ID].address.city
44 --OR--
6a42520 @rtyler Tidying up whitespace
rtyler authored
45 $customers()[$ID].address.city
46 --OR--
47 $customers[$ID].address.city (27 chars)
48
49
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
50 Which of these would you prefer to explain to the designers, who have no
51 programming experience? The last form is 15 characters shorter than the PSP
52 and, conceptually, is far more accessible. With PHP or ASP, the code would be
53 even messier than the PSP
54
55 This is a rather extreme example and, of course, you could also just implement
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
56 '$getCustomer($ID).city' and obey the Law of Demeter (search Google for more on that).
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
57 But good object orientated design isn't the point here.
58
59 Details
60 ================================================================================
61 The parenthesized letters below correspond to the aims in the second paragraph.
62
bdea212 revert to version 1.22, without dictionary key changes
tavis_rudd authored
63 DICTIONARY ACCESS (a)
64 ---------------------
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
65
66 NameMapper allows access to items in a dictionary using the same dotted notation
67 used to access object attributes in Python. This aspect of NameMapper is known
68 as 'Unified Dotted Notation'.
69
70 For example, with Cheetah it is possible to write:
71 $customers()['kerr'].address() --OR-- $customers().kerr.address()
72 where the second form is in NameMapper syntax.
73
74 This only works with dictionary keys that are also valid python identifiers:
75 regex = '[a-zA-Z_][a-zA-Z_0-9]*'
76
77
78 AUTOCALLING (b,d)
79 -----------------
80
bdea212 revert to version 1.22, without dictionary key changes
tavis_rudd authored
81 NameMapper automatically detects functions and methods in Cheetah $vars and calls
6a42520 @rtyler Tidying up whitespace
rtyler authored
82 them if the parentheses have been left off.
32567d8 Initial revision
tavis_rudd authored
83
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
84 For example if 'a' is an object, 'b' is a method
85 $a.b
86 is equivalent to
87 $a.b()
32567d8 Initial revision
tavis_rudd authored
88
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
89 If b returns a dictionary, then following variations are possible
90 $a.b.c --OR-- $a.b().c --OR-- $a.b()['c']
91 where 'c' is a key in the dictionary that a.b() returns.
32567d8 Initial revision
tavis_rudd authored
92
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
93 Further notes:
94 * NameMapper autocalls the function or method without any arguments. Thus
95 autocalling can only be used with functions or methods that either have no
96 arguments or have default values for all arguments.
ab99e8d Added key argument to raising some NotFound exceptions
echuck authored
97
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
98 * NameMapper only autocalls functions and methods. Classes and callable object instances
6a42520 @rtyler Tidying up whitespace
rtyler authored
99 will not be autocalled.
32567d8 Initial revision
tavis_rudd authored
100
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
101 * Autocalling can be disabled using Cheetah's 'useAutocalling' setting.
ab99e8d Added key argument to raising some NotFound exceptions
echuck authored
102
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
103 LEAVING OUT 'self' (c,d)
104 ------------------------
32567d8 Initial revision
tavis_rudd authored
105
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
106 NameMapper makes it possible to access the attributes of a servlet in Cheetah
107 without needing to include 'self' in the variable names. See the NAMESPACE
108 CASCADING section below for details.
32567d8 Initial revision
tavis_rudd authored
109
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
110 NAMESPACE CASCADING (d)
111 --------------------
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
112 ...
32567d8 Initial revision
tavis_rudd authored
113
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
114 Implementation details
32567d8 Initial revision
tavis_rudd authored
115 ================================================================================
116
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
117 * NameMapper's search order is dictionary keys then object attributes
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
118
119 * NameMapper.NotFound is raised if a value can't be found for a name.
120
121 Performance and the C version
122 ================================================================================
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
123
124 Cheetah comes with both a C version and a Python version of NameMapper. The C
125 version is significantly faster and the exception tracebacks are much easier to
126 read. It's still slower than standard Python syntax, but you won't notice the
127 difference in realistic usage scenarios.
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
128
129 Cheetah uses the optimized C version (_namemapper.c) if it has
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
130 been compiled or falls back to the Python version if not.
32567d8 Initial revision
tavis_rudd authored
131 """
47fbe57 @rtyler Clean up a swath of fixes suggested by 2to3
rtyler authored
132
7fd2570 changed my email address over from calrudd.com to damnsimple.com
tavis_rudd authored
133 __author__ = "Tavis Rudd <tavis@damnsimple.com>," +\
bdea212 revert to version 1.22, without dictionary key changes
tavis_rudd authored
134 "\nChuck Esterbrook <echuck@mindspring.com>"
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
135 from pprint import pformat
136 import inspect
32567d8 Initial revision
tavis_rudd authored
137
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
138 _INCLUDE_NAMESPACE_REPR_IN_NOTFOUND_EXCEPTIONS = False
139 _ALLOW_WRAPPING_OF_NOTFOUND_EXCEPTIONS = True
140 __all__ = ['NotFound',
141 'hasKey',
142 'valueForKey',
143 'valueForName',
144 'valueFromSearchList',
145 'valueFromFrameOrSearchList',
146 'valueFromFrame',
147 ]
32567d8 Initial revision
tavis_rudd authored
148
832c25a Fix a defect in the Google App Engine implementation of the inspect modu...
R. Tyler Ballance authored
149 if not hasattr(inspect.imp, 'get_suffixes'):
6a42520 @rtyler Tidying up whitespace
rtyler authored
150 # This is to fix broken behavior of the inspect module under the
832c25a Fix a defect in the Google App Engine implementation of the inspect modu...
R. Tyler Ballance authored
151 # Google App Engine, see the following issue:
152 # http://bugs.communitycheetah.org/view.php?id=10
153 setattr(inspect.imp, 'get_suffixes', lambda: [('.py', 'U', 1)])
bdea212 revert to version 1.22, without dictionary key changes
tavis_rudd authored
154
e933911 added import comment
tavis_rudd authored
155 ## N.B. An attempt is made at the end of this module to import C versions of
156 ## these functions. If _namemapper.c has been compiled succesfully and the
157 ## import goes smoothly, the Python versions defined here will be replaced with
158 ## the C versions.
32567d8 Initial revision
tavis_rudd authored
159
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
160 class NotFound(LookupError):
161 pass
bdea212 revert to version 1.22, without dictionary key changes
tavis_rudd authored
162
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
163 def _raiseNotFoundException(key, namespace):
164 excString = "cannot find '%s'"%key
165 if _INCLUDE_NAMESPACE_REPR_IN_NOTFOUND_EXCEPTIONS:
166 excString += ' in the namespace %s'%pformat(namespace)
167 raise NotFound(excString)
bdea212 revert to version 1.22, without dictionary key changes
tavis_rudd authored
168
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
169 def _wrapNotFoundException(exc, fullName, namespace):
170 if not _ALLOW_WRAPPING_OF_NOTFOUND_EXCEPTIONS:
6a42520 @rtyler Tidying up whitespace
rtyler authored
171 raise
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
172 else:
173 excStr = exc.args[0]
174 if excStr.find('while searching')==-1: # only wrap once!
175 excStr +=" while searching for '%s'"%fullName
176 if _INCLUDE_NAMESPACE_REPR_IN_NOTFOUND_EXCEPTIONS:
6ca0c96 fix wrong var name
tavis_rudd authored
177 excStr += ' in the namespace %s'%pformat(namespace)
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
178 exc.args = (excStr,)
179 raise
b4d9e34 make autocalling in valueForName correctly ignore newstyle classes and i...
tavis_rudd authored
180
181 def _isInstanceOrClass(obj):
62f5a2f @rtyler Fix regression in the pure Python NameMapper
rtyler authored
182 if isinstance(obj, type):
b4d9e34 make autocalling in valueForName correctly ignore newstyle classes and i...
tavis_rudd authored
183 # oldstyle
184 return True
185
186 if hasattr(obj, "__class__"):
187 # newstyle
188 if hasattr(obj, 'mro'):
189 # type/class
190 return True
191 elif (hasattr(obj, 'im_func') or hasattr(obj, 'func_code') or hasattr(obj, '__self__')):
192 # method, func, or builtin func
193 return False
194 elif hasattr(obj, '__init__'):
195 # instance
196 return True
197 return False
6a42520 @rtyler Tidying up whitespace
rtyler authored
198
f9f444b reimplemented the parsing of $placeholders - see the change log
tavis_rudd authored
199 def hasKey(obj, key):
200 """Determine if 'obj' has 'key' """
47fbe57 @rtyler Clean up a swath of fixes suggested by 2to3
rtyler authored
201 if hasattr(obj, 'has_key') and key in obj:
f9f444b reimplemented the parsing of $placeholders - see the change log
tavis_rudd authored
202 return True
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
203 elif hasattr(obj, key):
f9f444b reimplemented the parsing of $placeholders - see the change log
tavis_rudd authored
204 return True
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
205 else:
206 return False
f9f444b reimplemented the parsing of $placeholders - see the change log
tavis_rudd authored
207
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
208 def valueForKey(obj, key):
47fbe57 @rtyler Clean up a swath of fixes suggested by 2to3
rtyler authored
209 if hasattr(obj, 'has_key') and key in obj:
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
210 return obj[key]
211 elif hasattr(obj, key):
212 return getattr(obj, key)
213 else:
214 _raiseNotFoundException(key, obj)
215
216 def _valueForName(obj, name, executeCallables=False):
217 nameChunks=name.split('.')
218 for i in range(len(nameChunks)):
219 key = nameChunks[i]
47fbe57 @rtyler Clean up a swath of fixes suggested by 2to3
rtyler authored
220 if hasattr(obj, 'has_key') and key in obj:
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
221 nextObj = obj[key]
222 else:
0db5614 @rtyler Fix the regression test I added in the previuos commit
rtyler authored
223 try:
224 nextObj = getattr(obj, key)
225 except AttributeError:
226 _raiseNotFoundException(key, obj)
6a42520 @rtyler Tidying up whitespace
rtyler authored
227
47fbe57 @rtyler Clean up a swath of fixes suggested by 2to3
rtyler authored
228 if executeCallables and hasattr(nextObj, '__call__') and not _isInstanceOrClass(nextObj):
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
229 obj = nextObj()
230 else:
231 obj = nextObj
232 return obj
233
234 def valueForName(obj, name, executeCallables=False):
235 try:
236 return _valueForName(obj, name, executeCallables)
237 except NotFound, e:
238 _wrapNotFoundException(e, fullName=name, namespace=obj)
239
240 def valueFromSearchList(searchList, name, executeCallables=False):
241 key = name.split('.')[0]
242 for namespace in searchList:
243 if hasKey(namespace, key):
244 return _valueForName(namespace, name,
245 executeCallables=executeCallables)
246 _raiseNotFoundException(key, searchList)
247
248 def _namespaces(callerFrame, searchList=None):
249 yield callerFrame.f_locals
250 if searchList:
251 for namespace in searchList:
252 yield namespace
253 yield callerFrame.f_globals
254 yield __builtins__
255
256 def valueFromFrameOrSearchList(searchList, name, executeCallables=False,
257 frame=None):
258 def __valueForName():
259 try:
260 return _valueForName(namespace, name, executeCallables=executeCallables)
261 except NotFound, e:
262 _wrapNotFoundException(e, fullName=name, namespace=searchList)
263 try:
264 if not frame:
265 frame = inspect.stack()[1][0]
266 key = name.split('.')[0]
267 for namespace in _namespaces(frame, searchList):
8982ecd Add a warning to the Template __init__ if the user passes in a searchLis...
R. Tyler Ballance authored
268 if hasKey(namespace, key):
269 return __valueForName()
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
270 _raiseNotFoundException(key, searchList)
271 finally:
272 del frame
273
274 def valueFromFrame(name, executeCallables=False, frame=None):
275 # @@TR consider implementing the C version the same way
276 # at the moment it provides a seperate but mirror implementation
277 # to valueFromFrameOrSearchList
6a42520 @rtyler Tidying up whitespace
rtyler authored
278 try:
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
279 if not frame:
280 frame = inspect.stack()[1][0]
281 return valueFromFrameOrSearchList(searchList=None,
282 name=name,
283 executeCallables=executeCallables,
284 frame=frame)
285 finally:
286 del frame
287
f9f444b reimplemented the parsing of $placeholders - see the change log
tavis_rudd authored
288 def hasName(obj, name):
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
289 #Not in the C version
f9f444b reimplemented the parsing of $placeholders - see the change log
tavis_rudd authored
290 """Determine if 'obj' has the 'name' """
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
291 key = name.split('.')[0]
292 if not hasKey(obj, key):
293 return False
f9f444b reimplemented the parsing of $placeholders - see the change log
tavis_rudd authored
294 try:
295 valueForName(obj, name)
296 return True
297 except NotFound:
298 return False
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
299 try:
2a7231b @rtyler Fix implicit relative import of _namemapper
rtyler authored
300 from Cheetah._namemapper import NotFound, valueForKey, valueForName, \
46f241a cleanup, syncing with the C version and addition of the two new lookup f...
tavis_rudd authored
301 valueFromSearchList, valueFromFrameOrSearchList, valueFromFrame
302 # it is possible with Jython or Windows, for example, that _namemapper.c hasn't been compiled
303 C_VERSION = True
304 except:
305 C_VERSION = False
f9f444b reimplemented the parsing of $placeholders - see the change log
tavis_rudd authored
306
32567d8 Initial revision
tavis_rudd authored
307 ##################################################
354c23e first attempt to merge the DEVEL_BRANCH changes
tavis_rudd authored
308 ## CLASSES
32567d8 Initial revision
tavis_rudd authored
309
310 class Mixin:
311 """@@ document me"""
f9f444b reimplemented the parsing of $placeholders - see the change log
tavis_rudd authored
312 def valueForName(self, name):
313 return valueForName(self, name)
32567d8 Initial revision
tavis_rudd authored
314
f9f444b reimplemented the parsing of $placeholders - see the change log
tavis_rudd authored
315 def valueForKey(self, key):
316 return valueForKey(self, key)
32567d8 Initial revision
tavis_rudd authored
317
318 ##################################################
319 ## if run from the command line ##
320
321 def example():
322 class A(Mixin):
323 classVar = 'classVar val'
324 def method(self,arg='method 1 default arg'):
325 return arg
ab99e8d Added key argument to raising some NotFound exceptions
echuck authored
326
32567d8 Initial revision
tavis_rudd authored
327 def method2(self, arg='meth 2 default arg'):
328 return {'item1':arg}
329
330 def method3(self, arg='meth 3 default'):
331 return arg
ab99e8d Added key argument to raising some NotFound exceptions
echuck authored
332
32567d8 Initial revision
tavis_rudd authored
333 class B(A):
334 classBvar = 'classBvar val'
ab99e8d Added key argument to raising some NotFound exceptions
echuck authored
335
32567d8 Initial revision
tavis_rudd authored
336 a = A()
337 a.one = 'valueForOne'
338 def function(whichOne='default'):
339 values = {
340 'default': 'default output',
341 'one': 'output option one',
342 'two': 'output option two'
343 }
344 return values[whichOne]
ab99e8d Added key argument to raising some NotFound exceptions
echuck authored
345
32567d8 Initial revision
tavis_rudd authored
346 a.dic = {
347 'func': function,
348 'method': a.method3,
349 'item': 'itemval',
350 'subDict': {'nestedMethod':a.method3}
351 }
352 b = 'this is local b'
353
47fbe57 @rtyler Clean up a swath of fixes suggested by 2to3
rtyler authored
354 print(valueForKey(a.dic, 'subDict'))
0b8c21f @rtyler Refactor raw print statements in accordance with 2to3
rtyler authored
355 print(valueForName(a, 'dic.item'))
356 print(valueForName(vars(), 'b'))
357 print(valueForName(__builtins__, 'dir')())
358 print(valueForName(vars(), 'a.classVar'))
359 print(valueForName(vars(), 'a.dic.func', executeCallables=True))
360 print(valueForName(vars(), 'a.method2.item1', executeCallables=True))
ab99e8d Added key argument to raising some NotFound exceptions
echuck authored
361
32567d8 Initial revision
tavis_rudd authored
362 if __name__ == '__main__':
363 example()
364
bdea212 revert to version 1.22, without dictionary key changes
tavis_rudd authored
365
366
Something went wrong with that request. Please try again.