From 46dad7c6dc54fc237e5ad6a08942762dde9a8dfd Mon Sep 17 00:00:00 2001 From: ninsbl Date: Thu, 27 Apr 2023 00:11:56 +0200 Subject: [PATCH 01/19] add get_json_dict --- .../grass/pygrass/modules/interface/module.py | 101 +++++++++++++----- 1 file changed, 77 insertions(+), 24 deletions(-) diff --git a/python/grass/pygrass/modules/interface/module.py b/python/grass/pygrass/modules/interface/module.py index 3fd215aab7f..630024b4ac1 100644 --- a/python/grass/pygrass/modules/interface/module.py +++ b/python/grass/pygrass/modules/interface/module.py @@ -1,15 +1,7 @@ -from __future__ import ( - nested_scopes, - generators, - division, - absolute_import, - with_statement, - print_function, - unicode_literals, -) -import sys -from multiprocessing import cpu_count, Process, Queue import time + +from multiprocessing import cpu_count, Process, Queue +from itertools import zip_longest from xml.etree.ElementTree import fromstring from grass.exceptions import CalledModuleError, GrassError, ParameterError @@ -22,13 +14,6 @@ from .read import GETFROMTAG, DOC from .env import G_debug -if sys.version_info[0] == 2: - from itertools import izip_longest as zip_longest -else: - from itertools import zip_longest - - unicode = str - def _get_bash(self, *args, **kargs): return self.get_bash() @@ -528,9 +513,7 @@ def f(*args, **kargs): """ def __init__(self, cmd, *args, **kargs): - if isinstance(cmd, unicode): - self.name = str(cmd) - elif isinstance(cmd, str): + if isinstance(cmd, str): self.name = cmd else: raise GrassError("Problem initializing the module {s}".format(s=cmd)) @@ -539,8 +522,9 @@ def __init__(self, cmd, *args, **kargs): get_cmd_xml = Popen([cmd, "--interface-description"], stdout=PIPE) except OSError as e: print("OSError error({0}): {1}".format(e.errno, e.strerror)) - str_err = "Error running: `%s --interface-description`." - raise GrassError(str_err % self.name) + raise GrassError( + "Error running: `{} --interface-description`.".format(self.name) + ) # get the xml of the module self.xml = get_cmd_xml.communicate()[0] # transform and parse the xml into an Element class: @@ -675,7 +659,7 @@ def update(self, *args, **kargs): # verbose and quiet) work like parameters self.flags[key].value = val else: - raise ParameterError("%s is not a valid parameter." % key) + raise ParameterError("{} is not a valid parameter.".format(key)) def get_bash(self): """Return a BASH representation of the Module.""" @@ -755,6 +739,75 @@ def check(self): msg = "Required parameter <%s> not set." raise ParameterError(msg % k) + def get_json_dict(self, export=None): + """Return a dictionary that includes the name, all valid + inputs, outputs and flags as well as export settings for + usage with actinia + "export": {"format": "GTiff", "type": "raster"} + """ + import uuid + + export_dict = { + "GTiff": "raster", + "COG": "raster", + "strds": "GTiff", # (multiple files packed in an tar.gz archive) + "PostgreSQL": "vector", + "GPKG": "vector", + "GML": "vector", + "GeoJSON": "vector", + "ESRI_Shapefile": "vector", + "SQLite": "vector", + "CSV": ["vector", "file"], + "TXT": "file", + } + if export and not all( + [export_format in export_dict for export_format in export.values()] + ): + raise GrassError("Invalid Export format.") + json_dict = { + "module": self.name, + "id": f"{self.name.replace('.', '_')}_{uuid.uuid4().hex}", + "verbose": self.flags["verbose"].value, + "overwrite": self.flags["overwrite"].value, + "flags": "".join( + [ + flg + for flg in self.flags + if self.flags[flg].value + and flg not in ["overwrite", "verbose", "quiet", "help"] + ] + ), + "inputs": [ + { + "param": key, + "value": ",".join(val.value) + if type(val.value) == list + else str(val.value), + } + for key, val in self.inputs.items() + if val.value + ], + } + outputs = [] + for key, val in self.outputs.items(): + if val.value: + param = { + "param": key, + "value": ",".join(val.value) + if type(val.value) == list + else str(val.value), + } + if export and key in export.keys(): + print("export") + param["export"] = { + "format": export[key], + "type": export_dict[export[key]], + } + outputs.append(param) + json_dict["outputs"] = outputs + + return {key: val for key, val in json_dict.items() if val} + def get_dict(self): """Return a dictionary that includes the name, all valid inputs, outputs and flags From 70159851aa6911537de470481719578909e518e3 Mon Sep 17 00:00:00 2001 From: ninsbl Date: Thu, 27 Apr 2023 00:12:21 +0200 Subject: [PATCH 02/19] add test for get_json_dict --- .../modules/interface/testsuite/test_modules.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/python/grass/pygrass/modules/interface/testsuite/test_modules.py b/python/grass/pygrass/modules/interface/testsuite/test_modules.py index 41dc27a88b8..bfa65bcbcbc 100644 --- a/python/grass/pygrass/modules/interface/testsuite/test_modules.py +++ b/python/grass/pygrass/modules/interface/testsuite/test_modules.py @@ -5,6 +5,8 @@ """ import sys from fnmatch import fnmatch +from io import BytesIO as StringIO + from grass.gunittest.case import TestCase from grass.gunittest.main import test @@ -12,12 +14,6 @@ from grass.exceptions import ParameterError from grass.pygrass.modules.interface import Module -PY2 = sys.version_info[0] == 2 -if PY2: - from StringIO import StringIO -else: - from io import BytesIO as StringIO - SKIP = [ "g.parser", @@ -71,6 +67,13 @@ def test_rsun(self): out.close() +class TestModulesJsonDictExport(TestCase): + def test_rsun(self): + """Test if a Module can be exported to json dict""" + + Module("r.info", map="elevation", run_=False).get_json_dict() + + class TestModulesCheck(TestCase): def test_flags_with_suppress_required(self): """Test if flags with suppress required are handle correctly""" From d8933b6a94257b6d7d4b8aa01e9cea4616e1accd Mon Sep 17 00:00:00 2001 From: ninsbl Date: Sun, 7 May 2023 18:12:15 +0200 Subject: [PATCH 03/19] handle export --- .../grass/pygrass/modules/interface/module.py | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/python/grass/pygrass/modules/interface/module.py b/python/grass/pygrass/modules/interface/module.py index 630024b4ac1..9a8b873acb4 100644 --- a/python/grass/pygrass/modules/interface/module.py +++ b/python/grass/pygrass/modules/interface/module.py @@ -743,7 +743,7 @@ def get_json_dict(self, export=None): """Return a dictionary that includes the name, all valid inputs, outputs and flags as well as export settings for usage with actinia - "export": {"format": "GTiff", "type": "raster"} + param export: string with export format, e.g. GTiff """ import uuid @@ -757,24 +757,26 @@ def get_json_dict(self, export=None): "GeoJSON": "vector", "ESRI_Shapefile": "vector", "SQLite": "vector", - "CSV": ["vector", "file"], + "CSV": "file", "TXT": "file", } - if export and not all( - [export_format in export_dict for export_format in export.values()] - ): + special_flags = ["overwrite", "verbose", "quiet"] + skip = ["stdin", "stdout", "stderr"] + + # Check export formats + if export and export not in export_dict: raise GrassError("Invalid Export format.") + + # Handle inputs and flags json_dict = { "module": self.name, "id": f"{self.name.replace('.', '_')}_{uuid.uuid4().hex}", - "verbose": self.flags["verbose"].value, "overwrite": self.flags["overwrite"].value, "flags": "".join( [ flg for flg in self.flags - if self.flags[flg].value - and flg not in ["overwrite", "verbose", "quiet", "help"] + if self.flags[flg].value and flg not in special_flags + ["help"] ] ), "inputs": [ @@ -785,9 +787,16 @@ def get_json_dict(self, export=None): else str(val.value), } for key, val in self.inputs.items() - if val.value + if val.value and key not in skip ], } + + # Handle special flags + for special_flag in special_flags: + if special_flag in self.flags: + json_dict[special_flag] = self.flags[special_flag].value + + # Handle outputs outputs = [] for key, val in self.outputs.items(): if val.value: @@ -797,11 +806,10 @@ def get_json_dict(self, export=None): if type(val.value) == list else str(val.value), } - if export and key in export.keys(): - print("export") + if export: param["export"] = { - "format": export[key], - "type": export_dict[export[key]], + "format": export, + "type": export_dict[export], } outputs.append(param) json_dict["outputs"] = outputs From b62eb04e099f26df0240438ea6074becbe2e2c8a Mon Sep 17 00:00:00 2001 From: ninsbl Date: Sun, 7 May 2023 18:13:12 +0200 Subject: [PATCH 04/19] add more tests --- .../interface/testsuite/test_modules.py | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/python/grass/pygrass/modules/interface/testsuite/test_modules.py b/python/grass/pygrass/modules/interface/testsuite/test_modules.py index bfa65bcbcbc..2e9ddffcc27 100644 --- a/python/grass/pygrass/modules/interface/testsuite/test_modules.py +++ b/python/grass/pygrass/modules/interface/testsuite/test_modules.py @@ -68,11 +68,48 @@ def test_rsun(self): class TestModulesJsonDictExport(TestCase): - def test_rsun(self): + def test_rinfo_simple(self): """Test if a Module can be exported to json dict""" Module("r.info", map="elevation", run_=False).get_json_dict() + def test_rinfo_ov(self): + """Test if a Module can be exported to json dict with overwite + and verbose flags""" + + Module( + "r.info", overwite=True, verbose=True, map="elevation", run_=False + ).get_json_dict() + + def test_rinfo_ov_export(self): + """Test if a Module can be exported to json dict with overwite + and verbose flags and results exported to CSV""" + + Module( + "r.info", + overwite=True, + verbose=True, + map="elevation", + flags="g", + run_=False, + ).get_json_dict(export="CSV") + + def test_rslopeaspect_ov_export(self): + """Test if a Module can be exported to json dict with overwite + and verbose flags and results exported to CSV""" + + Module( + "r.slope.aspect", + elevation="elevation", + slope="slope", + aspect="aspect", + overwite=True, + verbose=True, + map="elevation", + flags="g", + run_=False, + ).get_json_dict(export="GTiff") + class TestModulesCheck(TestCase): def test_flags_with_suppress_required(self): From 26d26843d8691dd46111ad651df43df15b5bcd6e Mon Sep 17 00:00:00 2001 From: ninsbl Date: Sun, 7 May 2023 21:56:20 +0200 Subject: [PATCH 05/19] fix typo --- .../modules/interface/testsuite/test_modules.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/python/grass/pygrass/modules/interface/testsuite/test_modules.py b/python/grass/pygrass/modules/interface/testsuite/test_modules.py index 2e9ddffcc27..372c42155a8 100644 --- a/python/grass/pygrass/modules/interface/testsuite/test_modules.py +++ b/python/grass/pygrass/modules/interface/testsuite/test_modules.py @@ -74,20 +74,20 @@ def test_rinfo_simple(self): Module("r.info", map="elevation", run_=False).get_json_dict() def test_rinfo_ov(self): - """Test if a Module can be exported to json dict with overwite + """Test if a Module can be exported to json dict with overwrite and verbose flags""" Module( - "r.info", overwite=True, verbose=True, map="elevation", run_=False + "r.info", overwrite=True, verbose=True, map="elevation", run_=False ).get_json_dict() def test_rinfo_ov_export(self): - """Test if a Module can be exported to json dict with overwite + """Test if a Module can be exported to json dict with overwrite and verbose flags and results exported to CSV""" Module( "r.info", - overwite=True, + overwrite=True, verbose=True, map="elevation", flags="g", @@ -95,7 +95,7 @@ def test_rinfo_ov_export(self): ).get_json_dict(export="CSV") def test_rslopeaspect_ov_export(self): - """Test if a Module can be exported to json dict with overwite + """Test if a Module can be exported to json dict with overwrite and verbose flags and results exported to CSV""" Module( @@ -103,7 +103,7 @@ def test_rslopeaspect_ov_export(self): elevation="elevation", slope="slope", aspect="aspect", - overwite=True, + overwrite=True, verbose=True, map="elevation", flags="g", From 561353c6e14def7ffdec75eb7a93cbf9601d2907 Mon Sep 17 00:00:00 2001 From: ninsbl Date: Wed, 10 May 2023 22:48:23 +0200 Subject: [PATCH 06/19] fix some tests --- .../pygrass/modules/interface/testsuite/test_modules.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/python/grass/pygrass/modules/interface/testsuite/test_modules.py b/python/grass/pygrass/modules/interface/testsuite/test_modules.py index 372c42155a8..daf312eec00 100644 --- a/python/grass/pygrass/modules/interface/testsuite/test_modules.py +++ b/python/grass/pygrass/modules/interface/testsuite/test_modules.py @@ -74,12 +74,9 @@ def test_rinfo_simple(self): Module("r.info", map="elevation", run_=False).get_json_dict() def test_rinfo_ov(self): - """Test if a Module can be exported to json dict with overwrite - and verbose flags""" + """Test if a Module can be exported to json dict with verbose flag""" - Module( - "r.info", overwrite=True, verbose=True, map="elevation", run_=False - ).get_json_dict() + Module("r.info", verbose=True, map="elevation", run_=False).get_json_dict() def test_rinfo_ov_export(self): """Test if a Module can be exported to json dict with overwrite @@ -87,7 +84,6 @@ def test_rinfo_ov_export(self): Module( "r.info", - overwrite=True, verbose=True, map="elevation", flags="g", @@ -106,7 +102,6 @@ def test_rslopeaspect_ov_export(self): overwrite=True, verbose=True, map="elevation", - flags="g", run_=False, ).get_json_dict(export="GTiff") From 17ff69fb86e27772e630f5bed4deb95509c52189 Mon Sep 17 00:00:00 2001 From: ninsbl Date: Wed, 10 May 2023 23:12:07 +0200 Subject: [PATCH 07/19] fix tests --- python/grass/pygrass/modules/interface/testsuite/test_modules.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/grass/pygrass/modules/interface/testsuite/test_modules.py b/python/grass/pygrass/modules/interface/testsuite/test_modules.py index daf312eec00..598f223467f 100644 --- a/python/grass/pygrass/modules/interface/testsuite/test_modules.py +++ b/python/grass/pygrass/modules/interface/testsuite/test_modules.py @@ -101,7 +101,6 @@ def test_rslopeaspect_ov_export(self): aspect="aspect", overwrite=True, verbose=True, - map="elevation", run_=False, ).get_json_dict(export="GTiff") From 949e84d8f03b26cbc8fe9071e16884a1c8193980 Mon Sep 17 00:00:00 2001 From: ninsbl Date: Wed, 10 May 2023 23:12:27 +0200 Subject: [PATCH 08/19] fix tests --- python/grass/pygrass/modules/interface/module.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/grass/pygrass/modules/interface/module.py b/python/grass/pygrass/modules/interface/module.py index 9a8b873acb4..849b45082bf 100644 --- a/python/grass/pygrass/modules/interface/module.py +++ b/python/grass/pygrass/modules/interface/module.py @@ -771,7 +771,6 @@ def get_json_dict(self, export=None): json_dict = { "module": self.name, "id": f"{self.name.replace('.', '_')}_{uuid.uuid4().hex}", - "overwrite": self.flags["overwrite"].value, "flags": "".join( [ flg From e4af98859c1dfff2f45a5fcaba14708405fd9c21 Mon Sep 17 00:00:00 2001 From: ninsbl Date: Thu, 11 May 2023 23:11:09 +0200 Subject: [PATCH 09/19] use f-strings --- python/grass/pygrass/modules/interface/module.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/python/grass/pygrass/modules/interface/module.py b/python/grass/pygrass/modules/interface/module.py index 849b45082bf..b1f859f3dab 100644 --- a/python/grass/pygrass/modules/interface/module.py +++ b/python/grass/pygrass/modules/interface/module.py @@ -521,10 +521,8 @@ def __init__(self, cmd, *args, **kargs): # call the command with --interface-description get_cmd_xml = Popen([cmd, "--interface-description"], stdout=PIPE) except OSError as e: - print("OSError error({0}): {1}".format(e.errno, e.strerror)) - raise GrassError( - "Error running: `{} --interface-description`.".format(self.name) - ) + print(f"OSError error({e.errno}): {e.strerror}") + raise GrassError(f"Error running: `{self.name} --interface-description`.") # get the xml of the module self.xml = get_cmd_xml.communicate()[0] # transform and parse the xml into an Element class: @@ -765,7 +763,7 @@ def get_json_dict(self, export=None): # Check export formats if export and export not in export_dict: - raise GrassError("Invalid Export format.") + raise GrassError(f"Invalid export format <{export}>.") # Handle inputs and flags json_dict = { From 63bf42e56d6d3a7e139c3d23839a51c78e8caedb Mon Sep 17 00:00:00 2001 From: Stefan Blumentrath Date: Thu, 30 May 2024 13:11:00 +0200 Subject: [PATCH 10/19] Update python/grass/pygrass/modules/interface/module.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- python/grass/pygrass/modules/interface/module.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/python/grass/pygrass/modules/interface/module.py b/python/grass/pygrass/modules/interface/module.py index a504ca1a822..019d493ab02 100644 --- a/python/grass/pygrass/modules/interface/module.py +++ b/python/grass/pygrass/modules/interface/module.py @@ -813,9 +813,11 @@ def get_json_dict(self, export=None): "inputs": [ { "param": key, - "value": ",".join(val.value) - if type(val.value) == list - else str(val.value), + "value": ( + ",".join(val.value) + if type(val.value) == list + else str(val.value) + ), } for key, val in self.inputs.items() if val.value and key not in skip From 27f49fb174033aef0b36cea90a2f14ce2650da61 Mon Sep 17 00:00:00 2001 From: Stefan Blumentrath Date: Thu, 30 May 2024 13:11:10 +0200 Subject: [PATCH 11/19] Update python/grass/pygrass/modules/interface/module.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- python/grass/pygrass/modules/interface/module.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/python/grass/pygrass/modules/interface/module.py b/python/grass/pygrass/modules/interface/module.py index 019d493ab02..f1ebfc3f1f2 100644 --- a/python/grass/pygrass/modules/interface/module.py +++ b/python/grass/pygrass/modules/interface/module.py @@ -835,9 +835,11 @@ def get_json_dict(self, export=None): if val.value: param = { "param": key, - "value": ",".join(val.value) - if type(val.value) == list - else str(val.value), + "value": ( + ",".join(val.value) + if type(val.value) == list + else str(val.value) + ), } if export: param["export"] = { From fe3de57f821eabf8d4ae3e953860e1c47c209130 Mon Sep 17 00:00:00 2001 From: Stefan Blumentrath Date: Thu, 30 May 2024 13:18:58 +0200 Subject: [PATCH 12/19] flake8 --- python/grass/pygrass/modules/interface/module.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/python/grass/pygrass/modules/interface/module.py b/python/grass/pygrass/modules/interface/module.py index f1ebfc3f1f2..c55eabeeafa 100644 --- a/python/grass/pygrass/modules/interface/module.py +++ b/python/grass/pygrass/modules/interface/module.py @@ -14,8 +14,6 @@ from .read import GETFROMTAG, DOC from .env import G_debug -from itertools import zip_longest - def _get_bash(self, *args, **kargs): return self.get_bash() From 4eb76a0b098ff1db2f69180c051e200cbc799dc7 Mon Sep 17 00:00:00 2001 From: ninsbl Date: Fri, 31 May 2024 00:22:19 +0200 Subject: [PATCH 13/19] handle STDOUT --- .../grass/pygrass/modules/interface/module.py | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/python/grass/pygrass/modules/interface/module.py b/python/grass/pygrass/modules/interface/module.py index c55eabeeafa..3d02a580f6d 100644 --- a/python/grass/pygrass/modules/interface/module.py +++ b/python/grass/pygrass/modules/interface/module.py @@ -769,11 +769,23 @@ def check(self): msg = "Required parameter <%s> not set." raise ParameterError(msg % k) - def get_json_dict(self, export=None): + def get_json_dict( + self, export=None, stdout_export=None, stdout_id="stdout", stdout_delimiter="|" + ): """Return a dictionary that includes the name, all valid inputs, outputs and flags as well as export settings for usage with actinia - param export: string with export format, e.g. GTiff + param export: string with export format for non-stdout output, one of + "GTiff", "COG", for raster; + "strds" for SpaceTimeRasterDatasets, + "PostgreSQL", "GPKG", "GML", "GeoJSON", "ESRI_Shapefile", + "SQLite" for vector and + "CSV", "TXT" for files + param stdout_export: string with export format for stdout output, one of + "table", "list", "kv", "json" + param stdout_id: unique string with "id" for stdout output of the module + defaults to "stdout" + param stdout_delimiter: string with single delimiter, defaults to "|" """ import uuid @@ -790,6 +802,7 @@ def get_json_dict(self, export=None): "CSV": "file", "TXT": "file", } + stdout_export_formats = ["table", "list", "kv", "json"] special_flags = ["overwrite", "verbose", "quiet"] skip = ["stdin", "stdout", "stderr"] @@ -847,6 +860,16 @@ def get_json_dict(self, export=None): outputs.append(param) json_dict["outputs"] = outputs + # Handle stdout + if stdout_export is not None: + if stdout_export not in stdout_export_formats: + raise GrassError(f"Invalid export format <{stdout_export}> for stdout.") + json_dict["stdout"] = { + "id": stdout_id, + "format": stdout_export, + "delimiter": stdout_delimiter, + } + return {key: val for key, val in json_dict.items() if val} def get_dict(self): From 9239679b41ffbbc79834c8cd5b1bc575a82b01b4 Mon Sep 17 00:00:00 2001 From: ninsbl Date: Fri, 31 May 2024 00:23:16 +0200 Subject: [PATCH 14/19] add pytest --- .../pygrass_modules_interface_json_test.py | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 python/grass/pygrass/modules/interface/testsuite/pygrass_modules_interface_json_test.py diff --git a/python/grass/pygrass/modules/interface/testsuite/pygrass_modules_interface_json_test.py b/python/grass/pygrass/modules/interface/testsuite/pygrass_modules_interface_json_test.py new file mode 100644 index 00000000000..6281e31d933 --- /dev/null +++ b/python/grass/pygrass/modules/interface/testsuite/pygrass_modules_interface_json_test.py @@ -0,0 +1,120 @@ +############################################################################ +# +# MODULE: Test of JSON export from pygrass.Module for usage in actinia +# AUTHOR(S): Stefan Blumentrath +# PURPOSE: Test of JSON export from pygrass.Module for usage in actinia +# COPYRIGHT: (C) 2024 by Stefan Blumentrath and the GRASS Development Team +# +# This program is free software under the GNU General Public +# License (>=v2). Read the file COPYING that comes with GRASS +# for details. +# +############################################################################# + +"""Test of JSON export from pygrass.Module for usage in actinia """ + + +import pytest + +from subprocess import PIPE +from grass.pygrass.modules.interface import Module + + +def test_rinfo_simple(): + """Test if a Module can be exported to json dict""" + + mod_json_dict = Module("r.info", map="elevation", run_=False).get_json_dict() + + assert isinstance(mod_json_dict, dict) + assert set(mod_json_dict.keys()) == {"module", "id", "inputs"} + assert mod_json_dict["module"] == "r.info" + assert mod_json_dict["id"].startswith("r_info_") + assert "flags" not in mod_json_dict + assert set(mod_json_dict["inputs"][0].keys()) == {"param", "value"} + + +def test_rinfo_ov(): + """Test if a Module can be exported to json dict with quiet and overwrite flags""" + + mod_json_dict = Module( + "r.info", quiet=True, map="elevation", run_=False + ).get_json_dict() + + assert isinstance(mod_json_dict, dict) + assert set(mod_json_dict.keys()) == {"module", "id", "inputs", "quiet"} + assert mod_json_dict["module"] == "r.info" + assert mod_json_dict["id"].startswith("r_info_") + assert "flags" not in mod_json_dict + assert mod_json_dict["quiet"] is True + assert isinstance(mod_json_dict["inputs"], list) + assert set(mod_json_dict["inputs"][0].keys()) == {"param", "value"} + + +def test_rinfo_ov_export(): + """Test if a Module can be exported to json dict with overwrite + and verbose flags and results exported to CSV""" + + mod_json_dict = Module( + "r.info", + verbose=True, + map="elevation", + flags="g", + run_=False, + ).get_json_dict(stdout_export="list") + + assert isinstance(mod_json_dict, dict) + assert set(mod_json_dict.keys()) == { + "module", + "id", + "flags", + "inputs", + "verbose", + "stdout", + } + assert mod_json_dict["module"] == "r.info" + assert mod_json_dict["id"].startswith("r_info_") + assert mod_json_dict["flags"] == "g" + assert mod_json_dict["verbose"] is True + assert isinstance(mod_json_dict["inputs"], list) + assert set(mod_json_dict["inputs"][0].keys()) == {"param", "value"} + assert mod_json_dict["stdout"] == { + "id": "stdout", + "format": "list", + "delimiter": "|", + } + + +def test_rslopeaspect_ov_export(): + """Test if a Module can be exported to json dict with overwrite + and verbose flags and results exported to CSV""" + + mod_json_dict = Module( + "r.slope.aspect", + elevation="elevation", + slope="slope", + aspect="aspect", + overwrite=True, + verbose=True, + run_=False, + ).get_json_dict(export="GTiff") + + assert isinstance(mod_json_dict, dict) + assert set(mod_json_dict.keys()) == { + "module", + "id", + "inputs", + "outputs", + "overwrite", + "verbose", + } + assert mod_json_dict["module"] == "r.slope.aspect" + assert mod_json_dict["id"].startswith("r_slope_aspect_") + assert mod_json_dict["overwrite"] is True + assert mod_json_dict["verbose"] is True + assert isinstance(mod_json_dict["inputs"], list) + assert isinstance(mod_json_dict["outputs"], list) + assert set(mod_json_dict["outputs"][0].keys()) == {"param", "value", "export"} + assert mod_json_dict["outputs"][0]["export"] == { + "format": "GTiff", + "type": "raster", + } From 22527b0d8704832dfeb0177b7e1e23277bca3ee9 Mon Sep 17 00:00:00 2001 From: ninsbl Date: Fri, 31 May 2024 00:25:12 +0200 Subject: [PATCH 15/19] pylint --- .../interface/testsuite/pygrass_modules_interface_json_test.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/python/grass/pygrass/modules/interface/testsuite/pygrass_modules_interface_json_test.py b/python/grass/pygrass/modules/interface/testsuite/pygrass_modules_interface_json_test.py index 6281e31d933..94db20b018e 100644 --- a/python/grass/pygrass/modules/interface/testsuite/pygrass_modules_interface_json_test.py +++ b/python/grass/pygrass/modules/interface/testsuite/pygrass_modules_interface_json_test.py @@ -14,9 +14,6 @@ """Test of JSON export from pygrass.Module for usage in actinia """ -import pytest - -from subprocess import PIPE from grass.pygrass.modules.interface import Module From 47c3b2f520a43d348303b5575eac2785a83901f9 Mon Sep 17 00:00:00 2001 From: ninsbl Date: Fri, 31 May 2024 00:28:57 +0200 Subject: [PATCH 16/19] remove TestModulesJsonDictExport TestCase --- .../interface/testsuite/test_modules.py | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/python/grass/pygrass/modules/interface/testsuite/test_modules.py b/python/grass/pygrass/modules/interface/testsuite/test_modules.py index 52584d71849..3318154f87d 100644 --- a/python/grass/pygrass/modules/interface/testsuite/test_modules.py +++ b/python/grass/pygrass/modules/interface/testsuite/test_modules.py @@ -53,44 +53,6 @@ def test_rsun(self): out.close() -class TestModulesJsonDictExport(TestCase): - def test_rinfo_simple(self): - """Test if a Module can be exported to json dict""" - - Module("r.info", map="elevation", run_=False).get_json_dict() - - def test_rinfo_ov(self): - """Test if a Module can be exported to json dict with verbose flag""" - - Module("r.info", verbose=True, map="elevation", run_=False).get_json_dict() - - def test_rinfo_ov_export(self): - """Test if a Module can be exported to json dict with overwrite - and verbose flags and results exported to CSV""" - - Module( - "r.info", - verbose=True, - map="elevation", - flags="g", - run_=False, - ).get_json_dict(export="CSV") - - def test_rslopeaspect_ov_export(self): - """Test if a Module can be exported to json dict with overwrite - and verbose flags and results exported to CSV""" - - Module( - "r.slope.aspect", - elevation="elevation", - slope="slope", - aspect="aspect", - overwrite=True, - verbose=True, - run_=False, - ).get_json_dict(export="GTiff") - - class TestModulesCheck(TestCase): def test_flags_with_suppress_required(self): """Test if flags with suppress required are handle correctly""" From e4888e68f1c5ff634952d10084d7b94fc4ba0799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Sun, 26 Oct 2025 20:05:16 +0000 Subject: [PATCH 17/19] Fix extra space --- .../interface/testsuite/pygrass_modules_interface_json_test.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/grass/pygrass/modules/interface/testsuite/pygrass_modules_interface_json_test.py b/python/grass/pygrass/modules/interface/testsuite/pygrass_modules_interface_json_test.py index 94db20b018e..ecc47e29cf9 100644 --- a/python/grass/pygrass/modules/interface/testsuite/pygrass_modules_interface_json_test.py +++ b/python/grass/pygrass/modules/interface/testsuite/pygrass_modules_interface_json_test.py @@ -11,8 +11,7 @@ # ############################################################################# -"""Test of JSON export from pygrass.Module for usage in actinia """ - +"""Test of JSON export from pygrass.Module for usage in actinia""" from grass.pygrass.modules.interface import Module From 851b28f651680b218fa40a31ac67e06080ab7ef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Sun, 26 Oct 2025 20:07:26 +0000 Subject: [PATCH 18/19] Fix ruff rule EM102 --- python/grass/pygrass/modules/interface/module.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/python/grass/pygrass/modules/interface/module.py b/python/grass/pygrass/modules/interface/module.py index 8f13fcfb6c0..6be05dcefe9 100644 --- a/python/grass/pygrass/modules/interface/module.py +++ b/python/grass/pygrass/modules/interface/module.py @@ -698,7 +698,8 @@ def update(self, *args, **kargs): # verbose and quiet) work like parameters self.flags[key].value = val else: - raise ParameterError("{} is not a valid parameter.".format(key)) + msg = "{} is not a valid parameter.".format(key) + raise ParameterError(msg) def get_bash(self): """Return a BASH representation of the Module.""" @@ -816,7 +817,8 @@ def get_json_dict( # Check export formats if export and export not in export_dict: - raise GrassError(f"Invalid export format <{export}>.") + msg = f"Invalid export format <{export}>." + raise GrassError(msg) # Handle inputs and flags json_dict = { @@ -871,7 +873,8 @@ def get_json_dict( # Handle stdout if stdout_export is not None: if stdout_export not in stdout_export_formats: - raise GrassError(f"Invalid export format <{stdout_export}> for stdout.") + msg = f"Invalid export format <{stdout_export}> for stdout." + raise GrassError(msg) json_dict["stdout"] = { "id": stdout_id, "format": stdout_export, From d0feda9c098c0fd0348eaad1881b9550ceb0f351 Mon Sep 17 00:00:00 2001 From: ninsbl Date: Tue, 2 Dec 2025 23:09:53 +0100 Subject: [PATCH 19/19] ruff and better docstring --- .../grass/pygrass/modules/interface/module.py | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/python/grass/pygrass/modules/interface/module.py b/python/grass/pygrass/modules/interface/module.py index 6be05dcefe9..66a2aa80062 100644 --- a/python/grass/pygrass/modules/interface/module.py +++ b/python/grass/pygrass/modules/interface/module.py @@ -779,9 +779,15 @@ def check(self): raise ParameterError(msg % k) def get_json_dict( - self, export=None, stdout_export=None, stdout_id="stdout", stdout_delimiter="|" - ): - """Return a dictionary that includes the name, all valid + self, + export: str | None = None, + stdout_export: str | None = None, + stdout_id: str = "stdout", + stdout_delimiter: str = "|", + ) -> dict: + """Return a dictionary with the module represented in JSON format. + + The dictionary includes the module name, an id, all valid inputs, outputs and flags as well as export settings for usage with actinia param export: string with export format for non-stdout output, one of @@ -828,15 +834,15 @@ def get_json_dict( [ flg for flg in self.flags - if self.flags[flg].value and flg not in special_flags + ["help"] + if self.flags[flg].value and flg not in {*special_flags, "help"} ] ), "inputs": [ { "param": key, "value": ( - ",".join(val.value) - if type(val.value) == list + ",".join(map(str, val.value)) + if isinstance(val.value, list) else str(val.value) ), } @@ -857,8 +863,8 @@ def get_json_dict( param = { "param": key, "value": ( - ",".join(val.value) - if type(val.value) == list + ",".join(map(str, val.value)) + if isinstance(val.value, list) else str(val.value) ), }