Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 84 additions & 24 deletions python/grass/pygrass/modules/interface/module.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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))
Expand All @@ -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:
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -755,6 +739,82 @@ 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
param export: string with export format, e.g. GTiff
"""
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": "file",
"TXT": "file",
}
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}",
"flags": "".join(
[
flg
for flg in self.flags
if self.flags[flg].value and flg not in special_flags + ["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 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:
param = {
"param": key,
"value": ",".join(val.value)
if type(val.value) == list
else str(val.value),
}
if export:
param["export"] = {
"format": export,
"type": export_dict[export],
}
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
Expand Down
46 changes: 40 additions & 6 deletions python/grass/pygrass/modules/interface/testsuite/test_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,15 @@
"""
import sys
from fnmatch import fnmatch
from io import BytesIO as StringIO

from grass.gunittest.case import TestCase
from grass.gunittest.main import test

from grass.script.core import get_commands
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",
Expand Down Expand Up @@ -71,6 +67,44 @@ def test_rsun(self):
out.close()


class TestModulesJsonDictExport(TestCase):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using pytest for these test. No data needed. Assert functionality suited for for plain values.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And we can run them faster/in parallel! (I was exploring making pytest run for windows and macOS the other day, but still had problems making it find the pytest I installed on the windows CI)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test case is now removed and replaced with pytest in new file: pygrass_modules_interface_json_test.py which has assertations. Hope that is roughly along your lines of thought...

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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing against smoke tests in general, but these tests are unnecessary simple. Please add assertions to move it beyond smoke tests. It does not really tell what is the expected output.



class TestModulesCheck(TestCase):
def test_flags_with_suppress_required(self):
"""Test if flags with suppress required are handle correctly"""
Expand Down