@@ -280,7 +280,7 @@ def arg_decorator(func: ArgparseCommandFunc[CmdOrSet]) -> RawCommandFuncOptional
280280 """
281281
282282 @functools .wraps (func )
283- def cmd_wrapper (* args : Any , ** kwargs : dict [ str , Any ] ) -> bool | None :
283+ def cmd_wrapper (* args : Any , ** kwargs : Any ) -> bool | None :
284284 """Command function wrapper which translates command line into argparse Namespace and call actual command function.
285285
286286 :param args: All positional arguments to this function. We're expecting there to be:
@@ -349,72 +349,101 @@ def cmd_wrapper(*args: Any, **kwargs: dict[str, Any]) -> bool | None:
349349def with_annotated (
350350 func : Callable [..., Any ] | None = None ,
351351 * ,
352+ ns_provider : Callable [..., argparse .Namespace ] | None = None ,
352353 preserve_quotes : bool = False ,
353354 with_unknown_args : bool = False ,
354355) -> Any :
355356 """Decorate a ``do_*`` method to build its argparse parser from type annotations.
356357
357- Can be used bare or with keyword arguments::
358+ :param func: the command function (when used without parentheses)
359+ :param ns_provider: optional callable returning a prepopulated argparse.Namespace
360+ :param preserve_quotes: if True, preserve quotes in arguments
361+ :param with_unknown_args: if True, capture unknown args (passed as extra kwarg ``_unknown``)
362+
358363
364+ Example:
365+ ```py
366+ class MyApp(cmd2.Cmd):
359367 @with_annotated
360368 def do_greet(self, name: str, count: int = 1): ...
361369
362370 @with_annotated(preserve_quotes=True)
363371 def do_raw(self, text: str): ...
364372
365- :param func: the command function (when used without parentheses)
366- :param preserve_quotes: if True, preserve quotes in arguments
367- :param with_unknown_args: if True, capture unknown args (passed as extra kwarg ``_unknown``)
368373 """
369374 from .annotated import build_parser_from_function
370375
371- def decorator (fn : Callable [..., Any ]) -> Callable [..., Any ]:
376+ def decorator (func : Callable [..., Any ]) -> Callable [..., Any ]:
372377 if with_unknown_args :
373- unknown_param = inspect .signature (fn ).parameters .get ('_unknown' )
378+ unknown_param = inspect .signature (func ).parameters .get ('_unknown' )
374379 if unknown_param is None :
375380 raise TypeError ('with_annotated(with_unknown_args=True) requires a parameter named _unknown' )
376381 if unknown_param .kind is inspect .Parameter .POSITIONAL_ONLY :
377382 raise TypeError ('Parameter _unknown must be keyword-compatible when with_unknown_args=True' )
378383
379- command_name = fn .__name__ [len (constants .COMMAND_FUNC_PREFIX ) :]
384+ command_name = func .__name__ [len (constants .COMMAND_FUNC_PREFIX ) :]
380385
381- @functools .wraps (fn )
382- def cmd_wrapper (* args : Any , ** _kwargs : Any ) -> bool | None :
386+ @functools .wraps (func )
387+ def cmd_wrapper (* args : Any , ** kwargs : Any ) -> bool | None :
383388 cmd2_app , statement_arg = _parse_positionals (args )
384389 owner = args [0 ] # Cmd or CommandSet instance
385- _statement , parsed_arglist = cmd2_app .statement_parser .get_command_arg_list (
390+ statement , parsed_arglist = cmd2_app .statement_parser .get_command_arg_list (
386391 command_name , statement_arg , preserve_quotes
387392 )
388393
389394 arg_parser = cmd2_app ._command_parsers .get (cmd_wrapper )
390395 if arg_parser is None :
391396 raise ValueError (f'No argument parser found for { command_name } ' )
392397
398+ # Resolve namespace provider (same logic as with_argparser)
399+ if ns_provider is None :
400+ namespace = None
401+ else :
402+ provider_self = cmd2_app ._resolve_func_self (ns_provider , args [0 ])
403+ namespace = ns_provider (provider_self if provider_self is not None else cmd2_app )
404+
393405 try :
394406 if with_unknown_args :
395- ns , unknown = arg_parser .parse_known_args (parsed_arglist )
407+ ns , unknown = arg_parser .parse_known_args (parsed_arglist , namespace )
396408 else :
397- ns = arg_parser .parse_args (parsed_arglist )
409+ ns = arg_parser .parse_args (parsed_arglist , namespace )
398410 unknown = None
399411 except SystemExit as exc :
400412 raise Cmd2ArgparseError from exc
401413
402- # Unpack Namespace into function kwargs
414+ # Unpack Namespace into function kwargs, filtering cmd2 internals
403415 func_kwargs : dict [str , Any ] = {}
404416 for key , value in vars (ns ).items ():
405417 if key .startswith ('cmd2_' ) or key == constants .NS_ATTR_SUBCMD_HANDLER :
406418 continue
407419 func_kwargs [key ] = value
408420
421+ # Attach statement for commands that need it (access via cmd2_statement kwarg)
422+ func_kwargs ['cmd2_statement' ] = Cmd2AttributeWrapper (statement )
423+
424+ # Attach subcmd handler wrapper
425+ handler = getattr (ns , constants .NS_ATTR_SUBCMD_HANDLER , None )
426+ func_kwargs ['cmd2_handler' ] = Cmd2AttributeWrapper (handler )
427+
409428 if with_unknown_args :
410429 func_kwargs ['_unknown' ] = unknown
411430
412- result : bool | None = fn (owner , ** func_kwargs )
431+ # Only pass kwargs the function actually accepts
432+ sig = inspect .signature (func )
433+ accepted = set (sig .parameters .keys ()) - {'self' }
434+ filtered_kwargs = {k : v for k , v in func_kwargs .items () if k in accepted }
435+
436+ # Explicit kwargs provided by direct wrapper invocation should override
437+ # parser-derived values.
438+ for key in kwargs :
439+ filtered_kwargs .pop (key , None )
440+
441+ result : bool | None = func (owner , ** filtered_kwargs , ** kwargs )
413442 return result
414443
415444 # Store a parser-builder callable — _CommandParsers._build_parser()
416445 # already handles callables by calling them with no arguments.
417- setattr (cmd_wrapper , constants .CMD_ATTR_ARGPARSER , lambda : build_parser_from_function (fn ))
446+ setattr (cmd_wrapper , constants .CMD_ATTR_ARGPARSER , lambda : build_parser_from_function (func ))
418447 setattr (cmd_wrapper , constants .CMD_ATTR_PRESERVE_QUOTES , preserve_quotes )
419448
420449 return cmd_wrapper
0 commit comments