diff --git a/pygmt/alias.py b/pygmt/alias.py index 0a66c8a91dd..5017110794b 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -7,7 +7,7 @@ from collections.abc import Mapping, Sequence from typing import Any, Literal -from pygmt.exceptions import GMTInvalidInput, GMTValueError +from pygmt.exceptions import GMTConflictParameterError, GMTValueError from pygmt.helpers.utils import is_nonstr_iter, sequence_join @@ -387,10 +387,11 @@ def merge(self, kwargs: Mapping[str, Any]): # Long-form parameters are already specified. if long_param_given: msg = ( - f"Short-form parameter {short_param!r} conflicts with long-form " - f"parameters and is not recommended. {_msg_long}" + f"Conflicting parameters: {short_param!r}. Short-form parameter " + f"{short_param!r} conflicts with long-form parameters and is not " + f"recommended. {_msg_long}" ) - raise GMTInvalidInput(msg) + raise GMTConflictParameterError(msg) # Long-form parameters are not specified. msg = ( diff --git a/pygmt/datasets/load_remote_dataset.py b/pygmt/datasets/load_remote_dataset.py index 3318602a247..182bbc1b788 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 GMTRequiredParameterError, GMTValueError with contextlib.suppress(ImportError): # rioxarray is needed to register the rio accessor @@ -565,10 +565,10 @@ def _load_remote_dataset( if resinfo.tiled and region is None: msg = ( - f"The 'region' parameter is required for {dataset.description} " - f"resolution '{resolution}'." + f"Missing required parameter: 'region'. The 'region' parameter is required " + f"for {dataset.description} resolution '{resolution}'." ) - raise GMTInvalidInput(msg) + raise GMTRequiredParameterError(msg) fname = f"@{prefix}_{resolution}_{reg}" grid = xr.load_dataarray( diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index dedb64db7fa..7e853da5401 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -130,3 +130,69 @@ def __init__(self, dtype: object, /, reason: str | None = None): if reason: msg += f" {reason}" super().__init__(msg) + + +class GMTParameterError(GMTError): + """ + Base class for parameter-related errors. + + This exception is raised when there are issues with the parameters passed to a + function/method, such as missing required parameters or conflicting parameters. + """ + + +class GMTRequiredParameterError(GMTParameterError): + """ + Raised when a required parameter is missing. + + Parameters + ---------- + context + The error message describing the missing parameter and why it's required. + + Examples + -------- + >>> raise GMTRequiredParameterError("Missing required parameter: 'width'.") + Traceback (most recent call last): + ... + pygmt.exceptions.GMTRequiredParameterError: Missing required parameter: 'width'. + >>> raise GMTRequiredParameterError( + ... "Missing required parameter: 'data'. No input data provided." + ... ) + Traceback (most recent call last): + ... + pygmt.exceptions.GMTRequiredParameterError: Missing required parameter: + ... 'data'. No input data provided. + """ + + def __init__(self, context: str): + super().__init__(context) + + +class GMTConflictParameterError(GMTParameterError): + """ + Raised when conflicting parameters are provided. + + Parameters + ---------- + context + The error message describing the conflicting parameters and why they conflict. + + Examples + -------- + >>> raise GMTConflictParameterError("Conflicting parameters: 'width' and 'height'.") + Traceback (most recent call last): + ... + pygmt.exceptions.GMTConflictParameterError: Conflicting parameters: + ... 'width' and 'height'. + >>> raise GMTConflictParameterError( + ... "Conflicting parameters: 'data' and 'x/y'. Use either data or x/y/z." + ... ) + Traceback (most recent call last): + ... + pygmt.exceptions.GMTConflictParameterError: Conflicting parameters: + ... 'data' and 'x/y'. Use either data or x/y/z. + """ + + def __init__(self, context: str): + super().__init__(context) diff --git a/pygmt/helpers/decorators.py b/pygmt/helpers/decorators.py index 529e8047dd6..bdde03a427e 100644 --- a/pygmt/helpers/decorators.py +++ b/pygmt/helpers/decorators.py @@ -12,7 +12,7 @@ from inspect import Parameter, signature import numpy as np -from pygmt.exceptions import GMTInvalidInput, GMTValueError +from pygmt.exceptions import GMTConflictParameterError, GMTInvalidInput, GMTValueError from pygmt.helpers.utils import is_nonstr_iter COMMON_DOCSTRINGS = { @@ -551,10 +551,11 @@ def new_module(*args, **kwargs): for short_param, long_alias in aliases.items(): if long_alias in kwargs and short_param in kwargs: msg = ( - f"Parameters in short-form ({short_param}) and " - f"long-form ({long_alias}) can't coexist." + f"Conflicting parameters: {short_param}, {long_alias}. " + f"Parameters in short-form ({short_param}) and long-form " + f"({long_alias}) can't coexist." ) - raise GMTInvalidInput(msg) + raise GMTConflictParameterError(msg) if long_alias in kwargs: kwargs[short_param] = kwargs.pop(long_alias) elif short_param in kwargs: @@ -821,8 +822,11 @@ def new_module(*args, **kwargs): """ if oldname in kwargs: if newname in kwargs: - msg = f"Can't provide both '{newname}' and '{oldname}'." - raise GMTInvalidInput(msg) + msg = ( + f"Conflicting parameters: {newname}, {oldname}. " + f"Can't provide both '{newname}' and '{oldname}'." + ) + raise GMTConflictParameterError(msg) msg = ( f"The '{oldname}' parameter has been deprecated since {deprecate_version}" f" and will be removed in {remove_version}." diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index c2823bb8329..bed4792fb65 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -19,7 +19,12 @@ import xarray as xr from pygmt._typing import PathLike from pygmt.encodings import charset -from pygmt.exceptions import GMTInvalidInput, GMTValueError +from pygmt.exceptions import ( + GMTConflictParameterError, + GMTInvalidInput, + GMTRequiredParameterError, + GMTValueError, +) # Type hints for the list of encodings supported by PyGMT. Encoding = Literal[ @@ -121,40 +126,44 @@ def _validate_data_input( # noqa: PLR0912 if data is None: # data is None if x is None and y is None: # both x and y are None if required: # data is not optional - msg = "No input data provided." - raise GMTInvalidInput(msg) + msg = "Missing required parameter: 'data'. No input data provided." + raise GMTRequiredParameterError(msg) elif x is None or y is None: # either x or y is None - msg = "Must provide both x and y." - raise GMTInvalidInput(msg) + msg = "Missing required parameter: x and y. Must provide both x and y." + raise GMTRequiredParameterError(msg) if required_z and z is None: # both x and y are not None, now check z - msg = "Must provide x, y, and z." - raise GMTInvalidInput(msg) + msg = "Missing required parameter: 'z'. Must provide x, y, and z." + raise GMTRequiredParameterError(msg) else: # data is not None if x is not None or y is not None or z is not None: - msg = "Too much data. Use either data or x/y/z." - raise GMTInvalidInput(msg) + msg = ( + "Conflicting parameters: 'data' and 'x/y/z'. Use either data or x/y/z." + ) + raise GMTConflictParameterError(msg) # check if data has the required z column if required_z: - msg = "data must provide x, y, and z columns." if kind == "matrix" and data.shape[1] < 3: - raise GMTInvalidInput(msg) + msg = "Missing required parameter: 'data'. data must provide x, y, and z columns." + raise GMTRequiredParameterError(msg) if kind == "vectors": if hasattr(data, "shape") and ( (len(data.shape) == 1 and data.shape[0] < 3) or (len(data.shape) > 1 and data.shape[1] < 3) ): # np.ndarray or pd.DataFrame - raise GMTInvalidInput(msg) + msg = "Missing required parameter: 'data'. data must provide x, y, and z columns." + raise GMTRequiredParameterError(msg) if hasattr(data, "data_vars") and len(data.data_vars) < 3: # xr.Dataset - raise GMTInvalidInput(msg) + msg = "Missing required parameter: 'data'. data must provide x, y, and z columns." + raise GMTRequiredParameterError(msg) if kind == "vectors" and isinstance(data, dict): # Iterator over the up-to-3 first elements. arrays = list(islice(data.values(), 3)) if len(arrays) < 2 or any(v is None for v in arrays[:2]): # Check x/y - msg = "Must provide x and y." - raise GMTInvalidInput(msg) + msg = "Missing required parameter: x and y. Must provide x and y." + raise GMTRequiredParameterError(msg) if required_z and (len(arrays) < 3 or arrays[2] is None): # Check z - msg = "Must provide x, y, and z." - raise GMTInvalidInput(msg) + msg = "Missing required parameter: 'z'. Must provide x, y, and z." + raise GMTRequiredParameterError(msg) def _is_printable_ascii(argstr: str) -> bool: diff --git a/pygmt/helpers/validators.py b/pygmt/helpers/validators.py index 34a8e85e1b7..b00f9a00003 100644 --- a/pygmt/helpers/validators.py +++ b/pygmt/helpers/validators.py @@ -6,7 +6,7 @@ from typing import Literal from pygmt._typing import PathLike -from pygmt.exceptions import GMTInvalidInput, GMTValueError +from pygmt.exceptions import GMTRequiredParameterError, GMTValueError def validate_output_table_type( @@ -57,8 +57,8 @@ def validate_output_table_type( choices=_valids, ) if output_type == "file" and outfile is None: - msg = "Must specify 'outfile' for output_type='file'." - raise GMTInvalidInput(msg) + msg = "Missing required parameter: 'outfile'. Must specify 'outfile' for output_type='file'." + raise GMTRequiredParameterError(msg) if output_type != "file" and outfile is not None: msg = ( f"Changing 'output_type' from '{output_type}' to 'file' since 'outfile' " diff --git a/pygmt/params/box.py b/pygmt/params/box.py index 33b335d25d7..3e6947c2db5 100644 --- a/pygmt/params/box.py +++ b/pygmt/params/box.py @@ -6,7 +6,7 @@ from collections.abc import Sequence from pygmt.alias import Alias -from pygmt.exceptions import GMTInvalidInput, GMTValueError +from pygmt.exceptions import GMTRequiredParameterError, GMTValueError from pygmt.helpers import is_nonstr_iter from pygmt.params.base import BaseParam @@ -65,8 +65,8 @@ def _validate(self): """ # inner_pen is required when inner_gap is set. if self.inner_gap is not None and self.inner_pen is None: - msg = "Parameter 'inner_pen' is required when 'inner_gap' is set." - raise GMTInvalidInput(msg) + msg = "Missing required parameter: 'inner_pen'. Parameter 'inner_pen' is required when 'inner_gap' is set." + raise GMTRequiredParameterError(msg) # shade_offset must be a sequence of two values or None. if self.shade_offset and not ( diff --git a/pygmt/src/_common.py b/pygmt/src/_common.py index 42213930263..3c43139001a 100644 --- a/pygmt/src/_common.py +++ b/pygmt/src/_common.py @@ -7,7 +7,11 @@ from pathlib import Path from typing import Any, ClassVar, Literal -from pygmt.exceptions import GMTInvalidInput, GMTValueError +from pygmt.exceptions import ( + GMTConflictParameterError, + GMTInvalidInput, + GMTValueError, +) from pygmt.params.position import Position from pygmt.src.which import which @@ -365,11 +369,12 @@ def _parse_position( elif kwdict is not None: # Raw GMT command string with potential conflicts. if any(v is not None and v is not False for v in kwdict.values()): msg = ( + f"Conflicting parameters: 'position' and other parameters. " "Parameter 'position' is given with a raw GMT command string, " "and conflicts with parameters " f"{', '.join(repr(c) for c in kwdict)}." ) - raise GMTInvalidInput(msg) + raise GMTConflictParameterError(msg) else: # No conflicting parameters to check, indicating it's a new function. # The string must be an anchor code. diff --git a/pygmt/src/coast.py b/pygmt/src/coast.py index 857148a7308..c298b793065 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 GMTRequiredParameterError from pygmt.helpers import args_in_kwargs, build_arg_list, fmt_docstring, use_alias from pygmt.params import Box @@ -243,10 +243,11 @@ def coast( # noqa: PLR0913 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." + "Missing required parameter: 'land', 'water', 'rivers', 'borders', " + "'shorelines', 'lakes', 'dcw', or 'Q'. " + "At least one of these parameters must be specified." ) - raise GMTInvalidInput(msg) + raise GMTRequiredParameterError(msg) aliasdict = AliasSystem( D=Alias( diff --git a/pygmt/src/dimfilter.py b/pygmt/src/dimfilter.py index 250184e963e..72e90df633f 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 GMTRequiredParameterError from pygmt.helpers import build_arg_list, fmt_docstring, use_alias __doctest_skip__ = ["dimfilter"] @@ -144,10 +144,11 @@ def dimfilter( """ if not all(arg in kwargs for arg in ["D", "F", "N"]) and "Q" not in kwargs: msg = ( + "Missing required parameter: 'distance', 'filters', or 'sectors'. " "At least one of the following parameters must be specified: " "distance, filters, or sectors." ) - raise GMTInvalidInput(msg) + raise GMTRequiredParameterError(msg) aliasdict = AliasSystem( I=Alias(spacing, name="spacing", sep="/", size=2), diff --git a/pygmt/src/filter1d.py b/pygmt/src/filter1d.py index edad413c990..ace4ab8d931 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 GMTRequiredParameterError from pygmt.helpers import ( build_arg_list, fmt_docstring, @@ -112,8 +112,8 @@ def filter1d( (depends on ``output_type``) """ if kwargs.get("F") is None: - msg = "Pass a required argument to 'filter_type'." - raise GMTInvalidInput(msg) + msg = "Missing required parameter: 'filter_type'. Pass a required argument to 'filter_type'." + raise GMTRequiredParameterError(msg) output_type = validate_output_table_type(output_type, outfile=outfile) diff --git a/pygmt/src/grd2cpt.py b/pygmt/src/grd2cpt.py index a09ac3aa4c2..6d3813487a8 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 GMTConflictParameterError from pygmt.helpers import build_arg_list, fmt_docstring, kwargs_to_strings, use_alias __doctest_skip__ = ["grd2cpt"] @@ -199,8 +199,8 @@ 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) + msg = "Conflicting parameters: 'categorical' and 'cyclic'. Set only 'categorical' or 'cyclic' to True, not both." + raise GMTConflictParameterError(msg) 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 ec36fd8df88..bcd5c577a1b 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 GMTRequiredParameterError from pygmt.helpers import build_arg_list, fmt_docstring __doctest_skip__ = ["grdclip"] @@ -110,10 +110,10 @@ def grdclip( """ 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'." + "Missing required parameter: 'above', 'below', 'between', or 'replace'. " + "Must specify at least one of these parameters." ) - raise GMTInvalidInput(msg) + raise GMTRequiredParameterError(msg) aliasdict = AliasSystem( Sa=Alias(above, name="above", sep="/", size=2), diff --git a/pygmt/src/grdfill.py b/pygmt/src/grdfill.py index 40818a8f4d5..1cae900d189 100644 --- a/pygmt/src/grdfill.py +++ b/pygmt/src/grdfill.py @@ -10,7 +10,10 @@ 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 ( + GMTConflictParameterError, + GMTRequiredParameterError, +) from pygmt.helpers import build_arg_list, deprecate_parameter, fmt_docstring, use_alias __doctest_skip__ = ["grdfill"] @@ -54,14 +57,18 @@ 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) + msg = ( + f"Conflicting parameters: {_fill_params}/'inquire'. " + f"Parameters are mutually exclusive." + ) + raise GMTConflictParameterError(msg) if n_given == 0: # No parameters are given. msg = ( + f"Missing required parameter: {_fill_params} or 'inquire'. " f"Need to specify parameter {_fill_params} for filling holes or " - "'inquire' for inquiring the bounds of each hole." + f"'inquire' for inquiring the bounds of each hole." ) - raise GMTInvalidInput(msg) + raise GMTRequiredParameterError(msg) @fmt_docstring diff --git a/pygmt/src/grdgradient.py b/pygmt/src/grdgradient.py index 2ee0c62c7b5..a9992e3d150 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 +from pygmt.exceptions import GMTRequiredParameterError from pygmt.helpers import build_arg_list, fmt_docstring, use_alias __doctest_skip__ = ["grdgradient"] @@ -165,18 +165,19 @@ 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) + msg = "Missing required parameter: 'normalize'. Must specify normalize if tiles is specified." + raise GMTRequiredParameterError(msg) if ( kwargs.get("A", azimuth) is None and kwargs.get("D") is None and kwargs.get("E", radiance) is None ): msg = ( + "Missing required parameter: 'azimuth', 'direction', or 'radiance'. " "At least one of the following parameters must be specified: " "azimuth, direction, or radiance." ) - raise GMTInvalidInput(msg) + raise GMTRequiredParameterError(msg) aliasdict = AliasSystem( A=Alias(azimuth, name="azimuth", sep="/", size=2), diff --git a/pygmt/src/grdhisteq.py b/pygmt/src/grdhisteq.py index a92adade6bf..3e227e6081b 100644 --- a/pygmt/src/grdhisteq.py +++ b/pygmt/src/grdhisteq.py @@ -11,7 +11,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 GMTConflictParameterError from pygmt.helpers import ( build_arg_list, fmt_docstring, @@ -234,8 +234,8 @@ def compute_bins( output_type = validate_output_table_type(output_type, outfile=outfile) if kwargs.get("h") is not None and output_type != "file": - msg = "'header' is only allowed with output_type='file'." - raise GMTInvalidInput(msg) + msg = "Conflicting parameters: 'header' and output_type. 'header' is only allowed with output_type='file'." + raise GMTConflictParameterError(msg) aliasdict = AliasSystem().add_common( R=region, diff --git a/pygmt/src/grdlandmask.py b/pygmt/src/grdlandmask.py index b6f6721cd96..83eeb6d1efa 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 GMTRequiredParameterError from pygmt.helpers import build_arg_list, deprecate_parameter, fmt_docstring, use_alias __doctest_skip__ = ["grdlandmask"] @@ -118,8 +118,8 @@ def grdlandmask( >>> landmask = pygmt.grdlandmask(spacing=1, region=[125, 130, 30, 35]) """ if kwargs.get("I", spacing) is None or kwargs.get("R", region) is None: - msg = "Both 'region' and 'spacing' must be specified." - raise GMTInvalidInput(msg) + msg = "Missing required parameter: 'region' and 'spacing'. Both 'region' and 'spacing' must be specified." + raise GMTRequiredParameterError(msg) aliasdict = AliasSystem( D=Alias( diff --git a/pygmt/src/grdproject.py b/pygmt/src/grdproject.py index 13eb49856be..004956f8038 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 GMTRequiredParameterError from pygmt.helpers import build_arg_list, fmt_docstring, use_alias __doctest_skip__ = ["grdproject"] @@ -117,8 +117,8 @@ def grdproject( # noqa: PLR0913 >>> 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) + msg = "Missing required parameter: 'projection'. Parameter 'projection' must be specified." + raise GMTRequiredParameterError(msg) aliasdict = AliasSystem( C=Alias(center, name="center", sep="/", size=2), diff --git a/pygmt/src/grdsample.py b/pygmt/src/grdsample.py index b50d68369e2..cd6c765fe43 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 GMTConflictParameterError from pygmt.helpers import ( build_arg_list, deprecate_parameter, @@ -101,8 +101,8 @@ def grdsample( """ # 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) + msg = "Conflicting parameters: 'toggle' and 'registration'. Parameters 'toggle' and 'registration' cannot be used together." + raise GMTConflictParameterError(msg) aliasdict = AliasSystem( I=Alias(spacing, name="spacing", sep="/", size=2), diff --git a/pygmt/src/grdtrack.py b/pygmt/src/grdtrack.py index 236cff1da8e..d78436511c5 100644 --- a/pygmt/src/grdtrack.py +++ b/pygmt/src/grdtrack.py @@ -11,7 +11,10 @@ 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 ( + GMTConflictParameterError, + GMTRequiredParameterError, +) from pygmt.helpers import ( build_arg_list, fmt_docstring, @@ -299,16 +302,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) + msg = "Conflicting parameters: 'points' and 'profile'. Can't set both 'points' and 'profile'." + raise GMTConflictParameterError(msg) if points is None and kwargs.get("E") is None: - msg = "Must give 'points' or set 'profile'." - raise GMTInvalidInput(msg) + msg = "Missing required parameter: 'points' or 'profile'. Must give 'points' or set 'profile'." + raise GMTRequiredParameterError(msg) if hasattr(points, "columns") and newcolname is None: - msg = "Please pass in a str to 'newcolname'." - raise GMTInvalidInput(msg) + msg = "Missing required parameter: 'newcolname'. Please pass in a str to 'newcolname'." + raise GMTRequiredParameterError(msg) output_type = validate_output_table_type(output_type, outfile=outfile) diff --git a/pygmt/src/histogram.py b/pygmt/src/histogram.py index 349bf23d62a..8bfb858637f 100644 --- a/pygmt/src/histogram.py +++ b/pygmt/src/histogram.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 +from pygmt.exceptions import GMTRequiredParameterError from pygmt.helpers import ( build_arg_list, deprecate_parameter, @@ -163,8 +163,8 @@ def histogram( # noqa: PLR0913 self._activate_figure() if bar_offset is not None and bar_width is None: - msg = "Setting 'bar_offset' requires setting 'bar_width'." - raise GMTInvalidInput(msg) + msg = "Missing required parameter: 'bar_width'. Setting 'bar_offset' requires setting 'bar_width'." + raise GMTRequiredParameterError(msg) aliasdict = AliasSystem( E=[ diff --git a/pygmt/src/inset.py b/pygmt/src/inset.py index 1b7d9367bcb..ac8f56460ae 100644 --- a/pygmt/src/inset.py +++ b/pygmt/src/inset.py @@ -9,7 +9,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 GMTRequiredParameterError from pygmt.helpers import ( build_arg_list, deprecate_parameter, @@ -139,8 +139,8 @@ def inset( and width is None and (projection is None or region is None) ): - msg = "Parameter 'width' must be specified." - raise GMTInvalidInput(msg) + msg = "Missing required parameter: 'width'. Parameter must be specified." + raise GMTRequiredParameterError(msg) aliasdict = AliasSystem( D=[ diff --git a/pygmt/src/logo.py b/pygmt/src/logo.py index c12bb1bbf05..9086559d201 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 GMTConflictParameterError from pygmt.helpers import build_arg_list, fmt_docstring from pygmt.params import Box, Position from pygmt.src._common import _parse_position @@ -105,8 +105,8 @@ def logo( # noqa: PLR0913 # 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) + msg = "Conflicting parameters: 'width' and 'height'. Cannot specify both." + raise GMTConflictParameterError(msg) aliasdict = AliasSystem( D=[ diff --git a/pygmt/src/magnetic_rose.py b/pygmt/src/magnetic_rose.py index e91ef24c1af..5eecd3417ca 100644 --- a/pygmt/src/magnetic_rose.py +++ b/pygmt/src/magnetic_rose.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 GMTRequiredParameterError from pygmt.helpers import build_arg_list, fmt_docstring from pygmt.params import Box, Position from pygmt.src._common import _parse_position @@ -111,8 +111,11 @@ def magnetic_rose( # noqa: PLR0913 position = _parse_position(position, default=Position("BL", cstype="inside")) if declination_label is not None and declination is None: - msg = "Parameter 'declination' must be set when 'declination_label' is set." - raise GMTInvalidInput(msg) + msg = ( + "Missing required parameter: 'declination'. " + "Must be set when 'declination_label' is set." + ) + raise GMTRequiredParameterError(msg) aliasdict = AliasSystem( F=Alias(box, name="box"), diff --git a/pygmt/src/makecpt.py b/pygmt/src/makecpt.py index 4b9d3392927..7994475ef51 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 GMTConflictParameterError from pygmt.helpers import build_arg_list, fmt_docstring, kwargs_to_strings, use_alias @@ -169,8 +169,8 @@ 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) + msg = "Conflicting parameters: 'categorical' and 'cyclic'. Set only categorical or cyclic to True, not both." + raise GMTConflictParameterError(msg) 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 56c63037286..8e729a639b5 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 GMTRequiredParameterError, GMTValueError from pygmt.helpers import ( build_arg_list, data_kind, @@ -31,8 +31,8 @@ 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) + msg = "Missing required parameter: 'convention'. Parameter 'convention' must be specified." + raise GMTRequiredParameterError(msg) return _FocalMechanismConvention(convention=convention, component=component) diff --git a/pygmt/src/plot.py b/pygmt/src/plot.py index f7c92bebc6e..ac91c789a39 100644 --- a/pygmt/src/plot.py +++ b/pygmt/src/plot.py @@ -8,7 +8,10 @@ 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 ( + GMTConflictParameterError, + GMTTypeError, +) from pygmt.helpers import ( build_arg_list, data_kind, @@ -272,8 +275,8 @@ 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) + msg = "Conflicting parameters: 'data' and 'x/y'. Too much data. Use either data or x/y/z." + raise GMTConflictParameterError(msg) for name, value in [ ("direction", direction), ("fill", kwargs.get("G")), diff --git a/pygmt/src/plot3d.py b/pygmt/src/plot3d.py index d4ce2940488..9deb9e7c1e6 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 GMTConflictParameterError, GMTTypeError from pygmt.helpers import ( build_arg_list, data_kind, @@ -252,8 +252,8 @@ 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) + msg = "Conflicting parameters: 'data' and 'x/y/z'. Too much data. Use either data or x/y/z." + raise GMTConflictParameterError(msg) for name, value in [ ("direction", direction), diff --git a/pygmt/src/project.py b/pygmt/src/project.py index ad88345d1cb..2c1617357d9 100644 --- a/pygmt/src/project.py +++ b/pygmt/src/project.py @@ -10,7 +10,10 @@ 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 ( + GMTConflictParameterError, + GMTRequiredParameterError, +) from pygmt.helpers import ( build_arg_list, fmt_docstring, @@ -221,14 +224,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) + msg = "Missing required parameter: 'center'. Parameter 'center' must be specified." + raise GMTRequiredParameterError(msg) if kwargs.get("G") is None and data is None: - msg = "The 'data' parameter must be specified unless 'generate' is used." - raise GMTInvalidInput(msg) + msg = "Missing required parameter: 'data'. The 'data' parameter must be specified unless 'generate' is used." + raise GMTRequiredParameterError(msg) 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) + msg = "Conflicting parameters: 'convention' and 'generate'. The 'convention' parameter is not allowed with 'generate'." + raise GMTConflictParameterError(msg) output_type = validate_output_table_type(output_type, outfile=outfile) diff --git a/pygmt/src/sphdistance.py b/pygmt/src/sphdistance.py index 6d4af6c46ec..6bc1da37d10 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 Alias, AliasSystem from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTRequiredParameterError from pygmt.helpers import build_arg_list, fmt_docstring, use_alias __doctest_skip__ = ["sphdistance"] @@ -122,8 +122,8 @@ def sphdistance( ... ) """ if kwargs.get("I", spacing) is None or kwargs.get("R", region) is None: - msg = "Both 'region' and 'spacing' must be specified." - raise GMTInvalidInput(msg) + msg = "Missing required parameter: 'region' and 'spacing'. Both 'region' and 'spacing' must be specified." + raise GMTRequiredParameterError(msg) aliasdict = AliasSystem( I=Alias(spacing, name="spacing", sep="/", size=2), diff --git a/pygmt/src/subplot.py b/pygmt/src/subplot.py index 0ef2ed37e34..b36eb6ba160 100644 --- a/pygmt/src/subplot.py +++ b/pygmt/src/subplot.py @@ -8,7 +8,10 @@ from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput, GMTValueError +from pygmt.exceptions import ( + GMTConflictParameterError, + GMTValueError, +) from pygmt.helpers import build_arg_list, fmt_docstring, kwargs_to_strings, use_alias @@ -166,8 +169,8 @@ def subplot( ) if kwargs.get("Ff") and kwargs.get("Fs"): - msg = "Please provide either one of 'figsize' or 'subsize' only." - raise GMTInvalidInput(msg) + msg = "Conflicting parameters: 'figsize' and 'subsize'. Please provide either one of 'figsize' or 'subsize' only." + raise GMTConflictParameterError(msg) aliasdict = AliasSystem( M=Alias(margins, name="margins", sep="/", size=(2, 4)), diff --git a/pygmt/src/text.py b/pygmt/src/text.py index 156dd616a92..d270d553477 100644 --- a/pygmt/src/text.py +++ b/pygmt/src/text.py @@ -9,7 +9,11 @@ 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 ( + GMTConflictParameterError, + GMTRequiredParameterError, + GMTTypeError, +) from pygmt.helpers import ( _check_encoding, build_arg_list, @@ -191,16 +195,19 @@ 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) + msg = ( + "Conflicting parameters: 'textfiles', 'x'/'y'/'text', or 'position'/'text'. " + "Provide either 'textfiles', 'x'/'y'/'text', or 'position'/'text'." + ) + raise GMTConflictParameterError(msg) 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) + msg = "Missing required parameter: 'text'. 'text' can't be None when 'position' is given." + raise GMTRequiredParameterError(msg) if is_nonstr_iter(text): raise GMTTypeError( type(text), @@ -208,11 +215,11 @@ def text_( # noqa: PLR0912, PLR0913, PLR0915 ) if textfiles is not None and text is not None: - msg = "'text' can't be specified when 'textfiles' is given." - raise GMTInvalidInput(msg) + msg = "Conflicting parameters: 'text' and 'textfiles'. 'text' can't be specified when 'textfiles' is given." + raise GMTConflictParameterError(msg) if kind == "empty" and text is None: - msg = "Must provide text with x/y pairs." - raise GMTInvalidInput(msg) + msg = "Missing required parameter: 'text'. Must provide text with x/y pairs." + raise GMTRequiredParameterError(msg) # Arguments that can accept arrays. array_args = [ diff --git a/pygmt/src/velo.py b/pygmt/src/velo.py index d1013f582e9..9be99686896 100644 --- a/pygmt/src/velo.py +++ b/pygmt/src/velo.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, GMTTypeError +from pygmt.exceptions import GMTRequiredParameterError, GMTTypeError from pygmt.helpers import build_arg_list, deprecate_parameter, fmt_docstring, use_alias @@ -259,8 +259,8 @@ def velo( # noqa : PLR0913 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) + msg = "Missing required parameter: 'spec'. The parameter 'spec' is required and has to be a string." + raise GMTRequiredParameterError(msg) 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 ab1f9f59b10..f85f927fff2 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 Alias, AliasSystem from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTRequiredParameterError from pygmt.helpers import build_arg_list, fmt_docstring, use_alias __doctest_skip__ = ["xyz2grd"] @@ -158,8 +158,8 @@ def xyz2grd( ... ) """ if kwargs.get("I", spacing) is None or kwargs.get("R", region) is None: - msg = "Both 'region' and 'spacing' must be specified." - raise GMTInvalidInput(msg) + msg = "Missing required parameter: 'region' and 'spacing'. Both 'region' and 'spacing' must be specified." + raise GMTRequiredParameterError(msg) aliasdict = AliasSystem( I=Alias(spacing, name="spacing", sep="/", size=2), diff --git a/pygmt/tests/test_alias_system.py b/pygmt/tests/test_alias_system.py index e7eb5683442..d0d0670000b 100644 --- a/pygmt/tests/test_alias_system.py +++ b/pygmt/tests/test_alias_system.py @@ -4,7 +4,7 @@ import pytest from pygmt.alias import Alias, AliasSystem -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTConflictParameterError from pygmt.helpers import build_arg_list @@ -77,8 +77,8 @@ def test_alias_system_one_alias_short_form(): # Coexistence of long-form and short-form parameters. with pytest.raises( - GMTInvalidInput, - match=r"Short-form parameter 'J' conflicts with long-form parameters and is not recommended. Use long-form parameter 'projection' instead.", + GMTConflictParameterError, + match=r"Conflicting parameters: 'J'. Short-form parameter 'J' conflicts with long-form parameters and is not recommended. Use long-form parameter 'projection' instead.", ): func(projection="X10c", J="H10c") @@ -95,11 +95,11 @@ def test_alias_system_multiple_aliases_short_form(): assert func(U="abcd+tefg") == ["-Uabcd+tefg"] # Coexistence of long-form and short-form parameters. - msg = rf"Short-form parameter 'U' conflicts with long-form parameters and is not recommended. {_msg_long}" - with pytest.raises(GMTInvalidInput, match=msg): + msg = rf"Conflicting parameters: 'U'. Short-form parameter 'U' conflicts with long-form parameters and is not recommended. {_msg_long}" + with pytest.raises(GMTConflictParameterError, match=msg): func(label="abcd", U="efg") - with pytest.raises(GMTInvalidInput, match=msg): + with pytest.raises(GMTConflictParameterError, match=msg): func(text="efg", U="efg") diff --git a/pygmt/tests/test_grdfill.py b/pygmt/tests/test_grdfill.py index 29a654e38f0..42c621575a1 100644 --- a/pygmt/tests/test_grdfill.py +++ b/pygmt/tests/test_grdfill.py @@ -10,7 +10,10 @@ import xarray as xr from pygmt import grdfill from pygmt.enums import GridRegistration, GridType -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import ( + GMTConflictParameterError, + GMTRequiredParameterError, +) from pygmt.helpers import GMTTempFile from pygmt.helpers.testing import load_static_earth_relief @@ -146,7 +149,7 @@ def test_grdfill_required_args(grid): """ Test that grdfill fails without filling parameters or 'inquire'. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTRequiredParameterError): grdfill(grid=grid) @@ -154,5 +157,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(GMTConflictParameterError): grdfill(grid=grid, inquire=True, constant_fill=20) diff --git a/pygmt/tests/test_inset.py b/pygmt/tests/test_inset.py index 63c99fe5d90..8e55c915024 100644 --- a/pygmt/tests/test_inset.py +++ b/pygmt/tests/test_inset.py @@ -4,7 +4,10 @@ import pytest from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import ( + GMTConflictParameterError, + GMTRequiredParameterError, +) from pygmt.params import Box, Position @@ -86,14 +89,14 @@ def test_inset_invalid_inputs(): fig = Figure() fig.basemap(region="MG+r2", frame="afg") # Width is not given - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTRequiredParameterError): with fig.inset(position=Position("TL")): pass # Height is given but width is not given - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTRequiredParameterError): with fig.inset(position=Position("TL"), height="5c"): pass # Old position syntax conflicts with width/height - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTConflictParameterError): with fig.inset(position="jTL+w3.5c", width="3.5c"): pass diff --git a/pygmt/tests/test_params_box.py b/pygmt/tests/test_params_box.py index 0bdd70adefe..bed63c9dfdd 100644 --- a/pygmt/tests/test_params_box.py +++ b/pygmt/tests/test_params_box.py @@ -3,7 +3,7 @@ """ import pytest -from pygmt.exceptions import GMTInvalidInput, GMTValueError +from pygmt.exceptions import GMTRequiredParameterError, GMTValueError from pygmt.params import Box @@ -55,5 +55,5 @@ def test_params_box_invalid_innerborder(): """ Test that inner_pen is required when inner_gap is set. """ - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTRequiredParameterError): _ = str(Box(inner_gap="2p"))