Skip to content

Commit 8540a07

Browse files
committed
Let kwargs_to_strings work with default values and postional arguments
1 parent 1ef9fb4 commit 8540a07

File tree

1 file changed

+65
-32
lines changed

1 file changed

+65
-32
lines changed

pygmt/helpers/decorators.py

+65-32
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,6 @@ def kwargs_to_strings(**conversions):
636636
637637
Examples
638638
--------
639-
640639
>>> @kwargs_to_strings(
641640
... R="sequence", i="sequence_comma", files="sequence_space"
642641
... )
@@ -686,58 +685,92 @@ def kwargs_to_strings(**conversions):
686685
... ]
687686
... )
688687
{'R': '2005-01-01T08:00:00.000000000/2015-01-01T12:00:00.123456'}
688+
>>> # Here is a more realistic example
689+
>>> # See https://github.com/GenericMappingTools/pygmt/issues/2361
690+
>>> @kwargs_to_strings(
691+
... files="sequence_space",
692+
... offset="sequence",
693+
... R="sequence",
694+
... i="sequence_comma",
695+
... )
696+
... def module(files, offset=("-54p", "-54p"), **kwargs):
697+
... "A module that prints the arguments it received"
698+
... print(files, end=" ")
699+
... print(offset, end=" ")
700+
... print("{", end="")
701+
... print(
702+
... ", ".join(f"'{k}': {repr(kwargs[k])}" for k in sorted(kwargs)),
703+
... end="",
704+
... )
705+
... print("}")
706+
>>> module(files=["data1.txt", "data2.txt"])
707+
data1.txt data2.txt -54p/-54p {}
708+
>>> module(["data1.txt", "data2.txt"])
709+
data1.txt data2.txt -54p/-54p {}
710+
>>> module(files=["data1.txt", "data2.txt"], offset=("20p", "20p"))
711+
data1.txt data2.txt 20p/20p {}
712+
>>> module(["data1.txt", "data2.txt"], ("20p", "20p"))
713+
data1.txt data2.txt 20p/20p {}
714+
>>> module(["data1.txt", "data2.txt"], ("20p", "20p"), R=[1, 2, 3, 4])
715+
data1.txt data2.txt 20p/20p {'R': '1/2/3/4'}
689716
"""
690-
valid_conversions = [
691-
"sequence",
692-
"sequence_comma",
693-
"sequence_plus",
694-
"sequence_space",
695-
]
696-
697-
for arg, fmt in conversions.items():
698-
if fmt not in valid_conversions:
699-
raise GMTInvalidInput(
700-
f"Invalid conversion type '{fmt}' for argument '{arg}'."
701-
)
702-
703717
separators = {
704718
"sequence": "/",
705719
"sequence_comma": ",",
706720
"sequence_plus": "+",
707721
"sequence_space": " ",
708722
}
709723

724+
for arg, fmt in conversions.items():
725+
if fmt not in separators:
726+
raise GMTInvalidInput(
727+
f"Invalid conversion type '{fmt}' for argument '{arg}'."
728+
)
729+
710730
# Make the actual decorator function
711731
def converter(module_func):
712732
"""
713733
The decorator that creates our new function with the conversions.
714734
"""
735+
sig = signature(module_func)
715736

716737
@functools.wraps(module_func)
717738
def new_module(*args, **kwargs):
718739
"""
719740
New module instance that converts the arguments first.
720741
"""
742+
# Inspired by https://stackoverflow.com/a/69170441
743+
bound = sig.bind(*args, **kwargs)
744+
bound.apply_defaults()
745+
721746
for arg, fmt in conversions.items():
722-
if arg in kwargs:
723-
value = kwargs[arg]
724-
issequence = fmt in separators
725-
if issequence and is_nonstr_iter(value):
726-
for index, item in enumerate(value):
727-
try:
728-
# check if there is a space " " when converting
729-
# a pandas.Timestamp/xr.DataArray to a string.
730-
# If so, use np.datetime_as_string instead.
731-
assert " " not in str(item)
732-
except AssertionError:
733-
# convert datetime-like item to ISO 8601
734-
# string format like YYYY-MM-DDThh:mm:ss.ffffff
735-
value[index] = np.datetime_as_string(
736-
np.asarray(item, dtype=np.datetime64)
737-
)
738-
kwargs[arg] = separators[fmt].join(f"{item}" for item in value)
747+
# The arg may be in args or kwargs
748+
value = bound.arguments.get(arg, bound.arguments.get("kwargs").get(arg))
749+
750+
issequence = fmt in separators
751+
if issequence and is_nonstr_iter(value):
752+
for index, item in enumerate(value):
753+
try:
754+
# check if there is a space " " when converting
755+
# a pandas.Timestamp/xr.DataArray to a string.
756+
# If so, use np.datetime_as_string instead.
757+
assert " " not in str(item)
758+
except AssertionError:
759+
# convert datetime-like item to ISO 8601
760+
# string format like YYYY-MM-DDThh:mm:ss.ffffff
761+
value[index] = np.datetime_as_string(
762+
np.asarray(item, dtype=np.datetime64)
763+
)
764+
newvalue = separators[fmt].join(f"{item}" for item in value)
765+
# Changes in bound.arguments will reflect in bound.args
766+
# and bound.kwargs.
767+
if arg in bound.arguments:
768+
bound.arguments[arg] = newvalue
769+
elif arg in bound.arguments["kwargs"]:
770+
bound.arguments["kwargs"][arg] = newvalue
771+
739772
# Execute the original function and return its output
740-
return module_func(*args, **kwargs)
773+
return module_func(*bound.args, **bound.kwargs)
741774

742775
return new_module
743776

0 commit comments

Comments
 (0)