def _post_request(self): """HTTP GET request parser """ # check if input file size was not exceeded maxsize = configuration.get_config_value('server', 'maxrequestsize') maxsize = configuration.get_size_mb(maxsize) * 1024 * 1024 if self.http_request.content_length > maxsize: raise FileSizeExceeded( 'File size for input exceeded.' ' Maximum request size allowed: {} megabytes'.format( maxsize / 1024 / 1024)) try: doc = lxml.etree.fromstring(self.http_request.get_data()) except Exception as e: if PY2: raise NoApplicableCode(e.message) else: raise NoApplicableCode(e.msg) operation = doc.tag version = get_version_from_ns(doc.nsmap[doc.prefix]) self.set_version(version) request_parser = self._post_request_parser(operation) request_parser(doc)
def describe(self, identifiers): if not identifiers: raise MissingParameterValue('', 'identifier') identifier_elements = [] # 'all' keyword means all processes if 'all' in (ident.lower() for ident in identifiers): for process in self.processes: try: identifier_elements.append(self.processes[process].describe_xml()) except Exception as e: raise NoApplicableCode(e) else: for identifier in identifiers: try: process = self.processes[identifier] except KeyError: raise InvalidParameterValue("Unknown process %r" % identifier, "identifier") else: try: identifier_elements.append(process.describe_xml()) except Exception as e: raise NoApplicableCode(e) doc = WPS.ProcessDescriptions( *identifier_elements ) doc.attrib['{http://www.w3.org/2001/XMLSchema-instance}schemaLocation'] = 'http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsDescribeProcess_response.xsd' doc.attrib['service'] = 'WPS' doc.attrib['version'] = '1.0.0' doc.attrib['{http://www.w3.org/XML/1998/namespace}lang'] = 'en-CA' return xml_response(doc)
def sum_one(request, response): input = request.inputs['input'] # What do we need to assert a Complex input? #assert type(input) is text_type sys.path.append("/usr/lib/grass64/etc/python/") import grass.script as grass # Import the raster and set the region if grass.run_command("r.in.gdal", flags="o", out="input", input=input) != 0: raise NoApplicableCode( "Could not import cost map. Please check the WCS service.") if grass.run_command("g.region", flags="ap", rast="input") != 0: raise NoApplicableCode("Could not set GRASS region.") # Add 1 if grass.mapcalc("$output = $input + $value", output="output", input="input", value=1.0) != 0: raise NoApplicableCode("Could not set GRASS region.") # Export the result out = "./output.tif" if grass.run_command( "r.out.gdal", input="output", type="Float32", output=out) != 0: raise NoApplicableCode("Could not export result from GRASS.") response.outputs['output'] = out return response
def href_handler(complexinput, datain): """<wps:Reference /> handler""" tmp_dir = config.get_config_value('server', 'workdir') # save the reference input in workdir tmp_file = tempfile.mkstemp(dir=complexinput.workdir)[1] try: (reference_file, reference_file_data) = _openurl(href) data_size = reference_file.headers.get('Content-Length', 0) except Exception as e: raise NoApplicableCode('File reference error: %s' % e) # if the response did not return a 'Content-Length' header then calculate the size if data_size == 0: data_size = _get_datasize(reference_file_data) # check if input file size was not exceeded complexinput.calculate_max_input_size() byte_size = complexinput.max_size * 1024 * 1024 if int(data_size) > int(byte_size): raise FileSizeExceeded('File size for input exceeded.' ' Maximum allowed: %i megabytes' %\ complexinput.max_size, complexinput.get('identifier')) try: with open(tmp_file, 'w') as f: f.write(reference_file_data) f.close() except Exception as e: raise NoApplicableCode(e) complexinput.file = tmp_file complexinput.url = href complexinput.as_reference = True
def __call__(self, request): if self.wps_request.raw: if self.status == WPS_STATUS.FAILED: return NoApplicableCode(self.message) else: wps_output_identifier = next(iter( self.wps_request.outputs)) # get the first key only wps_output_value = self.outputs[wps_output_identifier] if wps_output_value.source_type is None: return NoApplicableCode( "Expected output was not generated") return Response( wps_output_value.data, mimetype=self.wps_request.outputs[wps_output_identifier] ['mimetype']) else: doc = None try: doc = self._construct_doc() if self.store_status_file: self.process.clean() # TODO: If an exception occur here we must generate a valid status file except HTTPException as httpexp: return httpexp except Exception as exp: return NoApplicableCode(exp) return Response(doc, mimetype='text/xml')
def file(self): if self._file is not None: return self._file self._file = self._build_file_name(href=self.url) max_byte_size = self.max_input_size() # Create request try: reference_file = self._openurl(self.url, self.post_data) data_size = reference_file.headers.get('Content-Length', 0) except Exception as e: raise NoApplicableCode('File reference error: {}'.format(e)) error_message = 'File size for input "{}" exceeded. Maximum allowed: {} megabytes'.format( self.inpt.get('identifier', '?'), max_byte_size) if int(data_size) > int(max_byte_size): raise FileSizeExceeded(error_message) try: with open(self._file, 'wb') as f: data_size = 0 for chunk in reference_file.iter_content(chunk_size=1024): data_size += len(chunk) if int(data_size) > int(max_byte_size): raise FileSizeExceeded(error_message) f.write(chunk) except Exception as e: raise NoApplicableCode(e) return self._file
def call(self, http_request): try: # This try block handle Exception generated before the request is accepted. Once the request is accepted # a valid wps_reponse must exist. To report error use the wps_response using # wps_response._update_status(WPS_STATUS.FAILED, ...). # # We need this behaviour to handle the status file correctly, once the request is accepted, a # status file may be created and failure must be reported in this file instead of a raw ows:ExceptionReport # # Exeception from CapabilityResponse and DescribeResponse are always catched by this try ... except close # because they never have status. request_uuid = uuid.uuid1() environ_cfg = http_request.environ.get('PYWPS_CFG') if 'PYWPS_CFG' not in os.environ and environ_cfg: LOGGER.debug('Setting PYWPS_CFG to %s', environ_cfg) os.environ['PYWPS_CFG'] = environ_cfg wps_request = WPSRequest(http_request) LOGGER.info('Request: %s', wps_request.operation) if wps_request.operation in [ 'getcapabilities', 'describeprocess', 'execute' ]: log_request(request_uuid, wps_request) try: response = None if wps_request.operation == 'getcapabilities': response = self.get_capabilities( wps_request, request_uuid) response._update_status(WPS_STATUS.SUCCEEDED, u'', 100) elif wps_request.operation == 'describeprocess': response = self.describe(wps_request, request_uuid, wps_request.identifiers) response._update_status(WPS_STATUS.SUCCEEDED, u'', 100) elif wps_request.operation == 'execute': response = self.execute(wps_request.identifier, wps_request, request_uuid) return response except Exception as e: # This ensure that logged request get terminated in case of exception while the request is not # accepted store_status(request_uuid, WPS_STATUS.FAILED, u'Request rejected due to exception', 100) raise e else: raise RuntimeError("Unknown operation %r" % wps_request.operation) except NoApplicableCode as e: return e except HTTPException as e: return NoApplicableCode(e.description, code=e.code) except Exception as e: msg = "No applicable error code, please check error log." return NoApplicableCode(msg, code=500)
def __call__(self, http_request): request_uuid = uuid.uuid1() environ_cfg = http_request.environ.get('PYWPS_CFG') if 'PYWPS_CFG' not in os.environ and environ_cfg: LOGGER.debug('Setting PYWPS_CFG to %s', environ_cfg) os.environ['PYWPS_CFG'] = environ_cfg try: wps_request = WPSRequest(http_request) LOGGER.info('Request: %s', wps_request.operation) if wps_request.operation in [ 'getcapabilities', 'describeprocess', 'execute' ]: log_request(request_uuid, wps_request) response = None if wps_request.operation == 'getcapabilities': response = self.get_capabilities(wps_request, request_uuid) elif wps_request.operation == 'describeprocess': response = self.describe(wps_request, request_uuid, wps_request.identifiers) elif wps_request.operation == 'execute': response = self.execute(wps_request.identifier, wps_request, request_uuid) update_response(request_uuid, response, close=True) return response else: update_response(request_uuid, response, close=True) raise RuntimeError("Unknown operation %r" % wps_request.operation) except HTTPException as e: # transform HTTPException to OWS NoApplicableCode exception if not isinstance(e, NoApplicableCode): e = NoApplicableCode(e.description, code=e.code) class FakeResponse: message = e.locator status = e.code status_percentage = 100 try: update_response(request_uuid, FakeResponse, close=True) except NoApplicableCode as e: return e return e except Exception as e: e = NoApplicableCode( "No applicable error code, please check error log", code=500) return e
def __call__(self, request): if self.wps_request.raw: if self.status == WPS_STATUS.FAILED: return NoApplicableCode(self.message) else: wps_output_identifier = next(iter(self.wps_request.outputs)) # get the first key only wps_output_value = self.outputs[wps_output_identifier] if wps_output_value.source_type is None: return NoApplicableCode("Expected output was not generated") return Response(wps_output_value.data, mimetype=self.wps_request.outputs[wps_output_identifier].get('mimetype', None)) else: if not self.doc: return NoApplicableCode("Output was not generated") return Response(self.doc, mimetype='text/xml')
def get_session(): """Get Connection for database """ LOGGER.debug('Initializing database connection') global _SESSION_MAKER global _LAST_SESSION if _LAST_SESSION: _LAST_SESSION.close() if _SESSION_MAKER: _SESSION_MAKER.close_all() _LAST_SESSION = _SESSION_MAKER() return _LAST_SESSION database = configuration.get_config_value('logging', 'database') echo = True level = configuration.get_config_value('logging', 'level') if level in ['INFO']: echo = False try: engine = sqlalchemy.create_engine(database, echo=echo) except sqlalchemy.exc.SQLAlchemyError as e: raise NoApplicableCode("Could not connect to database: {}".format( e.message)) Session = sessionmaker(bind=engine) ProcessInstance.metadata.create_all(engine) RequestInstance.metadata.create_all(engine) _SESSION_MAKER = Session _LAST_SESSION = _SESSION_MAKER() return _LAST_SESSION
def _update_status_doc(self): try: # rebuild the doc self.doc = self._construct_doc() except Exception as e: raise NoApplicableCode( 'Building Response Document failed with : {}'.format(e))
def href_handler(complexinput, datain): """<wps:Reference /> handler""" # save the reference input in workdir tmp_file = _build_input_file_name( href=datain.get('href'), workdir=complexinput.workdir, extension=_extension(complexinput)) try: reference_file = _openurl(datain) data_size = reference_file.headers.get('Content-Length', 0) except Exception as e: raise NoApplicableCode('File reference error: %s' % e) # if the response did not return a 'Content-Length' header then # calculate the size if data_size == 0: LOGGER.debug('no Content-Length, calculating size') # check if input file size was not exceeded complexinput.calculate_max_input_size() max_byte_size = complexinput.max_size * 1024 * 1024 if int(data_size) > int(max_byte_size): raise FileSizeExceeded( 'File size for input exceeded.' ' Maximum allowed: %i megabytes' % complexinput.max_size, complexinput.identifier) try: with open(tmp_file, 'wb') as f: data_size = 0 for chunk in reference_file.iter_content(chunk_size=1024): data_size += len(chunk) if int(data_size) > int(max_byte_size): raise FileSizeExceeded( 'File size for input exceeded.' ' Maximum allowed: %i megabytes' % complexinput.max_size, complexinput.identifier) f.write(chunk) except Exception as e: raise NoApplicableCode(e) complexinput.file = tmp_file complexinput.url = datain.get('href') complexinput.as_reference = True
def __call__(self, request): # This function must return a valid response. try: doc = self.get_response_doc() return xml_response(doc) except NoApplicableCode as e: return e except Exception as e: return NoApplicableCode(str(e))
def write_response_doc(self, doc): # TODO: check if file/directory is still present, maybe deleted in mean time try: with open(self.process.status_location, 'w') as f: f.write(etree.tostring(doc, pretty_print=True, encoding='utf-8').decode('utf-8')) f.flush() os.fsync(f.fileno()) except IOError as e: raise NoApplicableCode('Writing Response Document failed with : %s' % e)
def _update_status_file(self): # TODO: check if file/directory is still present, maybe deleted in mean time try: # update the status xml file self.process.status_store.write(self.doc, self.process.status_filename, data_format=FORMATS.XML) except Exception as e: raise NoApplicableCode( 'Writing Response Document failed with : {}'.format(e))
def __call__(self, request): doc = None try: doc = self._construct_doc() except HTTPException as httpexp: raise httpexp except Exception as exp: raise NoApplicableCode(exp) return xml_response(doc)
def _update_status_file(self): # TODO: check if file/directory is still present, maybe deleted in mean time try: # update the status xml file with open(self.process.status_location, 'w') as f: f.write(self.doc) f.flush() os.fsync(f.fileno()) except Exception as e: raise NoApplicableCode( 'Writing Response Document failed with : {}'.format(e))
def get_connection(): """Get Connection for database """ LOGGER.debug('Initializing database connection') global _CONNECTION if _CONNECTION: return _CONNECTION database = configuration.get_config_value('server', 'logdatabase') if not database: database = ':memory:' connection = sqlite3.connect(database) if check_db_table(connection): if check_db_columns(connection): _CONNECTION = connection else: raise NoApplicableCode(""" Columns in the table 'pywps_requests' or 'pywps_stored_requests' in database '%s' are in conflict """ % database) else: _CONNECTION = sqlite3.connect(database, check_same_thread=False) cursor = _CONNECTION.cursor() createsql = """ CREATE TABLE pywps_requests( uuid VARCHAR(255) not null primary key, pid INTEGER not null, operation varchar(30) not null, version varchar(5) not null, time_start text not null, time_end text, identifier text, message text, percent_done float, status varchar(30) ) """ cursor.execute(createsql) createsql = """ CREATE TABLE pywps_stored_requests( uuid VARCHAR(255) not null primary key, request BLOB not null ) """ cursor.execute(createsql) _CONNECTION.commit() return _CONNECTION
def _run_process(self, wps_request, wps_response): try: self._set_grass() wps_response.update_status('PyWPS Process started', 0) wps_response = self.handler(wps_request, wps_response) # if (not wps_response.status_percentage) or (wps_response.status_percentage != 100): LOGGER.debug('Updating process status to 100% if everything went correctly') wps_response.update_status('PyWPS Process {} finished'.format(self.title), 100, STATUS.DONE_STATUS, clean=self.async) except Exception as e: traceback.print_exc() LOGGER.debug('Retrieving file and line number where exception occurred') exc_type, exc_obj, exc_tb = sys.exc_info() found = False while not found: # search for the _handler method m_name = exc_tb.tb_frame.f_code.co_name if m_name == '_handler': found = True else: if exc_tb.tb_next is not None: exc_tb = exc_tb.tb_next else: # if not found then take the first exc_tb = sys.exc_info()[2] break fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] method_name = exc_tb.tb_frame.f_code.co_name # update the process status to display process failed msg = 'Process error: %s.%s Line %i %s' % (fname, method_name, exc_tb.tb_lineno, e) LOGGER.error(msg) if not wps_response: raise NoApplicableCode('Response is empty. Make sure the _handler method is returning a valid object.') else: wps_response.update_status(msg, -1) # tr stored_request = dblog.get_first_stored() if stored_request: (uuid, request_json) = (stored_request.uuid, stored_request.request) new_wps_request = WPSRequest() new_wps_request.json = json.loads(request_json) new_wps_response = WPSResponse(self, new_wps_request, uuid) new_wps_response.status = STATUS.STORE_AND_UPDATE_STATUS self._set_uuid(uuid) self._run_async(new_wps_request, new_wps_response) dblog.remove_stored(uuid) return wps_response
def __call__(self, request): doc = None try: doc = self._construct_doc() except HTTPException as httpexp: raise httpexp except Exception as exp: raise NoApplicableCode(exp) if self.status >= STATUS.DONE_STATUS: self.process.clean() return xml_response(doc)
def sum_one(request, response): input = request.inputs['input'][0].file # What do we need to assert a Complex input? # assert type(input) is text_type import grass.script as grass # Import the raster and set the region if grass.run_command( "r.in.gdal", flags="o", out="input", input=input, quiet=True) != 0: raise NoApplicableCode("Could not import cost map. " "Please check the WCS service.") if grass.run_command("g.region", flags="a", rast="input") != 0: raise NoApplicableCode("Could not set GRASS region.") # Add 1 if grass.mapcalc("$output = $input + $value", output="output", input="input", value=1.0, quiet=True): raise NoApplicableCode("Could not use GRASS map calculator.") # Export the result _, out = tempfile.mkstemp() os.environ['GRASS_VERBOSE'] = '-1' if grass.run_command("r.out.gdal", flags="f", input="output", type="UInt16", output=out, overwrite=True) != 0: raise NoApplicableCode("Could not export result from GRASS.") del os.environ['GRASS_VERBOSE'] response.outputs['output'].file = out return response
def get_session(): """Get Connection for database """ LOGGER.debug('Initializing database connection') global _SESSION_MAKER global _LAST_SESSION if _LAST_SESSION: _LAST_SESSION.close() if _SESSION_MAKER: _SESSION_MAKER.close_all() _LAST_SESSION = _SESSION_MAKER() return _LAST_SESSION database = configuration.get_config_value('logging', 'database') echo = True level = configuration.get_config_value('logging', 'level') level_name = logging.getLevelName(level) if isinstance(level_name, int) and level_name >= logging.INFO: echo = False try: if database.startswith("sqlite") or database.startswith("memory"): engine = sqlalchemy.create_engine( database, connect_args={'check_same_thread': False}, poolclass=StaticPool, echo=echo) else: engine = sqlalchemy.create_engine(database, echo=echo, poolclass=NullPool) except sqlalchemy.exc.SQLAlchemyError as e: raise NoApplicableCode("Could not connect to database: {}".format( e.message)) Session = sessionmaker(bind=engine) ProcessInstance.metadata.create_all(engine) RequestInstance.metadata.create_all(engine) _SESSION_MAKER = Session _LAST_SESSION = _SESSION_MAKER() return _LAST_SESSION
def update_status_file(self, clean): # TODO: check if file/directory is still present, maybe deleted in mean time try: # rebuild the doc and update the status xml file self.doc = self._construct_doc() with open(self.process.status_location, 'w') as f: f.write(self.doc) f.flush() os.fsync(f.fileno()) if (self.status == WPS_STATUS.SUCCEEDED or self.status == WPS_STATUS.FAILED) and clean: self.process.clean() except Exception as e: raise NoApplicableCode( 'Writing Response Document failed with : %s' % e)
def file_handler(complexinput, datain): """<wps:Reference /> handler. Used when href is a file url.""" # save the file reference input in workdir tmp_file = _build_input_file_name( href=datain.get('href'), workdir=complexinput.workdir, extension=_extension(complexinput)) try: inpt_file = urlparse(datain.get('href')).path os.symlink(inpt_file, tmp_file) LOGGER.debug("Linked input file %s to %s.", inpt_file, tmp_file) except Exception as e: raise NoApplicableCode("Could not link file reference: %s" % e) complexinput.file = tmp_file complexinput.url = datain.get('href') complexinput.as_reference = True
def __init__(self, host=None, port=None, debug=False, processes=[], config_file=None): self.app = flask.Flask(__name__) # Load config files and override settings if any file specified if config_file: configuration.load_configuration(config_file) self.host = configuration.get_config_value( 'wps', 'serveraddress').split('://')[1] self.port = int(configuration.get_config_value( 'wps', 'serverport')) # Override config host and port if they are passed to the constructor if host: self.host = host if port: self.port = port self.debug = debug self.output_url = configuration.get_config_value('server', 'outputUrl') self.output_path = configuration.get_config_value( 'server', 'outputPath') self.temp_path = configuration.get_config_value('server', 'workdir') # check if in the configuration file specified directory exists otherwise create it try: if not os.path.exists(self.temp_path): os.makedirs(self.temp_path) print('%s does not exist. Creating it.' % self.temp_path) if not os.path.exists(self.output_path): os.makedirs(self.output_path) print('%s does not exist. Creating it.' % self.output_path) except Exception as e: raise NoApplicableCode('File error: Could not create folder. %s' % e) self.processes = processes self.service = Service(processes=self.processes)
def __call__(self, http_request): try: wps_request = WPSRequest(http_request) if wps_request.operation == 'getcapabilities': return self.get_capabilities() elif wps_request.operation == 'describeprocess': return self.describe(wps_request.identifiers) elif wps_request.operation == 'execute': return self.execute(wps_request.identifier, wps_request) else: raise RuntimeError("Unknown operation %r" % wps_request.operation) except HTTPException as e: # transform HTTPException to OWS NoApplicableCode exception if not isinstance(e, NoApplicableCode): e = NoApplicableCode(e.description, code=e.code) return e
def get_session(): """Get Connection for database """ LOGGER.debug('Initializing database connection') global _SESSION_MAKER if _SESSION_MAKER: return _SESSION_MAKER() with lock: database = configuration.get_config_value('logging', 'database') echo = configuration.get_config_value('logging', 'db_echo') try: if ":memory:" in database: engine = sqlalchemy.create_engine( database, echo=echo, connect_args={'check_same_thread': False}, poolclass=StaticPool) elif database.startswith("sqlite"): engine = sqlalchemy.create_engine( database, echo=echo, connect_args={'check_same_thread': False}, poolclass=NullPool) else: engine = sqlalchemy.create_engine(database, echo=echo, poolclass=NullPool) except sqlalchemy.exc.SQLAlchemyError as e: raise NoApplicableCode( "Could not connect to database: {}".format(e)) Session = sessionmaker(bind=engine, expire_on_commit=True) _SESSION_MAKER = Session return _SESSION_MAKER()
def _get_request(self): """HTTP GET request parser """ # WSDL request wsdl = _get_get_param(self.http_request, 'WSDL') if wsdl is not None: # TODO: fix #57 then remove the exception raise NoApplicableCode('WSDL not implemented') # service shall be WPS service = _get_get_param(self.http_request, 'service', aslist=False) if service: if str(service).lower() != 'wps': raise InvalidParameterValue( 'parameter SERVICE [%s] not supported' % service) else: raise MissingParameterValue('service', 'service') operation = _get_get_param(self.http_request, 'request', aslist=False) request_parser = self._get_request_parser(operation) request_parser(self.http_request)
def write_response_doc(self, clean=True): # TODO: check if file/directory is still present, maybe deleted in mean time # check if storing of the status is requested if self.status >= STATUS.STORE_AND_UPDATE_STATUS: # rebuild the doc and update the status xml file self.doc = self._construct_doc() try: with open(self.process.status_location, 'w') as f: f.write( etree.tostring(self.doc, pretty_print=True, encoding='utf-8').decode('utf-8')) f.flush() os.fsync(f.fileno()) if self.status >= STATUS.DONE_STATUS and clean: self.process.clean() except IOError as e: raise NoApplicableCode( 'Writing Response Document failed with : %s' % e)
def _parse_and_execute(self, process, wps_request): """Parse and execute request """ # check if datainputs is required and has been passed if process.inputs: if wps_request.inputs is None: raise MissingParameterValue('', 'datainputs') # check if all mandatory inputs have been passed data_inputs = {} for inpt in process.inputs: if inpt.identifier not in wps_request.inputs: if inpt.min_occurs > 0: raise MissingParameterValue(inpt.identifier, inpt.identifier) else: data_inputs[inpt.identifier] = inpt.clone() # Replace the dicts with the dict of Literal/Complex inputs # set the input to the type defined in the process if isinstance(inpt, ComplexInput): data_inputs[inpt.identifier] = self.create_complex_inputs(inpt, wps_request.inputs[inpt.identifier]) elif isinstance(inpt, LiteralInput): data_inputs[inpt.identifier] = self.create_literal_inputs(inpt, wps_request.inputs[inpt.identifier]) elif isinstance(inpt, BoundingBoxInput): data_inputs[inpt.identifier] = self.create_bbox_inputs(inpt, wps_request.inputs[inpt.identifier]) wps_request.inputs = data_inputs # set as_reference to True for all the outputs specified as reference # if the output is not required to be raw if not wps_request.raw: for wps_outpt in wps_request.outputs: is_reference = wps_request.outputs[wps_outpt].get('asReference', 'false') if is_reference.lower() == 'true': # check if store is supported if process.store_supported == 'false': raise StorageNotSupported('The storage of data is not supported for this process.') is_reference = True else: is_reference = False for outpt in process.outputs: if outpt.identifier == wps_outpt: outpt.as_reference = is_reference # catch error generated by process code try: wps_response = process.execute(wps_request) except Exception as e: raise NoApplicableCode('Service error: %s' % e) # get the specified output as raw if wps_request.raw: for outpt in wps_request.outputs: for proc_outpt in process.outputs: if outpt == proc_outpt.identifier: return Response(proc_outpt.data) # if the specified identifier was not found raise error raise InvalidParameterValue('') return wps_response