root/Cheetah/NameMapper.py

Revision f17b49bd2a9cb5c693518283252cdbca4d04136b, 12.0 kB (checked in by Jason Michalski <armooo@armooo.net>, 2 years ago)

Lets try the import again

  • Property mode set to 100644
Line 
1 #!/usr/bin/env python
2 # $Id: NameMapper.py,v 1.29 2006/01/15 20:27:42 tavis_rudd Exp $
3
4 """This module supports Cheetah's optional NameMapper syntax.
5
6 Overview
7 ================================================================================
8
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.
12
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.
19
20 Consider this scenario:
21
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.
25
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.
30
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:
34
35   <%= self.customer()[ID].address()['city'] %>   (42 chars)
36
37 Using Cheetah's NameMapper syntax it could be any of the following:
38
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)                   
50   
51   
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
56
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.
60
61 Details
62 ================================================================================
63 The parenthesized letters below correspond to the aims in the second paragraph.
64
65 DICTIONARY ACCESS (a)
66 ---------------------
67
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'.
71
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.
75
76 This only works with dictionary keys that are also valid python identifiers:
77   regex = '[a-zA-Z_][a-zA-Z_0-9]*'
78
79
80 AUTOCALLING (b,d)
81 -----------------
82
83 NameMapper automatically detects functions and methods in Cheetah $vars and calls
84 them if the parentheses have been left off. 
85
86 For example if 'a' is an object, 'b' is a method
87   $a.b
88 is equivalent to
89   $a.b()
90
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.
94
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.
99
100 * NameMapper only autocalls functions and methods.  Classes and callable object instances
101 will not be autocalled. 
102
103 * Autocalling can be disabled using Cheetah's 'useAutocalling' setting.
104
105 LEAVING OUT 'self' (c,d)
106 ------------------------
107
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.
111
112 NAMESPACE CASCADING (d)
113 --------------------
114 ...
115
116 Implementation details
117 ================================================================================
118
119 * NameMapper's search order is dictionary keys then object attributes
120
121 * NameMapper.NotFound is raised if a value can't be found for a name.
122
123 Performance and the C version
124 ================================================================================
125
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.
130
131 Cheetah uses the optimized C version (_namemapper.c) if it has
132 been compiled or falls back to the Python version if not.
133
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 $
141 """
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
150
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',
160            ]
161
162
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.
167
168 class NotFound(LookupError):
169     pass
170
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)
176
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
188    
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
197
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)
205
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)
216        
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
223
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)
229
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)
237
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__
245
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
262
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
276
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
295
296 ##################################################
297 ## CLASSES
298
299 class Mixin:
300     """@@ document me"""
301     def valueForName(self, name):
302         return valueForName(self, name)
303
304     def valueForKey(self, key):
305         return valueForKey(self, key)
306
307 ##################################################
308 ## if run from the command line ##
309
310 def example():
311     class A(Mixin):
312         classVar = 'classVar val'
313         def method(self,arg='method 1 default arg'):
314             return arg
315
316         def method2(self, arg='meth 2 default arg'):
317             return {'item1':arg}
318
319         def method3(self, arg='meth 3 default'):
320             return arg
321
322     class B(A):
323         classBvar = 'classBvar val'
324
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'
332             }
333         return values[whichOne]
334
335     a.dic = {
336         'func': function,
337         'method': a.method3,
338         'item': 'itemval',
339         'subDict': {'nestedMethod':a.method3}
340         }
341     b = 'this is local b'
342
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)
350
351 if __name__ == '__main__':
352     example()
353
354
355
Note: See TracBrowser for help on using the browser.