#!/usr/bin/env python __version__ = '2.1' __release__ = '3' DEBUG_ERROR = False def DEBUG(s): if DEBUG_ERROR: sys.stderr.write('%s\n' % s) ################################################## # SPYCE - Python-based HTML Scripting # Copyright (c) 2002 Rimon Barr. # # Refer to LICENCE for legalese # # Name: spyce # Author: Rimon Barr # Start date: 8 April 2002 # Purpose: Python Server Pages # WWW: http://spyce.sourceforge.net/ ################################################## # note: doc string used in documentation: doc/index.spy __doc__ = ''' Spyce is a server-side language that supports elegant and efficient Python-based dynamic HTML generation. Spyce allows embedding Python in pages similar to how JSP embeds Java, but Spyce is far more than a JSP clone. Out of the box, Spyce provides development as rapid as other modern frameworks like Rails, but with an cohesive design rather than a morass of special cases. ''' import sys, os, copy, string, imp, Queue, time, traceback import spyceCompile, spyceException import spyceModule, spyceTag import spyceLock, spyceCache, spyceUtil ################################################## # Spyce engine globals # MAX_STACK = 100 WRAPPER_LIMIT = 3 # spyceServer object - one per engine instance SPYCE_SERVER = None def getServer(config=None): if not SPYCE_SERVER: # constructor sets SPYCE_SERVER if not config: import spycePreload config = spycePreload.getConfigModule() # guess DEBUG('creating server with config from ' + config.__file__) spyceServer(config) return SPYCE_SERVER SPYCE_GLOBALS = None def getServerGlobals(): global SPYCE_GLOBALS return SPYCE_GLOBALS SPYCE_LOADER = 'spyceLoader' SPYCE_ENTRY = 'SPYCE_ENTRY' DEFAULT_MODULES = ('request', 'response', 'stdout', 'error') # handler ids can change when tag is recompiled, so we need to invalidate # dependant pages when a tag changes tag_dependencies = {} ################################################## # Spyce core objects # class spyceServerObject: "serverObject placeholder" pass class spyceServer: "One per server, stored in SPYCE_SERVER (above) at processing of first request." def __init__(self, config): global SPYCE_SERVER, SPYCE_GLOBALS # it's possible to call getServer recursively -- one way is from an import # in spyceconf. This ensures all calls get the same object. SPYCE_SERVER = self # server object self.serverobject = spyceServerObject() # http headers try: self.entry = os.environ[SPYCE_ENTRY] except: self.entry = 'UNKNOWN' self.config = config global DEBUG_ERROR, SPYCE_GLOBALS DEBUG_ERROR = self.config.debug self.globals = self.config.globals SPYCE_GLOBALS = self.globals # hack # spyce module search path self.path = self.config.path self.imports = self.config.imports # spyce module cache self.module_cache = {} self.module_coderefs = {} # page error handler pageerror = self.config.pageerrortemplate if pageerror[0]=='string': pageerror = pageerror[0], self.loadModule(pageerror[2], pageerror[1]+'.py') self.pageerror = pageerror # engine error handler self.error = self.config.errorhandler # spyce thread-safe stdout object if self.multithreaded(): self.stdout = spyceUtil.ThreadedWriter(sys.stdout) sys.stdout = self.stdout else: self.stdout = None # spyce compilation cache raw_cache = self.config.cache if raw_cache in ('file',): raw_cache = spyceCache.fileCache(self.config.cachedir) elif raw_cache in ('mem', 'memory'): raw_cache = {} else: raise Exception('Unrecognized cache type ' + str(raw_cache)) # spyce_cache needs to lock to avoid potential multiple spyceCode objects # for the same spyce file or string # (or, potentially corrupt compiled code in the case of a fileCache) cache_lock = self.createLock('spycecache') self.spyce_cache = spyceCache.semanticCache(raw_cache, spyceCacheValid, spyceCacheGenerate, cache_lock) # ensure that global tags at least import correctly checker = spyceTag.spyceTagChecker(self) for tagtuple in self.config.globaltags: L = list(tagtuple) L.append(None) # relfile for loadmodule try: checker.loadLib(*L) except: sys.stderr.write(spyceUtil.exceptionString() + '\n') raise Exception('Error loading global taglib %s' % (tagtuple,)) # moved these here so imported modules can interact w/ getServer for i in self.config.imports: exec('import ' + i) def threaded(self): return 'spyceWWW' in sys.modules def multithreaded(self): return self.threaded() and self.config.minthreads > 1 and self.config.maxthreads > 1 def createLock(self, lockname): if self.threaded(): if self.multithreaded(): return spyceLock.threadLock() else: return spyceLock.dummyLock() return spyceLock.fileLock(os.path.join(self.config.tmp, lockname)) def _findModule(self, name, file, rel_file): """ Find (and cache) the path a spyce module given by may be loaded from ("file" argument does not include any path information) rel_file is relevant because the "current directory" is always searched first. """ if not file: file=name+'.py' key = name, file, rel_file try: pathkey = self.module_cache[key] path = pathkey[0] except KeyError: pathkey = None # 1st-level cache miss if not pathkey: if rel_file: # don't use path.append; we don't want to modify it for everybody! L = [os.path.dirname(rel_file)] + self.path else: L = self.path for path in L: f = None path = os.path.realpath(os.path.join(path, file)) if os.path.exists(path) and os.access(path, os.R_OK): self.module_cache[key] = pathkey = (path, name) break if not pathkey: raise ImportError('unable to find module "%s" in path %s' % (file, L)) return pathkey def loadModule(self, name, file=None, rel_file=None): """ Find and load a spyce module, with caching. (I.e., return the class inheriting spyceModule from the python module in the given file -- the actual python module is not returned!) This is also used to load stuff that isn't actually a spyceModule -- spyceTags and defaultErrorTemplate, for instance. Caching is performed at the name/actual filename [not file parameter] level. (This is done so that .spy files in different directories can ask for tags in their directory w/o worrying about name conflicts.) The 2nd level is the necessary one to avoid unnecessary reloads (which can break things that only expect to be loaded once); the first is just an optimization to avoid repeatedly walking the spyce path looking for the right source file. """ pathkey = self._findModule(name, file, rel_file) path = pathkey[0] try: (mod, mtime) = self.module_cache[pathkey] except KeyError: DEBUG('cache miss for %s' % (pathkey,)) else: if not self.config.check_mtime or mtime >= spyceCacheGetmtime(path): DEBUG('cache hit (%d) for %s in %s' % (mtime, name, path)) return mod for callback in tag_dependencies.get(path, []): callback() # else continue w/ (re)load def loadModuleHelper(p=path, name=name, pathkey=pathkey): try: f = open(p) if (spyceUtil.isTagCollection(f)): realname = spyceCompile.SPYCE_LIBNAME code, coderefs, modrefs, tagrefs = \ spyceCompile.spyceCompile(f.read(), p, '', getServer(), True) def clear(): del self.module_cache[pathkey] for taglibsrc in tagrefs: tag_dependencies.setdefault(taglibsrc, []).append(clear) f.close() f = os.tmpfile() DEBUG('compiled tag library:\n%s' % code) f.write(code) f.flush() f.seek(0) else: realname = name try: imported = getattr(imp.load_source(SPYCE_LOADER, p, f), realname) except: sys.stderr.write('exception loading %s from %s:\n%s' % (name, p, spyceUtil.exceptionString())) ex = sys.exc_info() info = traceback.format_exception_only(ex[0], ex[1])[0][:-1] try: raise 'Error loading Spyce module %s -- %s' % (name, info) except: raise spyceException.spyceRuntimeException() try: imported.__file__ = p except AttributeError: pass # "module" being loaded was str or other __slots__ user self.module_cache[(p, name)] = (imported, spyceCacheGetmtime(p)) try: self.module_coderefs[p] = coderefs except NameError: pass return imported finally: if f: f.close() dict = {'loadModuleHelper': loadModuleHelper} return loadModuleHelper() def fileHandler(self, request, response, filename, sig='', args=None, kwargs=None): return self.commonHandler(request, response, ('file', (filename, sig)), args, kwargs) def stringHandler(self, request, response, code, sig='', args=None, kwargs=None): return self.commonHandler(request, response, ('string', (code, sig)), args, kwargs) def commonHandler(self, request, response, spyceInfo, args=None, kwargs=None): "Handle a request. This method is threadsafe." start = time.time() try: thespyce = None theError = None try: spycecode = self.spyce_cache[spyceInfo] DEBUG('elapsed to get spycecode: %s' % (time.time() - start)) thespyce = spycecode.newWrapper() # does own locking try: thespyce.spyceInit(request, response) DEBUG('elapsed to init wrapper: %s' % (time.time() - start)) if args is None: args=[] if kwargs is None: kwargs={} parent_code = thespyce.spyceProcess(*args, **kwargs) if parent_code: return parent_code except spyceException.spyceRuntimeException, theError: DEBUG('caught RuntimeException') pass finally: if DEBUG_ERROR and theError: sys.stderr.write(spyceUtil.exceptionString() + '\n') if thespyce: thespyce.spyceDestroy(theError) spycecode.returnWrapper(thespyce) DEBUG('elapsed to finish: %s' % (time.time() - start)) except spyceException.spyceDone: pass except spyceException.spyceRedirect, e: return spyceFileHandler(request, response, e.filename) except KeyboardInterrupt: raise except (spyceException.spyceNotFound, spyceException.spyceForbidden, spyceException.spyceSyntaxError, spyceException.pythonSyntaxError, SyntaxError), e: DEBUG('sending %s to errorhandler' % e) return self.error(self, request, response, e) except SystemExit: pass except: errorString = spyceUtil.exceptionString() try: import cgi response.clear() response.write('
\n')
        response.write('Unexpected exception: (please report!)\n')
        response.write(cgi.escape(errorString))
        response.write('\n
\n') response.returncode = response.RETURN_OK except: sys.stderr.write(errorString+'\n') return response.returncode class spyceRequest: """Underlying Spyce request object. All implementations (CGI, Apache...) should subclass and implement the methods marked 'not implemented'.""" def __init__(self): self._in = None self._stack = [] def read(self, limit=None): if limit: return self._in.read(limit) else: return self._in.read() def readline(self, limit=None): if limit: return self._in.readline(limit) else: return self._in.readline() def env(self, name=None): raise 'not implemented' def getHeader(self, type=None): raise 'not implemented' def getServerID(self): raise 'not implemented' class spyceResponse: """Underlying Spyce response object. All implementations (CGI, Apache...) should subclass and implement the methods marked 'not implemented', and also properly define the RETURN codes.""" RETURN_CONTINUE = 100 RETURN_SWITCHING_PROTOCOLS = 101 RETURN_OK = 200 RETURN_CREATED = 201 RETURN_ACCEPTED = 202 RETURN_NON_AUTHORITATIVE_INFORMATION = 203 RETURN_NO_CONTENT = 204 RETURN_RESET_CONTENT = 205 RETURN_PARTIAL_CONTENT = 206 RETURN_MULTIPLE_CHOICES = 300 RETURN_MOVED_PERMANENTLY = 301 RETURN_MOVED_TEMPORARILY = 302 RETURN_SEE_OTHER = 303 RETURN_NOT_MODIFIED = 304 RETURN_USE_PROXY = 305 RETURN_TEMPORARY_REDIRECT = 307 RETURN_BAD_REQUEST = 400 RETURN_UNAUTHORIZED = 401 RETURN_PAYMENT_REQUIRED = 402 RETURN_FORBIDDEN = 403 RETURN_NOT_FOUND = 404 RETURN_METHOD_NOT_ALLOWED = 405 RETURN_NOT_ACCEPTABLE = 406 RETURN_PROXY_AUTHENTICATION_REQUIRED = 407 RETURN_REQUEST_TIMEOUT = 408 RETURN_CONFLICT = 409 RETURN_GONE = 410 RETURN_LENGTH_REQUIRED = 411 RETURN_PRECONDITION_FAILED = 412 RETURN_REQUEST_ENTITY_TOO_LARGE = 413 RETURN_REQUEST_URI_TOO_LONG = 414 RETURN_UNSUPPORTED_MEDIA_TYPE = 415 RETURN_REQUEST_RANGE_NOT_SATISFIABLE = 416 RETURN_EXPECTATION_FAILED = 417 RETURN_INTERNAL_SERVER_ERROR = 500 RETURN_NOT_IMPLEMENTED = 501 RETURN_BAD_GATEWAY = 502 RETURN_SERVICE_UNAVAILABLE = 503 RETURN_GATEWAY_TIMEOUT = 504 RETURN_HTTP_VERSION_NOT_SUPPORTED = 505 RETURN_CODE = { RETURN_CONTINUE: 'CONTINUE', RETURN_SWITCHING_PROTOCOLS: 'SWITCHING PROTOCOLS', RETURN_OK: 'OK', RETURN_CREATED: 'CREATED', RETURN_ACCEPTED: 'ACCEPTED', RETURN_NON_AUTHORITATIVE_INFORMATION: 'NON AUTHORITATIVE INFORMATION', RETURN_NO_CONTENT: 'NO CONTENT', RETURN_RESET_CONTENT: 'RESET CONTENT', RETURN_PARTIAL_CONTENT: 'PARTIAL CONTENT', RETURN_MULTIPLE_CHOICES: 'MULTIPLE CHOICES', RETURN_MOVED_PERMANENTLY: 'MOVED PERMANENTLY', RETURN_MOVED_TEMPORARILY: 'MOVED TEMPORARILY', RETURN_SEE_OTHER: 'SEE OTHER', RETURN_NOT_MODIFIED: 'NOT MODIFIED', RETURN_USE_PROXY: 'USE PROXY', RETURN_TEMPORARY_REDIRECT: 'TEMPORARY REDIRECT', RETURN_BAD_REQUEST: 'BAD REQUEST', RETURN_UNAUTHORIZED: 'UNAUTHORIZED', RETURN_PAYMENT_REQUIRED: 'PAYMENT REQUIRED', RETURN_FORBIDDEN: 'FORBIDDEN', RETURN_NOT_FOUND: 'NOT FOUND', RETURN_METHOD_NOT_ALLOWED: 'METHOD NOT ALLOWED', RETURN_NOT_ACCEPTABLE: 'NOT ACCEPTABLE', RETURN_PROXY_AUTHENTICATION_REQUIRED: 'PROXY AUTHENTICATION REQUIRED', RETURN_REQUEST_TIMEOUT: 'REQUEST TIMEOUT', RETURN_CONFLICT: 'CONFLICT', RETURN_GONE: 'GONE', RETURN_LENGTH_REQUIRED: 'LENGTH REQUIRED', RETURN_PRECONDITION_FAILED: 'PRECONDITION FAILED', RETURN_REQUEST_ENTITY_TOO_LARGE: 'REQUEST ENTITY TOO LARGE', RETURN_REQUEST_URI_TOO_LONG: 'REQUEST URI TOO LONG', RETURN_UNSUPPORTED_MEDIA_TYPE: 'UNSUPPORTED MEDIA TYPE', RETURN_REQUEST_RANGE_NOT_SATISFIABLE: 'REQUEST RANGE NOT SATISFIABLE', RETURN_EXPECTATION_FAILED: 'EXPECTATION FAILED', RETURN_INTERNAL_SERVER_ERROR: 'INTERNAL SERVER ERROR', RETURN_NOT_IMPLEMENTED: 'NOT IMPLEMENTED', RETURN_BAD_GATEWAY: 'BAD GATEWAY', RETURN_SERVICE_UNAVAILABLE: 'SERVICE UNAVAILABLE', RETURN_GATEWAY_TIMEOUT: 'GATEWAY TIMEOUT', RETURN_HTTP_VERSION_NOT_SUPPORTED: 'HTTP VERSION NOT SUPPORTED', } def __init__(self): pass def write(self, s): raise 'not implemented' def writeErr(self, s): raise 'not implemented' def close(self): raise 'not implemented' def clear(self): raise 'not implemented' def sendHeaders(self): raise 'not implemented' def clearHeaders(self): raise 'not implemented' def setContentType(self, content_type): raise 'not implemented' def setReturnCode(self, code): raise 'not implemented' def addHeader(self, type, data, replace=0): raise 'not implemented' def flush(self): raise 'not implemented' def unbuffer(self): raise 'not implemented' class spyceCode: '''Takes care of compiling the Spyce file, and generating a wrapper''' def __init__(self, code, key, filename=None, sig=''): # store variables self._filename = filename # generate code self._code, self._coderefs, self._modrefs, tagrefs = \ spyceCompile.spyceCompile(code, filename, sig, getServer()) def clear(): del getServer().spyce_cache[key] for taglibsrc in tagrefs: tag_dependencies.setdefault(taglibsrc, []).append(clear) # wrapper instantiation is slow, so we keep a pool around self._wrapperQueue = Queue.Queue() # wrappers def newWrapper(self): """ Get a wrapper for this code from queue, or make new one. Threadsafe thanke to queue object. """ try: return self._wrapperQueue.get_nowait() except Queue.Empty: pass DEBUG('creating new wrapper for %s\n' % self._filename) return spyceWrapper(self) def returnWrapper(self, w): """ Return wrapper back to queue after use (Mostly) Threadsafe thanke to queue object. ("Mostly" because we could actually store more wrappers than "limit" but this is not a problem worth introducing another lock to solve.) """ if self._wrapperQueue.qsize() < WRAPPER_LIMIT: self._wrapperQueue.put(w) # serialization -- used by spyceCache.fileCache def __getstate__(self): return self._filename, self._code, self._coderefs, self._modrefs def __setstate__(self, state): self._filename, self._code, self._coderefs, self._modrefs = state # it's faster to recreate wrappers than to write them out/read back in self._wrapperQueue = Queue.Queue() # accessors def getCode(self): "Return processed Spyce (i.e. Python) code" return self._code def getFilename(self): "Return source filename, if it exists" return self._filename def getCodeRefs(self): "Return python-to-Spyce code line references" return self._coderefs def getModRefs(self): "Return list of Spyce modules imported by Spyce code" return self._modrefs class spyceWrapper: """Wrapper object runs the entire show, bringing together the code, the Spyce environment, the request and response objects and the modules. This object is generated by a spyceCode object. The common Spyce handler code calls the 'processing' functions. Module writers interact with this object via the spyceModuleAPI calls. This is arguably the trickiest portion of the Spyce so don't touch unless you know what you are doing.""" def __init__(self, spycecode): # store variables self._spycecode = spycecode # api object self._api = self # module tracking self._modCache = {} self._modstarted = [] self._modules = {} # insert compiled python code into the _codeenv context self._codeenv = { spyceCompile.SPYCE_WRAPPER: self._api, 'db': getServer().config.db } try: exec self.getCode() in self._codeenv except SyntaxError: raise spyceException.pythonSyntaxError(self) # remember what freshly loaded context looked like, so we can # re-use this wrapper for others requests to the same Spyce. self._initialEnvKeys = self._codeenv.keys() # request, response self._response = self._request = None self._responseCallback = {} self._moduleCallback = {} self._parent = None def _startModule(self, name, file=None, as=None, force=0): "Initialise module for current request." if as==None: as=name if force or not self._codeenv.has_key(as): DEBUG(as+'.load') modclass = getServer().loadModule(name, file, self._spycecode.getFilename()) mod = modclass(self._api) self.setModule(as, mod, 0) DEBUG(as+'.start') mod.start() self._modstarted.append((as, mod)) else: mod = self._codeenv[as] return mod # spyce processing def spyceInit(self, request, response): "Initialise a Spyce for processing." self._parent = None self._request = request self._response = response for mod in DEFAULT_MODULES: self._startModule(mod) self._modstarteddefault = self._modstarted self._modstarted = [] for (modname, modfrom, modas) in self.getModRefs(): self._startModule(modname, modfrom, modas, 1) instance = self._codeenv[spyceCompile.SPYCE_CLASS]() self.process = getattr(instance, spyceCompile.SPYCE_PROCESS_FUNC) def spyceProcess(self, *args, **kwargs): """ Process current request, including recursing to parent tags; returns parent return code, or None """ if self._hasParent(): self.getModules()['stdout'].push() try: self.spyceProcessSingle(*args, **kwargs) finally: if self._hasParent(): result = self.getModules()['stdout'].pop() if self._hasParent(): if self._parent: # may have been if-d out if len(self._request._stack) >= MAX_STACK: try: # spoof a runtimeexception raise 'Maximum stack depth exceeded! (infinite parent template loop?)' except: raise spyceException.spyceRuntimeException() else: err = self._finishUserModules() if err: raise err (src, childargs) = self._parent childargs['_body'] = result return spyceFileHandler(self._request, self._response, src, 'child', kwargs={'child': spyceUtil.attrdict(childargs)}) else: # could have had a parent, but didn't: write body to the "real" outstream self._response.write(result) return None def spyceProcessSingle(self, *args, **kwargs): "Process the current Spyce request; no parent tag processing" path = sys.path try: # munge path so .spy can import from .py in current directory if self._spycecode.getFilename(): path = copy.copy(sys.path) sys.path.append(os.path.dirname(self._spycecode.getFilename())) dict = { '_spyce_process': self.process, '_spyce_args': args, '_spyce_kwargs': kwargs, } exec '_spyce_result = _spyce_process(*_spyce_args, **_spyce_kwargs)' in dict return dict['_spyce_result'] finally: sys.path = path def _finishUserModules(self, theError=None): try: self._modstarted.reverse() for as, mod in self._modstarted: try: DEBUG(as+'.finish') mod.finish(theError) except spyceException.spyceDone: pass except spyceException.spyceRedirect, e: # We don't want to just return a new handler here because # (a) we want to finish out the other modules first, # (b) it's not a form of state management we want to encourage, # (as a user, figuring out which module's redirect takes precedence could get tricky) # (c) finish is designed for a module to cleanup after itself, not to # silently affect the page it was on ("explicit is better than implicit") try: raise Exception("Modules may not perform internal redirects in their start/finish methods. (External redirects are permitted, if you really must do this.)") except: theError = spyceException.spyceRuntimeException(self._api) except KeyboardInterrupt: raise except SystemExit: pass except: # let error module show an error page when it finishes theError = spyceException.spyceRuntimeException(self._api) finally: self._modstarted = [] return theError def spyceDestroy(self, theError=None): "Cleanup after the request processing." theError = self._finishUserModules(theError) or theError finishError = None try: # default modules self._modstarteddefault.reverse() for as, mod in self._modstarteddefault: try: DEBUG(as+'.finish') mod.finish(theError) except: finishError = 1 self._request = None self._response = None if finishError: raise finally: self.spyceCleanup() DEBUG('finished destroy for %s' % str(self._spycecode._filename)) def spyceCleanup(self): "Sweep the Spyce environment." self._modstarteddefault = [] # done by _finishUserModules: self._modstarted = [] self._modules = {} # changes to existing keys stick around but other globals are nuked # we'd reset back to a virgin dict entirely but that's too expensive # -- in practice this is "good enough" for e in self._codeenv.keys(): if e not in self._initialEnvKeys: del self._codeenv[e] def _hasParent(self): return self._codeenv[spyceCompile.SPYCE_CLASS]._has_parent # API methods def getStack(self): "Return spyce call stack (includes, parent templates, internal redirects)" return self._request._stack def getFilename(self): "Return filename of current Spyce" return self._spycecode.getFilename() def getCode(self): "Return processed Spyce (i.e. Python) code" return self._spycecode.getCode() def getCodeRefs(self): "Return python-to-Spyce code line references" return self._spycecode.getCodeRefs() def getModRefs(self): "Return list of import references in Spyce code" return self._spycecode.getModRefs() def getServerObject(self): "Return unique (per engine instance) server object" return getServer().serverobject def getServerGlobals(self): "Return server configuration globals" return getServer().globals def getServerID(self): "Return unique server identifier" return self._request.getServerID() def getPageError(self): "Return default page error value" return getServer().pageerror def getRequest(self): "Return internal request object" return self._request def getResponse(self): "Return internal response object" return self._response def setResponse(self, o): "Set internal response object" self._response = o for f in self._responseCallback.keys(): f() def registerResponseCallback(self, f): "Register a callback for when internal response changes" self._responseCallback[f] = 1 def unregisterResponseCallback(self, f): "Unregister a callback for when internal response changes" try: del self._responseCallback[f] except KeyError: pass def getModules(self): "Return references to currently loaded modules" return self._modules def getModule(self, name, as=None): """Get module reference. The module is dynamically loaded and initialised if it does not exist (ie. if it was not explicitly imported, but requested by another module during processing)""" return self._startModule(name, as=as) def setModule(self, name, mod, notify=1): "Add existing module (by reference) to Spyce namespace (used for includes)" self._codeenv[name] = mod self._modules[name] = mod if notify: for f in self._moduleCallback.keys(): f() def delModule(self, name, notify=1): "Add existing module (by reference) to Spyce namespace (used for includes)" del self._codeenv[name] del self._modules[name] if notify: for f in self._moduleCallback.keys(): f() def getGlobals(self): "Return the Spyce global namespace dictionary" return self._codeenv def registerModuleCallback(self, f): "Register a callback for modules change" self._moduleCallback[f] = 1 def unregisterModuleCallback(self, f): "Unregister a callback for modules change" try: del self._moduleCallback[f] except KeyError: pass def spyceFile(self, file): "Return a spyceCode object of a file" return getServer().spyce_cache[('file', file)] def spyceString(self, code): "Return a spyceCode object of a string" return getServer().spyce_cache[('string', code)] def spyceModule(self, name, file=None, rel_file=None): "Return Spyce module class" return getServer().loadModule(name, file, rel_file) def spyceTaglib(self, name, file=None, rel_file=None): "Return Spyce taglib class" return getServer().loadModule(name, file, rel_file) def setStdout(self, out): "Set the stdout stream (thread-safe)" serverout = getServer().stdout if serverout: serverout.setObject(out) else: sys.stdout = out def getStdout(self): "Get the stdout stream (thread-safe)" serverout = getServer().stdout if serverout: return serverout.getObject() else: return sys.stdout ################################################## # Spyce cache # def spyceFileCacheValid(key, validity): "Determine whether compiled Spyce is valid" try: filename, sig = key except: filename, sig = key, '' if not os.path.exists(filename): return 0 if not os.access(filename, os.R_OK): return 0 return not getServer().config.check_mtime or spyceCacheGetmtime(filename) == validity def spyceFileCacheGenerate(key): "Generate new Spyce wrapper (recompiles)." try: filename, sig = key except: filename, sig = key, '' DEBUG('generating new spyceCode for %s' % filename) # ensure file exists and we have permissions if not os.path.exists(filename): raise spyceException.spyceNotFound(filename) if not os.access(filename, os.R_OK): raise spyceException.spyceForbidden(filename) # generate mtime = spyceCacheGetmtime(filename) f = None try: f = open(filename) code = f.read() finally: if f: f.close() s = spyceCode(code, key, filename=filename, sig=sig) return mtime, s def spyceStringCacheValid(code, validity): return 1 def spyceStringCacheGenerate(key): try: code, sig = key except: code, sig = key, '' s = spyceCode(code, key, sig=sig) return None, s def spyceCacheValid((type, key), validity): return { 'string': spyceStringCacheValid, 'file': spyceFileCacheValid, }[type](key, validity) def spyceCacheGenerate((type, key)): return { 'string': spyceStringCacheGenerate, 'file': spyceFileCacheGenerate, }[type](key) def spyceCacheGetmtime(fname): while os.path.islink(fname): # chase links fname = os.path.join(os.path.dirname(fname), os.readlink(fname)) return os.path.getmtime(fname) ################################################## # Spyce common entry points # def spyceFileHandler(request, response, filename, sig='', args=None, kwargs=None, config=None): filename = os.path.realpath(filename) # normalize for cache's benefit request._stack.append(filename) return _spyceCommonHandler(request, response, ('file', (filename, sig)), args, kwargs, config) def spyceStringHandler(request, response, code, sig='', args=None, kwargs=None, config=None): request._stack.append('string') return _spyceCommonHandler(request, response, ('string', (code, sig)), args, kwargs, config) def _spyceCommonHandler(request, response, spyceInfo, args=None, kwargs=None, config=None): return getServer(config).commonHandler(request, response, spyceInfo, args, kwargs) if __name__ == '__main__': execfile(os.path.join(os.path.split(__file__)[0],'run_spyceCmd.py'))