home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2009 May / maximum-cd-2009-05.iso / DiscContents / XBMC_for_Windows-8.10.exe / system / python / spyce / spyce.py < prev    next >
Encoding:
Python Source  |  2008-11-03  |  22.1 KB  |  624 lines

  1. #!/usr/bin/env python
  2.  
  3. __version__ = '1.3.11'
  4. __release__ = '1'
  5.  
  6. ##################################################
  7. # SPYCE - Python-based HTML Scripting
  8. # Copyright (c) 2002 Rimon Barr.
  9. #
  10. # Refer to LICENCE for legalese
  11. #
  12. # Name:        spyce
  13. # Author:      Rimon Barr <rimon-AT-acm.org>
  14. # Start date:  8 April 2002
  15. # Purpose:     Python Server Pages
  16. # WWW:         http://spyce.sourceforge.net/
  17. # CVS:         $Id: spyce.py 5659 2006-04-27 16:15:15Z jwnmulder $
  18. ##################################################
  19.  
  20. # note: doc string used in documentation: doc/index.spy
  21. __doc__ = '''SPYCE is a server-side language that supports simple and
  22. efficient Python-based dynamic HTML generation. Those who are familiar with
  23. JSP, PHP, or ASP and like Python, should have a look at Spyce. Its modular
  24. design makes it very flexible and extensible. It can also be used as a
  25. command-line utility for static text pre-processing or as a web-server 
  26. proxy.'''
  27.  
  28. import sys, os, copy, string, imp
  29. import spyceConfig, spyceCompile, spyceException
  30. import spyceModule, spyceTag
  31. import spyceLock, spyceCache, spyceUtil
  32.  
  33. ##################################################
  34. # Spyce engine globals
  35. #
  36.  
  37. # spyceServer object - one per engine instance
  38. SPYCE_SERVER = None
  39. def getServer(
  40.     config_file=None, 
  41.     overide_www_port=None,
  42.     overide_www_root=None,
  43.     force=0):
  44.   global SPYCE_SERVER
  45.   if force or not SPYCE_SERVER:
  46.     SPYCE_SERVER = spyceServer(
  47.       config_file=config_file,
  48.       overide_www_root=overide_www_root,
  49.       overide_www_port=overide_www_port,
  50.     )
  51.   return SPYCE_SERVER
  52.  
  53. SPYCE_LOADER = 'spyceLoader'
  54. SPYCE_ENTRY = 'SPYCE_ENTRY'
  55.  
  56. ##################################################
  57. # Spyce core objects
  58. #
  59.  
  60. class spyceServerObject:
  61.   "serverObject placeholder"
  62.   pass
  63.  
  64. class spyceServer:
  65.   "One per server, stored in SPYCE_SERVER (above) at processing of first request."
  66.   def __init__(self, 
  67.       config_file=None,
  68.       overide_www_root=None,
  69.       overide_www_port=None,
  70.     ):
  71.     # server object
  72.     self.serverobject = spyceServerObject()
  73.     # http headers
  74.     try: self.entry = os.environ[SPYCE_ENTRY]
  75.     except: self.entry = 'UNKNOWN'
  76.     self.spyceHeader = 'Spyce/%s_%s Python/%s' % (self.entry, str(__version__), sys.version[:3])
  77.     # configuration dictionary
  78.     self.config = spyceConfig.spyceConfig(
  79.       file=config_file,
  80.       overide_www_root=overide_www_root,
  81.       overide_www_port=overide_www_port,
  82.     )
  83.     # server globals/constants
  84.     self.globals = self.config.getSpyceGlobals()
  85.     # spyce module search path
  86.     self.path = self.config.getSpycePath()
  87.     # concurrency mode
  88.     self.concurrency = self.config.getSpyceConcurrency()
  89.     # imports
  90.     self.imports = self.config.getSpyceImport()
  91.     # debug mode
  92.     self.debug = self.config.getSpyceDebug()
  93.     # spyce cache
  94.     type, info = self.config.getSpyceCache()
  95.     if type in ('file',):
  96.       type = spyceCache.fileCache(info)
  97.     elif type in ('mem', 'memory'):
  98.       type = spyceCache.memoryCache(info)
  99.     else: type = spyceCache.memoryCache()
  100.     if self.debug: type = None
  101.     self.spyce_cache = spyceCache.semanticCache(type, spyceCacheValid, spyceCacheGenerate)
  102.     # spyce module cache
  103.     self.module_cache = {}
  104.     if self.debug:
  105.       self.module_cache = None
  106.     # page error handler
  107.     pageerror = self.config.getSpycePageError()
  108.     if pageerror[0]=='string':
  109.       pageerror = pageerror[0], self.loadModule(pageerror[2], pageerror[1]+'.py')
  110.     self.pageerror = pageerror
  111.     # engine error handler
  112.     error = self.config.getSpyceError()
  113.     self.error = self.loadModule(error[1], error[0]+'.py')
  114.     # spyce thread-safe stdout object
  115.     if self.concurrency == spyceConfig.SPYCE_CONCURRENCY_THREAD:
  116.       self.stdout = spyceUtil.ThreadedWriter(sys.stdout)
  117.       self.lock = spyceLock.threadLock()
  118.     else:
  119.       self.stdout = spyceUtil.NonThreadedWriter(sys.stdout)
  120.       self.lock = spyceLock.dummyLock()
  121.     # set sys.stdout
  122.     sys.stdout = self.stdout
  123.   def loadModule(self, name, file=None, rel_file=None):
  124.     "Find and load a module, with caching"
  125.     if not file: file=name+'.py'
  126.     key = name, file, rel_file
  127.     if self.module_cache!=None:
  128.       try: return self.module_cache[key]
  129.       except: pass # cache miss
  130.     def loadModuleHelper(file=file, rel_file=rel_file, path=self.path):
  131.       if rel_file: path = path + [os.path.dirname(rel_file)]
  132.       for p in path:
  133.         f=None
  134.         try:
  135.           p = os.path.join(p, file)
  136.           if os.path.exists(p) and os.access(p, os.R_OK):
  137.             f = open(p, 'r')
  138.             return imp.load_source(SPYCE_LOADER, p, f)
  139.         finally:
  140.           if f: f.close()
  141.       raise 'unable to find module "%s" in path: %s' % (file, path)
  142.     # load and cache module
  143.     dict = {'loadModuleHelper': loadModuleHelper}
  144.     exec 'foo = loadModuleHelper()' in dict
  145.     mod = eval('dict["foo"].%s' % name)
  146.     if self.module_cache!=None:
  147.       self.module_cache[key] = mod
  148.     return mod
  149.   def fileHandler(self, request, response, filename, sig='', args=None, kwargs=None):
  150.     return self.commonHandler(request, response, ('file', (filename, sig)), args, kwargs)
  151.   def stringHandler(self, request, response, code, sig='', args=None, kwargs=None):
  152.     return self.commonHandler(request, response, ('string', (code, sig)), args, kwargs)
  153.   def commonHandler(self, request, response, spyceInfo, args=None, kwargs=None):
  154.     "Handle a request. This method is NOT thread safe."
  155.     try:
  156.       thespyce = theError = None
  157.       try:
  158.         spycecode = self.spyce_cache[spyceInfo]
  159.         thespyce = spycecode.newWrapper()
  160.         response.addHeader('X-Spyce', self.spyceHeader, 1)
  161.         try:
  162.           thespyce.spyceInit(request, response)
  163.           if args==None: args=[]
  164.           if kwargs==None: kwargs={}
  165.           apply(thespyce.spyceProcess, args, kwargs)
  166.         except spyceException.spyceRuntimeException, theError: pass
  167.       finally:
  168.         if thespyce:
  169.           thespyce.spyceDestroy(theError)
  170.           spycecode.returnWrapper(thespyce)
  171.     except spyceException.spyceDone: pass
  172.     except spyceException.spyceRedirect, e:
  173.       return spyceFileHandler(request, response, e.filename)
  174.     except KeyboardInterrupt: raise
  175.     except (spyceException.spyceNotFound, spyceException.spyceForbidden, 
  176.         spyceException.spyceSyntaxError, spyceException.pythonSyntaxError, 
  177.         SyntaxError), e:
  178.       return self.error(self, request, response, e)
  179.     except SystemExit: pass
  180.     except:
  181.       errorString = spyceUtil.exceptionString()
  182.       try:
  183.         import cgi
  184.         response.clear()
  185.         response.write('<html><pre>\n')
  186.         response.write('Unexpected exception: (please report!)\n')
  187.         response.write(cgi.escape(errorString))
  188.         response.write('\n</pre></html>\n')
  189.         response.returncode = response.RETURN_OK
  190.       except:
  191.         sys.stderr.write(errorString+'\n')
  192.     return response.returncode
  193.  
  194. class spyceRequest:
  195.   """Underlying Spyce request object. All implementations (CGI, Apache...)
  196.   should subclass and implement the methods marked 'not implemented'."""
  197.   def __init__(self):
  198.     self._in = None
  199.   def read(self, limit=None):
  200.     if limit:
  201.       return self._in.read(limit)
  202.     else:
  203.       return self._in.read()
  204.   def readline(self, limit=None):
  205.     if limit:
  206.       return self._in.readline(limit)
  207.     else:
  208.       return self._in.readline()
  209.   def env(self, name=None):
  210.     raise 'not implemented'
  211.   def getHeader(self, type=None):
  212.     raise 'not implemented'
  213.   def getServerID(self):
  214.     raise 'not implemented'
  215.  
  216. class spyceResponse:
  217.   """Underlying Spyce response object. All implementations (CGI, Apache...)
  218.   should subclass and implement the methods marked 'not implemented', and
  219.   also properly define the RETURN codes."""
  220.   RETURN_CONTINUE = 100
  221.   RETURN_SWITCHING_PROTOCOLS = 101
  222.   RETURN_OK = 200
  223.   RETURN_CREATED = 201
  224.   RETURN_ACCEPTED = 202
  225.   RETURN_NON_AUTHORITATIVE_INFORMATION = 203
  226.   RETURN_NO_CONTENT = 204
  227.   RETURN_RESET_CONTENT = 205
  228.   RETURN_PARTIAL_CONTENT = 206
  229.   RETURN_MULTIPLE_CHOICES = 300
  230.   RETURN_MOVED_PERMANENTLY = 301
  231.   RETURN_MOVED_TEMPORARILY = 302
  232.   RETURN_SEE_OTHER = 303
  233.   RETURN_NOT_MODIFIED = 304
  234.   RETURN_USE_PROXY = 305
  235.   RETURN_TEMPORARY_REDIRECT = 307
  236.   RETURN_BAD_REQUEST = 400
  237.   RETURN_UNAUTHORIZED = 401
  238.   RETURN_PAYMENT_REQUIRED = 402
  239.   RETURN_FORBIDDEN = 403
  240.   RETURN_NOT_FOUND = 404
  241.   RETURN_METHOD_NOT_ALLOWED = 405
  242.   RETURN_NOT_ACCEPTABLE = 406
  243.   RETURN_PROXY_AUTHENTICATION_REQUIRED = 407
  244.   RETURN_REQUEST_TIMEOUT = 408
  245.   RETURN_CONFLICT = 409
  246.   RETURN_GONE = 410
  247.   RETURN_LENGTH_REQUIRED = 411
  248.   RETURN_PRECONDITION_FAILED = 412
  249.   RETURN_REQUEST_ENTITY_TOO_LARGE = 413
  250.   RETURN_REQUEST_URI_TOO_LONG = 414
  251.   RETURN_UNSUPPORTED_MEDIA_TYPE = 415
  252.   RETURN_REQUEST_RANGE_NOT_SATISFIABLE = 416
  253.   RETURN_EXPECTATION_FAILED = 417
  254.   RETURN_INTERNAL_SERVER_ERROR = 500
  255.   RETURN_NOT_IMPLEMENTED = 501
  256.   RETURN_BAD_GATEWAY = 502
  257.   RETURN_SERVICE_UNAVAILABLE = 503
  258.   RETURN_GATEWAY_TIMEOUT = 504
  259.   RETURN_HTTP_VERSION_NOT_SUPPORTED = 505
  260.   RETURN_CODE = {
  261.     RETURN_CONTINUE: 'CONTINUE',
  262.     RETURN_SWITCHING_PROTOCOLS: 'SWITCHING PROTOCOLS',
  263.     RETURN_OK: 'OK',
  264.     RETURN_CREATED: 'CREATED',
  265.     RETURN_ACCEPTED: 'ACCEPTED',
  266.     RETURN_NON_AUTHORITATIVE_INFORMATION: 'NON AUTHORITATIVE INFORMATION',
  267.     RETURN_NO_CONTENT: 'NO CONTENT',
  268.     RETURN_RESET_CONTENT: 'RESET CONTENT',
  269.     RETURN_PARTIAL_CONTENT: 'PARTIAL CONTENT',
  270.     RETURN_MULTIPLE_CHOICES: 'MULTIPLE CHOICES',
  271.     RETURN_MOVED_PERMANENTLY: 'MOVED PERMANENTLY',
  272.     RETURN_MOVED_TEMPORARILY: 'MOVED TEMPORARILY',
  273.     RETURN_SEE_OTHER: 'SEE OTHER',
  274.     RETURN_NOT_MODIFIED: 'NOT MODIFIED',
  275.     RETURN_USE_PROXY: 'USE PROXY',
  276.     RETURN_TEMPORARY_REDIRECT: 'TEMPORARY REDIRECT',
  277.     RETURN_BAD_REQUEST: 'BAD REQUEST',
  278.     RETURN_UNAUTHORIZED: 'UNAUTHORIZED',
  279.     RETURN_PAYMENT_REQUIRED: 'PAYMENT REQUIRED',
  280.     RETURN_FORBIDDEN: 'FORBIDDEN',
  281.     RETURN_NOT_FOUND: 'NOT FOUND',
  282.     RETURN_METHOD_NOT_ALLOWED: 'METHOD NOT ALLOWED',
  283.     RETURN_NOT_ACCEPTABLE: 'NOT ACCEPTABLE',
  284.     RETURN_PROXY_AUTHENTICATION_REQUIRED: 'PROXY AUTHENTICATION REQUIRED',
  285.     RETURN_REQUEST_TIMEOUT: 'REQUEST TIMEOUT',
  286.     RETURN_CONFLICT: 'CONFLICT',
  287.     RETURN_GONE: 'GONE',
  288.     RETURN_LENGTH_REQUIRED: 'LENGTH REQUIRED',
  289.     RETURN_PRECONDITION_FAILED: 'PRECONDITION FAILED',
  290.     RETURN_REQUEST_ENTITY_TOO_LARGE: 'REQUEST ENTITY TOO LARGE',
  291.     RETURN_REQUEST_URI_TOO_LONG: 'REQUEST URI TOO LONG',
  292.     RETURN_UNSUPPORTED_MEDIA_TYPE: 'UNSUPPORTED MEDIA TYPE',
  293.     RETURN_REQUEST_RANGE_NOT_SATISFIABLE: 'REQUEST RANGE NOT SATISFIABLE',
  294.     RETURN_EXPECTATION_FAILED: 'EXPECTATION FAILED',
  295.     RETURN_INTERNAL_SERVER_ERROR: 'INTERNAL SERVER ERROR',
  296.     RETURN_NOT_IMPLEMENTED: 'NOT IMPLEMENTED',
  297.     RETURN_BAD_GATEWAY: 'BAD GATEWAY',
  298.     RETURN_SERVICE_UNAVAILABLE: 'SERVICE UNAVAILABLE',
  299.     RETURN_GATEWAY_TIMEOUT: 'GATEWAY TIMEOUT',
  300.     RETURN_HTTP_VERSION_NOT_SUPPORTED: 'HTTP VERSION NOT SUPPORTED',
  301.   }
  302.   def __init__(self):
  303.     pass
  304.   def write(self, s):
  305.     raise 'not implemented'
  306.   def writeErr(self, s):
  307.     raise 'not implemented'
  308.   def close(self):
  309.     raise 'not implemented'
  310.   def clear(self):
  311.     raise 'not implemented'
  312.   def sendHeaders(self):
  313.     raise 'not implemented'
  314.   def clearHeaders(self):
  315.     raise 'not implemented'
  316.   def setContentType(self, content_type):
  317.     raise 'not implemented'
  318.   def setReturnCode(self, code):
  319.     raise 'not implemented'
  320.   def addHeader(self, type, data, replace=0):
  321.     raise 'not implemented'
  322.   def flush(self):
  323.     raise 'not implemented'
  324.   def unbuffer(self):
  325.     raise 'not implemented'
  326.  
  327. class spyceCode:
  328.   '''Takes care of compiling the Spyce file, and generating a wrapper'''
  329.   def __init__(self, code, filename=None, sig='', limit=3):
  330.     # store variables
  331.     self._filename = filename
  332.     self._limit = limit
  333.     # generate code
  334.     self._code, self._coderefs, self._modrefs = \
  335.       spyceCompile.spyceCompile(code, filename, sig, getServer())
  336.     self._wrapperQueue = []
  337.     self._wrappersMade = 0
  338.   # wrappers
  339.   def newWrapper(self):
  340.     "Get a wrapper for this code from queue, or make new one"
  341.     try: return self._wrapperQueue.pop()
  342.     except IndexError: pass
  343.     self._wrappersMade = self._wrappersMade + 1
  344.     return spyceWrapper(self)
  345.   def returnWrapper(self, w):
  346.     "Return wrapper back to queue after use"
  347.     if len(self._wrapperQueue)<self._limit:
  348.       self._wrapperQueue.append(w)
  349.   # serialization
  350.   def __getstate__(self):
  351.     return self._filename, self._code, self._coderefs, self._modrefs, self._limit
  352.   def __setstate__(self, state):
  353.     self._filename, self._code, self._coderefs, self._modrefs, self._limit = state
  354.     self._wrapperQueue = []
  355.     self._wrappersMade = 0
  356.   # accessors
  357.   def getCode(self):
  358.     "Return processed Spyce (i.e. Python) code"
  359.     return self._code
  360.   def getFilename(self):
  361.     "Return source filename, if it exists"
  362.     return self._filename
  363.   def getCodeRefs(self):
  364.     "Return python-to-Spyce code line references"
  365.     return self._coderefs
  366.   def getModRefs(self):
  367.     "Return list of import references in Spyce code"
  368.     return self._modrefs
  369.  
  370. class spyceWrapper:
  371.   """Wrapper object runs the entire show, bringing together the code, the
  372.   Spyce environment, the request and response objects and the modules. It is
  373.   NOT thread safe - new instances are generated as necessary by spyceCode!
  374.   This object is generated by a spyceCode object. The common Spyce handler
  375.   code calls the 'processing' functions. Module writers interact with this
  376.   object via the spyceModuleAPI calls. This is arguable the trickiest portion
  377.   of the Spyce so don't touch unless you know what you are doing."""
  378.   def __init__(self, spycecode):
  379.     # store variables
  380.     self._spycecode = spycecode
  381.     # api object
  382.     self._api = self
  383.     # module tracking
  384.     self._modCache = {}
  385.     self._modstarted = []
  386.     self._modules = {}
  387.     # compile python code
  388.     self._codeenv = { spyceCompile.SPYCE_WRAPPER: self._api }
  389.     try: exec self._spycecode.getCode() in self._codeenv
  390.     except SyntaxError: raise spyceException.pythonSyntaxError(self)
  391.     self._freshenv = self._codeenv.keys()
  392.     # spyce hooks
  393.     noop = lambda *args: None
  394.     self.process = self._codeenv[spyceCompile.SPYCE_PROCESS_FUNC]
  395.     try: self.init = self._codeenv[spyceCompile.SPYCE_INIT_FUNC]
  396.     except: self.init = noop
  397.     try: self.destroy = self._codeenv[spyceCompile.SPYCE_DESTROY_FUNC]
  398.     except: self.destroy = noop
  399.     # request, response
  400.     self._response = self._request = None
  401.     self._responseCallback = {}
  402.     self._moduleCallback = {}
  403.   def _startModule(self, name, file=None, as=None, force=0):
  404.     "Initialise module for current request."
  405.     if as==None: as=name
  406.     if force or not self._codeenv.has_key(as):
  407.       modclass = getServer().loadModule(name, file, self._spycecode.getFilename())
  408.       mod = modclass(self._api)
  409.       self.setModule(as, mod, 0)
  410.       mod.start()
  411.       self._modstarted.append((as, mod))
  412.     else: mod = self._codeenv[as]
  413.     return mod
  414.   # spyce processing
  415.   def spyceInit(self, request, response):
  416.     "Initialise a Spyce for processing."
  417.     self._request = request
  418.     self._response = response
  419.     for mod in ('request', 'response', 'stdout', 'error'):
  420.       self._startModule(mod)
  421.     for (modname, modfrom, modas) in self.getModRefs():
  422.       self._startModule(modname, modfrom, modas, 1)
  423.     exec '_spyce_start()' in { '_spyce_start': self.init }
  424.   def spyceProcess(self, *args, **kwargs):
  425.     "Process the current Spyce request."
  426.     path = sys.path
  427.     if self._spycecode.getFilename():
  428.       path = copy.copy(sys.path)
  429.       sys.path.append(os.path.dirname(self._spycecode.getFilename()))
  430.     dict = { '_spyce_process': self.process,
  431.       '_spyce_args': args, '_spyce_kwargs': kwargs, }
  432.     exec '_spyce_result = apply(_spyce_process, _spyce_args, _spyce_kwargs)' in dict
  433.     sys.path = path
  434.     return dict['_spyce_result']
  435.   def spyceDestroy(self, theError=None):
  436.     "Cleanup after the request processing."
  437.     try:
  438.       exec '_spyce_finish()' in { '_spyce_finish': self.destroy }
  439.       self._modstarted.reverse()
  440.       finishError = None
  441.       for as, mod in self._modstarted:
  442.         try: mod.finish(theError)
  443.         except: finishError = 1
  444.       self._request = None
  445.       self._response = None
  446.       if finishError: raise
  447.     finally:
  448.       self.spyceCleanup()
  449.   def spyceCleanup(self):
  450.     "Sweep the Spyce environment."
  451.     self._modstarted = []
  452.     self._modules = {}
  453.     if self._freshenv!=None:
  454.       for e in self._codeenv.keys():
  455.         if e not in self._freshenv:
  456.           del self._codeenv[e]
  457.   # API methods
  458.   def getFilename(self):
  459.     "Return filename of current Spyce"
  460.     return self._spycecode.getFilename()
  461.   def getCode(self):
  462.     "Return processed Spyce (i.e. Python) code"
  463.     return self._spycecode.getCode()
  464.   def getCodeRefs(self):
  465.     "Return python-to-Spyce code line references"
  466.     return self._spycecode.getCodeRefs()
  467.   def getModRefs(self):
  468.     "Return list of import references in Spyce code"
  469.     return self._spycecode.getModRefs()
  470.   def getServerObject(self):
  471.     "Return unique (per engine instance) server object"
  472.     return getServer().serverobject
  473.   def getServerGlobals(self):
  474.     "Return server configuration globals"
  475.     return getServer().globals
  476.   def getServerID(self):
  477.     "Return unique server identifier"
  478.     return self._request.getServerID()
  479.   def getPageError(self):
  480.     "Return default page error value"
  481.     return getServer().pageerror
  482.   def getRequest(self):
  483.     "Return internal request object"
  484.     return self._request
  485.   def getResponse(self):
  486.     "Return internal response object"
  487.     return self._response
  488.   def setResponse(self, o):
  489.     "Set internal response object"
  490.     self._response = o
  491.     for f in self._responseCallback.keys(): f()
  492.   def registerResponseCallback(self, f):
  493.     "Register a callback for when internal response changes"
  494.     self._responseCallback[f] = 1
  495.   def unregisterResponseCallback(self, f):
  496.     "Unregister a callback for when internal response changes"
  497.     try: del self._responseCallback[f]
  498.     except KeyError: pass
  499.   def getModules(self):
  500.     "Return references to currently loaded modules"
  501.     return self._modules
  502.   def getModule(self, name):
  503.     """Get module reference. The module is dynamically loaded and initialised
  504.     if it does not exist (ie. if it was not explicitly imported, but requested
  505.     by another module during processing)"""
  506.     return self._startModule(name)
  507.   def setModule(self, name, mod, notify=1):
  508.     "Add existing module (by reference) to Spyce namespace (used for includes)"
  509.     self._codeenv[name] = mod
  510.     self._modules[name] = mod
  511.     if notify:
  512.       for f in self._moduleCallback.keys(): f()
  513.   def getGlobals(self):
  514.     "Return the Spyce global namespace dictionary"
  515.     return self._codeenv
  516.   def registerModuleCallback(self, f):
  517.     "Register a callback for modules change"
  518.     self._moduleCallback[f] = 1
  519.   def unregisterModuleCallback(self, f):
  520.     "Unregister a callback for modules change"
  521.     try: del self._moduleCallback[f]
  522.     except KeyError: pass
  523.   def spyceFile(self, file):
  524.     "Return a spyceCode object of a file"
  525.     return getServer().spyce_cache[('file', file)]
  526.   def spyceString(self, code):
  527.     "Return a spyceCode object of a string"
  528.     return getServer().spyce_cache[('string', code)]
  529.   def spyceModule(self, name, file=None, rel_file=None):
  530.     "Return Spyce module class"
  531.     return getServer().loadModule(name, file, rel_file)
  532.   def spyceTaglib(self, name, file=None, rel_file=None):
  533.     "Return Spyce taglib class"
  534.     return getServer().loadModule(name, file, rel_file)
  535.   def setStdout(self, out):
  536.     "Set the stdout stream (thread-safe)"
  537.     return getServer().stdout.setObject(out)
  538.   def getStdout(self):
  539.     "Get the stdout stream (thread-safe)"
  540.     return getServer().stdout.getObject()
  541.  
  542. ##################################################
  543. # Spyce cache
  544. #
  545.  
  546. def spyceFileCacheValid(key, validity):
  547.   "Determine whether compiled Spyce is valid"
  548.   try: 
  549.     filename, sig = key
  550.   except:
  551.     filename, sig = key, ''
  552.   try:
  553.     if not os.path.exists(filename):
  554.       return 0
  555.     if not os.access(filename, os.R_OK):
  556.       return 0
  557.     return os.path.getmtime(filename) == validity
  558.   except KeyboardInterrupt: raise
  559.   except:
  560.     return 0
  561.  
  562. def spyceFileCacheGenerate(key):
  563.   "Generate new Spyce wrapper (recompiles)."
  564.   try: 
  565.     filename, sig = key
  566.   except:
  567.     filename, sig = key, ''
  568.   # ensure file exists and we have permissions
  569.   if not os.path.exists(filename):
  570.     raise spyceException.spyceNotFound(filename)
  571.   if not os.access(filename, os.R_OK):
  572.     raise spyceException.spyceForbidden(filename)
  573.   # generate
  574.   mtime = os.path.getmtime(filename)
  575.   f = None
  576.   try:
  577.     f = open(filename)
  578.     code = f.read()
  579.   finally:
  580.     if f: f.close()
  581.   s = spyceCode(code, filename=filename, sig=sig)
  582.   return mtime, s
  583.  
  584. def spyceStringCacheValid(code, validity):
  585.   return 1
  586.  
  587. def spyceStringCacheGenerate(key):
  588.   try: 
  589.     code, sig = key
  590.   except:
  591.     code, sig = key, ''
  592.   s = spyceCode(code, sig=sig)
  593.   return None, s
  594.  
  595. def spyceCacheValid((type, key), validity):
  596.   return { 
  597.     'string': spyceStringCacheValid,
  598.     'file': spyceFileCacheValid,
  599.   }[type](key, validity)
  600.  
  601. def spyceCacheGenerate((type, key)):
  602.   return {
  603.     'string': spyceStringCacheGenerate,
  604.     'file': spyceFileCacheGenerate,
  605.   }[type](key)
  606.  
  607.  
  608. ##################################################
  609. # Spyce common entry points
  610. #
  611.  
  612. def spyceFileHandler(request, response, filename, sig='', args=None, kwargs=None, config_file=None):
  613.   return _spyceCommonHandler(request, response, ('file', (filename, sig)), args, kwargs, config_file)
  614.  
  615. def spyceStringHandler(request, response, code, sig='', args=None, kwargs=None, config_file=None):
  616.   return _spyceCommonHandler(request, response, ('string', (code, sig)), args, kwargs, config_file)
  617.  
  618. def _spyceCommonHandler(request, response, spyceInfo, args=None, kwargs=None, config_file=None):
  619.   return getServer(config_file).commonHandler(request, response, spyceInfo, args, kwargs)
  620.  
  621. if __name__ == '__main__':
  622.   execfile('run_spyceCmd.py')
  623.  
  624.