
Research
2025 Report: Destructive Malware in Open Source Packages
Destructive malware is rising across open source registries, using delays and kill switches to wipe code, break builds, and disrupt CI/CD.
askuser
Advanced tools
Smart and robust CLI input: validated prompts, menu-driven selections, default value support, and smart autocompletion.
AskUser is a smart CLI utility for collecting and validating user input in Python. It wraps common prompt patterns—validation, menus, defaults, multi-selects, and autocompletion—into a simple, consistent API.
validate_inputpip install askuser
| Function / Class | What It Does |
|---|---|
validate_input(...) | Prompt for free-form input, validate type/pattern, retry until valid |
pretty_menu(*args, **kwargs) | Print a formatted menu |
validate_user_option(...) | Show a menu and return the selected key |
validate_user_option_value(...) | Return the selected value |
validate_user_option_enumerated(...) | Enumerate a dict and return (key, value) |
validate_user_option_multi(...) | Multi-select menu (returns keys) |
validate_user_option_value_multi(...) | Multi-select menu (returns values) |
choose_from_db(...) | Select an existing DB id from tabulated rows |
choose_dict_from_list_of_dicts(...) | Select and return a dict |
yes(...) | Yes/No shortcut |
user_prompt(...) | Prompt with autocomplete |
SubstringCompleter | Substring-based completer (advanced use) |
validate_inputvalidate_input(
input_msg: str,
validation_type: str | Literal[
'custom','required','not_in','none_if_blank','yes_no',
'int','float','decimal','alpha','alphanum','custom_chars','regex',
'date','future_date','time','url','slug','email','phone','language'
],
expected_inputs: list = None,
not_in: list = None,
maximum: int | float = None,
minimum: int | float = None,
allowed_chars: str = None,
allowed_regex: str = None,
default: Any = None
) -> Union[str,int,float,None]
default is set, pressing Enter returns the default.yes_no → (y/n)none_if_blank → (optional)time → (hh:mm:ss)maximum / minimum → (max: …) / (min: …)default → (default: …)int, float, decimal, alpha, alphanum, date, future_date, time, url, email, phone, slug, language.custom with expected_inputs=[...]not_in with not_in=[...]custom_chars with allowed_chars="abc123"regex with allowed_regex="^[A-Z]+$"count = validate_input(
"How many items?",
"int",
minimum=1,
maximum=100,
default=10
)
pretty_menu(*args, **kwargs)Prints a menu without prompting:
pretty_menu("List", "Add", d="Delete", q="Quit")
Output:
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,
**kwargs # pass q=False to suppress quit
) -> Any
q: quit unless q=False**kwargs*args, keys are enumerated strings ("0", "1", …)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(...)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.d: done by default. If d is already used in your options, exit appears as xd, or xd2, xd3, ...d=False to disable exit and force “pick until exhausted.”STATUS = {0: "new", 1: "active", 7: "rejected"}
picked = validate_user_option_multi("Select statuses:", **STATUS)
# → [1, 7]
d: done (or xd, xd2, … if d is taken)d=False to force selection until exhaustedvalidate_user_option_value_multi(...)validate_user_option_value.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,
msg: str = "Option:",
start: int = 1
) -> tuple
.items() starting at start.q: quit.(key, value) or ('q', None).movies = {101: "Inception", 202: "Memento"}
mid, title = validate_user_option_enumerated(movies, start=1)
q: quit(key, value) or ('q', None)choose_from_db(db_result, input_msg=None, table_desc=None, xq=False)choose_from_db(
db_result: list[dict],
input_msg: str = None,
table_desc: str = None,
xq: bool = False
) -> tuple
tabulate.id values in db_result are valid.xq=True, also accepts xq → returns ('xq', 'quit').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
dict[key_to_choose].fruits = [
{"name": "Apple", "color": "red"},
{"name": "Banana", "color": "yellow"},
]
choice = choose_dict_from_list_of_dicts(fruits, "name")
yes("Continue?", default="y") # True if 'y', False if 'n'; blank → default
from askuser.autocomplete import user_prompt
country = user_prompt("Country: ", ["USA", "UK", "IN"])
# typing ≥2 chars will start showing suggestions from list
With dicts:
opts = {"usa": "United States", "uk": "United Kingdom"}
val = user_prompt("Code: ", opts, return_value=True)
print(val)
# Code: usa
# United States
This table reflects actual runtime behavior, including case handling.
| Type | Description |
|---|---|
required | Must not be blank |
none_if_blank | Blank input returns None |
yes_no | Accepts y / n (case-insensitive, normalized to lowercase) |
int | Integer with optional minimum / maximum |
float | Float with optional minimum / maximum |
decimal | Decimal with optional bounds |
alpha | Alphabetic characters only |
alphanum | Alphanumeric characters only |
date | YYYY-MM-DD or YYYY-MM-DD HH:MM:SS |
future_date | Date must be today or in the future |
time | HH:MM:SS |
email | RFC-compliant email |
phone | Digits with optional + (spaces/dashes stripped) |
url | Hostname with optional path/query |
slug | Lowercased [a-z0-9-], deduplicated delimiter |
language | ISO-639 via pycountry |
custom | Exact match against expected_inputs (case-sensitive) |
not_in | Reject values in not_in (case-insensitive comparison) |
custom_chars | Only characters in allowed_chars |
regex | Must match provided regex |
Design note: Case-sensitivity is intentional.
If you want case-insensitive behavior forcustom, normalize input yourself or register a custom validator.
AskUser supports registering additional validators at runtime without mutating internal globals directly.
from askuser import register_validator, validate_input
def is_even(user_input: str) -> int:
n = int(user_input)
if n % 2:
raise ValueError("Must be even")
return n
register_validator("even", is_even)
x = validate_input("Enter even number:", "even")
Helpers available:
get_validators()register_validator(name, func, overwrite=False)register_validators({name: func, ...}, overwrite=False)unregister_validator(name)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/
MIT — free to use, modify, and distribute.
FAQs
Smart and robust CLI input: validated prompts, menu-driven selections, default value support, and smart autocompletion.
We found that askuser demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
Destructive malware is rising across open source registries, using delays and kill switches to wipe code, break builds, and disrupt CI/CD.

Security News
Socket CTO Ahmad Nassri shares practical AI coding techniques, tools, and team workflows, plus what still feels noisy and why shipping remains human-led.

Research
/Security News
A five-month operation turned 27 npm packages into durable hosting for browser-run lures that mimic document-sharing portals and Microsoft sign-in, targeting 25 organizations across manufacturing, industrial automation, plastics, and healthcare for credential theft.