@@ -636,7 +636,6 @@ def kwargs_to_strings(**conversions):
636
636
637
637
Examples
638
638
--------
639
-
640
639
>>> @kwargs_to_strings(
641
640
... R="sequence", i="sequence_comma", files="sequence_space"
642
641
... )
@@ -686,58 +685,92 @@ def kwargs_to_strings(**conversions):
686
685
... ]
687
686
... )
688
687
{'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'}
689
716
"""
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
-
703
717
separators = {
704
718
"sequence" : "/" ,
705
719
"sequence_comma" : "," ,
706
720
"sequence_plus" : "+" ,
707
721
"sequence_space" : " " ,
708
722
}
709
723
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
+
710
730
# Make the actual decorator function
711
731
def converter (module_func ):
712
732
"""
713
733
The decorator that creates our new function with the conversions.
714
734
"""
735
+ sig = signature (module_func )
715
736
716
737
@functools .wraps (module_func )
717
738
def new_module (* args , ** kwargs ):
718
739
"""
719
740
New module instance that converts the arguments first.
720
741
"""
742
+ # Inspired by https://stackoverflow.com/a/69170441
743
+ bound = sig .bind (* args , ** kwargs )
744
+ bound .apply_defaults ()
745
+
721
746
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
+
739
772
# Execute the original function and return its output
740
- return module_func (* args , ** kwargs )
773
+ return module_func (* bound . args , ** bound . kwargs )
741
774
742
775
return new_module
743
776
0 commit comments