Example #1
0
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))
Example #2
0
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)
Example #3
0
    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
Example #4
0
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))
Example #5
0
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)