From d921506f3bcc50cdc2e1919053a6b5952b8466b7 Mon Sep 17 00:00:00 2001 From: chuan Date: Sat, 31 Jan 2026 21:24:11 +0800 Subject: [PATCH 01/13] add Error types to exceptions.py:At least one is required modified files for grdclip, grdfill, grdgradient,test_grdclip, test_grdfill, test_grdgradient --- pygmt/exceptions.py | 12 ++++++++++++ pygmt/src/grdclip.py | 9 ++++----- pygmt/src/grdfill.py | 11 +++++------ pygmt/src/grdgradient.py | 9 ++++----- pygmt/tests/test_grdclip.py | 6 +++--- pygmt/tests/test_grdfill.py | 4 ++-- pygmt/tests/test_grdgradient.py | 4 ++-- 7 files changed, 32 insertions(+), 23 deletions(-) diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index 93b80bb082b..0a576c3f51c 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -140,6 +140,8 @@ class GMTParameterError(GMTError): ---------- required Name or a set of names of required parameters. + at_least_one + A set of names of parameters where at least one must be specified. reason Detailed reason why the parameters are invalid. """ @@ -148,6 +150,7 @@ def __init__( self, *, required: str | set[str] | None = None, + at_least_one: set[str] | None = None, reason: str | None = None, ): msg = [] @@ -159,6 +162,15 @@ def __init__( "Missing required parameters: " f"{', '.join(repr(par) for par in required)}." ) + if at_least_one: + if len(at_least_one) == 1: + msg.append(f"Missing required parameter: {next(iter(at_least_one))!r}.") + else: + msg.append( + "Missing required parameters: " + f"{', '.join(repr(par) for par in at_least_one)}. " + "Must specify at least one." + ) if reason: msg.append(reason) super().__init__(" ".join(msg)) diff --git a/pygmt/src/grdclip.py b/pygmt/src/grdclip.py index ab0c3dcc276..5726be00947 100644 --- a/pygmt/src/grdclip.py +++ b/pygmt/src/grdclip.py @@ -9,7 +9,7 @@ from pygmt._typing import PathLike from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTParameterError from pygmt.helpers import build_arg_list, fmt_docstring __doctest_skip__ = ["grdclip"] @@ -110,11 +110,10 @@ def grdclip( [np.float32(0.0), np.float32(10000.0)] """ if all(v is None for v in (above, below, between, replace)): - msg = ( - "Must specify at least one of the following parameters: " - "'above', 'below', 'between', or 'replace'." + raise GMTParameterError( + at_least_one={"above", "below", "between", "replace"}, + reason="Must specify at least one of the clipping parameters.", ) - raise GMTInvalidInput(msg) aliasdict = AliasSystem( Sa=Alias(above, name="above", sep="/", size=2), diff --git a/pygmt/src/grdfill.py b/pygmt/src/grdfill.py index 485c0918fde..8c88f0d6760 100644 --- a/pygmt/src/grdfill.py +++ b/pygmt/src/grdfill.py @@ -10,7 +10,7 @@ from pygmt._typing import PathLike from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTInvalidInput, GMTParameterError from pygmt.helpers import build_arg_list, deprecate_parameter, fmt_docstring, use_alias __doctest_skip__ = ["grdfill"] @@ -39,7 +39,7 @@ def _validate_params( >>> _validate_params() Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Need to specify parameter ... + pygmt.exceptions.GMTParameterError: Missing required parameters: ... """ _fill_params = "'constant_fill'/'grid_fill'/'neighbor_fill'/'spline_fill'" @@ -57,11 +57,10 @@ def _validate_params( msg = f"Parameters {_fill_params}/'inquire' are mutually exclusive." raise GMTInvalidInput(msg) if n_given == 0: # No parameters are given. - msg = ( - f"Need to specify parameter {_fill_params} for filling holes or " - "'inquire' for inquiring the bounds of each hole." + raise GMTParameterError( + at_least_one={"constant_fill", "grid_fill", "neighbor_fill", "spline_fill"}, + reason="Need to specify a fill parameter or 'inquire' for inquiring the bounds of each hole.", ) - raise GMTInvalidInput(msg) @fmt_docstring diff --git a/pygmt/src/grdgradient.py b/pygmt/src/grdgradient.py index 6036cc54f56..9a5c2a9dec8 100644 --- a/pygmt/src/grdgradient.py +++ b/pygmt/src/grdgradient.py @@ -9,7 +9,7 @@ from pygmt._typing import PathLike from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput, GMTParameterError +from pygmt.exceptions import GMTParameterError from pygmt.helpers import build_arg_list, fmt_docstring, use_alias __doctest_skip__ = ["grdgradient"] @@ -174,11 +174,10 @@ def grdgradient( and kwargs.get("D") is None and kwargs.get("E", radiance) is None ): - msg = ( - "At least one of the following parameters must be specified: " - "azimuth, direction, or radiance." + raise GMTParameterError( + at_least_one={"azimuth", "direction", "radiance"}, + reason="At least one of these parameters must be specified to compute gradients.", ) - raise GMTInvalidInput(msg) aliasdict = AliasSystem( A=Alias(azimuth, name="azimuth", sep="/", size=2), diff --git a/pygmt/tests/test_grdclip.py b/pygmt/tests/test_grdclip.py index 50217f16de9..f7a0bf534ad 100644 --- a/pygmt/tests/test_grdclip.py +++ b/pygmt/tests/test_grdclip.py @@ -11,7 +11,7 @@ from pygmt import grdclip from pygmt.datasets import load_earth_mask from pygmt.enums import GridRegistration, GridType -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTParameterError from pygmt.helpers import GMTTempFile from pygmt.helpers.testing import load_static_earth_relief @@ -102,7 +102,7 @@ def test_grdclip_between_repeated(): def test_grdclip_missing_required_parameter(grid): """ - Test that grdclip raises a ValueError if the required parameter is missing. + Test that grdclip raises GMTParameterError if the clipping parameters are missing. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): grdclip(grid=grid) diff --git a/pygmt/tests/test_grdfill.py b/pygmt/tests/test_grdfill.py index 29a654e38f0..d52f3261f7f 100644 --- a/pygmt/tests/test_grdfill.py +++ b/pygmt/tests/test_grdfill.py @@ -10,7 +10,7 @@ import xarray as xr from pygmt import grdfill from pygmt.enums import GridRegistration, GridType -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTInvalidInput, GMTParameterError from pygmt.helpers import GMTTempFile from pygmt.helpers.testing import load_static_earth_relief @@ -146,7 +146,7 @@ def test_grdfill_required_args(grid): """ Test that grdfill fails without filling parameters or 'inquire'. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): grdfill(grid=grid) diff --git a/pygmt/tests/test_grdgradient.py b/pygmt/tests/test_grdgradient.py index 130ce64ccac..40b27414c17 100644 --- a/pygmt/tests/test_grdgradient.py +++ b/pygmt/tests/test_grdgradient.py @@ -8,7 +8,7 @@ import xarray as xr from pygmt import grdgradient from pygmt.enums import GridRegistration, GridType -from pygmt.exceptions import GMTInvalidInput, GMTParameterError +from pygmt.exceptions import GMTParameterError from pygmt.helpers import GMTTempFile from pygmt.helpers.testing import load_static_earth_relief @@ -80,7 +80,7 @@ def test_grdgradient_fails(grid): Check that grdgradient fails correctly when `tiles` is specified but normalize is not. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): grdgradient(grid=grid) # fails without required arguments with pytest.raises(GMTParameterError): # fails when tiles is specified but not normalize From bd7085549a12318da203c991ae398c3e6bb1ebcf Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 1 Feb 2026 17:18:04 +0800 Subject: [PATCH 02/13] Update pygmt/exceptions.py Co-authored-by: Dongdong Tian --- pygmt/exceptions.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index 0a576c3f51c..913adc661ba 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -163,14 +163,11 @@ def __init__( f"{', '.join(repr(par) for par in required)}." ) if at_least_one: - if len(at_least_one) == 1: - msg.append(f"Missing required parameter: {next(iter(at_least_one))!r}.") - else: - msg.append( - "Missing required parameters: " - f"{', '.join(repr(par) for par in at_least_one)}. " - "Must specify at least one." - ) + msg.append( + "Missing required parameters: " + f"{', '.join(repr(par) for par in at_least_one)}. " + "Must specify at least one." + ) if reason: msg.append(reason) super().__init__(" ".join(msg)) From 2fff8a273c4914b249cab037bdb2675298dae2b4 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 1 Feb 2026 17:18:18 +0800 Subject: [PATCH 03/13] Update pygmt/src/grdclip.py Co-authored-by: Dongdong Tian --- pygmt/src/grdclip.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pygmt/src/grdclip.py b/pygmt/src/grdclip.py index 5726be00947..9538ec3f7d4 100644 --- a/pygmt/src/grdclip.py +++ b/pygmt/src/grdclip.py @@ -112,7 +112,6 @@ def grdclip( if all(v is None for v in (above, below, between, replace)): raise GMTParameterError( at_least_one={"above", "below", "between", "replace"}, - reason="Must specify at least one of the clipping parameters.", ) aliasdict = AliasSystem( From 64463dc04eec25dddb964161797314b8fa1dc150 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 1 Feb 2026 17:18:30 +0800 Subject: [PATCH 04/13] Update pygmt/src/grdfill.py Co-authored-by: Dongdong Tian --- pygmt/src/grdfill.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pygmt/src/grdfill.py b/pygmt/src/grdfill.py index 8c88f0d6760..610d94653bd 100644 --- a/pygmt/src/grdfill.py +++ b/pygmt/src/grdfill.py @@ -58,8 +58,7 @@ def _validate_params( raise GMTInvalidInput(msg) if n_given == 0: # No parameters are given. raise GMTParameterError( - at_least_one={"constant_fill", "grid_fill", "neighbor_fill", "spline_fill"}, - reason="Need to specify a fill parameter or 'inquire' for inquiring the bounds of each hole.", + at_least_one={"constant_fill", "grid_fill", "neighbor_fill", "spline_fill", "inquire"}, ) From 097822bc086759317b3d88056b6ecc3bdb752ce6 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 1 Feb 2026 17:18:45 +0800 Subject: [PATCH 05/13] Update pygmt/src/grdgradient.py Co-authored-by: Dongdong Tian --- pygmt/src/grdgradient.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pygmt/src/grdgradient.py b/pygmt/src/grdgradient.py index 9a5c2a9dec8..67565526b8b 100644 --- a/pygmt/src/grdgradient.py +++ b/pygmt/src/grdgradient.py @@ -176,7 +176,6 @@ def grdgradient( ): raise GMTParameterError( at_least_one={"azimuth", "direction", "radiance"}, - reason="At least one of these parameters must be specified to compute gradients.", ) aliasdict = AliasSystem( From 026f5ff9428051f72916260f49ccd5187b812898 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 1 Feb 2026 17:49:37 +0800 Subject: [PATCH 06/13] Update pygmt/src/grdgradient.py Co-authored-by: Dongdong Tian --- pygmt/src/grdgradient.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pygmt/src/grdgradient.py b/pygmt/src/grdgradient.py index 67565526b8b..6ac656f181d 100644 --- a/pygmt/src/grdgradient.py +++ b/pygmt/src/grdgradient.py @@ -174,9 +174,7 @@ def grdgradient( and kwargs.get("D") is None and kwargs.get("E", radiance) is None ): - raise GMTParameterError( - at_least_one={"azimuth", "direction", "radiance"}, - ) + raise GMTParameterError(at_least_one={"azimuth", "direction", "radiance"}) aliasdict = AliasSystem( A=Alias(azimuth, name="azimuth", sep="/", size=2), From cb961e1594802ff69c05934cb4ae8fe507cf28b2 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 1 Feb 2026 17:49:59 +0800 Subject: [PATCH 07/13] Update pygmt/src/grdfill.py Co-authored-by: Dongdong Tian --- pygmt/src/grdfill.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdfill.py b/pygmt/src/grdfill.py index 610d94653bd..10df56ef6b5 100644 --- a/pygmt/src/grdfill.py +++ b/pygmt/src/grdfill.py @@ -58,7 +58,7 @@ def _validate_params( raise GMTInvalidInput(msg) if n_given == 0: # No parameters are given. raise GMTParameterError( - at_least_one={"constant_fill", "grid_fill", "neighbor_fill", "spline_fill", "inquire"}, + at_least_one={"constant_fill", "grid_fill", "neighbor_fill", "spline_fill", "inquire"} ) From 095fa1e099ca656210d69221d9ef67d167b7d850 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 1 Feb 2026 17:50:07 +0800 Subject: [PATCH 08/13] Update pygmt/src/grdclip.py Co-authored-by: Dongdong Tian --- pygmt/src/grdclip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdclip.py b/pygmt/src/grdclip.py index 9538ec3f7d4..ed593c5dfc6 100644 --- a/pygmt/src/grdclip.py +++ b/pygmt/src/grdclip.py @@ -111,7 +111,7 @@ def grdclip( """ if all(v is None for v in (above, below, between, replace)): raise GMTParameterError( - at_least_one={"above", "below", "between", "replace"}, + at_least_one={"above", "below", "between", "replace"} ) aliasdict = AliasSystem( From 484d102b97beae41919cdb26bdda18202a9d912e Mon Sep 17 00:00:00 2001 From: chuan Date: Sun, 1 Feb 2026 18:04:58 +0800 Subject: [PATCH 09/13] modified coast,dimfilter and update layout of others --- pygmt/src/coast.py | 17 ++++++++++++----- pygmt/src/dimfilter.py | 8 ++------ pygmt/src/grdclip.py | 4 +--- pygmt/src/grdfill.py | 8 +++++++- pygmt/tests/test_coast.py | 4 ++-- pygmt/tests/test_dimfilter.py | 4 ++-- 6 files changed, 26 insertions(+), 19 deletions(-) diff --git a/pygmt/src/coast.py b/pygmt/src/coast.py index 79919f38f66..5e2ab073e27 100644 --- a/pygmt/src/coast.py +++ b/pygmt/src/coast.py @@ -7,7 +7,7 @@ from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTParameterError from pygmt.helpers import args_in_kwargs, build_arg_list, fmt_docstring, use_alias from pygmt.params import Box @@ -241,11 +241,18 @@ def coast( # noqa: PLR0913 and kwargs.get("W", shorelines) is False and not args_in_kwargs(args=["C", "E", "Q"], kwargs=kwargs) ): - msg = ( - "At least one of the following parameters must be specified: " - "land, water, rivers, borders, shorelines, lakes, dcw, or Q." + raise GMTParameterError( + at_least_one={ + "land", + "water", + "rivers", + "borders", + "shorelines", + "lakes", + "dcw", + "Q", + } ) - raise GMTInvalidInput(msg) aliasdict = AliasSystem( D=Alias( diff --git a/pygmt/src/dimfilter.py b/pygmt/src/dimfilter.py index f96febb7e0c..5f41f7b19a3 100644 --- a/pygmt/src/dimfilter.py +++ b/pygmt/src/dimfilter.py @@ -9,7 +9,7 @@ from pygmt._typing import PathLike from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTParameterError from pygmt.helpers import build_arg_list, fmt_docstring, use_alias __doctest_skip__ = ["dimfilter"] @@ -144,11 +144,7 @@ def dimfilter( ... ) """ if not all(arg in kwargs for arg in ["D", "F", "N"]) and "Q" not in kwargs: - msg = ( - "At least one of the following parameters must be specified: " - "distance, filters, or sectors." - ) - raise GMTInvalidInput(msg) + raise GMTParameterError(at_least_one={"distance", "filters", "sectors"}) aliasdict = AliasSystem( I=Alias(spacing, name="spacing", sep="/", size=2), diff --git a/pygmt/src/grdclip.py b/pygmt/src/grdclip.py index ed593c5dfc6..bd3f8d0d544 100644 --- a/pygmt/src/grdclip.py +++ b/pygmt/src/grdclip.py @@ -110,9 +110,7 @@ def grdclip( [np.float32(0.0), np.float32(10000.0)] """ if all(v is None for v in (above, below, between, replace)): - raise GMTParameterError( - at_least_one={"above", "below", "between", "replace"} - ) + raise GMTParameterError(at_least_one={"above", "below", "between", "replace"}) aliasdict = AliasSystem( Sa=Alias(above, name="above", sep="/", size=2), diff --git a/pygmt/src/grdfill.py b/pygmt/src/grdfill.py index 10df56ef6b5..cf40da10406 100644 --- a/pygmt/src/grdfill.py +++ b/pygmt/src/grdfill.py @@ -58,7 +58,13 @@ def _validate_params( raise GMTInvalidInput(msg) if n_given == 0: # No parameters are given. raise GMTParameterError( - at_least_one={"constant_fill", "grid_fill", "neighbor_fill", "spline_fill", "inquire"} + at_least_one={ + "constant_fill", + "grid_fill", + "neighbor_fill", + "spline_fill", + "inquire", + } ) diff --git a/pygmt/tests/test_coast.py b/pygmt/tests/test_coast.py index 78650014a80..24d00f48025 100644 --- a/pygmt/tests/test_coast.py +++ b/pygmt/tests/test_coast.py @@ -4,7 +4,7 @@ import pytest from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTInvalidInput, GMTParameterError @pytest.mark.benchmark @@ -40,7 +40,7 @@ def test_coast_required_args(): Test if fig.coast fails when not given required arguments. """ fig = Figure() - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.coast(region="EG") diff --git a/pygmt/tests/test_dimfilter.py b/pygmt/tests/test_dimfilter.py index ca1f5e120e1..24717e9322f 100644 --- a/pygmt/tests/test_dimfilter.py +++ b/pygmt/tests/test_dimfilter.py @@ -8,7 +8,7 @@ import xarray as xr from pygmt import dimfilter from pygmt.enums import GridRegistration, GridType -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTParameterError from pygmt.helpers import GMTTempFile from pygmt.helpers.testing import load_static_earth_relief @@ -80,5 +80,5 @@ def test_dimfilter_fails(grid): Check that dimfilter fails correctly when not all of sectors, filters, and distance are specified. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): dimfilter(grid=grid, sectors="l6", distance=4) From 08bd9f14d6f0d365cf1772dc01fefb5095ade573 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 1 Feb 2026 19:41:45 +0800 Subject: [PATCH 10/13] Update pygmt/exceptions.py Co-authored-by: Dongdong Tian --- pygmt/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index 913adc661ba..dba4c284d7a 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -141,7 +141,7 @@ class GMTParameterError(GMTError): required Name or a set of names of required parameters. at_least_one - A set of names of parameters where at least one must be specified. + A set of parameter names, of which at least one must be specified. reason Detailed reason why the parameters are invalid. """ From 74a04ecf66e5c9a861f7d54b134e95f1efdd90e2 Mon Sep 17 00:00:00 2001 From: chuan Date: Mon, 2 Feb 2026 11:00:56 +0800 Subject: [PATCH 11/13] modified language statement --- pygmt/exceptions.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index dba4c284d7a..dbf8200dedb 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -164,9 +164,8 @@ def __init__( ) if at_least_one: msg.append( - "Missing required parameters: " - f"{', '.join(repr(par) for par in at_least_one)}. " - "Must specify at least one." + "Missing parameter: requires at least one of:" + f"{', '.join(repr(par) for par in at_least_one)}." ) if reason: msg.append(reason) From f911ea828211a860af446facb0cacb5fac970d13 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Mon, 2 Feb 2026 11:22:01 +0800 Subject: [PATCH 12/13] Update pygmt/exceptions.py Co-authored-by: Dongdong Tian --- pygmt/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index dbf8200dedb..7fcfa4d6fb2 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -164,7 +164,7 @@ def __init__( ) if at_least_one: msg.append( - "Missing parameter: requires at least one of:" + "Missing parameter: requires at least one of " f"{', '.join(repr(par) for par in at_least_one)}." ) if reason: From e766516199d2e661eee070a1bfaa9f7eb051694a Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Mon, 2 Feb 2026 11:31:50 +0800 Subject: [PATCH 13/13] Update pygmt/src/grdfill.py Co-authored-by: Dongdong Tian --- pygmt/src/grdfill.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdfill.py b/pygmt/src/grdfill.py index cf40da10406..4825d934828 100644 --- a/pygmt/src/grdfill.py +++ b/pygmt/src/grdfill.py @@ -39,7 +39,7 @@ def _validate_params( >>> _validate_params() Traceback (most recent call last): ... - pygmt.exceptions.GMTParameterError: Missing required parameters: ... + pygmt.exceptions.GMTParameterError: Missing parameter: requires at least one ... """ _fill_params = "'constant_fill'/'grid_fill'/'neighbor_fill'/'spline_fill'"