def loadConfig(self, appVars): if appVars.get('beforeConfigHook'): appVars['beforeConfigHook'](appVars) self.config = utils.defaultattrdict(appVars) def initConstants(varlist, default): #add the given list of config properties as attributes #on this RequestProcessor return assignAttrs(self, appVars, varlist, default) initConstants( [ 'actions'], {}) initConstants( ['default_mime_type'], '') self.initLock(appVars) self.txnSvc = transactions.ProcessorTransactionService(self) initConstants( [ 'stores', 'storeDefaults'], {}) addNewResourceHook = self.actions.get('before-new') self.defaultStore = None if 'stores' in appVars: stores = utils.attrdict() for name, storeConfig in appVars['stores'].items(): stores[name] = self.loadDataStore(storeConfig, self.storeDefaults, addNewResourceHook) if storeConfig.get('default_store') or name == 'default': self.defaultStore = stores[name] if stores and not self.defaultStore: if len(stores) > 1: raise VesperError('default store not set') else: self.defaultStore = stores.values()[0] #XXX in order to allow cmdline and config file storage settings to be useful #merge appVars into the default store's config before loadDataStore self.stores = stores else: self.defaultStore = self.loadDataStore(appVars,self.storeDefaults,addNewResourceHook) self.stores = utils.attrdict(default=self.defaultStore) #app_name is a unique name for this request processor instance initConstants( ['app_name'], 'root') self.log = logging.getLogger("app." + self.app_name) self.defaultRequestTrigger = appVars.get('default_trigger','http-request') initConstants( ['global_request_vars', 'static_path', 'template_path'], []) self.work_dir = appVars.get('work_dir', 'vesper_work') self.mako_module_dir = appVars.get('mako_module_dir', os.path.join(self.work_dir,'mako_modules')) initConstants( ['template_options'], {}) self.global_request_vars.extend( self.defaultGlobalVars ) self.default_page_name = appVars.get('default_page_name', 'index') #cache settings: initConstants( ['secure_file_access', 'use_etags'], True) self.default_expires_in = appVars.get('default_expires_in', 0) initConstants( ['action_cache_size'], 0) self.validate_external_request = appVars.get('validate_external_request', lambda *args: True) self.get_principal_func = appVars.get('get_principal_func', lambda kw: '') self.argsForConfig = appVars.get('argsForConfig', []) if appVars.get('configHook'): appVars['configHook'](appVars)
def _importApp(baseapp): ''' Executes the given app config file. If `createApp()` was called during execution of the config file, the `_current_config` global will be set to the app configuration returned by `createApp()`. ''' baseglobals = utils.attrdict(Action=Action, createApp=createApp) #assuming the baseapp file calls createApp(), it will set _current_config if os.path.exists(baseapp): #set this global so we can resolve relative paths against the location #of the config file they appear in _current_configpath.append( os.path.dirname(os.path.abspath(baseapp)) ) execfile(baseapp, baseglobals) else: (path, isdir) = _get_module_path(baseapp) # print "_get_module_path for:" + str(baseapp) + " --> path:" + str(path) + " isdir:" + str(isdir) assert path #set this global so we can resolve relative paths against the location #of the config file they appear in _current_configpath.append( os.path.abspath(path) ) basemod = sys.modules.get(baseapp) if basemod: reload(basemod) else: __import__(baseapp, baseglobals) _current_configpath.pop()
def callActions(self, actions, kw, retVal, errorSequence=None, globalVars=None, newTransaction=False): ''' process another set of actions using the current context as input, but without modified the current context. Particularly useful for template processing. ''' globalVars = self.global_request_vars + (globalVars or []) #merge previous prevkw, overriding vars as necessary prevkw = kw.get('_prevkw', {}).copy() templatekw = utils.attrdict() for k, v in kw.items(): #initialize the templates variable map copying the #core request kws and copy the r est (the application #specific kws) to _prevkw this way the template #processing doesn't mix with the orginal request but #are made available in the 'previous' namespace (think #of them as template parameters) if k in globalVars: templatekw[k] = v elif k != '_metadatachanges': prevkw[k] = v templatekw['_prevkw'] = prevkw templatekw['_contents'] = Result(retVal) return self.doActions(actions, templatekw, errorSequence=errorSequence, newTransaction=newTransaction)
def testReplaceEmbeddedList(self): updates = utils.attrdict() @vesper.app.Action def recordUpdates(kw, retval): updates.update(kw._dbchanges[0]) store = vesper.app.createStore({ "id": "hello", "embedded": [{'foo' : 'a'}, {'foo' : 'b'}] }, storage_template_options=dict(generateBnode='counter'), actions = { 'after-commit' : [recordUpdates]} ) store.replace({"id":"hello", "embedded": [{'id' : '_:j:e:object:hello:3', 'foo' : 'c'}, {'id' : '_:j:e:object:hello:4', 'foo' : 'd'}] }) removed = set(updates._removedStatements) - set(updates._addedStatements) expectedRemoved = set([('_:j:e:object:hello:2', 'foo', 'b', 'L', ''), ('_:j:e:object:hello:1', 'foo', 'a', 'L', '')]) self.assertEquals(removed, expectedRemoved) added = set(updates._addedStatements) - set(updates._removedStatements) expectedAdded = set([('_:j:e:object:hello:2', 'foo', 'd', 'L', ''), ('_:j:e:object:hello:1', 'foo', 'c', 'L', '')]) self.assertEquals(added, expectedAdded)
def testUpdateEmbeddedList(self): updates = utils.attrdict() @vesper.app.Action def recordUpdates(kw, retval): updates.update(kw._dbchanges[0]) store = vesper.app.createStore({ "id": "hello", "embedded": [{'foo' : 'a', 'prop2':'a'}, {'foo' : 'b'}] }, storage_template_options=dict(generateBnode='counter'), actions = { 'after-commit' : [recordUpdates]} ) store.update({"id":"hello", "embedded": [{'id' : '_:j:e:object:hello:3', 'foo' : 'c', 'prop2':'a'}, {'id' : '_:j:e:object:hello:4', 'foo' : 'd'}] }) removed = [('_:j:e:object:hello:1', 'foo', 'a', 'L', ''), ('_:j:e:object:hello:2', 'foo', 'b', 'L', '')] #XXX shouldn't include the redundant proplist statements added = [('_:j:e:object:hello:1', 'foo', 'c', 'L', ''), ('_:j:e:object:hello:2', 'foo', 'd', 'L', ''), ('_:j:proplist:hello;embedded', u'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', u'http://www.w3.org/1999/02/22-rdf-syntax-ns#Seq', 'R', ''), ('_:j:proplist:hello;embedded', u'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'pjson:schema#propseqtype', 'R', ''), ('_:j:proplist:hello;embedded', 'pjson:schema#propseqprop', 'embedded', 'R', ''), ('_:j:proplist:hello;embedded', 'pjson:schema#propseqsubject', 'hello', 'R', '')] self.assertEquals(updates._removedStatements, removed) self.assertEquals(updates._addedStatements, added)
def _runActions(self, trigger, morekw=None): actions = self.server.actions.get(trigger) if actions: state = self.state kw = state.kw.copy() if morekw is None: morekw = {'_dbchanges' : [ utils.attrdict({ '_addedStatements' : dbstate.additions, '_removedStatements' : dbstate.removals, '_added' : pjson.tojson(dbstate.additions, **db.model_options.get('serializeOptions',{}).get('pjson',{})), '_removed' : pjson.tojson(dbstate.removals, **db.model_options.get('serializeOptions',{}).get('pjson',{})), '_newResources' : dbstate.newResources, '_db' : db }) for db, dbstate in self.state.dbstates.items() ] } kw.update(morekw) kw['_txninfo'] = self.state.info errorSequence= self.server.actions.get(trigger+'-error') self.server.callActions(actions, kw, state.retVal, globalVars=morekw.keys(), errorSequence=errorSequence)
def requestFromEnviron(self, environ): import Cookie, wsgiref.util _name = environ['PATH_INFO'].lstrip('/') if not _name or _name.endswith('/'): _name += self.default_page_name _responseCookies = Cookie.SimpleCookie() _responseHeaders = utils.attrdict(_status="200 OK") #include response code pseudo-header kw = utils.attrdict(_environ=utils.attrdict(environ), _uri = wsgiref.util.request_uri(environ), _baseUri = wsgiref.util.application_uri(environ), _responseCookies = _responseCookies, _requestCookies = Cookie.SimpleCookie(environ.get('HTTP_COOKIE', '')), _responseHeaders= _responseHeaders, _name=_name ) paramsdict = get_http_params(environ) kw.update(paramsdict) return kw
def handleCommandLine(self, argv): ''' the command line is translated into the `_params` request variable as follows: * arguments beginning with a '-' are treated as a variable name with its value being the next argument unless that argument also starts with a '-' * the entire command line is assigned to the variable 'cmdline' ''' kw = utils.attrdict() kw._params = utils.defaultattrdict(argsToKw(argv)) kw['cmdline'] = '"' + '" "'.join(argv) + '"' self.runActions('run-cmds', kw)
def runActions(self, triggerName, kw = None, initVal=None, newTransaction=True): ''' Retrieve the action sequences associated with the triggerName. Each Action has a list of RxPath expressions that are evaluated after mapping runActions keyword parameters to RxPath variables. If an expression returns a non-empty nodeset the Action is invoked and the value it returns is passed to the next invoked Action until the end of the sequence, upon which the final return value is return by this function. ''' kw = utils.attrdict(kw or {}) sequence = self.actions.get(triggerName) if sequence: errorSequence = self.actions.get(triggerName+'-error') return self.doActions(sequence, kw, retVal=initVal, errorSequence=errorSequence, newTransaction=newTransaction)
def getErrorKWs(): type, value, traceback = exc_info if (isinstance(value, utils.NestedException) and value.useNested): message, module, name, errorCode=extractErrorInfo( value.nested_exc_info[0], value.nested_exc_info[1]) else: message, module, name, errorCode=extractErrorInfo( type, value) #these should always be the wrapper exception: (fileName, lineNumber, functionName, text) = traceback_module.extract_tb( traceback, 1)[0] details = ''.join( traceback_module.format_exception( type, value, traceback) ) return utils.attrdict(locals())
def _testUpdateEmbedded(self, op): #XXX test nested lists, nested embedded objects updates = utils.attrdict() @vesper.app.Action def recordUpdates(kw, retval): updates.update(kw._dbchanges[0]) store = vesper.app.createStore({ "id": "hello", "embedded": { 'foo' : 'not first-class', 'prop2' : 'a' } }, storage_template_options=dict(generateBnode='counter'), actions = { 'after-commit' : [recordUpdates]} ) self.assertEquals(store.model.getStatements(), [('_:j:e:object:hello:1', 'foo', 'not first-class', 'L', ''), ('_:j:e:object:hello:1', 'prop2', 'a', 'L', ''), ('hello', 'embedded', '_:j:e:object:hello:1', 'R', '')]) getattr(store, op)({"id":"hello", "embedded": { 'id' : '_:j:e:object:hello:2', #here just to make sure a different bnode name is used 'foo' : 'modified', 'prop2' : 'a' } }) #XXX this statement shouldn't be duplicated self.assertEquals(updates._removedStatements, [ ('_:j:e:object:hello:1', 'foo', 'not first-class', 'L', ''), ('_:j:e:object:hello:1', 'foo', 'not first-class', 'L', '')]) self.assertEquals(updates._addedStatements, [('_:j:e:object:hello:1', 'foo', 'modified', 'L', '')]) self.assertEquals(store.model.getStatements(), [('_:j:e:object:hello:1', 'foo', 'modified', 'L', ''), ('_:j:e:object:hello:1', 'prop2', 'a', 'L', ''), ('hello', 'embedded', '_:j:e:object:hello:1', 'R', '')] )
def getResults(query, model, bindvars=None, explain=None, debug=False, forUpdate=False, captureErrors=False, contextShapes=None, useSerializer=True, printast=False, queryCache=None): ''' Returns a dict with the following keys: - `results`: the result of the query (either a list or None if the query failed) - `errors`: An error string if the query failed or an empty list if it succeeded. :Parameters: query the query model the store upon which to execute the query bindvars a dictionary used to resolve any `bindvars` in the query explain if True, the result will include a key named `explain` whose value is a string "explaining" the query plan to execute it. debug if True, the result will include a very verbose trace of the query execution. forUpdate include in the query results enough information so that the response objects can be modified and those changes saved back into the store. captureErrors if True, exceptions raise during query execution will be caught and appended to the `errors` key. By default, such exceptions will be propagated when they occur. contextShapes A dictionary that specifies alternative constructors used when creating `dicts` and `lists` in the query results. useSerializer If value is a boolean, indicates whether pjson serialization is used or not (default: True). If value is a dict it is passed as keyword arguments to the `pjson.Serializer` constructor. ''' #XXX? add option to include `resources` in the result, # a list describing the resources (used for track changes) start = time.clock() response = utils.attrdict() errors = [] (ast, parseErrors) = buildAST(query) errors.extend(parseErrors) response['results'] = [] if explain: explain = StringIO.StringIO() if debug and not hasattr(debug, 'write'): debug = StringIO.StringIO() if ast != None: try: results = list(evalAST(ast, model, bindvars, explain, debug, forUpdate, contextShapes, useSerializer, queryCache)) #XXX: if forUpdate add a pjson header including namemap #this we have a enough info to reconstruct refs and datatypes without guessing #if forUpdate: # #need a context.datamap # pjson.addHeader(context.datamap, response) response['results'] = results except QueryException, qe: if captureErrors: errors.append('error: %s' % qe.message) else: raise except Exception, ex: if captureErrors: import traceback errors.append("unexpected exception: %s" % traceback.format_exc()) else: raise
def doActions(self, sequence, kw=None, retVal=None, errorSequence=None, newTransaction=False): if kw is None: kw = utils.attrdict() kw['__requestor__'] = self.requestDispatcher kw['__server__'] = self try: if newTransaction: retVal = self._doActionsTxn(sequence, kw, retVal) else: retVal = self._doActionsBare(sequence, kw, retVal) except (KeyboardInterrupt, SystemExit): raise except: #print newTransaction, self.txnSvc.state.timestamp exc_info = sys.exc_info() if isinstance(exc_info[1], ActionWrapperException): retVal = exc_info[1].state exc_info = exc_info[1].nested_exc_info if self.inErrorHandler or kw.get('_noErrorHandling'): #avoid endless loops raise exc_info[1] or exc_info[0], None, exc_info[2] else: self.inErrorHandler += 1 try: if isinstance(exc_info[1], DoNotHandleException): raise exc_info[1] or exc_info[0], None, exc_info[2] if errorSequence and sequence is not errorSequence: import traceback as traceback_module def extractErrorInfo(type, value): #value may be either the nested exception #or the wrapper exception message = str(value) module = '.'.join( str(type).split('.')[:-1] ) name = str(type).split('.')[-1].strip("'>") errorCode = getattr(value, 'errorCode', '') return message, module, name, errorCode def getErrorKWs(): type, value, traceback = exc_info if (isinstance(value, utils.NestedException) and value.useNested): message, module, name, errorCode=extractErrorInfo( value.nested_exc_info[0], value.nested_exc_info[1]) else: message, module, name, errorCode=extractErrorInfo( type, value) #these should always be the wrapper exception: (fileName, lineNumber, functionName, text) = traceback_module.extract_tb( traceback, 1)[0] details = ''.join( traceback_module.format_exception( type, value, traceback) ) return utils.attrdict(locals()) kw['_errorInfo'] = getErrorKWs() self.log.warning("invoking error handler on exception:\n"+ kw['_errorInfo']['details']) try: #if we're creating a new transaction, #it has been aborted by now, so start a new one #however if the error was thrown during commit we're in the midst #of a bad transaction and its not safe to create a new one newTransaction = newTransaction and not self.txnSvc.isActive() return self.callActions(errorSequence, kw, retVal, newTransaction=newTransaction) finally: del kw['_errorInfo'] else: #traceback.print_exception(*exc_info) raise exc_info[1] or exc_info[0], None, exc_info[2] finally: self.inErrorHandler -= 1 return retVal