From c9ed573ce55fc8ababc1235525e1e7ea1545826a Mon Sep 17 00:00:00 2001 From: Tom Kralidis Date: Thu, 5 Mar 2026 20:29:52 -0500 Subject: [PATCH] OAProc: derive jobControlOptions and outputTransmission from plugin (#2257) --- docs/source/plugins.rst | 1 + pygeoapi/api/processes.py | 10 +- tests/api/test_processes.py | 39 ++++++- .../pygeoapi-test-config-process-metadata.yml | 106 ++++++++++++++++++ 4 files changed, 150 insertions(+), 6 deletions(-) create mode 100644 tests/pygeoapi-test-config-process-metadata.yml diff --git a/docs/source/plugins.rst b/docs/source/plugins.rst index 4d7e52e59..4b9a86922 100644 --- a/docs/source/plugins.rst +++ b/docs/source/plugins.rst @@ -410,6 +410,7 @@ Below is a sample process definition as a Python dictionary: 'it back as output. Intended to demonstrate a simple ' 'process with a single literal input.', 'jobControlOptions': ['sync-execute', 'async-execute'], # whether the process can be executed in sync or async mode + 'outputTransmission': ['value', 'reference'], # whether the process can return inline data or URL references 'keywords': ['hello world', 'example', 'echo'], # keywords associated with the process 'links': [{ # a list of 1..n # link objects relevant to the process 'type': 'text/html', diff --git a/pygeoapi/api/processes.py b/pygeoapi/api/processes.py index 9b506d0d3..a3167cb7e 100644 --- a/pygeoapi/api/processes.py +++ b/pygeoapi/api/processes.py @@ -130,11 +130,15 @@ def describe_processes(api: API, request: APIRequest, p2.pop('outputs') p2.pop('example', None) - p2['jobControlOptions'] = ['sync-execute'] - if api.manager.is_async: + jco = p.metadata.get('jobControlOptions', ['sync-execute']) + p2['jobControlOptions'] = jco + + if api.manager.is_async and 'async-execute' not in jco: + LOGGER.debug('Adding async capability') p2['jobControlOptions'].append('async-execute') - p2['outputTransmission'] = ['value'] + p2['outputTransmission'] = p.metadata.get( + 'outputTransmission', ['value']) p2['links'] = p2.get('links', []) diff --git a/tests/api/test_processes.py b/tests/api/test_processes.py index e69ecb029..9d837747c 100644 --- a/tests/api/test_processes.py +++ b/tests/api/test_processes.py @@ -37,12 +37,28 @@ import time from unittest import mock +import pytest + +from pygeoapi.api import API from pygeoapi.api.processes import ( describe_processes, execute_process, delete_job, get_job_result, get_jobs ) from pygeoapi.formats import FORMAT_TYPES, F_HTML, F_JSON +from pygeoapi.util import yaml_load + +from tests.util import get_test_file_path, mock_api_request + -from tests.util import mock_api_request +@pytest.fixture() +def config_process_metadata() -> dict: + """ Returns a pygeoapi configuration with process metadata.""" + with open(get_test_file_path('pygeoapi-test-config-process-metadata.yml')) as fh: # noqa + return yaml_load(fh) + + +@pytest.fixture() +def api_process_metadata(config_process_metadata, openapi): + return API(config_process_metadata, openapi) def test_describe_processes(config, api_): @@ -143,8 +159,8 @@ def test_describe_processes(config, api_): # Test describe doesn't crash if example is missing req = mock_api_request() - processor = api_.manager.get_processor("hello-world") - example = processor.metadata.pop("example") + processor = api_.manager.get_processor('hello-world') + example = processor.metadata.pop('example') rsp_headers, code, response = describe_processes(api_, req) processor.metadata['example'] = example data = json.loads(response) @@ -152,6 +168,23 @@ def test_describe_processes(config, api_): assert len(data['processes']) == 2 +def test_describe_processes_metadata(config_process_metadata, + api_process_metadata): + + req = mock_api_request({'limit': 1}) + # Test for description of single processes + rsp_headers, code, response = describe_processes( + api_process_metadata, req, 'echo') + data = json.loads(response) + assert code == HTTPStatus.OK + assert len(data['jobControlOptions']) == 2 + assert 'sync-execute' in data['jobControlOptions'] + assert 'async-execute' in data['jobControlOptions'] + assert len(data['outputTransmission']) == 2 + assert 'value' in data['outputTransmission'] + assert 'reference' in data['outputTransmission'] + + def test_execute_process(config, api_): req_body_0 = { 'inputs': { diff --git a/tests/pygeoapi-test-config-process-metadata.yml b/tests/pygeoapi-test-config-process-metadata.yml new file mode 100644 index 000000000..1d8ddb8fd --- /dev/null +++ b/tests/pygeoapi-test-config-process-metadata.yml @@ -0,0 +1,106 @@ +# ================================================================= +# +# Authors: Tom Kralidis +# +# Copyright (c) 2026 Tom Kralidis +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# ================================================================= + +server: + bind: + host: 0.0.0.0 + port: 5000 + url: http://localhost:5000/ + mimetype: application/json; charset=UTF-8 + encoding: utf-8 + gzip: false + languages: + # First language is the default language + - en-US + - fr-CA + cors: true + pretty_print: true + limits: + default_items: 10 + max_items: 10 + # templates: /path/to/templates + map: + url: https://tile.openstreetmap.org/{z}/{x}/{y}.png + attribution: '© OpenStreetMap contributors' + manager: + name: TinyDB + connection: /tmp/pygeoapi-test-process-manager.db + output_dir: /tmp + +logging: + level: DEBUG + #logfile: /tmp/pygeoapi.log + +metadata: + identification: + title: + en: pygeoapi default instance + fr: instance par défaut de pygeoapi + description: + en: pygeoapi provides an API to geospatial data + fr: pygeoapi fournit une API aux données géospatiales + keywords: + en: + - geospatial + - data + - api + fr: + - géospatiale + - données + - api + keywords_type: theme + terms_of_service: https://creativecommons.org/licenses/by/4.0/ + url: http://example.org + license: + name: CC-BY 4.0 license + url: https://creativecommons.org/licenses/by/4.0/ + provider: + name: Organization Name + url: https://pygeoapi.io + contact: + name: Lastname, Firstname + position: Position Title + address: Mailing Address + city: City + stateorprovince: Administrative Area + postalcode: Zip or Postal Code + country: Country + phone: +xx-xxx-xxx-xxxx + fax: +xx-xxx-xxx-xxxx + email: you@example.org + url: Contact URL + hours: Hours of Service + instructions: During hours of service. Off on weekends. + role: pointOfContact + +resources: + echo: + type: process + processor: + name: Echo