Writing clean, flexible, and easy-to-maintain custom tips

I am often asked a question asking users for input. I always just wrote my “as needed” tips in my main execution scripts. This is disgusting, and because I often ask you to enter the same input types of several scripts, a ton of my code is just copy / paste the contours of the queries. Here is what I have done in the past:

while True: username = input("Enter New Username: ") if ldap.search(username): print " [!] Username already taken." if not validator.validate_username(username): print " [!] Invalid Username." else: break 

I would like to create something that can be called:

 username = prompt(prompt="Enter New Username: ", default=None, rules=["user_does_not_exist", "valid_username"]) 

Then the query function looks like this:

 def prompt(prompt, default, rules): while True: retval = input(prompt) if default and retval == "": break return default if not rule_match(retval, rules): continue break return retval def rule_match(value, rules): if "user_does_not_exist" in rules: if not user.user_exists(value): return False if "valid_username" in rules: if not validator.username(value): return False if "y_n_or_yes_no" in rules: if "ignore_case" in rules: if value.lower() not in ["y", "yes", "n", "no"]: return False else: if value not in ["y", "yes", "n", "no"]: return False return True 

An alternative that I am considering is to create a Prompt class that will increase the flexibility of the results. For example, if I want to convert "y" or "n" to True or False, this does not work.

 create_another = Prompt(prompt="Create another user? (y/n): ," default=False, rules=["y_n_or_yes_no", "ignore_case"]).prompt().convert_to_bool() 

Another alternative that I am considering is simply to make individualized prompts and call them, each of them written in the same way as my original code. This does not change anything. It simply serves to get these loops from my main execution code, which makes it easier to see the main execution code:

 username = prompt("get_new_username") def prompt(prompt_name): if prompt_name == "get_new_username": while True: username = input("Enter New Username: ") if ldap.search(username): print " [!] Username already taken." if not validator.validate_username(username): print " [!] Invalid Username." else: break return username if prompt_name == "y_n_yes_no_ignore_case": # do prompt if prompt_name == "y_n_yes_no": # do prompt if prompt_name == "y_n": # do prompt if prompt_name == "y_n_ignore_case": # do prompt if prompt_name == "yes_no": # do prompt if prompt_name == "yes_no_ignore_case": # do prompt 

I understand that it might just be nice to agree to one accepted "y / n" format for all my programs, and I will. This is just to show that in cases where I need a very similar, but slightly different invitation, this will lead to a lot of copy / paste of the code (generally there is no flexibility in this method).

What is a good approach to writing clean, flexible, and easy-to-maintain custom tips?

(I saw this: User request for input until he gives a valid answer and some other answers. My question is not how to get input and confirm it, how to create a flexible input system that can be reused use with several programs).

+7
python input prompt
source share
2 answers

I once wrote a function for something like this. The explanation is given in the doc line:

 def xory(question = "", setx = ["yes"], sety = ["no"], setz = [], strict = False): """xory([question][, setx][, sety][, setz][, strict]) -> string Asks question. If the answer is equal to one of the elements in setx, returns True. If the answer is equal to one of the elements in sety, returns False. If the answer is equal to one of the elements in setz, returns the element in setz that answer is equal to. If the answer is not in any of the sets, reasks the question. Strict controls whether the answer is case-sensitive. If show is True, an indication of the acceptable answers will be displayed next to the prompt.""" if isinstance(setx, str): setx = [setx] if isinstance(sety, str): sety = [sety] if isinstance(setz, str): setz = [setz] if (setx[0])[0] != (sety[0])[0]: setx = [(setx[0])[0]] + setx sety = [(sety[0])[0]] + sety question = question.strip(" ") + " " while True: if show: shows = "[%s/%s] " % (setx[0], sety[0]) else: shows = "" user_input = raw_input(question + shows) for y in [setx, sety, setz]: for x in y: if (user_input == x) or ((not strict) and (user_input.lower() == x.lower())): if y is setx: return True elif y is sety: return False else: return x question = "" show = True 

Examples:

 >>> response = xory("1 or 0?", ["1", "one", "uno"], ["0", "zero", "null"], ["quit", "exit"]) 1 or 0? x [1/0] eante [1/0] uno >>> print(response) True >>> response = xory("Is that so?") Is that so? Who knows? [y/n] no >>> print(response) False >>> response = xory("Will you do it?", setz=["quit", "exit", "restart"]) Will you do it? hm [y/n] quit >>> print(response) quit 
0
source share

I would suggest writing a library containing a number of very clearly defined building blocks, each of which would be as small and light as possible without too many assumptions baked into how you are going to collect the pieces.

That is, I would include one function that executes the loop, but instead of passing a set of rules, I would allow passing only one function that returns a value (if the actual input was given and after its conversion is necessary in some way) or raises ValueError if the input was not used. Other building blocks will perform certain checks or conversions (for example, resolving 'y' and 'n' to logical values).

Thus, you completely leave it to the user to collect the material in a way that is suitable for use.

 # library: def prompt(prompt, default, postprocess): value = input('{} ({}): '.format(prompt, default)) or default try: return postprocess(value) except ValueError: continue def check_lower(value): if not value.islower(): raise ValueError() def to_bool(value): return value in 'yes' # using the library: def postprocess(value): check_lower(value) return to_bool(value) prompt('Really?', 'n', postprocess) 
0
source share

All Articles