summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Van Brunt <kmvanbrunt@gmail.com>2019-08-05 17:27:23 -0400
committerKevin Van Brunt <kmvanbrunt@gmail.com>2019-08-05 17:27:23 -0400
commit978dcbc027a21d2ad152ac2c4b9f761f8e769fbb (patch)
tree8c00b2bcae70fb28b300b50d9f6b67aba2f6fbff
parent4aa8370092d1aba5edaf77bf9a867b23adf4aa97 (diff)
downloadcmd2-git-978dcbc027a21d2ad152ac2c4b9f761f8e769fbb.tar.gz
Added functions to manually add choice providing functions to an argparse action.
-rw-r--r--cmd2/argparse_custom.py88
-rw-r--r--tests/test_argparse_custom.py25
2 files changed, 85 insertions, 28 deletions
diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py
index 5432314b..2a7be287 100644
--- a/cmd2/argparse_custom.py
+++ b/cmd2/argparse_custom.py
@@ -94,6 +94,14 @@ Tab Completion:
as dynamic. Therefore it is up to the developer to validate if the user has typed an acceptable value for these
arguments.
+ The following functions exist in cases where you may want to manually add choice providing function/methods to
+ an existing argparse action. For instance, in __init__() of a custom action class.
+
+ set_choices_function(action, func)
+ set_choices_method(action, method)
+ set_completer_function(action, func)
+ set_completer_method(action, method)
+
CompletionItem Class:
This class was added to help in cases where uninformative data is being tab completed. For instance,
tab completing ID numbers isn't very helpful to a user without context. Returning a list of CompletionItems
@@ -223,6 +231,9 @@ class CompletionItem(str):
self.description = desc
+############################################################################################################
+# Class and functions related to ChoicesCallable
+############################################################################################################
class ChoicesCallable:
"""
Enables using a callable as the choices provider for an argparse argument.
@@ -241,6 +252,48 @@ class ChoicesCallable:
self.to_call = to_call
+def _set_choices_callable(action: argparse.Action, choices_callable: ChoicesCallable) -> None:
+ """
+ Set the choices_callable attribute of an argparse Action
+ :param action: action being edited
+ :param choices_callable: the ChoicesCallable instance to use
+ :raises: TypeError if used on incompatible action type
+ """
+ # Verify consistent use of parameters
+ if action.choices is not None:
+ err_msg = ("None of the following parameters can be used alongside a choices parameter:\n"
+ "choices_function, choices_method, completer_function, completer_method")
+ raise (TypeError(err_msg))
+ elif action.nargs == 0:
+ err_msg = ("None of the following parameters can be used on an action that takes no arguments:\n"
+ "choices_function, choices_method, completer_function, completer_method")
+ raise (TypeError(err_msg))
+
+ setattr(action, ATTR_CHOICES_CALLABLE, choices_callable)
+
+
+def set_choices_function(action: argparse.Action, choices_function: Callable[[], Iterable[Any]]) -> None:
+ """Set choices_function on an argparse action"""
+ _set_choices_callable(action, ChoicesCallable(is_method=False, is_completer=False, to_call=choices_function))
+
+
+def set_choices_method(action: argparse.Action, choices_method: Callable[[Any], Iterable[Any]]) -> None:
+ """Set choices_method on an argparse action"""
+ _set_choices_callable(action, ChoicesCallable(is_method=True, is_completer=False, to_call=choices_method))
+
+
+def set_completer_function(action: argparse.Action,
+ completer_function: Callable[[str, str, int, int], List[str]]) -> None:
+ """Set completer_function on an argparse action"""
+ _set_choices_callable(action, ChoicesCallable(is_method=False, is_completer=True, to_call=completer_function))
+
+
+def set_completer_method(action: argparse.Action,
+ completer_method: Callable[[Any, str, str, int, int], List[str]]) -> None:
+ """Set completer_method on an argparse action"""
+ _set_choices_callable(action, ChoicesCallable(is_method=True, is_completer=True, to_call=completer_method))
+
+
############################################################################################################
# Patch _ActionsContainer.add_argument with our wrapper to support more arguments
############################################################################################################
@@ -291,7 +344,17 @@ def _add_argument_wrapper(self, *args,
See the header of this file for more information
:return: the created argument action
+ :raises ValueError on incorrect parameter usage
"""
+ # Verify consistent use of arguments
+ choices_callables = [choices_function, choices_method, completer_function, completer_method]
+ num_params_set = len(choices_callables) - choices_callables.count(None)
+
+ if num_params_set > 1:
+ err_msg = ("Only one of the following parameters may be used at a time:\n"
+ "choices_function, choices_method, completer_function, completer_method")
+ raise (ValueError(err_msg))
+
# Pre-process special ranged nargs
nargs_range = None
@@ -345,34 +408,17 @@ def _add_argument_wrapper(self, *args,
# Create the argument using the original add_argument function
new_arg = orig_actions_container_add_argument(self, *args, **kwargs)
- # Verify consistent use of arguments
- choices_params = [new_arg.choices, choices_function, choices_method, completer_function, completer_method]
- num_params_set = len(choices_params) - choices_params.count(None)
-
- if num_params_set > 1:
- err_msg = ("Only one of the following parameters may be used at a time:\n"
- "choices, choices_function, choices_method, completer_function, completer_method")
- raise (ValueError(err_msg))
- elif num_params_set > 0 and new_arg.nargs == 0:
- err_msg = ("None of the following parameters can be used for this type of action:\n"
- "choices, choices_function, choices_method, completer_function, completer_method")
- raise (TypeError(err_msg))
-
# Set the custom attributes
setattr(new_arg, ATTR_NARGS_RANGE, nargs_range)
if choices_function:
- setattr(new_arg, ATTR_CHOICES_CALLABLE,
- ChoicesCallable(is_method=False, is_completer=False, to_call=choices_function))
+ set_choices_function(new_arg, choices_function)
elif choices_method:
- setattr(new_arg, ATTR_CHOICES_CALLABLE,
- ChoicesCallable(is_method=True, is_completer=False, to_call=choices_method))
+ set_choices_method(new_arg, choices_method)
elif completer_function:
- setattr(new_arg, ATTR_CHOICES_CALLABLE,
- ChoicesCallable(is_method=False, is_completer=True, to_call=completer_function))
+ set_completer_function(new_arg, completer_function)
elif completer_method:
- setattr(new_arg, ATTR_CHOICES_CALLABLE,
- ChoicesCallable(is_method=True, is_completer=True, to_call=completer_method))
+ set_completer_method(new_arg, completer_method)
setattr(new_arg, ATTR_SUPPRESS_TAB_HINT, suppress_tab_hint)
setattr(new_arg, ATTR_DESCRIPTIVE_COMPLETION_HEADER, descriptive_header)
diff --git a/tests/test_argparse_custom.py b/tests/test_argparse_custom.py
index f5d45fd1..99afe2dd 100644
--- a/tests/test_argparse_custom.py
+++ b/tests/test_argparse_custom.py
@@ -40,17 +40,15 @@ def fake_func():
@pytest.mark.parametrize('kwargs, is_valid', [
- ({'choices': []}, True),
({'choices_function': fake_func}, True),
({'choices_method': fake_func}, True),
({'completer_function': fake_func}, True),
({'completer_method': fake_func}, True),
- ({'choices': [], 'choices_function': fake_func}, False),
- ({'choices': [], 'choices_method': fake_func}, False),
+ ({'choices_function': fake_func, 'choices_method': fake_func}, False),
({'choices_method': fake_func, 'completer_function': fake_func}, False),
- ({'choices_method': fake_func, 'completer_method': fake_func}, False),
+ ({'completer_function': fake_func, 'completer_method': fake_func}, False),
])
-def test_apcustom_choices_params_count(kwargs, is_valid):
+def test_apcustom_choices_callable_count(kwargs, is_valid):
parser = Cmd2ArgumentParser(prog='test')
try:
parser.add_argument('name', **kwargs)
@@ -66,11 +64,24 @@ def test_apcustom_choices_params_count(kwargs, is_valid):
({'completer_function': fake_func}),
({'completer_method': fake_func})
])
-def test_apcustom_no_choices_when_nargs_is_0(kwargs):
+def test_apcustom_no_choices_callables_alongside_choices(kwargs):
+ with pytest.raises(TypeError) as excinfo:
+ parser = Cmd2ArgumentParser(prog='test')
+ parser.add_argument('name', choices=['my', 'choices', 'list'], **kwargs)
+ assert 'None of the following parameters can be used alongside a choices parameter' in str(excinfo.value)
+
+
+@pytest.mark.parametrize('kwargs', [
+ ({'choices_function': fake_func}),
+ ({'choices_method': fake_func}),
+ ({'completer_function': fake_func}),
+ ({'completer_method': fake_func})
+])
+def test_apcustom_no_choices_callables_when_nargs_is_0(kwargs):
with pytest.raises(TypeError) as excinfo:
parser = Cmd2ArgumentParser(prog='test')
parser.add_argument('name', action='store_true', **kwargs)
- assert 'None of the following parameters can be used' in str(excinfo.value)
+ assert 'None of the following parameters can be used on an action that takes no arguments' in str(excinfo.value)
def test_apcustom_usage():