Browse Source

pythonlib: add env variables to all relevant functions (#677)

Passing environment is needed for some functions for fixes of r.import.
The rest of the functions get it for consistency.

The following functions have newly added env parameter:
* core.py: tempfile, tempdir, locn_is_latlong, find_file, list_strings, list_pairs, list_grouped, mapsets
* db.py: db_describe, db_table_exist, db_connection, db_select, db_table_in_vector
* raster.py: raster_history, raster_info
* raster3d.py: raster3d_info
* vector.py: vector_db, vector_layer_db, vector_columns, vector_history, vector_info_topo, vector_info, vector_db_select, vector_what
* array.py
Anna Petrasova 4 years ago
parent
commit
dbeb5f36cb

+ 20 - 12
lib/python/script/array.py

@@ -124,8 +124,8 @@ from grass.exceptions import CalledModuleError
 ###############################################################################
 
 class _tempfile(object):
-    def __init__(self):
-        self.filename = gcore.tempfile()
+    def __init__(self, env=None):
+        self.filename = gcore.tempfile(env=env)
 
     def __del__(self):
         try_remove(self.filename)
@@ -133,18 +133,19 @@ class _tempfile(object):
 ###############################################################################
 
 class array(numpy.memmap):
-    def __new__(cls, mapname=None, null=None, dtype=numpy.double):
+    def __new__(cls, mapname=None, null=None, dtype=numpy.double, env=None):
         """Define new numpy array
 
         :param cls:
         :param dtype: data type (default: numpy.double)
+        :param env: environment
         """
-        reg = gcore.region()
+        reg = gcore.region(env=env)
         r = reg['rows']
         c = reg['cols']
         shape = (r, c)
 
-        tempfile = _tempfile()
+        tempfile = _tempfile(env)
         if mapname:
             kind = numpy.dtype(dtype).kind
             size = numpy.dtype(dtype).itemsize
@@ -167,7 +168,8 @@ class array(numpy.memmap):
                 bytes=size,
                 null=null,
                 quiet=True,
-                overwrite=True)
+                overwrite=True,
+                env=env)
 
         self = numpy.memmap.__new__(
             cls,
@@ -178,6 +180,7 @@ class array(numpy.memmap):
 
         self.tempfile = tempfile
         self.filename = tempfile.filename
+        self._env = env
         return self
 
     def read(self, mapname, null=None):
@@ -253,7 +256,7 @@ class array(numpy.memmap):
         else:
             raise ValueError(_('Invalid kind <%s>') % kind)
 
-        reg = gcore.region()
+        reg = gcore.region(env=self._env)
 
         try:
             gcore.run_command(
@@ -271,7 +274,8 @@ class array(numpy.memmap):
                 east=reg['e'],
                 west=reg['w'],
                 rows=reg['rows'],
-                cols=reg['cols'])
+                cols=reg['cols'],
+                env=self._env)
         except CalledModuleError:
             return 1
         else:
@@ -281,11 +285,12 @@ class array(numpy.memmap):
 
 
 class array3d(numpy.memmap):
-    def __new__(cls, mapname=None, null=None, dtype=numpy.double):
+    def __new__(cls, mapname=None, null=None, dtype=numpy.double, env=None):
         """Define new 3d numpy array
 
         :param cls:
         :param dtype: data type (default: numpy.double)
+        :param env: environment
         """
         reg = gcore.region(True)
         r = reg['rows3']
@@ -316,7 +321,8 @@ class array3d(numpy.memmap):
                 bytes=size,
                 null=null,
                 quiet=True,
-                overwrite=True)
+                overwrite=True,
+                env=env)
 
         self = numpy.memmap.__new__(
             cls,
@@ -327,6 +333,7 @@ class array3d(numpy.memmap):
 
         self.tempfile = tempfile
         self.filename = tempfile.filename
+        self._env = env
 
         return self
 
@@ -398,7 +405,7 @@ class array3d(numpy.memmap):
         else:
             raise ValueError(_('Invalid kind <%s>') % kind)
 
-        reg = gcore.region(True)
+        reg = gcore.region(True, env=self._env)
 
         try:
             gcore.run_command(
@@ -418,7 +425,8 @@ class array3d(numpy.memmap):
                 west=reg['w'],
                 depths=reg['depths'],
                 rows=reg['rows3'],
-                cols=reg['cols3'])
+                cols=reg['cols3'],
+                env=self._env)
 
         except CalledModuleError:
             return 1

+ 40 - 17
lib/python/script/core.py

@@ -904,10 +904,11 @@ def parser():
 # interface to g.tempfile
 
 
-def tempfile(create=True):
+def tempfile(create=True, env=None):
     """Returns the name of a temporary file, created with g.tempfile.
 
     :param bool create: True to create a file
+    :param env: environment
 
     :return: path to a tmp file
     """
@@ -915,12 +916,12 @@ def tempfile(create=True):
     if not create:
         flags += 'd'
 
-    return read_command("g.tempfile", flags=flags, pid=os.getpid()).strip()
+    return read_command("g.tempfile", flags=flags, pid=os.getpid(), env=env).strip()
 
 
-def tempdir():
+def tempdir(env=None):
     """Returns the name of a temporary dir, created with g.tempfile."""
-    tmp = tempfile(create=False)
+    tmp = tempfile(create=False, env=env)
     os.mkdir(tmp)
 
     return tmp
@@ -1144,13 +1145,13 @@ def gisenv(env=None):
 # interface to g.region
 
 
-def locn_is_latlong():
+def locn_is_latlong(env=None):
     """Tests if location is lat/long. Value is obtained
     by checking the "g.region -pu" projection code.
 
     :return: True for a lat/long region, False otherwise
     """
-    s = read_command("g.region", flags='pu')
+    s = read_command("g.region", flags='pu', env=env)
     kv = parse_key_val(s, ':')
     if kv['projection'].split(' ')[0] == '3':
         return True
@@ -1295,7 +1296,7 @@ def del_temp_region():
 # interface to g.findfile
 
 
-def find_file(name, element='cell', mapset=None):
+def find_file(name, element='cell', mapset=None, env=None):
     """Returns the output from running g.findfile as a
     dictionary. Example:
 
@@ -1309,6 +1310,7 @@ def find_file(name, element='cell', mapset=None):
     :param str name: file name
     :param str element: element type (default 'cell')
     :param str mapset: mapset name (default all mapsets in search path)
+    :param env: environment
 
     :return: parsed output of g.findfile
     """
@@ -1319,14 +1321,15 @@ def find_file(name, element='cell', mapset=None):
     # se we ignore return code and just focus on stdout
     process = start_command('g.findfile', flags='n',
                             element=element, file=name, mapset=mapset,
-                            stdout=PIPE)
+                            stdout=PIPE, env=env)
     stdout = process.communicate()[0]
     return parse_key_val(stdout)
 
 # interface to g.list
 
 
-def list_strings(type, pattern=None, mapset=None, exclude=None, flag=''):
+def list_strings(type, pattern=None, mapset=None, exclude=None,
+                 flag='', env=None):
     """List of elements as strings.
 
     Returns the output from running g.list, as a list of qualified
@@ -1338,6 +1341,7 @@ def list_strings(type, pattern=None, mapset=None, exclude=None, flag=''):
     :param str exclude: pattern string to exclude maps from the research
     :param str flag: pattern type: 'r' (basic regexp), 'e' (extended regexp),
                      or '' (glob pattern)
+    :param env: environment
 
     :return: list of elements
     """
@@ -1351,13 +1355,15 @@ def list_strings(type, pattern=None, mapset=None, exclude=None, flag=''):
                              type=type,
                              pattern=pattern,
                              exclude=exclude,
-                             mapset=mapset).splitlines():
+                             mapset=mapset,
+                             env=env).splitlines():
         result.append(line.strip())
 
     return result
 
 
-def list_pairs(type, pattern=None, mapset=None, exclude=None, flag=''):
+def list_pairs(type, pattern=None, mapset=None, exclude=None,
+               flag='', env=None):
     """List of elements as pairs
 
     Returns the output from running g.list, as a list of
@@ -1369,16 +1375,17 @@ def list_pairs(type, pattern=None, mapset=None, exclude=None, flag=''):
     :param str exclude: pattern string to exclude maps from the research
     :param str flag: pattern type: 'r' (basic regexp), 'e' (extended regexp),
                      or '' (glob pattern)
+    :param env: environment
 
     :return: list of elements
     """
     return [tuple(map.split('@', 1)) for map in list_strings(type, pattern,
                                                               mapset, exclude,
-                                                              flag)]
+                                                              flag, env)]
 
 
 def list_grouped(type, pattern=None, check_search_path=True, exclude=None,
-                 flag=''):
+                 flag='', env=None):
     """List of elements grouped by mapsets.
 
     Returns the output from running g.list, as a dictionary where the
@@ -1395,6 +1402,7 @@ def list_grouped(type, pattern=None, check_search_path=True, exclude=None,
     :param str exclude: pattern string to exclude maps from the research
     :param str flag: pattern type: 'r' (basic regexp), 'e' (extended regexp),
                                     or '' (glob pattern)
+    :param env: environment
 
     :return: directory of mapsets/elements
     """
@@ -1411,7 +1419,7 @@ def list_grouped(type, pattern=None, check_search_path=True, exclude=None,
             types[i] = 'raster'
     result = {}
     if check_search_path:
-        for mapset in mapsets(search_path=True):
+        for mapset in mapsets(search_path=True, env=env):
             if store_types:
                 result[mapset] = {}
             else:
@@ -1419,7 +1427,8 @@ def list_grouped(type, pattern=None, check_search_path=True, exclude=None,
 
     mapset = None
     for line in read_command("g.list", quiet=True, flags="m" + flag,
-                             type=types, pattern=pattern, exclude=exclude).splitlines():
+                             type=types, pattern=pattern,
+                             exclude=exclude, env=env).splitlines():
         try:
             name, mapset = line.split('@')
         except ValueError:
@@ -1546,7 +1555,7 @@ def find_program(pgm, *args):
 # interface to g.mapsets
 
 
-def mapsets(search_path=False):
+def mapsets(search_path=False, env=None):
     """List available mapsets
 
     :param bool search_path: True to list mapsets only in search path
@@ -1560,7 +1569,8 @@ def mapsets(search_path=False):
     mapsets = read_command('g.mapsets',
                            flags=flags,
                            sep='newline',
-                           quiet=True)
+                           quiet=True,
+                           env=env)
     if not mapsets:
         fatal(_("Unable to list mapsets"))
 
@@ -1767,6 +1777,17 @@ def legal_name(s):
     return True
 
 
+def sanitize_mapset_environment(env):
+    """Remove environmental variables relevant only
+    for a specific mapset. This should be called
+    when a copy of environment is used with a different mapset."""
+    if "WIND_OVERRIDE" in env:
+        del env["WIND_OVERRIDE"]
+    if "GRASS_REGION" in env:
+        del env["GRASS_REGION"]
+    return env
+
+
 def create_environment(gisdbase, location, mapset):
     """Creates environment to be passed in run_command for example.
     Returns tuple with temporary file path and the environment. The user
@@ -1778,6 +1799,8 @@ def create_environment(gisdbase, location, mapset):
         f.write('GUI: text\n')
     env = os.environ.copy()
     env['GISRC'] = f.name
+    # remove mapset-specific env vars
+    env = sanitize_mapset_environment(env)
     return f.name, env
 
 

+ 20 - 14
lib/python/script/db.py

@@ -24,7 +24,7 @@ from .utils import try_remove
 from grass.exceptions import CalledModuleError
 
 
-def db_describe(table, **args):
+def db_describe(table, env=None, **args):
     """Return the list of columns for a database table
     (interface to `db.describe -c`). Example:
 
@@ -37,6 +37,7 @@ def db_describe(table, **args):
 
     :param str table: table name
     :param list args:
+    :param env: environment
 
     :return: parsed module output
     """
@@ -44,7 +45,7 @@ def db_describe(table, **args):
         args.pop('database')
     if 'driver' in args and args['driver'] == '':
         args.pop('driver')
-    s = read_command('db.describe', flags='c', table=table, **args)
+    s = read_command('db.describe', flags='c', table=table, env=env, **args)
     if not s:
         fatal(_("Unable to describe table <%s>") % table)
 
@@ -66,7 +67,7 @@ def db_describe(table, **args):
     return result
 
 
-def db_table_exist(table, **args):
+def db_table_exist(table, env=None, **args):
     """Check if table exists.
 
     If no driver or database are given, then default settings is used
@@ -81,6 +82,7 @@ def db_table_exist(table, **args):
 
     :param str table: table name
     :param args:
+    :param env: environment
 
     :return: True for success, False otherwise
     """
@@ -88,7 +90,7 @@ def db_table_exist(table, **args):
     ok = True
     try:
         run_command('db.describe', flags='c', table=table,
-                    stdout=nuldev, stderr=nuldev, **args)
+                    stdout=nuldev, stderr=nuldev, env=env, **args)
     except CalledModuleError:
         ok = False
     finally:
@@ -97,7 +99,7 @@ def db_table_exist(table, **args):
     return ok
 
 
-def db_connection(force=False):
+def db_connection(force=False, env=None):
     """Return the current database connection parameters
     (interface to `db.connect -g`). Example:
 
@@ -105,23 +107,25 @@ def db_connection(force=False):
     {'group': '', 'schema': '', 'driver': 'sqlite', 'database': '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db'}
 
     :param force True to set up default DB connection if not defined
+    :param env: environment
 
     :return: parsed output of db.connect
     """
     try:
         nuldev = open(os.devnull, 'w')
-        conn = parse_command('db.connect', flags='g', stderr=nuldev)
+        conn = parse_command('db.connect', flags='g', stderr=nuldev, env=env)
         nuldev.close()
     except CalledModuleError:
         conn = None
     
     if not conn and force:
-        run_command('db.connect', flags='c')
-        conn = parse_command('db.connect', flags='g')
+        run_command('db.connect', flags='c', env=env)
+        conn = parse_command('db.connect', flags='g', env=env)
 
     return conn
 
-def db_select(sql=None, filename=None, table=None, **args):
+
+def db_select(sql=None, filename=None, table=None, env=None, **args):
     """Perform SQL select statement
 
     Note: one of <em>sql</em>, <em>filename</em>, or <em>table</em>
@@ -145,8 +149,9 @@ def db_select(sql=None, filename=None, table=None, **args):
     :param str filename: name of file with SQL statements (or None)
     :param str table: name of table to query (or None)
     :param str args:  see \gmod{db.select} arguments
+    :param env: environment
     """
-    fname = tempfile(create=False)
+    fname = tempfile(create=False, env=env)
     if sql:
         args['sql'] = sql
     elif filename:
@@ -162,7 +167,7 @@ def db_select(sql=None, filename=None, table=None, **args):
 
     try:
         run_command('db.select', quiet=True, flags='c',
-                    output=fname, **args)
+                    output=fname, env=env, **args)
     except CalledModuleError:
         fatal(_("Fetching data failed"))
 
@@ -174,7 +179,7 @@ def db_select(sql=None, filename=None, table=None, **args):
     return tuple(result)
 
 
-def db_table_in_vector(table, mapset='.'):
+def db_table_in_vector(table, mapset='.', env=None):
     """Return the name of vector connected to the table.
     By default it check only in the current mapset, because the same table
     name could be used also in other mapset by other vector.
@@ -189,13 +194,14 @@ def db_table_in_vector(table, mapset='.'):
     0
 
     :param str table: name of table to query
+    :param env: environment
     """
     from .vector import vector_db
     nuldev = open(os.devnull, 'w')
     used = []
-    vects = list_strings('vector', mapset=mapset)
+    vects = list_strings('vector', mapset=mapset, env=env)
     for vect in vects:
-        for f in vector_db(vect, stderr=nuldev).values():
+        for f in vector_db(vect, stderr=nuldev, env=env).values():
             if not f:
                 continue
             if f['table'] == table:

+ 10 - 8
lib/python/script/raster.py

@@ -33,27 +33,28 @@ if sys.version_info.major >= 3:
     unicode = str
 
 
-def raster_history(map, overwrite=False):
+def raster_history(map, overwrite=False, env=None):
     """Set the command history for a raster map to the command used to
     invoke the script (interface to `r.support`).
 
     :param str map: map name
+    :param env: environment
 
     :return: True on success
     :return: False on failure
 
     """
-    current_mapset = gisenv()['MAPSET']
-    if find_file(name=map)['mapset'] == current_mapset:
+    current_mapset = gisenv(env)['MAPSET']
+    if find_file(name=map, env=env)['mapset'] == current_mapset:
         if overwrite == True:
-            historyfile = tempfile()
+            historyfile = tempfile(env=env)
             f = open(historyfile, 'w')
             f.write(os.environ['CMDLINE'])
             f.close()
-            run_command('r.support', map=map, loadhistory=historyfile)
+            run_command('r.support', map=map, loadhistory=historyfile, env=env)
             try_remove(historyfile)
         else:
-            run_command('r.support', map=map, history=os.environ['CMDLINE'])
+            run_command('r.support', map=map, history=os.environ['CMDLINE'], env=env)
         return True
 
     warning(_("Unable to write history for <%(map)s>. "
@@ -61,7 +62,7 @@ def raster_history(map, overwrite=False):
     return False
 
 
-def raster_info(map):
+def raster_info(map, env=None):
     """Return information about a raster map (interface to
     `r.info -gre`). Example:
 
@@ -69,6 +70,7 @@ def raster_info(map):
     {'creator': '"helena"', 'cols': '1500' ... 'south': 215000.0}
 
     :param str map: map name
+    :param env: environment
 
     :return: parsed raster info
 
@@ -80,7 +82,7 @@ def raster_info(map):
         else:
             return float(s)
 
-    s = read_command('r.info', flags='gre', map=map)
+    s = read_command('r.info', flags='gre', map=map, env=env)
     kv = parse_key_val(s)
     for k in ['min', 'max']:
         kv[k] = float_or_null(kv[k])

+ 3 - 2
lib/python/script/raster3d.py

@@ -27,7 +27,7 @@ from .utils import float_or_dms, parse_key_val
 from grass.exceptions import CalledModuleError
 
 
-def raster3d_info(map):
+def raster3d_info(map, env=None):
     """Return information about a raster3d map (interface to `r3.info`).
     Example:
 
@@ -38,6 +38,7 @@ def raster3d_info(map):
     0
 
     :param str map: map name
+    :param env: environment
 
     :return: parsed raster3d info
     """
@@ -48,7 +49,7 @@ def raster3d_info(map):
         else:
             return float(s)
 
-    s = read_command('r3.info', flags='rg', map=map)
+    s = read_command('r3.info', flags='rg', map=map, env=env)
     kv = parse_key_val(s)
     for k in ['min', 'max']:
         kv[k] = float_or_null(kv[k])

+ 36 - 25
lib/python/script/vector.py

@@ -33,7 +33,7 @@ from .core import *
 from grass.exceptions import CalledModuleError
 
 
-def vector_db(map, **args):
+def vector_db(map, env=None, **kwargs):
     """Return the database connection details for a vector map
     (interface to `v.db.connect -g`). Example:
 
@@ -41,12 +41,13 @@ def vector_db(map, **args):
     {1: {'layer': 1, ... 'table': 'geology'}}
 
     :param str map: vector map
-    :param args: other v.db.connect's arguments
+    :param kwargs: other v.db.connect's arguments
+    :param env: environment
 
     :return: dictionary
     """
     s = read_command('v.db.connect', quiet=True, flags='g', map=map, sep=';',
-                     **args)
+                     env=env, **kwargs)
     result = {}
 
     for l in s.splitlines():
@@ -73,17 +74,18 @@ def vector_db(map, **args):
     return result
 
 
-def vector_layer_db(map, layer):
+def vector_layer_db(map, layer, env=None):
     """Return the database connection details for a vector map layer.
     If db connection for given layer is not defined, fatal() is called.
 
     :param str map: map name
     :param layer: layer number
+    :param env: environment
 
     :return: parsed output
     """
     try:
-        f = vector_db(map)[int(layer)]
+        f = vector_db(map, env=env)[int(layer)]
     except KeyError:
         fatal(_("Database connection not defined for layer %s") % layer)
 
@@ -92,7 +94,7 @@ def vector_layer_db(map, layer):
 # run "v.info -c ..." and parse output
 
 
-def vector_columns(map, layer=None, getDict=True, **args):
+def vector_columns(map, layer=None, getDict=True, env=None, **kwargs):
     """Return a dictionary (or a list) of the columns for the
     database table connected to a vector map (interface to `v.info -c`).
 
@@ -118,12 +120,13 @@ def vector_columns(map, layer=None, getDict=True, **args):
     :param layer: layer number or name (None for all layers)
     :param bool getDict: True to return dictionary of columns otherwise list
                          of column names is returned
-    :param args: (v.info's arguments)
+    :param kwargs: (v.info's arguments)
+    :param env: environment
 
     :return: dictionary/list of columns
     """
     s = read_command('v.info', flags='c', map=map, layer=layer, quiet=True,
-                     **args)
+                     env=env, **kwargs)
     if getDict:
         result = dict()
     else:
@@ -140,20 +143,21 @@ def vector_columns(map, layer=None, getDict=True, **args):
     return result
 
 
-def vector_history(map, replace=False):
+def vector_history(map, replace=False, env=None):
     """Set the command history for a vector map to the command used to
     invoke the script (interface to `v.support`).
 
     :param str map: mapname
     :param bool replace: Replace command line instead of appending it
+    :param env: environment
 
     :return: v.support output
     """
     run_command('v.support', map=map, cmdhist=os.environ['CMDLINE'],
-                flags='h' if replace else None)
+                flags='h' if replace else None, env=env)
 
 
-def vector_info_topo(map, layer=1):
+def vector_info_topo(map, layer=1, env=None):
     """Return information about a vector map (interface to `v.info -t`).
     Example:
 
@@ -164,10 +168,12 @@ def vector_info_topo(map, layer=1):
 
     :param str map: map name
     :param int layer: layer number
+    :param env: environment
 
     :return: parsed output
     """
-    s = read_command('v.info', flags='t', layer=layer, map=map)
+    s = read_command('v.info', flags='t', layer=layer, map=map,
+                     env=env)
     ret = parse_key_val(s, val_type=int)
     if 'map3d' in ret:
         ret['map3d'] = bool(ret['map3d'])
@@ -175,7 +181,7 @@ def vector_info_topo(map, layer=1):
     return ret
 
 
-def vector_info(map, layer=1):
+def vector_info(map, layer=1, env=None):
     """Return information about a vector map (interface to
     `v.info`). Example:
 
@@ -184,11 +190,13 @@ def vector_info(map, layer=1):
 
     :param str map: map name
     :param int layer: layer number
+    :param env: environment
 
     :return: parsed vector info
     """
 
-    s = read_command('v.info', flags='get', layer=layer, map=map)
+    s = read_command('v.info', flags='get', layer=layer, map=map,
+                     env=env)
 
     kv = parse_key_val(s)
     for k in ['north', 'south', 'east', 'west', 'top', 'bottom']:
@@ -207,7 +215,7 @@ def vector_info(map, layer=1):
     return kv
 
 
-def vector_db_select(map, layer=1, **kwargs):
+def vector_db_select(map, layer=1, env=None, **kwargs):
     """Get attribute data of selected vector map layer.
 
     Function returns list of columns and dictionary of values ordered by
@@ -223,11 +231,12 @@ def vector_db_select(map, layer=1, **kwargs):
     :param str map: map name
     :param int layer: layer number
     :param kwargs: v.db.select options
+    :param env: environment
 
     :return: dictionary ('columns' and 'values')
     """
     try:
-        key = vector_db(map=map)[layer]['key']
+        key = vector_db(map=map, env=env)[layer]['key']
     except KeyError:
         error(_('Missing layer %(layer)d in vector map <%(map)s>') % \
               {'layer': layer, 'map': map})
@@ -241,7 +250,8 @@ def vector_db_select(map, layer=1, **kwargs):
             debug("Adding key column to the output")
             kwargs['columns'] += ',' + key
 
-    ret = read_command('v.db.select', map=map, layer=layer, **kwargs)
+    ret = read_command('v.db.select', map=map, layer=layer,
+                       env=env, **kwargs)
 
     if not ret:
         error(_('vector_db_select() failed'))
@@ -273,7 +283,9 @@ json = None
 orderedDict = None
 
 
-def vector_what(map, coord, distance=0.0, ttype=None, encoding=None, skip_attributes=False, layer=None, multiple=False):
+def vector_what(map, coord, distance=0.0, ttype=None,
+                encoding=None, skip_attributes=False,
+                layer=None, multiple=False, env=None):
     """Query vector map at given locations
 
     To query one vector map at one location
@@ -334,12 +346,14 @@ def vector_what(map, coord, distance=0.0, ttype=None, encoding=None, skip_attrib
     :param layer: layer number or list of layers (one for each vector),
                   if None, all layers (-1) are used
     :param multiple: find multiple features within threshold distance
+    :param env: environment
 
     :return: parsed list
     """
-    if "LC_ALL" in os.environ:
-        locale = os.environ["LC_ALL"]
-        os.environ["LC_ALL"] = "C"
+    if not env:
+        env = os.environ.copy()
+    if "LC_ALL" in env:
+        env["LC_ALL"] = "C"
 
     if isinstance(map, (bytes, unicode)):
         map_list = [map]
@@ -380,14 +394,11 @@ def vector_what(map, coord, distance=0.0, ttype=None, encoding=None, skip_attrib
         cmdParams['type'] = ','.join(ttype)
 
     try:
-        ret = read_command('v.what',
+        ret = read_command('v.what', env=env,
                            **cmdParams).strip()
     except CalledModuleError as e:
         raise ScriptError(e.msg)
 
-    if "LC_ALL" in os.environ:
-        os.environ["LC_ALL"] = locale
-
     data = list()
     if not ret:
         return data