"Fossies" - the Fresh Open Source Software Archive

Member "cheetah3-3.2.6.post2/Cheetah/NameMapper.py" (20 Apr 2021, 12510 Bytes) of package /linux/www/cheetah3-3.2.6.post2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "NameMapper.py" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 3-3.2.3_vs_3-3.2.4.

    1 #!/usr/bin/env python
    2 """This module supports Cheetah's optional NameMapper syntax.
    3 
    4 Overview
    5 ================================================================================
    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.
   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 
   18 Consider this scenario:
   19 
   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.
   23 
   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
   26 that returns the a dictionary with information about the customer's address.
   27 The designers want to be able to access that information.
   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)
   38    --OR--
   39    $customers()[$ID].address()['city']
   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                   (27 chars)
   48 
   49 
   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,
   56 you could also just implement '$getCustomer($ID).city'
   57 and obey the Law of Demeter (search Google for more on that).
   58 But good object orientated design isn't the point here.
   59 
   60 Details
   61 ================================================================================
   62 The parenthesized letters below correspond to the aims in the second paragraph.
   63 
   64 DICTIONARY ACCESS (a)
   65 ---------------------
   66 
   67 NameMapper allows access to items in a dictionary
   68 using the same dotted notation used to access object attributes in Python.
   69 This aspect of NameMapper is known as 'Unified Dotted Notation'.
   70 
   71 For example, with Cheetah it is possible to write::
   72 
   73     $customers()['kerr'].address()  --OR--  $customers().kerr.address()
   74 
   75 where the second form is in NameMapper syntax.
   76 
   77 This only works with dictionary keys that are also valid python identifiers::
   78 
   79     regex = '[a-zA-Z_][a-zA-Z_0-9]*'
   80 
   81 
   82 AUTOCALLING (b,d)
   83 -----------------
   84 
   85 NameMapper automatically detects functions and methods in Cheetah $vars
   86 and calls them if the parentheses have been left off.
   87 
   88 For example if 'a' is an object, 'b' is a method::
   89 
   90   $a.b
   91 
   92 is equivalent to::
   93 
   94   $a.b()
   95 
   96 If b returns a dictionary, then following variations are possible::
   97 
   98   $a.b.c  --OR--  $a.b().c  --OR--  $a.b()['c']
   99 
  100 where 'c' is a key in the dictionary that a.b() returns.
  101 
  102 Further notes:
  103 
  104 * NameMapper autocalls the function or method without any arguments.  Thus
  105   autocalling can only be used with functions or methods that either have no
  106   arguments or have default values for all arguments.
  107 
  108 * NameMapper only autocalls functions and methods.
  109   Classes and callable object instances will not be autocalled.
  110 
  111 * Autocalling can be disabled using Cheetah's 'useAutocalling' setting.
  112 
  113 LEAVING OUT 'self' (c,d)
  114 ------------------------
  115 
  116 NameMapper makes it possible to access the attributes of a servlet in Cheetah
  117 without needing to include 'self' in the variable names.  See the NAMESPACE
  118 CASCADING section below for details.
  119 
  120 NAMESPACE CASCADING (d)
  121 -----------------------
  122 ...
  123 
  124 Implementation details
  125 ================================================================================
  126 
  127 * NameMapper's search order is dictionary keys then object attributes
  128 
  129 * NameMapper.NotFound is raised if a value can't be found for a name.
  130 
  131 Performance and the C version
  132 ================================================================================
  133 
  134 Cheetah comes with both a C version and a Python version of NameMapper.  The C
  135 version is significantly faster and the exception tracebacks are much easier to
  136 read.  It's still slower than standard Python syntax, but you won't notice the
  137 difference in realistic usage scenarios.
  138 
  139 Cheetah uses the optimized C version (_namemapper.c) if it has
  140 been compiled or falls back to the Python version if not.
  141 """
  142 
  143 import inspect
  144 from pprint import pformat
  145 
  146 from Cheetah.compat import PY2
  147 if PY2:
  148     from collections import Mapping
  149 else:
  150     from collections.abc import Mapping
  151 
  152 _INCLUDE_NAMESPACE_REPR_IN_NOTFOUND_EXCEPTIONS = False
  153 _ALLOW_WRAPPING_OF_NOTFOUND_EXCEPTIONS = True
  154 __all__ = ['NotFound',
  155            'hasKey',
  156            'valueForKey',
  157            'valueForName',
  158            'valueFromSearchList',
  159            'valueFromFrameOrSearchList',
  160            'valueFromFrame',
  161            ]
  162 
  163 if PY2 and not hasattr(inspect.imp, 'get_suffixes'):
  164     # This is to fix broken behavior of the inspect module under the
  165     # Google App Engine
  166     setattr(inspect.imp, 'get_suffixes', lambda: [('.py', 'U', 1)])
  167 
  168 # N.B. An attempt is made at the end of this module to import C versions of
  169 # these functions.  If _namemapper.c has been compiled succesfully and the
  170 # import goes smoothly, the Python versions defined here will be replaced with
  171 # the C versions.
  172 
  173 
  174 def _raiseNotFoundException(key, namespace):
  175     excString = "cannot find '%s'" % key
  176     if _INCLUDE_NAMESPACE_REPR_IN_NOTFOUND_EXCEPTIONS:
  177         excString += ' in the namespace %s' % pformat(namespace)
  178     raise NotFound(excString)
  179 
  180 
  181 def _wrapNotFoundException(exc, fullName, namespace):
  182     if not _ALLOW_WRAPPING_OF_NOTFOUND_EXCEPTIONS:
  183         raise
  184     else:
  185         excStr = exc.args[0]
  186         if excStr.find('while searching') == -1:  # only wrap once!
  187             excStr += " while searching for '%s'" % fullName
  188             if _INCLUDE_NAMESPACE_REPR_IN_NOTFOUND_EXCEPTIONS:
  189                 excStr += ' in the namespace %s' % pformat(namespace)
  190             exc.args = (excStr,)
  191         raise
  192 
  193 
  194 def _isInstanceOrClass(obj):
  195     if isinstance(obj, type):
  196         # oldstyle
  197         return True
  198 
  199     if hasattr(obj, "__class__"):
  200         # newstyle
  201         if hasattr(obj, 'mro'):
  202             # type/class
  203             return True
  204         elif (hasattr(obj, '__func__') or hasattr(obj, '__code__')
  205                 or hasattr(obj, '__self__')):
  206             # method, func, or builtin func
  207             return False
  208         elif hasattr(obj, '__init__'):
  209             # instance
  210             return True
  211     return False
  212 
  213 
  214 def hasKey(obj, key):
  215     """Determine if 'obj' has 'key' """
  216     if isinstance(obj, Mapping) and key in obj:
  217         return True
  218     elif hasattr(obj, key):
  219         return True
  220     else:
  221         return False
  222 
  223 
  224 def _valueForName(obj, name, executeCallables=False):
  225     nameChunks = name.split('.')
  226     for key in nameChunks:
  227         if isinstance(obj, Mapping) and key in obj:
  228             nextObj = obj[key]
  229         else:
  230             try:
  231                 nextObj = getattr(obj, key)
  232             except AttributeError:
  233                 _raiseNotFoundException(key, obj)
  234 
  235         if executeCallables and callable(nextObj) and \
  236                 not _isInstanceOrClass(nextObj):
  237             obj = nextObj()
  238         else:
  239             obj = nextObj
  240     return obj
  241 
  242 
  243 def _namespaces(callerFrame, searchList=None):
  244     yield callerFrame.f_locals
  245     if searchList:
  246         for namespace in searchList:
  247             yield namespace
  248     yield callerFrame.f_globals
  249     yield __builtins__
  250 
  251 
  252 def hasName(obj, name):
  253     # Not in the C version
  254     """Determine if 'obj' has the 'name' """
  255     key = name.split('.')[0]
  256     if not hasKey(obj, key):
  257         return False
  258     try:
  259         valueForName(obj, name)
  260         return True
  261     except NotFound:
  262         return False
  263 
  264 
  265 try:
  266     from Cheetah._namemapper import NotFound, valueForKey, valueForName, \
  267         valueFromSearchList, valueFromFrameOrSearchList, valueFromFrame
  268     C_VERSION = True
  269 except Exception:
  270     class NotFound(LookupError):
  271         pass
  272 
  273     def valueForKey(obj, key):
  274         if isinstance(obj, Mapping) and key in obj:
  275             return obj[key]
  276         elif hasattr(obj, key):
  277             return getattr(obj, key)
  278         else:
  279             _raiseNotFoundException(key, obj)
  280 
  281     def valueForName(obj, name, executeCallables=False):
  282         try:
  283             return _valueForName(obj, name, executeCallables)
  284         except NotFound as e:
  285             _wrapNotFoundException(e, fullName=name, namespace=obj)
  286 
  287     def valueFromSearchList(searchList, name, executeCallables=False):
  288         key = name.split('.')[0]
  289         for namespace in searchList:
  290             if hasKey(namespace, key):
  291                 return _valueForName(namespace, name,
  292                                      executeCallables=executeCallables)
  293         _raiseNotFoundException(key, searchList)
  294 
  295     def valueFromFrame(name, executeCallables=False, frame=None):
  296         # @@TR consider implementing the C version the same way
  297         # at the moment it provides a seperate but mirror implementation
  298         # to valueFromFrameOrSearchList
  299         try:
  300             if not frame:
  301                 frame = inspect.stack()[1][0]
  302             return valueFromFrameOrSearchList(
  303                 searchList=None, name=name,
  304                 executeCallables=executeCallables, frame=frame)
  305         finally:
  306             del frame
  307 
  308     def valueFromFrameOrSearchList(searchList, name, executeCallables=False,
  309                                    frame=None):
  310         def __valueForName():
  311             try:
  312                 return _valueForName(namespace, name,
  313                                      executeCallables=executeCallables)
  314             except NotFound as e:
  315                 _wrapNotFoundException(e, fullName=name, namespace=searchList)
  316         try:
  317             if not frame:
  318                 frame = inspect.stack()[1][0]
  319             key = name.split('.')[0]
  320             for namespace in _namespaces(frame, searchList):
  321                 if hasKey(namespace, key):
  322                     return __valueForName()
  323             _raiseNotFoundException(key, searchList)
  324         finally:
  325             del frame
  326 
  327     # It is possible with PyPy, Jython or Windows, for example,
  328     # that _namemapper.c hasn't been compiled.
  329     C_VERSION = False
  330 
  331 
  332 ##################################################
  333 # CLASSES
  334 
  335 
  336 class Mixin:
  337     """@@ document me"""
  338     def valueForName(self, name):
  339         return valueForName(self, name)
  340 
  341     def valueForKey(self, key):
  342         return valueForKey(self, key)
  343 
  344 ##################################################
  345 # if run from the command line ##
  346 
  347 
  348 def example():
  349     class A(Mixin):
  350         classVar = 'classVar val'
  351 
  352         def method(self, arg='method 1 default arg'):
  353             return arg
  354 
  355         def method2(self, arg='meth 2 default arg'):
  356             return {'item1': arg}
  357 
  358         def method3(self, arg='meth 3 default'):
  359             return arg
  360 
  361     class B(A):
  362         classBvar = 'classBvar val'
  363 
  364     a = A()
  365     a.one = 'valueForOne'
  366 
  367     def function(whichOne='default'):
  368         values = {
  369             'default': 'default output',
  370             'one': 'output option one',
  371             'two': 'output option two'
  372         }
  373         return values[whichOne]
  374 
  375     a.dic = {
  376         'func': function,
  377         'method': a.method3,
  378         'item': 'itemval',
  379         'subDict': {'nestedMethod': a.method3}
  380     }
  381     # b = 'this is local b'
  382 
  383     print(valueForKey(a.dic, 'subDict'))
  384     print(valueForName(a, 'dic.item'))
  385     print(valueForName(vars(), 'b'))
  386     print(valueForName(__builtins__, 'dir')())
  387     print(valueForName(vars(), 'a.classVar'))
  388     print(valueForName(vars(), 'a.dic.func', executeCallables=True))
  389     print(valueForName(vars(), 'a.method2.item1', executeCallables=True))
  390 
  391 
  392 if __name__ == '__main__':
  393     example()