Skip to content

Commit 7637f7f

Browse files
authored
Resolve parameters completely from stubs when inspect.signature fails (#698)
1 parent 295919d commit 7637f7f

File tree

3 files changed

+190
-54
lines changed

3 files changed

+190
-54
lines changed

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Added
2323
<https://github.com/omni-us/jsonargparse/pull/700>`__).
2424
- ``auto_cli`` now supports class ``@property`` (`#701
2525
<https://github.com/omni-us/jsonargparse/pull/701>`__).
26+
- Resolve parameters completely from stubs when ``inspect.signature`` fails
27+
(`#698 <https://github.com/omni-us/jsonargparse/pull/698>`__).
2628

2729
Changed
2830
^^^^^^^

jsonargparse/_parameter_resolvers.py

Lines changed: 93 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
)
2626
from ._optionals import get_annotated_base_type, is_annotated, is_pydantic_model, parse_docs
2727
from ._postponed_annotations import evaluate_postponed_annotations
28-
from ._stubs_resolver import get_stub_types
28+
from ._stubs_resolver import get_arg_type, get_stub_types, get_stubs_resolver
2929
from ._util import (
3030
ClassFromFunctionBase,
3131
get_import_path,
@@ -500,7 +500,10 @@ def get_component_and_parent(
500500
method_or_property = method_or_property.__name__
501501
parent = component = None
502502
if method_or_property:
503-
attr = inspect.getattr_static(get_generic_origin(function_or_class), method_or_property)
503+
try:
504+
attr = inspect.getattr_static(get_generic_origin(function_or_class), method_or_property)
505+
except AttributeError as ex:
506+
raise AttributeError(f"Attribute '{method_or_property}' not found in {function_or_class}") from ex
504507
if is_staticmethod(attr):
505508
component = getattr(function_or_class, method_or_property)
506509
return component, parent, method_or_property
@@ -880,26 +883,6 @@ def get_parameters(self) -> ParamList:
880883
return params
881884

882885

883-
def get_parameters_by_assumptions(
884-
function_or_class: Union[Callable, Type],
885-
method_name: Optional[str] = None,
886-
logger: Union[bool, str, dict, logging.Logger] = True,
887-
) -> ParamList:
888-
component, parent, method_name = get_component_and_parent(function_or_class, method_name)
889-
params, args_idx, kwargs_idx, _, stubs = get_signature_parameters_and_indexes(component, parent, logger)
890-
891-
if parent and (args_idx >= 0 or kwargs_idx >= 0):
892-
with mro_context(parent):
893-
subparams = get_mro_parameters(method_name, get_parameters_by_assumptions, logger)
894-
if subparams:
895-
args, kwargs = split_args_and_kwargs(subparams)
896-
params = replace_args_and_kwargs(params, args, kwargs)
897-
898-
params = replace_args_and_kwargs(params, [], [])
899-
add_stub_types(stubs, params, component)
900-
return params
901-
902-
903886
def get_field_data_pydantic1_model(field, name, doc_params):
904887
default = field.default
905888
if field.required:
@@ -997,6 +980,7 @@ def get_parameters_from_pydantic_or_attrs(
997980

998981
if method_or_property or not (pydantic_support or attrs_support):
999982
return None
983+
1000984
function_or_class = get_unaliased_type(function_or_class)
1001985
fields_iterator = get_field_data = None
1002986
if pydantic_support:
@@ -1039,6 +1023,71 @@ def get_parameters_from_pydantic_or_attrs(
10391023
)
10401024
)
10411025
evaluate_postponed_annotations(params, function_or_class, None, logger)
1026+
1027+
return params
1028+
1029+
1030+
def get_parameters_from_ast(
1031+
function_or_class: Union[Callable, Type],
1032+
method_or_property: Optional[str],
1033+
logger: logging.Logger,
1034+
) -> Optional[ParamList]:
1035+
visitor = ParametersVisitor(function_or_class, method_or_property, logger=logger)
1036+
return visitor.get_parameters()
1037+
1038+
1039+
def get_parameters_from_stubs(
1040+
function_or_class: Union[Callable, Type],
1041+
method_or_property: Optional[str],
1042+
logger: logging.Logger,
1043+
) -> Optional[ParamList]:
1044+
component, parent, _ = get_component_and_parent(function_or_class, method_or_property)
1045+
params = None
1046+
resolver = get_stubs_resolver()
1047+
stub_import = resolver.get_component_imported_info(component, parent)
1048+
if stub_import:
1049+
origin = get_parameter_origins(component, parent)
1050+
aliases = resolver.get_aliases(stub_import)
1051+
arg_asts = stub_import.info.ast.args.args + stub_import.info.ast.args.kwonlyargs
1052+
params = []
1053+
for num, arg_ast in enumerate(arg_asts):
1054+
if parent and num == 0:
1055+
continue
1056+
try:
1057+
annotation = get_arg_type(arg_ast.annotation, aliases)
1058+
except Exception:
1059+
annotation = inspect._empty
1060+
params.append(
1061+
ParamData(
1062+
name=arg_ast.arg,
1063+
annotation=annotation,
1064+
default=UnknownDefault("stubs-resolver"),
1065+
kind=inspect._ParameterKind.KEYWORD_ONLY,
1066+
component=component,
1067+
parent=parent,
1068+
origin=origin,
1069+
)
1070+
)
1071+
return params
1072+
1073+
1074+
def get_parameters_by_assumptions(
1075+
function_or_class: Union[Callable, Type],
1076+
method_name: Optional[str],
1077+
logger: logging.Logger,
1078+
) -> ParamList:
1079+
component, parent, method_name = get_component_and_parent(function_or_class, method_name)
1080+
params, args_idx, kwargs_idx, _, stubs = get_signature_parameters_and_indexes(component, parent, logger)
1081+
1082+
if parent and (args_idx >= 0 or kwargs_idx >= 0):
1083+
with mro_context(parent):
1084+
subparams = get_mro_parameters(method_name, get_parameters_by_assumptions, logger)
1085+
if subparams:
1086+
args, kwargs = split_args_and_kwargs(subparams)
1087+
params = replace_args_and_kwargs(params, args, kwargs)
1088+
1089+
params = replace_args_and_kwargs(params, [], [])
1090+
add_stub_types(stubs, params, component)
10421091
return params
10431092

10441093

@@ -1047,7 +1096,7 @@ def get_signature_parameters(
10471096
method_or_property: Optional[str] = None,
10481097
logger: Union[bool, str, dict, logging.Logger] = True,
10491098
) -> ParamList:
1050-
"""Get parameters by inspecting ASTs or by inheritance assumptions if source not available.
1099+
"""Get parameters by inspecting ASTs, stubs or by inheritance assumptions.
10511100
10521101
In contrast to inspect.signature, it follows the use of *args and **kwargs
10531102
attempting to find all accepted named parameters.
@@ -1060,22 +1109,26 @@ def get_signature_parameters(
10601109
the parameters for ``__init__``.
10611110
logger: Useful for debugging. Only logs at ``DEBUG`` level.
10621111
"""
1112+
get_component_and_parent(function_or_class, method_or_property) # verify input
10631113
logger = parse_logger(logger, "get_signature_parameters")
1064-
try:
1065-
params = get_parameters_from_pydantic_or_attrs(function_or_class, method_or_property, logger)
1114+
params = None
1115+
for get_parameters in [
1116+
get_parameters_from_pydantic_or_attrs,
1117+
get_parameters_from_ast,
1118+
get_parameters_from_stubs,
1119+
get_parameters_by_assumptions,
1120+
]:
1121+
try:
1122+
params = get_parameters(function_or_class, method_or_property, logger)
1123+
except Exception as ex:
1124+
logger.debug(
1125+
"%s failed: function_or_class=%s, method_or_property=%s: %s",
1126+
get_parameters.__name__,
1127+
function_or_class,
1128+
method_or_property,
1129+
ex,
1130+
exc_info=ex,
1131+
)
10661132
if params is not None:
1067-
return params
1068-
visitor = ParametersVisitor(function_or_class, method_or_property, logger=logger)
1069-
return visitor.get_parameters()
1070-
except Exception as ex:
1071-
cause = "Source not available"
1072-
exc_info = None
1073-
if not isinstance(ex, SourceNotAvailable):
1074-
cause = "Problems with AST resolving"
1075-
exc_info = ex
1076-
logger.debug(
1077-
f"{cause}, falling back to parameters by assumptions: function_or_class={function_or_class} "
1078-
f"method_or_property={method_or_property}: {ex}",
1079-
exc_info=exc_info,
1080-
)
1081-
return get_parameters_by_assumptions(function_or_class, method_or_property, logger)
1133+
break
1134+
return params or []

0 commit comments

Comments
 (0)