def test_get_wps_output_context_resolution(): test_cases = [ # header provided uses it regardless of setting ("somewhere", None, "somewhere"), ("somewhere", "", "somewhere"), ("somewhere", "test", "somewhere"), # header empty or omitted defaults to none used when no/empty default context ("", None, None), ("", "", None), (None, None, None), (None, "", None), # header empty or omitted defaults to defined default context setting ("", "test", "test"), (None, "test", "test"), ] for i, (test_header, test_setting, expect_result) in enumerate(test_cases): try: headers = {"X-WPS-Output-Context": test_header} settings = {"weaver.wps_output_context": test_setting} req = DummyRequest(headers=headers) with mock.patch("weaver.wps.utils.get_settings", return_value=settings): res = get_wps_output_context(req) assert res == expect_result except Exception as exc: pytest.fail( "Exception raised when none is expected [{}]: {!s}: ${!s}". format(i, exc.__class__.__name__, exc))
def submit_job(request, reference, tags=None): # type: (Request, Union[Service, Process], Optional[List[str]]) -> JSON """ Generates the job submission from details retrieved in the request. .. seealso:: :func:`submit_job_handler` to provide elements pre-extracted from requests or from other parsing. """ # validate body with expected JSON content and schema if CONTENT_TYPE_APP_JSON not in request.content_type: raise HTTPBadRequest(json={ "code": "InvalidHeaderValue", "name": "Content-Type", "description": "Request 'Content-Type' header other than '{}' not supported.".format(CONTENT_TYPE_APP_JSON), "value": str(request.content_type) }) try: json_body = request.json_body except Exception as ex: raise HTTPBadRequest("Invalid JSON body cannot be decoded for job submission. [{}]".format(ex)) # validate context if needed later on by the job for early failure context = get_wps_output_context(request) provider_id = None # None OK if local process_id = None # None OK if remote, but can be found as well if available from WPS-REST path tags = tags or [] lang = request.accept_language.header_value # can only preemptively check if local process if isinstance(reference, Process): service_url = reference.processEndpointWPS1 process_id = reference.id visibility = reference.visibility is_workflow = reference.type == PROCESS_WORKFLOW is_local = True tags += "local" if lang and request.accept_language.best_match(ACCEPT_LANGUAGES) is None: raise HTTPNotAcceptable("Requested language [{}] is not in supported languages [{}].".format( lang, ", ".join(ACCEPT_LANGUAGES) )) elif isinstance(reference, Service): service_url = reference.url provider_id = reference.id process_id = request.matchdict.get("process_id") visibility = VISIBILITY_PUBLIC is_workflow = False is_local = False tags += "remote" else: LOGGER.error("Expected process/service, got: %s", type(reference)) raise TypeError("Invalid process or service reference to execute job.") tags = request.params.get("tags", "").split(",") + tags user = request.authenticated_userid headers = dict(request.headers) settings = get_settings(request) return submit_job_handler(json_body, settings, service_url, provider_id, process_id, is_workflow, is_local, visibility, language=lang, auth=headers, tags=tags, user=user, context=context)
def _submit_job(self, wps_request): # type: (WPSRequest) -> Union[WPSResponse, HTTPValid, JSON] """ Dispatch operation to WPS-REST endpoint, which in turn should call back the real Celery Worker for execution. Returns the status response as is if XML, or convert it to JSON, according to request ``Accept`` header. """ req = wps_request.http_request pid = wps_request.identifier ctx = get_wps_output_context( req ) # re-validate here in case submitted via WPS endpoint instead of REST-API proc = get_process( process_id=pid, settings=self.settings) # raises if invalid or missing wps_process = self.processes.get(pid) # create the JSON payload from the XML content and submit job is_workflow = proc.type == ProcessType.WORKFLOW tags = req.args.get( "tags", "").split(",") + ["xml", f"wps-{wps_request.version}"] data = wps2json_job_payload(wps_request, wps_process) resp = submit_job_handler(data, self.settings, proc.processEndpointWPS1, process_id=pid, is_local=True, is_workflow=is_workflow, visibility=Visibility.PUBLIC, language=wps_request.language, tags=tags, headers=dict(req.headers), context=ctx) # enforced JSON results with submitted data that includes 'response=document' # use 'json_body' to work with any 'response' implementation body = resp.json_body # if Accept was JSON, provide response content as is # if anything else (even */*), return as XML # NOTE: # It is very important to respect default XML since 'owslib.wps.WebProcessingService' does not provide any # way to provide explicitly Accept header. Even our Wps1Process as Workflow step depends on this behaviour. accept_type = get_header("Accept", req.headers) if accept_type == ContentType.APP_JSON: resp = get_job_submission_response(body, resp.headers) setattr( resp, "_update_status", lambda *_, **__: None) # patch to avoid pywps server raising return resp return body
def test_get_wps_output_context_validation(): bad_cases = [ "test/////test", "test/test//", "test/./test/test", "test/../test/test", "/test/test/test/test", "/test/test/", "/test", "/test/", "./test", "../test", "/" ] good_cases = [ ("test", "test"), ("test/test", "test/test"), ("test/test/", "test/test"), # allow trailing slash auto-removed ("test/test/test/test", "test/test/test/test"), ("test-test", "test-test"), ("test_test", "test_test"), ("test-test/test/test_test", "test-test/test/test_test"), ] header_names = [ "x-wps-output-context", "X-WPS-OUTPUT-CONTEXT", "X-WPS-Output-Context" ] for header in header_names: for case in bad_cases: # validate against provided header try: req = DummyRequest(headers={header: case}) get_wps_output_context(req) except HTTPUnprocessableEntity: pass else: pytest.fail( "Exception not raised when expected: (header={}, case={})". format(header, case)) # validate same conditions fulfilled by default context if header omitted try: settings = {"weaver.wps_output_context": case} with mock.patch("weaver.wps.utils.get_settings", return_value=settings): req = DummyRequest(headers={}) get_wps_output_context(req) except HTTPUnprocessableEntity: pass else: pytest.fail( "Exception not raised when expected: (<setting>, case={})". format(case)) for case, result in good_cases: # validate against provided header try: req = DummyRequest(headers={header: case}) ctx = get_wps_output_context(req) assert ctx == result except Exception as exc: pytest.fail( "Exception raised when none is expected: (header={}, case={})\n" "Exception: {!s}: ${!s}".format(header, case, exc.__class__.__name__, exc)) # validate same conditions fulfilled by default context if header omitted settings = {"weaver.wps_output_context": case} with mock.patch("weaver.wps.utils.get_settings", return_value=settings): try: req = DummyRequest(headers={}) ctx = get_wps_output_context(req) assert ctx == result except Exception as exc: pytest.fail( "Exception raised when none is expected: (<setting>, case={})\n" "Exception: {!s}: ${!s}".format( case, exc.__class__.__name__, exc))
def submit_job(request, reference, tags=None): # type: (Request, Union[Service, Process], Optional[List[str]]) -> AnyResponseType """ Generates the job submission from details retrieved in the request. .. seealso:: :func:`submit_job_handler` to provide elements pre-extracted from requests or from other parsing. """ # validate body with expected JSON content and schema if ContentType.APP_JSON not in request.content_type: raise HTTPBadRequest( json={ "code": "InvalidHeaderValue", "name": "Content-Type", "description": f"Request 'Content-Type' header other than '{ContentType.APP_JSON}' not supported.", "value": str(request.content_type) }) try: json_body = request.json_body except Exception as ex: raise HTTPBadRequest( f"Invalid JSON body cannot be decoded for job submission. [{ex}]") # validate context if needed later on by the job for early failure context = get_wps_output_context(request) provider_id = None # None OK if local process_id = None # None OK if remote, but can be found as well if available from WPS-REST path # noqa tags = tags or [] lang = request.accept_language.header_value # can only preemptively check if local process if isinstance(reference, Process): service_url = reference.processEndpointWPS1 process_id = reference.identifier # explicit 'id:version' process revision if available, otherwise simply 'id' visibility = reference.visibility is_workflow = reference.type == ProcessType.WORKFLOW is_local = True tags += "local" support_lang = AcceptLanguage.values() if lang and request.accept_language.best_match(support_lang) is None: raise HTTPNotAcceptable( f"Requested language [{lang}] not in supported languages [{sorted(support_lang)}]." ) elif isinstance(reference, Service): service_url = reference.url provider_id = reference.id process_id = request.matchdict.get("process_id") visibility = Visibility.PUBLIC is_workflow = False is_local = False tags += "remote" else: # pragma: no cover LOGGER.error("Expected process/service, got: %s", type(reference)) raise TypeError("Invalid process or service reference to execute job.") tags = request.params.get("tags", "").split(",") + tags user = request.authenticated_userid headers = dict(request.headers) settings = get_settings(request) return submit_job_handler(json_body, settings, service_url, provider_id, process_id, is_workflow, is_local, visibility, language=lang, headers=headers, tags=tags, user=user, context=context)