diff --git a/.gunittest.cfg b/.gunittest.cfg index 3655f2d4e37..b6ecda06c3a 100644 --- a/.gunittest.cfg +++ b/.gunittest.cfg @@ -5,9 +5,6 @@ # but it includes mainly tests which can (and should) be fixed. exclude = gui/wxpython/core/testsuite/toolboxes.sh - python/grass/gunittest/testsuite/test_gunitest_doctests.py - python/grass/pygrass/raster/testsuite/test_pygrass_raster_doctests.py - python/grass/pygrass/rpc/testsuite/test_pygrass_rpc_doctests.py python/grass/temporal/testsuite/unittests_temporal_raster_algebra_equal_ts.py python/grass/temporal/testsuite/unittests_temporal_raster_conditionals_complement_else.py raster/r.in.lidar/testsuite/test_base_resolution.sh diff --git a/python/grass/pygrass/modules/grid/grid.py b/python/grass/pygrass/modules/grid/grid.py index e3c3c3ebdbc..a54086dab62 100644 --- a/python/grass/pygrass/modules/grid/grid.py +++ b/python/grass/pygrass/modules/grid/grid.py @@ -12,7 +12,7 @@ from grass.pygrass.gis import Mapset, Location from grass.pygrass.gis.region import Region from grass.pygrass.modules import Module -from grass.pygrass.utils import get_mapset_raster, findmaps +from grass.pygrass.utils import findmaps, get_mapset_raster, get_mapset_vector from grass.pygrass.modules.grid.split import ( split_region_tiles, @@ -650,11 +650,27 @@ def get_works(self): def define_mapset_inputs(self): """Add the mapset information to the input maps""" for inmap in self.module.inputs: - inm = self.module.inputs[inmap] - if inm.type in {"raster", "vector"} and inm.value: - if "@" not in inm.value: - mset = get_mapset_raster(inm.value) - inm.value += "@%s" % mset + param = self.module.inputs[inmap] + if param.typedesc == "raster": + get_mapset = get_mapset_raster + elif param.typedesc == "vector": + get_mapset = get_mapset_vector + else: + continue + if not param.value: + continue + + if isinstance(param.value, list): + param.value = [self._with_mapset(n, get_mapset) for n in param.value] + else: + param.value = self._with_mapset(param.value, get_mapset) + + @staticmethod + def _with_mapset(name, get_mapset): + """Return the map name qualified with its mapset, e.g. ``name@mapset``""" + if "@" in name: + return name + return "%s@%s" % (name, get_mapset(name)) def run(self, patch=True, clean=True): """Run the GRASS command diff --git a/python/grass/pygrass/raster/buffer.py b/python/grass/pygrass/raster/buffer.py index 67ba86563bc..ce8aa36b76c 100644 --- a/python/grass/pygrass/raster/buffer.py +++ b/python/grass/pygrass/raster/buffer.py @@ -43,12 +43,16 @@ def __array_finalize__(self, obj): self.pointer_type = getattr(obj, "pointer_type", None) self.p = getattr(obj, "p", None) - def __array_wrap__(self, out_arr, context=None): + def __array_wrap__(self, out_arr, context=None, *args): """See: https://numpy.org/doc/stable/user/basics.subclassing.html#array-wrap-for-ufuncs-and-other-functions """ if out_arr.dtype == bool: # there is not support for boolean maps, so convert into integer out_arr = out_arr.astype(np.int32) - out_arr.p = out_arr.ctypes.data_as(out_arr.pointer_type) - return np.ndarray.__array_wrap__(self, out_arr, context) + # NumPy 2 passes a plain ndarray (and an additional return_scalar + # argument), so the pointer can be set only after wrapping into Buffer. + wrapped = np.ndarray.__array_wrap__(self, out_arr, context, *args) + if isinstance(wrapped, Buffer): + wrapped.p = wrapped.ctypes.data_as(wrapped.pointer_type) + return wrapped diff --git a/python/grass/pygrass/raster/rowio.py b/python/grass/pygrass/raster/rowio.py index a49da7efe26..72cb54b6533 100644 --- a/python/grass/pygrass/raster/rowio.py +++ b/python/grass/pygrass/raster/rowio.py @@ -13,20 +13,25 @@ from grass.pygrass.raster.raster_type import TYPE as RTYPE -CMPFUNC = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_void_p, ctypes.c_int) +# Rowio_setup() takes a callback which reads one row into a buffer. The +# generated binding declares the type of that callback (a ctypes function +# prototype with the arguments fd, buffer, row and length). The functions below +# have to be wrapped with this exact type: a prototype made here with +# ctypes.CFUNCTYPE() is a different class, and ctypes rejects it as an argument. +CMPFUNC = librowio.Rowio_setup.argtypes[4] -def getmaprow_CELL(fd, buf, row): +def getmaprow_CELL(fd, buf, row, length): librast.Rast_get_c_row(fd, ctypes.cast(buf, ctypes.POINTER(librast.CELL)), row) return 1 -def getmaprow_FCELL(fd, buf, row): +def getmaprow_FCELL(fd, buf, row, length): librast.Rast_get_f_row(fd, ctypes.cast(buf, ctypes.POINTER(librast.FCELL)), row) return 1 -def getmaprow_DCELL(fd, buf, row): +def getmaprow_DCELL(fd, buf, row, length): librast.Rast_get_d_row(fd, ctypes.cast(buf, ctypes.POINTER(librast.DCELL)), row) return 1 diff --git a/python/grass/pygrass/utils.py b/python/grass/pygrass/utils.py index 344e4237ea4..0467c2a6006 100644 --- a/python/grass/pygrass/utils.py +++ b/python/grass/pygrass/utils.py @@ -278,9 +278,14 @@ def get_raster_for_points(poi_vector, raster, column=None, region=None): >>> l = get_raster_for_points(vect, ele, region=region) >>> l[0] # doctest: +ELLIPSIS - (1, 10.0, 6.0, 1) + (1, 10.0, 6.0, ...) >>> l[1] # doctest: +ELLIPSIS - (2, 12.0, 6.0, 1) + (2, 12.0, 6.0, ...) + + The function opened the vector map because it was not open. Close it + again, since a map which is still open cannot be removed on Windows. + + >>> vect.close() Add a new column and sample again diff --git a/python/grass/pygrass/vector/table.py b/python/grass/pygrass/vector/table.py index ea54af90841..f52d734615c 100644 --- a/python/grass/pygrass/vector/table.py +++ b/python/grass/pygrass/vector/table.py @@ -207,6 +207,27 @@ def __init__(self, tname, connection, key="cat"): self.odict = None self.update_odict() + def __getstate__(self): + """Return the state for pickling without the database connection + + The connection cannot be pickled, so an unpickled object is a snapshot + of the columns which can be read but not updated. + + >>> import pickle + >>> import sqlite3 + >>> from grass.pygrass.vector.table import get_path + >>> path = "$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db" + >>> cols = Columns(test_vector_name, sqlite3.connect(get_path(path))) + >>> copy = pickle.loads(pickle.dumps(cols)) + >>> copy == cols + True + >>> copy.conn is None + True + """ + state = self.__dict__.copy() + state["conn"] = None + return state + def __contains__(self, item): return item in self.names()