@@ -226,7 +226,7 @@ def do_paint(
226226 UnboundCompleter ,
227227)
228228
229- if TYPE_CHECKING :
229+ if TYPE_CHECKING : # pragma: no cover
230230 from .argparse_completer import ArgparseCompleter
231231
232232#: ``nargs`` values accepted by cmd2's patched ``add_argument`` (incl. ranged tuples).
@@ -1745,8 +1745,19 @@ def _resolve_parameters(
17451745 f"with_annotated(base_command=True) requires a '{ constants .NS_ATTR_SUBCOMMAND_FUNC } ' "
17461746 f"parameter in { func .__qualname__ } "
17471747 )
1748+ # Resolve hints only for the parameters that become arguments: the bound first parameter
1749+ # (self/cls), the injected skip_params, and the "return" annotation never become arguments
1750+ ignored = {next (iter (sig .parameters ), None ), "return" , * skip_params }
1751+ ignored .discard (None )
1752+ relevant_annotations = {name : ann for name , ann in getattr (func , "__annotations__" , {}).items () if name not in ignored }
1753+ # Forward references resolve against the *original* function's module during functools.wraps wrapper.
1754+ unwrapped = inspect .unwrap (func )
17481755 try :
1749- hints = get_type_hints (func , include_extras = True )
1756+ hints = get_type_hints (
1757+ types .SimpleNamespace (__annotations__ = relevant_annotations ),
1758+ globalns = getattr (unwrapped , "__globals__" , {}),
1759+ include_extras = True ,
1760+ )
17501761 except (NameError , AttributeError , TypeError ) as exc :
17511762 raise TypeError (
17521763 f"Failed to resolve type hints for { func .__qualname__ } . Ensure all annotations use valid, importable types."
@@ -1986,25 +1997,16 @@ def build_parser_from_function(
19861997 mutually_exclusive_groups = mutually_exclusive_groups ,
19871998 )
19881999
1989- # ``argument_default=argparse.SUPPRESS`` removes an absent argument from the parsed namespace.
1990- # That is safe only for arguments that are always supplied (required) or carry their own default;
1991- # an *omittable* argument with no default (e.g. a ``T | None`` positional -> nargs='?') would be
1992- # dropped when absent, leaving the function without a keyword argument it expects. ``*args`` is
1993- # exempt: the invocation path substitutes an empty tuple for it. Reject the combination here,
1994- # mirroring the per-argument ``default=argparse.SUPPRESS`` rejection.
2000+ # ``argument_default=argparse.SUPPRESS`` drops an absent argument from the parsed namespace.
2001+ # @with_annotated builds the call from the function signature, so every declared parameter is
2002+ # expected at invocation -- an argument vanishing from the namespace can never be valid here.
2003+ # Reject it outright, mirroring the per-argument ``default=argparse.SUPPRESS`` rejection.
19952004 if parser_kwargs .get ("argument_default" ) is argparse .SUPPRESS :
1996- dropped = [
1997- arg .name
1998- for arg in resolved
1999- if arg .default is _UNSET and arg .omittable and not arg .required and not arg .is_variadic
2000- ]
2001- if dropped :
2002- raise TypeError (
2003- f"argument_default=argparse.SUPPRESS is not supported by @with_annotated for { func .__qualname__ } : "
2004- f"it would drop { dropped !r} from the parsed namespace when absent, but the function expects "
2005- f"{ 'them' if len (dropped ) > 1 else 'it' } as a keyword argument. Give each an explicit default or "
2006- f"make it required, or drop argument_default=argparse.SUPPRESS."
2007- )
2005+ raise TypeError (
2006+ f"argument_default=argparse.SUPPRESS is not supported by @with_annotated for { func .__qualname__ } : "
2007+ f"it drops absent arguments from the parsed namespace, but every parameter built from the "
2008+ f"signature is expected at invocation. Drop argument_default=argparse.SUPPRESS."
2009+ )
20082010
20092011 # Build the group lookup (member references already validated by _resolve_parameters).
20102012 target_for , argument_group_for = _build_argument_group_targets (parser , groups = groups )
@@ -2124,7 +2126,7 @@ def handler(self_arg: Any, ns: Any) -> Any:
21242126 filtered = _filtered_namespace_kwargs (ns , accepted = _accepted )
21252127 if constants .NS_ATTR_SUBCOMMAND_FUNC in filtered :
21262128 cmd2_h = filtered [constants .NS_ATTR_SUBCOMMAND_FUNC ]
2127- if isinstance (cmd2_h , functools .partial ) and cmd2_h .func is handler :
2129+ if isinstance (cmd2_h , functools .partial ) and getattr ( cmd2_h .func , "__func__" , cmd2_h . func ) is handler :
21282130 filtered [constants .NS_ATTR_SUBCOMMAND_FUNC ] = None
21292131 return _invoke_command_func (
21302132 func , self_arg , filtered , leading_names = _leading_names , var_positional_name = _var_positional_name
0 commit comments