diff --git a/doc/api/index.rst b/doc/api/index.rst index 3656bba286e..bbf7595564a 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -295,10 +295,10 @@ All custom exceptions are derived from :class:`pygmt.exceptions.GMTError`. exceptions.GMTCLibError exceptions.GMTCLibNoSessionError exceptions.GMTCLibNotFoundError + exceptions.GMTParameterError exceptions.GMTTypeError exceptions.GMTValueError - .. currentmodule:: pygmt GMT C API diff --git a/pygmt/datasets/load_remote_dataset.py b/pygmt/datasets/load_remote_dataset.py index 3318602a247..4e6b1f67ccf 100644 --- a/pygmt/datasets/load_remote_dataset.py +++ b/pygmt/datasets/load_remote_dataset.py @@ -7,7 +7,7 @@ from typing import Any, Literal, NamedTuple import xarray as xr -from pygmt.exceptions import GMTInvalidInput, GMTValueError +from pygmt.exceptions import GMTParameterError, GMTValueError with contextlib.suppress(ImportError): # rioxarray is needed to register the rio accessor @@ -564,11 +564,10 @@ def _load_remote_dataset( reg = registration[0] if resinfo.tiled and region is None: - msg = ( - f"The 'region' parameter is required for {dataset.description} " - f"resolution '{resolution}'." + raise GMTParameterError( + required={"region"}, + reason=f"Required for {dataset.description} resolution {resolution!r} with tiled grids.", ) - raise GMTInvalidInput(msg) fname = f"@{prefix}_{resolution}_{reg}" grid = xr.load_dataarray( diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index dedb64db7fa..b275067f6e7 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -4,7 +4,7 @@ All exceptions derive from GMTError. """ -from collections.abc import Iterable +from collections.abc import Iterable, Set from typing import Any @@ -130,3 +130,51 @@ def __init__(self, dtype: object, /, reason: str | None = None): if reason: msg += f" {reason}" super().__init__(msg) + + +class GMTParameterError(GMTError): + """ + Raised when parameters are missing or invalid. + + Parameters + ---------- + required + Names of required parameters. + require_any + Names of parameters where at least one must be specified. + exclusive + Names of mutually exclusive parameters. + reason + Detailed reason why the parameters are invalid. + """ + + def __init__( + self, + *, + required: Set[str] | None = None, + require_any: Set[str] | None = None, + exclusive: Set[str] | None = None, + reason: str | None = None, + ): + msg = [] + if required: + msg.append( + "Required parameter(s) are missing: " + f"{', '.join(repr(par) for par in required)}." + ) + + if require_any: + msg.append( + "At least one of the following parameters must be specified: " + f"{', '.join(repr(par) for par in require_any)}." + ) + + if exclusive: + msg.append( + "Mutually exclusive parameter(s) are specified: " + f"{', '.join(repr(par) for par in exclusive)}." + ) + + if reason: + msg.append(reason) + super().__init__(" ".join(msg)) diff --git a/pygmt/src/coast.py b/pygmt/src/coast.py index 9f54ffcafe0..2ef9b68a4ab 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, @@ -202,11 +202,18 @@ def coast( """ self._activate_figure() if not args_in_kwargs(args=["C", "G", "S", "I", "N", "E", "Q", "W"], kwargs=kwargs): - msg = ( - "At least one of the following parameters must be specified: " - "lakes, land, water, rivers, borders, dcw, Q, or shorelines." + raise GMTParameterError( + require_any={ + "lakes", + "land", + "water", + "rivers", + "borders", + "dcw", + "Q", + "shorelines", + } ) - raise GMTInvalidInput(msg) aliasdict = AliasSystem( D=Alias( diff --git a/pygmt/src/dimfilter.py b/pygmt/src/dimfilter.py index f70b6ea12a6..acd63154f9d 100644 --- a/pygmt/src/dimfilter.py +++ b/pygmt/src/dimfilter.py @@ -9,7 +9,7 @@ from pygmt._typing import PathLike from pygmt.alias import 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__ = ["dimfilter"] @@ -142,11 +142,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(require_any={"distance", "filter", "sectors"}) aliasdict = AliasSystem().add_common( R=region, diff --git a/pygmt/src/filter1d.py b/pygmt/src/filter1d.py index 101cd9c9f1d..983be6d8da3 100644 --- a/pygmt/src/filter1d.py +++ b/pygmt/src/filter1d.py @@ -9,7 +9,7 @@ from pygmt._typing import PathLike, TableLike from pygmt.alias import 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, @@ -112,8 +112,7 @@ def filter1d( (depends on ``output_type``) """ if kwargs.get("F") is None: - msg = "Pass a required argument to 'filter_type'." - raise GMTInvalidInput(msg) + raise GMTParameterError(required={"filter_type"}) output_type = validate_output_table_type(output_type, outfile=outfile) diff --git a/pygmt/src/grd2cpt.py b/pygmt/src/grd2cpt.py index 3408faa2c65..38697479025 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(exclusive={"categorical", "cyclic"}) if (output := kwargs.pop("H", None)) is not None: kwargs["H"] = True diff --git a/pygmt/src/grdclip.py b/pygmt/src/grdclip.py index d735f6ee1dc..18c8ca69899 100644 --- a/pygmt/src/grdclip.py +++ b/pygmt/src/grdclip.py @@ -9,8 +9,12 @@ from pygmt._typing import PathLike from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import build_arg_list, deprecate_parameter, fmt_docstring +from pygmt.exceptions import GMTParameterError +from pygmt.helpers import ( + build_arg_list, + deprecate_parameter, + fmt_docstring, +) __doctest_skip__ = ["grdclip"] @@ -111,11 +115,7 @@ def grdclip( [0.0, 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(require_any={"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 11dd368caf5..3b83904cfdd 100644 --- a/pygmt/src/grdfill.py +++ b/pygmt/src/grdfill.py @@ -11,8 +11,13 @@ from pygmt._typing import PathLike from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import build_arg_list, deprecate_parameter, fmt_docstring, use_alias +from pygmt.exceptions import GMTParameterError +from pygmt.helpers import ( + build_arg_list, + deprecate_parameter, + fmt_docstring, + use_alias, +) __doctest_skip__ = ["grdfill"] @@ -34,22 +39,22 @@ def _validate_params( >>> _validate_params(constantfill=20.0, gridfill="bggrid.nc") Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Parameters ... are mutually exclusive. + pygmt.exceptions.GMTParameterError: Mutually exclusive parameter... >>> _validate_params(constantfill=20.0, inquire=True) Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Parameters ... are mutually exclusive. + pygmt.exceptions.GMTParameterError: Mutually exclusive parameter... >>> _validate_params() Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Need to specify parameter ... + pygmt.exceptions.GMTParameterError: ... """ - _fill_params = "'constantfill'/'gridfill'/'neighborfill'/'splinefill'" + _fill_params = {"constantfill", "gridfill", "neighborfill", "splinefill"} # The deprecated 'mode' parameter is given. if mode is not None: msg = ( "The 'mode' parameter is deprecated since v0.15.0 and will be removed in " - f"v0.19.0. Use {_fill_params} instead." + f"v0.19.0. Use {', '.join(repr(par) for par in _fill_params)} instead." ) warnings.warn(msg, FutureWarning, stacklevel=2) @@ -58,14 +63,15 @@ def _validate_params( for param in [constantfill, gridfill, neighborfill, splinefill, inquire, mode] ) if n_given > 1: # More than one mutually exclusive parameter is given. - msg = f"Parameters {_fill_params}/'inquire'/'mode' are mutually exclusive." - raise GMTInvalidInput(msg) + raise GMTParameterError(exclusive=[*_fill_params, "inquire", "mode"]) 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( + required=_fill_params, + reason=( + f"Need to specify parameter {_fill_params!r} for filling holes 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 c10784a0391..3c6631ea95d 100644 --- a/pygmt/src/grdgradient.py +++ b/pygmt/src/grdgradient.py @@ -9,7 +9,7 @@ from pygmt._typing import PathLike from pygmt.alias import 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, @@ -170,14 +170,12 @@ def grdgradient( >>> new_grid = pygmt.grdgradient(grid=grid, azimuth=10) """ if kwargs.get("Q") is not None and kwargs.get("N") is None: - msg = "Must specify normalize if tiles is specified." - raise GMTInvalidInput(msg) - if not args_in_kwargs(args=["A", "D", "E"], kwargs=kwargs): - msg = ( - "At least one of the following parameters must be specified: " - "azimuth, direction, or radiance." + raise GMTParameterError( + required={"normalize"}, + reason="Must specify 'normalize' if 'tiles' is specified.", ) - raise GMTInvalidInput(msg) + if not args_in_kwargs(args=["A", "D", "E"], kwargs=kwargs): + raise GMTParameterError(require_any={"azimuth", "direction", "radiance"}) aliasdict = AliasSystem().add_common( R=region, diff --git a/pygmt/src/grdlandmask.py b/pygmt/src/grdlandmask.py index c91cb262e67..85eebd92a7c 100644 --- a/pygmt/src/grdlandmask.py +++ b/pygmt/src/grdlandmask.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__ = ["grdlandmask"] @@ -111,8 +111,7 @@ def grdlandmask( >>> landmask = pygmt.grdlandmask(spacing=1, region=[125, 130, 30, 35]) """ if kwargs.get("I") is None or kwargs.get("R", region) is None: - msg = "Both 'region' and 'spacing' must be specified." - raise GMTInvalidInput(msg) + raise GMTParameterError(required={"spacing", "region"}) aliasdict = AliasSystem( D=Alias( diff --git a/pygmt/src/grdproject.py b/pygmt/src/grdproject.py index 7678ab42d63..9683d0a3018 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 +from pygmt.exceptions import GMTParameterError from pygmt.helpers import build_arg_list, fmt_docstring, kwargs_to_strings, use_alias __doctest_skip__ = ["grdproject"] @@ -117,8 +117,7 @@ def grdproject( >>> new_grid = pygmt.grdproject(grid=grid, projection="M10c", region=region) """ if kwargs.get("J", projection) is None: - msg = "Parameter 'projection' must be specified." - raise GMTInvalidInput(msg) + raise GMTParameterError(required={"projection"}) aliasdict = AliasSystem( C=Alias(center, name="center", sep="/", size=2), diff --git a/pygmt/src/grdtrack.py b/pygmt/src/grdtrack.py index fd0631de1fc..6c1546b4783 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 +from pygmt.exceptions import GMTParameterError from pygmt.helpers import ( build_arg_list, fmt_docstring, @@ -297,16 +297,16 @@ 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(exclusive={"points", "profile"}) if points is None and kwargs.get("E") is None: - msg = "Must give 'points' or set 'profile'." - raise GMTInvalidInput(msg) + raise GMTParameterError(require_any={"points", "profile"}) if hasattr(points, "columns") and newcolname is None: - msg = "Please pass in a str to 'newcolname'." - raise GMTInvalidInput(msg) + raise GMTParameterError( + required={"newcolname"}, + reason="Parameter 'newcolname' is required when 'points' is a pandas.DataFrame object.", + ) output_type = validate_output_table_type(output_type, outfile=outfile) diff --git a/pygmt/src/makecpt.py b/pygmt/src/makecpt.py index 96adc0d426d..f19ff762dfd 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(exclusive={"categorical", "cyclic"}) if (output := kwargs.pop("H", None)) is not None: kwargs["H"] = True diff --git a/pygmt/src/meca.py b/pygmt/src/meca.py index 54fb84cac16..a7208eb5ca5 100644 --- a/pygmt/src/meca.py +++ b/pygmt/src/meca.py @@ -10,7 +10,7 @@ from pygmt._typing import PathLike, TableLike 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, data_kind, @@ -30,8 +30,7 @@ def _get_focal_convention(spec, convention, component) -> _FocalMechanismConvent # Determine the convention from the 'convention' parameter. if convention is None: - msg = "Parameter 'convention' must be specified." - raise GMTInvalidInput(msg) + raise GMTParameterError(required={"convention"}) return _FocalMechanismConvention(convention=convention, component=component) diff --git a/pygmt/src/plot.py b/pygmt/src/plot.py index 6fecd648733..1700907b978 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, @@ -273,8 +273,10 @@ 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( + exclusive={"data", "x/y/z"}, + reason="Too much data. Use either data or x/y/z.", + ) for name, value in [ ("direction", direction), ("fill", kwargs.get("G")), diff --git a/pygmt/src/plot3d.py b/pygmt/src/plot3d.py index c7c4cad5d74..37e137beee0 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, @@ -253,8 +253,10 @@ 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( + exclusive={"data", "x/y/z"}, + reason="Too much data. Use either data or x/y/z.", + ) for name, value in [ ("direction", direction), diff --git a/pygmt/src/project.py b/pygmt/src/project.py index 1778a331cd3..c9d6520c6a8 100644 --- a/pygmt/src/project.py +++ b/pygmt/src/project.py @@ -10,7 +10,7 @@ from pygmt._typing import PathLike, TableLike 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, @@ -221,14 +221,14 @@ def project( # noqa: PLR0913 (depends on ``output_type``) """ if kwargs.get("C", center) is None: - msg = "Parameter 'center' must be specified." - raise GMTInvalidInput(msg) + raise GMTParameterError(required={"center"}) if kwargs.get("G") is None and data is None: - msg = "The 'data' parameter must be specified unless 'generate' is used." - raise GMTInvalidInput(msg) + raise GMTParameterError( + required={"data"}, + reason="Parameter 'data' must be specified unless 'generate' is used.", + ) if kwargs.get("G") is not None and kwargs.get("F") is not None: - msg = "The 'convention' parameter is not allowed with 'generate'." - raise GMTInvalidInput(msg) + raise GMTParameterError(exclusive={"generate", "convention"}) output_type = validate_output_table_type(output_type, outfile=outfile) diff --git a/pygmt/src/sphdistance.py b/pygmt/src/sphdistance.py index ba185a7a665..d8f4c86abf6 100644 --- a/pygmt/src/sphdistance.py +++ b/pygmt/src/sphdistance.py @@ -10,7 +10,7 @@ from pygmt._typing import PathLike, TableLike from pygmt.alias import 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__ = ["sphdistance"] @@ -122,8 +122,7 @@ def sphdistance( ... ) """ if kwargs.get("I") is None or kwargs.get("R", region) is None: - msg = "Both 'region' and 'spacing' must be specified." - raise GMTInvalidInput(msg) + raise GMTParameterError(required={"spacing", "region"}) aliasdict = AliasSystem().add_common( R=region, diff --git a/pygmt/src/subplot.py b/pygmt/src/subplot.py index 8916d0edbb7..0dad9685e97 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, @@ -170,8 +170,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(exclusive={"figsize", "subsize"}) aliasdict = AliasSystem( M=Alias(margins, name="margins", sep="/", size=(2, 4)), diff --git a/pygmt/src/text.py b/pygmt/src/text.py index 900bda554c9..e1b3c586dad 100644 --- a/pygmt/src/text.py +++ b/pygmt/src/text.py @@ -9,7 +9,7 @@ from pygmt._typing import AnchorCode, PathLike, StringArrayTypes, 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 ( _check_encoding, build_arg_list, @@ -34,7 +34,7 @@ it="use_word", w="wrap", ) -def text_( # noqa: PLR0912, PLR0913, PLR0915 +def text_( # noqa: PLR0912, PLR0913 self, textfiles: PathLike | TableLike | None = None, x=None, @@ -191,28 +191,31 @@ def text_( # noqa: PLR0912, PLR0913, PLR0915 + (position is not None) + (x is not None or y is not None) ) != 1: - msg = "Provide either 'textfiles', 'x'/'y'/'text', or 'position'/'text'." - raise GMTInvalidInput(msg) + raise GMTParameterError( + exclusive={"textfiles", "x/y/text", "position/text"}, + reason="Provide either 'textfiles', 'x'/'y'/'text', or 'position'/'text'.", + ) data_is_required = position is None kind = data_kind(textfiles, required=data_is_required) if position is not None: if text is None: - msg = "'text' can't be None when 'position' is given." - raise GMTInvalidInput(msg) + raise GMTParameterError( + required={"text"}, + reason="Parameter 'text' is required when 'position' is given.", + ) if is_nonstr_iter(text): raise GMTTypeError( type(text), reason="Parameter 'text' can't be a sequence when 'position' is given.", ) - if textfiles is not None and text is not None: - msg = "'text' can't be specified when 'textfiles' is given." - raise GMTInvalidInput(msg) + raise GMTParameterError(exclusive={"text", "textfiles"}) if kind == "empty" and text is None: - msg = "Must provide text with x/y pairs." - raise GMTInvalidInput(msg) + raise GMTParameterError( + required={"text"}, reason="Must provide text with x/y pairs." + ) # Arguments that can accept arrays. array_args = [ diff --git a/pygmt/src/velo.py b/pygmt/src/velo.py index 01adc1b22ab..2693132af3a 100644 --- a/pygmt/src/velo.py +++ b/pygmt/src/velo.py @@ -10,8 +10,13 @@ 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.helpers import build_arg_list, fmt_docstring, kwargs_to_strings, use_alias +from pygmt.exceptions import GMTParameterError, GMTTypeError +from pygmt.helpers import ( + build_arg_list, + fmt_docstring, + kwargs_to_strings, + use_alias, +) @fmt_docstring @@ -252,11 +257,8 @@ def velo( """ self._activate_figure() - if kwargs.get("S") is None or ( - kwargs.get("S") is not None and not isinstance(kwargs["S"], str) - ): - msg = "The parameter 'spec' is required and has to be a string." - raise GMTInvalidInput(msg) + if kwargs.get("S") is None: + raise GMTParameterError(required={"spec"}) if isinstance(data, np.ndarray) and not pd.api.types.is_numeric_dtype(data): raise GMTTypeError( diff --git a/pygmt/src/xyz2grd.py b/pygmt/src/xyz2grd.py index 83bccb2e305..54736dfa26e 100644 --- a/pygmt/src/xyz2grd.py +++ b/pygmt/src/xyz2grd.py @@ -9,7 +9,7 @@ from pygmt._typing import PathLike, TableLike from pygmt.alias import 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__ = ["xyz2grd"] @@ -158,8 +158,7 @@ def xyz2grd( ... ) """ if kwargs.get("I") is None or kwargs.get("R", region) is None: - msg = "Both 'region' and 'spacing' must be specified." - raise GMTInvalidInput(msg) + raise GMTParameterError(required={"spacing", "region"}) aliasdict = AliasSystem().add_common( J=projection, 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_datasets_load_remote_datasets.py b/pygmt/tests/test_datasets_load_remote_datasets.py index e2cef8f8ac2..dfa7fc5bf75 100644 --- a/pygmt/tests/test_datasets_load_remote_datasets.py +++ b/pygmt/tests/test_datasets_load_remote_datasets.py @@ -5,7 +5,7 @@ import pytest from pygmt.datasets.load_remote_dataset import _load_remote_dataset from pygmt.enums import GridRegistration -from pygmt.exceptions import GMTInvalidInput, GMTValueError +from pygmt.exceptions import GMTParameterError, GMTValueError def load_remote_dataset_wrapper(resolution="01d", region=None, registration=None): @@ -61,7 +61,7 @@ def test_load_remote_dataset_tiled_grid_without_region(): Make sure _load_remote_dataset fails when trying to load a tiled grid without specifying a region. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): load_remote_dataset_wrapper(resolution="01m") 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_grd2cpt.py b/pygmt/tests/test_grd2cpt.py index db8eecb73a4..5430742054d 100644 --- a/pygmt/tests/test_grd2cpt.py +++ b/pygmt/tests/test_grd2cpt.py @@ -6,7 +6,7 @@ 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 +70,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="batlow", categorical=True, cyclic=True) diff --git a/pygmt/tests/test_grdclip.py b/pygmt/tests/test_grdclip.py index a8824467131..1a8284dfaaa 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 @@ -110,5 +110,5 @@ def test_grdclip_missing_required_parameter(grid): """ Test that grdclip raises a ValueError if the required parameter is 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 8014fe3ce03..953bfbab688 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 GMTParameterError from pygmt.helpers import GMTTempFile from pygmt.helpers.testing import load_static_earth_relief @@ -152,7 +152,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) @@ -160,7 +160,7 @@ 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, constantfill=20) diff --git a/pygmt/tests/test_grdgradient.py b/pygmt/tests/test_grdgradient.py index 5749b237aea..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 +from pygmt.exceptions import GMTParameterError from pygmt.helpers import GMTTempFile from pygmt.helpers.testing import load_static_earth_relief @@ -80,8 +80,8 @@ 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(GMTInvalidInput): + with pytest.raises(GMTParameterError): # fails when tiles is specified but not normalize grdgradient(grid=grid, azimuth=10, direction="c", tiles="c") diff --git a/pygmt/tests/test_grdlandmask.py b/pygmt/tests/test_grdlandmask.py index 21b45d8d8c5..9155890c414 100644 --- a/pygmt/tests/test_grdlandmask.py +++ b/pygmt/tests/test_grdlandmask.py @@ -8,7 +8,7 @@ import xarray as xr from pygmt import grdlandmask from pygmt.enums import GridRegistration, GridType -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTParameterError from pygmt.helpers import GMTTempFile @@ -64,5 +64,5 @@ def test_grdlandmask_fails(): """ Check that grdlandmask fails correctly when region and spacing are not given. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): grdlandmask() diff --git a/pygmt/tests/test_grdproject.py b/pygmt/tests/test_grdproject.py index 8d4a1f70a21..f985b1df1ed 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 +from pygmt.exceptions import GMTParameterError from pygmt.helpers import GMTTempFile from pygmt.helpers.testing import load_static_earth_relief @@ -85,5 +85,5 @@ def test_grdproject_fails(grid): """ Check that grdproject fails correctly. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): grdproject(grid=grid) diff --git a/pygmt/tests/test_grdtrack.py b/pygmt/tests/test_grdtrack.py index 1b63dae041a..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, GMTTypeError +from pygmt.exceptions import GMTParameterError, GMTTypeError from pygmt.helpers import GMTTempFile from pygmt.helpers.testing import load_static_earth_relief @@ -146,7 +146,7 @@ def test_grdtrack_without_newcolname_setting(dataarray, dataframe): """ Run grdtrack by not passing in newcolname parameter setting. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): grdtrack(points=dataframe, grid=dataarray) @@ -154,7 +154,7 @@ def test_grdtrack_without_outfile_setting(dataarray, dataframe): """ Run grdtrack by not passing in outfile parameter setting. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): grdtrack(points=dataframe, grid=dataarray) @@ -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_makecpt.py b/pygmt/tests/test_makecpt.py index 6b55c21b27c..cc1013cdf18 100644 --- a/pygmt/tests/test_makecpt.py +++ b/pygmt/tests/test_makecpt.py @@ -7,7 +7,7 @@ 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 +166,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="batlow", categorical=True, cyclic=True) diff --git a/pygmt/tests/test_meca.py b/pygmt/tests/test_meca.py index 618b1cfbbed..2b1b41218f3 100644 --- a/pygmt/tests/test_meca.py +++ b/pygmt/tests/test_meca.py @@ -8,7 +8,7 @@ import pandas as pd import pytest from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput, GMTValueError +from pygmt.exceptions import GMTParameterError, GMTValueError from pygmt.helpers import GMTTempFile @@ -283,7 +283,7 @@ def test_meca_spec_ndarray_no_convention(): """ fig = Figure() fig.basemap(region=[-125, -122, 47, 49], projection="M6c", frame=True) - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.meca(spec=np.array([[-124, 48, 12.0, 330, 30, 90, 3]]), scale="1c") diff --git a/pygmt/tests/test_plot.py b/pygmt/tests/test_plot.py index c38f195b958..6c589ae6876 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 GMTParameterError, GMTTypeError from pygmt.helpers import GMTTempFile POINTS_DATA = Path(__file__).parent / "data" / "points.txt" @@ -55,11 +55,11 @@ def test_plot_fail_no_data(data, region): Plot should raise an exception if no data is given. """ fig = Figure() - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.plot( region=region, projection="X10c", style="c0.2c", fill="red", frame="afg" ) - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.plot( x=data[:, 0], region=region, @@ -68,7 +68,7 @@ def test_plot_fail_no_data(data, region): fill="red", frame="afg", ) - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.plot( y=data[:, 0], region=region, @@ -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 69237e02447..dd14f17ea1b 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 GMTParameterError, GMTTypeError from pygmt.helpers import GMTTempFile POINTS_DATA = Path(__file__).parent / "data" / "points.txt" @@ -93,11 +93,11 @@ def test_plot3d_fail_no_data(data, region): Should raise an exception if data is not enough or too much. """ fig = Figure() - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): 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" ) diff --git a/pygmt/tests/test_project.py b/pygmt/tests/test_project.py index 36917368c63..ea032d15e30 100644 --- a/pygmt/tests/test_project.py +++ b/pygmt/tests/test_project.py @@ -10,7 +10,7 @@ import pytest import xarray as xr from pygmt import project -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTParameterError from pygmt.helpers import GMTTempFile @@ -81,12 +81,12 @@ def test_project_incorrect_parameters(): Run project by providing incorrect parameters such as 1) no `center`; 2) no `data` or `generate`; and 3) `generate` with `convention`. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError, match="center"): # No `center` project(azimuth=45) - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): # No `data` or `generate` project(center=[0, -1], azimuth=45, flat_earth=True) - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): # Using `generate` with `convention` project(center=[0, -1], generate=0.5, convention="xypqrsz") diff --git a/pygmt/tests/test_sphdistance.py b/pygmt/tests/test_sphdistance.py index f96f14530e5..a4ad9fb2cdf 100644 --- a/pygmt/tests/test_sphdistance.py +++ b/pygmt/tests/test_sphdistance.py @@ -9,7 +9,7 @@ import pytest from pygmt import sphdistance from pygmt.enums import GridRegistration, GridType -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTParameterError from pygmt.helpers import GMTTempFile @@ -69,5 +69,5 @@ def test_sphdistance_fails(array): """ Check that sphdistance fails correctly when neither increment nor region is given. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): sphdistance(data=array) diff --git a/pygmt/tests/test_subplot.py b/pygmt/tests/test_subplot.py index 62b015f188e..dec12bbb194 100644 --- a/pygmt/tests/test_subplot.py +++ b/pygmt/tests/test_subplot.py @@ -4,7 +4,7 @@ import pytest from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput, GMTValueError +from pygmt.exceptions import GMTParameterError, GMTValueError @pytest.mark.benchmark @@ -89,7 +89,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 diff --git a/pygmt/tests/test_text.py b/pygmt/tests/test_text.py index c053248be02..25082b0d65a 100644 --- a/pygmt/tests/test_text.py +++ b/pygmt/tests/test_text.py @@ -7,7 +7,11 @@ import numpy as np import pytest from pygmt import Figure, config -from pygmt.exceptions import GMTCLibError, GMTInvalidInput, GMTTypeError +from pygmt.exceptions import ( + GMTCLibError, + GMTParameterError, + GMTTypeError, +) from pygmt.helpers import GMTTempFile from pygmt.helpers.testing import skip_if_no @@ -85,7 +89,7 @@ def test_text_without_text_input(region, projection): Run text by passing in x and y, but no text. """ fig = Figure() - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.text(region=region, projection=projection, x=1.2, y=2.4) @@ -146,20 +150,20 @@ def test_text_invalid_inputs(region): Run text by providing invalid combinations of inputs. """ fig = Figure() - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.text( region=region, projection="x1c", x=1.2, y=2.4, position="MC", text="text" ) - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.text(region=region, projection="x1c", textfiles="file.txt", text="text") - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.text(region=region, projection="x1c", position="MC", text=None) + with pytest.raises(GMTParameterError): + fig.text(region=region, projection="x1c", textfiles="file.txt", x=1.2, y=2.4) with pytest.raises(GMTTypeError): fig.text( region=region, projection="x1c", position="MC", text=["text1", "text2"] ) - with pytest.raises(GMTInvalidInput): - fig.text(region=region, projection="x1c", textfiles="file.txt", x=1.2, y=2.4) @pytest.mark.mpl_image_compare diff --git a/pygmt/tests/test_velo.py b/pygmt/tests/test_velo.py index ae5d44b62dd..a5675c6bc43 100644 --- a/pygmt/tests/test_velo.py +++ b/pygmt/tests/test_velo.py @@ -5,7 +5,7 @@ import pandas as pd import pytest from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput, GMTTypeError +from pygmt.exceptions import GMTParameterError, GMTTypeError @pytest.fixture(scope="module", name="dataframe") @@ -60,7 +60,7 @@ def test_velo_without_spec(dataframe): Check that velo fails when the spec parameter is not given. """ fig = Figure() - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError, match="spec"): fig.velo(data=dataframe) diff --git a/pygmt/tests/test_xyz2grd.py b/pygmt/tests/test_xyz2grd.py index 9f70c73cf8c..bf7fe854233 100644 --- a/pygmt/tests/test_xyz2grd.py +++ b/pygmt/tests/test_xyz2grd.py @@ -10,7 +10,7 @@ from pygmt import xyz2grd from pygmt.datasets import load_sample_data from pygmt.enums import GridRegistration, GridType -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTParameterError from pygmt.helpers import GMTTempFile @@ -75,9 +75,9 @@ def test_xyz2grd_missing_region_spacing(ship_data): """ Test xyz2grd raise an exception if region or spacing is missing. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): xyz2grd(data=ship_data) - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): xyz2grd(data=ship_data, region=[245, 255, 20, 30]) - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): xyz2grd(data=ship_data, spacing=5)