From 0105bb66620489f1035dfccbf9ef96ead6cdcbb5 Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 4 Feb 2026 12:14:33 +0800 Subject: [PATCH 01/41] add conflict parameter for GMTParameterError --- pygmt/exceptions.py | 10 ++++++++++ pygmt/src/_common.py | 15 ++++++++------- pygmt/src/grdview.py | 10 +++++----- pygmt/tests/test_grdview.py | 10 +++++----- pygmt/tests/test_inset.py | 4 ++-- pygmt/tests/test_logo.py | 6 +++--- 6 files changed, 33 insertions(+), 22 deletions(-) diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index ae76cc12ff5..e36f521fc76 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -145,6 +145,9 @@ class GMTParameterError(GMTError): at_most_one A set of mutually exclusive parameter names, of which at most one can be specified. + conflicts_with + A dictionary mapping parameter names to sets of conflicting parameter + names. Used to indicate which parameters cannot be used together. reason Detailed reason why the parameters are invalid. """ @@ -155,6 +158,7 @@ def __init__( required: str | set[str] | None = None, at_least_one: set[str] | None = None, at_most_one: set[str] | None = None, + conflicts_with: dict[str, set[str]] | None = None, reason: str | None = None, ): msg = [] @@ -177,6 +181,12 @@ def __init__( f"{', '.join(repr(par) for par in at_most_one)}. " "Specify at most one of them." ) + if conflicts_with: + for param, conflicts in conflicts_with.items(): + msg.append( + f"Conflicting parameters: {param!r} cannot be used with " + f"{', '.join(repr(c) for c in sorted(conflicts))}." + ) if reason: msg.append(reason) super().__init__(" ".join(msg)) diff --git a/pygmt/src/_common.py b/pygmt/src/_common.py index e6bb2ebf275..48d65156917 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, GMTTypeError, GMTValueError +from pygmt.exceptions import ( + GMTParameterError, + GMTTypeError, + GMTValueError, +) from pygmt.params.position import Position from pygmt.src.which import which @@ -328,7 +332,7 @@ def _parse_position( ... ) Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Parameter 'position' is given with a raw GMT... + pygmt.exceptions.GMTParameterError: ... >>> _parse_position( ... 123, @@ -364,12 +368,9 @@ def _parse_position( position = Position(position, cstype="inside") 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 = ( - "Parameter 'position' is given with a raw GMT command string, " - "and conflicts with parameters " - f"{', '.join(repr(c) for c in kwdict)}." + raise GMTParameterError( + conflicts_with={"position": set(kwdict.keys())} ) - raise GMTInvalidInput(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/grdview.py b/pygmt/src/grdview.py index f628378f9e4..eddfaed28fe 100644 --- a/pygmt/src/grdview.py +++ b/pygmt/src/grdview.py @@ -10,7 +10,7 @@ from pygmt._typing import PathLike from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session, __gmt_version__ -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTInvalidInput, GMTParameterError from pygmt.helpers import build_arg_list, deprecate_parameter, fmt_docstring, use_alias from pygmt.src.grdinfo import grdinfo @@ -71,11 +71,11 @@ def _alias_option_Q( # noqa: N802 v is not None and v is not False for v in (dpi, mesh_fill, monochrome, nan_transparent) ): - msg = ( - "Parameter 'surftype' is given with a raw GMT command string, and conflicts " - "with parameters 'dpi', 'mesh_fill', 'monochrome', or 'nan_transparent'." + raise GMTParameterError( + conflicts_with={ + "surftype": {"dpi", "mesh_fill", "monochrome", "nan_transparent"} + } ) - raise GMTInvalidInput(msg) if dpi is not None and surftype != "image": msg = "Parameter 'dpi' can only be used when 'surftype' is 'image'." diff --git a/pygmt/tests/test_grdview.py b/pygmt/tests/test_grdview.py index 4e84fdcfebb..6bb5e5b6594 100644 --- a/pygmt/tests/test_grdview.py +++ b/pygmt/tests/test_grdview.py @@ -4,7 +4,7 @@ import pytest from pygmt import Figure, grdcut -from pygmt.exceptions import GMTInvalidInput, GMTTypeError +from pygmt.exceptions import GMTInvalidInput, GMTParameterError, GMTTypeError from pygmt.helpers import GMTTempFile from pygmt.helpers.testing import load_static_earth_relief @@ -343,13 +343,13 @@ def test_grdview_mixed_syntax(gridfile): Run grdview using grid as a file and drapegrid as an xarray.DataArray. """ fig = Figure() - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.grdview(grid=gridfile, cmap="SCM/oleron", surftype="i", dpi=300) - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.grdview(grid=gridfile, cmap="SCM/oleron", surftype="m", mesh_fill="red") - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.grdview(grid=gridfile, cmap="SCM/oleron", surftype="s", monochrome=True) - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.grdview( grid=gridfile, cmap="SCM/oleron", surftype="i", nan_transparent=True ) diff --git a/pygmt/tests/test_inset.py b/pygmt/tests/test_inset.py index a5c219bf02a..8b4aceaad59 100644 --- a/pygmt/tests/test_inset.py +++ b/pygmt/tests/test_inset.py @@ -4,7 +4,7 @@ import pytest from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput, GMTParameterError +from pygmt.exceptions import GMTParameterError from pygmt.params import Box, Position @@ -94,6 +94,6 @@ def test_inset_invalid_inputs(): with fig.inset(position=Position("TL"), height="5c"): pass # Old position syntax conflicts with width/height - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): with fig.inset(position="jTL+w3.5c", width="3.5c"): pass diff --git a/pygmt/tests/test_logo.py b/pygmt/tests/test_logo.py index 95fc5fa8534..8772c9803ac 100644 --- a/pygmt/tests/test_logo.py +++ b/pygmt/tests/test_logo.py @@ -4,7 +4,7 @@ import pytest from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput, GMTParameterError +from pygmt.exceptions import GMTParameterError from pygmt.params import Position @@ -66,7 +66,7 @@ def test_logo_position_mixed_syntax(): Test that an error is raised when mixing new and deprecated syntax in 'position'. """ fig = Figure() - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.logo(position="jTL", width="5c") - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.logo(position="jTL", height="6c") From d8c15e7942ac3d3d212a48fdff81270c4360c222 Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 4 Feb 2026 12:41:41 +0800 Subject: [PATCH 02/41] update test file --- pygmt/tests/test_colorbar.py | 18 +++++++++--------- pygmt/tests/test_image.py | 10 +++++----- pygmt/tests/test_legend.py | 8 ++++---- pygmt/tests/test_wiggle.py | 10 +++++----- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/pygmt/tests/test_colorbar.py b/pygmt/tests/test_colorbar.py index 3024ba38341..5cd121eada5 100644 --- a/pygmt/tests/test_colorbar.py +++ b/pygmt/tests/test_colorbar.py @@ -4,7 +4,7 @@ import pytest from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTParameterError from pygmt.params.position import Position @@ -50,21 +50,21 @@ def test_image_position_mixed_syntax(): Test that mixing deprecated GMT CLI syntax string with new parameters. """ fig = Figure() - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.colorbar(cmap="gmt/rainbow", position="x0/0", length="4c") - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.colorbar(cmap="gmt/rainbow", position="x0/0", width="0.5c") - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.colorbar(cmap="gmt/rainbow", position="x0/0", orientation="horizontal") - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.colorbar(cmap="gmt/rainbow", position="x0/0", reverse=True) - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.colorbar(cmap="gmt/rainbow", position="x0/0", nan=True) - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.colorbar( cmap="gmt/rainbow", position="x0/0", fg_triangle=True, bg_triangle=True ) - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.colorbar(cmap="gmt/rainbow", position="x0/0", move_text="label") - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.colorbar(cmap="gmt/rainbow", position="x0/0", label_as_column=True) diff --git a/pygmt/tests/test_image.py b/pygmt/tests/test_image.py index d48836a0539..b9370a3560b 100644 --- a/pygmt/tests/test_image.py +++ b/pygmt/tests/test_image.py @@ -4,7 +4,7 @@ import pytest from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTParameterError from pygmt.params import Box, Position @@ -66,11 +66,11 @@ def test_image_position_mixed_syntax(): and conflicts with other parameters. """ fig = Figure() - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.image(imagefile="@circuit.png", position="x0/0", width="4c") - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.image(imagefile="@circuit.png", position="x0/0", height="3c") - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.image(imagefile="@circuit.png", position="x0/0", dpi="300") - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.image(imagefile="@circuit.png", position="x0/0", replicate=(2, 1)) diff --git a/pygmt/tests/test_legend.py b/pygmt/tests/test_legend.py index e43b1b39cad..c5af43fa994 100644 --- a/pygmt/tests/test_legend.py +++ b/pygmt/tests/test_legend.py @@ -7,7 +7,7 @@ import pytest from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput, GMTTypeError +from pygmt.exceptions import GMTParameterError, GMTTypeError from pygmt.helpers import GMTTempFile @@ -174,9 +174,9 @@ def test_legend_position_mixed_syntax(legend_spec): spec = io.StringIO(legend_spec) fig = Figure() fig.basemap(projection="x6i", region=[0, 1, 0, 1], frame=True) - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.legend(spec, position="jTL", width="5i") - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.legend(spec, position="jTL", height="5i") - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.legend(spec, position="jTL", line_spacing=2.0) diff --git a/pygmt/tests/test_wiggle.py b/pygmt/tests/test_wiggle.py index 8e4ba6718d4..3e76d128d60 100644 --- a/pygmt/tests/test_wiggle.py +++ b/pygmt/tests/test_wiggle.py @@ -5,7 +5,7 @@ import numpy as np import pytest from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTParameterError from pygmt.params import Position @@ -136,11 +136,11 @@ def test_wiggle_mixed_syntax(data): "pen": "1.0p", "track": "0.5p", } - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.wiggle(position="jMR+w2+lnT", length=2, **kwargs) - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.wiggle(position="jMR+w2+lnT", label="nT", **kwargs) - with pytest.raises(GMTInvalidInput): - fig.wiggle(position="jMR+w2+lnT", length_alignment="left", **kwargs) + with pytest.raises(GMTParameterError): + fig.wiggle(position="jMR+w2+lnT", label_alignment="left", **kwargs) From 406eb7b9598196df15290fbe04e4ef8848d9dcda Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Wed, 4 Feb 2026 17:21:28 +0800 Subject: [PATCH 03/41] Update pygmt/src/_common.py Co-authored-by: Dongdong Tian --- pygmt/src/_common.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pygmt/src/_common.py b/pygmt/src/_common.py index 48d65156917..ec2dee73e5c 100644 --- a/pygmt/src/_common.py +++ b/pygmt/src/_common.py @@ -7,11 +7,7 @@ from pathlib import Path from typing import Any, ClassVar, Literal -from pygmt.exceptions import ( - GMTParameterError, - GMTTypeError, - GMTValueError, -) +from pygmt.exceptions import GMTParameterError, GMTTypeError, GMTValueError from pygmt.params.position import Position from pygmt.src.which import which From b28747dbd86fd58a1c288ba43e4a21372be109f3 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Wed, 4 Feb 2026 17:22:55 +0800 Subject: [PATCH 04/41] Update pygmt/src/_common.py Co-authored-by: Dongdong Tian --- pygmt/src/_common.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pygmt/src/_common.py b/pygmt/src/_common.py index ec2dee73e5c..4ad2f0f9949 100644 --- a/pygmt/src/_common.py +++ b/pygmt/src/_common.py @@ -365,7 +365,8 @@ 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()): raise GMTParameterError( - conflicts_with={"position": set(kwdict.keys())} + conflicts_with={"position": set(kwdict.keys())}, + reason="Parameter 'position' is given with a raw GMT command string", ) else: # No conflicting parameters to check, indicating it's a new function. From c15f3f3a96261d9536981907176d9fe3c1ff87d4 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Wed, 4 Feb 2026 17:23:10 +0800 Subject: [PATCH 05/41] Update pygmt/src/grdview.py Co-authored-by: Dongdong Tian --- pygmt/src/grdview.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pygmt/src/grdview.py b/pygmt/src/grdview.py index eddfaed28fe..c1e884c0a2c 100644 --- a/pygmt/src/grdview.py +++ b/pygmt/src/grdview.py @@ -73,7 +73,8 @@ def _alias_option_Q( # noqa: N802 ): raise GMTParameterError( conflicts_with={ - "surftype": {"dpi", "mesh_fill", "monochrome", "nan_transparent"} + "surftype": {"dpi", "mesh_fill", "monochrome", "nan_transparent"}, + reason="Parameter 'surftype' is given with a raw GMT command string", } ) From 25bad04d8131b0d6866970f730f8fbfcfae17958 Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 4 Feb 2026 17:25:28 +0800 Subject: [PATCH 06/41] fix check --- pygmt/src/grdview.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/src/grdview.py b/pygmt/src/grdview.py index c1e884c0a2c..666a0952f39 100644 --- a/pygmt/src/grdview.py +++ b/pygmt/src/grdview.py @@ -73,9 +73,9 @@ def _alias_option_Q( # noqa: N802 ): raise GMTParameterError( conflicts_with={ - "surftype": {"dpi", "mesh_fill", "monochrome", "nan_transparent"}, - reason="Parameter 'surftype' is given with a raw GMT command string", - } + "surftype": {"dpi", "mesh_fill", "monochrome", "nan_transparent"} + }, + reason="Parameter 'surftype' is given with a raw GMT command string", ) if dpi is not None and surftype != "image": From 7d9bd896192d4b25eeb361a45af6ca8f307fa53e Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Wed, 4 Feb 2026 17:36:21 +0800 Subject: [PATCH 07/41] Update pygmt/src/grdview.py Co-authored-by: Dongdong Tian --- pygmt/src/grdview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdview.py b/pygmt/src/grdview.py index 666a0952f39..2b03ec076fe 100644 --- a/pygmt/src/grdview.py +++ b/pygmt/src/grdview.py @@ -75,7 +75,7 @@ def _alias_option_Q( # noqa: N802 conflicts_with={ "surftype": {"dpi", "mesh_fill", "monochrome", "nan_transparent"} }, - reason="Parameter 'surftype' is given with a raw GMT command string", + reason="'surftype' is specified using the unrecommended GMT command string syntax.", ) if dpi is not None and surftype != "image": From fc029adebca9a1965a56b293b8cbb4ce82d5862c Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Wed, 4 Feb 2026 17:36:31 +0800 Subject: [PATCH 08/41] Update pygmt/src/_common.py Co-authored-by: Dongdong Tian --- pygmt/src/_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/_common.py b/pygmt/src/_common.py index 4ad2f0f9949..c834d132c30 100644 --- a/pygmt/src/_common.py +++ b/pygmt/src/_common.py @@ -366,7 +366,7 @@ def _parse_position( if any(v is not None and v is not False for v in kwdict.values()): raise GMTParameterError( conflicts_with={"position": set(kwdict.keys())}, - reason="Parameter 'position' is given with a raw GMT command string", + reason="'position' is specified using the unrecommended GMT command string syntax.", ) else: # No conflicting parameters to check, indicating it's a new function. From 47f3e1e7e65bd393b5f87206cf23e0baea4701ae Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Wed, 4 Feb 2026 17:36:38 +0800 Subject: [PATCH 09/41] Update pygmt/src/_common.py Co-authored-by: Dongdong Tian --- pygmt/src/_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/_common.py b/pygmt/src/_common.py index c834d132c30..c8b400e12e9 100644 --- a/pygmt/src/_common.py +++ b/pygmt/src/_common.py @@ -365,7 +365,7 @@ 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()): raise GMTParameterError( - conflicts_with={"position": set(kwdict.keys())}, + conflicts_with={"position": set(kwdict)}, reason="'position' is specified using the unrecommended GMT command string syntax.", ) else: From 11991fefe7132e04085c92b7c2a4995198f0c768 Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 4 Feb 2026 18:03:10 +0800 Subject: [PATCH 10/41] update alias --- pygmt/alias.py | 18 +++++++++++++----- pygmt/tests/test_alias_system.py | 17 +++++++++++------ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index 958a08a4607..1d7bf846f4f 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 GMTParameterError, GMTValueError from pygmt.helpers.utils import is_nonstr_iter, sequence_join @@ -416,11 +416,19 @@ 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}" + if not isinstance(aliases, Sequence): # Single Alias object. + raise GMTParameterError( + conflicts_with={aliases.name: {short_param}}, + reason=f"'{aliases.name}' is already specified using the long-form parameter.", + ) + # Sequence of Alias objects. + long_params = {v.name for v in aliases if not v.prefix} + params_str = ", ".join(repr(p) for p in sorted(long_params)) + verb = "is" if len(long_params) == 1 else "are" + raise GMTParameterError( + conflicts_with={param: {short_param} for param in long_params}, + reason=f"{params_str} {verb} already specified using long-form parameters.", ) - raise GMTInvalidInput(msg) # Long-form parameters are not specified. msg = ( diff --git a/pygmt/tests/test_alias_system.py b/pygmt/tests/test_alias_system.py index e7eb5683442..d1d74d5e034 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 GMTParameterError 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.", + GMTParameterError, + match=r"Conflicting parameters: 'projection' cannot be used with 'J'. 'projection' is already specified using the long-form parameter.", ): func(projection="X10c", J="H10c") @@ -95,11 +95,16 @@ 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): + with pytest.raises( + GMTParameterError, + match=r"Conflicting parameters: 'label' cannot be used with 'U'. 'label' is already specified using long-form parameters.", + ): func(label="abcd", U="efg") - with pytest.raises(GMTInvalidInput, match=msg): + with pytest.raises( + GMTParameterError, + match=r"Conflicting parameters: 'label' cannot be used with 'U'. 'label' is already specified using long-form parameters.", + ): func(text="efg", U="efg") From 3fef364f6ce5e906559b76794665a2d40bbd2094 Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 4 Feb 2026 19:26:08 +0800 Subject: [PATCH 11/41] simplify the error message --- pygmt/alias.py | 7 ++----- pygmt/tests/test_alias_system.py | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index 1d7bf846f4f..e184e9325d1 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -418,16 +418,13 @@ def merge(self, kwargs: Mapping[str, Any]): if long_param_given: if not isinstance(aliases, Sequence): # Single Alias object. raise GMTParameterError( - conflicts_with={aliases.name: {short_param}}, + conflicts_with={short_param: {aliases.name}}, reason=f"'{aliases.name}' is already specified using the long-form parameter.", ) # Sequence of Alias objects. long_params = {v.name for v in aliases if not v.prefix} - params_str = ", ".join(repr(p) for p in sorted(long_params)) - verb = "is" if len(long_params) == 1 else "are" raise GMTParameterError( - conflicts_with={param: {short_param} for param in long_params}, - reason=f"{params_str} {verb} already specified using long-form parameters.", + conflicts_with={short_param: long_params}, ) # Long-form parameters are not specified. diff --git a/pygmt/tests/test_alias_system.py b/pygmt/tests/test_alias_system.py index d1d74d5e034..6b073f81159 100644 --- a/pygmt/tests/test_alias_system.py +++ b/pygmt/tests/test_alias_system.py @@ -78,7 +78,7 @@ def test_alias_system_one_alias_short_form(): # Coexistence of long-form and short-form parameters. with pytest.raises( GMTParameterError, - match=r"Conflicting parameters: 'projection' cannot be used with 'J'. 'projection' is already specified using the long-form parameter.", + match=r"Conflicting parameters: 'J' cannot be used with 'projection'. 'projection' is already specified using the long-form parameter.", ): func(projection="X10c", J="H10c") From 350f67d06f712ffa1c01e1a8da4779f0b71d0cdb Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 4 Feb 2026 19:50:38 +0800 Subject: [PATCH 12/41] update the error message --- pygmt/alias.py | 13 +++++++++++-- pygmt/tests/test_alias_system.py | 2 +- pygmt/tests/test_coast.py | 4 ++-- pygmt/tests/test_solar.py | 4 ++-- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index e184e9325d1..0e5de340e9d 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -418,13 +418,22 @@ def merge(self, kwargs: Mapping[str, Any]): if long_param_given: if not isinstance(aliases, Sequence): # Single Alias object. raise GMTParameterError( - conflicts_with={short_param: {aliases.name}}, + conflicts_with={aliases.name: {short_param}}, reason=f"'{aliases.name}' is already specified using the long-form parameter.", ) # Sequence of Alias objects. long_params = {v.name for v in aliases if not v.prefix} + if len(long_params) == 1: + param_list = f"{sorted(long_params)[0]!r} is" + else: + param_list = ( + f"{', '.join(repr(p) for p in sorted(long_params))} are" + ) raise GMTParameterError( - conflicts_with={short_param: long_params}, + conflicts_with={ + long_param: {short_param} for long_param in long_params + }, + reason=f"{param_list} already specified using long-form parameters.", ) # Long-form parameters are not specified. diff --git a/pygmt/tests/test_alias_system.py b/pygmt/tests/test_alias_system.py index 6b073f81159..d1d74d5e034 100644 --- a/pygmt/tests/test_alias_system.py +++ b/pygmt/tests/test_alias_system.py @@ -78,7 +78,7 @@ def test_alias_system_one_alias_short_form(): # Coexistence of long-form and short-form parameters. with pytest.raises( GMTParameterError, - match=r"Conflicting parameters: 'J' cannot be used with 'projection'. 'projection' is already specified using the long-form parameter.", + match=r"Conflicting parameters: 'projection' cannot be used with 'J'. 'projection' is already specified using the long-form parameter.", ): func(projection="X10c", J="H10c") diff --git a/pygmt/tests/test_coast.py b/pygmt/tests/test_coast.py index 24d00f48025..e03ce819344 100644 --- a/pygmt/tests/test_coast.py +++ b/pygmt/tests/test_coast.py @@ -4,7 +4,7 @@ import pytest from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput, GMTParameterError +from pygmt.exceptions import GMTParameterError @pytest.mark.benchmark @@ -82,7 +82,7 @@ def test_coast_resolution_long_short_form_conflict(): using the long form. """ fig = Figure() - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): fig.coast( region=[-180, 180, -80, 80], projection="M15c", diff --git a/pygmt/tests/test_solar.py b/pygmt/tests/test_solar.py index b63b6b40899..b4ddf78016d 100644 --- a/pygmt/tests/test_solar.py +++ b/pygmt/tests/test_solar.py @@ -6,7 +6,7 @@ import pytest from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput, GMTValueError +from pygmt.exceptions import GMTParameterError, GMTValueError @pytest.mark.mpl_image_compare @@ -84,7 +84,7 @@ def test_invalid_parameter(): arguments for 'terminator' and 'terminator_datetime'. """ fig = Figure() - with pytest.raises(GMTInvalidInput): + with pytest.raises(GMTParameterError): # Use single-letter parameter 'T' for testing fig.solar( region="d", projection="W0/15c", frame="a", T="d+d1990-02-17T04:25:00" From e4f040f686198e92ab7cd373a33d1c51cfe8fe43 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Thu, 5 Feb 2026 11:24:25 +0800 Subject: [PATCH 13/41] Update pygmt/exceptions.py Co-authored-by: Dongdong Tian --- pygmt/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index e36f521fc76..c3cf1c486e6 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -185,7 +185,7 @@ def __init__( for param, conflicts in conflicts_with.items(): msg.append( f"Conflicting parameters: {param!r} cannot be used with " - f"{', '.join(repr(c) for c in sorted(conflicts))}." + f"{', '.join(repr(c) for c in conflicts)}." ) if reason: msg.append(reason) From f73fcdb1b324fe52f02298c7c7ad27d146753115 Mon Sep 17 00:00:00 2001 From: chuan Date: Thu, 5 Feb 2026 11:30:09 +0800 Subject: [PATCH 14/41] Replace long-form parameter(s) with short-form parameter(s) --- pygmt/alias.py | 40 ++++++++++++---------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index 0e5de340e9d..d5a8d130f0b 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -405,40 +405,24 @@ def merge(self, kwargs: Mapping[str, Any]): # Long-form parameters exist. aliases = self.aliasdict.get(short_param) if not isinstance(aliases, Sequence): # Single Alias object. - _msg_long = f"Use long-form parameter {aliases.name!r} instead." + long_params = [aliases.name] else: # Sequence of Alias objects. - _params = [f"{v.name!r}" for v in aliases if not v.prefix] - _modifiers = [f"{v.name!r} ({v.prefix})" for v in aliases if v.prefix] - _msg_long = ( - f"Use long-form parameters {', '.join(_params)}, " - f"with optional parameters {', '.join(_modifiers)} instead." - ) + long_params = [ + f"{alias.name} ({alias.prefix})" if alias.prefix else alias.name + for alias in aliases + ] + + long_params_text = ", ".join(repr(name) for name in long_params) + msg = ( + f"Short-form parameter {short_param!r} is not recommended. " + f"Use long-form parameter(s) {long_params_text} instead." + ) - # Long-form parameters are already specified. if long_param_given: - if not isinstance(aliases, Sequence): # Single Alias object. - raise GMTParameterError( - conflicts_with={aliases.name: {short_param}}, - reason=f"'{aliases.name}' is already specified using the long-form parameter.", - ) - # Sequence of Alias objects. - long_params = {v.name for v in aliases if not v.prefix} - if len(long_params) == 1: - param_list = f"{sorted(long_params)[0]!r} is" - else: - param_list = ( - f"{', '.join(repr(p) for p in sorted(long_params))} are" - ) raise GMTParameterError( - conflicts_with={ - long_param: {short_param} for long_param in long_params - }, - reason=f"{param_list} already specified using long-form parameters.", + conflicts_with={short_param: long_params}, reason=msg ) # Long-form parameters are not specified. - msg = ( - f"Short-form parameter {short_param!r} is not recommended. {_msg_long}" - ) warnings.warn(msg, category=SyntaxWarning, stacklevel=2) return self From a096843c4ff077100646297b566d092c67af7fff Mon Sep 17 00:00:00 2001 From: chuan Date: Thu, 5 Feb 2026 11:41:04 +0800 Subject: [PATCH 15/41] fix test --- pygmt/tests/test_alias_system.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pygmt/tests/test_alias_system.py b/pygmt/tests/test_alias_system.py index d1d74d5e034..c43d72736b9 100644 --- a/pygmt/tests/test_alias_system.py +++ b/pygmt/tests/test_alias_system.py @@ -71,14 +71,14 @@ def test_alias_system_one_alias_short_form(): # Long-form exists but is not given, and short-form is given. with pytest.warns( SyntaxWarning, - match=r"Short-form parameter 'J' is not recommended. Use long-form parameter 'projection' instead.", + match=r"Short-form parameter 'J' is not recommended. Use long-form parameter\(s\) 'projection' instead.", ): assert func(J="X10c") == ["-JX10c"] # Coexistence of long-form and short-form parameters. with pytest.raises( GMTParameterError, - match=r"Conflicting parameters: 'projection' cannot be used with 'J'. 'projection' is already specified using the long-form parameter.", + match=r"Conflicting parameters: 'J' cannot be used with 'projection'. Short-form parameter 'J' is not recommended. Use long-form parameter\(s\) 'projection' instead.", ): func(projection="X10c", J="H10c") @@ -88,7 +88,7 @@ def test_alias_system_multiple_aliases_short_form(): Test that the alias system works with multiple aliases when short-form parameters are used. """ - _msg_long = r"Use long-form parameters 'label', with optional parameters 'text' \(\+t\), 'offset' \(\+o\) instead." + _msg_long = r"Use long-form parameter\(s\) 'label', 'text \(\+t\)', 'offset \(\+o\)' instead." # Long-form exists but is not given, and short-form is given. msg = rf"Short-form parameter 'U' is not recommended. {_msg_long}" with pytest.warns(SyntaxWarning, match=msg): @@ -97,13 +97,13 @@ def test_alias_system_multiple_aliases_short_form(): # Coexistence of long-form and short-form parameters. with pytest.raises( GMTParameterError, - match=r"Conflicting parameters: 'label' cannot be used with 'U'. 'label' is already specified using long-form parameters.", + match=r"Conflicting parameters: 'U' cannot be used with 'label', 'text \(\+t\)', 'offset \(\+o\)'. Short-form parameter 'U' is not recommended. Use long-form parameter\(s\) 'label', 'text \(\+t\)', 'offset \(\+o\)' instead.", ): func(label="abcd", U="efg") with pytest.raises( GMTParameterError, - match=r"Conflicting parameters: 'label' cannot be used with 'U'. 'label' is already specified using long-form parameters.", + match=r"Conflicting parameters: 'U' cannot be used with 'label', 'text \(\+t\)', 'offset \(\+o\)'. Short-form parameter 'U' is not recommended. Use long-form parameter\(s\) 'label', 'text \(\+t\)', 'offset \(\+o\)' instead.", ): func(text="efg", U="efg") From 5cbcbe3e7c12c8ecc6725740bcd0e75635c8e4ff Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Thu, 5 Feb 2026 13:21:07 +0800 Subject: [PATCH 16/41] Update pygmt/alias.py Co-authored-by: Dongdong Tian --- pygmt/alias.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index d5a8d130f0b..9cca46ed79e 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -407,10 +407,7 @@ def merge(self, kwargs: Mapping[str, Any]): if not isinstance(aliases, Sequence): # Single Alias object. long_params = [aliases.name] else: # Sequence of Alias objects. - long_params = [ - f"{alias.name} ({alias.prefix})" if alias.prefix else alias.name - for alias in aliases - ] + long_params = [alias.name for alias in aliases] long_params_text = ", ".join(repr(name) for name in long_params) msg = ( From af21ef2adf8efd967d5b33aaa0f0aa27cf86f0ac Mon Sep 17 00:00:00 2001 From: chuan Date: Thu, 5 Feb 2026 13:24:42 +0800 Subject: [PATCH 17/41] fix error --- pygmt/tests/test_alias_system.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/tests/test_alias_system.py b/pygmt/tests/test_alias_system.py index c43d72736b9..5117c291126 100644 --- a/pygmt/tests/test_alias_system.py +++ b/pygmt/tests/test_alias_system.py @@ -88,7 +88,7 @@ def test_alias_system_multiple_aliases_short_form(): Test that the alias system works with multiple aliases when short-form parameters are used. """ - _msg_long = r"Use long-form parameter\(s\) 'label', 'text \(\+t\)', 'offset \(\+o\)' instead." + _msg_long = r"Use long-form parameter\(s\) 'label', 'text', 'offset' instead." # Long-form exists but is not given, and short-form is given. msg = rf"Short-form parameter 'U' is not recommended. {_msg_long}" with pytest.warns(SyntaxWarning, match=msg): @@ -97,13 +97,13 @@ def test_alias_system_multiple_aliases_short_form(): # Coexistence of long-form and short-form parameters. with pytest.raises( GMTParameterError, - match=r"Conflicting parameters: 'U' cannot be used with 'label', 'text \(\+t\)', 'offset \(\+o\)'. Short-form parameter 'U' is not recommended. Use long-form parameter\(s\) 'label', 'text \(\+t\)', 'offset \(\+o\)' instead.", + match=r"Conflicting parameters: 'U' cannot be used with 'label', 'text', 'offset'. Short-form parameter 'U' is not recommended. Use long-form parameter\(s\) 'label', 'text', 'offset' instead.", ): func(label="abcd", U="efg") with pytest.raises( GMTParameterError, - match=r"Conflicting parameters: 'U' cannot be used with 'label', 'text \(\+t\)', 'offset \(\+o\)'. Short-form parameter 'U' is not recommended. Use long-form parameter\(s\) 'label', 'text \(\+t\)', 'offset \(\+o\)' instead.", + match=r"Conflicting parameters: 'U' cannot be used with 'label', 'text', 'offset'. Short-form parameter 'U' is not recommended. Use long-form parameter\(s\) 'label', 'text', 'offset' instead.", ): func(text="efg", U="efg") From 3c94d178af45c9154e2120bfa1f815a2df163a67 Mon Sep 17 00:00:00 2001 From: chuan Date: Thu, 5 Feb 2026 21:16:27 +0800 Subject: [PATCH 18/41] fix sort error in alias system --- pygmt/alias.py | 5 +++-- pygmt/exceptions.py | 2 +- pygmt/tests/test_alias_system.py | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index 9cca46ed79e..bf0a8e09258 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -409,7 +409,8 @@ def merge(self, kwargs: Mapping[str, Any]): else: # Sequence of Alias objects. long_params = [alias.name for alias in aliases] - long_params_text = ", ".join(repr(name) for name in long_params) + long_params_sorted = sorted(long_params) + long_params_text = ", ".join(repr(name) for name in long_params_sorted) msg = ( f"Short-form parameter {short_param!r} is not recommended. " f"Use long-form parameter(s) {long_params_text} instead." @@ -417,7 +418,7 @@ def merge(self, kwargs: Mapping[str, Any]): if long_param_given: raise GMTParameterError( - conflicts_with={short_param: long_params}, reason=msg + conflicts_with={short_param: set(long_params_sorted)}, reason=msg ) # Long-form parameters are not specified. diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index c3cf1c486e6..e36f521fc76 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -185,7 +185,7 @@ def __init__( for param, conflicts in conflicts_with.items(): msg.append( f"Conflicting parameters: {param!r} cannot be used with " - f"{', '.join(repr(c) for c in conflicts)}." + f"{', '.join(repr(c) for c in sorted(conflicts))}." ) if reason: msg.append(reason) diff --git a/pygmt/tests/test_alias_system.py b/pygmt/tests/test_alias_system.py index 5117c291126..4a7188e9bbe 100644 --- a/pygmt/tests/test_alias_system.py +++ b/pygmt/tests/test_alias_system.py @@ -88,7 +88,7 @@ def test_alias_system_multiple_aliases_short_form(): Test that the alias system works with multiple aliases when short-form parameters are used. """ - _msg_long = r"Use long-form parameter\(s\) 'label', 'text', 'offset' instead." + _msg_long = r"Use long-form parameter\(s\) 'label', 'offset', 'text' instead." # Long-form exists but is not given, and short-form is given. msg = rf"Short-form parameter 'U' is not recommended. {_msg_long}" with pytest.warns(SyntaxWarning, match=msg): @@ -97,13 +97,13 @@ def test_alias_system_multiple_aliases_short_form(): # Coexistence of long-form and short-form parameters. with pytest.raises( GMTParameterError, - match=r"Conflicting parameters: 'U' cannot be used with 'label', 'text', 'offset'. Short-form parameter 'U' is not recommended. Use long-form parameter\(s\) 'label', 'text', 'offset' instead.", + match=r"Conflicting parameters: 'U' cannot be used with 'label', 'offset', 'text'. Short-form parameter 'U' is not recommended. Use long-form parameter\(s\) 'label', 'offset', 'text' instead.", ): func(label="abcd", U="efg") with pytest.raises( GMTParameterError, - match=r"Conflicting parameters: 'U' cannot be used with 'label', 'text', 'offset'. Short-form parameter 'U' is not recommended. Use long-form parameter\(s\) 'label', 'text', 'offset' instead.", + match=r"Conflicting parameters: 'U' cannot be used with 'label', 'offset', 'text'. Short-form parameter 'U' is not recommended. Use long-form parameter\(s\) 'label', 'offset', 'text' instead.", ): func(text="efg", U="efg") From 7c454ba92bae89ebc53662718ac180c326ce5514 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 10:31:36 +0800 Subject: [PATCH 19/41] Update pygmt/alias.py Co-authored-by: Dongdong Tian --- pygmt/alias.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index bf0a8e09258..263b19e7cbc 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -409,7 +409,6 @@ def merge(self, kwargs: Mapping[str, Any]): else: # Sequence of Alias objects. long_params = [alias.name for alias in aliases] - long_params_sorted = sorted(long_params) long_params_text = ", ".join(repr(name) for name in long_params_sorted) msg = ( f"Short-form parameter {short_param!r} is not recommended. " From 355ba29f8ec2917c0e8bf1e7c16d15d7bed019d3 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 10:32:24 +0800 Subject: [PATCH 20/41] Update pygmt/exceptions.py Co-authored-by: Dongdong Tian --- pygmt/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index e36f521fc76..c3cf1c486e6 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -185,7 +185,7 @@ def __init__( for param, conflicts in conflicts_with.items(): msg.append( f"Conflicting parameters: {param!r} cannot be used with " - f"{', '.join(repr(c) for c in sorted(conflicts))}." + f"{', '.join(repr(c) for c in conflicts)}." ) if reason: msg.append(reason) From c260d4a57d355bbd0480ee876a6d87e421ec1292 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 10:32:39 +0800 Subject: [PATCH 21/41] Update pygmt/alias.py Co-authored-by: Dongdong Tian --- pygmt/alias.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index 263b19e7cbc..e2960e3a8e1 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -417,7 +417,7 @@ def merge(self, kwargs: Mapping[str, Any]): if long_param_given: raise GMTParameterError( - conflicts_with={short_param: set(long_params_sorted)}, reason=msg + conflicts_with={short_param: long_params}, reason=msg ) # Long-form parameters are not specified. From b3c4ba513a2f861289a1ee7ac36ce5b067750de6 Mon Sep 17 00:00:00 2001 From: chuan Date: Fri, 6 Feb 2026 10:35:17 +0800 Subject: [PATCH 22/41] simplify the code --- pygmt/alias.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index e2960e3a8e1..0d5fa256fce 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -404,12 +404,13 @@ def merge(self, kwargs: Mapping[str, Any]): # Long-form parameters exist. aliases = self.aliasdict.get(short_param) - if not isinstance(aliases, Sequence): # Single Alias object. - long_params = [aliases.name] - else: # Sequence of Alias objects. - long_params = [alias.name for alias in aliases] + long_params = set( + [v.name for v in aliases] + if isinstance(aliases, Sequence) + else [aliases.name] + ) - long_params_text = ", ".join(repr(name) for name in long_params_sorted) + long_params_text = ", ".join(repr(name) for name in sorted(long_params)) msg = ( f"Short-form parameter {short_param!r} is not recommended. " f"Use long-form parameter(s) {long_params_text} instead." From 368a102941f51942d8beb3a183c17f6544724c4e Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 11:01:34 +0800 Subject: [PATCH 23/41] Update pygmt/exceptions.py Co-authored-by: Dongdong Tian --- pygmt/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index c3cf1c486e6..88564786824 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -158,7 +158,7 @@ def __init__( required: str | set[str] | None = None, at_least_one: set[str] | None = None, at_most_one: set[str] | None = None, - conflicts_with: dict[str, set[str]] | None = None, + conflicts_with: tuple[str, set[str]] | None = None, reason: str | None = None, ): msg = [] From 35ebe3c7f1dee6da1ae1cf35f56553544f3c0e31 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 11:01:45 +0800 Subject: [PATCH 24/41] Update pygmt/exceptions.py Co-authored-by: Dongdong Tian --- pygmt/exceptions.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index 88564786824..454b5fdaa30 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -182,11 +182,11 @@ def __init__( "Specify at most one of them." ) if conflicts_with: - for param, conflicts in conflicts_with.items(): - msg.append( - f"Conflicting parameters: {param!r} cannot be used with " - f"{', '.join(repr(c) for c in conflicts)}." - ) + param, conflicts = conflicts_with + msg.append( + f"Conflicting parameters: {param!r} cannot be used with " + f"{', '.join(repr(c) for c in conflicts)}." + ) if reason: msg.append(reason) super().__init__(" ".join(msg)) From e8380d4a69baefc72a128e17ddc3803d03ee0332 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 11:05:48 +0800 Subject: [PATCH 25/41] Update pygmt/exceptions.py Co-authored-by: Dongdong Tian --- pygmt/exceptions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index 454b5fdaa30..003e644e555 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -146,8 +146,8 @@ class GMTParameterError(GMTError): A set of mutually exclusive parameter names, of which at most one can be specified. conflicts_with - A dictionary mapping parameter names to sets of conflicting parameter - names. Used to indicate which parameters cannot be used together. + A tuple with the parameter name and a set of conflicting parameter names, + indicating which parameters cannot be used together. reason Detailed reason why the parameters are invalid. """ From fdeba84a17c2eeb57883105535e07bd7f3b63d75 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 11:05:59 +0800 Subject: [PATCH 26/41] Update pygmt/src/_common.py Co-authored-by: Dongdong Tian --- pygmt/src/_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/_common.py b/pygmt/src/_common.py index c8b400e12e9..c8a8b9cb1bf 100644 --- a/pygmt/src/_common.py +++ b/pygmt/src/_common.py @@ -365,7 +365,7 @@ 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()): raise GMTParameterError( - conflicts_with={"position": set(kwdict)}, + conflicts_with=("position", set(kwdict)), reason="'position' is specified using the unrecommended GMT command string syntax.", ) else: From 0deddd2e2fd07edbe3f8c137d7bc5f991d576ad0 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 11:06:10 +0800 Subject: [PATCH 27/41] Update pygmt/src/grdview.py Co-authored-by: Dongdong Tian --- pygmt/src/grdview.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/src/grdview.py b/pygmt/src/grdview.py index 2b03ec076fe..98b74e22092 100644 --- a/pygmt/src/grdview.py +++ b/pygmt/src/grdview.py @@ -72,9 +72,9 @@ def _alias_option_Q( # noqa: N802 for v in (dpi, mesh_fill, monochrome, nan_transparent) ): raise GMTParameterError( - conflicts_with={ - "surftype": {"dpi", "mesh_fill", "monochrome", "nan_transparent"} - }, + conflicts_with=( + "surftype", {"dpi", "mesh_fill", "monochrome", "nan_transparent"} + ), reason="'surftype' is specified using the unrecommended GMT command string syntax.", ) From 6e951b38eeef91da6913347c709117afa7cc1a53 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 11:10:23 +0800 Subject: [PATCH 28/41] Update pygmt/alias.py Co-authored-by: Dongdong Tian --- pygmt/alias.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index 0d5fa256fce..970e3a68828 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -418,7 +418,7 @@ def merge(self, kwargs: Mapping[str, Any]): if long_param_given: raise GMTParameterError( - conflicts_with={short_param: long_params}, reason=msg + conflicts_with=(short_param, long_params), reason=msg ) # Long-form parameters are not specified. From c3bf105d80584517d61164ff6707e398d1e53f2b Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 11:35:21 +0800 Subject: [PATCH 29/41] Update pygmt/alias.py Co-authored-by: Dongdong Tian --- pygmt/alias.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index 970e3a68828..9b3803dde63 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -404,13 +404,16 @@ def merge(self, kwargs: Mapping[str, Any]): # Long-form parameters exist. aliases = self.aliasdict.get(short_param) - long_params = set( - [v.name for v in aliases] - if isinstance(aliases, Sequence) - else [aliases.name] + if not isinstance(aliases, Sequence): + aliases = [aliases] + + long_params = [v.name for v in aliases] + long_params_text = ", ".join( + [ + f"{v.name!r} ({v.prefix})" if v.prefix.startswith("+") else v.name + for v in aliases + ] ) - - long_params_text = ", ".join(repr(name) for name in sorted(long_params)) msg = ( f"Short-form parameter {short_param!r} is not recommended. " f"Use long-form parameter(s) {long_params_text} instead." From 28e0582aa913c33bd82b696645e26c4a78156648 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 11:59:11 +0800 Subject: [PATCH 30/41] Update pygmt/alias.py Co-authored-by: Dongdong Tian --- pygmt/alias.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index 9b3803dde63..f9aa3f69109 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -410,7 +410,9 @@ def merge(self, kwargs: Mapping[str, Any]): long_params = [v.name for v in aliases] long_params_text = ", ".join( [ - f"{v.name!r} ({v.prefix})" if v.prefix.startswith("+") else v.name + f"{v.name!r} ({v.prefix})" + if v.prefix.startswith("+") + else f"{v.name!r}" for v in aliases ] ) From e33a11aceb3ba4b9cf71c094ecc3d757829461ff Mon Sep 17 00:00:00 2001 From: chuan Date: Fri, 6 Feb 2026 12:08:55 +0800 Subject: [PATCH 31/41] update test_alias_system.py --- pygmt/src/grdview.py | 3 ++- pygmt/tests/test_alias_system.py | 30 +++++++++++------------------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/pygmt/src/grdview.py b/pygmt/src/grdview.py index 98b74e22092..761c4187c1f 100644 --- a/pygmt/src/grdview.py +++ b/pygmt/src/grdview.py @@ -73,7 +73,8 @@ def _alias_option_Q( # noqa: N802 ): raise GMTParameterError( conflicts_with=( - "surftype", {"dpi", "mesh_fill", "monochrome", "nan_transparent"} + "surftype", + {"dpi", "mesh_fill", "monochrome", "nan_transparent"}, ), reason="'surftype' is specified using the unrecommended GMT command string syntax.", ) diff --git a/pygmt/tests/test_alias_system.py b/pygmt/tests/test_alias_system.py index 4a7188e9bbe..76796929d70 100644 --- a/pygmt/tests/test_alias_system.py +++ b/pygmt/tests/test_alias_system.py @@ -65,21 +65,17 @@ def test_alias_system_one_alias_short_form(): """ Test that the alias system works when short-form parameters coexist. """ + _msg_conflict = "Conflicting parameters: 'J' cannot be used with 'projection'." + _msg_reason = r"Short-form parameter 'J' is not recommended. Use long-form parameter\(s\) 'projection' instead." # Long-form does not exist. assert func(A="abc") == ["-Aabc"] # Long-form exists but is not given, and short-form is given. - with pytest.warns( - SyntaxWarning, - match=r"Short-form parameter 'J' is not recommended. Use long-form parameter\(s\) 'projection' instead.", - ): + with pytest.warns(SyntaxWarning, match=_msg_reason): assert func(J="X10c") == ["-JX10c"] # Coexistence of long-form and short-form parameters. - with pytest.raises( - GMTParameterError, - match=r"Conflicting parameters: 'J' cannot be used with 'projection'. Short-form parameter 'J' is not recommended. Use long-form parameter\(s\) 'projection' instead.", - ): + with pytest.raises(GMTParameterError, match=rf"{_msg_conflict} {_msg_reason}"): func(projection="X10c", J="H10c") @@ -88,23 +84,19 @@ def test_alias_system_multiple_aliases_short_form(): Test that the alias system works with multiple aliases when short-form parameters are used. """ - _msg_long = r"Use long-form parameter\(s\) 'label', 'offset', 'text' instead." + _msg_conflict = ( + "Conflicting parameters: 'U' cannot be used with 'label', 'offset', 'text'." + ) + _msg_reason = r"Short-form parameter 'U' is not recommended. Use long-form parameter\(s\) 'label', 'offset', 'text' instead." # Long-form exists but is not given, and short-form is given. - msg = rf"Short-form parameter 'U' is not recommended. {_msg_long}" - with pytest.warns(SyntaxWarning, match=msg): + with pytest.warns(SyntaxWarning, match=_msg_reason): assert func(U="abcd+tefg") == ["-Uabcd+tefg"] # Coexistence of long-form and short-form parameters. - with pytest.raises( - GMTParameterError, - match=r"Conflicting parameters: 'U' cannot be used with 'label', 'offset', 'text'. Short-form parameter 'U' is not recommended. Use long-form parameter\(s\) 'label', 'offset', 'text' instead.", - ): + with pytest.raises(GMTParameterError, match=rf"{_msg_conflict} {_msg_reason}"): func(label="abcd", U="efg") - with pytest.raises( - GMTParameterError, - match=r"Conflicting parameters: 'U' cannot be used with 'label', 'offset', 'text'. Short-form parameter 'U' is not recommended. Use long-form parameter\(s\) 'label', 'offset', 'text' instead.", - ): + with pytest.raises(GMTParameterError, match=rf"{_msg_conflict} {_msg_reason}"): func(text="efg", U="efg") From 3743e11a97a037167aa9c33db61b817522cf3d58 Mon Sep 17 00:00:00 2001 From: chuan Date: Fri, 6 Feb 2026 12:15:00 +0800 Subject: [PATCH 32/41] fix test --- pygmt/tests/test_alias_system.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/tests/test_alias_system.py b/pygmt/tests/test_alias_system.py index 76796929d70..f430ded16aa 100644 --- a/pygmt/tests/test_alias_system.py +++ b/pygmt/tests/test_alias_system.py @@ -85,9 +85,9 @@ def test_alias_system_multiple_aliases_short_form(): are used. """ _msg_conflict = ( - "Conflicting parameters: 'U' cannot be used with 'label', 'offset', 'text'." + "Conflicting parameters: 'U' cannot be used with 'label', 'text', 'offset'." ) - _msg_reason = r"Short-form parameter 'U' is not recommended. Use long-form parameter\(s\) 'label', 'offset', 'text' instead." + _msg_reason = r"Short-form parameter 'U' is not recommended. Use long-form parameter\(s\) 'label', 'text' \(\+t\), 'offset' \(\+o\) instead." # Long-form exists but is not given, and short-form is given. with pytest.warns(SyntaxWarning, match=_msg_reason): assert func(U="abcd+tefg") == ["-Uabcd+tefg"] From f2194bf798f87519193721d7f3ae5028b83f7060 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 13:42:22 +0800 Subject: [PATCH 33/41] Update pygmt/alias.py Co-authored-by: Dongdong Tian --- pygmt/alias.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index f9aa3f69109..404af6d115c 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -407,7 +407,7 @@ def merge(self, kwargs: Mapping[str, Any]): if not isinstance(aliases, Sequence): aliases = [aliases] - long_params = [v.name for v in aliases] + long_params = {v.name for v in aliases} long_params_text = ", ".join( [ f"{v.name!r} ({v.prefix})" From 3a733810782f75fc92bb02142bcf38e219ab5de4 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 16:27:09 +0800 Subject: [PATCH 34/41] Update pygmt/alias.py Co-authored-by: Dongdong Tian --- pygmt/alias.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index 404af6d115c..f9aa3f69109 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -407,7 +407,7 @@ def merge(self, kwargs: Mapping[str, Any]): if not isinstance(aliases, Sequence): aliases = [aliases] - long_params = {v.name for v in aliases} + long_params = [v.name for v in aliases] long_params_text = ", ".join( [ f"{v.name!r} ({v.prefix})" From ff0c829b32e70f55b1eab364248080ba4e1029a7 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 16:27:22 +0800 Subject: [PATCH 35/41] Update pygmt/exceptions.py Co-authored-by: Dongdong Tian --- pygmt/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index 003e644e555..33258790dbb 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -146,7 +146,7 @@ class GMTParameterError(GMTError): A set of mutually exclusive parameter names, of which at most one can be specified. conflicts_with - A tuple with the parameter name and a set of conflicting parameter names, + A tuple with the parameter name and a collection of conflicting parameter names, indicating which parameters cannot be used together. reason Detailed reason why the parameters are invalid. From 389161c47286d03ba2fbd5ceb7c68bf0e279865b Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 16:27:36 +0800 Subject: [PATCH 36/41] Update pygmt/exceptions.py Co-authored-by: Dongdong Tian --- pygmt/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index 33258790dbb..02fc2446f28 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -158,7 +158,7 @@ def __init__( required: str | set[str] | None = None, at_least_one: set[str] | None = None, at_most_one: set[str] | None = None, - conflicts_with: tuple[str, set[str]] | None = None, + conflicts_with: tuple[str, Iterable[str]] | None = None, reason: str | None = None, ): msg = [] From 5cbfba4d2830d6d33504d7eb401a3582e0753863 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 16:27:48 +0800 Subject: [PATCH 37/41] Update pygmt/src/_common.py Co-authored-by: Dongdong Tian --- pygmt/src/_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/_common.py b/pygmt/src/_common.py index c8a8b9cb1bf..69066e0d26d 100644 --- a/pygmt/src/_common.py +++ b/pygmt/src/_common.py @@ -365,7 +365,7 @@ 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()): raise GMTParameterError( - conflicts_with=("position", set(kwdict)), + conflicts_with=("position", kwdict.keys()), reason="'position' is specified using the unrecommended GMT command string syntax.", ) else: From 9737971defd26e4fbcb866dbebf1f45f9efb6354 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 16:27:59 +0800 Subject: [PATCH 38/41] Update pygmt/src/grdview.py Co-authored-by: Dongdong Tian --- pygmt/src/grdview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdview.py b/pygmt/src/grdview.py index 761c4187c1f..d67cb1b7659 100644 --- a/pygmt/src/grdview.py +++ b/pygmt/src/grdview.py @@ -74,7 +74,7 @@ def _alias_option_Q( # noqa: N802 raise GMTParameterError( conflicts_with=( "surftype", - {"dpi", "mesh_fill", "monochrome", "nan_transparent"}, + ["dpi", "mesh_fill", "monochrome", "nan_transparent"], ), reason="'surftype' is specified using the unrecommended GMT command string syntax.", ) From b5ca4510330dfe82094400d06099a0e45f30aad0 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 16:32:47 +0800 Subject: [PATCH 39/41] Update pygmt/exceptions.py Co-authored-by: Dongdong Tian --- pygmt/exceptions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index 4eee9b5dec8..bade4f08814 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -155,9 +155,9 @@ class GMTParameterError(GMTError): def __init__( self, *, - required: str | set[str] | None = None, - at_least_one: set[str] | None = None, - at_most_one: set[str] | None = None, + required: str | Iterable[str] | None = None, + at_least_one: Iterable[str] | None = None, + at_most_one: Iterable[str] | None = None, conflicts_with: tuple[str, Iterable[str]] | None = None, reason: str | None = None, ): From 983caf31127a92460e6c0c87abc3a6cc2baef3bb Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 16:41:07 +0800 Subject: [PATCH 40/41] Update pygmt/exceptions.py Co-authored-by: Dongdong Tian --- pygmt/exceptions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index bade4f08814..bd4571ad936 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -146,8 +146,8 @@ class GMTParameterError(GMTError): A set of mutually exclusive parameter names, of which at most one can be specified. conflicts_with - A tuple with the parameter name and a collection of conflicting parameter names, - indicating which parameters cannot be used together. + A tuple with the parameter name and a collection of conflicting parameter names, + indicating which parameters cannot be used together. reason Detailed reason why the parameters are invalid. """ From dffbc3fda90de05d438d707bc9af412da3d8057c Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Feb 2026 16:41:17 +0800 Subject: [PATCH 41/41] Update pygmt/exceptions.py Co-authored-by: Dongdong Tian --- pygmt/exceptions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/exceptions.py b/pygmt/exceptions.py index bd4571ad936..dbffb77d2c0 100644 --- a/pygmt/exceptions.py +++ b/pygmt/exceptions.py @@ -143,8 +143,8 @@ class GMTParameterError(GMTError): at_least_one A collection of parameter names, of which at least one must be specified. at_most_one - A set of mutually exclusive parameter names, of which at most one can be - specified. + A collection of mutually exclusive parameter names, of which at most one can be + specified. conflicts_with A tuple with the parameter name and a collection of conflicting parameter names, indicating which parameters cannot be used together.