From 69a5dd7d22b772509086c513dc7d906eb94dada9 Mon Sep 17 00:00:00 2001 From: David Caron Date: Thu, 6 Feb 2020 17:09:56 -0500 Subject: [PATCH 1/6] pass the Accept-Language header to ... the `execute_process` function --- weaver/datatype.py | 12 ++++++++++++ weaver/store/mongodb.py | 2 ++ weaver/wps_restapi/processes/processes.py | 12 +++++++++++- weaver/wps_restapi/swagger_definitions.py | 7 ++++++- 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/weaver/datatype.py b/weaver/datatype.py index 0a37300b9..ac9c64e57 100644 --- a/weaver/datatype.py +++ b/weaver/datatype.py @@ -325,6 +325,18 @@ def notification_email(self, email): raise TypeError("Type 'str' is required for '{}.notification_email'".format(type(self))) self["notification_email"] = email + @property + def accept_language(self): + # type: () -> Optional[AnyStr] + return self.get("accept_language") + + @accept_language.setter + def accept_language(self, language): + # type: (Optional[Union[AnyStr]]) -> None + if not isinstance(language, six.string_types): + raise TypeError("Type 'str' is required for '{}.accept_language'".format(type(self))) + self["accept_language"] = language + @property def execute_async(self): # type: () -> bool diff --git a/weaver/store/mongodb.py b/weaver/store/mongodb.py index 971ef916c..377e6a709 100644 --- a/weaver/store/mongodb.py +++ b/weaver/store/mongodb.py @@ -382,6 +382,7 @@ def save_job(self, custom_tags=None, # type: Optional[List[AnyStr]] access=None, # type: Optional[AnyStr] notification_email=None, # type: Optional[AnyStr] + accept_language=None, # type: Optional[AnyStr] ): # type: (...) -> Job """ Stores a job in mongodb. @@ -412,6 +413,7 @@ def save_job(self, "tags": list(set(tags)), "access": access, "notification_email": notification_email, + "accept_language": accept_language, }) self.collection.insert_one(new_job.params()) job = self.fetch_by_id(job_id=new_job.id) diff --git a/weaver/wps_restapi/processes/processes.py b/weaver/wps_restapi/processes/processes.py index be8261cf3..51a114cbf 100644 --- a/weaver/wps_restapi/processes/processes.py +++ b/weaver/wps_restapi/processes/processes.py @@ -60,6 +60,15 @@ LOGGER = logging.getLogger(__name__) +def set_wps_language(wps, accept_language): + """Set the `language` property on the `WebProcessingService` object. + + Given the `accept_language` header value, match the best language + to the supported languages. + """ + pass + + @app.task(bind=True) def execute_process(self, job_id, url, headers=None, notification_email=None): settings = get_settings(app) @@ -77,6 +86,7 @@ def execute_process(self, job_id, url, headers=None, notification_email=None): try: LOGGER.debug("Execute process WPS request for [%s]", job.process) wps = WebProcessingService(url=url, headers=get_cookie_headers(headers), verify=ssl_verify) + set_wps_language(wps, job.accept_language) # noinspection PyProtectedMember raise_on_xml_exception(wps._capabilities) except Exception as ex: @@ -248,7 +258,7 @@ def submit_job_handler(request, service_url, is_workflow=False, visibility=None) job = store.save_job(task_id=STATUS_ACCEPTED, process=process_id, service=provider_id, inputs=json_body.get("inputs"), is_workflow=is_workflow, access=visibility, user_id=request.authenticated_userid, execute_async=is_execute_async, custom_tags=tags, - notification_email=encrypted_email) + notification_email=encrypted_email, accept_language=request.accept_language) result = execute_process.delay( job_id=job.id, url=clean_ows_url(service_url), diff --git a/weaver/wps_restapi/swagger_definitions.py b/weaver/wps_restapi/swagger_definitions.py index b42a6eaea..ca56e273d 100644 --- a/weaver/wps_restapi/swagger_definitions.py +++ b/weaver/wps_restapi/swagger_definitions.py @@ -258,6 +258,11 @@ class AcceptHeader(MappingSchema): ])) +class AcceptLanguageHeader(AcceptHeader): + AcceptLanguage = SchemaNode(String(), missing=drop) + AcceptLanguage.name = "Accept-Language" + + class KeywordList(SequenceSchema): keyword = SchemaNode(String()) @@ -1172,7 +1177,7 @@ class PostProcessesEndpoint(MappingSchema): class PostProcessJobsEndpoint(ProcessPath): - header = AcceptHeader() + header = AcceptLanguageHeader() body = Execute() From c46021118324e009abdfd966226201f9446030d4 Mon Sep 17 00:00:00 2001 From: David Caron Date: Thu, 6 Feb 2020 23:23:57 -0500 Subject: [PATCH 2/6] complete the set_wps_language function --- weaver/wps_restapi/processes/processes.py | 26 +++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/weaver/wps_restapi/processes/processes.py b/weaver/wps_restapi/processes/processes.py index 51a114cbf..e1d499833 100644 --- a/weaver/wps_restapi/processes/processes.py +++ b/weaver/wps_restapi/processes/processes.py @@ -60,15 +60,6 @@ LOGGER = logging.getLogger(__name__) -def set_wps_language(wps, accept_language): - """Set the `language` property on the `WebProcessingService` object. - - Given the `accept_language` header value, match the best language - to the supported languages. - """ - pass - - @app.task(bind=True) def execute_process(self, job_id, url, headers=None, notification_email=None): settings = get_settings(app) @@ -211,6 +202,23 @@ def execute_process(self, job_id, url, headers=None, notification_email=None): return job.status +def set_wps_language(wps, accept_language): + """Set the `language` property on the `WebProcessingService` object. + + Given the `accept_language` header value, match the best language + to the supported languages. + + By default, and if no match is found, the `language` property is None. + """ + accepted_languages = [lang.strip() for lang in accept_language.lower().split(",")] + + for accept in accepted_languages: + for language in wps.languages.supported: + if language.lower().startswith(accept): + wps.language = language + return + + def validate_supported_submit_job_handler_parameters(json_body): """ Tests supported parameters not automatically validated by colander deserialize. From 2e1ee7a3ab8374c331cc209146ff86b47cd75042 Mon Sep 17 00:00:00 2001 From: David Caron Date: Fri, 7 Feb 2020 16:24:47 -0500 Subject: [PATCH 3/6] fix accept_language storing and add documentation --- weaver/datatype.py | 1 + weaver/wps_restapi/processes/processes.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/weaver/datatype.py b/weaver/datatype.py index ac9c64e57..6631a4cd8 100644 --- a/weaver/datatype.py +++ b/weaver/datatype.py @@ -567,6 +567,7 @@ def params(self): "request": self.request, "response": self.response, "notification_email": self.notification_email, + "accept_language": self.accept_language, } diff --git a/weaver/wps_restapi/processes/processes.py b/weaver/wps_restapi/processes/processes.py index e1d499833..e8d0769b4 100644 --- a/weaver/wps_restapi/processes/processes.py +++ b/weaver/wps_restapi/processes/processes.py @@ -203,17 +203,26 @@ def execute_process(self, job_id, url, headers=None, notification_email=None): def set_wps_language(wps, accept_language): + # type: (WebProcessingService, str) -> None """Set the `language` property on the `WebProcessingService` object. Given the `accept_language` header value, match the best language to the supported languages. By default, and if no match is found, the `language` property is None. + + https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language + (q-factor weighting is ignored, only order is considered) """ - accepted_languages = [lang.strip() for lang in accept_language.lower().split(",")] + if not hasattr(wps, "languages"): + # owslib version doesn't support setting a language + return + + accepted_languages = [lang.strip().split(';')[0] for lang in accept_language.lower().split(",")] for accept in accepted_languages: for language in wps.languages.supported: + # Accept-Language header could be only 'fr' instead of 'fr-CA' if language.lower().startswith(accept): wps.language = language return @@ -266,7 +275,7 @@ def submit_job_handler(request, service_url, is_workflow=False, visibility=None) job = store.save_job(task_id=STATUS_ACCEPTED, process=process_id, service=provider_id, inputs=json_body.get("inputs"), is_workflow=is_workflow, access=visibility, user_id=request.authenticated_userid, execute_async=is_execute_async, custom_tags=tags, - notification_email=encrypted_email, accept_language=request.accept_language) + notification_email=encrypted_email, accept_language=str(request.accept_language)) result = execute_process.delay( job_id=job.id, url=clean_ows_url(service_url), From 8feabf1f3d9af643254d0f67fb73b72374539f74 Mon Sep 17 00:00:00 2001 From: David Caron Date: Fri, 7 Feb 2020 16:25:11 -0500 Subject: [PATCH 4/6] add test for wps languages --- tests/wps_restapi/test_processes.py | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/wps_restapi/test_processes.py b/tests/wps_restapi/test_processes.py index a3e958aca..a5eb73858 100644 --- a/tests/wps_restapi/test_processes.py +++ b/tests/wps_restapi/test_processes.py @@ -1,6 +1,7 @@ import os import unittest from copy import deepcopy +from unittest.mock import Mock import pyramid.testing import pytest @@ -32,6 +33,7 @@ from weaver.utils import fully_qualified_name, ows_context_href from weaver.visibility import VISIBILITY_PRIVATE, VISIBILITY_PUBLIC from weaver.wps import get_wps_url +from weaver.wps_restapi.processes.processes import set_wps_language # simulated remote server with remote processes (mocked with `responses` package) TEST_REMOTE_SERVER_URL = "https://remote-server.com" @@ -486,6 +488,36 @@ def test_execute_process_success(self): assert job.id == resp.json["jobID"] assert job.task_id == STATUS_ACCEPTED # temporary value until processed by celery + def test_execute_process_language(self): + uri = "/processes/{}/jobs".format(self.process_public.identifier) + data = self.get_process_execute_template() + task = "job-{}".format(fully_qualified_name(self)) + mock_execute = mocked_process_job_runner(task) + + with contextlib.ExitStack() as stack: + for exe in mock_execute: + stack.enter_context(exe) + headers = self.json_headers.copy() + headers["Accept-Language"] = "fr-CA" + resp = self.app.post_json(uri, params=data, headers=headers) + assert resp.status_code == 201 + try: + job = self.job_store.fetch_by_id(resp.json["jobID"]) + except JobNotFound: + self.fail("Job should have been created and be retrievable.") + assert job.id == resp.json["jobID"] + assert job.accept_language == "fr-CA" + + def test_set_wps_language(self): + wps = Mock() + languages = Mock() + wps.languages = languages + languages.default = 'en-US' + languages.supported = ['en-US', 'fr-CA'] + + set_wps_language(wps, 'ru, fr;q=0.5') + assert wps.language == 'fr-CA' + def test_execute_process_no_json_body(self): uri = "/processes/{}/jobs".format(self.process_public.identifier) resp = self.app.post_json(uri, headers=self.json_headers, expect_errors=True) From ffef5ed884e8c91cbef5714f4ce34cb4375d6e57 Mon Sep 17 00:00:00 2001 From: David Caron Date: Fri, 7 Feb 2020 16:44:26 -0500 Subject: [PATCH 5/6] fix for mock module in python 2.7 --- tests/wps_restapi/test_processes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/wps_restapi/test_processes.py b/tests/wps_restapi/test_processes.py index a5eb73858..bf7059b8b 100644 --- a/tests/wps_restapi/test_processes.py +++ b/tests/wps_restapi/test_processes.py @@ -1,7 +1,7 @@ import os import unittest from copy import deepcopy -from unittest.mock import Mock +import mock import pyramid.testing import pytest @@ -509,8 +509,8 @@ def test_execute_process_language(self): assert job.accept_language == "fr-CA" def test_set_wps_language(self): - wps = Mock() - languages = Mock() + wps = mock.Mock() + languages = mock.Mock() wps.languages = languages languages.default = 'en-US' languages.supported = ['en-US', 'fr-CA'] From 788674423a8a037ac731a948d9afb9381f4bd300 Mon Sep 17 00:00:00 2001 From: David Caron Date: Mon, 10 Feb 2020 14:00:58 -0500 Subject: [PATCH 6/6] update docstring --- weaver/wps_restapi/processes/processes.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/weaver/wps_restapi/processes/processes.py b/weaver/wps_restapi/processes/processes.py index e8d0769b4..b1b645e51 100644 --- a/weaver/wps_restapi/processes/processes.py +++ b/weaver/wps_restapi/processes/processes.py @@ -204,15 +204,18 @@ def execute_process(self, job_id, url, headers=None, notification_email=None): def set_wps_language(wps, accept_language): # type: (WebProcessingService, str) -> None - """Set the `language` property on the `WebProcessingService` object. + """Set the :attr:`language` property on the :class:`WebProcessingService` object. - Given the `accept_language` header value, match the best language + Given the `Accept-Language` header value, match the best language to the supported languages. - By default, and if no match is found, the `language` property is None. + By default, and if no match is found, the :attr:`WebProcessingService.language` + property is set to None. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language (q-factor weighting is ignored, only order is considered) + + :param str accept_language: the value of the Accept-Language header """ if not hasattr(wps, "languages"): # owslib version doesn't support setting a language