[GRASS-dev] Python Scripting

Michael Barton michael.barton at asu.edu
Sat Jul 19 17:53:02 EDT 2008



On Jul 19, 2008, at 12:01 PM, Glynn Clements wrote:

>
> Michael Barton wrote:
>
>>
>> On Jul 18, 2008, at 11:03 PM, <grass-dev-request at lists.osgeo.org>  
>> wrote:
>>
>>> Date: Fri, 18 Jul 2008 18:20:46 +0100
>>> From: Glynn Clements <glynn at gclements.plus.com>
>>> Subject: Re: [GRASS-dev] Python Scripting
>>> To: "Dan D'Alimonte" <dan at dalimonte.ca>, grass-dev at lists.osgeo.org
>>> Message-ID: <18560.53486.897323.701173 at cerise.gclements.plus.com>
>>> Content-Type: text/plain; charset="us-ascii"
>>>
>>>
>>> Glynn Clements wrote:
>>>
>>>>> As to existing modules, what about a helper function to access  
>>>>> then?
>>>>>
>>>>> module.executeModule( name="r.stats", options={ "input":
>>>>> "elevation.dem,slope,aspect", "fs": ",", "output": "elev.csv"},
>>>>> flags=["q", "1", "n", "g"] )
>>>>
>>>> This idea has occurred to me. Some comments:
>>>>
>>>> Pass argument values as Python values, e.g. passing multiple values
>>>> as
>>>> lists, passing numeric types directly, etc, and have the interface
>>>> convert them to strings. Pass the flags as a single string.
>>>>
>>>> module.execute( "r.stats",
>>>>               options = { "input": ["elevation.dem", "slope",  
>>>> "aspect"],
>>>>                           "fs": ",",
>>>>                           "output": "elev.csv" },
>>>>               flags = "q1ng" )
>>>>
>>>> Provide a lower-level function which simply generates the command  
>>>> to
>>>> pass to Popen(), for cases where you want to interact with the  
>>>> child
>>>> process.
>>>
>>> I have attached a first draft of such a module, which also  
>>> includes a
>>> wrapper function for g.parser (for which an example script is also
>>> attached).
>
>> Do you still run a GRASS command as subprocess.call([command...])? Or
>> is there another syntax with your wrapper script (e.g., as in the
>> module.execute() above)?
>
> Yes, although it's actually called grass.run_command().
>
> Specifically, this:
>
> 	def make_command(prog, options = [], flags = "", overwrite = False,  
> quiet = False, verbose = False):
> 		...
>
> constructs a list suitable for use as the "args" argument to the
> Popen() constructor or to call(). E.g.:
>
> 	>>> import grass
> 	>>> grass.make_command( "r.stats",
> 	                options = { "input": ["elevation.dem", "slope",  
> "aspect"],
> 	                            "fs": ",",
> 	                            "output": "elev.csv" },
> 	                flags = "1ng" )
> 	['r.stats', '-1ng', 'input=elevation.dem,slope,aspect', 'fs=,',  
> 'output=elev.csv']
>
> This:
>
> 	def start_command(prog, options = [], flags = "", overwrite =  
> False, quiet = False, verbose = False, **kwargs):
> 	    args = make_command(prog, options, flags, overwrite, quiet,  
> verbose)
> 	    return subprocess.Popen(args, **kwargs)
>
> does just that: constructs the argument list then passes it to the
> Popen() constructor, along with any additional keyword arguments (so
> you can set stdin, stdout, etc), and returns the Popen() object. E.g.:
>
> 	>>> import sys
> 	>>> import subprocess
> 	>>> import grass
> 	>>> p = grass.start_command( "g.list", options = { "type":  
> "rast" }, stdout = subprocess.PIPE )
> 	>>> txt = p.communicate()[0]
> 	>>> sys.stdout.write(txt)
> 	----------------------------------------------
> 	raster files available in mapset <PERMANENT>:
> [snip]
>
> Finally, this:
>
> 	def run_command(*args, **kwargs):
> 	    ps = start_command(*args, **kwargs)
> 	    return ps.wait()
>
> is analogous to call(), but with the GRASS-oriented interface of
> make_command and start_command, e.g.:
>
> 	>>> import grass
> 	>>> grass.run_command( "g.list", options = { "type": "rast" } )
> 	----------------------------------------------
> 	raster files available in mapset <PERMANENT>:
> [snip]
>
> The option values can be strings, numbers, tuples, or lists, and are
> converted appropriately; numbers (well, anything except for strings,
> tuples and lists) are converted with str(), strings are taken
> literally (i.e. they aren't quoted), tuples and lists have their
> components converted and separated by commas, e.g.:
>
> 	>>> import grass
> 	>>> grass.make_command( "prog", options = { "arg": [(1,10), 
> (2,20)] } )
> 	['prog', 'arg=1,10,2,20']
>
> It has just occurred to me that it might be better to take the options
> as keyword arguments, rather than an explicit dictionary, e.g.:
>
> 	grass.make_command( "r.stats",
> 		flags = "1ng",
> 		input = ["elevation.dem", "slope", "aspect"],
> 		fs = ",",
> 		output = "elev.csv" )
>
> You could still pass a dictionary using the ** syntax:
>
> 	opts = {"input": ["elevation.dem", "slope", "aspect"],
> 		"fs": ",",
> 		"output": "elev.csv" }
> 	grass.make_command( "r.stats", flags = "1ng", **opts)
>
> This would be trivial to implement:
>
> -def make_command(prog, options = [], flags = "", overwrite = False,  
> quiet = False, verbose = False):
> +def make_command(prog, flags = "", overwrite = False, quiet =  
> False, verbose = False, **options):
>
> but there is the (remote) possibility that a module option could
> conflict with one of the predefined arguments (prog, flags, overwrite,
> quiet, or verbose), or with one of Popen()'s arguments (which
> run_command and start_command would have to handle explicitly, rather
> than using **kwargs).
>

Glynn,

I am probably missing something. But I guess I don't understand the  
advantage of using the modules available from this grass library over  
using normal Python functions to run a GRASS command. That is, the  
syntax for running a command here doesn't seem any easier--and maybe a  
shade more complicated than simply using subprocess.call for simple  
one-shot commands and subprocess.Popen for commands where you need to  
return stdout or stderr.

Are there other benefits to using a grass library that I'm not  
understanding?

Michael



More information about the grass-dev mailing list