Request: Create a settable.setter_cb callback option
See original GitHub issueI’m writing a somewhat complex application with Cmd2 and I’m loving the ease at which I can add new commands, functionality, and such to it. One hang-up I’m having is with the Settable class, more specifically that it lacks the ability to allow the user to define how a settable parameter is set.
I looked at the code for the onchange_cb() method which allows the programmer to define actions after the settable parameter is given a new value, but it does not allow the user to change the behavior of how the parameter is set. This would be extremely useful, especially in my case where I am trying to make it possible for the user to set the values for a complex object, in this case, a dictionary. In my particular use-case, I created a form-like method for the user to input values into the dictionary which would be validated before being passed back to the settable dictionary.
I know I can get around this by simply writing a dedicated function to set this value as a property of my cmd2 app class, but it feels dirty and unnecessary.
My use case
The way I imagine this working at the user-level is something like this:
class MyApp(cmd2.Cmd):
def __init__(self, my_params, blahblah, *args, **kwargs)
... truncated ...
self.add_settable(cmd2.Settable('dict_param', bool, setter_cb=self._set_dict_param,
description='Input true to change the dict_param'))
... truncated ...
def _set_dict_param(self, param, old, new):
... code for setting the dictionary values ...
return new_dict_param
At the user-layer, setting the value looks something like this:
prompt> set dict_param
dict_param = { 'foo': 'bar', 'bin': 'baz }
prompt> set dict_param true
Set your dict_param values here. Leave empty to end loop:
> foo=baz
> bin=bar
>
dict_param: { 'foo': 'baz', 'bin': 'bar' }
I have a very particular use-case which is a lot more complex than this example, but you get the idea…
What I tried so far on my own:
I took the liberty of modifying the cmd2 source a little bit to see how practical this would be, and it seems quite simple to me, but I think I’m missing something.
I modified the Settable class in cmd2/utils.py like so:
class Settable:
"""Used to configure a cmd2 instance member to be settable via the set command in the CLI"""
def __init__(self, name: str, val_type: Callable, description: str, *,
onchange_cb: Callable[[str, Any, Any], Any] = None,
setter_cb: Callable[[str, Any, Any], Any] = None,
... snip ...
""" Inside the docstring
... snip ...
:param setter_cb: : optional function or method to call which defines how the value of this settable
is altered by the set command. (e.g. setter_cb=self.param_setter)
Cmd.do_set() passes the following 3 arguments to setter_cb:
param_name: str - name of the parameter to change
old_value: Any - the value before being changed
new_value: Any - the value sent by the caller
:return : Any - the value returned by the callback function which updates
the settable value
... snip ...
"""
self.setter_cb = setter_cb
Then I modified cmd2/cmd2.py’s Cmd.do_set() method like so:
def do_set(self, args: argparse.Namespace) -> None:
... snip ...
if args.value:
args.value = utils.strip_quotes(args.value)
# Try to update the settable's value
try:
orig_value = getattr(self, args.param)
if getattr(settable, setter_cb):
setattr(self, args.param, settable.setter_cb(args.param, orig_value, args.value))
else:
setattr(self, args.param, settable.val_type(args.value))
new_value = getattr(self, args.param)
# noinspection PyBroadException
except Exception as e:
err_msg = "Error setting {}: {}".format(args.param, e)
self.perror(err_msg)
return
... snip ....
In effect, my goal was to emulate the same parameters and behavior as the onchange_cb method, except that instead of performing actions after the parameter was set, it would expect a return value which would then set the settable parameter’s value.
I would be happy to submit a PR for this, but this change always results in the following error:
Error setting dict_param: name 'setter_cb' is not defined
Maybe I’m missing something obvious, or python is importing from some hidden version of the library that I haven’t changed and I’m just too dumb to figure out how to bypass that.
Whatever the case, I figure this would be best left up to the maintainers of Cmd2. If this change seems useful/
Issue Analytics
- State:
- Created 3 years ago
- Comments:6 (3 by maintainers)

Top Related StackOverflow Question
The
Settableclass has a member calledval_typewhich is defined as:I believe this already does what you are looking for. Just set
val_typeto your function which prompts the user and return the dictionary when finished. Settingchoicesto true/false will give you tab completion on the set command.Please view
Settable.__init__()for full documentation of all parameters.For the sake of those who run into this in the future, I wanted settable to be able to accept a dictionary, but one that had very specific value requirements. in order to set this dictionary values, I needed to make sure to hold the user’s hand and prevent them from putting in bad/invalid values for the dict.
In this case, this dictionary was a dict with the CVSS (Common Vulnerability Scoring System) thresholds for measuring vulnerabilities in applications, but this could apply to any kind of complex, mutable data type such as records via
NamedTuplesor just simpleLists.Here is what my implementation looked like so others can take advantage of this hidden gem:
Example Run:
Example perror output:
Note: CVSS values are only validi between 0.0 and 10.0
Example set to default