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/pyproject.toml b/pyproject.toml index c9b8a7bb24a..1f7c998cdb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -439,7 +439,6 @@ known-first-party = ["grass"] "python/grass/pygrass/modules/grid/testsuite/test_*_modules_grid_doctests.py" = ["F401"] "python/grass/pygrass/modules/interface/env.py" = ["PLW1514", "SIM115"] "python/grass/pygrass/modules/interface/parameter.py" = ["PLR6201"] -"python/grass/pygrass/modules/testsuite/test_pygrass_modules_doctests.py" = ["F401"] "python/grass/pygrass/raster/category.py" = ["FURB189", "PLW1514"] "python/grass/pygrass/raster/segment.py" = ["PLW1514", "SIM115"] "python/grass/pygrass/tests/*.py" = ["SIM115"] diff --git a/python/grass/pygrass/modules/grid/grid.py b/python/grass/pygrass/modules/grid/grid.py index e3c3c3ebdbc..c28ec3c9c09 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, @@ -651,10 +651,17 @@ 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 + if inm.typedesc not in {"raster", "vector"} or not inm.value: + continue + find_mapset = ( + get_mapset_raster if inm.typedesc == "raster" else get_mapset_vector + ) + names = inm.value if isinstance(inm.value, list) else [inm.value] + qualified = [ + name if "@" in name else "%s@%s" % (name, find_mapset(name)) + for name in names + ] + inm.value = qualified if isinstance(inm.value, list) else qualified[0] def run(self, patch=True, clean=True): """Run the GRASS command diff --git a/python/grass/pygrass/modules/grid/testsuite/test_pygrass_modules_grid_doctests.py b/python/grass/pygrass/modules/grid/testsuite/test_pygrass_modules_grid_doctests.py index 5034c2de3f9..ebea878fec3 100644 --- a/python/grass/pygrass/modules/grid/testsuite/test_pygrass_modules_grid_doctests.py +++ b/python/grass/pygrass/modules/grid/testsuite/test_pygrass_modules_grid_doctests.py @@ -8,7 +8,7 @@ import grass.gunittest.main import grass.gunittest.utils -import grass.pygrass.modules as gmodules +from grass.pygrass.modules.grid import grid, patch, split # doctest does not allow changing the base classes of test case, skip test case @@ -31,8 +31,9 @@ def load_tests(loader, tests, ignore): # TODO: ultimate solution is not to use _ as a builtin in lib/python # for now it is the only place where it works grass.gunittest.utils.do_doctest_gettext_workaround() - - # tests.addTests(doctest.DocTestSuite(gmodules.shortcuts)) + tests.addTests(doctest.DocTestSuite(grid)) + tests.addTests(doctest.DocTestSuite(patch)) + tests.addTests(doctest.DocTestSuite(split)) return tests diff --git a/python/grass/pygrass/modules/testsuite/test_pygrass_modules_doctests.py b/python/grass/pygrass/modules/testsuite/test_pygrass_modules_doctests.py deleted file mode 100644 index 77b49fef82f..00000000000 --- a/python/grass/pygrass/modules/testsuite/test_pygrass_modules_doctests.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -Tests checkers -""" - -import doctest - -import grass.gunittest.case -import grass.gunittest.main -import grass.gunittest.utils - -from grass.pygrass.modules import shortcuts, grid, interface - - -# 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 because 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 builtin 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 - tests.addTests(doctest.DocTestSuite(interface)) - tests.addTests(doctest.DocTestSuite(grid.grid)) - tests.addTests(doctest.DocTestSuite(grid.patch)) - tests.addTests(doctest.DocTestSuite(grid.split)) - # tests.addTests(doctest.DocTestSuite(shortcuts)) - return tests - - -if __name__ == "__main__": - grass.gunittest.main.test() 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..e1c3c6413d8 100644 --- a/python/grass/pygrass/raster/rowio.py +++ b/python/grass/pygrass/raster/rowio.py @@ -13,20 +13,23 @@ 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) +# The callback type must be the one the generated binding declares for +# Rowio_setup(). It takes the row length as a fourth argument, and an equivalent +# type created with ctypes.CFUNCTYPE() is rejected by ctypes. +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..aaa969f87f4 100644 --- a/python/grass/pygrass/utils.py +++ b/python/grass/pygrass/utils.py @@ -278,12 +278,16 @@ 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 opens the vector map if it is not open, so close it before + opening it again. A map which is still open cannot be removed on Windows. Add a new column and sample again + >>> vect.close() >>> vect.open("r") >>> vect.table.columns.add(test_raster_name, "double precision") >>> vect.table.conn.commit() 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()