Skip to content

Commit

Permalink
Fixed issue where a ns_provider could be passed None instead of its c…
Browse files Browse the repository at this point in the history
…orrect cmd2.Cmd or CommandSet value.
  • Loading branch information
kmvanbrunt committed Nov 22, 2021
1 parent 087c206 commit 8e9a21c
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 5 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 0 additions & 1 deletion cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions cmd2/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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``
Expand Down Expand Up @@ -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]]]
Expand Down
43 changes: 43 additions & 0 deletions tests_isolated/test_commandset/test_commandset.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 8e9a21c

Please sign in to comment.