Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d023f6e
Add to API doc
seisman Jul 15, 2025
19d3df2
Add new exception GMTParameterError for missing required parameters
seisman Jul 21, 2025
d553a25
Use GMTParameterError for missing required parameters
seisman Jul 21, 2025
11fd4a0
Use GMTParameteError for mutally exclusive parameters
seisman Jul 21, 2025
e51bb31
Use GMTParameterError for parameters where at least one is required
seisman Jul 21, 2025
b14dccf
Merge branch 'main' into exception/parametererror
seisman Jul 26, 2025
170d11b
More fixes
seisman Jul 26, 2025
c2c81d7
Improve exception
seisman Jul 26, 2025
c42c587
Fix typing issue
seisman Jul 26, 2025
eb93ba4
Merge branch 'main' into exception/parametererror
seisman Jul 29, 2025
f558ed5
Merge branch 'main' into exception/parametererror
seisman Aug 7, 2025
c6b6652
Merge branch 'main' into exception/parametererror
seisman Aug 8, 2025
a02a089
Merge branch 'main' into exception/parametererror
seisman Aug 10, 2025
b4829b6
Migrate more exceptions to GMTParameterError
seisman Aug 10, 2025
5a18415
Merge branch 'main' into exception/parametererror
seisman Aug 21, 2025
8d9a122
Fix the order in doc/api
seisman Aug 21, 2025
2d6fc06
Merge branch 'main' into exception/parametererror
seisman Aug 23, 2025
c8b7a7d
Merge branch 'main' into exception/parametererror
seisman Sep 6, 2025
7924399
Merge branch 'main' into exception/parametererror
seisman Sep 16, 2025
9bf8c07
Merge branch 'main' into exception/parametererror
seisman Sep 24, 2025
e1580cc
Merge branch 'main' into exception/parametererror
seisman Oct 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 4 additions & 5 deletions pygmt/datasets/load_remote_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
50 changes: 49 additions & 1 deletion pygmt/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
All exceptions derive from GMTError.
"""

from collections.abc import Iterable
from collections.abc import Iterable, Set
from typing import Any


Expand Down Expand Up @@ -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))
17 changes: 12 additions & 5 deletions pygmt/src/coast.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down
8 changes: 2 additions & 6 deletions pygmt/src/dimfilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 2 additions & 3 deletions pygmt/src/filter1d.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)

Expand Down
5 changes: 2 additions & 3 deletions pygmt/src/grd2cpt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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
Expand Down
14 changes: 7 additions & 7 deletions pygmt/src/grdclip.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down Expand Up @@ -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),
Expand Down
32 changes: 19 additions & 13 deletions pygmt/src/grdfill.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand All @@ -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)

Expand All @@ -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
Expand Down
14 changes: 6 additions & 8 deletions pygmt/src/grdgradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 2 additions & 3 deletions pygmt/src/grdlandmask.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -109,8 +109,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(
Expand Down
5 changes: 2 additions & 3 deletions pygmt/src/grdproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__ = ["grdproject"]
Expand Down Expand Up @@ -116,8 +116,7 @@ def grdproject(
>>> new_grid = pygmt.grdproject(grid=grid, projection="M10c", region=region)
"""
if projection is None:
msg = "The projection must be specified."
raise GMTInvalidInput(msg)
raise GMTParameterError(required={"projection"})

aliasdict = AliasSystem().add_common(
J=projection,
Expand Down
14 changes: 7 additions & 7 deletions pygmt/src/grdtrack.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)

Expand Down
Loading
Loading