From db56e2577d58562bd1e702f3c1b839f6bf5d9480 Mon Sep 17 00:00:00 2001 From: lindakladivova Date: Wed, 24 Jun 2026 12:27:21 +0200 Subject: [PATCH 1/2] new region_name parameter to RegionManager allowing users to work with saved regions --- python/grass/script/raster.py | 47 +++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/python/grass/script/raster.py b/python/grass/script/raster.py index 47f9d7f2542..1c79136c159 100644 --- a/python/grass/script/raster.py +++ b/python/grass/script/raster.py @@ -509,23 +509,54 @@ class RegionManager: >>> with gs.RegionManager(raster="elevation", env=os.environ.copy()) as manager: ... gs.run_command("r.univar", map="elevation", env=manager.env) + The *region_name* parameter allows working with named saved regions (created with + ``g.region save=...``). If *region_name* refers to an existing saved region and no + additional *g.region* parameters are given, the saved region is used directly without + overwriting it. If *region_name* is not provided, a unique name is generated and the + temporary region is removed at the end of the context. + + Example using an existing saved region: + + >>> with gs.RegionManager(region_name="study_area"): + ... gs.parse_command("r.univar", map="elevation", format="json") + + Example saving to a named region that persists after the context: + + >>> with gs.RegionManager(region_name="my_region", raster="elevation"): + ... gs.run_command("r.slope.aspect", elevation="elevation", slope="slope") + In the background, this class manages the `WIND_OVERRIDE` environment variable that holds the unique name of the saved region to use. """ - def __init__(self, env: dict[str, str] | None = None, **kwargs): + def __init__( + self, + region_name: str | None = None, + env: dict[str, str] | None = None, + **kwargs, + ): """ Initializes the RegionManager. + :param region_name: Name of the saved region. Generated if not provided. + If it refers to an existing saved region and no kwargs are given, + the region is used as-is and never removed at the end. :param env: Environment to use. Defaults to modifying :external:py:data:`os.environ`. - :param kwargs: Keyword arguments passed to `g.region` + :param kwargs: Keyword arguments passed to `g.region` when saving the region. """ self.env = env if env is not None else os.environ self._original_value = None - self.region_name = append_uuid(append_node_pid("region")) self._region_inputs = kwargs or {} self._active = False + if region_name is None: + self.region_name = append_uuid(append_node_pid("region")) + self._remove = True + self._save_on_activate = True + else: + self.region_name = region_name + self._remove = False + self._save_on_activate = bool(kwargs) def set_region(self, **kwargs): """Sets region. @@ -535,16 +566,17 @@ def set_region(self, **kwargs): run_command("g.region", **kwargs, env=self.env) def activate(self): - """Sets the `WIND_OVERRIDE` environment variable to the generated region name. + """Sets the `WIND_OVERRIDE` environment variable to this manager's region name. :return: Returns the :class:`RegionManager` instance. """ if self._active: return None self._original_value = self.env.get("WIND_OVERRIDE") - run_command( - "g.region", save=self.region_name, env=self.env, **self._region_inputs - ) + if self._save_on_activate: + run_command( + "g.region", save=self.region_name, env=self.env, **self._region_inputs + ) self.env["WIND_OVERRIDE"] = self.region_name self._active = True return self @@ -564,6 +596,7 @@ def deactivate(self): self.env["WIND_OVERRIDE"] = self._original_value else: self.env.pop("WIND_OVERRIDE", None) + if self._remove: run_command( "g.remove", flags="f", From b5037647713a72d0ce8f60b2e92711b618196c01 Mon Sep 17 00:00:00 2001 From: lindakladivova Date: Thu, 9 Jul 2026 12:47:00 +0200 Subject: [PATCH 2/2] docstring expanded and save method added --- python/grass/script/raster.py | 73 ++++++++----------- .../tests/grass_script_raster_region_test.py | 22 ++++++ 2 files changed, 52 insertions(+), 43 deletions(-) diff --git a/python/grass/script/raster.py b/python/grass/script/raster.py index 1c79136c159..c2d114325f0 100644 --- a/python/grass/script/raster.py +++ b/python/grass/script/raster.py @@ -465,8 +465,9 @@ class RegionManager: settings are automatically restored. This is useful in scripts or functions that need to work with a specific region without permanently altering the user's working environment. - The new region can be defined by passing *g.region* parameters when initializing the context, - or by calling *g.region* directly within the context. + The new region can be defined either by passing *g.region* parameters as keyword + arguments when initializing the context or by calling *g.region* directly within + the context. The original region is saved at the beginning of the context and restored at the end. @@ -480,18 +481,29 @@ class RegionManager: >>> with gs.RegionManager(raster="elevation"): ... gs.run_command("r.slope.aspect", elevation="elevation", slope="slope") + Example using a saved region: + + >>> with gs.RegionManager(region="study_area"): + ... gs.parse_command("r.univar", map="elevation", format="json") + Example using g.region: >>> with gs.RegionManager(): ... gs.run_command("g.region", n=226000, s=222000, w=634000, e=638000) ... gs.parse_command("r.univar", map="elevation", format="json") - Example using :py:func:`~grass.script.raster.RegionManager.set_region`: + Example using :py:meth:`~grass.script.raster.RegionManager.set_region`: >>> with gs.RegionManager() as manager: ... manager.set_region(n=226000, s=222000, w=634000, e=638000) ... gs.parse_command("r.univar", map="elevation", format="json") + Example using :py:meth:`~grass.script.raster.RegionManager.save` to store the current + region under a persistent name so it can be reused after the context exits: + + >>> with gs.RegionManager(raster="elevation") as manager: + ... manager.save("elevation_region", overwrite=True) + Example using explicit activate and deactivate: >>> manager = gs.RegionManager(raster="elevation") @@ -509,54 +521,23 @@ class RegionManager: >>> with gs.RegionManager(raster="elevation", env=os.environ.copy()) as manager: ... gs.run_command("r.univar", map="elevation", env=manager.env) - The *region_name* parameter allows working with named saved regions (created with - ``g.region save=...``). If *region_name* refers to an existing saved region and no - additional *g.region* parameters are given, the saved region is used directly without - overwriting it. If *region_name* is not provided, a unique name is generated and the - temporary region is removed at the end of the context. - - Example using an existing saved region: - - >>> with gs.RegionManager(region_name="study_area"): - ... gs.parse_command("r.univar", map="elevation", format="json") - - Example saving to a named region that persists after the context: - - >>> with gs.RegionManager(region_name="my_region", raster="elevation"): - ... gs.run_command("r.slope.aspect", elevation="elevation", slope="slope") - In the background, this class manages the `WIND_OVERRIDE` environment variable that holds the unique name of the saved region to use. """ - def __init__( - self, - region_name: str | None = None, - env: dict[str, str] | None = None, - **kwargs, - ): + def __init__(self, env: dict[str, str] | None = None, **kwargs): """ Initializes the RegionManager. - :param region_name: Name of the saved region. Generated if not provided. - If it refers to an existing saved region and no kwargs are given, - the region is used as-is and never removed at the end. :param env: Environment to use. Defaults to modifying :external:py:data:`os.environ`. - :param kwargs: Keyword arguments passed to `g.region` when saving the region. + :param kwargs: Keyword arguments passed to `g.region` """ self.env = env if env is not None else os.environ self._original_value = None + self.region_name = append_uuid(append_node_pid("region")) self._region_inputs = kwargs or {} self._active = False - if region_name is None: - self.region_name = append_uuid(append_node_pid("region")) - self._remove = True - self._save_on_activate = True - else: - self.region_name = region_name - self._remove = False - self._save_on_activate = bool(kwargs) def set_region(self, **kwargs): """Sets region. @@ -565,18 +546,25 @@ def set_region(self, **kwargs): """ run_command("g.region", **kwargs, env=self.env) + def save(self, name, overwrite=False): + """Save the current region under a persistent name. + + :param name: Name of the region to save. + :param overwrite: Whether to overwrite an existing saved region. + """ + run_command("g.region", save=name, overwrite=overwrite, env=self.env) + def activate(self): - """Sets the `WIND_OVERRIDE` environment variable to this manager's region name. + """Sets the `WIND_OVERRIDE` environment variable to the generated region name. :return: Returns the :class:`RegionManager` instance. """ if self._active: return None self._original_value = self.env.get("WIND_OVERRIDE") - if self._save_on_activate: - run_command( - "g.region", save=self.region_name, env=self.env, **self._region_inputs - ) + run_command( + "g.region", save=self.region_name, env=self.env, **self._region_inputs + ) self.env["WIND_OVERRIDE"] = self.region_name self._active = True return self @@ -596,7 +584,6 @@ def deactivate(self): self.env["WIND_OVERRIDE"] = self._original_value else: self.env.pop("WIND_OVERRIDE", None) - if self._remove: run_command( "g.remove", flags="f", diff --git a/python/grass/script/tests/grass_script_raster_region_test.py b/python/grass/script/tests/grass_script_raster_region_test.py index 6428842d24e..c2b63ffb593 100644 --- a/python/grass/script/tests/grass_script_raster_region_test.py +++ b/python/grass/script/tests/grass_script_raster_region_test.py @@ -163,3 +163,25 @@ def test_region_manager_env_activate_deactivate(session_2x2): # Deactivate twice is harmless manager.deactivate() assert "GRASS_REGION" not in session_2x2.env + + +def test_region_manager_save(session_2x2): + """Test RegionManager can persist the current region explicitly.""" + + with gs.RegionManager(n=6, s=0, e=6, w=0, res=1, env=session_2x2.env) as manager: + manager.save("saved_region", overwrite=True) + + try: + with gs.RegionManager(region="saved_region", env=session_2x2.env): + region = gs.region(env=session_2x2.env) + assert region["rows"] == 6 + assert region["cols"] == 6 + finally: + gs.run_command( + "g.remove", + flags="f", + type="region", + name="saved_region", + env=session_2x2.env, + quiet=True, + )