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) |
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 |
SpeechCompleter |
(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. |
🔍 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','ms_url','slug','email','phone',
'currency','country','language','movie_ids','ss_video_ids','bundle_ids'
],
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, etc. - 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
validate_user_option(...)
validate_user_option(
input_msg: str = "Option:",
*args: str,
**kwargs: str|bool # pass q=False to suppress quit
) -> str
- Auto-adds
q: quitunlessq=False. - Returns the chosen key.
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.
genre = validate_user_option_value(a="Action", c="Comedy", d="Drama")
# 'c' → "Comedy"
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)
🗄 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"},{"name":"Banana"}]
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 |
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 |
country |
Recognizes common abbreviations for countries |
🧪 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.1.tar.gz.
File metadata
- Download URL: askuser-0.1.1.tar.gz
- Upload date:
- Size: 18.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a5866018f675a819f120e17fbed1ac38e7ccfafba0e22720ce3d368a2913253a
|
|
| MD5 |
24f63afa6fd7b4c0efc523a44ee76c2b
|
|
| BLAKE2b-256 |
a31dfa8aac678c1f0fe36547e18128c25527b3e81c5fbc675a2ef918fc319164
|
File details
Details for the file askuser-0.1.1-py3-none-any.whl.
File metadata
- Download URL: askuser-0.1.1-py3-none-any.whl
- Upload date:
- Size: 14.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
823ebf08c9e13d8f5fb17ee13e8ee3db5a96c945e46b344d007fb1f2480a7f2f
|
|
| MD5 |
2ae7ca1e8f5ca0126d22536c92d588f5
|
|
| BLAKE2b-256 |
f29e172d2f8cc139cd353cc5e13d90fa0dfd1848d87f08db6195e594b1a86c39
|