@@ -641,7 +641,6 @@ def kwargs_to_strings(**conversions):
641
641
642
642
Examples
643
643
--------
644
-
645
644
>>> @kwargs_to_strings(
646
645
... R="sequence", i="sequence_comma", files="sequence_space"
647
646
... )
@@ -691,58 +690,97 @@ def kwargs_to_strings(**conversions):
691
690
... ]
692
691
... )
693
692
{'R': '2005-01-01T08:00:00.000000000/2015-01-01T12:00:00.123456'}
693
+ >>> # Here is a more realistic example
694
+ >>> # See https://github.com/GenericMappingTools/pygmt/issues/2361
695
+ >>> @kwargs_to_strings(
696
+ ... files="sequence_space",
697
+ ... offset="sequence",
698
+ ... R="sequence",
699
+ ... i="sequence_comma",
700
+ ... )
701
+ ... def module(files, offset=("-54p", "-54p"), **kwargs):
702
+ ... "A module that prints the arguments it received"
703
+ ... print(files, end=" ")
704
+ ... print(offset, end=" ")
705
+ ... print("{", end="")
706
+ ... print(
707
+ ... ", ".join(f"'{k}': {repr(kwargs[k])}" for k in sorted(kwargs)),
708
+ ... end="",
709
+ ... )
710
+ ... print("}")
711
+ >>> module(files=["data1.txt", "data2.txt"])
712
+ data1.txt data2.txt -54p/-54p {}
713
+ >>> module(["data1.txt", "data2.txt"])
714
+ data1.txt data2.txt -54p/-54p {}
715
+ >>> module(files=["data1.txt", "data2.txt"], offset=("20p", "20p"))
716
+ data1.txt data2.txt 20p/20p {}
717
+ >>> module(["data1.txt", "data2.txt"], ("20p", "20p"))
718
+ data1.txt data2.txt 20p/20p {}
719
+ >>> module(["data1.txt", "data2.txt"], ("20p", "20p"), R=[1, 2, 3, 4])
720
+ data1.txt data2.txt 20p/20p {'R': '1/2/3/4'}
694
721
"""
695
- valid_conversions = [
696
- "sequence" ,
697
- "sequence_comma" ,
698
- "sequence_plus" ,
699
- "sequence_space" ,
700
- ]
701
-
702
- for arg , fmt in conversions .items ():
703
- if fmt not in valid_conversions :
704
- raise GMTInvalidInput (
705
- f"Invalid conversion type '{ fmt } ' for argument '{ arg } '."
706
- )
707
-
708
722
separators = {
709
723
"sequence" : "/" ,
710
724
"sequence_comma" : "," ,
711
725
"sequence_plus" : "+" ,
712
726
"sequence_space" : " " ,
713
727
}
714
728
729
+ for arg , fmt in conversions .items ():
730
+ if fmt not in separators :
731
+ raise GMTInvalidInput (
732
+ f"Invalid conversion type '{ fmt } ' for argument '{ arg } '."
733
+ )
734
+
715
735
# Make the actual decorator function
716
736
def converter (module_func ):
717
737
"""
718
738
The decorator that creates our new function with the conversions.
719
739
"""
740
+ sig = signature (module_func )
720
741
721
742
@functools .wraps (module_func )
722
743
def new_module (* args , ** kwargs ):
723
744
"""
724
745
New module instance that converts the arguments first.
725
746
"""
747
+ # Inspired by https://stackoverflow.com/a/69170441
748
+ bound = sig .bind (* args , ** kwargs )
749
+ bound .apply_defaults ()
750
+
726
751
for arg , fmt in conversions .items ():
727
- if arg in kwargs :
728
- value = kwargs [arg ]
729
- issequence = fmt in separators
730
- if issequence and is_nonstr_iter (value ):
731
- for index , item in enumerate (value ):
732
- try :
733
- # check if there is a space " " when converting
734
- # a pandas.Timestamp/xr.DataArray to a string.
735
- # If so, use np.datetime_as_string instead.
736
- assert " " not in str (item )
737
- except AssertionError :
738
- # convert datetime-like item to ISO 8601
739
- # string format like YYYY-MM-DDThh:mm:ss.ffffff
740
- value [index ] = np .datetime_as_string (
741
- np .asarray (item , dtype = np .datetime64 )
742
- )
743
- kwargs [arg ] = separators [fmt ].join (f"{ item } " for item in value )
752
+ # The arg may be in args or kwargs
753
+ if arg in bound .arguments :
754
+ value = bound .arguments [arg ]
755
+ elif arg in bound .arguments .get ("kwargs" ):
756
+ value = bound .arguments ["kwargs" ][arg ]
757
+ else :
758
+ continue
759
+
760
+ issequence = fmt in separators
761
+ if issequence and is_nonstr_iter (value ):
762
+ for index , item in enumerate (value ):
763
+ try :
764
+ # Check if there is a space " " when converting
765
+ # a pandas.Timestamp/xr.DataArray to a string.
766
+ # If so, use np.datetime_as_string instead.
767
+ assert " " not in str (item )
768
+ except AssertionError :
769
+ # Convert datetime-like item to ISO 8601
770
+ # string format like YYYY-MM-DDThh:mm:ss.ffffff.
771
+ value [index ] = np .datetime_as_string (
772
+ np .asarray (item , dtype = np .datetime64 )
773
+ )
774
+ newvalue = separators [fmt ].join (f"{ item } " for item in value )
775
+ # Changes in bound.arguments will reflect in bound.args
776
+ # and bound.kwargs.
777
+ if arg in bound .arguments :
778
+ bound .arguments [arg ] = newvalue
779
+ elif arg in bound .arguments .get ("kwargs" ):
780
+ bound .arguments ["kwargs" ][arg ] = newvalue
781
+
744
782
# Execute the original function and return its output
745
- return module_func (* args , ** kwargs )
783
+ return module_func (* bound . args , ** bound . kwargs )
746
784
747
785
return new_module
748
786
0 commit comments