Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
19ec6f4
pygrass: fix Python 3.14 pickling error in grid tests
SyedAhad01 Apr 1, 2026
1aca37a
Update python/grass/pygrass/modules/tests/grass_pygrass_grid_test.py
SyedAhad01 Apr 1, 2026
502ddda
Update python/grass/pygrass/modules/tests/grass_pygrass_grid_test.py
SyedAhad01 Apr 1, 2026
16c1a0d
Update python/grass/pygrass/modules/tests/grass_pygrass_grid_test.py
SyedAhad01 Apr 1, 2026
6e169e2
Update python/grass/pygrass/modules/tests/grass_pygrass_grid_test.py
SyedAhad01 Apr 1, 2026
8f2f7b1
Update python/grass/pygrass/modules/tests/grass_pygrass_grid_test.py
SyedAhad01 Apr 1, 2026
91f092c
fix: convert line endings to LF
SyedAhad01 Apr 2, 2026
0f6a319
fix: final formatting
SyedAhad01 Apr 2, 2026
3246d81
final fix: formatting applied
SyedAhad01 Apr 3, 2026
4d8e071
fix: use set for exitcode check
SyedAhad01 Apr 3, 2026
ca5876e
final fix: correct subprocess handling
SyedAhad01 Apr 3, 2026
ec23d60
fix: propagate GridModule errors via sys.exit in subprocess
SyedAhad01 Apr 3, 2026
9670574
fix: correct indentation and cleanup in test_cleans
SyedAhad01 Apr 3, 2026
a813fa7
fix: correct check condition in test_cleans for spawn vs fork
SyedAhad01 Apr 4, 2026
d1616f1
Update python/grass/pygrass/modules/tests/grass_pygrass_grid_test.py
SyedAhad01 Apr 5, 2026
e65d6b4
Update python/grass/pygrass/modules/tests/grass_pygrass_grid_test.py
SyedAhad01 Apr 5, 2026
f3360f6
Update python/grass/pygrass/modules/tests/grass_pygrass_grid_test.py
SyedAhad01 Apr 5, 2026
4d91b1b
fix: remove xfail_mp_spawn decorators as pickling is now fixed
SyedAhad01 Apr 5, 2026
9f5e665
fix: correct new_mset tuple to string in get_works
SyedAhad01 Apr 6, 2026
e05641f
fix: pass environment variables explicitly to subprocess workers for …
SyedAhad01 Apr 7, 2026
c80c67e
pygrass: fix copy_groups to use passed env instead of overwriting it
SyedAhad01 Apr 8, 2026
d6048dc
pygrass: fix copy_groups to use passed env instead of overwriting it
SyedAhad01 Apr 8, 2026
7906a4b
Update python/grass/pygrass/modules/grid/grid.py
SyedAhad01 Apr 8, 2026
ffbc286
trigger CI: verify ruff fixes
SyedAhad01 Apr 8, 2026
c9cbdbf
Update python/grass/pygrass/modules/grid/grid.py
SyedAhad01 Apr 8, 2026
96fb15f
pygrass: fix missing ctx assignment in GridModule.run
SyedAhad01 Apr 8, 2026
b3ad7f7
pygrass: add self.env and pass it consistently to workers
SyedAhad01 Apr 9, 2026
829c5db
fix: correct line endings
SyedAhad01 Apr 9, 2026
984519d
fix: use shell=False in Popen for security (Bandit B603)
SyedAhad01 Apr 10, 2026
3a29d20
fix: correct indentation and f-string in grid.py
SyedAhad01 Apr 10, 2026
6cb8b92
fix: check return code in cmd_exe to surface GRASS errors
SyedAhad01 Apr 11, 2026
b21a564
fix: Python 3.14 pickling compatibility in GridModule
SyedAhad01 Apr 12, 2026
834728b
fix: remove forced spawn context, keep pickling fixes for Python 3.14
SyedAhad01 Apr 12, 2026
66dcb3e
debug: print worker exitcode in test
SyedAhad01 Apr 12, 2026
05cf371
debug: print full traceback in subprocess
SyedAhad01 Apr 13, 2026
7c4d5c7
style: apply ruff and format fixes
SyedAhad01 Apr 14, 2026
1127d69
ci: temporarily add Python 3.14 to pytest workflow to verify fix
SyedAhad01 Apr 15, 2026
995cba0
fix: address ruff linting issues in GridModule and tests
SyedAhad01 Apr 19, 2026
5f641c3
ci: add Python 3.14 to pytest workflow
SyedAhad01 Apr 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ jobs:
python-version:
- "3.10"
- "3.13"
fail-fast: true
- "3.14"
fail-fast: false

runs-on: ${{ matrix.os }}
env:
Expand Down
29 changes: 22 additions & 7 deletions python/grass/pygrass/modules/grid/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,15 @@ def get_cmd(cmdd):
]


def _grass_worker_init():
"""Initialize GRASS environment in spawned worker processes (Windows/spawn)."""
gisbase = os.environ.get("GISBASE", "")
if gisbase and gisbase not in sys.path:
etc_python = os.path.join(gisbase, "etc", "python")
if etc_python not in sys.path:
sys.path.insert(0, etc_python)


def cmd_exe(args):
"""Create a mapset, and execute a cmd inside.

Expand Down Expand Up @@ -383,7 +392,7 @@ def cmd_exe(args):
# reset the inputs to
for key in mapnames:
inputs[key] = mapnames[key]
cmd["inputs"] = inputs.items()
cmd["inputs"] = list(inputs.items())
# set the region to the tile
sub.Popen(["g.region", "raster=%s" % key], shell=shell, env=env).wait()
else:
Expand Down Expand Up @@ -615,6 +624,8 @@ def get_works(self):
else:
ldst, gdst = self.mset.location, self.mset.gisdbase
cmd = self.module.get_dict()
cmd["inputs"] = list(cmd["inputs"])
cmd["outputs"] = list(cmd["outputs"])
groups = list(select(self.module.inputs, "group"))
for row, box_row in enumerate(self.bboxes):
for col, box in enumerate(box_row):
Expand All @@ -628,14 +639,12 @@ def get_works(self):
inms[key] = "%s@%s" % (self.inlist[key][indx], self.mset.name)
# set the computational region, prepare the region parameters
bbox = {
**{k[0]: str(v) for k, v in box.items()[:-2]},
**{k[0]: str(v) for k, v in list(box.items())[:-2]},
"nsres": "%f" % reg.nsres,
"ewres": "%f" % reg.ewres,
}

new_mset = (
self.msetstr % (self.start_row + row, self.start_col + col),
)
new_mset = self.msetstr % (self.start_row + row, self.start_col + col)
works.append(
(
bbox,
Expand Down Expand Up @@ -683,13 +692,19 @@ def _actual_run(self, patch):
for wrk in self.get_works():
cmd_exe(wrk)
else:
pool = mltp.Pool(processes=self.processes)
ctx = mltp.get_context("spawn")
ctx.set_executable(sys.executable)
pool = ctx.Pool(processes=self.processes, initializer=_grass_worker_init)
result = pool.map_async(cmd_exe, self.get_works())
result.wait()
pool.close()
pool.join()
if not result.successful():
raise RuntimeError(_("Execution of subprocesses was not successful"))
try:
result.get()
except Exception as e:
msg = f"Worker failed with: {e}"
raise RuntimeError(msg) from e

if patch:
if self.move:
Expand Down
10 changes: 10 additions & 0 deletions python/grass/pygrass/modules/interface/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,16 @@ def __str__(self):
def __repr__(self):
return "Module(%r)" % self.name

def __getstate__(self):
"""Make Module picklable by excluding unpicklable popen object."""
state = self.__dict__.copy()
state["_popen"] = None
return state

def __setstate__(self, state):
"""Restore Module from pickled state."""
self.__dict__.update(state)

@docstring_property(__doc__)
def __doc__(self):
"""{cmd_name}({cmd_params})"""
Expand Down
Loading
Loading