Skip to main content

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., 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:

# 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: quit unless q=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 q by 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: 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)
# → [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 (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")
# 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 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")  # 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

🧪 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

askuser-0.1.4.tar.gz (20.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

askuser-0.1.4-py3-none-any.whl (16.1 kB view details)

Uploaded Python 3

File details

Details for the file askuser-0.1.4.tar.gz.

File metadata

  • Download URL: askuser-0.1.4.tar.gz
  • Upload date:
  • Size: 20.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.0

File hashes

Hashes for askuser-0.1.4.tar.gz
Algorithm Hash digest
SHA256 654b4032df9439a4e36371a179678621c736f7582dc5581220c742f6e40193ae
MD5 035d7a5c69715230bcccee7a8996a63d
BLAKE2b-256 9157b6481cc5bbedb8455ebaddd3b7cc2d46c0f3ca67f2f6af5442ecc6678305

See more details on using hashes here.

File details

Details for the file askuser-0.1.4-py3-none-any.whl.

File metadata

  • Download URL: askuser-0.1.4-py3-none-any.whl
  • Upload date:
  • Size: 16.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.0

File hashes

Hashes for askuser-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 b403ed7b0e366706ac1843b827c893ab92ce8c40a3baddc1b3155b937d42bf6e
MD5 1c7b79a2d08cb9243c8bd474a3bbe3ac
BLAKE2b-256 87ef2ba4749d16f032c66297224d88414982da3e61929b944e9f1f6d517e4a18

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page