Skip to content

Commit 24c27e6

Browse files
committed
Add doc, remove old code and todos
1 parent 744cfac commit 24c27e6

File tree

1 file changed

+45
-34
lines changed

1 file changed

+45
-34
lines changed

python/grass/experimental/tools.py

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,59 @@
1+
#!/usr/bin/env python
2+
3+
##############################################################################
4+
# AUTHOR(S): Vaclav Petras <wenzeslaus gmail com>
5+
#
6+
# PURPOSE: API to call GRASS tools (modules) as Python functions
7+
#
8+
# COPYRIGHT: (C) 2023 Vaclav Petras and the GRASS Development Team
9+
#
10+
# This program is free software under the GNU General Public
11+
# License (>=v2). Read the file COPYING that comes with GRASS
12+
# for details.
13+
##############################################################################
14+
15+
"""API to call GRASS tools (modules) as Python functions"""
16+
17+
import json
118
import os
2-
import sys
319
import shutil
420

521
import grass.script as gs
622

723

824
class ExecutedTool:
25+
"""Result returned after executing a tool"""
26+
927
def __init__(self, name, kwargs, stdout, stderr):
1028
self._name = name
29+
self._kwargs = kwargs
1130
self._stdout = stdout
31+
self._stderr = stderr
1232
if self._stdout:
1333
self._decoded_stdout = gs.decode(self._stdout)
1434
else:
1535
self._decoded_stdout = ""
1636

1737
@property
18-
def text(self):
38+
def text(self) -> str:
39+
"""Text output as decoded string"""
1940
return self._decoded_stdout.strip()
2041

2142
@property
2243
def json(self):
23-
import json
44+
"""Text output read as JSON
2445

46+
This returns the nested structure of dictionaries and lists or fails when
47+
the output is not JSON.
48+
"""
2549
return json.loads(self._stdout)
2650

2751
@property
2852
def keyval(self):
53+
"""Text output read as key-value pairs separated by equal signs"""
54+
2955
def conversion(value):
56+
"""Convert text to int or float if possible, otherwise return it as is"""
3057
try:
3158
return int(value)
3259
except ValueError:
@@ -41,33 +68,30 @@ def conversion(value):
4168

4269
@property
4370
def comma_items(self):
71+
"""Text output read as comma-separated list"""
4472
return self.text_split(",")
4573

4674
@property
4775
def space_items(self):
76+
"""Text output read as whitespace-separated list"""
4877
return self.text_split(None)
4978

5079
def text_split(self, separator=None):
80+
"""Parse text output read as list separated by separators
81+
82+
Any leading or trailing newlines are removed prior to parsing.
83+
"""
5184
# The use of strip is assuming that the output is one line which
5285
# ends with a newline character which is for display only.
5386
return self._decoded_stdout.strip("\n").split(separator)
5487

5588

56-
class SubExecutor:
57-
"""use as tools().params(a="x", b="y").g_region()"""
58-
59-
# a and b would be overwrite or stdin
60-
# Can support other envs or all PIPE and encoding read command supports
61-
def __init__(self, *, tools, env, stdin=None):
62-
self._tools = tools
63-
self._env = env
64-
self._stdin = stdin
65-
66-
def run(self, name, /, **kwargs):
67-
pass
89+
class Tools:
90+
"""Call GRASS tools as methods
6891

92+
GRASS tools (modules) can be executed as methods of this class.
93+
"""
6994

70-
class Tools:
7195
def __init__(
7296
self,
7397
*,
@@ -81,9 +105,6 @@ def __init__(
81105
stdin=None,
82106
errors=None,
83107
):
84-
# TODO: fix region, so that external g.region call in the middle
85-
# is not a problem
86-
# i.e. region is independent/internal/fixed
87108
if env:
88109
self._env = env.copy()
89110
elif session and hasattr(session, "env"):
@@ -122,6 +143,7 @@ def _set_stdin(self, stdin, /):
122143

123144
@property
124145
def env(self):
146+
"""Internally used environment (reference to it, not a copy)"""
125147
return self._env
126148

127149
def run(self, name, /, **kwargs):
@@ -152,31 +174,21 @@ def run(self, name, /, **kwargs):
152174
stdout, stderr = process.communicate(input=stdin)
153175
stderr = gs.utils.decode(stderr)
154176
returncode = process.poll()
155-
# TODO: instead of printing, do exception right away
156-
# but right now, handle errors does not accept stderr
157-
# or don't use handle errors and raise instead
158177
if returncode and self._errors != "ignore":
159178
raise gs.CalledModuleError(
160179
name,
161180
code=" ".join([f"{key}={value}" for key, value in kwargs.items()]),
162181
returncode=returncode,
163182
errors=stderr,
164183
)
165-
166-
# Print only when we are capturing it and there was some output.
167-
# (User can request ignoring the subprocess stderr and then
168-
# we get only None.)
169-
if stderr:
170-
sys.stderr.write(stderr)
171-
gs.handle_errors(returncode, stdout, [name], kwargs)
172184
return ExecutedTool(name=name, kwargs=kwargs, stdout=stdout, stderr=stderr)
173-
# executor = SubExecutor(tools=self, env=self._env, stdin=self._stdin)
174-
# return executor.run(name, **kwargs)
175185

176186
def feed_input_to(self, stdin, /):
187+
"""Get a new object which will feed text input to a tool or tools"""
177188
return Tools(env=self._env, stdin=stdin)
178189

179190
def ignore_errors_of(self):
191+
"""Get a new object which will ignore errors of the called tools"""
180192
return Tools(env=self._env, errors="ignore")
181193

182194
def __getattr__(self, name):
@@ -202,6 +214,7 @@ def wrapper(**kwargs):
202214

203215

204216
def _test():
217+
"""Ad-hoc tests and examples of the Tools class"""
205218
session = gs.setup.init("~/grassdata/nc_spm_08_grass7/user1")
206219

207220
tools = Tools()
@@ -227,9 +240,7 @@ def _test():
227240
env["GRASS_REGION"] = gs.region_env(res=250)
228241
coarse_computation = Tools(env=env)
229242
current_region = coarse_computation.g_region(flags="g").keyval
230-
print(
231-
current_region["ewres"], current_region["nsres"]
232-
) # TODO: should keyval convert?
243+
print(current_region["ewres"], current_region["nsres"])
233244
coarse_computation.r_slope_aspect(
234245
elevation="elevation", slope="slope", flags="a", overwrite=True
235246
)

0 commit comments

Comments
 (0)