Skip to content

grass.experimental: Add name prefixes to Tools #5887

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 37 commits into
base: main
Choose a base branch
from

Conversation

wenzeslaus
Copy link
Member

Name prefixes allows a Tools object to represent only a subset of tools with a common prefix. This provides functionality which is similar to grass.pygrass.modules.shortcuts, although the PR as it is implemented now adds really only the functionality to have things running that way and doesn't provide the actual objects to import. It may provide an alternative interface appealing to some users (grass.pygrass.modules.shortcuts is used in several addon tools and I know that @pesekon2 is using it). However, this would add another way of using it, making documentation and examples more challenging. In any case, this is an optional functionality which we can, but don't have to, add to tools. We can also add the functionality (what is in the PR now, but don't add any of the library objects like grass.pygrass.modules.shortcuts has).

The PR provides code which not only makes the individual tools usable this way, but also implements support for prefixes in attribute access and error messages when the tools are not found (showing the short method and the theoretical underlying tool).

I would be interested in opinions on whether to include this, how much to promote it, and whether to go further, and include "shortcut"/"prefix" objects into the library.

The PR currently build on top of #2923 (not merged yet), so the diff is large.

Examples

Basic example of use code. Creating a variable like this is totally a user choice, so it does not complicate the documentation of tools themselves. Unlike grass.pygrass.modules.shortcuts, this is actually more aligned with the basic case because only piece which is different is using raster.mapcalc(...) instead of tools.r_mapcalc(...) (while instantiating Module from grass.pygrass is different than importing from grass.pygrass.modules.shortcuts).

raster = Tools(prefix="r")
raster.mapcalc(expression="streams = if(row() > 1, 1, null())")
raster.buffer(input="streams", output="buffer", distance=1)
assert raster.info(map="streams", format="json")["datatype"] == "CELL"

The variable name is again up to the user, similarly to using aliases for imports in grass.pygrass.modules.shortcuts, so using the short, original prefix is possible (I find this hard to understand actually because it breaks the Python expectations about what is a name and what is a operator - the Python operator . becomes part of the tool name). Anyway, this common way of using grass.pygrass.modules.shortcuts is possible:

r = Tools(prefix="r")
r.mapcalc(expression="streams = if(row() > 1, 1, null())")
r.buffer(input="streams", output="buffer", distance=1)
assert r.info(map="streams", format="json")["datatype"] == "CELL"

Just to make the point, that user can choose the variable name to fit whatever they need, here is another example:

raster_tools = Tools(prefix="r")
raster_tools.mapcalc(expression="streams = if(row() > 1, 1, null())")
raster_tools.buffer(input="streams", output="buffer", distance=1)
assert raster_tools.info(map="streams", format="json")["datatype"] == "CELL"

What I find somewhat interesting, although without seeing any potential big of it, is using prefixes for sets of tools such as r.sim or r.stream, possibly tailoring the syntax to a specific use case:

simwe = Tools(prefix="r.sim")
simwe.water(...)
simwe.sediment(...)

Just like grass.pygrass.modules.shortcuts, shorter names suffer from a greater Python keyword conflict potential, so with prefixes active, the implementation allows trailing underscore for tool names so that things like v.import work:

vector = Tools(prefix="v")
assert vector.import_(...)

As already mentioned above, the proposed functionality could be used in the library to create the tools to be used later which would make it really similar to grass.pygrass.modules.shortcuts):

# grass/experimental/tools/shortcuts.py
raster = Tools(prefix="r")

# my_script.py
from grass.experimental.tools.shortcuts import raster as r

r.mapcalc(expression="streams = if(row() > 1, 1, null())")
r.buffer(input="streams", output="buffer", distance=1)
assert r.info(map="streams", format="json")["datatype"] == "CELL"

However, a session would then need to be provided through an env parameter like now (while using a Tools object directly avoids that by Tools accepting a session parameter):

from grass.experimental.tools.shortcuts import raster as r

# ...some special session setup

r.mapcalc(expression="streams = if(row() > 1, 1, null())", env=session.env)
r.buffer(input="streams", output="buffer", distance=1, env=session.env)
assert r.info(map="streams", format="json", env=session.env)["datatype"] == "CELL"

wenzeslaus and others added 30 commits June 3, 2023 23:57
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.
…needs to be improved there). Allow usage through attributes, run with run_command syntax, and subprocess-like execution.
…est a tool when there is a close match for the name.
…tool in XY project, so useful only for things like g.extension or m.proj, but, with a significant workaround for argparse --help, it can do --help for a tool.
…ng on Windows. Although the subprocess.run call is much nicer, whatever is necessary for the subprocess itself, good or bad, is contained in gs.Popen. This makes it consistent with the actual tool call later on. We may need a library wrapper for this in the future.
…workaround the tool help by manually handling the argparse help, but only for the run subcommand
… the error message which may get automatic suggestion by Python when __dir__ is now in place.
…me, count on Python possibly adding a sentence), use g.region rather than g.search.modules output in tests because the output is more predictable (although we loose the JSON test without format=json)
@wenzeslaus wenzeslaus added the enhancement New feature or request label Jun 13, 2025
@github-actions github-actions bot added Python Related code is in Python libraries tests Related to Test Suite labels Jun 13, 2025
@pesekon2
Copy link
Contributor

I like it and I'm up for that. However, if I would be the only user, I can live without that in order to make code cleaner and without unused features.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request libraries Python Related code is in Python tests Related to Test Suite
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants