From 802176810ee1ccc4fbeb5df870f22e97154567ce Mon Sep 17 00:00:00 2001 From: chuan Date: Tue, 3 Feb 2026 11:21:35 +0800 Subject: [PATCH 01/13] Add exactly_one parameter to GMTParameterError for mutually exclusive parameters --- pygmt/exceptions.py | 9 +++++++++ pygmt/src/grdfill.py | 19 ++++++++++++------- pygmt/src/grdproject.py | 5 ++--- pygmt/src/grdsample.py | 6 ++---- pygmt/src/grdtrack.py | 8 +++----- pygmt/src/logo.py | 6 ++---- pygmt/tests/test_grdfill.py | 4 ++-- pygmt/tests/test_grdproject.py | 4 ++-- pygmt/tests/test_grdsample.py | 4 ++-- pygmt/tests/test_grdtrack.py | 6 +++--- pygmt/tests/test_logo.py | 4 ++-- 11 files changed, 41 insertions(+), 34 deletions(-) diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index 7fcfa4d6fb2..78d5939a52c 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -142,6 +142,8 @@ class GMTParameterError(GMTError): Name or a set of names of required parameters. at_least_one A set of parameter names, of which at least one must be specified. + exactly_one + A set of parameter names, of which exactly one must be specified. reason Detailed reason why the parameters are invalid. """ @@ -151,6 +153,7 @@ def __init__( *, required: str | set[str] | None = None, at_least_one: set[str] | None = None, + exactly_one: set[str] | None = None, reason: str | None = None, ): msg = [] @@ -167,6 +170,12 @@ def __init__( "Missing parameter: requires at least one of " f"{', '.join(repr(par) for par in at_least_one)}." ) + if exactly_one: + msg.append( + "Mutually exclusive parameters: " + f"{', '.join(repr(par) for par in exactly_one)}. " + "Specify exactly one." + ) if reason: msg.append(reason) super().__init__(" ".join(msg)) diff --git a/pygmt/src/grdfill.py b/pygmt/src/grdfill.py index 4825d934828..d3125a1a465 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, GMTParameterError +from pygmt.exceptions import GMTParameterError from pygmt.helpers import build_arg_list, deprecate_parameter, fmt_docstring, use_alias __doctest_skip__ = ["grdfill"] @@ -31,18 +31,16 @@ def _validate_params( >>> _validate_params(constant_fill=20.0, grid_fill="bggrid.nc") Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Parameters ... are mutually exclusive. + pygmt.exceptions.GMTParameterError: Mutually exclusive parameters: ... >>> _validate_params(constant_fill=20.0, inquire=True) Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Parameters ... are mutually exclusive. + pygmt.exceptions.GMTParameterError: Mutually exclusive parameters: ... >>> _validate_params() Traceback (most recent call last): ... pygmt.exceptions.GMTParameterError: Missing parameter: requires at least one ... """ - _fill_params = "'constant_fill'/'grid_fill'/'neighbor_fill'/'spline_fill'" - n_given = sum( param is not None and param is not False for param in [ @@ -54,8 +52,15 @@ def _validate_params( ] ) if n_given > 1: # More than one mutually exclusive parameter is given. - msg = f"Parameters {_fill_params}/'inquire' are mutually exclusive." - raise GMTInvalidInput(msg) + raise GMTParameterError( + exactly_one={ + "constant_fill", + "grid_fill", + "neighbor_fill", + "spline_fill", + "inquire", + } + ) if n_given == 0: # No parameters are given. raise GMTParameterError( at_least_one={ diff --git a/pygmt/src/grdproject.py b/pygmt/src/grdproject.py index 6a5290ef5e8..510f1fec25a 100644 --- a/pygmt/src/grdproject.py +++ b/pygmt/src/grdproject.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__ = ["grdproject"] @@ -121,8 +121,7 @@ def grdproject( # noqa: PLR0913 raise GMTParameterError(required="projection") if kwargs.get("M", unit) is not None and kwargs.get("F", scaling) is not False: - msg = "Cannot use both 'unit' and 'scaling'." - raise GMTInvalidInput(msg) + raise GMTParameterError(exactly_one={"unit", "scaling"}) aliasdict = AliasSystem( C=Alias(center, name="center", sep="/", size=2), diff --git a/pygmt/src/grdsample.py b/pygmt/src/grdsample.py index ff96c31e7e1..9d0c8d955f1 100644 --- a/pygmt/src/grdsample.py +++ b/pygmt/src/grdsample.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, deprecate_parameter, @@ -100,10 +100,8 @@ def grdsample( >>> # and set both x- and y-spacings to 0.5 arc-degrees >>> new_grid = pygmt.grdsample(grid=grid, toggle=True, spacing=[0.5, 0.5]) """ - # Enforce mutual exclusivity between -T (toggle) and -r (registration) if kwargs.get("T", toggle) and kwargs.get("r", registration): - msg = "Parameters 'toggle' and 'registration' cannot be used together." - raise GMTInvalidInput(msg) + raise GMTParameterError(exactly_one={"toggle", "registration"}) aliasdict = AliasSystem( I=Alias(spacing, name="spacing", sep="/", size=2), diff --git a/pygmt/src/grdtrack.py b/pygmt/src/grdtrack.py index ed4c04bcb94..60bb35b0b07 100644 --- a/pygmt/src/grdtrack.py +++ b/pygmt/src/grdtrack.py @@ -11,7 +11,7 @@ from pygmt._typing import PathLike, TableLike from pygmt.alias import 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, @@ -299,12 +299,10 @@ def grdtrack( ... ) """ if points is not None and kwargs.get("E") is not None: - msg = "Can't set both 'points' and 'profile'." - raise GMTInvalidInput(msg) + raise GMTParameterError(exactly_one={"points", "profile"}) if points is None and kwargs.get("E") is None: - msg = "Must give 'points' or set 'profile'." - raise GMTInvalidInput(msg) + raise GMTParameterError(at_least_one={"points", "profile"}) if hasattr(points, "columns") and newcolname is None: raise GMTParameterError( diff --git a/pygmt/src/logo.py b/pygmt/src/logo.py index c12bb1bbf05..6fe8a0142a7 100644 --- a/pygmt/src/logo.py +++ b/pygmt/src/logo.py @@ -8,7 +8,7 @@ from pygmt._typing import AnchorCode 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 from pygmt.params import Box, Position from pygmt.src._common import _parse_position @@ -103,10 +103,8 @@ def logo( # noqa: PLR0913 kwdict={"width": width, "height": height}, ) - # width and height are mutually exclusive. if width is not None and height is not None: - msg = "Cannot specify both 'width' and 'height'." - raise GMTInvalidInput(msg) + raise GMTParameterError(exactly_one={"width", "height"}) aliasdict = AliasSystem( D=[ diff --git a/pygmt/tests/test_grdfill.py b/pygmt/tests/test_grdfill.py index d52f3261f7f..66e7dc552d8 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, GMTParameterError +from pygmt.exceptions import GMTParameterError from pygmt.helpers import GMTTempFile from pygmt.helpers.testing import load_static_earth_relief @@ -154,5 +154,5 @@ def test_grdfill_inquire_and_fill(grid): """ Test that grdfill fails if both inquire and fill parameters are given. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): grdfill(grid=grid, inquire=True, constant_fill=20) diff --git a/pygmt/tests/test_grdproject.py b/pygmt/tests/test_grdproject.py index 4207b4a4521..e1def13d90d 100644 --- a/pygmt/tests/test_grdproject.py +++ b/pygmt/tests/test_grdproject.py @@ -8,7 +8,7 @@ import xarray as xr from pygmt import grdproject 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 @@ -86,7 +86,7 @@ def test_grdproject_unit_scaling(grid): Test that the input validation to prevent passing both 'unit' and 'scaling' is performed. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): grdproject( grid=grid, projection="M10c", diff --git a/pygmt/tests/test_grdsample.py b/pygmt/tests/test_grdsample.py index 43411573508..ce14ef99bb0 100644 --- a/pygmt/tests/test_grdsample.py +++ b/pygmt/tests/test_grdsample.py @@ -8,7 +8,7 @@ import xarray as xr from pygmt import grdsample 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 @@ -101,5 +101,5 @@ def test_grdsample_toggle_and_registration_mutually_exclusive(grid): """ Raise an exception if toggle and registration are both set. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): grdsample(grid=grid, toggle=True, registration="pixel") diff --git a/pygmt/tests/test_grdtrack.py b/pygmt/tests/test_grdtrack.py index c6d45958094..4bedac10a24 100644 --- a/pygmt/tests/test_grdtrack.py +++ b/pygmt/tests/test_grdtrack.py @@ -9,7 +9,7 @@ import pandas as pd import pytest from pygmt import grdtrack -from pygmt.exceptions import GMTInvalidInput, GMTParameterError, GMTTypeError +from pygmt.exceptions import GMTParameterError, GMTTypeError from pygmt.helpers import GMTTempFile from pygmt.helpers.testing import load_static_earth_relief @@ -162,7 +162,7 @@ def test_grdtrack_no_points_and_profile(dataarray): """ Run grdtrack but don't set 'points' and 'profile'. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): grdtrack(grid=dataarray) @@ -170,5 +170,5 @@ def test_grdtrack_set_points_and_profile(dataarray, dataframe): """ Run grdtrack but set both 'points' and 'profile'. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): grdtrack(grid=dataarray, points=dataframe, profile="BL/TR") diff --git a/pygmt/tests/test_logo.py b/pygmt/tests/test_logo.py index ad70acf62cd..95fc5fa8534 100644 --- a/pygmt/tests/test_logo.py +++ b/pygmt/tests/test_logo.py @@ -4,7 +4,7 @@ import pytest from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTInvalidInput, GMTParameterError from pygmt.params import Position @@ -57,7 +57,7 @@ def test_logo_width_and_height(): Test that an error is raised when both width and height are specified. """ fig = Figure() - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.logo(width="5c", height="5c") From ce0de629ab41554d7d474cd4792ff398ec750007 Mon Sep 17 00:00:00 2001 From: chuan Date: Tue, 3 Feb 2026 12:28:28 +0800 Subject: [PATCH 02/13] add at_most_one parameter to GMTParameterError --- pygmt/exceptions.py | 13 +++++++------ pygmt/src/grdfill.py | 2 +- pygmt/src/grdproject.py | 2 +- pygmt/src/grdsample.py | 2 +- pygmt/src/grdtrack.py | 2 +- pygmt/src/logo.py | 2 +- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index 78d5939a52c..df650aff8a1 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -142,8 +142,9 @@ class GMTParameterError(GMTError): Name or a set of names of required parameters. at_least_one A set of parameter names, of which at least one must be specified. - exactly_one - A set of parameter names, of which exactly one must be specified. + at_most_one + A set of mutually exclusive parameter names, of which at most one must be + specified. reason Detailed reason why the parameters are invalid. """ @@ -153,7 +154,7 @@ def __init__( *, required: str | set[str] | None = None, at_least_one: set[str] | None = None, - exactly_one: set[str] | None = None, + at_most_one: set[str] | None = None, reason: str | None = None, ): msg = [] @@ -170,11 +171,11 @@ def __init__( "Missing parameter: requires at least one of " f"{', '.join(repr(par) for par in at_least_one)}." ) - if exactly_one: + if at_most_one: msg.append( "Mutually exclusive parameters: " - f"{', '.join(repr(par) for par in exactly_one)}. " - "Specify exactly one." + f"{', '.join(repr(par) for par in at_most_one)}. " + "Specify at most one of them." ) if reason: msg.append(reason) diff --git a/pygmt/src/grdfill.py b/pygmt/src/grdfill.py index d3125a1a465..c761d6be1f4 100644 --- a/pygmt/src/grdfill.py +++ b/pygmt/src/grdfill.py @@ -53,7 +53,7 @@ def _validate_params( ) if n_given > 1: # More than one mutually exclusive parameter is given. raise GMTParameterError( - exactly_one={ + at_most_one={ "constant_fill", "grid_fill", "neighbor_fill", diff --git a/pygmt/src/grdproject.py b/pygmt/src/grdproject.py index 510f1fec25a..9ef2ced1cb2 100644 --- a/pygmt/src/grdproject.py +++ b/pygmt/src/grdproject.py @@ -121,7 +121,7 @@ def grdproject( # noqa: PLR0913 raise GMTParameterError(required="projection") if kwargs.get("M", unit) is not None and kwargs.get("F", scaling) is not False: - raise GMTParameterError(exactly_one={"unit", "scaling"}) + raise GMTParameterError(at_most_one={"unit", "scaling"}) aliasdict = AliasSystem( C=Alias(center, name="center", sep="/", size=2), diff --git a/pygmt/src/grdsample.py b/pygmt/src/grdsample.py index 9d0c8d955f1..7b799829e5d 100644 --- a/pygmt/src/grdsample.py +++ b/pygmt/src/grdsample.py @@ -101,7 +101,7 @@ def grdsample( >>> new_grid = pygmt.grdsample(grid=grid, toggle=True, spacing=[0.5, 0.5]) """ if kwargs.get("T", toggle) and kwargs.get("r", registration): - raise GMTParameterError(exactly_one={"toggle", "registration"}) + raise GMTParameterError(at_most_one={"toggle", "registration"}) aliasdict = AliasSystem( I=Alias(spacing, name="spacing", sep="/", size=2), diff --git a/pygmt/src/grdtrack.py b/pygmt/src/grdtrack.py index 60bb35b0b07..0286ea9690e 100644 --- a/pygmt/src/grdtrack.py +++ b/pygmt/src/grdtrack.py @@ -299,7 +299,7 @@ def grdtrack( ... ) """ if points is not None and kwargs.get("E") is not None: - raise GMTParameterError(exactly_one={"points", "profile"}) + raise GMTParameterError(at_most_one={"points", "profile"}) if points is None and kwargs.get("E") is None: raise GMTParameterError(at_least_one={"points", "profile"}) diff --git a/pygmt/src/logo.py b/pygmt/src/logo.py index 6fe8a0142a7..3b220ee41c8 100644 --- a/pygmt/src/logo.py +++ b/pygmt/src/logo.py @@ -104,7 +104,7 @@ def logo( # noqa: PLR0913 ) if width is not None and height is not None: - raise GMTParameterError(exactly_one={"width", "height"}) + raise GMTParameterError(at_most_one={"width", "height"}) aliasdict = AliasSystem( D=[ From 59568a319c7a7dbdadc9dd2fc57fc9034fb9401b Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Tue, 3 Feb 2026 12:49:21 +0800 Subject: [PATCH 03/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 df650aff8a1..ae76cc12ff5 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -143,7 +143,7 @@ class GMTParameterError(GMTError): at_least_one A set of parameter names, of which at least one must be specified. at_most_one - A set of mutually exclusive parameter names, of which at most one must be + A set of mutually exclusive parameter names, of which at most one can be specified. reason Detailed reason why the parameters are invalid. From 04b8f5f0277547c7a3436f6549c8e6bd92eca2b6 Mon Sep 17 00:00:00 2001 From: chuan Date: Tue, 3 Feb 2026 12:51:29 +0800 Subject: [PATCH 04/13] use a set to simplify --- pygmt/src/grdfill.py | 49 +++++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/pygmt/src/grdfill.py b/pygmt/src/grdfill.py index c761d6be1f4..1f24dcdd305 100644 --- a/pygmt/src/grdfill.py +++ b/pygmt/src/grdfill.py @@ -41,36 +41,25 @@ def _validate_params( ... pygmt.exceptions.GMTParameterError: Missing parameter: requires at least one ... """ - n_given = sum( - param is not None and param is not False - for param in [ - constant_fill, - grid_fill, - neighbor_fill, - spline_fill, - inquire, - ] - ) - if n_given > 1: # More than one mutually exclusive parameter is given. - raise GMTParameterError( - at_most_one={ - "constant_fill", - "grid_fill", - "neighbor_fill", - "spline_fill", - "inquire", - } - ) - if n_given == 0: # No parameters are given. - raise GMTParameterError( - at_least_one={ - "constant_fill", - "grid_fill", - "neighbor_fill", - "spline_fill", - "inquire", - } - ) + params = { + "constant_fill", + "grid_fill", + "neighbor_fill", + "spline_fill", + "inquire", + } + param_values = [ + constant_fill, + grid_fill, + neighbor_fill, + spline_fill, + inquire, + ] + n_given = sum(param is not None and param is not False for param in param_values) + if n_given > 1: + raise GMTParameterError(at_most_one=params) + if n_given == 0: + raise GMTParameterError(at_least_one=params) @fmt_docstring From a207a106091a48a919e1904cd8483c10001fc153 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Tue, 3 Feb 2026 13:31:24 +0800 Subject: [PATCH 05/13] Update pygmt/src/grdfill.py Co-authored-by: Dongdong Tian --- pygmt/src/grdfill.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/pygmt/src/grdfill.py b/pygmt/src/grdfill.py index 1f24dcdd305..8bf0ac1c1c9 100644 --- a/pygmt/src/grdfill.py +++ b/pygmt/src/grdfill.py @@ -42,19 +42,12 @@ def _validate_params( pygmt.exceptions.GMTParameterError: Missing parameter: requires at least one ... """ params = { - "constant_fill", - "grid_fill", - "neighbor_fill", - "spline_fill", - "inquire", + "constant_fill": constant_fill + "grid_fill": grid_fill, + "neighbor_fill": 'neighbor_fill', + "spline_fill": 'spline_fill', + "inquire": inquire, } - param_values = [ - constant_fill, - grid_fill, - neighbor_fill, - spline_fill, - inquire, - ] n_given = sum(param is not None and param is not False for param in param_values) if n_given > 1: raise GMTParameterError(at_most_one=params) From 7b587f91c41aff7fb3945970779c1a08947b310d Mon Sep 17 00:00:00 2001 From: chuan Date: Tue, 3 Feb 2026 13:35:12 +0800 Subject: [PATCH 06/13] update at_most_one parameter --- pygmt/src/grd2cpt.py | 5 ++--- pygmt/src/grdfill.py | 8 ++++---- pygmt/src/makecpt.py | 5 ++--- pygmt/src/subplot.py | 5 ++--- pygmt/tests/test_grd2cpt.py | 8 ++++++-- pygmt/tests/test_makecpt.py | 7 +++++-- pygmt/tests/test_subplot.py | 7 +++++-- 7 files changed, 26 insertions(+), 19 deletions(-) diff --git a/pygmt/src/grd2cpt.py b/pygmt/src/grd2cpt.py index a09ac3aa4c2..0295c71f300 100644 --- a/pygmt/src/grd2cpt.py +++ b/pygmt/src/grd2cpt.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, kwargs_to_strings, use_alias __doctest_skip__ = ["grd2cpt"] @@ -199,8 +199,7 @@ def grd2cpt( >>> fig.show() """ if kwargs.get("W") is not None and kwargs.get("Ww") is not None: - msg = "Set only 'categorical' or 'cyclic' to True, not both." - raise GMTInvalidInput(msg) + raise GMTParameterError(at_most_one={"categorical", "cyclic"}) if (output := kwargs.pop("H", None)) is not None: kwargs["H"] = True diff --git a/pygmt/src/grdfill.py b/pygmt/src/grdfill.py index 8bf0ac1c1c9..96adde00ae4 100644 --- a/pygmt/src/grdfill.py +++ b/pygmt/src/grdfill.py @@ -42,13 +42,13 @@ def _validate_params( pygmt.exceptions.GMTParameterError: Missing parameter: requires at least one ... """ params = { - "constant_fill": constant_fill + "constant_fill": constant_fill, "grid_fill": grid_fill, - "neighbor_fill": 'neighbor_fill', - "spline_fill": 'spline_fill', + "neighbor_fill": neighbor_fill, + "spline_fill": spline_fill, "inquire": inquire, } - n_given = sum(param is not None and param is not False for param in param_values) + n_given = sum(param is not None and param is not False for param in params.values()) if n_given > 1: raise GMTParameterError(at_most_one=params) if n_given == 0: diff --git a/pygmt/src/makecpt.py b/pygmt/src/makecpt.py index 4b9d3392927..1f6e504e053 100644 --- a/pygmt/src/makecpt.py +++ b/pygmt/src/makecpt.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 build_arg_list, fmt_docstring, kwargs_to_strings, use_alias @@ -169,8 +169,7 @@ def makecpt( ``categorical=True``. """ if kwargs.get("W") is not None and kwargs.get("Ww") is not None: - msg = "Set only categorical or cyclic to True, not both." - raise GMTInvalidInput(msg) + raise GMTParameterError(at_most_one={"categorical", "cyclic"}) if (output := kwargs.pop("H", None)) is not None: kwargs["H"] = True diff --git a/pygmt/src/subplot.py b/pygmt/src/subplot.py index 0ef2ed37e34..62e37438be8 100644 --- a/pygmt/src/subplot.py +++ b/pygmt/src/subplot.py @@ -8,7 +8,7 @@ from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput, GMTValueError +from pygmt.exceptions import GMTParameterError, GMTValueError from pygmt.helpers import build_arg_list, fmt_docstring, kwargs_to_strings, use_alias @@ -166,8 +166,7 @@ def subplot( ) if kwargs.get("Ff") and kwargs.get("Fs"): - msg = "Please provide either one of 'figsize' or 'subsize' only." - raise GMTInvalidInput(msg) + raise GMTParameterError(at_most_one={"figsize", "subsize"}) aliasdict = AliasSystem( M=Alias(margins, name="margins", sep="/", size=(2, 4)), diff --git a/pygmt/tests/test_grd2cpt.py b/pygmt/tests/test_grd2cpt.py index 38e35a919a2..0505de0b126 100644 --- a/pygmt/tests/test_grd2cpt.py +++ b/pygmt/tests/test_grd2cpt.py @@ -6,7 +6,11 @@ import pytest from pygmt import Figure, grd2cpt -from pygmt.exceptions import GMTInvalidInput, GMTTypeError, GMTValueError +from pygmt.exceptions import ( + GMTParameterError, + GMTTypeError, + GMTValueError, +) from pygmt.helpers import GMTTempFile from pygmt.helpers.testing import load_static_earth_relief @@ -70,5 +74,5 @@ def test_grd2cpt_categorical_and_cyclic(grid): """ Use incorrect setting by setting both categorical and cyclic to True. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): grd2cpt(grid=grid, cmap="SCM/batlow", categorical=True, cyclic=True) diff --git a/pygmt/tests/test_makecpt.py b/pygmt/tests/test_makecpt.py index 5f9193e31e4..8dee5e4f638 100644 --- a/pygmt/tests/test_makecpt.py +++ b/pygmt/tests/test_makecpt.py @@ -7,7 +7,10 @@ import numpy as np import pytest from pygmt import Figure, makecpt -from pygmt.exceptions import GMTInvalidInput, GMTValueError +from pygmt.exceptions import ( + GMTParameterError, + GMTValueError, +) from pygmt.helpers import GMTTempFile POINTS_DATA = Path(__file__).parent / "data" / "points.txt" @@ -166,5 +169,5 @@ def test_makecpt_categorical_and_cyclic(): """ Use incorrect setting by setting both categorical and cyclic to True. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): makecpt(cmap="SCM/batlow", categorical=True, cyclic=True) diff --git a/pygmt/tests/test_subplot.py b/pygmt/tests/test_subplot.py index b6143c1ac21..6a19cbc8f25 100644 --- a/pygmt/tests/test_subplot.py +++ b/pygmt/tests/test_subplot.py @@ -4,7 +4,10 @@ import pytest from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput, GMTValueError +from pygmt.exceptions import ( + GMTParameterError, + GMTValueError, +) from pygmt.params import Position @@ -90,7 +93,7 @@ def test_subplot_figsize_and_subsize_error(): into subplot. """ fig = Figure() - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): with fig.subplot(figsize=("2c", "1c"), subsize=("2c", "1c")): pass From daa3a89109589cff36e8f855f5c91cd651270a64 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Tue, 3 Feb 2026 19:57:01 +0800 Subject: [PATCH 07/13] Update pygmt/tests/test_grd2cpt.py Co-authored-by: Dongdong Tian --- pygmt/tests/test_grd2cpt.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pygmt/tests/test_grd2cpt.py b/pygmt/tests/test_grd2cpt.py index 0505de0b126..b50522475fb 100644 --- a/pygmt/tests/test_grd2cpt.py +++ b/pygmt/tests/test_grd2cpt.py @@ -6,11 +6,7 @@ import pytest from pygmt import Figure, grd2cpt -from pygmt.exceptions import ( - GMTParameterError, - GMTTypeError, - GMTValueError, -) +from pygmt.exceptions import GMTParameterError, GMTTypeError, GMTValueError from pygmt.helpers import GMTTempFile from pygmt.helpers.testing import load_static_earth_relief From 988ed816d015f3b1200dccc93ce317719ed90801 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Tue, 3 Feb 2026 19:57:11 +0800 Subject: [PATCH 08/13] Update pygmt/src/grdfill.py Co-authored-by: Dongdong Tian --- pygmt/src/grdfill.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pygmt/src/grdfill.py b/pygmt/src/grdfill.py index 96adde00ae4..adaa50c17ca 100644 --- a/pygmt/src/grdfill.py +++ b/pygmt/src/grdfill.py @@ -49,10 +49,13 @@ def _validate_params( "inquire": inquire, } n_given = sum(param is not None and param is not False for param in params.values()) - if n_given > 1: - raise GMTParameterError(at_most_one=params) - if n_given == 0: - raise GMTParameterError(at_least_one=params) + match n_given: + case 0: + raise GMTParameterError(at_least_one=params) + case 1: + pass + case _: + raise GMTParameterError(at_most_one=params) @fmt_docstring From 941d2a763612377b5ed05c657cad5798ab66a7e6 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Tue, 3 Feb 2026 19:57:36 +0800 Subject: [PATCH 09/13] Update pygmt/tests/test_makecpt.py Co-authored-by: Dongdong Tian --- pygmt/tests/test_makecpt.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pygmt/tests/test_makecpt.py b/pygmt/tests/test_makecpt.py index 8dee5e4f638..2f0d86bcaf4 100644 --- a/pygmt/tests/test_makecpt.py +++ b/pygmt/tests/test_makecpt.py @@ -7,10 +7,7 @@ import numpy as np import pytest from pygmt import Figure, makecpt -from pygmt.exceptions import ( - GMTParameterError, - GMTValueError, -) +from pygmt.exceptions import GMTParameterError, GMTValueError from pygmt.helpers import GMTTempFile POINTS_DATA = Path(__file__).parent / "data" / "points.txt" From fa1963757d69b1f36bd8de9d03800ee208a94257 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Tue, 3 Feb 2026 19:57:47 +0800 Subject: [PATCH 10/13] Update pygmt/tests/test_subplot.py Co-authored-by: Dongdong Tian --- pygmt/tests/test_subplot.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pygmt/tests/test_subplot.py b/pygmt/tests/test_subplot.py index 6a19cbc8f25..5fd96cea60d 100644 --- a/pygmt/tests/test_subplot.py +++ b/pygmt/tests/test_subplot.py @@ -4,10 +4,7 @@ import pytest from pygmt import Figure -from pygmt.exceptions import ( - GMTParameterError, - GMTValueError, -) +from pygmt.exceptions import GMTParameterError, GMTValueError from pygmt.params import Position From bc9b9df5ab9f3b240b3b842b9241b80683c209f8 Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 4 Feb 2026 10:43:50 +0800 Subject: [PATCH 11/13] update plot,plot3d --- pygmt/src/plot.py | 5 ++--- pygmt/src/plot3d.py | 5 ++--- pygmt/tests/test_plot.py | 4 ++-- pygmt/tests/test_plot3d.py | 4 ++-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/pygmt/src/plot.py b/pygmt/src/plot.py index f7c92bebc6e..f29875dc6ee 100644 --- a/pygmt/src/plot.py +++ b/pygmt/src/plot.py @@ -8,7 +8,7 @@ from pygmt._typing import PathLike, TableLike from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput, GMTTypeError +from pygmt.exceptions import GMTParameterError, GMTTypeError from pygmt.helpers import ( build_arg_list, data_kind, @@ -272,8 +272,7 @@ def plot( # noqa: PLR0912, PLR0913 data["symbol"] = symbol else: if any(v is not None for v in (x, y)): - msg = "Too much data. Use either data or x/y/z." - raise GMTInvalidInput(msg) + raise GMTParameterError(at_most_one={"data", "x", "y"}) for name, value in [ ("direction", direction), ("fill", kwargs.get("G")), diff --git a/pygmt/src/plot3d.py b/pygmt/src/plot3d.py index d4ce2940488..47a3c48dd63 100644 --- a/pygmt/src/plot3d.py +++ b/pygmt/src/plot3d.py @@ -8,7 +8,7 @@ from pygmt._typing import PathLike, TableLike from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput, GMTTypeError +from pygmt.exceptions import GMTParameterError, GMTTypeError from pygmt.helpers import ( build_arg_list, data_kind, @@ -252,8 +252,7 @@ def plot3d( # noqa: PLR0912, PLR0913 data["symbol"] = symbol else: if any(v is not None for v in (x, y, z)): - msg = "Too much data. Use either data or x/y/z." - raise GMTInvalidInput(msg) + raise GMTParameterError(at_most_one={"data", "x", "y", "z"}) for name, value in [ ("direction", direction), diff --git a/pygmt/tests/test_plot.py b/pygmt/tests/test_plot.py index 06d816457c9..20da1814cac 100644 --- a/pygmt/tests/test_plot.py +++ b/pygmt/tests/test_plot.py @@ -10,7 +10,7 @@ import pytest import xarray as xr from pygmt import Figure, which -from pygmt.exceptions import GMTInvalidInput, GMTTypeError +from pygmt.exceptions import GMTInvalidInput, GMTParameterError, GMTTypeError from pygmt.helpers import GMTTempFile POINTS_DATA = Path(__file__).parent / "data" / "points.txt" @@ -78,7 +78,7 @@ def test_plot_fail_no_data(data, region): frame="afg", ) # Should also fail if given too much data - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.plot( x=data[:, 0], y=data[:, 1], diff --git a/pygmt/tests/test_plot3d.py b/pygmt/tests/test_plot3d.py index f15f9101fad..caf9e3cdddd 100644 --- a/pygmt/tests/test_plot3d.py +++ b/pygmt/tests/test_plot3d.py @@ -7,7 +7,7 @@ import numpy as np import pytest from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput, GMTTypeError +from pygmt.exceptions import GMTInvalidInput, GMTParameterError, GMTTypeError from pygmt.helpers import GMTTempFile POINTS_DATA = Path(__file__).parent / "data" / "points.txt" @@ -97,7 +97,7 @@ def test_plot3d_fail_no_data(data, region): fig.plot3d( style="c0.2c", x=data[0], y=data[1], region=region, projection="X10c" ) - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.plot3d( style="c0.2c", data=data, x=data[0], region=region, projection="X10c" ) From ae78f9ae07b4bd374764d1c010a17dc4e6448958 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Wed, 4 Feb 2026 10:50:06 +0800 Subject: [PATCH 12/13] Update pygmt/src/plot.py Co-authored-by: Dongdong Tian --- pygmt/src/plot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/plot.py b/pygmt/src/plot.py index f29875dc6ee..54740704586 100644 --- a/pygmt/src/plot.py +++ b/pygmt/src/plot.py @@ -272,7 +272,7 @@ def plot( # noqa: PLR0912, PLR0913 data["symbol"] = symbol else: if any(v is not None for v in (x, y)): - raise GMTParameterError(at_most_one={"data", "x", "y"}) + raise GMTParameterError(at_most_one={"data", "x/y/z"}) for name, value in [ ("direction", direction), ("fill", kwargs.get("G")), From 1da7ab944f3108133a5073071393965dc3830952 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Wed, 4 Feb 2026 10:50:15 +0800 Subject: [PATCH 13/13] Update pygmt/src/plot3d.py Co-authored-by: Dongdong Tian --- pygmt/src/plot3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/plot3d.py b/pygmt/src/plot3d.py index 47a3c48dd63..3f4e4b5cb5d 100644 --- a/pygmt/src/plot3d.py +++ b/pygmt/src/plot3d.py @@ -252,7 +252,7 @@ def plot3d( # noqa: PLR0912, PLR0913 data["symbol"] = symbol else: if any(v is not None for v in (x, y, z)): - raise GMTParameterError(at_most_one={"data", "x", "y", "z"}) + raise GMTParameterError(at_most_one={"data", "x/y/z"}) for name, value in [ ("direction", direction),