From b7019d04705859717ef8c12e2966cc87970fd3e2 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 20 Jun 2025 15:51:35 +0800 Subject: [PATCH 1/6] Add the _to_string function --- pygmt/alias.py | 135 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 pygmt/alias.py diff --git a/pygmt/alias.py b/pygmt/alias.py new file mode 100644 index 00000000000..325c8c6b4ed --- /dev/null +++ b/pygmt/alias.py @@ -0,0 +1,135 @@ +""" +The PyGMT alias system to convert PyGMT long-form arguments to GMT's short-form. +""" + +from collections.abc import Mapping, Sequence +from typing import Any, Literal + +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers.utils import is_nonstr_iter, sequence_join + + +def _to_string( + value: Any, + prefix: str = "", # Default to an empty string to simplify the code logic. + mapping: bool | Mapping = False, + separator: Literal["/", ","] | None = None, + size: int | Sequence[int] | None = None, + ndim: int = 1, + name: str | None = None, +) -> str | list[str] | None: + """ + Convert any value to a string, a sequence of strings or None. + + The general rules are: + + - ``None``/``False`` will be converted to ``None``. + - ``True`` will be converted to an empty string. + - A sequence will be joined by the separator if a separator is provided. Otherwise, + each item in the sequence will be converted to a string and a sequence of strings + will be returned. + - Any other type of values will be converted to a string if possible. + + If a mapping dictionary is provided, the value will be converted to the short-form + string that GMT accepts (e.g., mapping PyGMT long-form argument ``"high"`` to GMT's + short-form argument ``"h"``). If the value is not in the mapping dictionary, the + original value will be returned. If ``mapping`` is set to ``True``, the first letter + of the long-form argument will be used as the short-form argument. + + An optional prefix (e.g., `"+o"`) can be added to the beginning of the converted + string. + + To avoid extra overhead, this function does not validate parameter combinations. For + example, if ``value`` is a sequence but ``separator`` is not specified, the function + will return a sequence of strings. In this case, ``prefix`` has no effect, but the + function does not check for such inconsistencies. The maintaner should ensure that + the parameter combinations are valid. + + Parameters + ---------- + value + The value to convert. + prefix + The string to add as a prefix to the returned value. + mapping + A mapping dictionary or ``True`` to map long-form arguments to GMT's short-form + arguments. If ``True``, will use the first letter of the long-form arguments. + separator + The separator to use if the value is a sequence. + + Returns + ------- + ret + The converted value. + + Examples + -------- + >>> _to_string("text") + 'text' + >>> _to_string(12) + '12' + >>> _to_string(True) + '' + >>> _to_string(False) + >>> _to_string(None) + + >>> _to_string("text", prefix="+a") + '+atext' + >>> _to_string(12, prefix="+a") + '+a12' + >>> _to_string(True, prefix="+a") + '+a' + >>> _to_string(False, prefix="+a") + >>> _to_string(None, prefix="+a") + + >>> _to_string("high", mapping=True) + 'h' + >>> _to_string("mean", mapping={"mean": "a", "mad": "d", "full": "g"}) + 'a' + >>> _to_string("invalid", mapping={"mean": "a", "mad": "d", "full": "g"}) + Traceback (most recent call last): + ... + pygmt...GMTInvalidInput: Invalid value: 'invalid'. Valid values are: mean, ... + + >>> _to_string((12, 34), separator="/") + '12/34' + >>> _to_string(("12p", "34p"), separator=",") + '12p,34p' + >>> _to_string(("12p", "34p"), prefix="+o", separator="/") + '+o12p/34p' + + >>> _to_string(["xaf", "yaf", "WSen"]) + ['xaf', 'yaf', 'WSen'] + """ + # None and False are converted to None. + if value is None or value is False: + return None + # True is converted to an empty string with the optional prefix. + if value is True: + return f"{prefix}" + # Any non-sequence value is converted to a string. + if not is_nonstr_iter(value): + match mapping: + case False: + pass + case True: + value = value[0] + case Mapping(): + if value not in mapping and value not in mapping.values(): + _name = f"Parameter {name!r}: " if name else "" + msg = ( + f"{_name}Invalid value: {value!r}. " + f"Valid values are: {', '.join(mapping)}." + ) + raise GMTInvalidInput(msg) + value = mapping.get(value, value) + return f"{prefix}{value}" + + # Return the sequence if separator is not specified for options like '-B'. + # True in a sequence will be converted to an empty string. + if separator is None: + return [str(item) if item is not True else "" for item in value] + # Join the sequence of values with the separator. + # "prefix" and "mapping" are ignored. We can enable them when needed. + _value = sequence_join(value, separator=separator, size=size, ndim=ndim, name=name) + return f"{prefix}{_value}" From 8f5c52ba6838b3d3a9d918bdd7381f2e334cadc4 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 24 Jun 2025 11:42:10 +0800 Subject: [PATCH 2/6] Use GMTValueError --- pygmt/alias.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index 325c8c6b4ed..f5c9f5da6ed 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -5,7 +5,7 @@ from collections.abc import Mapping, Sequence from typing import Any, Literal -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTValueError from pygmt.helpers.utils import is_nonstr_iter, sequence_join @@ -116,12 +116,11 @@ def _to_string( value = value[0] case Mapping(): if value not in mapping and value not in mapping.values(): - _name = f"Parameter {name!r}: " if name else "" - msg = ( - f"{_name}Invalid value: {value!r}. " - f"Valid values are: {', '.join(mapping)}." + raise GMTValueError( + value, + description="value for parameter {name!r}" if name else "value", + choices=mapping.keys(), ) - raise GMTInvalidInput(msg) value = mapping.get(value, value) return f"{prefix}{value}" From c499aab2c1274b1c9b7251439548aa0028c68495 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 2 Jul 2025 09:01:21 +0800 Subject: [PATCH 3/6] Remove the support of mapping=True --- pygmt/alias.py | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index f5c9f5da6ed..c64ff13da1b 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -12,7 +12,7 @@ def _to_string( value: Any, prefix: str = "", # Default to an empty string to simplify the code logic. - mapping: bool | Mapping = False, + mapping: Mapping | None = None, separator: Literal["/", ","] | None = None, size: int | Sequence[int] | None = None, ndim: int = 1, @@ -32,9 +32,7 @@ def _to_string( If a mapping dictionary is provided, the value will be converted to the short-form string that GMT accepts (e.g., mapping PyGMT long-form argument ``"high"`` to GMT's - short-form argument ``"h"``). If the value is not in the mapping dictionary, the - original value will be returned. If ``mapping`` is set to ``True``, the first letter - of the long-form argument will be used as the short-form argument. + short-form argument ``"h"``). An optional prefix (e.g., `"+o"`) can be added to the beginning of the converted string. @@ -52,8 +50,7 @@ def _to_string( prefix The string to add as a prefix to the returned value. mapping - A mapping dictionary or ``True`` to map long-form arguments to GMT's short-form - arguments. If ``True``, will use the first letter of the long-form arguments. + A mapping dictionary to map long-form arguments to GMT's short-form arguments. separator The separator to use if the value is a sequence. @@ -82,8 +79,6 @@ def _to_string( >>> _to_string(False, prefix="+a") >>> _to_string(None, prefix="+a") - >>> _to_string("high", mapping=True) - 'h' >>> _to_string("mean", mapping={"mean": "a", "mad": "d", "full": "g"}) 'a' >>> _to_string("invalid", mapping={"mean": "a", "mad": "d", "full": "g"}) @@ -109,19 +104,14 @@ def _to_string( return f"{prefix}" # Any non-sequence value is converted to a string. if not is_nonstr_iter(value): - match mapping: - case False: - pass - case True: - value = value[0] - case Mapping(): - if value not in mapping and value not in mapping.values(): - raise GMTValueError( - value, - description="value for parameter {name!r}" if name else "value", - choices=mapping.keys(), - ) - value = mapping.get(value, value) + if mapping: + if value not in mapping and value not in mapping.values(): + raise GMTValueError( + value, + description="value for parameter {name!r}" if name else "value", + choices=mapping.keys(), + ) + value = mapping.get(value, value) return f"{prefix}{value}" # Return the sequence if separator is not specified for options like '-B'. From 232ff52e291a03426be72b86d42ab3cd56b19e35 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 2 Jul 2025 10:06:08 +0800 Subject: [PATCH 4/6] Add more docstrings --- pygmt/alias.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index c64ff13da1b..482ef60563b 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -1,5 +1,5 @@ """ -The PyGMT alias system to convert PyGMT long-form arguments to GMT's short-form. +The PyGMT alias system to convert PyGMT's long-form arguments to GMT's short-form. """ from collections.abc import Mapping, Sequence @@ -53,6 +53,14 @@ def _to_string( A mapping dictionary to map long-form arguments to GMT's short-form arguments. separator The separator to use if the value is a sequence. + size + Expected size of the 1-D sequence. It can be either an integer or a sequence of + integers. If an integer, it is the expected size of the 1-D sequence. If it is a + sequence, it is the allowed sizes of the 1-D sequence. + ndim + The expected maximum number of dimensions of the sequence. + name + The name of the parameter to be used in the error message. Returns ------- From 2dc37c30714055b37e97910aa7fd3beff78de426 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 2 Jul 2025 10:21:54 +0800 Subject: [PATCH 5/6] Improve docstrings --- pygmt/alias.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index 482ef60563b..ceb405cd1cd 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -27,12 +27,13 @@ def _to_string( - ``True`` will be converted to an empty string. - A sequence will be joined by the separator if a separator is provided. Otherwise, each item in the sequence will be converted to a string and a sequence of strings - will be returned. + will be returned. It's also possible to validate the size and dimension of the + sequence. - Any other type of values will be converted to a string if possible. If a mapping dictionary is provided, the value will be converted to the short-form - string that GMT accepts (e.g., mapping PyGMT long-form argument ``"high"`` to GMT's - short-form argument ``"h"``). + string that GMT accepts (e.g., mapping PyGMT's long-form argument ``"high"`` to + GMT's short-form argument ``"h"``). An optional prefix (e.g., `"+o"`) can be added to the beginning of the converted string. @@ -50,7 +51,7 @@ def _to_string( prefix The string to add as a prefix to the returned value. mapping - A mapping dictionary to map long-form arguments to GMT's short-form arguments. + A mapping dictionary to map PyGMT's long-form arguments to GMT's short-form. separator The separator to use if the value is a sequence. size From b76ee579e406c8afd85f6412355ed327a0d5261b Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 10 Jul 2025 09:16:12 +0800 Subject: [PATCH 6/6] Fix one doctest --- pygmt/alias.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/alias.py b/pygmt/alias.py index ceb405cd1cd..3d46dd11b82 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -93,7 +93,7 @@ def _to_string( >>> _to_string("invalid", mapping={"mean": "a", "mad": "d", "full": "g"}) Traceback (most recent call last): ... - pygmt...GMTInvalidInput: Invalid value: 'invalid'. Valid values are: mean, ... + pygmt...GMTValueError: Invalid value: 'invalid'. Expected one of: 'mean', ... >>> _to_string((12, 34), separator="/") '12/34'