-
Notifications
You must be signed in to change notification settings - Fork 124
Description
I'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_paramAt 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_cbThen 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/