I have a Python command line application that needs to ask the user for confirmation at some point. I want to add a --assume-yes
/--assume-no
command line flag to skip the confirmation. I have several ideas how to handle this flag and am not sure which is good/best/terrible.
Initial code
# main.py, this file is simplified, "work" knows nothing about the structure of "args"
import cli, interactive
def work(foo):
if interactive.confirm(f"do you want {foo}"):
print("ok, I will do it")
else:
print("skipping")
if __name__ == "__main__":
args = cli.parse()
work(args.foo)
# cli.py, this is simplified
import argparse
def parse():
parser = argparse.ArgumentParser()
parser.add_argument("--foo")
parser.add_argument("--assume-yes", action="store_true", dest="assume",
default=None)
parser.add_argument("--assume-no", action="store_false", dest="assume")
args = parser.parse_args()
# how to handle args.assume at this point? see below for my ideas
return args
# interactive.py, this is the original function from my project
def confirm(message: str, accept_enter_key: bool = True) -> bool:
"""Ask the user for confirmation on the terminal.
:param message: the question to print
:param accept_enter_key: Accept ENTER as alternative for "n"
:returns: the answer of the user
"""
while True:
answer = input(message + ' (y/N) ')
answer = answer.lower()
if answer == 'y':
return True
if answer == 'n':
return False
if answer == '' and accept_enter_key:
return False
print('Please answer with "y" for yes or "n" for no.')
Ideas how to handle args.assume
I came up with three ideas but I can find something about each idea that I don't like:
replace
interactive.confirm
with another function dynamically# insert this into cli.py if args.assume is not None: import interactive interactive.confirm = lambda _, __=None: args.assume
I do not like this because it changes a function on the fly. I like it because I can forget the
args.assume
thing in the rest of my application. It does not show up in the unit tests ofinteractive.confirm
.add an extra argument to all functions that call
interactive.confirm
and to the function itself:# in interactive.py def confirm(message: str, assume: bool|None, accept_enter_key: bool = True) -> bool: if assume is not None: return assume ... # in main.py def work(foo, assume): if interactive.confirm(f"do you want {foo}", assume): ... if __name__ == "__main__": args = cli.parse() work(args.foo, args.assume)
I don't like this because I have to change the signature of many functions in my program and the new argument has to be tested in the unittest of interactive.confirm.
save the value of
args.assume
to a global variable that is read byinteractive.confirm
:# insert this into cli.py if args.assume is not None: import interactive interactive.assume = args.assume # and in interactive.py def confirm(message: str, accept_enter_key: bool = True) -> bool: if assume is not None: return assume ...
This (nearly) has the advantage of (1) because it does not touch any other code but the command line parser and
confirm
. But it uses a global mutable variable to toggle program behaviour which I thought was bad style.
Question
What do you think about my ideas to implement the assume logic? What pros and cons did I forget? Do you know another way to implement it?
Background
The console application is khard. confirm
is called directly in nine places by six other functions. Some of these get called by other functions in turn.