From ac24ec9e889b23f01f06f4ad4872a8f013a904a2 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Mon, 22 Nov 2021 10:49:39 -0500 Subject: [PATCH] Fixed issue where a ns_provider could be passed None instead of its correct cmd2.Cmd or CommandSet value. --- CHANGELOG.md | 4 ++ cmd2/cmd2.py | 1 - cmd2/decorators.py | 8 ++-- .../test_commandset/test_commandset.py | 43 +++++++++++++++++++ 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e32dd4afb..0f231fecc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.3.2 (November 22, 2021) +* Bug Fixes + * Fixed issue where a `ns_provider` could be passed `None` instead of its correct `cmd2.Cmd` or `CommandSet` value. + ## 2.3.1 (November 18, 2021) * Bug Fixes * Fixed issue introduced in 2.3.0 with `AlternatingTable`, `BorderedTable`, and `SimpleTable` that caused diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 09b3fe431..b36d40bca 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -5418,7 +5418,6 @@ def _resolve_func_self( :param cmd_support_func: command support function. This could be a completer or namespace provider :param cmd_self: The `self` associated with the command or subcommand - :return: """ # figure out what class the command support function was defined in func_class: Optional[Type[Any]] = get_defining_class(cmd_support_func) diff --git a/cmd2/decorators.py b/cmd2/decorators.py index 1ff0bdbef..c06142fb3 100644 --- a/cmd2/decorators.py +++ b/cmd2/decorators.py @@ -276,9 +276,9 @@ def with_argparser( with the given instance of argparse.ArgumentParser. :param parser: unique instance of ArgumentParser - :param ns_provider: An optional function that accepts a cmd2.Cmd object as an argument and returns an - argparse.Namespace. This is useful if the Namespace needs to be prepopulated with - state data that affects parsing. + :param ns_provider: An optional function that accepts a cmd2.Cmd or cmd2.CommandSet object as an argument and returns an + argparse.Namespace. This is useful if the Namespace needs to be prepopulated with state data that + affects parsing. :param preserve_quotes: if ``True``, then arguments passed to argparse maintain their quotes :param with_unknown_args: if true, then capture unknown args :return: function that gets passed argparse-parsed args in a ``Namespace`` @@ -351,7 +351,7 @@ def cmd_wrapper(*args: Any, **kwargs: Dict[str, Any]) -> Optional[bool]: # functions are registered with the command argparser before anything is instantiated, we # need to find an instance at runtime that matches the types during declaration provider_self = cmd2_app._resolve_func_self(ns_provider, args[0]) - namespace = ns_provider(provider_self if not None else cmd2_app) + namespace = ns_provider(provider_self if provider_self is not None else cmd2_app) try: new_args: Union[Tuple[argparse.Namespace], Tuple[argparse.Namespace, List[str]]] diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py index e9fba1148..522a99e1e 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -1104,3 +1104,46 @@ def __init__(self): settable_attrib_name='some_value', ) ) + + +class NsProviderSet(cmd2.CommandSet): + # CommandSet which implements a namespace provider + def __init__(self, dummy): + # Use dummy argument so this won't be autoloaded by other tests + super(NsProviderSet, self).__init__() + + def ns_provider(self) -> argparse.Namespace: + ns = argparse.Namespace() + # Save what was passed as self from with_argparser(). + ns.self = self + return ns + + +class NsProviderApp(cmd2.Cmd): + # Used to test namespace providers in CommandSets + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + super(NsProviderApp, self).__init__(*args, **kwargs) + + @cmd2.with_argparser(cmd2.Cmd2ArgumentParser(), ns_provider=NsProviderSet.ns_provider) + def do_test_ns(self, args: argparse.Namespace) -> None: + # Save args.self so the unit tests can read it. + self.last_result = args.self + + +def test_ns_provider(): + """This exercises code in with_argparser() decorator that calls namespace providers""" + ns_provider_set = NsProviderSet(1) + app = NsProviderApp(auto_load_commands=False) + + # First test the case in which a namespace provider function resides in a CommandSet class which is registered. + # with_argparser() will pass the CommandSet instance to the ns_provider() function. + app.register_command_set(ns_provider_set) + run_cmd(app, "test_ns") + assert app.last_result == ns_provider_set + + # Now test the case in which a namespace provider function resides in a CommandSet class which is not registered. + # with_argparser() will receive None from cmd2.Cmd._resolve_func_self() and therefore pass app as self to ns_provider(). + app.unregister_command_set(ns_provider_set) + run_cmd(app, "test_ns") + assert app.last_result == app