#!/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 <rimon-AT-acm.org>
# 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('<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
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'))
|