diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index 93b80bb082b..7fcfa4d6fb2 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 parameter names, of which 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,11 @@ def __init__( "Missing required parameters: " f"{', '.join(repr(par) for par in required)}." ) + if at_least_one: + msg.append( + "Missing parameter: requires at least one of " + f"{', '.join(repr(par) for par in at_least_one)}." + ) if reason: msg.append(reason) super().__init__(" ".join(msg)) 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 ab0c3dcc276..bd3f8d0d544 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,7 @@ 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 GMTInvalidInput(msg) + 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 485c0918fde..4825d934828 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 parameter: requires at least one ... """ _fill_params = "'constant_fill'/'grid_fill'/'neighbor_fill'/'spline_fill'" @@ -57,11 +57,15 @@ 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", + "inquire", + } ) - raise GMTInvalidInput(msg) @fmt_docstring diff --git a/pygmt/src/grdgradient.py b/pygmt/src/grdgradient.py index 6036cc54f56..6ac656f181d 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,7 @@ 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 GMTInvalidInput(msg) + raise GMTParameterError(at_least_one={"azimuth", "direction", "radiance"}) aliasdict = AliasSystem( A=Alias(azimuth, name="azimuth", sep="/", size=2), 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) 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