1
+ import ast
1
2
import inspect
2
3
import re
3
4
import sys
5
+ import textwrap
4
6
import traceback
5
7
import types
6
8
import warnings
7
9
from functools import wraps
8
- from typing import List , Optional
10
+ from typing import List , Optional , Tuple
9
11
10
12
from widget_code_input import WidgetCodeInput
11
13
from widget_code_input .utils import (
20
22
class CodeInput (WidgetCodeInput ):
21
23
"""
22
24
Small wrapper around WidgetCodeInput that controls the output
25
+
26
+ :param function: We can automatically parse the function. Note that during
27
+ parsing the source code might be differently formatted and certain
28
+ python functionalities are not formatted. If you notice undesired
29
+ changes by the parsing, please directly specify the function as string
30
+ using the other parameters.
31
+ :param function_name: The name of the function
32
+ :param function_paramaters: The parameters as continuous string as specified in
33
+ the signature of the function. e.g for `foo(x, y = 5)` it should be
34
+ `"x, y = 5"`
35
+ :param docstring: The docstring of the function
36
+ :param function_body: The function definition without indentation
23
37
"""
24
38
25
39
valid_code_themes = ["nord" , "solarizedLight" , "basicLight" ]
@@ -38,13 +52,15 @@ def __init__(
38
52
function .__name__ if function_name is None else function_name
39
53
)
40
54
function_parameters = (
41
- ", " . join ( inspect . getfullargspec ( function ). args )
55
+ self . get_function_parameters ( function )
42
56
if function_parameters is None
43
57
else function_parameters
44
58
)
45
- docstring = inspect . getdoc (function ) if docstring is None else docstring
59
+ docstring = self . get_docstring (function ) if docstring is None else docstring
46
60
function_body = (
47
- self .get_code (function ) if function_body is None else function_body
61
+ self .get_function_body (function )
62
+ if function_body is None
63
+ else function_body
48
64
)
49
65
50
66
# default parameters from WidgetCodeInput
@@ -105,8 +121,68 @@ def function_parameters_name(self) -> List[str]:
105
121
return self .function_parameters .replace ("," , "" ).split (" " )
106
122
107
123
@staticmethod
108
- def get_code (func : types .FunctionType ) -> str :
109
- source_lines , _ = inspect .getsourcelines (func )
124
+ def get_docstring (function : types .FunctionType ) -> str :
125
+ docstring = function .__doc__
126
+ return "" if docstring is None else textwrap .dedent (docstring )
127
+
128
+ @staticmethod
129
+ def _get_function_source_and_def (
130
+ function : types .FunctionType ,
131
+ ) -> Tuple [str , ast .FunctionDef ]:
132
+ function_source = inspect .getsource (function )
133
+ function_source = textwrap .dedent (function_source )
134
+ module = ast .parse (function_source )
135
+ if len (module .body ) != 1 :
136
+ raise ValueError (
137
+ f"Expected code with one function definition but found { module .body } "
138
+ )
139
+ function_definition = module .body [0 ]
140
+ if not isinstance (function_definition , ast .FunctionDef ):
141
+ raise ValueError (
142
+ f"While parsing code found { module .body [0 ]} "
143
+ " but only ast.FunctionDef is supported."
144
+ )
145
+ return function_source , function_definition
146
+
147
+ @staticmethod
148
+ def get_function_parameters (function : types .FunctionType ) -> str :
149
+ function_parameters = []
150
+ function_source , function_definition = CodeInput ._get_function_source_and_def (
151
+ function
152
+ )
153
+ idx_start_defaults = len (function_definition .args .args ) - len (
154
+ function_definition .args .defaults
155
+ )
156
+ for i , arg in enumerate (function_definition .args .args ):
157
+ function_parameter = ast .get_source_segment (function_source , arg )
158
+ # Following PEP 8 in formatting
159
+ if arg .annotation :
160
+ annotation = function_parameter = ast .get_source_segment (
161
+ function_source , arg .annotation
162
+ )
163
+ function_parameter = f"{ arg .arg } : { annotation } "
164
+ else :
165
+ function_parameter = f"{ arg .arg } "
166
+ if i >= idx_start_defaults :
167
+ default_val = ast .get_source_segment (
168
+ function_source ,
169
+ function_definition .args .defaults [i - idx_start_defaults ],
170
+ )
171
+ # Following PEP 8 in formatting
172
+ if arg .annotation :
173
+ function_parameter = f"{ function_parameter } = { default_val } "
174
+ else :
175
+ function_parameter = f"{ function_parameter } ={ default_val } "
176
+ function_parameters .append (function_parameter )
177
+
178
+ if function_definition .args .kwarg is not None :
179
+ function_parameters .append (f"**{ function_definition .args .kwarg .arg } " )
180
+
181
+ return ", " .join (function_parameters )
182
+
183
+ @staticmethod
184
+ def get_function_body (function : types .FunctionType ) -> str :
185
+ source_lines , _ = inspect .getsourcelines (function )
110
186
111
187
found_def = False
112
188
def_index = 0
0 commit comments