[GRASS-dev] Python Scripting

Glynn Clements glynn at gclements.plus.com
Sat Jul 19 15:01:28 EDT 2008


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 Clements <glynn at gclements.plus.com>


More information about the grass-dev mailing list