Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

file 376 lines (309 sloc) 13.124 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
# $Id: NameMapper.py,v 1.32 2007/12/10 19:20:09 tavis_rudd Exp $

"""This module supports Cheetah's optional NameMapper syntax.

Overview
================================================================================

NameMapper provides a simple syntax for accessing Python data structures,
functions, and methods from Cheetah. It's called NameMapper because it 'maps'
simple 'names' in Cheetah templates to possibly more complex syntax in Python.

Its purpose is to make working with Cheetah easy for non-programmers.
Specifically, non-programmers using Cheetah should NOT need to be taught (a)
what the difference is between an object and a dictionary, (b) what functions
and methods are, and (c) what 'self' is. A further aim (d) is to buffer the
code in Cheetah templates from changes in the implementation of the Python data
structures behind them.

Consider this scenario:

You are building a customer information system. The designers with you want to
use information from your system on the client's website --AND-- they want to
understand the display code and so they can maintian it themselves.

You write a UI class with a 'customers' method that returns a dictionary of all
the customer objects. Each customer object has an 'address' method that returns
the a dictionary with information about the customer's address. The designers
want to be able to access that information.

Using PSP, the display code for the website would look something like the
following, assuming your servlet subclasses the class you created for managing
customer information:

<%= self.customer()[ID].address()['city'] %> (42 chars)

Using Cheetah's NameMapper syntax it could be any of the following:

$self.customers()[$ID].address()['city'] (39 chars)
--OR--
$customers()[$ID].address()['city']
--OR--
$customers()[$ID].address().city
--OR--
$customers()[$ID].address.city
--OR--
$customers()[$ID].address.city
--OR--
$customers[$ID].address.city (27 chars)
Which of these would you prefer to explain to the designers, who have no
programming experience? The last form is 15 characters shorter than the PSP
and, conceptually, is far more accessible. With PHP or ASP, the code would be
even messier than the PSP

This is a rather extreme example and, of course, you could also just implement
'$getCustomer($ID).city' and obey the Law of Demeter (search Google for more on that).
But good object orientated design isn't the point here.

Details
================================================================================
The parenthesized letters below correspond to the aims in the second paragraph.

DICTIONARY ACCESS (a)
---------------------

NameMapper allows access to items in a dictionary using the same dotted notation
used to access object attributes in Python. This aspect of NameMapper is known
as 'Unified Dotted Notation'.

For example, with Cheetah it is possible to write:
$customers()['kerr'].address() --OR-- $customers().kerr.address()
where the second form is in NameMapper syntax.

This only works with dictionary keys that are also valid python identifiers:
regex = '[a-zA-Z_][a-zA-Z_0-9]*'


AUTOCALLING (b,d)
-----------------

NameMapper automatically detects functions and methods in Cheetah $vars and calls
them if the parentheses have been left off.

For example if 'a' is an object, 'b' is a method
$a.b
is equivalent to
$a.b()

If b returns a dictionary, then following variations are possible
$a.b.c --OR-- $a.b().c --OR-- $a.b()['c']
where 'c' is a key in the dictionary that a.b() returns.

Further notes:
* NameMapper autocalls the function or method without any arguments. Thus
autocalling can only be used with functions or methods that either have no
arguments or have default values for all arguments.

* NameMapper only autocalls functions and methods. Classes and callable object instances
will not be autocalled.

* Autocalling can be disabled using Cheetah's 'useAutocalling' setting.

LEAVING OUT 'self' (c,d)
------------------------

NameMapper makes it possible to access the attributes of a servlet in Cheetah
without needing to include 'self' in the variable names. See the NAMESPACE
CASCADING section below for details.

NAMESPACE CASCADING (d)
--------------------
...

Implementation details
================================================================================

* NameMapper's search order is dictionary keys then object attributes

* NameMapper.NotFound is raised if a value can't be found for a name.

Performance and the C version
================================================================================

Cheetah comes with both a C version and a Python version of NameMapper. The C
version is significantly faster and the exception tracebacks are much easier to
read. It's still slower than standard Python syntax, but you won't notice the
difference in realistic usage scenarios.

Cheetah uses the optimized C version (_namemapper.c) if it has
been compiled or falls back to the Python version if not.

Meta-Data
================================================================================
Authors: Tavis Rudd <tavis@damnsimple.com>,
Chuck Esterbrook <echuck@mindspring.com>
Version: $Revision: 1.32 $
Start Date: 2001/04/03
Last Revision Date: $Date: 2007/12/10 19:20:09 $
"""
from __future__ import generators
__author__ = "Tavis Rudd <tavis@damnsimple.com>," +\
             "\nChuck Esterbrook <echuck@mindspring.com>"
__revision__ = "$Revision: 1.32 $"[11:-2]
import types
from types import StringType, InstanceType, ClassType, TypeType
from pprint import pformat
import inspect
import pdb

_INCLUDE_NAMESPACE_REPR_IN_NOTFOUND_EXCEPTIONS = False
_ALLOW_WRAPPING_OF_NOTFOUND_EXCEPTIONS = True
__all__ = ['NotFound',
           'hasKey',
           'valueForKey',
           'valueForName',
           'valueFromSearchList',
           'valueFromFrameOrSearchList',
           'valueFromFrame',
           ]

if not hasattr(inspect.imp, 'get_suffixes'):
    # This is to fix broken behavior of the inspect module under the
    # Google App Engine, see the following issue:
    # http://bugs.communitycheetah.org/view.php?id=10
    setattr(inspect.imp, 'get_suffixes', lambda: [('.py', 'U', 1)])

## N.B. An attempt is made at the end of this module to import C versions of
## these functions. If _namemapper.c has been compiled succesfully and the
## import goes smoothly, the Python versions defined here will be replaced with
## the C versions.

class NotFound(LookupError):
    pass

def _raiseNotFoundException(key, namespace):
    excString = "cannot find '%s'"%key
    if _INCLUDE_NAMESPACE_REPR_IN_NOTFOUND_EXCEPTIONS:
        excString += ' in the namespace %s'%pformat(namespace)
    raise NotFound(excString)

def _wrapNotFoundException(exc, fullName, namespace):
    if not _ALLOW_WRAPPING_OF_NOTFOUND_EXCEPTIONS:
        raise
    else:
        excStr = exc.args[0]
        if excStr.find('while searching')==-1: # only wrap once!
            excStr +=" while searching for '%s'"%fullName
            if _INCLUDE_NAMESPACE_REPR_IN_NOTFOUND_EXCEPTIONS:
                excStr += ' in the namespace %s'%pformat(namespace)
            exc.args = (excStr,)
        raise

def _isInstanceOrClass(obj):
    if type(obj) in (InstanceType, ClassType):
        # oldstyle
        return True

    if hasattr(obj, "__class__"):
        # newstyle
        if hasattr(obj, 'mro'):
            # type/class
            return True
        elif (hasattr(obj, 'im_func') or hasattr(obj, 'func_code') or hasattr(obj, '__self__')):
            # method, func, or builtin func
            return False
        elif hasattr(obj, '__init__'):
            # instance
            return True
    return False
    
def hasKey(obj, key):
    """Determine if 'obj' has 'key' """
    if hasattr(obj,'has_key') and obj.has_key(key):
        return True
    elif hasattr(obj, key):
        return True
    else:
        return False

def valueForKey(obj, key):
    if hasattr(obj, 'has_key') and obj.has_key(key):
        return obj[key]
    elif hasattr(obj, key):
        return getattr(obj, key)
    else:
        _raiseNotFoundException(key, obj)

def _valueForName(obj, name, executeCallables=False):
    nameChunks=name.split('.')
    for i in range(len(nameChunks)):
        key = nameChunks[i]
        if hasattr(obj, 'has_key') and obj.has_key(key):
            nextObj = obj[key]
        else:
            try:
                nextObj = getattr(obj, key)
            except AttributeError:
                _raiseNotFoundException(key, obj)
        
        if executeCallables and callable(nextObj) and not _isInstanceOrClass(nextObj):
            obj = nextObj()
        else:
            obj = nextObj
    return obj

def valueForName(obj, name, executeCallables=False):
    try:
        return _valueForName(obj, name, executeCallables)
    except NotFound, e:
        _wrapNotFoundException(e, fullName=name, namespace=obj)

def valueFromSearchList(searchList, name, executeCallables=False):
    key = name.split('.')[0]
    for namespace in searchList:
        if hasKey(namespace, key):
            return _valueForName(namespace, name,
                                executeCallables=executeCallables)
    _raiseNotFoundException(key, searchList)

def _namespaces(callerFrame, searchList=None):
    yield callerFrame.f_locals
    if searchList:
        for namespace in searchList:
            yield namespace
    yield callerFrame.f_globals
    yield __builtins__

def valueFromFrameOrSearchList(searchList, name, executeCallables=False,
                               frame=None):
    def __valueForName():
        try:
            return _valueForName(namespace, name, executeCallables=executeCallables)
        except NotFound, e:
            _wrapNotFoundException(e, fullName=name, namespace=searchList)
    try:
        if not frame:
            frame = inspect.stack()[1][0]
        key = name.split('.')[0]
        for namespace in _namespaces(frame, searchList):
            if hasKey(namespace, key):
                return __valueForName()
        _raiseNotFoundException(key, searchList)
    finally:
        del frame

def valueFromFrame(name, executeCallables=False, frame=None):
    # @@TR consider implementing the C version the same way
    # at the moment it provides a seperate but mirror implementation
    # to valueFromFrameOrSearchList
    try:
        if not frame:
            frame = inspect.stack()[1][0]
        return valueFromFrameOrSearchList(searchList=None,
                                          name=name,
                                          executeCallables=executeCallables,
                                          frame=frame)
    finally:
        del frame

def hasName(obj, name):
    #Not in the C version
    """Determine if 'obj' has the 'name' """
    key = name.split('.')[0]
    if not hasKey(obj, key):
        return False
    try:
        valueForName(obj, name)
        return True
    except NotFound:
        return False
try:
    from _namemapper import NotFound, valueForKey, valueForName, \
         valueFromSearchList, valueFromFrameOrSearchList, valueFromFrame
    # it is possible with Jython or Windows, for example, that _namemapper.c hasn't been compiled
    C_VERSION = True
except:
    C_VERSION = False

##################################################
## CLASSES

class Mixin:
    """@@ document me"""
    def valueForName(self, name):
        return valueForName(self, name)

    def valueForKey(self, key):
        return valueForKey(self, key)

##################################################
## if run from the command line ##

def example():
    class A(Mixin):
        classVar = 'classVar val'
        def method(self,arg='method 1 default arg'):
            return arg

        def method2(self, arg='meth 2 default arg'):
            return {'item1':arg}

        def method3(self, arg='meth 3 default'):
            return arg

    class B(A):
        classBvar = 'classBvar val'

    a = A()
    a.one = 'valueForOne'
    def function(whichOne='default'):
        values = {
            'default': 'default output',
            'one': 'output option one',
            'two': 'output option two'
            }
        return values[whichOne]

    a.dic = {
        'func': function,
        'method': a.method3,
        'item': 'itemval',
        'subDict': {'nestedMethod':a.method3}
        }
    b = 'this is local b'

    print valueForKey(a.dic,'subDict')
    print valueForName(a, 'dic.item')
    print valueForName(vars(), 'b')
    print valueForName(__builtins__, 'dir')()
    print valueForName(vars(), 'a.classVar')
    print valueForName(vars(), 'a.dic.func', executeCallables=True)
    print valueForName(vars(), 'a.method2.item1', executeCallables=True)

if __name__ == '__main__':
    example()
Something went wrong with that request. Please try again.