home *** CD-ROM | disk | FTP | other *** search
- #!/usr/bin/env python
-
- __version__ = '1.3.11'
- __release__ = '1'
-
- ##################################################
- # SPYCE - Python-based HTML Scripting
- # Copyright (c) 2002 Rimon Barr.
- #
- # Refer to LICENCE for legalese
- #
- # Name: spyce
- # Author: Rimon Barr <rimon-AT-acm.org>
- # Start date: 8 April 2002
- # Purpose: Python Server Pages
- # WWW: http://spyce.sourceforge.net/
- # CVS: $Id: spyce.py 5659 2006-04-27 16:15:15Z jwnmulder $
- ##################################################
-
- # note: doc string used in documentation: doc/index.spy
- __doc__ = '''SPYCE is a server-side language that supports simple and
- efficient Python-based dynamic HTML generation. Those who are familiar with
- JSP, PHP, or ASP and like Python, should have a look at Spyce. Its modular
- design makes it very flexible and extensible. It can also be used as a
- command-line utility for static text pre-processing or as a web-server
- proxy.'''
-
- import sys, os, copy, string, imp
- import spyceConfig, spyceCompile, spyceException
- import spyceModule, spyceTag
- import spyceLock, spyceCache, spyceUtil
-
- ##################################################
- # Spyce engine globals
- #
-
- # spyceServer object - one per engine instance
- SPYCE_SERVER = None
- def getServer(
- config_file=None,
- overide_www_port=None,
- overide_www_root=None,
- force=0):
- global SPYCE_SERVER
- if force or not SPYCE_SERVER:
- SPYCE_SERVER = spyceServer(
- config_file=config_file,
- overide_www_root=overide_www_root,
- overide_www_port=overide_www_port,
- )
- return SPYCE_SERVER
-
- SPYCE_LOADER = 'spyceLoader'
- SPYCE_ENTRY = 'SPYCE_ENTRY'
-
- ##################################################
- # 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_file=None,
- overide_www_root=None,
- overide_www_port=None,
- ):
- # server object
- self.serverobject = spyceServerObject()
- # http headers
- try: self.entry = os.environ[SPYCE_ENTRY]
- except: self.entry = 'UNKNOWN'
- self.spyceHeader = 'Spyce/%s_%s Python/%s' % (self.entry, str(__version__), sys.version[:3])
- # configuration dictionary
- self.config = spyceConfig.spyceConfig(
- file=config_file,
- overide_www_root=overide_www_root,
- overide_www_port=overide_www_port,
- )
- # server globals/constants
- self.globals = self.config.getSpyceGlobals()
- # spyce module search path
- self.path = self.config.getSpycePath()
- # concurrency mode
- self.concurrency = self.config.getSpyceConcurrency()
- # imports
- self.imports = self.config.getSpyceImport()
- # debug mode
- self.debug = self.config.getSpyceDebug()
- # spyce cache
- type, info = self.config.getSpyceCache()
- if type in ('file',):
- type = spyceCache.fileCache(info)
- elif type in ('mem', 'memory'):
- type = spyceCache.memoryCache(info)
- else: type = spyceCache.memoryCache()
- if self.debug: type = None
- self.spyce_cache = spyceCache.semanticCache(type, spyceCacheValid, spyceCacheGenerate)
- # spyce module cache
- self.module_cache = {}
- if self.debug:
- self.module_cache = None
- # page error handler
- pageerror = self.config.getSpycePageError()
- if pageerror[0]=='string':
- pageerror = pageerror[0], self.loadModule(pageerror[2], pageerror[1]+'.py')
- self.pageerror = pageerror
- # engine error handler
- error = self.config.getSpyceError()
- self.error = self.loadModule(error[1], error[0]+'.py')
- # spyce thread-safe stdout object
- if self.concurrency == spyceConfig.SPYCE_CONCURRENCY_THREAD:
- self.stdout = spyceUtil.ThreadedWriter(sys.stdout)
- self.lock = spyceLock.threadLock()
- else:
- self.stdout = spyceUtil.NonThreadedWriter(sys.stdout)
- self.lock = spyceLock.dummyLock()
- # set sys.stdout
- sys.stdout = self.stdout
- def loadModule(self, name, file=None, rel_file=None):
- "Find and load a module, with caching"
- if not file: file=name+'.py'
- key = name, file, rel_file
- if self.module_cache!=None:
- try: return self.module_cache[key]
- except: pass # cache miss
- def loadModuleHelper(file=file, rel_file=rel_file, path=self.path):
- if rel_file: path = path + [os.path.dirname(rel_file)]
- for p in path:
- f=None
- try:
- p = os.path.join(p, file)
- if os.path.exists(p) and os.access(p, os.R_OK):
- f = open(p, 'r')
- return imp.load_source(SPYCE_LOADER, p, f)
- finally:
- if f: f.close()
- raise 'unable to find module "%s" in path: %s' % (file, path)
- # load and cache module
- dict = {'loadModuleHelper': loadModuleHelper}
- exec 'foo = loadModuleHelper()' in dict
- mod = eval('dict["foo"].%s' % name)
- if self.module_cache!=None:
- self.module_cache[key] = mod
- return mod
- 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 NOT thread safe."
- try:
- thespyce = theError = None
- try:
- spycecode = self.spyce_cache[spyceInfo]
- thespyce = spycecode.newWrapper()
- response.addHeader('X-Spyce', self.spyceHeader, 1)
- try:
- thespyce.spyceInit(request, response)
- if args==None: args=[]
- if kwargs==None: kwargs={}
- apply(thespyce.spyceProcess, args, kwargs)
- except spyceException.spyceRuntimeException, theError: pass
- finally:
- if thespyce:
- thespyce.spyceDestroy(theError)
- spycecode.returnWrapper(thespyce)
- 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:
- return self.error(self, request, response, e)
- except SystemExit: pass
- except:
- errorString = spyceUtil.exceptionString()
- try:
- import cgi
- response.clear()
- response.write('<html><pre>\n')
- response.write('Unexpected exception: (please report!)\n')
- response.write(cgi.escape(errorString))
- response.write('\n</pre></html>\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
- 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, filename=None, sig='', limit=3):
- # store variables
- self._filename = filename
- self._limit = limit
- # generate code
- self._code, self._coderefs, self._modrefs = \
- spyceCompile.spyceCompile(code, filename, sig, getServer())
- self._wrapperQueue = []
- self._wrappersMade = 0
- # wrappers
- def newWrapper(self):
- "Get a wrapper for this code from queue, or make new one"
- try: return self._wrapperQueue.pop()
- except IndexError: pass
- self._wrappersMade = self._wrappersMade + 1
- return spyceWrapper(self)
- def returnWrapper(self, w):
- "Return wrapper back to queue after use"
- if len(self._wrapperQueue)<self._limit:
- self._wrapperQueue.append(w)
- # serialization
- def __getstate__(self):
- return self._filename, self._code, self._coderefs, self._modrefs, self._limit
- def __setstate__(self, state):
- self._filename, self._code, self._coderefs, self._modrefs, self._limit = state
- self._wrapperQueue = []
- self._wrappersMade = 0
- # 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 import references in 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. It is
- NOT thread safe - new instances are generated as necessary by spyceCode!
- 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 arguable 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 = {}
- # compile python code
- self._codeenv = { spyceCompile.SPYCE_WRAPPER: self._api }
- try: exec self._spycecode.getCode() in self._codeenv
- except SyntaxError: raise spyceException.pythonSyntaxError(self)
- self._freshenv = self._codeenv.keys()
- # spyce hooks
- noop = lambda *args: None
- self.process = self._codeenv[spyceCompile.SPYCE_PROCESS_FUNC]
- try: self.init = self._codeenv[spyceCompile.SPYCE_INIT_FUNC]
- except: self.init = noop
- try: self.destroy = self._codeenv[spyceCompile.SPYCE_DESTROY_FUNC]
- except: self.destroy = noop
- # request, response
- self._response = self._request = None
- self._responseCallback = {}
- self._moduleCallback = {}
- 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):
- modclass = getServer().loadModule(name, file, self._spycecode.getFilename())
- mod = modclass(self._api)
- self.setModule(as, mod, 0)
- 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._request = request
- self._response = response
- for mod in ('request', 'response', 'stdout', 'error'):
- self._startModule(mod)
- for (modname, modfrom, modas) in self.getModRefs():
- self._startModule(modname, modfrom, modas, 1)
- exec '_spyce_start()' in { '_spyce_start': self.init }
- def spyceProcess(self, *args, **kwargs):
- "Process the current Spyce request."
- path = sys.path
- 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 = apply(_spyce_process, _spyce_args, _spyce_kwargs)' in dict
- sys.path = path
- return dict['_spyce_result']
- def spyceDestroy(self, theError=None):
- "Cleanup after the request processing."
- try:
- exec '_spyce_finish()' in { '_spyce_finish': self.destroy }
- self._modstarted.reverse()
- finishError = None
- for as, mod in self._modstarted:
- try: mod.finish(theError)
- except: finishError = 1
- self._request = None
- self._response = None
- if finishError: raise
- finally:
- self.spyceCleanup()
- def spyceCleanup(self):
- "Sweep the Spyce environment."
- self._modstarted = []
- self._modules = {}
- if self._freshenv!=None:
- for e in self._codeenv.keys():
- if e not in self._freshenv:
- del self._codeenv[e]
- # API methods
- 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):
- """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)
- 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 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)"
- return getServer().stdout.setObject(out)
- def getStdout(self):
- "Get the stdout stream (thread-safe)"
- return getServer().stdout.getObject()
-
- ##################################################
- # Spyce cache
- #
-
- def spyceFileCacheValid(key, validity):
- "Determine whether compiled Spyce is valid"
- try:
- filename, sig = key
- except:
- filename, sig = key, ''
- try:
- if not os.path.exists(filename):
- return 0
- if not os.access(filename, os.R_OK):
- return 0
- return os.path.getmtime(filename) == validity
- except KeyboardInterrupt: raise
- except:
- return 0
-
- def spyceFileCacheGenerate(key):
- "Generate new Spyce wrapper (recompiles)."
- try:
- filename, sig = key
- except:
- filename, sig = key, ''
- # 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 = os.path.getmtime(filename)
- f = None
- try:
- f = open(filename)
- code = f.read()
- finally:
- if f: f.close()
- s = spyceCode(code, 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, 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)
-
-
- ##################################################
- # Spyce common entry points
- #
-
- def spyceFileHandler(request, response, filename, sig='', args=None, kwargs=None, config_file=None):
- return _spyceCommonHandler(request, response, ('file', (filename, sig)), args, kwargs, config_file)
-
- def spyceStringHandler(request, response, code, sig='', args=None, kwargs=None, config_file=None):
- return _spyceCommonHandler(request, response, ('string', (code, sig)), args, kwargs, config_file)
-
- def _spyceCommonHandler(request, response, spyceInfo, args=None, kwargs=None, config_file=None):
- return getServer(config_file).commonHandler(request, response, spyceInfo, args, kwargs)
-
- if __name__ == '__main__':
- execfile('run_spyceCmd.py')
-
-