def result(self, process_id): """ Get the result of the given process. If the process has not completed, returns None. Once the result has been successfully fetched, it cannot be fetched again. """ completed = False try: try: process = self._get_process(process_id) completed = process['completed'] except ThreadDiedBeforeCompletionError: completed = True raise finally: if completed is True: del getProcessRegistry(self.context)[process_id] if completed: response = self.request.response response_details = process.get('result') status, headers, cookies, body = response_details response.setStatus(status) # Overwriting headers/cookies here is a bit crude, I have # tried to use declared interface methods wherever possible # but there are some omissions that have to be worked # around. # Currently, ZPublisher.HTTPResponse doesn't implement # IHTTPResponse, even though HTTPRequest implements # IHTTPRequest. current_headers = getattr(response, 'headers', getattr(response, '_headers', {})) result_headers = dict([(k.lower(), v) for k, v in headers.items()]) for h in set(current_headers.keys() + result_headers.keys()): if h not in headers and h in current_headers: # no interface-friendly way to unset headers anyway. del current_headers[h] else: response.setHeader(h, result_headers[h]) # no interface-friendly way to enumerate response cookies # or unset cookies. attr = (hasattr(response, 'cookies') and 'cookies') or \ (hasattr(response, '_cookies') and '_cookies') setattr(response, attr, cookies) if IHTTPResponse.providedBy(response): response.setResult(body) else: response.setBody(body) return response else: return None
def _get_process(self, process_id): # Internal function, used to raise an error if a process with # the given ID doesn't exist. process = getProcessRegistry(self.context).get(process_id) if not process: raise NoSuchProcessError return process
def _run(self): # Internal function to kick off the asynchronous process. process_id = uid_generator() # Copy the request: hasattrs for differentiating between # zope.publisher and ZPublisher HTTPRequests. if hasattr(self.context.REQUEST, 'environ'): request_environ = deepcopy(self.context.REQUEST.environ) elif hasattr(self.context.REQUEST, '_environ'): request_environ = deepcopy(self.context.REQUEST._environ) else: request_environ = {} if hasattr(self.context.REQUEST, 'stdin'): self.context.REQUEST.stdin.seek(0) request_body = self.context.REQUEST.stdin.read() elif hasattr(self.context.REQUEST, 'bodyStream'): self.context.REQUEST.bodyStream.stream.seek(0) request_body = self.context.REQUEST.bodyStream.stream.read() else: request_body = '' # Pass as little from the current thread to the new thread as # possible. Too easy to get lost in ZODB & transaction hell # otherwise. Nothing with 'context' or a connection should go # through. Function is outside of this class to avoid scope # mix-up. setup = { 'request_environ': request_environ, 'request_body': request_body, 'pid': process_id } name = '<%s>' % (process_id) # We start a new thread outside of the normal Zope limits, # naughty but necessary for now. t = threading.Thread(target=process_wrapper, name=name, kwargs=setup) getProcessRegistry(self.context)[process_id] = PersistentDict({ 'progress': False, 'result': None, 'completed': False }) # Ensure we have committed in this thread before the spawned # thread tries to retrieve the process record. transaction.commit() t.start() return process_id
def _run(self): # Internal function to kick off the asynchronous process. process_id = uid_generator() # Copy the request: hasattrs for differentiating between # zope.publisher and ZPublisher HTTPRequests. if hasattr(self.context.REQUEST, 'environ'): request_environ = deepcopy(self.context.REQUEST.environ) elif hasattr(self.context.REQUEST, '_environ'): request_environ = deepcopy(self.context.REQUEST._environ) else: request_environ = {} if hasattr(self.context.REQUEST, 'stdin'): self.context.REQUEST.stdin.seek(0) request_body = self.context.REQUEST.stdin.read() elif hasattr(self.context.REQUEST, 'bodyStream'): self.context.REQUEST.bodyStream.stream.seek(0) request_body = self.context.REQUEST.bodyStream.stream.read() else: request_body = '' # Pass as little from the current thread to the new thread as # possible. Too easy to get lost in ZODB & transaction hell # otherwise. Nothing with 'context' or a connection should go # through. Function is outside of this class to avoid scope # mix-up. setup = {'request_environ': request_environ, 'request_body': request_body, 'pid': process_id} name = '<%s>' % (process_id) # We start a new thread outside of the normal Zope limits, # naughty but necessary for now. t = threading.Thread(target=process_wrapper, name=name, kwargs=setup) getProcessRegistry(self.context)[process_id] = PersistentDict({'progress': False, 'result': None, 'completed': False}) # Ensure we have committed in this thread before the spawned # thread tries to retrieve the process record. transaction.commit() t.start() return process_id
def completed(self, process_id, output_json=False): """ Return some measure of completeness. If your _run method informs of some percentage completeness via the _set_progress method, returns a number between 0 and 100, otherwise returns False or True. """ if output_json: self.context.REQUEST.RESPONSE.setHeader('Content-Type', 'application/json') try: process = self._get_process(process_id) except NoSuchProcessError: if output_json: return json.dumps({'completed': 'ERROR'}) else: raise progress = process.get('progress', None) completed = process.get('completed', False) result = process.get('result') if isinstance(result, Exception) and \ completed is not True: exception = result if not output_json: del getProcessRegistry(self.context)[process_id] raise ThreadDiedBeforeCompletionError(exception) else: return json.dumps({'progress': 'ERROR'}) if not output_json: return progress else: if hasattr(self.context, 'portal_languages'): lang = self.context.portal_languages.getPreferredLanguage() else: lang = 'en' progress_message = _(u'percentage_completion', u'${percentage}% completed...', mapping={'percentage': progress or 0}) return json.dumps({ 'progress': progress, 'progress_message': translate(progress_message, target_language=lang) })
def completed(self, process_id, output_json=False): """ Return some measure of completeness. If your _run method informs of some percentage completeness via the _set_progress method, returns a number between 0 and 100, otherwise returns False or True. """ if output_json: self.context.REQUEST.RESPONSE.setHeader('Content-Type', 'application/json') try: process = self._get_process(process_id) except NoSuchProcessError: if output_json: return json.dumps({'completed': 'ERROR'}) else: raise progress = process.get('progress', None) completed = process.get('completed', False) result = process.get('result') if isinstance(result, Exception) and \ completed is not True: exception = result if not output_json: del getProcessRegistry(self.context)[process_id] raise ThreadDiedBeforeCompletionError(exception) else: return json.dumps({'progress': 'ERROR'}) if not output_json: return progress else: if hasattr(self.context, 'portal_languages'): lang = self.context.portal_languages.getPreferredLanguage() else: lang = 'en' progress_message = _(u'percentage_completion', u'${percentage}% completed...', mapping={'percentage': progress or 0}) return json.dumps({'progress': progress, 'progress_message': translate(progress_message, target_language=lang)})
def process_wrapper(pid, request_body, request_environ): # Sets up everything we need to run a view method in a new Zope-ish # context, then runs it and stores the result for later retrieval. _process = None def my_mapply(object, positional=(), keyword={}, debug=None, maybe=None, missing_name=None, handle_class=None, context=None, bind=0): if not isinstance(keyword, Mapping): keyword = {} keyword['process_id'] = pid args = (getattr(object, '__run__', object),) kwargs = dict(positional=positional, keyword=keyword, debug=debug, maybe=maybe, context=context, bind=bind ) if missing_name is not None: kwargs['missing_name'] = missing_name if handle_class is not None: kwargs['handle_class'] = handle_class return mapply(*args, **kwargs) response = HTTPResponse(stdout=StringIO(), stderr=StringIO()) request = HTTPRequest(StringIO(request_body), request_environ, response) request.set('process_id', pid) import Zope2 app = Zope2.bobo_application.__bobo_traverse__(request) reg = getProcessRegistry(app) _process = reg.get(pid) # Run try: try: response = publish(request, 'Zope2', [None], mapply=my_mapply) # We can't just pass the response back, as the data streams will not # be set up right. attr = (hasattr(response, 'cookies') and 'cookies') or \ (hasattr(response, '_cookies') and '_cookies') cookies = deepcopy(getattr(response, attr)) if IHTTPResponse.providedBy(response): _process['result'] = (response.getStatus(), dict(response.getHeaders()), cookies, response.consumeBody()) else: # Currently, ZPublisher.HTTPResponse doesn't implement # IHTTPResponse, even though HTTPRequest implements # IHTTPRequest. _process['result'] = (response.getStatus(), dict(response.headers), cookies, response.body) except Exception, e: # Set result to the exception raised _process['result'] = e raise else:
def process_wrapper(pid, request_body, request_environ): # Sets up everything we need to run a view method in a new Zope-ish # context, then runs it and stores the result for later retrieval. _process = None def my_mapply(object, positional=(), keyword={}, debug=None, maybe=None, missing_name=None, handle_class=None, context=None, bind=0): if not isinstance(keyword, Mapping): keyword = {} keyword['process_id'] = pid args = (getattr(object, '__run__', object), ) kwargs = dict(positional=positional, keyword=keyword, debug=debug, maybe=maybe, context=context, bind=bind) if missing_name is not None: kwargs['missing_name'] = missing_name if handle_class is not None: kwargs['handle_class'] = handle_class return mapply(*args, **kwargs) response = HTTPResponse(stdout=StringIO(), stderr=StringIO()) request = HTTPRequest(StringIO(request_body), request_environ, response) request.set('process_id', pid) import Zope2 app = Zope2.bobo_application.__bobo_traverse__(request) reg = getProcessRegistry(app) _process = reg.get(pid) # Run try: try: response = publish(request, 'Zope2', [None], mapply=my_mapply) # We can't just pass the response back, as the data streams will not # be set up right. attr = (hasattr(response, 'cookies') and 'cookies') or \ (hasattr(response, '_cookies') and '_cookies') cookies = deepcopy(getattr(response, attr)) if IHTTPResponse.providedBy(response): _process['result'] = (response.getStatus(), dict(response.getHeaders()), cookies, response.consumeBody()) else: # Currently, ZPublisher.HTTPResponse doesn't implement # IHTTPResponse, even though HTTPRequest implements # IHTTPRequest. _process['result'] = (response.getStatus(), dict(response.headers), cookies, response.body) except Exception, e: # Set result to the exception raised _process['result'] = e raise else: