Skip to content

Commit f16cd65

Browse files
authored
grass.script: add Activate and Deactivate methods to RegionManager and MaskManager (OSGeo#6268)
1 parent 1565d7d commit f16cd65

File tree

3 files changed

+171
-6
lines changed

3 files changed

+171
-6
lines changed

python/grass/script/raster.py

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,18 @@ class MaskManager:
294294
>>> with gs.MaskManager(mask_name="state_boundary"):
295295
... gs.parse_command("r.univar", map="elevation", format="json")
296296
297+
Example using explicit activate and deactivate:
298+
299+
>>> manager = gs.MaskManager()
300+
>>> manager.activate()
301+
>>> try:
302+
... # Create mask with r.mask
303+
... gs.run_command("r.mask", raster="state_boundary")
304+
... gs.parse_command("r.univar", map="elevation", format="json")
305+
... finally:
306+
... manager.deactivate()
307+
308+
297309
Note the difference between using the name of an existing raster map directly
298310
and using *r.mask* to create a new mask. Both zeros and NULL values are used
299311
to represent mask resulting in NULL cells, while *r.mask*
@@ -368,20 +380,24 @@ def __init__(
368380
else:
369381
self.mask_name = mask_name
370382
self._remove = False if remove is None else remove
383+
self._active = False
371384

372-
def __enter__(self):
385+
def activate(self):
373386
"""Set mask in the given environment.
374387
375388
Sets the `GRASS_MASK` environment variable to the provided or
376389
generated mask name.
377390
378391
:return: Returns the MaskManager instance.
379392
"""
393+
if self._active:
394+
return None
380395
self._original_value = self.env.get("GRASS_MASK")
381396
self.env["GRASS_MASK"] = self.mask_name
397+
self._active = True
382398
return self
383399

384-
def __exit__(self, exc_type, exc_val, exc_tb):
400+
def deactivate(self):
385401
"""Restore the previous mask state.
386402
387403
Restores the original value of `GRASS_MASK` and optionally removes
@@ -391,6 +407,8 @@ def __exit__(self, exc_type, exc_val, exc_tb):
391407
:param exc_val: Exception value, if any.
392408
:param exc_tb: Traceback, if any.
393409
"""
410+
if not self._active:
411+
return
394412
if self._original_value is not None:
395413
self.env["GRASS_MASK"] = self._original_value
396414
else:
@@ -405,6 +423,13 @@ def __exit__(self, exc_type, exc_val, exc_tb):
405423
env=self.env,
406424
quiet=True,
407425
)
426+
self._active = False
427+
428+
def __enter__(self):
429+
return self.activate()
430+
431+
def __exit__(self, exc_type, exc_val, exc_tb):
432+
self.deactivate()
408433

409434

410435
class RegionManager:
@@ -442,6 +467,15 @@ class RegionManager:
442467
... manager.set_region(n=226000, s=222000, w=634000, e=638000)
443468
... gs.parse_command("r.univar", map="elevation", format="json")
444469
470+
Example using explicit activate and deactivate:
471+
472+
>>> manager = gs.RegionManager(raster="elevation")
473+
>>> manager.activate()
474+
>>> try:
475+
... gs.run_command("r.slope.aspect", elevation="elevation", slope="slope")
476+
... finally:
477+
... manager.deactivate()
478+
445479
If no environment is provided, the global environment is used. When running parallel
446480
processes in the same mapset that modify region settings, it is useful to use a copy
447481
of the global environment. The following code creates the copy of the global environment
@@ -466,6 +500,7 @@ def __init__(self, env: dict[str, str] | None = None, **kwargs):
466500
self._original_value = None
467501
self.region_name = append_uuid(append_node_pid("region"))
468502
self._region_inputs = kwargs or {}
503+
self._active = False
469504

470505
def set_region(self, **kwargs):
471506
"""Sets region.
@@ -474,19 +509,22 @@ def set_region(self, **kwargs):
474509
"""
475510
run_command("g.region", **kwargs, env=self.env)
476511

477-
def __enter__(self):
512+
def activate(self):
478513
"""Sets the `WIND_OVERRIDE` environment variable to the generated region name.
479514
480515
:return: Returns the :class:`RegionManager` instance.
481516
"""
517+
if self._active:
518+
return None
482519
self._original_value = self.env.get("WIND_OVERRIDE")
483520
run_command(
484521
"g.region", save=self.region_name, env=self.env, **self._region_inputs
485522
)
486523
self.env["WIND_OVERRIDE"] = self.region_name
524+
self._active = True
487525
return self
488526

489-
def __exit__(self, exc_type, exc_val, exc_tb):
527+
def deactivate(self):
490528
"""Restore the previous region state.
491529
492530
Restores the original value of `WIND_OVERRIDE`.
@@ -495,6 +533,8 @@ def __exit__(self, exc_type, exc_val, exc_tb):
495533
:param exc_val: Exception value, if any.
496534
:param exc_tb: Traceback, if any.
497535
"""
536+
if not self._active:
537+
return
498538
if self._original_value is not None:
499539
self.env["WIND_OVERRIDE"] = self._original_value
500540
else:
@@ -507,6 +547,13 @@ def __exit__(self, exc_type, exc_val, exc_tb):
507547
name=self.region_name,
508548
env=self.env,
509549
)
550+
self._active = False
551+
552+
def __enter__(self):
553+
return self.activate()
554+
555+
def __exit__(self, exc_type, exc_val, exc_tb):
556+
self.deactivate()
510557

511558

512559
class RegionManagerEnv:
@@ -534,6 +581,17 @@ class RegionManagerEnv:
534581
manager.env["GRASS_REGION"] = gs.region_env()
535582
... gs.parse_command("r.univar", map="elevation", format="json")
536583
584+
585+
Example using explicit activate and deactivate:
586+
587+
>>> manager = gs.RegionManagerEnv(raster="elevation")
588+
>>> manager.activate()
589+
>>> try:
590+
... manager.set_region(n=226000, s=222000, w=634000, e=638000)
591+
... gs.parse_command("r.univar", map="elevation", format="json")
592+
... finally:
593+
... manager.deactivate()
594+
537595
.. caution::
538596
539597
To set region within the context, do not call `g.region`,
@@ -551,6 +609,7 @@ def __init__(self, env: dict[str, str] | None = None, **kwargs):
551609
self.env = env if env is not None else os.environ
552610
self._original_value = None
553611
self._region_inputs = kwargs or {}
612+
self._active = False
554613

555614
def set_region(self, **kwargs):
556615
"""Sets region.
@@ -559,16 +618,19 @@ def set_region(self, **kwargs):
559618
"""
560619
self.env["GRASS_REGION"] = region_env(**kwargs, env=self.env)
561620

562-
def __enter__(self):
621+
def activate(self):
563622
"""Sets the `GRASS_REGION` environment variable to the generated region name.
564623
565624
:return: Returns the :class:`RegionManagerEnv` instance.
566625
"""
626+
if self._active:
627+
return None
567628
self._original_value = self.env.get("GRASS_REGION")
568629
self.env["GRASS_REGION"] = region_env(**self._region_inputs, env=self.env)
630+
self._active = True
569631
return self
570632

571-
def __exit__(self, exc_type, exc_val, exc_tb):
633+
def deactivate(self):
572634
"""Restore the previous region state.
573635
574636
Restores the original value of `WIND_OVERRIDE`.
@@ -577,7 +639,16 @@ def __exit__(self, exc_type, exc_val, exc_tb):
577639
:param exc_val: Exception value, if any.
578640
:param exc_tb: Traceback, if any.
579641
"""
642+
if not self._active:
643+
return
580644
if self._original_value is not None:
581645
self.env["GRASS_REGION"] = self._original_value
582646
else:
583647
self.env.pop("GRASS_REGION", None)
648+
self._active = False
649+
650+
def __enter__(self):
651+
return self.activate()
652+
653+
def __exit__(self, exc_type, exc_val, exc_tb):
654+
self.deactivate()

python/grass/script/tests/grass_script_raster_mask_test.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,38 @@ def test_mask_manager_provided_name_remove_false(session_2x2):
223223
assert status["name"].startswith(DEFAULT_MASK_NAME)
224224
assert not status["present"]
225225
assert raster_sum("ones", env=session_2x2.env) == 4
226+
227+
228+
def test_mask_manager_activate_deactivate(session_2x2):
229+
"""Test MaskManager with explicit activate/deactivate."""
230+
assert "GRASS_MASK" not in session_2x2.env
231+
232+
manager = gs.MaskManager(env=session_2x2.env)
233+
manager.activate()
234+
assert "GRASS_MASK" in session_2x2.env
235+
assert session_2x2.env["GRASS_MASK"] == manager.mask_name
236+
237+
# Create mask using r.mask
238+
gs.run_command("r.mask", raster="nulls_and_one_1_1", env=session_2x2.env)
239+
assert raster_exists(manager.mask_name, env=session_2x2.env)
240+
status = gs.parse_command("r.mask.status", format="json", env=session_2x2.env)
241+
assert status["name"].startswith(manager.mask_name)
242+
assert status["present"]
243+
assert raster_sum("ones", env=session_2x2.env) == 1
244+
245+
# Deactivate restores state
246+
manager.deactivate()
247+
assert "GRASS_MASK" not in session_2x2.env
248+
assert not raster_exists(manager.mask_name, env=session_2x2.env)
249+
status = gs.parse_command("r.mask.status", format="json", env=session_2x2.env)
250+
assert status["name"].startswith(DEFAULT_MASK_NAME)
251+
assert not status["present"]
252+
assert raster_sum("ones", env=session_2x2.env) == 4
253+
254+
# Calling again is harmless
255+
manager.deactivate()
256+
assert "GRASS_MASK" not in session_2x2.env
257+
manager.activate()
258+
manager.activate()
259+
assert "GRASS_MASK" in session_2x2.env
260+
manager.deactivate()

python/grass/script/tests/grass_script_raster_region_test.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,62 @@ def test_region_manager_env_problem_with_g_region(session_2x2):
104104
region = gs.region(env=session_2x2.env)
105105
assert region["rows"] == 5
106106
assert region["cols"] == 5
107+
108+
109+
def test_region_manager_activate_deactivate(session_2x2):
110+
"""Test RegionManager with explicit activate/deactivate."""
111+
assert "WIND_OVERRIDE" not in session_2x2.env
112+
113+
manager = gs.RegionManager(env=session_2x2.env)
114+
manager.activate()
115+
assert "WIND_OVERRIDE" in session_2x2.env
116+
assert session_2x2.env["WIND_OVERRIDE"] == manager.region_name
117+
region = gs.region(env=session_2x2.env)
118+
assert region["rows"] == 2
119+
assert region["cols"] == 2
120+
121+
# Change region inside manager
122+
manager.set_region(n=4, s=0, e=4, w=0, res=1)
123+
region = gs.region(env=session_2x2.env)
124+
assert region["rows"] == 4
125+
assert region["cols"] == 4
126+
127+
# Deactivate restores original
128+
manager.deactivate()
129+
assert "WIND_OVERRIDE" not in session_2x2.env
130+
region = gs.region(env=session_2x2.env)
131+
assert region["rows"] == 2
132+
assert region["cols"] == 2
133+
134+
# Deactivate twice is harmless
135+
manager.deactivate()
136+
assert "WIND_OVERRIDE" not in session_2x2.env
137+
138+
139+
def test_region_manager_env_activate_deactivate(session_2x2):
140+
"""Test RegionManagerEnv with explicit activate/deactivate."""
141+
assert "GRASS_REGION" not in session_2x2.env
142+
143+
manager = gs.RegionManagerEnv(env=session_2x2.env)
144+
manager.activate()
145+
assert "GRASS_REGION" in session_2x2.env
146+
region = gs.region(env=session_2x2.env)
147+
assert region["rows"] == 2
148+
assert region["cols"] == 2
149+
150+
# Change region inside manager
151+
manager.set_region(n=5, s=0, e=5, w=0, res=1)
152+
region = gs.region(env=session_2x2.env)
153+
assert region["rows"] == 5
154+
assert region["cols"] == 5
155+
156+
# Deactivate restores original
157+
manager.deactivate()
158+
assert "GRASS_REGION" not in session_2x2.env
159+
region = gs.region(env=session_2x2.env)
160+
assert region["rows"] == 2
161+
assert region["cols"] == 2
162+
163+
# Deactivate twice is harmless
164+
manager.deactivate()
165+
assert "GRASS_REGION" not in session_2x2.env

0 commit comments

Comments
 (0)