diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e0e9baa..4a1e250 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,26 +33,3 @@ jobs: file: docker/actinia-module-plugin-test/Dockerfile no-cache: true # pull: true - - # Tests for GRASS 8.3 - tests-G83: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Replace run integration test command - run: | - sed -i "s+mundialis/actinia:latest+mundialis/actinia:grass8.3+g" \ - docker/actinia-module-plugin-test/Dockerfile - - name: Tests of actinia-module-plugin - id: docker_build - uses: docker/build-push-action@v6 - with: - push: false - tags: actinia-module-plugin-test:alpine - context: . - file: docker/actinia-module-plugin-test/Dockerfile - no-cache: true - # pull: true diff --git a/ruff.toml b/ruff.toml index 83c04d6..72103bf 100644 --- a/ruff.toml +++ b/ruff.toml @@ -86,6 +86,7 @@ lint.ignore = [ "RET504", "RET505", "RUF012", + "RUF067", "S101", "S107", "S110", @@ -106,4 +107,4 @@ lint.ignore = [ "UP015", "UP028", "UP031" -] \ No newline at end of file +] diff --git a/src/actinia_module_plugin/api/modules/actinia.py b/src/actinia_module_plugin/api/modules/actinia.py index 7273023..c32e5cb 100644 --- a/src/actinia_module_plugin/api/modules/actinia.py +++ b/src/actinia_module_plugin/api/modules/actinia.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -SPDX-FileCopyrightText: (c) 2018-2025 by mundialis GmbH & Co. KG +SPDX-FileCopyrightText: (c) 2018-2026 by mundialis GmbH & Co. KG SPDX-License-Identifier: Apache-2.0 @@ -14,11 +14,11 @@ __license__ = "Apache-2.0" __author__ = "Carmen Tawalika" -__copyright__ = "Copyright 2019-2025, mundialis" +__copyright__ = "Copyright 2019-2026, mundialis" __maintainer__ = "Carmen Tawalika" -from flask import jsonify, make_response +from flask import jsonify, make_response, request from flask_restful_swagger_2 import swagger from flask_restful import Resource @@ -71,8 +71,10 @@ class DescribeProcessChainTemplate(ResourceBase): def get(self, actiniamodule): """Describe an actinia module (process chain template).""" + returns = request.args.get("returns") or None + try: - virtual_module = createActiniaModule(self, actiniamodule) + virtual_module = createActiniaModule(self, actiniamodule, returns) return make_response(jsonify(virtual_module), 200) except Exception: msg = 'Error looking for actinia module "' + actiniamodule + '".' diff --git a/src/actinia_module_plugin/api/modules/combined.py b/src/actinia_module_plugin/api/modules/combined.py index 1898e68..3cf0329 100644 --- a/src/actinia_module_plugin/api/modules/combined.py +++ b/src/actinia_module_plugin/api/modules/combined.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -SPDX-FileCopyrightText: (c) 2018-2021 by mundialis GmbH & Co. KG +SPDX-FileCopyrightText: (c) 2018-2026 by mundialis GmbH & Co. KG SPDX-License-Identifier: Apache-2.0 @@ -14,7 +14,7 @@ __license__ = "Apache-2.0" __author__ = "Carmen Tawalika" -__copyright__ = "Copyright 2019, mundialis" +__copyright__ = "Copyright 2019-2026, mundialis" __maintainer__ = "Carmen Tawalika" @@ -86,11 +86,13 @@ class DescribeVirtualModule(ResourceBase): def get(self, module): """Describe a module.""" + returns = request.args.get("returns") or None + try: try: virtual_module = createGrassModule(self, module) except Exception: - virtual_module = createActiniaModule(self, module) + virtual_module = createActiniaModule(self, module, returns) finally: return make_response(jsonify(virtual_module), 200) diff --git a/src/actinia_module_plugin/apidocs/modules.py b/src/actinia_module_plugin/apidocs/modules.py index 47a56e6..8c49682 100644 --- a/src/actinia_module_plugin/apidocs/modules.py +++ b/src/actinia_module_plugin/apidocs/modules.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -SPDX-FileCopyrightText: (c) 2018-2021 by mundialis GmbH & Co. KG +SPDX-FileCopyrightText: (c) 2018-2026 by mundialis GmbH & Co. KG SPDX-License-Identifier: Apache-2.0 @@ -9,7 +9,7 @@ """ __author__ = "Carmen Tawalika" -__copyright__ = "2018-2021 mundialis GmbH & Co. KG" +__copyright__ = "2018-2026 mundialis GmbH & Co. KG" __license__ = "Apache-2.0" @@ -21,7 +21,6 @@ from actinia_module_plugin.model.modules import Module, ModuleList - null = "null" @@ -95,7 +94,21 @@ "type": "string", "description": "The name of a module", "required": True, - } + }, + { + "in": "path", + "name": "returns", + "type": "string", + "description": "Defines which outputs are returned for actinia " + "modules. 'export' shows the exported results. 'persistent' to " + "show persistent results might be added in the future. Without " + "this parameter no outputs are returned. No effect for single " + "GRASS GIS modules.", + "enum": [ + "export", + # "persistent", + ], + }, ], "description": "Get the description of a module. " "Minimum required user role: user." @@ -111,6 +124,10 @@ "describing modules did not succeeded", "schema": SimpleStatusCodeResponseModel, }, + "404": { + "description": "The error message that the module was not found.", + "schema": SimpleStatusCodeResponseModel, + }, }, } diff --git a/src/actinia_module_plugin/apidocs/templates.py b/src/actinia_module_plugin/apidocs/templates.py index 9e03198..8e0cc46 100644 --- a/src/actinia_module_plugin/apidocs/templates.py +++ b/src/actinia_module_plugin/apidocs/templates.py @@ -23,7 +23,6 @@ SimpleStatusCodeResponseModel, ) - script_dir = os.path.dirname(os.path.abspath(__file__)) rel_path = "./examples/pc_template.json" abs_file_path = os.path.join(script_dir, rel_path) diff --git a/src/actinia_module_plugin/core/common.py b/src/actinia_module_plugin/core/common.py index e36ae73..d9433f6 100644 --- a/src/actinia_module_plugin/core/common.py +++ b/src/actinia_module_plugin/core/common.py @@ -25,7 +25,6 @@ from actinia_module_plugin.resources.logging import log from actinia_module_plugin.resources.templating import pcTplEnv - ENV = { key.replace("TEMPLATE_VALUE_", ""): val for key, val in env.items() diff --git a/src/actinia_module_plugin/core/filter.py b/src/actinia_module_plugin/core/filter.py index 6cf636c..e693dce 100644 --- a/src/actinia_module_plugin/core/filter.py +++ b/src/actinia_module_plugin/core/filter.py @@ -16,7 +16,6 @@ from flask import request - global family global tag global category diff --git a/src/actinia_module_plugin/core/modules/actinia_common.py b/src/actinia_module_plugin/core/modules/actinia_common.py index 49dc3cf..66be1bb 100644 --- a/src/actinia_module_plugin/core/modules/actinia_common.py +++ b/src/actinia_module_plugin/core/modules/actinia_common.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -SPDX-FileCopyrightText: (c) 2018-2025 by mundialis GmbH & Co. KG +SPDX-FileCopyrightText: (c) 2018-2026 by mundialis GmbH & Co. KG SPDX-License-Identifier: Apache-2.0 @@ -9,9 +9,11 @@ Common module for file based and kvdb templates """ +from __future__ import annotations + __license__ = "Apache-2.0" __author__ = "Carmen Tawalika, Anika Weinmann" -__copyright__ = "Copyright 2019-2025, mundialis" +__copyright__ = "Copyright 2019-2026, mundialis" __maintainer__ = "Carmen Tawalika" @@ -36,6 +38,7 @@ from actinia_module_plugin.core.modules.processor import run_process_chain from actinia_module_plugin.core.modules.parser import ParseInterfaceDescription from actinia_module_plugin.model.modules import Module +from actinia_module_plugin.core.templates.utils import isGlobalTemplate def render_template(pc, return_source=False): @@ -436,7 +439,74 @@ def set_not_needed_param_to_optional(params, returns, undef, tpl_source): ret["description"] += reason_text[reason] -def createActiniaModule(resourceBaseSelf, processchain): +def _expand_nested_exported_results(pc_template_list_items): + """ + Expaned nested list items when a nested actinia module is called + """ + # find nested modules which may have an export on their own + # TODO no recursion (finds exports only one level down) + new_pc_template_list_items = [] + for i in pc_template_list_items: + module_name = i.get("module") + # check if it is a global template, then add pc list of that tpl + if isGlobalTemplate(module_name): + nested_pc_template, _ = render_template(module_name) + new_pc_template_list_items += nested_pc_template["template"][ + "list" + ] + # user_templates are not checked yet and treated as normal modules + # meaning that they are added but not checked for nested exporter + else: + new_pc_template_list_items.append(i) + return new_pc_template_list_items + + +def _generate_exported_results(pc_template_list_items): + """ + Populate returns array with results which are generated by the exporter. + """ + exported_results = [] + + for i in pc_template_list_items: + module = i.get("module") + outputs = i.get("outputs") + + if not module or not outputs: + continue + + for o in outputs: + export = o.get("export") + if not export: + continue + + exported_results.append( + { + "name": o["value"], + "description": f"Exported result from exporter {module} " + f"{o["param"]} parameter", + "schema": { + "type": export.get("type", "string"), + "subtype": export.get("format", "undefined"), + }, + } + ) + return exported_results + + +def _generate_exported_results_including_nested_items(pc_template_list_items): + # Populate list items if there are nested modules + new_pc_template_list_items = _expand_nested_exported_results( + pc_template_list_items + ) + # create results array out of list items + return _generate_exported_results(new_pc_template_list_items) + + +def createActiniaModule( + resourceBaseSelf, + processchain, + returns: str | None = None, +): """ This method is used to create self-descriptions for actinia-modules. In this method the terms "processchain" and "exec_process_chain" are @@ -517,13 +587,19 @@ def createActiniaModule(resourceBaseSelf, processchain): if "projects" in pc_template: projects = pc_template["projects"] + exported_results = [] + if returns == "export": + exported_results = _generate_exported_results_including_nested_items( + pc_template_list_items + ) + virtual_module = Module( id=pc_template["id"], description=pc_template["description"], categories=categories, projects=projects, - parameters=pt.vm_params, - returns=pt.vm_returns, + parameters=pt.vm_params + pt.vm_returns, + returns=exported_results, ) return virtual_module diff --git a/src/actinia_module_plugin/core/template_parameters.py b/src/actinia_module_plugin/core/template_parameters.py index 9bcea31..a24bd50 100644 --- a/src/actinia_module_plugin/core/template_parameters.py +++ b/src/actinia_module_plugin/core/template_parameters.py @@ -114,7 +114,9 @@ def get_not_needed_params(undef, tpl_source, parsed_content=None): # return also the reason why the parameters is not needed reason = ["default"] * default_len + ["if"] * if_len - return {key: val for key, val in zip(not_needed_vars, reason)} + return { + key: val for key, val in zip(not_needed_vars, reason, strict=False) + } def get_template_undef(tpl_source): diff --git a/src/actinia_module_plugin/core/templates/utils.py b/src/actinia_module_plugin/core/templates/utils.py new file mode 100644 index 0000000..b703eb3 --- /dev/null +++ b/src/actinia_module_plugin/core/templates/utils.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +SPDX-FileCopyrightText: (c) 2026 by mundialis GmbH & Co. KG + +SPDX-License-Identifier: Apache-2.0 + +Process Chain Template Management +File reader for global templates +""" + +__license__ = "Apache-2.0" +__author__ = "Carmen Tawalika" +__copyright__ = "Copyright 2026, mundialis" +__maintainer__ = "Carmen Tawalika" + + +from actinia_module_plugin.core.common import filter_func +from actinia_module_plugin.resources.templating import pcTplEnv + + +# duplicated from global_templates, left as is due to circular references +def getAllGlobalTemplates(): + """ + This method creates a list of all global templates stored as file + to return the list to the api method listing all templates. + """ + tpl_list = pcTplEnv.list_templates(filter_func=filter_func) + tpl_list = [i.replace(".json", "").split("/")[-1] for i in tpl_list] + return tpl_list + + +def isGlobalTemplate(template_id): + """ + This method checks if an input string is an existing template id. + """ + if template_id in getAllGlobalTemplates(): + return True + return False diff --git a/src/actinia_module_plugin/main.py b/src/actinia_module_plugin/main.py index fb79bf7..f23e169 100644 --- a/src/actinia_module_plugin/main.py +++ b/src/actinia_module_plugin/main.py @@ -20,7 +20,6 @@ from actinia_module_plugin import endpoints from actinia_module_plugin.resources.logging import log - app = Flask(__name__) CORS(app) diff --git a/src/actinia_module_plugin/model/modules.py b/src/actinia_module_plugin/model/modules.py index 211f6cf..e47ffc8 100644 --- a/src/actinia_module_plugin/model/modules.py +++ b/src/actinia_module_plugin/model/modules.py @@ -18,7 +18,6 @@ from flask_restful_swagger_2 import Schema - script_dir = os.path.dirname(os.path.abspath(__file__)) rel_path = "../apidocs/examples/gm_describemodule_get_example_v_random.json" abs_file_path = os.path.join(script_dir, rel_path) diff --git a/src/actinia_module_plugin/resources/logging.py b/src/actinia_module_plugin/resources/logging.py index b7d1ffe..90525a5 100644 --- a/src/actinia_module_plugin/resources/logging.py +++ b/src/actinia_module_plugin/resources/logging.py @@ -23,7 +23,6 @@ from actinia_module_plugin.resources.config import LOGCONFIG - log = logging.getLogger("actinia-module-plugin") werkzeugLog = logging.getLogger("werkzeug") gunicornLog = logging.getLogger("gunicorn") diff --git a/src/actinia_module_plugin/resources/templating.py b/src/actinia_module_plugin/resources/templating.py index e13be60..16861db 100644 --- a/src/actinia_module_plugin/resources/templating.py +++ b/src/actinia_module_plugin/resources/templating.py @@ -17,7 +17,6 @@ from actinia_module_plugin.resources.config import PCTEMPLATECONFIG - # this environment is used for all cases where individual templates are loaded tplEnv = Environment( loader=PackageLoader("actinia_module_plugin", "templates") diff --git a/tests/conftest.py b/tests/conftest.py index 1472ae2..70ba733 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,6 +7,7 @@ Read more about conftest.py under: https://pytest.org/latest/plugins.html """ + from __future__ import print_function, absolute_import, division # import pytest diff --git a/tests/resources/actinia_modules/add_enumeration.json b/tests/resources/actinia_modules/add_enumeration.json index 3f8e75b..20c8a29 100644 --- a/tests/resources/actinia_modules/add_enumeration.json +++ b/tests/resources/actinia_modules/add_enumeration.json @@ -12,5 +12,6 @@ "type": "array" } }], + "projects": [], "returns": [] } diff --git a/tests/resources/actinia_modules/default_value.json b/tests/resources/actinia_modules/default_value.json index 83e1a95..2f1b578 100644 --- a/tests/resources/actinia_modules/default_value.json +++ b/tests/resources/actinia_modules/default_value.json @@ -10,12 +10,13 @@ "type": "string" } }, { - "description": "Expression to evaluate. [generated from r.mapcalc_expression] - output = r.mapcalc result, string; value = raster value (default=0.428), float", + "description": "Expression to evaluate. [generated from r.mapcalc_expression] - output = r.mapcalc result, string; value = raster value (default=0.428), float; Default value exist in the process chain.", "name": "value", "optional": true, "schema": { "type": "string" } }], + "projects": [], "returns": [] } diff --git a/tests/resources/actinia_modules/index_NDVI.json b/tests/resources/actinia_modules/index_NDVI.json index 4aaa217..4ed8164 100644 --- a/tests/resources/actinia_modules/index_NDVI.json +++ b/tests/resources/actinia_modules/index_NDVI.json @@ -24,5 +24,6 @@ "type": "string" } }], + "projects": [], "returns": [] } diff --git a/tests/resources/actinia_modules/loop.json b/tests/resources/actinia_modules/loop.json new file mode 100644 index 0000000..160393d --- /dev/null +++ b/tests/resources/actinia_modules/loop.json @@ -0,0 +1,11 @@ +{ + "categories": [ + "actinia-module", + "global-template" + ], + "description": "Example loop with multiple parameters", + "id": "loop", + "parameters": [], + "projects": [], + "returns": [] +} diff --git a/tests/resources/actinia_modules/loop_simple.json b/tests/resources/actinia_modules/loop_simple.json new file mode 100644 index 0000000..c519098 --- /dev/null +++ b/tests/resources/actinia_modules/loop_simple.json @@ -0,0 +1,11 @@ +{ + "categories": [ + "actinia-module", + "global-template" + ], + "description": "Simple loop", + "id": "loop_simple", + "parameters": [], + "projects": [], + "returns": [] +} diff --git a/tests/resources/actinia_modules/nested_modules_test.json b/tests/resources/actinia_modules/nested_modules_test.json index 7512fd4..ae0c722 100644 --- a/tests/resources/actinia_modules/nested_modules_test.json +++ b/tests/resources/actinia_modules/nested_modules_test.json @@ -27,21 +27,20 @@ "type": "array" } }, { - "description": "Expression to evaluate. [generated from r.mapcalc_expression]", - "name": "B04_mosaic", + "description": "The input source that may be a landsat scene name, a sentinel2 scene name, a postGIS database string, or an URL that points to an accessible raster or vector file [generated from import_descr_source]", + "name": "url_to_geojson_point", "optional": true, "schema": { "type": "string" } }, { - "description": "The input source that may be a landsat scene name, a sentinel2 scene name, a postGIS database string, or an URL that points to an accessible raster or vector file [generated from import_descr_source]", - "name": "url_to_geojson_point", + "description": "Expression to evaluate. [generated from r.mapcalc_expression]", + "name": "B04_mosaic", "optional": true, "schema": { "type": "string" } - }], - "returns": [{ + }, { "description": "Name for output slope raster map. [generated from r.slope.aspect_slope]", "name": "output_slope_name", "optional": true, @@ -49,5 +48,7 @@ "subtype": "cell", "type": "string" } - }] + }], + "projects": [], + "returns": [] } diff --git a/tests/resources/actinia_modules/nested_modules_test_with_export.json b/tests/resources/actinia_modules/nested_modules_test_with_export.json new file mode 100644 index 0000000..d41629f --- /dev/null +++ b/tests/resources/actinia_modules/nested_modules_test_with_export.json @@ -0,0 +1,89 @@ +{ + "categories": [ + "actinia-module", + "global-template" + ], + "description": "PC makes no sense but includes a lot of cases for testing self-description", + "id": "nested_modules_test", + "parameters": [ + { + "description": "Name of OGR datasource to be imported. [generated from v.import_input]", + "name": "db_connection", + "optional": false, + "schema": { + "subtype": "datasource", + "type": "string" + } + }, + { + "description": "OGR layer name. If not given, all available layers are imported. [generated from v.import_layer]", + "name": "layer", + "optional": true, + "schema": { + "subtype": "datasource_layer", + "type": "array" + } + }, + { + "description": "Name of raster map(s). [generated from r.colors_map]", + "name": "output", + "optional": true, + "schema": { + "subtype": "cell", + "type": "array" + } + }, + { + "description": "The input source that may be a landsat scene name, a sentinel2 scene name, a postGIS database string, or an URL that points to an accessible raster or vector file [generated from import_descr_source]", + "name": "url_to_geojson_point", + "optional": true, + "schema": { + "type": "string" + } + }, + { + "description": "Expression to evaluate. [generated from r.mapcalc_expression]", + "name": "B04_mosaic", + "optional": true, + "schema": { + "type": "string" + } + }, + { + "description": "Name for output slope raster map. [generated from r.slope.aspect_slope]", + "name": "output_slope_name", + "optional": true, + "schema": { + "subtype": "cell", + "type": "string" + } + } + ], + "projects": [], + "returns": [ + { + "description": "Exported result from exporter r.slope.aspect aspect parameter", + "name": "{{ name_of_output_aspect }}", + "schema": { + "subtype": "GTiff", + "type": "raster" + } + }, + { + "description": "Exported result from exporter exporter raster parameter", + "name": "{{ name_of_output_slope }}", + "schema": { + "subtype": "GTiff", + "type": "raster" + } + }, + { + "description": "Exported result from exporter exporter map parameter", + "name": "point", + "schema": { + "subtype": "GeoJSON", + "type": "vector" + } + } + ] +} diff --git a/tests/resources/actinia_modules/point_in_polygon_with_export.json b/tests/resources/actinia_modules/point_in_polygon_with_export.json new file mode 100644 index 0000000..0b53d21 --- /dev/null +++ b/tests/resources/actinia_modules/point_in_polygon_with_export.json @@ -0,0 +1,31 @@ +{ + "categories": [ + "actinia-module", + "global-template" + ], + "description": "Imports point and polygon and checks if point is in polygon.", + "id": "point_in_polygon", + "parameters": [ + { + "description": "The input source that may be a landsat scene name, a sentinel2 scene name, a postGIS database string, or an URL that points to an accessible raster or vector file [generated from import_descr_source]", + "name": "url_to_geojson_point", + "optional": true, + "schema": { + "type": "string" + } + } + ], + "projects": [ + "nc_spm_08" + ], + "returns": [ + { + "description": "Exported result from exporter exporter map parameter", + "name": "point", + "schema": { + "subtype": "GeoJSON", + "type": "vector" + } + } + ] +} diff --git a/tests/resources/actinia_modules/r.slope.aspect.json b/tests/resources/actinia_modules/r.slope.aspect.json index 4fe776c..d056c6a 100644 --- a/tests/resources/actinia_modules/r.slope.aspect.json +++ b/tests/resources/actinia_modules/r.slope.aspect.json @@ -1,5 +1,5 @@ { - "categories": ["aspect", "curvature", "grass-module", "raster", "slope", "terrain"], + "categories": ["aspect", "curvature", "grass-module", "parallel", "raster", "slope", "terrain"], "description": "Aspect is calculated counterclockwise from east.", "id": "r.slope.aspect", "parameters": [{ @@ -44,10 +44,27 @@ "schema": { "type": "number" } + }, { + "default": "0", + "description": "Number of threads for parallel computing. 0: use OpenMP default; >0: use nprocs; <0: use MAX-nprocs. ", + "name": "nprocs", + "optional": true, + "schema": { + "type": "integer" + } + }, { + "default": "300", + "description": "Maximum memory to be used (in MB). Cache size for raster rows. ", + "name": "memory", + "optional": true, + "schema": { + "type": "integer" + } }, { "default": "False", "description": "Do not align the current region to the raster elevation map. ", "name": "a", + "optional": true, "schema": { "type": "boolean" } @@ -55,6 +72,7 @@ "default": "False", "description": "Compute output at edges and near NULL values. ", "name": "e", + "optional": true, "schema": { "type": "boolean" } @@ -62,6 +80,7 @@ "default": "False", "description": "Create aspect as degrees clockwise from North (azimuth), with flat = -9999. Default: degrees counter-clockwise from East, with flat = 0. ", "name": "n", + "optional": true, "schema": { "type": "boolean" } @@ -69,6 +88,7 @@ "default": "False", "description": "Allow output files to overwrite existing files. ", "name": "overwrite", + "optional": true, "schema": { "type": "boolean" } @@ -76,6 +96,7 @@ "default": "False", "description": "Print usage summary. ", "name": "help", + "optional": true, "schema": { "type": "boolean" } @@ -83,6 +104,7 @@ "default": "False", "description": "Verbose module output. ", "name": "verbose", + "optional": true, "schema": { "type": "boolean" } @@ -90,6 +112,7 @@ "default": "False", "description": "Quiet module output. ", "name": "quiet", + "optional": true, "schema": { "type": "boolean" } diff --git a/tests/resources/actinia_modules/slope_aspect.json b/tests/resources/actinia_modules/slope_aspect.json index 768621f..8c1f578 100644 --- a/tests/resources/actinia_modules/slope_aspect.json +++ b/tests/resources/actinia_modules/slope_aspect.json @@ -2,6 +2,7 @@ "categories": ["actinia-module", "global-template"], "description": "Calculates slope and aspect of elevation map.", "id": "slope_aspect", + "projects": [], "parameters": [{ "default": "FCELL", "description": "Type of output aspect and slope maps. Storage type for resultant raster map. [generated from r.slope.aspect_precision]", @@ -11,8 +12,7 @@ "enum": ["CELL", "FCELL", "DCELL"], "type": "string" } - }], - "returns": [{ + }, { "description": "Name for output slope raster map. [generated from r.slope.aspect_slope]", "name": "name_of_output_slope", "optional": true, @@ -28,5 +28,6 @@ "subtype": "cell", "type": "string" } - }] -} + }], + "returns": [] + } diff --git a/tests/resources/actinia_modules/slope_aspect_with_export.json b/tests/resources/actinia_modules/slope_aspect_with_export.json new file mode 100644 index 0000000..b09e098 --- /dev/null +++ b/tests/resources/actinia_modules/slope_aspect_with_export.json @@ -0,0 +1,47 @@ +{ + "categories": ["actinia-module", "global-template"], + "description": "Calculates slope and aspect of elevation map.", + "id": "slope_aspect", + "projects": [], + "parameters": [{ + "default": "FCELL", + "description": "Type of output aspect and slope maps. Storage type for resultant raster map. [generated from r.slope.aspect_precision]", + "name": "datatype", + "optional": true, + "schema": { + "enum": ["CELL", "FCELL", "DCELL"], + "type": "string" + } + }, { + "description": "Name for output slope raster map. [generated from r.slope.aspect_slope]", + "name": "name_of_output_slope", + "optional": true, + "schema": { + "subtype": "cell", + "type": "string" + } + }, { + "description": "Name for output aspect raster map. [generated from r.slope.aspect_aspect]", + "name": "name_of_output_aspect", + "optional": true, + "schema": { + "subtype": "cell", + "type": "string" + } + }], + "returns": [{ + "description": "Exported result from exporter r.slope.aspect aspect parameter", + "name": "{{ name_of_output_aspect }}", + "schema": { + "subtype": "GTiff", + "type": "raster" + } + }, { + "description": "Exported result from exporter exporter raster parameter", + "name": "{{ name_of_output_slope }}", + "schema": { + "subtype": "GTiff", + "type": "raster" + } + }] +} diff --git a/tests/resources/actinia_modules/vector_area.json b/tests/resources/actinia_modules/vector_area.json index 58e86fc..548d5b6 100644 --- a/tests/resources/actinia_modules/vector_area.json +++ b/tests/resources/actinia_modules/vector_area.json @@ -42,5 +42,6 @@ "type": "array" } }], + "projects": [], "returns": [] } diff --git a/tests/test_modules.py b/tests/test_modules.py index cdf101c..15e7d1b 100644 --- a/tests/test_modules.py +++ b/tests/test_modules.py @@ -13,6 +13,7 @@ __copyright__ = "Copyright 2021, mundialis" +import types from flask import Response from actinia_api import URL_PREFIX @@ -32,6 +33,11 @@ "loop", ] someVirtualModules = GrassModules + someActiniaModules +someActiniaModulesWithExport = [ + "point_in_polygon", + "slope_aspect", + # "nested_modules_test", +] class VirtualModulesTest(ActiniaTestCase): @@ -215,5 +221,36 @@ def test_filter_list_modules_get_restricted_user_2(self): compare_module_to_file.__defaults__ = ( "modules", i, + None, + ) + new_func = types.FunctionType( + compare_module_to_file.__code__, + compare_module_to_file.__globals__, + name=def_name, + argdefs=compare_module_to_file.__defaults__, + closure=compare_module_to_file.__closure__, + ) + setattr(VirtualModulesTest, def_name, new_func) + + +for i in someActiniaModulesWithExport: + """ + Test HTTP GET /modules/ with HTTP GET parameter + """ + # create method for every actinia-module to have a better overview in + # test summary + def_name = "test_describe_process_chain_template_get_" + i + "_with_export" + compare_module_to_file.__defaults__ = ( + "modules", + i, + "export", + ) + + new_func = types.FunctionType( + compare_module_to_file.__code__, + compare_module_to_file.__globals__, + name=def_name, + argdefs=compare_module_to_file.__defaults__, + closure=compare_module_to_file.__closure__, ) - setattr(VirtualModulesTest, def_name, compare_module_to_file) + setattr(VirtualModulesTest, def_name, new_func) diff --git a/tests/test_modules_actinia_global.py b/tests/test_modules_actinia_global.py index 67edd18..c2e1aa4 100644 --- a/tests/test_modules_actinia_global.py +++ b/tests/test_modules_actinia_global.py @@ -13,13 +13,13 @@ __copyright__ = "Copyright 2021, mundialis" +import types from flask import Response from actinia_api import URL_PREFIX from testsuite import ActiniaTestCase, compare_module_to_file - someActiniaModules = [ "add_enumeration", "default_value", @@ -32,8 +32,15 @@ "loop", ] +someActiniaModulesWithExport = [ + "point_in_polygon", + "slope_aspect", + # "nested_modules_test", +] + class ActiniaModulesTest(ActiniaTestCase): + def test_list_modules_get(self): """Test HTTP GET /actinia_modules""" global someActiniaModules @@ -85,5 +92,37 @@ def test_describe_modules_not_found(self): compare_module_to_file.__defaults__ = ( "actinia_modules", i, + None, + ) + + new_func = types.FunctionType( + compare_module_to_file.__code__, + compare_module_to_file.__globals__, + name=def_name, + argdefs=compare_module_to_file.__defaults__, + closure=compare_module_to_file.__closure__, + ) + setattr(ActiniaModulesTest, def_name, new_func) + + +for i in someActiniaModulesWithExport: + """ + Test HTTP GET /actinia_modules/ with HTTP GET parameter + """ + # create method for every actinia-module to have a better overview in + # test summary + def_name = "test_describe_process_chain_template_get_" + i + "_with_export" + compare_module_to_file.__defaults__ = ( + "actinia_modules", + i, + "export", + ) + + new_func = types.FunctionType( + compare_module_to_file.__code__, + compare_module_to_file.__globals__, + name=def_name, + argdefs=compare_module_to_file.__defaults__, + closure=compare_module_to_file.__closure__, ) - setattr(ActiniaModulesTest, def_name, compare_module_to_file) + setattr(ActiniaModulesTest, def_name, new_func) diff --git a/tests/test_modules_grass.py b/tests/test_modules_grass.py index 08625f1..52d8c85 100644 --- a/tests/test_modules_grass.py +++ b/tests/test_modules_grass.py @@ -14,7 +14,7 @@ # import unittest - +import types from flask import Response from actinia_api import URL_PREFIX @@ -23,7 +23,6 @@ from testsuite import ActiniaTestCase, compare_module_to_file - someGrassModules = ["r.slope.aspect", "importer", "exporter"] @@ -155,5 +154,13 @@ def test_filter_list_modules_get_admin_3(self): compare_module_to_file.__defaults__ = ( "grass_modules", i, + None, + ) + new_func = types.FunctionType( + compare_module_to_file.__code__, + compare_module_to_file.__globals__, + name=def_name, + argdefs=compare_module_to_file.__defaults__, + closure=compare_module_to_file.__closure__, ) - setattr(GmodulesTest, def_name, compare_module_to_file) + setattr(GmodulesTest, def_name, new_func) diff --git a/tests/test_processing_global.py b/tests/test_processing_global.py index 1735867..00ce207 100644 --- a/tests/test_processing_global.py +++ b/tests/test_processing_global.py @@ -19,10 +19,8 @@ from actinia_api import URL_PREFIX - from testsuite import ActiniaTestCase, check_started_process - global allTemplatesCount global templateUUID diff --git a/tests/test_processing_user.py b/tests/test_processing_user.py index aac0cf0..f5457c4 100644 --- a/tests/test_processing_user.py +++ b/tests/test_processing_user.py @@ -25,7 +25,6 @@ check_started_process, ) - global allTemplatesCount global templateUUID diff --git a/tests/test_templates.py b/tests/test_templates.py index f39c090..9c81184 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -21,7 +21,6 @@ from testsuite import ActiniaTestCase - global allTemplatesCount global templateUUID diff --git a/tests/testsuite.py b/tests/testsuite.py index 54a19c2..ef830b9 100644 --- a/tests/testsuite.py +++ b/tests/testsuite.py @@ -187,20 +187,33 @@ def createUser( # import unittest # @unittest.skip("compare response to file") -def compare_module_to_file(self, uri_path="modules", module=None): +def compare_module_to_file( + self, + uri_path="modules", + module=None, + returns=None, +): """Compares response of API call to file""" # Won't run with module=None but ensures, that "passing of arguments" # below is successful. + url = URL_PREFIX + "/" + uri_path + "/" + module + if returns == "export": + url += "?returns=export" + resp = self.app.get( - URL_PREFIX + "/" + uri_path + "/" + module, + url, headers=self.user_auth_header, ) respStatusCode = 200 assert hasattr(resp, "json") currentResp = resp.json - with open("tests/resources/actinia_modules/" + module + ".json") as file: + filename = module + if returns == "export": + filename += "_with_export" + + with open("tests/resources/actinia_modules/" + filename + ".json") as file: expectedResp = json.load(file) assert resp.status_code == respStatusCode