i have the following code:
import inspect, importlib
from pydoc import locate
def func(var:str = 'test', var2: int = 1, var3:list[int] = [1,2,3]):
pass
fspec = inspect.getfullargspec(func)
dflts = fspec.defaults
# fspec
spec_dict = {}
for i, (a) in enumerate(fspec.annotations.items()):
type_name = a[1].__name__ if type(a[1]) is type else str(a[1])
spec_dict[a[0]] =(str(dflts[i]), str(type_name))
print(spec_dict)
this produces a dictionary that stores the function parameter name, and a tuple with the value and type, all as strings.
{'var': ('test', 'str'), 'var2': ('1', 'int'), 'var3': ('[1, 2, 3]', 'list[int]')}
Now, from the string values, i'd like to convert the values to their proper type:
for v in spec_dict.values():
val_as_str = v[0]
param_type = locate(v[1])
print(param_type(val_as_str))
This however fails when it gets to 'list[int]', as the call for locate with that string returns None.
What is the proper way to handle this situation, if possible?
(fwiw...reason i am doing this is that the functions, their types and defaults are serialized and deserialized as json strings - so i need to be able to convert back to the function signature)
any adivce/help would be appreciated
CodePudding user response:
typing.get_type_hints
is able to convert forward-ref strings into the actual types. i.e.:
def f(x: "int") -> "str":
pass
print(get_type_hints(f))
Will output:
{'x': <class 'int'>, 'return': <class 'str'>}
Knowing this, you can make a function, set its function signature and annotations with the parsed string and then use get_type_hints
to get the forward-ref types:
def convert_type_string(s):
args = {"a": s}
def tmp(**kwargs):
pass
params = [
inspect.Parameter(
param,
inspect.Parameter.POSITIONAL_OR_KEYWORD,
annotation=type_
) for param, type_ in args.items()
]
tmp.__signature__ = inspect.Signature(params)
tmp.__annotations__ = args
return typing.get_type_hints(tmp)["a"]
And applying this on your class (With an extra 'Path' to prove it works on non builtins)
def func(
var: str = 'test',
var2: int = 1,
var3: list[int] = [1,2,3],
var4: Path = Path("__file__")
):
pass
fspec = inspect.getfullargspec(func)
dflts = fspec.defaults
# fspec
spec_dict = {}
for i, (a) in enumerate(fspec.annotations.items()):
type_name = a[1].__name__ if type(a[1]) is type else str(a[1])
spec_dict[a[0]] =(str(dflts[i]), str(type_name))
print({k: convert_type_string(spec_dict[k][1]) for k in spec_dict})
Outputs:
{'var': <class 'str'>, 'var2': <class 'int'>, 'var3': list[int], 'var4': <class 'pathlib.Path'>}