1+ import ast
12import inspect
23import re
34import sys
5+ import textwrap
46import traceback
57import types
68import warnings
79from functools import wraps
8- from typing import List , Optional
10+ from typing import List , Optional , Tuple
911
1012from widget_code_input import WidgetCodeInput
1113from widget_code_input .utils import (
2022class CodeInput (WidgetCodeInput ):
2123 """
2224 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
2337 """
2438
2539 valid_code_themes = ["nord" , "solarizedLight" , "basicLight" ]
@@ -38,13 +52,15 @@ def __init__(
3852 function .__name__ if function_name is None else function_name
3953 )
4054 function_parameters = (
41- ", " . join ( inspect . getfullargspec ( function ). args )
55+ self . get_function_parameters ( function )
4256 if function_parameters is None
4357 else function_parameters
4458 )
45- docstring = inspect . getdoc (function ) if docstring is None else docstring
59+ docstring = self . get_docstring (function ) if docstring is None else docstring
4660 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
4864 )
4965
5066 # default parameters from WidgetCodeInput
@@ -105,8 +121,68 @@ def function_parameters_name(self) -> List[str]:
105121 return self .function_parameters .replace ("," , "" ).split (" " )
106122
107123 @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 )
110186
111187 found_def = False
112188 def_index = 0
0 commit comments