Skip to content

POC: WIP: New alias system #2949

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
96 changes: 96 additions & 0 deletions pygmt/alias.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""
Alias system to convert PyGMT parameters to GMT options.
"""
import inspect
import warnings
from collections import defaultdict
from typing import NamedTuple

from pygmt.helpers import is_nonstr_iter


class Alias(NamedTuple):
"""
Alias system for mapping a PyGMT parameter to its equivalent GMT option string.

Attributes
----------
name : str
PyGMT parameter name.
flag : str
GMT single-letter option flag.
modifier : str
GMT option modifier. Can be None.
separator : str
Separator to join the iterable argument into a string.
"""

name: str
flag: str
modifier: str
separator: str


def convert_aliases():
"""
Convert PyGMT parameters to GMT options.

The caller function must have the special variable ``_aliases`` defined.

Examples
--------
>>> def module_func(**kwargs):
... _aliases = [
... Alias("par1", "A", "", ""),
... Alias("par2", "B", "", "/"),
... Alias("par3", "C", "", ","),
... Alias("pard1", "D", "", ""),
... Alias("pard2", "D", "+a", ""),
... Alias("pard3", "D", "+b", ","),
... Alias("pard4", "D", "+c", "/"),
... ]
... options = convert_aliases()
... print(options)
>>>
>>> module_func(
... par1="value1",
... par2=[1, 2, 3, 4],
... par3=[0, 1],
... pard1="value2",
... pard2="value3",
... pard3=[1, 2, 3, 4],
... pard4=[1, 2, 3, 4],
... )
{'A': 'value1', 'B': '1/2/3/4', 'C': '0,1', 'D': 'value2+avalue3+b1,2,3,4+c1/2/3/4'}
"""
# Get the local namespace of the caller function
p_locals = inspect.currentframe().f_back.f_locals
p_kwargs = p_locals.get("kwargs", {})
params = p_locals | p_kwargs

# Define a dict to store GMT option flags and arguments
kwdict = defaultdict(str) # default value is an empty string
for alias in p_locals.get("_aliases"):
value = params.get(alias.name)
if is_nonstr_iter(value):
if alias.separator != "":
value = alias.separator.join(str(item) for item in value)
else:
value = ""
elif value in (None, False): # None or False are skipped
continue
elif value is True: # Convert True to an empty string
value = ""
kwdict[alias.flag] += f"{alias.modifier}{value}"

# Handling of deprecated common options.
# timestamp (U) is deprecated since v0.9.0.
if "U" in p_kwargs or "timestamp" in p_kwargs:
msg = (
"Parameters 'U' and 'timestamp' are deprecated since v0.9.0 and will be "
"removed in v0.12.0. Use Figure.timestamp() instead."
)
warnings.warn(msg, category=SyntaxWarning, stacklevel=2)
kwdict["U"] = p_kwargs["U"] if "U" in p_kwargs else p_kwargs["timestamp"]

return dict(kwdict)
46 changes: 28 additions & 18 deletions pygmt/src/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,28 @@
info - Get information about data tables.
"""
import numpy as np
from pygmt.alias import Alias, convert_aliases
from pygmt.clib import Session
from pygmt.helpers import (
GMTTempFile,
build_arg_string,
fmt_docstring,
kwargs_to_strings,
use_alias,
)


@fmt_docstring
@use_alias(
C="per_column",
I="spacing",
T="nearest_multiple",
V="verbose",
a="aspatial",
f="coltypes",
i="incols",
r="registration",
)
@kwargs_to_strings(I="sequence", i="sequence_comma")
def info(data, **kwargs):
def info(
data,
per_column=None,
spacing=None,
nearest_multiple=None,
verbose=None,
aspatial=None,
coltypes=None,
incols=None,
registration=None,
**kwargs,
):
r"""
Get information about data tables.

Expand All @@ -43,8 +42,6 @@ def info(data, **kwargs):

Full option list at :gmt-docs:`gmtinfo.html`

{aliases}

Parameters
----------
data : str, {table-like}
Expand Down Expand Up @@ -79,17 +76,30 @@ def info(data, **kwargs):
- :class:`numpy.ndarray` if either of the above parameters are used.
- str if none of the above parameters are used.
"""
_aliases = [
Alias("per_column", "C", "", ""),
Alias("spacing", "I", "", "/"),
Alias("nearest_multiple", "T", "", ""),
Alias("verbose", "V", "", ""),
Alias("aspatial", "a", "", ""),
Alias("coltypes", "f", "", ""),
Alias("incols", "i", "", ","),
Alias("registration", "r", "", ""),
]

options = convert_aliases()

with Session() as lib:
file_context = lib.virtualfile_from_data(check_kind="vector", data=data)
with GMTTempFile() as tmpfile:
with file_context as fname:
lib.call_module(
module="info",
args=build_arg_string(kwargs, infile=fname, outfile=tmpfile.name),
args=build_arg_string(options, infile=fname, outfile=tmpfile.name),
)
result = tmpfile.read()

if any(kwargs.get(arg) is not None for arg in ["C", "I", "T"]):
if any(arg is not None for arg in (per_column, spacing, nearest_multiple)):
# Converts certain output types into a numpy array
# instead of a raw string that is less useful.
if result.startswith(("-R", "-T")): # e.g. -R0/1/2/3 or -T0/9/1
Expand Down
39 changes: 17 additions & 22 deletions pygmt/src/legend.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,18 @@
legend - Plot a legend.
"""

from pygmt.alias import Alias, convert_aliases
from pygmt.clib import Session
from pygmt.exceptions import GMTInvalidInput
from pygmt.helpers import (
build_arg_string,
data_kind,
fmt_docstring,
kwargs_to_strings,
use_alias,
)


@fmt_docstring
@use_alias(
R="region",
J="projection",
D="position",
F="box",
V="verbose",
c="panel",
p="perspective",
t="transparency",
)
@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence")
def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwargs):
def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwargs): # noqa: ARG001
r"""
Plot legends on maps.

Expand All @@ -37,8 +25,6 @@ def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwarg

Full option list at :gmt-docs:`legend.html`

{aliases}

Parameters
----------
spec : None or str
Expand Down Expand Up @@ -67,12 +53,19 @@ def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwarg
{perspective}
{transparency}
"""
kwargs = self._preprocess(**kwargs)
_aliases = [
Alias("region", "R", "", "/"),
Alias("projection", "J", "", ""),
Alias("position", "D", "", ""),
Alias("box", "F", "", ""),
Alias("verbose", "v", "", ""),
Alias("panel", "c", "", ","),
Alias("perspective", "p", "", "/"),
Alias("transparency", "t", "", ""),
]

if kwargs.get("D") is None:
kwargs["D"] = position
if kwargs.get("F") is None:
kwargs["F"] = box
kwargs = self._preprocess(**kwargs)
options = convert_aliases()

with Session() as lib:
if spec is None:
Expand All @@ -81,4 +74,6 @@ def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwarg
specfile = spec
else:
raise GMTInvalidInput(f"Unrecognized data type: {type(spec)}")
lib.call_module(module="legend", args=build_arg_string(kwargs, infile=specfile))
lib.call_module(
module="legend", args=build_arg_string(options, infile=specfile)
)
Loading