AskUser
AskUser is a smart CLI utility for collecting and validating user input in Python. It wraps common prompt patterns—validation, menus, defaults, and autocompletion—into a simple, consistent API.
📦 Installation
pip install askuser
📖 API Overview
validate_input(...) | Prompt for free-form input, validate type/pattern, show hints (min/max/default) and retry until valid |
pretty_menu(*args, **kwargs) | Print a formatted menu of positional (0:) and keyword (x:) options |
validate_user_option(...) | Show a menu, auto-add q: quit (unless q=False), prompt user, and return the selected key |
validate_user_option_value(...) | Like validate_user_option, but maps the chosen key to its value |
validate_user_option_enumerated(dict,...) | Enumerate a dict into numbered options, add q: quit, and return (key, value) |
validate_user_option_multi(...) | Multi-select version of validate_user_option, returns keys in pick order; exit with d: done (or xd, xd2, …) |
validate_user_option_value_multi(...) | Multi-select version of validate_user_option_value, returns values in pick order; exit with d: done |
choose_from_db(list_of_dicts,...) | Tabulate DB rows, prompt for an existing id, optionally accept xq: quit, and return (id, row_dict) |
choose_dict_from_list_of_dicts(list, field) | Display each item’s field as a menu, return the selected dict |
yes(prompt, default=None) | Shorthand for validate_input(prompt, "yes_no", default) == "y" → returns bool |
SubstringCompleter | (internal) a prompt_toolkit completer that finds substring matches in suggestions |
user_prompt(prompt, items, return_value=False) | Prompt with autocomplete over a list/dict of items; if return_value=True (and items is a dict), returns the value instead. |
🔎 Key types: When you pass options via **kwargs, selected keys are returned in their original types (e.g., int stays int). With positional *args, menu keys are enumerated strings: '0', '1', …
🔍 validate_input
validate_input(
input_msg: str,
validation_type: Literal[
'custom','required','not_in','none_if_blank','yes_no',
'int','float','alpha','alphanum','custom_chars','regex',
'date','future_date','time','url','slug','email','phone','language'
],
expected_inputs: list = None,
not_in: list = None,
maximum: Union[int, float] = None,
minimum: Union[int, float] = None,
allowed_chars: str = None,
allowed_regex: str = None,
default: Any = None
) -> Union[str,int,float,None]
- Default values
- If you set
default, pressing Enter returns the default.
- Hints added automatically
yes_no adds (y/n)
none_if_blank adds (optional)
time adds (hh:mm:ss)
maximum/minimum display (max: …) / (min: …)
default displays (default: …)
- Validation types
- Built-in:
int, float, alpha, alphanum, date, future_date, time, url, email, phone, slug, language.
- Custom:
custom with expected_inputs=[…]
not_in with not_in=[…]
custom_chars with allowed_chars="abc123"
regex with allowed_regex="^[A-Z]+$"
Example:
count = validate_input(
"How many items?", "int",
minimum=1, maximum=100,
default=10
)
Prints a menu—no prompt:
pretty_menu("List", "Add", d="Delete", q="Quit")
Keys are case-sensitive. What you see is what you type.
validate_user_option(...)
validate_user_option(
input_msg: str = "Option:",
*args: Any,
**kwargs: Any
) -> Any
- Auto-adds
q: quit unless q=False.
- Returns the chosen key (preserving original type for
**kwargs).
Examples:
opt = validate_user_option("Pick:", "Red","Blue", g="Green")
opt = validate_user_option("Pick:", "One","Two", q=False)
validate_user_option_value(...)
validate_user_option_value(
input_msg: str = "Option:",
*args,
**kwargs: Any
) -> Any
- Builds same menu, returns the value.
- No
q by default (legacy behavior).
genre = validate_user_option_value(a="Action", c="Comedy", d="Drama")
validate_user_option_multi(...)
validate_user_option_multi(
input_msg: str = "Option:",
*args,
**kwargs: Any
) -> List[Any]
- Multi-select version of
validate_user_option.
- Exit with
d: done by default. If d is already used in your options, exit appears as xd, or xd2, xd3, …
- Pass
d=False to disable exit and force “pick until exhausted.”
- Returns a list of keys in the order picked (kwargs preserve original key types).
STATUS = {0:"new", 1:"active", 7:"we rejected"}
picked = validate_user_option_multi("Select statuses:", **STATUS)
validate_user_option_value_multi(...)
validate_user_option_value_multi(
input_msg: str = "Option:",
*args,
**kwargs: Any
) -> List[Any]
- Multi-select version of
validate_user_option_value.
- Exit with
d: done (or xd, xd2, … if d is taken). Use d=False to disable exit.
- Returns a list of values in the order picked.
vals = validate_user_option_value_multi("Pick genres", a="Action", c="Comedy", d="Drama")
validate_user_option_enumerated(dict, msg="Option:", start=1)
validate_user_option_enumerated(
a_dict: Dict[Any, str],
msg: str = "Option:",
start: int = 1
) -> Tuple[Any, str]
- Enumerates
.items() starting at start.
- Adds
q: quit.
- Returns
(key, value) or ('q', None).
movies = {101:"Inception", 202:"Memento"}
mid, title = validate_user_option_enumerated(movies, start=1)
🗄 Database-Style Selection
choose_from_db(db_result, input_msg=None, table_desc=None, xq=False)
choose_from_db(
db_result: List[Dict[str,Any]],
input_msg: str = None,
table_desc: str = None,
xq: bool = False
) -> Tuple[int, Dict]
- Pretty-prints rows with
tabulate.
- Only existing
id values in db_result are valid.
- If
xq=True, also accepts xq → returns ('xq','quit').
- Invalid entries re-prompt.
choose_dict_from_list_of_dicts(list_of_dicts, key_to_choose)
choose_dict_from_list_of_dicts(
list_of_dicts: List[Dict],
key_to_choose: str
) -> Dict
- Menu of
dict[key_to_choose].
- Returns selected dict.
fruits = [{"name":"Apple","color":"red"},{"name":"Banana","color":"yellow"}]
choice = choose_dict_from_list_of_dicts(fruits, "name")
✅ Yes/No Shortcut
yes("Continue?", default="y")
💬 Autocomplete
from askuser.autocomplete import user_prompt
res = user_prompt("Country: ", ["USA","UK","IN"], return_value=False)
opts = {"us":"United States","uk":"United Kingdom"}
code = user_prompt("Code: ", opts, return_value=True)
🔍 All Validation Types
int / float / decimal | Numeric with optional minimum / maximum |
alpha / alphanum | Only letters / letters+digits |
date / future_date | YYYY-MM-DD or YYYY-MM-DD HH:MM:SS |
time | HH:MM:SS |
email | Standard RFC email |
phone | Digits with optional +, strips spaces/dashes |
url | Hostname + optional path/query |
slug | [a-z0-9-] only, single delimiter |
custom | Only values in expected_inputs |
not_in | Reject values in not_in |
custom_chars | Only chars in allowed_chars |
regex | Match your regex |
language | ISO-639 via pycountry |
🧪 Testing
Under tests/, examples:
import pytest
from askuser import validate_input, yes, validate_user_option
def test_default(monkeypatch):
monkeypatch.setattr('builtins.input', lambda _: '')
assert validate_input("Prompt?", "int", default=7) == 7
def test_no_quit(monkeypatch):
monkeypatch.setattr('builtins.input', lambda _: '0')
assert validate_user_option("Pick:", "A","B", q=False) == '0'
Run:
pytest tests/
📜 License
MIT — free to use, modify, and distribute.