Smart and robust CLI input: validated prompts, menu-driven selections, default value support, and smart autocompletion.
Project description
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
| Function / Class | What It Does |
|---|---|
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.,intstaysint). 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.
- If you set
- Hints added automatically
yes_noadds(y/n)none_if_blankadds(optional)timeadds(hh:mm:ss)maximum/minimumdisplay(max: …)/(min: …)defaultdisplays(default: …)
- Validation types
- Built-in:
int,float,alpha,alphanum,date,future_date,time,url,email,phone,slug,language. - Custom:
customwithexpected_inputs=[…]not_inwithnot_in=[…]custom_charswithallowed_chars="abc123"regexwithallowed_regex="^[A-Z]+$"
- Built-in:
Example:
# integer with bounds & default
count = validate_input(
"How many items?", "int",
minimum=1, maximum=100,
default=10
)
# • Blank → count == 10
# • Invalid/out-of-range → re-prompt
🧭 Menus & Options
pretty_menu(*args, **kwargs)
Prints a menu—no prompt:
pretty_menu("List", "Add", d="Delete", q="Quit")
# 0: List 1: 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 # pass q=False to suppress quit
) -> Any # key (kwargs preserves original key types; args return '0','1',...)
- Auto-adds
q: quitunlessq=False. - Returns the chosen key (preserving original type for
**kwargs).
Examples:
opt = validate_user_option("Pick:", "Red","Blue", g="Green")
# keys: '0','1','g','q'
opt = validate_user_option("Pick:", "One","Two", q=False)
# keys: '0','1'
validate_user_option_value(...)
validate_user_option_value(
input_msg: str = "Option:",
*args,
**kwargs: Any # key -> value
) -> Any
- Builds same menu, returns the value.
- No
qby default (legacy behavior).
genre = validate_user_option_value(a="Action", c="Comedy", d="Drama")
# 'c' → "Comedy"
validate_user_option_multi(...)
validate_user_option_multi(
input_msg: str = "Option:",
*args,
**kwargs: Any # key -> label
) -> List[Any]
- Multi-select version of
validate_user_option. - Exit with
d: doneby default. Ifdis already used in your options, exit appears asxd, orxd2,xd3, … - Pass
d=Falseto 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)
# → [1, 7]
validate_user_option_value_multi(...)
validate_user_option_value_multi(
input_msg: str = "Option:",
*args,
**kwargs: Any # key -> value
) -> List[Any]
- Multi-select version of
validate_user_option_value. - Exit with
d: done(orxd,xd2, … ifdis taken). Used=Falseto 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")
# user picks: c, a → ['Comedy', 'Action']
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 atstart. - 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
idvalues indb_resultare valid. - If
xq=True, also acceptsxq→ 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") # True if 'y', False if 'n'; blank → default
💬 Autocomplete
from askuser.autocomplete import user_prompt
res = user_prompt("Country: ", ["USA","UK","IN"], return_value=False)
# ≥2 chars → suggestions
opts = {"us":"United States","uk":"United Kingdom"}
code = user_prompt("Code: ", opts, return_value=True)
# returns 'us'
🔍 All Validation Types
| Type | Description |
|---|---|
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file askuser-0.1.5.tar.gz.
File metadata
- Download URL: askuser-0.1.5.tar.gz
- Upload date:
- Size: 21.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1dfc6196aa16824b4db83954080c8cefdc16512bff16cba786ac671d12a85c97
|
|
| MD5 |
fdaf6084c3083446842e55aff6547e16
|
|
| BLAKE2b-256 |
76debe330dfa018798554ca2acb1eb2b8a1b76d93222bd9bbb4821ca2bef9e01
|
File details
Details for the file askuser-0.1.5-py3-none-any.whl.
File metadata
- Download URL: askuser-0.1.5-py3-none-any.whl
- Upload date:
- Size: 16.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6dd662d81f6ef604616106ac15980c1ace30ed4fd7bff80990b0407e3fb4613c
|
|
| MD5 |
58a8004538243785f27fc6827595e188
|
|
| BLAKE2b-256 |
79b75d1fbf7bb5d5fba653c8ed33050e3ed88c2b06a882f7046b1b437be19ea5
|