Skip to content

Commit 61410c8

Browse files
seismanweiji14
andcommitted
BREAKING: Raise GMTValueError exception for invalid values. Previously raise GMTInvalidInput (#3985)
Co-authored-by: Wei Ji <[email protected]>
1 parent 020032b commit 61410c8

31 files changed

+235
-152
lines changed

doc/api/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ All custom exceptions are derived from :class:`pygmt.exceptions.GMTError`.
283283
exceptions.GMTCLibError
284284
exceptions.GMTCLibNoSessionError
285285
exceptions.GMTCLibNotFoundError
286+
exceptions.GMTValueError
286287

287288

288289
.. currentmodule:: pygmt

pygmt/clib/session.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@
2424
)
2525
from pygmt.clib.loading import get_gmt_version, load_libgmt
2626
from pygmt.datatypes import _GMT_DATASET, _GMT_GRID, _GMT_IMAGE
27-
from pygmt.exceptions import GMTCLibError, GMTCLibNoSessionError, GMTInvalidInput
27+
from pygmt.exceptions import (
28+
GMTCLibError,
29+
GMTCLibNoSessionError,
30+
GMTInvalidInput,
31+
GMTValueError,
32+
)
2833
from pygmt.helpers import (
2934
_validate_data_input,
3035
data_kind,
@@ -560,11 +565,13 @@ def get_common(self, option: str) -> bool | int | float | np.ndarray:
560565
... lib.get_common("A")
561566
Traceback (most recent call last):
562567
...
563-
pygmt.exceptions.GMTInvalidInput: Unknown GMT common option flag 'A'.
568+
pygmt.exceptions.GMTValueError: Invalid GMT common option: 'A'. Expected ...
564569
"""
565-
if option not in "BIJRUVXYabfghinoprst:":
566-
msg = f"Unknown GMT common option flag '{option}'."
567-
raise GMTInvalidInput(msg)
570+
valid_options = "BIJRUVXYabfghinoprst:"
571+
if option not in valid_options:
572+
raise GMTValueError(
573+
option, description="GMT common option", choices=valid_options
574+
)
568575

569576
c_get_common = self.get_libgmt_func(
570577
"GMT_Get_Common",
@@ -847,15 +854,15 @@ def _parse_constant(
847854
their values are added.
848855
849856
If no valid modifiers are given, then will assume that modifiers are not
850-
allowed. In this case, will raise a :class:`pygmt.exceptions.GMTInvalidInput`
857+
allowed. In this case, will raise a :class:`pygmt.exceptions.GMTValueError`
851858
exception if given a modifier.
852859
853860
Parameters
854861
----------
855862
constant
856863
The name of a valid GMT API constant, with an optional modifier.
857864
valid
858-
A list of valid values for the constant. Will raise a GMTInvalidInput
865+
A list of valid values for the constant. Will raise a GMTValueError
859866
exception if the given value is not in the list.
860867
valid_modifiers
861868
A list of valid modifiers that can be added to the constant. If ``None``,
@@ -866,28 +873,23 @@ def _parse_constant(
866873
nmodifiers = len(parts) - 1
867874

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

872878
match nmodifiers:
873879
case 1 if valid_modifiers is None:
874-
msg = (
875-
f"Constant modifiers are not allowed since valid values "
876-
f"were not given: '{constant}'."
880+
raise GMTValueError(
881+
constant,
882+
reason="Constant modifiers are not allowed since valid values were not given.",
877883
)
878-
raise GMTInvalidInput(msg)
879884
case 1 if valid_modifiers is not None and parts[1] not in valid_modifiers:
880-
msg = (
881-
f"Invalid constant modifier '{parts[1]}'. "
882-
f"Must be one of {valid_modifiers}."
885+
raise GMTValueError(
886+
parts[1], description="constant modifier", choices=valid_modifiers
883887
)
884-
raise GMTInvalidInput(msg)
885888
case n if n > 1:
886-
msg = (
887-
f"Only one modifier is allowed in constants, "
888-
f"{nmodifiers} given: '{constant}'."
889+
raise GMTValueError(
890+
constant,
891+
reason=f"Only one modifier is allowed in constants but {nmodifiers} given.",
889892
)
890-
raise GMTInvalidInput(msg)
891893

892894
integer_value = sum(self[part] for part in parts)
893895
return integer_value

pygmt/datasets/earth_magnetic_anomaly.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import xarray as xr
1212
from pygmt.datasets.load_remote_dataset import _load_remote_dataset
13-
from pygmt.exceptions import GMTInvalidInput
13+
from pygmt.exceptions import GMTValueError
1414

1515
__doctest_skip__ = ["load_earth_magnetic_anomaly"]
1616

@@ -135,11 +135,11 @@ def load_earth_magnetic_anomaly(
135135
"wdmam": "earth_wdmam",
136136
}.get(data_source)
137137
if prefix is None:
138-
msg = (
139-
f"Invalid earth magnetic anomaly data source '{data_source}'. "
140-
"Valid values are 'emag2', 'emag2_4km', and 'wdmam'."
138+
raise GMTValueError(
139+
data_source,
140+
description="earth magnetic anomaly data source",
141+
choices=["emag2", "emag2_4km", "wdmam"],
141142
)
142-
raise GMTInvalidInput(msg)
143143
grid = _load_remote_dataset(
144144
name="earth_wdmam" if data_source == "wdmam" else "earth_mag",
145145
prefix=prefix,

pygmt/datasets/earth_relief.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import xarray as xr
1212
from pygmt.datasets.load_remote_dataset import _load_remote_dataset
13-
from pygmt.exceptions import GMTInvalidInput
13+
from pygmt.exceptions import GMTValueError
1414

1515
__doctest_skip__ = ["load_earth_relief"]
1616

@@ -154,19 +154,22 @@ def load_earth_relief(
154154
"synbath": "earth_synbath",
155155
}.get(data_source)
156156
if prefix is None:
157-
msg = (
158-
f"Invalid earth relief data source '{data_source}'. "
159-
"Valid values are 'igpp', 'gebco', 'gebcosi', and 'synbath'."
157+
raise GMTValueError(
158+
data_source,
159+
description="earth relief data source",
160+
choices=["igpp", "gebco", "gebcosi", "synbath"],
160161
)
161-
raise GMTInvalidInput(msg)
162162
# Use SRTM or not.
163163
if use_srtm and resolution in land_only_srtm_resolutions:
164164
if data_source != "igpp":
165-
msg = (
166-
f"Option 'use_srtm=True' doesn't work with data source '{data_source}'. "
167-
"Please set 'data_source' to 'igpp'."
165+
raise GMTValueError(
166+
data_source,
167+
description="data source",
168+
reason=(
169+
"Option 'use_srtm=True' doesn't work with data source "
170+
f"{data_source!r}. Please set 'data_source' to 'igpp'."
171+
),
168172
)
169-
raise GMTInvalidInput(msg)
170173
prefix = "srtm_relief"
171174
# Choose earth relief dataset
172175
match data_source:

pygmt/datasets/load_remote_dataset.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from typing import Any, Literal, NamedTuple
88

99
import xarray as xr
10-
from pygmt.exceptions import GMTInvalidInput
10+
from pygmt.exceptions import GMTInvalidInput, GMTValueError
1111

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

547547
# Check resolution
548548
if resolution not in dataset.resolutions:
549-
msg = (
550-
f"Invalid resolution '{resolution}' for {dataset.description} dataset. "
551-
f"Available resolutions are: {', '.join(dataset.resolutions)}."
549+
raise GMTValueError(
550+
resolution,
551+
description=f"resolution for {dataset.description} dataset",
552+
choices=dataset.resolutions.keys(),
552553
)
553-
raise GMTInvalidInput(msg)
554554
resinfo = dataset.resolutions[resolution]
555555

556556
# Check registration
@@ -559,13 +559,15 @@ def _load_remote_dataset(
559559
# Use gridline registration unless only pixel registration is available
560560
reg = "g" if "gridline" in resinfo.registrations else "p"
561561
case x if x not in resinfo.registrations:
562-
msg = (
563-
f"Invalid grid registration '{registration}' for the {resolution} "
564-
f"{dataset.description} dataset. Should be either 'pixel', 'gridline' "
565-
"or None. Default is None, where a gridline-registered grid is "
566-
"returned unless only the pixel-registered grid is available."
562+
raise GMTValueError(
563+
registration,
564+
description=f"grid registration for the {resolution} {dataset.description} dataset",
565+
choices=[*resinfo.registrations, None],
566+
reason=(
567+
"Default is None, where a gridline-registered grid is returned "
568+
"unless only the pixel-registered grid is available."
569+
),
567570
)
568-
raise GMTInvalidInput(msg)
569571
case _:
570572
reg = registration[0]
571573

pygmt/datasets/samples.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import pandas as pd
99
import xarray as xr
10-
from pygmt.exceptions import GMTInvalidInput
10+
from pygmt.exceptions import GMTValueError
1111
from pygmt.src import which
1212

1313

@@ -346,6 +346,5 @@ def load_sample_data(
346346
>>> data = load_sample_data("bathymetry")
347347
""" # noqa: W505
348348
if name not in datasets:
349-
msg = f"Invalid dataset name '{name}'."
350-
raise GMTInvalidInput(msg)
349+
raise GMTValueError(name, choices=datasets.keys(), description="dataset name")
351350
return datasets[name].func()

pygmt/exceptions.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
All exceptions derive from GMTError.
55
"""
66

7+
from collections.abc import Iterable
8+
from typing import Any
9+
710

811
class GMTError(Exception):
912
"""
@@ -51,3 +54,64 @@ class GMTImageComparisonFailure(AssertionError): # noqa: N818
5154
"""
5255
Raised when a comparison between two images fails.
5356
"""
57+
58+
59+
class GMTValueError(GMTError, ValueError):
60+
"""
61+
Raised when an invalid value is passed to a function/method.
62+
63+
Parameters
64+
----------
65+
value
66+
The invalid value.
67+
description
68+
The description of the value.
69+
choices
70+
The valid choices for the value.
71+
reason
72+
The detailed reason why the value is invalid.
73+
74+
Examples
75+
--------
76+
>>> raise GMTValueError("invalid")
77+
Traceback (most recent call last):
78+
...
79+
pygmt.exceptions.GMTValueError: Invalid value: 'invalid'.
80+
>>> raise GMTValueError("invalid", description="constant name")
81+
Traceback (most recent call last):
82+
...
83+
pygmt.exceptions.GMTValueError: Invalid constant name: 'invalid'.
84+
>>> raise GMTValueError("invalid", choices=["a", "b", 1, 2])
85+
Traceback (most recent call last):
86+
...
87+
pygmt.exceptions.GMTValueError: Invalid value: 'invalid'. Expected one of: 'a', 'b', 1, 2.
88+
>>> raise GMTValueError("invalid", choices=["a", 0, True, False, None])
89+
Traceback (most recent call last):
90+
...
91+
pygmt.exceptions.GMTValueError: Invalid value: 'invalid'. Expected one of: 'a', 0, True, False, None.
92+
93+
>>> from pygmt.enums import GridType
94+
>>> raise GMTValueError("invalid", choices=GridType)
95+
Traceback (most recent call last):
96+
...
97+
pygmt.exceptions.GMTValueError: Invalid value: 'invalid'. Expected one of: <GridType.CARTESIAN: 0>, <GridType.GEOGRAPHIC: 1>.
98+
>>> raise GMTValueError("invalid", reason="Explain why it's invalid.")
99+
Traceback (most recent call last):
100+
...
101+
pygmt.exceptions.GMTValueError: Invalid value: 'invalid'. Explain why it's invalid.
102+
""" # noqa: W505
103+
104+
def __init__(
105+
self,
106+
value: Any,
107+
/,
108+
description: str = "value",
109+
choices: Iterable[Any] | None = None,
110+
reason: str | None = None,
111+
):
112+
msg = f"Invalid {description}: {value!r}."
113+
if choices:
114+
msg += f" Expected one of: {', '.join(repr(c) for c in choices)}."
115+
if reason:
116+
msg += f" {reason}"
117+
super().__init__(msg)

pygmt/figure.py

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
import numpy as np
2222
from pygmt.clib import Session
23-
from pygmt.exceptions import GMTInvalidInput
23+
from pygmt.exceptions import GMTValueError
2424
from pygmt.helpers import launch_external_viewer, unique_name
2525

2626

@@ -234,23 +234,34 @@ def savefig(
234234
case "kml": # KML
235235
kwargs["W"] = "+k"
236236
case "ps":
237-
msg = "Extension '.ps' is not supported. Use '.eps' or '.pdf' instead."
238-
raise GMTInvalidInput(msg)
237+
raise GMTValueError(
238+
ext,
239+
description="file extension",
240+
reason="Extension '.ps' is not supported. Use '.eps' or '.pdf' instead.",
241+
)
239242
case ext if ext not in fmts:
240-
msg = f"Unknown extension '.{ext}'."
241-
raise GMTInvalidInput(msg)
243+
raise GMTValueError(
244+
ext, description="file extension", choices=fmts.keys()
245+
)
242246

243247
if transparent and ext not in {"kml", "png"}:
244-
msg = f"Transparency unavailable for '{ext}', only for png and kml."
245-
raise GMTInvalidInput(msg)
248+
raise GMTValueError(
249+
transparent,
250+
description="value for parameter 'transparent'",
251+
reason=f"Transparency unavailable for '{ext}', only for png and kml.",
252+
)
246253
if anti_alias:
247254
kwargs["Qt"] = 2
248255
kwargs["Qg"] = 2
249256

250257
if worldfile:
251258
if ext in {"eps", "kml", "pdf", "tiff"}:
252-
msg = f"Saving a world file is not supported for '{ext}' format."
253-
raise GMTInvalidInput(msg)
259+
raise GMTValueError(
260+
ext,
261+
description="file extension",
262+
choices=["eps", "kml", "pdf", "tiff"],
263+
reason="Saving a world file is not supported for this format.",
264+
)
254265
kwargs["W"] = True
255266

256267
# pytest-mpl v0.17.0 added the "metadata" parameter to Figure.savefig, which is
@@ -358,11 +369,11 @@ def show(
358369
case "none":
359370
pass # Do nothing
360371
case _:
361-
msg = (
362-
f"Invalid display method '{method}'. "
363-
"Valid values are 'external', 'notebook', 'none' or None."
372+
raise GMTValueError(
373+
method,
374+
description="display method",
375+
choices=["external", "notebook", "none", None],
364376
)
365-
raise GMTInvalidInput(msg)
366377

367378
@overload
368379
def _preview(
@@ -494,8 +505,8 @@ def set_display(method: Literal["external", "notebook", "none", None] = None) ->
494505
case None:
495506
SHOW_CONFIG["method"] = _get_default_display_method()
496507
case _:
497-
msg = (
498-
f"Invalid display method '{method}'. "
499-
"Valid values are 'external', 'notebook', 'none' or None."
508+
raise GMTValueError(
509+
method,
510+
description="display method",
511+
choices=["external", "notebook", "none", None],
500512
)
501-
raise GMTInvalidInput(msg)

0 commit comments

Comments
 (0)