Skip to content
Merged
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
1 change: 1 addition & 0 deletions doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ All custom exceptions are derived from :class:`pygmt.exceptions.GMTError`.
exceptions.GMTCLibError
exceptions.GMTCLibNoSessionError
exceptions.GMTCLibNotFoundError
exceptions.GMTValueError


.. currentmodule:: pygmt
Expand Down
44 changes: 23 additions & 21 deletions pygmt/clib/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@
)
from pygmt.clib.loading import get_gmt_version, load_libgmt
from pygmt.datatypes import _GMT_DATASET, _GMT_GRID, _GMT_IMAGE
from pygmt.exceptions import GMTCLibError, GMTCLibNoSessionError, GMTInvalidInput
from pygmt.exceptions import (
GMTCLibError,
GMTCLibNoSessionError,
GMTInvalidInput,
GMTValueError,
)
from pygmt.helpers import (
_validate_data_input,
data_kind,
Expand Down Expand Up @@ -560,11 +565,13 @@ def get_common(self, option: str) -> bool | int | float | np.ndarray:
... lib.get_common("A")
Traceback (most recent call last):
...
pygmt.exceptions.GMTInvalidInput: Unknown GMT common option flag 'A'.
pygmt.exceptions.GMTValueError: Invalid GMT common option: 'A'. Expected ...
"""
if option not in "BIJRUVXYabfghinoprst:":
msg = f"Unknown GMT common option flag '{option}'."
raise GMTInvalidInput(msg)
valid_options = "BIJRUVXYabfghinoprst:"
if option not in valid_options:
raise GMTValueError(
option, description="GMT common option", choices=valid_options
)

c_get_common = self.get_libgmt_func(
"GMT_Get_Common",
Expand Down Expand Up @@ -847,15 +854,15 @@ def _parse_constant(
their values are added.

If no valid modifiers are given, then will assume that modifiers are not
allowed. In this case, will raise a :class:`pygmt.exceptions.GMTInvalidInput`
allowed. In this case, will raise a :class:`pygmt.exceptions.GMTValueError`
exception if given a modifier.

Parameters
----------
constant
The name of a valid GMT API constant, with an optional modifier.
valid
A list of valid values for the constant. Will raise a GMTInvalidInput
A list of valid values for the constant. Will raise a GMTValueError
exception if the given value is not in the list.
valid_modifiers
A list of valid modifiers that can be added to the constant. If ``None``,
Expand All @@ -866,28 +873,23 @@ def _parse_constant(
nmodifiers = len(parts) - 1

if name not in valid:
msg = f"Invalid constant name '{name}'. Must be one of {valid}."
raise GMTInvalidInput(msg)
raise GMTValueError(name, description="constant name", choices=valid)

match nmodifiers:
case 1 if valid_modifiers is None:
msg = (
f"Constant modifiers are not allowed since valid values "
f"were not given: '{constant}'."
raise GMTValueError(
constant,
reason="Constant modifiers are not allowed since valid values were not given.",
)
raise GMTInvalidInput(msg)
case 1 if valid_modifiers is not None and parts[1] not in valid_modifiers:
msg = (
f"Invalid constant modifier '{parts[1]}'. "
f"Must be one of {valid_modifiers}."
raise GMTValueError(
parts[1], description="constant modifier", choices=valid_modifiers
)
raise GMTInvalidInput(msg)
case n if n > 1:
msg = (
f"Only one modifier is allowed in constants, "
f"{nmodifiers} given: '{constant}'."
raise GMTValueError(
constant,
reason=f"Only one modifier is allowed in constants but {nmodifiers} given.",
)
raise GMTInvalidInput(msg)

integer_value = sum(self[part] for part in parts)
return integer_value
Expand Down
10 changes: 5 additions & 5 deletions pygmt/datasets/earth_magnetic_anomaly.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import xarray as xr
from pygmt.datasets.load_remote_dataset import _load_remote_dataset
from pygmt.exceptions import GMTInvalidInput
from pygmt.exceptions import GMTValueError

__doctest_skip__ = ["load_earth_magnetic_anomaly"]

Expand Down Expand Up @@ -135,11 +135,11 @@ def load_earth_magnetic_anomaly(
"wdmam": "earth_wdmam",
}.get(data_source)
if prefix is None:
msg = (
f"Invalid earth magnetic anomaly data source '{data_source}'. "
"Valid values are 'emag2', 'emag2_4km', and 'wdmam'."
raise GMTValueError(
data_source,
description="earth magnetic anomaly data source",
choices=["emag2", "emag2_4km", "wdmam"],
)
raise GMTInvalidInput(msg)
grid = _load_remote_dataset(
name="earth_wdmam" if data_source == "wdmam" else "earth_mag",
prefix=prefix,
Expand Down
21 changes: 12 additions & 9 deletions pygmt/datasets/earth_relief.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import xarray as xr
from pygmt.datasets.load_remote_dataset import _load_remote_dataset
from pygmt.exceptions import GMTInvalidInput
from pygmt.exceptions import GMTValueError

__doctest_skip__ = ["load_earth_relief"]

Expand Down Expand Up @@ -154,19 +154,22 @@ def load_earth_relief(
"synbath": "earth_synbath",
}.get(data_source)
if prefix is None:
msg = (
f"Invalid earth relief data source '{data_source}'. "
"Valid values are 'igpp', 'gebco', 'gebcosi', and 'synbath'."
raise GMTValueError(
data_source,
description="earth relief data source",
choices=["igpp", "gebco", "gebcosi", "synbath"],
)
raise GMTInvalidInput(msg)
# Use SRTM or not.
if use_srtm and resolution in land_only_srtm_resolutions:
if data_source != "igpp":
msg = (
f"Option 'use_srtm=True' doesn't work with data source '{data_source}'. "
"Please set 'data_source' to 'igpp'."
raise GMTValueError(
data_source,
description="data source",
reason=(
"Option 'use_srtm=True' doesn't work with data source "
f"{data_source!r}. Please set 'data_source' to 'igpp'."
),
)
raise GMTInvalidInput(msg)
prefix = "srtm_relief"
# Choose earth relief dataset
match data_source:
Expand Down
24 changes: 13 additions & 11 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
from pygmt.exceptions import GMTInvalidInput, GMTValueError

with contextlib.suppress(ImportError):
# rioxarray is needed to register the rio accessor
Expand Down Expand Up @@ -546,11 +546,11 @@ def _load_remote_dataset(

# Check resolution
if resolution not in dataset.resolutions:
msg = (
f"Invalid resolution '{resolution}' for {dataset.description} dataset. "
f"Available resolutions are: {', '.join(dataset.resolutions)}."
raise GMTValueError(
resolution,
description=f"resolution for {dataset.description} dataset",
choices=dataset.resolutions.keys(),
)
raise GMTInvalidInput(msg)
resinfo = dataset.resolutions[resolution]

# Check registration
Expand All @@ -559,13 +559,15 @@ def _load_remote_dataset(
# Use gridline registration unless only pixel registration is available
reg = "g" if "gridline" in resinfo.registrations else "p"
case x if x not in resinfo.registrations:
msg = (
f"Invalid grid registration '{registration}' for the {resolution} "
f"{dataset.description} dataset. Should be either 'pixel', 'gridline' "
"or None. Default is None, where a gridline-registered grid is "
"returned unless only the pixel-registered grid is available."
raise GMTValueError(
registration,
description=f"grid registration for the {resolution} {dataset.description} dataset",
choices=[*resinfo.registrations, None],
reason=(
"Default is None, where a gridline-registered grid is returned "
"unless only the pixel-registered grid is available."
),
)
raise GMTInvalidInput(msg)
case _:
reg = registration[0]

Expand Down
5 changes: 2 additions & 3 deletions pygmt/datasets/samples.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import pandas as pd
import xarray as xr
from pygmt.exceptions import GMTInvalidInput
from pygmt.exceptions import GMTValueError
from pygmt.src import which


Expand Down Expand Up @@ -346,6 +346,5 @@ def load_sample_data(
>>> data = load_sample_data("bathymetry")
""" # noqa: W505
if name not in datasets:
msg = f"Invalid dataset name '{name}'."
raise GMTInvalidInput(msg)
raise GMTValueError(name, choices=datasets.keys(), description="dataset name")
return datasets[name].func()
64 changes: 64 additions & 0 deletions pygmt/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
All exceptions derive from GMTError.
"""

from collections.abc import Iterable
from typing import Any


class GMTError(Exception):
"""
Expand Down Expand Up @@ -51,3 +54,64 @@ class GMTImageComparisonFailure(AssertionError): # noqa: N818
"""
Raised when a comparison between two images fails.
"""


class GMTValueError(GMTError, ValueError):
"""
Raised when an invalid value is passed to a function/method.

Parameters
----------
value
The invalid value.
description
The description of the value.
choices
The valid choices for the value.
reason
The detailed reason why the value is invalid.

Examples
--------
>>> raise GMTValueError("invalid")
Traceback (most recent call last):
...
pygmt.exceptions.GMTValueError: Invalid value: 'invalid'.
>>> raise GMTValueError("invalid", description="constant name")
Traceback (most recent call last):
...
pygmt.exceptions.GMTValueError: Invalid constant name: 'invalid'.
>>> raise GMTValueError("invalid", choices=["a", "b", 1, 2])
Traceback (most recent call last):
...
pygmt.exceptions.GMTValueError: Invalid value: 'invalid'. Expected one of: 'a', 'b', 1, 2.
>>> raise GMTValueError("invalid", choices=["a", 0, True, False, None])
Traceback (most recent call last):
...
pygmt.exceptions.GMTValueError: Invalid value: 'invalid'. Expected one of: 'a', 0, True, False, None.

>>> from pygmt.enums import GridType
>>> raise GMTValueError("invalid", choices=GridType)
Traceback (most recent call last):
...
pygmt.exceptions.GMTValueError: Invalid value: 'invalid'. Expected one of: <GridType.CARTESIAN: 0>, <GridType.GEOGRAPHIC: 1>.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error message is slightly different from the previous formatting GridRegistration.GRIDLINE (0) or GridRegistration.PIXEL (1), but hopefully ok.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<GridType.CARTESIAN: 0> is from the repr() output:

>>> from pygmt.enums import GridType

>>> repr(GridType.CARTESIAN)
<GridType.CARTESIAN: 0>

So I think it's better than previously custom string format.

>>> raise GMTValueError("invalid", reason="Explain why it's invalid.")
Traceback (most recent call last):
...
pygmt.exceptions.GMTValueError: Invalid value: 'invalid'. Explain why it's invalid.
""" # noqa: W505

def __init__(
self,
value: Any,
/,
description: str = "value",
choices: Iterable[Any] | None = None,
reason: str | None = None,
):
msg = f"Invalid {description}: {value!r}."
if choices:
msg += f" Expected one of: {', '.join(repr(c) for c in choices)}."
if reason:
msg += f" {reason}"
Comment on lines +112 to +116
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lines would probably be a good use of t-strings (Template strings, PEP 750), but it is for Python 3.14+

super().__init__(msg)
45 changes: 28 additions & 17 deletions pygmt/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

import numpy as np
from pygmt.clib import Session
from pygmt.exceptions import GMTInvalidInput
from pygmt.exceptions import GMTValueError
from pygmt.helpers import launch_external_viewer, unique_name


Expand Down Expand Up @@ -234,23 +234,34 @@ def savefig(
case "kml": # KML
kwargs["W"] = "+k"
case "ps":
msg = "Extension '.ps' is not supported. Use '.eps' or '.pdf' instead."
raise GMTInvalidInput(msg)
raise GMTValueError(
ext,
description="file extension",
reason="Extension '.ps' is not supported. Use '.eps' or '.pdf' instead.",
)
case ext if ext not in fmts:
msg = f"Unknown extension '.{ext}'."
raise GMTInvalidInput(msg)
raise GMTValueError(
ext, description="file extension", choices=fmts.keys()
)

if transparent and ext not in {"kml", "png"}:
msg = f"Transparency unavailable for '{ext}', only for png and kml."
raise GMTInvalidInput(msg)
raise GMTValueError(
transparent,
description="value for parameter 'transparent'",
reason=f"Transparency unavailable for '{ext}', only for png and kml.",
)
if anti_alias:
kwargs["Qt"] = 2
kwargs["Qg"] = 2

if worldfile:
if ext in {"eps", "kml", "pdf", "tiff"}:
msg = f"Saving a world file is not supported for '{ext}' format."
raise GMTInvalidInput(msg)
raise GMTValueError(
ext,
description="file extension",
choices=["eps", "kml", "pdf", "tiff"],
reason="Saving a world file is not supported for this format.",
)
kwargs["W"] = True

# pytest-mpl v0.17.0 added the "metadata" parameter to Figure.savefig, which is
Expand Down Expand Up @@ -358,11 +369,11 @@ def show(
case "none":
pass # Do nothing
case _:
msg = (
f"Invalid display method '{method}'. "
"Valid values are 'external', 'notebook', 'none' or None."
raise GMTValueError(
method,
description="display method",
choices=["external", "notebook", "none", None],
)
raise GMTInvalidInput(msg)

@overload
def _preview(
Expand Down Expand Up @@ -494,8 +505,8 @@ def set_display(method: Literal["external", "notebook", "none", None] = None) ->
case None:
SHOW_CONFIG["method"] = _get_default_display_method()
case _:
msg = (
f"Invalid display method '{method}'. "
"Valid values are 'external', 'notebook', 'none' or None."
raise GMTValueError(
method,
description="display method",
choices=["external", "notebook", "none", None],
)
raise GMTInvalidInput(msg)
Loading
Loading