[GRASS-SVN] r66104 - in grass/trunk/lib/python/pygrass: . rpc rpc/testsuite vector

svn_grass at osgeo.org svn_grass at osgeo.org
Fri Sep 4 17:41:31 PDT 2015


Author: huhabla
Date: 2015-09-04 17:41:30 -0700 (Fri, 04 Sep 2015)
New Revision: 66104

Added:
   grass/trunk/lib/python/pygrass/rpc/
   grass/trunk/lib/python/pygrass/rpc/Makefile
   grass/trunk/lib/python/pygrass/rpc/__init__.py
   grass/trunk/lib/python/pygrass/rpc/base.py
   grass/trunk/lib/python/pygrass/rpc/testsuite/
   grass/trunk/lib/python/pygrass/rpc/testsuite/test_doctests.py
Modified:
   grass/trunk/lib/python/pygrass/Makefile
   grass/trunk/lib/python/pygrass/utils.py
   grass/trunk/lib/python/pygrass/vector/__init__.py
Log:
pygrass: Added rpc data provider that deliver WKB representations of vector maps, numpy image 
representations of raster maps and vector map attribute tables as dict using cats as keys.


Modified: grass/trunk/lib/python/pygrass/Makefile
===================================================================
--- grass/trunk/lib/python/pygrass/Makefile	2015-09-04 21:44:31 UTC (rev 66103)
+++ grass/trunk/lib/python/pygrass/Makefile	2015-09-05 00:41:30 UTC (rev 66104)
@@ -9,7 +9,7 @@
 
 MODULES = errors utils orderdict
 
-CLEAN_SUBDIRS = messages modules raster vector gis shell tests
+CLEAN_SUBDIRS = messages modules raster vector gis shell tests rpc
 
 PYFILES := $(patsubst %,$(DSTDIR)/%.py,$(MODULES) __init__)
 PYCFILES := $(patsubst %,$(DSTDIR)/%.pyc,$(MODULES) __init__)
@@ -22,6 +22,7 @@
 	-$(MAKE) -C gis || echo $(CURDIR)/gis >> $(ERRORLOG)
 	-$(MAKE) -C shell || echo $(CURDIR)/shell >> $(ERRORLOG)
 	-$(MAKE) -C tests || echo $(CURDIR)/tests >> $(ERRORLOG)
+	-$(MAKE) -C rpc || echo $(CURDIR)/gis >> $(ERRORLOG)
 
 $(PYDIR):
 	$(MKDIR) $@

Added: grass/trunk/lib/python/pygrass/rpc/Makefile
===================================================================
--- grass/trunk/lib/python/pygrass/rpc/Makefile	                        (rev 0)
+++ grass/trunk/lib/python/pygrass/rpc/Makefile	2015-09-05 00:41:30 UTC (rev 66104)
@@ -0,0 +1,32 @@
+MODULE_TOPDIR = ../../../..
+
+include $(MODULE_TOPDIR)/include/Make/Other.make
+include $(MODULE_TOPDIR)/include/Make/Python.make
+include $(MODULE_TOPDIR)/include/Make/Doxygen.make
+
+PYDIR = $(ETC)/python
+GDIR = $(PYDIR)/grass
+PGDIR = $(GDIR)/pygrass
+DSTDIR= $(PGDIR)/rpc
+
+MODULES = base
+
+PYFILES := $(patsubst %,$(DSTDIR)/%.py,$(MODULES) __init__)
+PYCFILES := $(patsubst %,$(DSTDIR)/%.pyc,$(MODULES) __init__)
+
+default: $(PYFILES) $(PYCFILES) $(GDIR)/__init__.py $(GDIR)/__init__.pyc
+
+$(PYDIR):
+	$(MKDIR) $@
+
+$(GDIR): | $(PYDIR)
+	$(MKDIR) $@
+
+$(DSTDIR): | $(GDIR)
+	$(MKDIR) $@
+
+$(DSTDIR)/%: % | $(DSTDIR)
+	$(INSTALL_DATA) $< $@
+
+#doxygen:
+DOXNAME = pythonpygrass

Added: grass/trunk/lib/python/pygrass/rpc/__init__.py
===================================================================
--- grass/trunk/lib/python/pygrass/rpc/__init__.py	                        (rev 0)
+++ grass/trunk/lib/python/pygrass/rpc/__init__.py	2015-09-05 00:41:30 UTC (rev 66104)
@@ -0,0 +1,424 @@
+# -*- coding: utf-8 -*-
+"""
+Fast and exit-safe interface to PyGRASS Raster and Vector layer
+using multiprocessing
+
+(C) 2015 by the GRASS Development Team
+This program is free software under the GNU General Public
+License (>=v2). Read the file COPYING that comes with GRASS
+for details.
+
+:authors: Soeren Gebbert
+"""
+
+import time
+import threading
+import sys
+from multiprocessing import Process, Lock, Pipe
+from ctypes import *
+
+from grass.exceptions import FatalError
+from grass.pygrass.vector import *
+from grass.pygrass.raster import *
+import grass.lib.gis as libgis
+from base import RPCServerBase
+from grass.pygrass.gis.region import Region
+import logging
+
+###############################################################################
+###############################################################################
+
+class RPCDefs(object):
+    # Function identifier and index
+    STOP = 0
+    GET_VECTOR_TABLE_AS_DICT = 1
+    GET_VECTOR_FEATURES_AS_WKB = 2
+    GET_RASTER_IMAGE_AS_NP = 3
+    G_FATAL_ERROR = 14
+
+
+def _get_raster_image_as_np(lock, conn, data):
+    """Convert a raster map into an image and return 
+       a numpy array with RGB or Gray values.
+       
+       :param lock: A multiprocessing.Lock instance
+       :param conn: A multiprocessing.Pipe instance used to send True or False
+       :param data: The list of data entries [function_id, raster_name, extent, color]
+    """
+    raster_name = data[1]
+    extent = data[2]
+    color = data[3]
+
+    rast = RasterRow(raster_name)
+    array = None
+    
+    if rast.exist():
+        
+        reg = Region()
+        reg.from_rast(raster_name)
+        
+        if extent is not None:
+            if "north" in extent:
+                reg.north = extent["north"]
+            if "south" in extent:
+                reg.south = extent["south"]
+            if "east" in extent:
+                reg.east =  extent["east"]
+            if "west" in extent:
+                reg.west =  extent["west"]
+            if "rows" in extent:
+                reg.rows =  extent["rows"]
+            if "cols" in extent:
+                reg.cols =  extent["cols"]
+            reg.adjust()
+
+        array = raster2numpy_img(raster_name, reg, color)
+
+    conn.send(array)
+    
+def _get_vector_table_as_dict(lock, conn, data):
+    """Get the table of a vector map layer as dictionary
+
+       The value to be send via pipe is True in case the map exists and False
+       if not.
+
+       :param lock: A multiprocessing.Lock instance
+       :param conn: A multiprocessing.Pipe instance used to send True or False
+       :param data: The list of data entries [function_id, name, where]
+
+    """
+    name = data[1]
+    where = data[2]
+    layer = VectorTopo(name)
+    ret = None
+    
+    if layer.exist() is True:        
+        layer.open("r")
+        columns = None
+        table = None
+        if layer.table is not None:
+            columns = layer.table.columns
+            table = layer.table_to_dict(where=where)
+        layer.close()
+        
+        ret = {}
+        ret["table"] = table
+        ret["columns"] = columns
+
+    conn.send(ret)
+
+def _get_vector_features_as_wkb_list(lock, conn, data):
+    """Return vector layer features as wkb list
+    
+       supported feature types:
+       point, centroid, line, boundary, area
+
+       The value to be send via pipe is True in case the map exists and False
+       if not.
+
+       :param lock: A multiprocessing.Lock instance
+       :param conn: A multiprocessing.Pipe instance used to send True or False
+       :param data: The list of data entries [function_id,name,extent,
+                                              feature_type, field]
+
+    """
+    name = data[1]
+    extent = data[2]
+    feature_type = data[3]
+    field = data[4]
+    
+    wkb_list = None
+    bbox = None
+    
+    layer = VectorTopo(name)
+    
+    try:
+        if layer.exist() is True:
+            if extent is not None:
+                bbox = basic.Bbox(north=extent["north"],
+                                  south=extent["south"],
+                                  east=extent["east"],
+                                  west=extent["west"])
+                logging.warning(str(bbox))
+            layer.open("r")
+            if feature_type.lower() == "area":
+                wkb_list = layer.areas_to_wkb_list(bbox=bbox, field=field)
+            else:
+                wkb_list = layer.features_to_wkb_list(bbox=bbox,
+                                                      feature_type=feature_type, 
+                                                      field=field)
+            layer.close()
+    except Exception, e:
+        print(e)
+    
+    conn.send(wkb_list)
+
+###############################################################################
+
+def _fatal_error(lock, conn, data):
+    """Calls G_fatal_error()"""
+    libgis.G_fatal_error("Fatal Error in C library server")
+
+
+###############################################################################
+
+def _stop(lock, conn, data):
+    conn.close()
+    lock.release()
+    sys.exit()
+
+###############################################################################
+
+def data_provider_server(lock, conn):
+    """The PyGRASS data provider server designed to be a target for
+       multiprocessing.Process
+
+       :param lock: A multiprocessing.Lock
+       :param conn: A multiprocessing.Pipe
+    """
+
+    def error_handler(data):
+        """This function will be called in case of a fatal error in libgis"""
+        #sys.stderr.write("Error handler was called\n")
+        # We send an exeption that will be handled in
+        # the parent process, then close the pipe
+        # and release any possible lock
+        conn.send(FatalError())
+        conn.close()
+        lock.release()
+
+    CALLBACK = CFUNCTYPE(c_void_p, c_void_p)
+    CALLBACK.restype = c_void_p
+    CALLBACK.argtypes = c_void_p
+
+    cerror_handler = CALLBACK(error_handler)
+
+    libgis.G_add_error_handler(cerror_handler, None)
+
+    # Crerate the function array
+    functions = [0]*15
+    functions[RPCDefs.GET_VECTOR_TABLE_AS_DICT] = _get_vector_table_as_dict
+    functions[RPCDefs.GET_VECTOR_FEATURES_AS_WKB] = _get_vector_features_as_wkb_list
+    functions[RPCDefs.GET_RASTER_IMAGE_AS_NP] = _get_raster_image_as_np
+    functions[RPCDefs.STOP] = _stop
+    functions[RPCDefs.G_FATAL_ERROR] = _fatal_error
+
+    while True:
+        # Avoid busy waiting
+        conn.poll(None)
+        data = conn.recv()
+        lock.acquire()
+        functions[data[0]](lock, conn, data)
+        lock.release()
+
+test_vector_name="data_provider_vector_map"
+test_raster_name="data_provider_raster_map"
+
+class DataProvider(RPCServerBase):
+    """Fast and exit-safe interface to PyGRASS data delivery functions
+
+    """
+    def __init__(self):
+        RPCServerBase.__init__(self)
+        
+    def start_server(self):
+        """This function must be re-implemented in the subclasses
+        """
+        self.client_conn, self.server_conn = Pipe(True)
+        self.lock = Lock()
+        self.server = Process(target=data_provider_server, args=(self.lock,
+                                                             self.server_conn))
+        self.server.daemon = True
+        self.server.start()
+        
+    def get_raster_image_as_np(self, name, extent=None, color="RGB"):
+        """Return the attribute table of a vector map as dictionary.
+        
+           See documentation of: pygrass.raster.raster2numpy_img
+                      
+           Usage:
+
+           .. code-block:: python
+
+            >>> from grass.pygrass.rpc import DataProvider
+            >>> provider = DataProvider()
+            >>> ret = provider.get_raster_image_as_np(name=test_raster_name)
+            >>> len(ret)
+            64
+            
+            >>> extent = {"north":30, "south":10, "east":30, "west":10, 
+            ...           "rows":2, "cols":2}
+            >>> ret = provider.get_raster_image_as_np(name=test_raster_name, 
+            ...                                       extent=extent)
+            >>> len(ret)
+            16
+            >>> ret           # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
+            array([169, 255,   0, 255, 255,   0,  46, 255, 208, 255,   
+                   0, 255, 255, 0,  84, 255], dtype=uint8)
+                   
+            >>> extent = {"rows":3, "cols":1}
+            >>> ret = provider.get_raster_image_as_np(name=test_raster_name, 
+            ...                                       extent=extent)
+            >>> len(ret)
+            12
+            >>> ret           # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
+            array([255,   0,   7, 255, 255,   0,  84, 255, 255,   
+                   0, 123, 255], dtype=uint8)
+            >>> provider.stop()
+        
+            ..
+        """
+        self.check_server()
+        self.client_conn.send([RPCDefs.GET_RASTER_IMAGE_AS_NP,
+                               name, extent, color])
+        return self.safe_receive("get_raster_image_as_np")
+        
+    def get_vector_table_as_dict(self, name, where=None):
+        """Return the attribute table of a vector map as dictionary.
+        
+           See documentation of: pygrass.vector.VectorTopo::table_to_dict
+                      
+           Usage:
+
+           .. code-block:: python
+
+            >>> from grass.pygrass.rpc import DataProvider
+            >>> provider = DataProvider()
+            >>> ret = provider.get_vector_table_as_dict(name=test_vector_name)
+            >>> ret["table"]
+            {1: [1, u'point', 1.0], 2: [2, u'line', 2.0], 3: [3, u'centroid', 3.0]}
+            >>> ret["columns"]
+            Columns([(u'cat', u'INTEGER'), (u'name', u'varchar(50)'), (u'value', u'double precision')])
+            >>> ret = provider.get_vector_table_as_dict(name=test_vector_name,
+            ...                                           where="value > 1")
+            >>> ret["table"]
+            {2: [2, u'line', 2.0], 3: [3, u'centroid', 3.0]}
+            >>> ret["columns"]
+            Columns([(u'cat', u'INTEGER'), (u'name', u'varchar(50)'), (u'value', u'double precision')])
+            >>> provider.get_vector_table_as_dict(name="no_map",
+            ...                                   where="value > 1")
+            >>> provider.stop()
+        
+            ..
+        """
+        self.check_server()
+        self.client_conn.send([RPCDefs.GET_VECTOR_TABLE_AS_DICT,
+                               name, where])
+        return self.safe_receive("get_vector_table_as_dict")
+
+    def get_vector_features_as_wkb_list(self, name, extent=None,
+                                        feature_type="point", field=1):
+        """Return the features of a vector map as wkb list.
+        
+           :param extent: A dictionary of {"north":double, "south":double,
+                                           "east":double, "west":double}
+           :param feature_type: point, centroid, line, boundary or area
+        
+           See documentation: pygrass.vector.VectorTopo::features_to_wkb_list
+                              pygrass.vector.VectorTopo::areas_to_wkb_list
+                              
+
+           Usage:
+
+           .. code-block:: python
+
+            >>> from grass.pygrass.rpc import DataProvider
+            >>> provider = DataProvider()
+            >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
+            ...                                                extent=None,
+            ...                                                feature_type="point")
+            >>> for entry in wkb:
+            ...     f_id, cat, string = entry
+            ...     print(f_id, cat, len(string))
+            1 1 21
+            2 1 21
+            3 1 21
+
+            >>> extent = {"north":6.6, "south":5.5, "east":14.5, "west":13.5}
+            >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
+            ...                                                extent=extent,
+            ...                                                feature_type="point")
+            >>> for entry in wkb:
+            ...     f_id, cat, string = entry
+            ...     print(f_id, cat, len(string))
+            3 1 21
+
+            >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
+            ...                                                extent=None,
+            ...                                                feature_type="line")
+            >>> for entry in wkb:
+            ...     f_id, cat, string = entry
+            ...     print(f_id, cat, len(string))
+            4 2 57
+            5 2 57
+            6 2 57
+
+
+            >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
+            ...                                                extent=None,
+            ...                                                feature_type="centroid")
+            >>> for entry in wkb:
+            ...     f_id, cat, string = entry
+            ...     print(f_id, cat, len(string))
+            19 3 21
+            18 3 21
+            20 3 21
+            21 3 21
+
+            >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
+            ...                                                extent=None,
+            ...                                                feature_type="area")
+            >>> for entry in wkb:
+            ...     f_id, cat, string = entry
+            ...     print(f_id, cat, len(string))
+            1 3 225
+            2 3 141
+            3 3 93
+            4 3 141
+
+            >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
+            ...                                                extent=None,
+            ...                                                feature_type="boundary")
+            >>> for entry in wkb:
+            ...     f_id, cat, string = entry
+            ...     print(f_id, cat, len(string))
+            10 None 41
+            7 None 41
+            8 None 41
+            9 None 41
+            11 None 89
+            12 None 41
+            14 None 41
+            13 None 41
+            17 None 41
+            15 None 41
+            16 None 41
+               
+            >>> provider.stop()
+            
+            ..
+        """
+        self.check_server()
+        self.client_conn.send([RPCDefs.GET_VECTOR_FEATURES_AS_WKB,
+                               name, extent, feature_type, field])
+        return self.safe_receive("get_vector_features_as_wkb_list")
+
+
+if __name__ == "__main__":
+    import doctest
+    from grass.pygrass import utils
+    from grass.pygrass.modules import Module
+    Module("g.region", n=40, s=0, e=40, w=0, res=10)
+    Module("r.mapcalc", expression="%s = row() + (10 * col())"%(test_raster_name),
+                             overwrite=True)
+    utils.create_test_vector_map(test_vector_name)
+    
+    doctest.testmod()
+
+    """Remove the generated maps, if exist"""
+    mset = utils.get_mapset_raster(test_raster_name, mapset='')
+    if mset:
+        Module("g.remove", flags='f', type='raster', name=test_raster_name)
+    mset = utils.get_mapset_vector(test_vector_name, mapset='')
+    if mset:
+        Module("g.remove", flags='f', type='vector', name=test_vector_name)

Added: grass/trunk/lib/python/pygrass/rpc/base.py
===================================================================
--- grass/trunk/lib/python/pygrass/rpc/base.py	                        (rev 0)
+++ grass/trunk/lib/python/pygrass/rpc/base.py	2015-09-05 00:41:30 UTC (rev 66104)
@@ -0,0 +1,184 @@
+# -*- coding: utf-8 -*-
+"""
+Fast and exit-safe interface to PyGRASS Raster and Vector layer
+using multiprocessing
+
+(C) 2015 by the GRASS Development Team
+This program is free software under the GNU General Public
+License (>=v2). Read the file COPYING that comes with GRASS
+for details.
+
+:authors: Soeren Gebbert
+"""
+
+from grass.exceptions import FatalError
+import time
+import threading
+import sys
+from multiprocessing import Process, Lock, Pipe
+import logging
+
+###############################################################################
+
+def dummy_server(lock, conn):
+    """Dummy server process
+
+       :param lock: A multiprocessing.Lock
+       :param conn: A multiprocessing.Pipe
+    """
+
+    while True:
+        # Avoid busy waiting
+        conn.poll(None)
+        data = conn.recv()
+        lock.acquire()
+        if data[0] == 0:
+            conn.close()
+            lock.release()
+            sys.exit()
+        if data[0] == 1:
+            raise Exception("Server process intentionally killed by exception")
+        lock.release()
+
+class RPCServerBase(object):
+    """This is the base class for send and receive RPC server
+       It uses a Pipe for IPC.
+    
+    
+        >>> import grass.script as gscript
+        >>> from grass.pygrass.rpc.base import RPCServerBase
+        >>> provider = RPCServerBase()
+        
+        >>> provider.is_server_alive()
+        True
+        
+        >>> provider.is_check_thread_alive()
+        True
+
+        >>> provider.stop()
+        >>> provider.is_server_alive()
+        False
+        
+        >>> provider.is_check_thread_alive()
+        False
+        
+        >>> provider = RPCServerBase()
+        >>> provider.is_server_alive()
+        True
+        >>> provider.is_check_thread_alive()
+        True
+        
+        Kill the server process with an exception, it should restart
+        
+        >>> provider.client_conn.send([1])
+        >>> provider.is_server_alive()
+        True
+        
+        >>> provider.is_check_thread_alive()
+        True
+
+    """
+    
+    def __init__(self):
+        self.client_conn = None
+        self.server_conn = None
+        self.queue = None
+        self.server = None
+        self.checkThread = None
+        self.threadLock = threading.Lock()
+        self.start_server()
+        self.start_checker_thread()
+        self.stopThread = False
+        
+    def is_server_alive(self):
+        return self.server.is_alive()
+    
+    def is_check_thread_alive(self):
+        return self.checkThread.is_alive()
+
+    def start_checker_thread(self):
+        if self.checkThread is not None and self.checkThread.is_alive():
+            self.stop_checker_thread()
+
+        self.checkThread = threading.Thread(target=self.thread_checker)
+        self.checkThread.daemon = True
+        self.stopThread = False
+        self.checkThread.start()
+
+    def stop_checker_thread(self):
+        self.threadLock.acquire()
+        self.stopThread = True
+        self.threadLock.release()
+        self.checkThread.join(None)
+
+    def thread_checker(self):
+        """Check every 200 micro seconds if the server process is alive"""
+        while True:
+            time.sleep(0.2)
+            #sys.stderr.write("Check server process\n")
+            self._check_restart_server()
+            self.threadLock.acquire()
+            if self.stopThread == True:
+                #sys.stderr.write("Stop thread\n")
+                self.threadLock.release()
+                return
+            self.threadLock.release()
+
+    def start_server(self):
+        """This function must be re-implemented in the subclasses
+        """
+        self.client_conn, self.server_conn = Pipe(True)
+        self.lock = Lock()
+        self.server = Process(target=dummy_server, args=(self.lock,
+                                                             self.server_conn))
+        self.server.daemon = True
+        self.server.start()
+
+    def check_server(self):
+        self._check_restart_server()
+
+    def _check_restart_server(self):
+        """Restart the server if it was terminated
+        """
+        self.threadLock.acquire()
+        if self.server.is_alive() is True:
+            self.threadLock.release()
+            return
+        self.client_conn.close()
+        self.server_conn.close()
+        self.start_server()
+
+        logging.warning("Needed to restart the libgis server")
+
+        self.threadLock.release()
+
+    def safe_receive(self, message):
+        """Receive the data and throw an FatalError exception in case the server
+           process was killed and the pipe was closed by the checker thread"""
+        try:
+            ret = self.client_conn.recv()
+            if isinstance(ret,  FatalError):
+               raise FatalError()
+            return ret
+        except (EOFError,  IOError,  FatalError):
+            # The pipe was closed by the checker thread because
+            # the server process was killed
+            raise FatalError(message)
+
+    def stop(self):
+        """Stop the check thread, the libgis server and close the pipe
+
+           This method should be called at exit using the package atexit
+        """
+        #sys.stderr.write("###### Stop was called\n")
+        self.stop_checker_thread()
+        if self.server is not None and self.server.is_alive():
+            self.client_conn.send([0, ])
+            self.server.join()
+        if self.client_conn is not None:
+            self.client_conn.close()
+
+
+if __name__ == "__main__":
+    import doctest
+    doctest.testmod()

Added: grass/trunk/lib/python/pygrass/rpc/testsuite/test_doctests.py
===================================================================
--- grass/trunk/lib/python/pygrass/rpc/testsuite/test_doctests.py	                        (rev 0)
+++ grass/trunk/lib/python/pygrass/rpc/testsuite/test_doctests.py	2015-09-05 00:41:30 UTC (rev 66104)
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+"""
+Tests checkers
+"""
+
+import doctest
+
+import grass.gunittest.case
+import grass.gunittest.main
+import grass.gunittest.utils
+
+import grass.pygrass.rpc as pygrpc
+
+
+# doctest does not allow changing the base classes of test case, skip test case
+# and test suite, so we need to create a new type which inherits from our class
+# and contains doctest's methods
+# the alternative is to copy 500 from doctest and change what is needed
+# (this might be necessary anyway beacuse of the reports and stdout and stderr)
+doctest.DocFileCase = type('DocFileCase',
+                           (grass.gunittest.case.TestCase,),
+                           dict(doctest.DocFileCase.__dict__))
+doctest.SkipDocTestCase = type('SkipDocTestCase',
+                               (grass.gunittest.case.TestCase,),
+                               dict(doctest.SkipDocTestCase.__dict__))
+
+
+def load_tests(loader, tests, ignore):
+    # TODO: this must be somewhere when doctest is called, not here
+    # TODO: ultimate solution is not to use _ as a buildin in lib/python
+    # for now it is the only place where it works
+    grass.gunittest.utils.do_doctest_gettext_workaround()
+    # this should be called at some top level
+
+    from grass.pygrass import utils
+    from grass.pygrass.modules import Module
+    Module("g.region", n=40, s=0, e=40, w=0, res=10)
+    Module("r.mapcalc", expression="%s = row() + (10 * col())"%(pygrpc.test_raster_name),
+                             overwrite=True)
+    utils.create_test_vector_map(pygrpc.test_vector_name)
+    
+
+    tests.addTests(doctest.DocTestSuite(pygrpc))
+    tests.addTests(doctest.DocTestSuite(pygrpc.base))
+    return tests
+
+
+if __name__ == '__main__':
+    grass.gunittest.main.test()

Modified: grass/trunk/lib/python/pygrass/utils.py
===================================================================
--- grass/trunk/lib/python/pygrass/utils.py	2015-09-04 21:44:31 UTC (rev 66103)
+++ grass/trunk/lib/python/pygrass/utils.py	2015-09-05 00:41:30 UTC (rev 66104)
@@ -225,8 +225,8 @@
     True
     >>> get_raster_for_points(fire, ele, column=test_raster_name, region=region)
     True
-    >>> fire.table.filters.select('LABEL', test_raster_name)
-    Filters(u'SELECT LABEL, Utils_test_raster FROM test_vect_2;')
+    >>> fire.table.filters.select('name', test_raster_name)
+    Filters(u'SELECT name, Utils_test_raster FROM test_vect_2;')
     >>> cur = fire.table.execute()
     >>> r = cur.fetchall()
     >>> r[0]                                        # doctest: +ELLIPSIS
@@ -285,8 +285,6 @@
 
 def get_lib_path(modname, libname):
     """Return the path of the libname contained in the module.
-
-    >>> get_lib_path(modname='r.modis', libname='libmodis')
     """
     from os.path import isdir, join
     from os import getenv

Modified: grass/trunk/lib/python/pygrass/vector/__init__.py
===================================================================
--- grass/trunk/lib/python/pygrass/vector/__init__.py	2015-09-04 21:44:31 UTC (rev 66103)
+++ grass/trunk/lib/python/pygrass/vector/__init__.py	2015-09-05 00:41:30 UTC (rev 66104)
@@ -716,7 +716,7 @@
         
         supported = ['point', 'line', 'boundary', 'centroid']
         
-        if feature_type not in supported:
+        if feature_type.lower() not in supported:
             raise GrassError("Unsupported feature type <%s>, "\
                              "supported are <%s>"%(feature_type, 
                                                    ",".join(supported)))
@@ -724,7 +724,7 @@
         if bbox is None:
             bbox = self.bbox()
         
-        bboxlist = self.find_by_bbox.geos(bbox, type=feature_type, 
+        bboxlist = self.find_by_bbox.geos(bbox, type=feature_type.lower(), 
                                           bboxlist_only = True)
         
         if bboxlist is not None and len(bboxlist) > 0:



More information about the grass-commit mailing list