Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 5 additions & 4 deletions pygmt/alias.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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 = (
Expand Down
8 changes: 4 additions & 4 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 GMTRequiredParameterError, GMTValueError

with contextlib.suppress(ImportError):
# rioxarray is needed to register the rio accessor
Expand Down Expand Up @@ -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(
Expand Down
66 changes: 66 additions & 0 deletions pygmt/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
16 changes: 10 additions & 6 deletions pygmt/helpers/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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}."
Expand Down
43 changes: 26 additions & 17 deletions pygmt/helpers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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[
Expand Down Expand Up @@ -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:
Expand Down
6 changes: 3 additions & 3 deletions pygmt/helpers/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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' "
Expand Down
6 changes: 3 additions & 3 deletions pygmt/params/box.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 (
Expand Down
9 changes: 7 additions & 2 deletions pygmt/src/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down
9 changes: 5 additions & 4 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 GMTRequiredParameterError
from pygmt.helpers import args_in_kwargs, build_arg_list, fmt_docstring, use_alias
from pygmt.params import Box

Expand Down Expand Up @@ -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(
Expand Down
5 changes: 3 additions & 2 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 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"]
Expand Down Expand Up @@ -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),
Expand Down
6 changes: 3 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 GMTRequiredParameterError
from pygmt.helpers import (
build_arg_list,
fmt_docstring,
Expand Down Expand Up @@ -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)

Expand Down
6 changes: 3 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 GMTConflictParameterError
from pygmt.helpers import build_arg_list, fmt_docstring, kwargs_to_strings, use_alias

__doctest_skip__ = ["grd2cpt"]
Expand Down Expand Up @@ -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
Expand Down
Loading
Loading