Skip to content

Commit 96b1d0c

Browse files
committed
grass.experimental: Add object to access modules as functions
This adds a Tools class which allows to access GRASS tools (modules) to be accessed using methods. Once an instance is created, calling a tool is calling a function (method) similarly to grass.jupyter.Map. Unlike grass.script, this does not require generic function name and unlike grass.pygrass module shortcuts, this does not require special objects to mimic the module families. Outputs are handled through a returned object which is result of automatic capture of outputs and can do conversions from known formats using properties. Usage example is in the _test() function in the file. The code is included under new grass.experimental package which allows merging the code even when further breaking changes are anticipated.
1 parent 24ce8d3 commit 96b1d0c

File tree

2 files changed

+104
-0
lines changed

2 files changed

+104
-0
lines changed

python/grass/experimental/__init__.py

Whitespace-only changes.

python/grass/experimental/tools.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import sys
2+
import shutil
3+
4+
import grass.script as gs
5+
6+
7+
class ExecutedTool:
8+
def __init__(self, name, kwargs, stdout, stderr):
9+
self._name = name
10+
self._stdout = stdout
11+
12+
@property
13+
def text(self):
14+
return self._stdout
15+
16+
@property
17+
def json(self):
18+
import json
19+
20+
return json.loads(self._stdout)
21+
22+
@property
23+
def keyval(self):
24+
return gs.parse_key_val(self._stdout)
25+
26+
27+
class SubExecutor:
28+
"""use as tools().params(a="x", b="y").g_region()"""
29+
30+
# Can support other envs or all PIPE and encoding read command supports
31+
32+
33+
class Tools:
34+
def __init__(self):
35+
# TODO: fix region, so that external g.region call in the middle
36+
# is not a problem
37+
# i.e. region is independent/internal/fixed
38+
pass
39+
40+
def run(self, name, /, **kwargs):
41+
"""Run modules from the GRASS display family (modules starting with "d.").
42+
43+
This function passes arguments directly to grass.script.run_command()
44+
so the syntax is the same.
45+
46+
:param str module: name of GRASS module
47+
:param `**kwargs`: named arguments passed to run_command()"""
48+
# alternatively use dev null as default or provide it as convenient settings
49+
kwargs["stdout"] = gs.PIPE
50+
kwargs["stderr"] = gs.PIPE
51+
process = gs.pipe_command(name, **kwargs)
52+
stdout, stderr = process.communicate()
53+
stderr = gs.utils.decode(stderr)
54+
returncode = process.poll()
55+
# TODO: instead of printing, do exception right away
56+
if returncode:
57+
# Print only when we are capturing it and there was some output.
58+
# (User can request ignoring the subprocess stderr and then
59+
# we get only None.)
60+
if stderr:
61+
sys.stderr.write(stderr)
62+
gs.handle_errors(returncode, stdout, [name], kwargs)
63+
return ExecutedTool(name=name, kwargs=kwargs, stdout=stdout, stderr=stderr)
64+
65+
def __getattr__(self, name):
66+
"""Parse attribute to GRASS display module. Attribute should be in
67+
the form 'd_module_name'. For example, 'd.rast' is called with 'd_rast'.
68+
"""
69+
# Reformat string
70+
grass_module = name.replace("_", ".")
71+
# Assert module exists
72+
if not shutil.which(grass_module):
73+
raise AttributeError(
74+
_(
75+
"Cannot find GRASS tool {}. "
76+
"Is the session set up and the tool on path?"
77+
).format(grass_module)
78+
)
79+
80+
def wrapper(**kwargs):
81+
# Run module
82+
return self.run(grass_module, **kwargs)
83+
84+
return wrapper
85+
86+
87+
def _test():
88+
gs.setup.init("~/grassdata/nc_spm_08_grass7/user1")
89+
90+
tools = Tools()
91+
tools.g_region(raster="elevation")
92+
tools.r_slope_aspect(elevation="elevation", slope="slope", overwrite=True)
93+
print(tools.r_univar(map="slope", flags="g").keyval)
94+
95+
print(tools.v_info(map="bridges", flags="c").text)
96+
print(
97+
tools.v_db_univar(map="bridges", column="YEAR_BUILT", format="json").json[
98+
"statistics"
99+
]["mean"]
100+
)
101+
102+
103+
if __name__ == "__main__":
104+
_test()

0 commit comments

Comments
 (0)