[Winopenrpg-developer] openrpg1/plugins/cherrypy/lib __init__.py,NONE,1.1 aspect.py,NONE,1.1 cptools
Status: Inactive
Brought to you by:
digitalxero
Update of /cvsroot/winopenrpg/openrpg1/plugins/cherrypy/lib In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/plugins/cherrypy/lib Added Files: __init__.py aspect.py cptools.py csauthenticate.py defaultformmask.py form.py htmltools.py httptools.py Log Message: Initial commit of OpenRPG++ python --- NEW FILE: form.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ Simple form handling module. """ from cherrypy import cpg import defaultformmask class FormField: def __init__(self, label, name, typ, mask=None, mandatory=0, size=15, optionList=[], defaultValue='', defaultMessage='', validate=None): self.isField=1 self.label=label self.name=name self.typ=typ if not mask: self.mask=defaultformmask.defaultMask else: self.mask=mask self.mandatory=mandatory self.size=size self.optionList=optionList self.defaultValue=defaultValue self.defaultMessage=defaultMessage self.validate=validate self.errorMessage="" def render(self, leaveValues): if leaveValues: if self.typ!='submit': if cpg.request.paramMap.has_key(self.name): self.currentValue=cpg.request.paramMap[self.name] else: self.currentValue="" else: self.currentValue=self.defaultValue else: self.currentValue=self.defaultValue self.errorMessage=self.defaultMessage return self.mask(self) class FormSeparator: def __init__(self, label, mask): self.isField=0 self.label=label self.mask=mask def render(self, dummy): return self.mask(self.label) class Form: method="post" enctype="" def formView(self, leaveValues=0): if self.enctype: enctypeTag='enctype="%s"'%self.enctype else: enctypeTag="" res='<form method="%s" %s action="postForm">'%(self.method, enctypeTag) for field in self.fieldList: res+=field.render(leaveValues) return res+"</form>" def validateFields(self): # Should be subclassed # Update field's errorMessage value to set an error pass def validateForm(self): # Reset errorMesage for each field for field in self.fieldList: if field.isField: field.errorMessage="" # Validate mandatory fields for field in self.fieldList: if field.isField and field.mandatory and (not cpg.request.paramMap.has_key(field.name) or not cpg.request.paramMap[field.name]): field.errorMessage="Missing" # Validate fields one by one for field in self.fieldList: if field.isField and field.validate and not field.errorMessage: if cpg.request.paramMap.has_key(field.name): value=cpg.request.paramMap[field.name] else: value="" field.errorMessage=field.validate(value) # Validate all fields together (ie: check that passwords match) self.validateFields() for field in self.fieldList: if field.isField and field.errorMessage: return 0 return 1 def setFieldErrorMessage(self, fieldName, errorMessage): for field in self.fieldList: if field.isField and field.name==fieldName: field.errorMessage=errorMessage def getFieldOptionList(self, fieldName): for field in self.fieldList: if field.isField and field.name==fieldName: return field.optionList def getFieldDefaultValue(self, fieldName): for field in self.fieldList: if field.isField and field.name==fieldName: return field.defaultValue def setFieldDefaultValue(self, fieldName, defaultValue): for field in self.fieldList: if field.isField and field.name==fieldName: field.defaultValue=defaultValue def getFieldNameList(self, exceptList=[]): fieldNameList=[] for field in self.fieldList: if field.isField and field.name and field.name not in exceptList: fieldNameList.append(field.name) return fieldNameList --- NEW FILE: httptools.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ Just a few convenient functions """ from cherrypy import cpg import urlparse def canonicalizeUrl(url): """ Canonicalize a URL. The URL might be relative, absolute or canonical """ return urlparse.urljoin(cpg.request.base, url) def redirect(url): """ Sends a redirect to the browser (after canonicalizing the URL) """ cpg.response.headerMap['Status'] = 302 cpg.response.headerMap['Location'] = canonicalizeUrl(url) return "" --- NEW FILE: csauthenticate.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import time, whrandom from cherrypy import cpg from aspect import Aspect, STOP, CONTINUE class CSAuthenticate(Aspect): timeoutMessage = "Session timed out" wrongLoginPasswordMessage = "Wrong login/password" noCookieMessage = "No cookie" logoutMessage = "You have been logged out" sessionIdCookieName = "CherrySessionId" timeout = 60 # in minutes def _before(self, methodName, method): # If the method is not exposed, don't do anything if not getattr(method, 'exposed', None): return CONTINUE, None cpg.request.login = '' # If the method is one of these 4, do not try to find out who is logged in if methodName in ["loginScreen", "logoutScreen", "doLogin", "doLogout"]: return CONTINUE, None # Check if a user is logged in: # - If they are, set request.login with the right value # - If not, return the login screen if not cpg.request.simpleCookie.has_key(self.sessionIdCookieName): return STOP, self.loginScreen(self.noCookieMessage, cpg.request.browserUrl) sessionId = cpg.request.simpleCookie[self.sessionIdCookieName].value now=time.time() # Check that session exists and hasn't timed out timeout=0 if not cpg.request.sessionMap.has_key(sessionId): return STOP, self.loginScreen(self.noCookieMessage, cpg.request.browserUrl) else: login, expire = cpg.request.sessionMap[sessionId] if expire < now: timeout=1 else: expire = now + self.timeout*60 cpg.request.sessionMap[sessionId] = login, expire if timeout: return STOP, self.loginScreen(self.timeoutMessage, cpg.request.browserUrl) cpg.request.login = login return CONTINUE, None def checkLoginAndPassword(self, login, password): if (login,password) == ('login','password'): return '' return 'Wrong login/password' def generateSessionId(self, sessionIdList): choice="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" while 1: sessionId="" for dummy in range(20): sessionId += whrandom.choice(choice) if sessionId not in sessionIdList: return sessionId def doLogin(self, login, password, fromPage): # Check that login/password match errorMsg = self.checkLoginAndPassword(login, password) if errorMsg: cpg.request.login = '' return self.loginScreen(errorMsg, fromPage, login) cpg.request.login = login # Set session newSessionId = self.generateSessionId(cpg.request.sessionMap.keys()) cpg.request.sessionMap[newSessionId] = login, time.time()+self.timeout*60 cpg.response.simpleCookie[self.sessionIdCookieName] = newSessionId cpg.response.simpleCookie[self.sessionIdCookieName]['path'] = '/' cpg.response.simpleCookie[self.sessionIdCookieName]['max-age'] = 31536000 cpg.response.simpleCookie[self.sessionIdCookieName]['version'] = 1 cpg.response.headerMap['Status'] = 302 cpg.response.headerMap['Location'] = fromPage return "" doLogin.exposed = True def doLogout(self): try: sessionId = request.simpleCookie[self.sessionIdCookieName].value del request.sessionMap[sessionId] except: pass cpg.response.simpleCookie[self.sessionIdCookieName] = "" cpg.response.simpleCookie[self.sessionIdCookieName]['path'] = '/' cpg.response.simpleCookie[self.sessionIdCookieName]['max-age'] = 0 cpg.response.simpleCookie[self.sessionIdCookieName]['version'] = 1 cpg.request.login = '' cpg.response.headerMap['Status'] = 302 cpg.response.headerMap['Location'] = 'logoutScreen' # TBCTBC: may not be the right URL return "" doLogout.exposed = True def logoutScreen(self): return self.loginScreen(self.logoutMessage, '/index') # TBC logoutScreen.exposed = True def loginScreen(self, message, fromPage, login=''): return """ <html><body> Message: %s <form method="post" action="doLogin"> Login: <input type=text name=login value="%s" size=10><br> Password: <input type=password name=password size=10><br> <input type=hidden name=fromPage value="%s"><br> <input type=submit> </form> </body></html> """ % (message, login, fromPage) loginScreen.exposed = True --- NEW FILE: aspect.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ # return codes for _before and _after aspect methods STOP = 0 CONTINUE = 1 class Aspect(object): """ Base class for aspects. Derive new aspect classes from this, then override one or both of _before and _after. """ def __getattribute__(self, methodName): # find method specified by methodName try: method = object.__getattribute__(self, methodName) except: raise # if requested attribute is not a method, simply return it if not callable(method): return method # define wrapper function def _wrapper(*k, **kw): # call _before method status, value = object.__getattribute__(self, '_before')(methodName, method) if status == STOP: return value # call wrapped method and append results result = method(*k, **kw) if value: result = value + result # call _after method status, value = object.__getattribute__(self, '_after')(methodName, method) if status == STOP: return value if value: result += value # done! return result # expose wrapper function if wrapped method is exposed if getattr(method, 'exposed', None): _wrapper.exposed = True # return wrapper function. It'll get called instead of the # requested method. return _wrapper def _before(self, methodName, method): return CONTINUE, None def _after(self, methodName, method): return CONTINUE, None --- NEW FILE: __init__.py --- """ CherryPy Standard Library """ --- NEW FILE: defaultformmask.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ Default mask for the form.py module """ def defaultMask(field): res="<tr><td valign=top>%s</td>"%field.label if field.typ=='text': res+='<td><input name="%s" type=text value="%s" size=%s></td>'%(field.name, field.currentValue, field.size) elif field.typ=='forced': res+='<td><input name="%s" type=hidden value="%s">%s</td>'%(field.name, field.currentValue, field.currentValue) elif field.typ=='password': res+='<td><input name="%s" type=password value="%s"></td>'%(field.name, field.currentValue) elif field.typ=='select': res+='<td><select name="%s">'%field.name for option in field.optionList: if type(option)==type(()): optionId, optionLabel=option if optionId==field.currentValue or str(optionId)==field.currentValue: res+="<option selected value=%s>%s</option>"%(optionId, optionLabel) else: res+="<option value=%s>%s</option>"%(optionId, optionLabel) else: if option==field.currentValue: res+="<option selected>%s</option>"%option else: res+="<option>%s</option>"%option res+='</select></td>' elif field.typ=='textarea': # Size is colsxrows if field.size==15: size="15x15" else: size=field.size cols, rows=size.split('x') res+='<td><textarea name="%s" rows="%s" cols="%s">%s</textarea></td>'%(field.name, rows, cols, field.currentValue) elif field.typ=='submit': res+='<td><input type=submit value="%s"></td>'%field.name elif field.typ=='hidden': if type(field.currentValue)==type([]): currentValue=field.currentValue else: currentValue=[field.currentValue] res="" for value in currentValue: res+='<input name="%s" type=hidden value="%s">'%(field.name, value) return res elif field.typ=='checkbox' or field.typ=='radio': res+='<td>' # print "##### currentValue:", field.currentValue # TBC for option in field.optionList: if type(option)==type(()): optionValue, optionLabel=option else: optionValue, optionLabel=option, option res+='<input type="%s" name="%s" value="%s"'%(field.typ, field.name, optionValue) if type(field.currentValue)==type([]): if optionValue in field.currentValue: res+=' checked' else: if optionValue==field.currentValue: res+=' checked' res+='> %s<br>'%optionLabel res+='</td>' if field.errorMessage: res+="<td><font color=red>%s</font></td>"%field.errorMessage else: res+="<td> </td>" return res+"</tr>" def hiddenMask(field): if type(field.currentValue)==type([]): currentValue=field.currentValue else: currentValue=[field.currentValue] res="" for value in currentValue: res+='<input name="%s" type=hidden value="%s">'%(field.name, value) return res def defaultHeader(label): return "<table>" def defaultFooter(label): return "</table>" def echoMask(label): return label --- NEW FILE: htmltools.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ Just a few convenient functions """ from cherrypy import cpg def redirect(newUrl): """ Sends a redirect to the browser """ if not newUrl.startswith('http://') and not newUrl.startswith('https://'): # If newUrl is not canonical, we must make it canonical if newUrl.startswith('/'): # newUrl was absolute: # we just add request.base in front of it newUrl = cpg.request.base + newUrl else: # newUrl was relative: # we remove the last bit from browserUrl and add newUrl to it i = cpg.request.browserUrl.rfind('/') newUrl = cpg.request.browserUrl[:i+1] + newUrl cpg.response.headerMap['Status'] = 302 cpg.response.headerMap['Location'] = newUrl return "" --- NEW FILE: cptools.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ Just a few convenient functions """ class ExposeItems: """ Utility class that exposes a getitem-aware object. It does not provide index() or default() methods, and it does not expose the individual item objects - just the list or dict that contains them. User-specific index() and default() methods can be implemented by inheriting from this class. Use case: from cherrypy.lib.cptools import ExposeItems ... cpg.root.foo = ExposeItems(mylist) cpg.root.bar = ExposeItems(mydict) """ exposed = True def __init__(self, items): self.items = items def __getattr__(self, key): return self.items[key] |