Lightweight helper toolkit for building small CLI (command-line) applications in Python.
Project description
cli_tools_by_oleksa
Lightweight helper toolkit for building robust and interactive CLI (command-line) applications in Python.
The library simplifies common CLI tasks such as:
- Safe Input & Validation: Handling user input with built-in retry mechanisms, type conversion, and defined validation rules.
- Error Management: Ensuring graceful execution and clean shutdown using exception handlers and context managers.
- Data Processing: Splitting, converting, and extracting structured data from raw input strings using focused utilities.
- Formatted Output: Providing clean, structured, and customizable console displays for lists and tables.
It is good for educational scripts, training exercises, small utilities, and simple CLI apps where readability, robustness, and speed of development matter more than complex frameworks.
✨ Features
valid_input— Base validation function: single-pass flow (RegEx → Validator → Conversion). RaisesValueErroron failure with no retry logic.get_valid_input— High-level input handler with automatic retrying, customif_incorrectmessages, and clean, predictable user prompts.choose_from_list— Safe list selection by name or index, case-insensitive, with built-in retry loop and clear error messages.extract_match— Extracts RegEx groups and converts them into the desired types.split— Flexible string splitting using eitherstrorre.Pattern, with optional conversion of each element.print_iterable/print_zipped_iterable— Utilities for clean, formatted output of iterables, pairs and structured data.safe_int/safe_float— Safe numeric converters returningNoneinstead of raising exceptions.- Predefined Patterns — Frequently used RegEx patterns:
INT,FLOAT,EMAIL,DATE_DMY,NUMBER, and more. - Validator Factories — Generator functions for common validations:
is_in_range,is_in_list,is_list_of,more,less_or_equal, and other composable checks. safe_run— Context manager for safe execution: catches exceptions, handlesCtrl+C, and ensures a clean exit.try_until_ok— General retry mechanism: repeatedly executes an operation until it succeeds.
Requires Python 3.10+ and has zero external dependencies.
📦 Installation
pip install --upgrade cli_tools_by_oleksa
🛠️ Usage Example
Basic example
New functions used:
- print_header
- valid_input
- get_valid_input
Code:
from cli_tools import print_header,valid_input, get_valid_input
from cli_tools.patterns import INT # Check if input is integer number
from cli_tools.validators import is_in_range # Check if input is a number in chosen range
print_header('Basic example')
# Without a pattern/validator, accepts any input and applies a converter to it
name = valid_input('Enter your name: ', converter= lambda x: x.strip().capitalize())
# Accepts only integer age, 1 <= age <= 120. Convert input to int
age = get_valid_input('Enter your age: ',
pattern=INT,
validator=is_in_range(1, 120),
converter=int,
if_incorrect='Be serious :)')
print(f'Hello, {name}! Your age is {age}. {'You are so young!' if age < 18 else 'How do you like being an adult?'}')
Output:
~~~~~~~~~~~~~
Basic example
~~~~~~~~~~~~~
Enter your name: oLeKSa
Enter your age: 0
Be serious :)
Enter your age: 1000
Be serious :)
Enter your age: 19
Hello, Oleksa! Your age is 19. How do you like being an adult?
Process finished with exit code 0
Interactive choice example
New functions used:
- print_iterable
- print_zipped_iterable
- choose_from_list
- split
Code:
from cli_tools import get_valid_input, print_header
from cli_tools import (print_iterable, # format and print iterable object
print_zipped_iterable, # format and print iterable object
choose_from_list, # ask for choose an option from the list
split) # split using str.split or re.split, optionally converts all elements
from cli_tools.patterns import NUMBER # pattern for a number (int or float)
from cli_tools.validators import is_list_of # check if input is a list of numbers
print_header('| CLI Interactive Choice Demo |')
options = ['Football', 'Music', 'Coding']
# print options using pattern
# there is also a format_iterable function that return string instead of print it
print_iterable(options, '- {}', '\n',
start='Imagine that you could only pursue one hobby for the next year. What would you choose?\n')
# return entered string if it is in options
hobby = choose_from_list(options,
case_sensitive=False,
prompt='What is your choice? ',
if_incorrect="Sorry, but your input is incorrect. Chose from the list!")
options = ['My hobby','Secret tip']
# print options using pattern
# there is also a format_zipped_iterable function that return string instead of print it
print_zipped_iterable(enumerate(options, start=1), '{}. {}', '\n', start='What next?\n')
# return index of chosen option
choice = choose_from_list(options, by_number=True, prompt='Enter a number: ',)
match choice:
case 0:
print(f'Your hobby is {hobby}.')
case 1:
print("Secret tip: if you dont have enough money - just find a job.")
# ask for list of numbers until get it
numbers = get_valid_input(prompt='Enter a numbers separated by spaces: ',
validator=is_list_of(NUMBER))
# split with conversation: return list of float numbers
number_list = split(numbers, converter=float)
print(f'Sum of these numbers: {sum(number_list)}')
print('Bye!')
Output:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| CLI Interactive Choice Demo |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Imagine that you could only pursue one hobby for the next year. What would you choose?
- Football
- Music
- Coding
What is your choice? IDK
Sorry, but your input is incorrect. Chose from the list!
What is your choice? music
What next?
1. My hobby
2. Secret tip
Enter a number: 2
Secret tip: if you dont have enough money - just find a job.
Enter a numbers separated by spaces: 12 8 23
Sum of these numbers: 43.0
Bye!
Process finished with exit code 0
Project example
New functions used:
- extract_match
Code:
import re
from cli_tools import (print_header, get_valid_input,
extract_match) # extract list of all matches, optionally convert every match. [] if no matches
from cli_tools.patterns import NUMBER # Ready-made pattern: accepts both int and float
# Pattern <number> <operator> <number>, spaces ignored
simple_expr_pattern = re.compile(fr' *({NUMBER.pattern}) *([+\-*/^]) *({NUMBER.pattern}) *')
# Accepts either numbers or operators. Converts numbers to float
converter = lambda x: float(x) if x not in '+-*/^' else x
print_header('| Simple calculator |', '—')
print('Supports simple expressions in format <number> <operator> <number>. Press Ctrl+C to exit.')
while True:
# Guaranteed to pass only input that matches the pattern
expr = get_valid_input(prompt='> ', pattern=simple_expr_pattern, if_incorrect='Wrong format!')
# Unpack the input, immediately converting the numbers
left, operator, right = extract_match(expr, simple_expr_pattern, converter=converter)
match operator:
case '+':
print(left+right)
case '-':
print(left-right)
case '*':
print(left*right)
case '/':
if right == 0:
print('Division by zero is not allowed!')
continue
print(left/right)
case '^':
print(left**right)
case _:
print('Wrong operator!')
Output:
—————————————————————
| Simple calculator |
—————————————————————
Supports simple expressions in format <number> <operator> <number>. Press Ctrl+C to exit.
> 2+2
4.0
> 5/0
Division by zero is not allowed!
> 3 ^ 2
9.0
> banana
Wrong format!
> 1.25 * 8
10.0
>
Process finished with exit code 0
Error handling features
New functions used:
- safe_run
- try_until_ok
Code:
import random, time
from cli_tools.exceptions import (CLIError, # Base class for all raised errors
APIError, # Error that raised when you pass invalid data to a function.
ValidationError, # Data not validated
ConversionError) # The transferred converter caused an error
from cli_tools import safe_run, try_until_ok, print_header
print_header('Safe Execution Demo')
with safe_run(debug=False, exit_on_error=False):
print("Press Ctrl+C to test graceful exit, or wait for the error...")
for i in range(3, 0, -1):
print(f"Crashing in {i}...")
time.sleep(1)
raise CLIError("Something went wrong inside the app!")
print("App still working.")
###
print_header('Retry Logic Demo')
def unstable_network_request():
"""Simulates a connection that fails 70% of the time."""
if random.random() < 0.7:
raise ConnectionError("Connection timed out")
return "200 OK"
print("Attempting to connect to server...")
status = try_until_ok(
unstable_network_request,
exceptions=ConnectionError,
on_exception="Connection failed. Retrying..."
)
print(f"Success! Server response: {status}")
Output:
~~~~~~~~~~~~~~~~~~~
Safe Execution Demo
~~~~~~~~~~~~~~~~~~~
Press Ctrl+C to test graceful exit, or wait for the error...
Crashing in 3...
Crashing in 2...
Crashing in 1...
Error: Something went wrong inside the app!
App still working.
~~~~~~~~~~~~~~~~
Retry Logic Demo
~~~~~~~~~~~~~~~~
Attempting to connect to server...
Connection failed. Retrying...
Connection failed. Retrying...
Connection failed. Retrying...
Success! Server response: 200 OK
Process finished with exit code 0
📚 API Reference
🏗️ cli.py
This module contains basic functions for handling user input from the console, formatting and outputting data. No separate import required: all functions are available via import cli_tools
⌨️ Input Handling
| Function | Description | Key Arguments | Returns |
|---|---|---|---|
valid_input() |
Prompts for input and performs single-pass validation (RegEx + custom validator). Throws ValueError if input is incorrect. Enforces the sequence: Pattern Check → Validator Check → Conversion. |
prompt: str = ''pattern: str | re.Pattern = ANYvalidator: Callable[[str], bool] = lambda x: Trueconverter: Callable[[str], Any] = str |
The converted input value (Any). |
get_valid_input() |
Wraps valid_input inside a retry loop (try_until_ok). Repeatedly prompts the user until input is valid, displaying if_incorrect on failure. |
prompt: str = ''pattern: str | re.Pattern = ANYvalidator: Callable[[str], bool] = lambda x: Trueconverter: Callable[[str], Any] = strif_incorrect: str = 'Incorrect input!' |
The converted input value (Any). |
choose_from_list() |
Prompts the user for a choice from a list of options. Automatically handles validation and retries internally using try_until_ok. |
options: list[str]by_number: bool = Falsecase_sensitive: bool = Trueprompt: str = 'Choose option: 'if_incorrect: str = 'Incorrect option!' |
If by_number is True, returns the 0-based index (int). Otherwise, returns the chosen option string (str). |
🖨️ Output Formatting
| Function | Description | Key Arguments | Notes |
|---|---|---|---|
print_header() |
Prints a centralized header with decorative lines above and below, using a customizable symbol. | header: strsymbol: str='~' |
Useful for creating clean section titles in the console. |
print_iterable() |
Convenient formatted output of any iterable object. | iterable: Iterable[Any]item_pattern: str = '{}', join_by: str = '\n', start: str = '', end: str = '' |
Formats each item using item_pattern.format(item) and then prints start+join_by.join(formated_items)+end. |
print_zipped_iterable() |
Convenient formatted output for iterables containing unpackable pairs (e.g., tuples or lists). | iterable: Iterable[Iterable[Any]], item_pattern: str = '{}: {}', join_by: str='\n', start: str = '', end: str = '' |
Formats each pair using item_pattern.format(*item) and then prints start+join_by.join(formated_items)+end. Ideal for items from zip()ordict.items()`. |
utils.py
This module contains essential helper functions for safe execution, safe type conversion, string manipulation, and data extraction using regular expressions. No separate import required: all functions are available via import cli_tools
Safe Execution
Context Manager: safe_run
A context manager designed to wrap sections of code (or the entire application entry point) to prevent program crashes due to unhandled exceptions, and to ensure graceful handling of user interruptions (Ctrl+C).
| Parameter | Type | Default | Description |
|---|---|---|---|
debug |
bool |
False |
If True, the full Python traceback is printed upon an error. If False, only a brief error message is shown. |
exit_on_error |
bool |
True |
If True, the program exits immediately (status code 1) when an exception is caught. If False, the execution continues after the with block. |
| Intercepts | Catches all general exceptions (Exception) and KeyboardInterrupt (Ctrl+C), ensuring a graceful exit (status code 0) in the latter case. |
Function: try_until_ok
Repeatedly executes a function until it succeeds (completes without raising a specified exception). This is the core retry mechanism used by get_valid_input and choose_from_list.
| Parameter | Type | Default | Description |
|---|---|---|---|
func |
Callable |
The function to execute repeatedly. | |
*args, **kwargs |
Any |
Positional and keyword arguments passed directly to func. |
|
exceptions |
tuple[Type] | Type |
Exception |
The exception type(s) to catch. If a function raises one of these, it will retry. If it raises anything else, the program will crash (as intended). |
on_exception |
str | Callable | None |
None |
The action to take when a caught error occurs before retrying: - str: The message to print. - Callable: A function to call with the exception object (Callable[[BaseException], Any]). |
| Handles | Gracefully intercepts KeyboardInterrupt (Ctrl+C) to exit the program (status code 0). |
Safe Converters
These functions safely convert strings to numeric types without raising exceptions (ValueError).
| Function | Description | Returns | Notes |
|---|---|---|---|
safe_int(string) |
Safely converts a string to an integer. | int if conversion succeeds, else None. |
|
safe_float(string) |
Safely converts a string to a float (number with decimal point). | float if conversion succeeds, else None. |
Used by numeric validators (is_in_range, more, etc.). |
String and RegEx Processing
| Function | Description | Key Arguments | Returns |
|---|---|---|---|
split() |
Splits an input string into a list of elements using a specified delimiter, and optionally converts each element. | string: strsplit_by: None | str | re.Pattern = Noneconverter: Callable[[str], Any] | None = None |
List of parsed and optionally converted elements. |
extract_match() |
Performs a RegEx search (re.search) and returns a list where each element corresponds to a captured group in the pattern. |
string: strpattern: re.Patternpos: int = 0endpos: int = sys.maxsize,converter: Callable[[str], Any] | None = None` |
List of captured groups, optionally converted. Returns [] if no match found. |
String Formatting
These functions implement the core formatting logic used by print_iterable and print_zipped_iterable. They return strings instead of printing them, offering flexibility for logging or file writing.
| Function | Description | Key Arguments | Returns |
|---|---|---|---|
format_iterable() |
Formats elements of a one-dimensional iterable into a single string. | iterable: Iterable[Any]item_pattern: str = '{}'join_by: str = '\n'start: str = ''end: str = '' |
The formatted str. |
format_zipped_iterable() |
Formats unpackable elements (pairs, tuples) into a single string using *item unpacking syntax. |
iterable: Iterable[Any]item_pattern: str = '{}'join_by: str = '\n'start: str = ''end: str = '' |
The formatted str. |
patterns.py
This module contains a set of precompiled regular expressions (re.Pattern) for common data formats and helper functions.
| Constant | Description | Match Example |
|---|---|---|
ANY |
Matches any string (including empty ones). | "", "text", "123" |
INT |
Integer number (optionally prefixed with + or -). |
10, -5, +42 |
FLOAT |
Floating-point number (must contain a decimal point). | 3.14, -0.01 |
NUMBER |
Universal number (matches both INT and FLOAT). |
42, -3.14, 0 |
USERNAME |
Variable name or identifier... | user_01, varName |
EMAIL |
Basic email format. | user@example.com |
DATE_DMY |
Date in DD.MM.YYYY format. | 31.12.2023 |
DATE_YMD |
Date in YYYY-MM-DD format. | 2023-12-31 |
TIME_24H |
Time in HH:MM format (24-hour clock). | 14:30, 09:05, 23:59 |
Helper Functions
in_line(pattern: re.Pattern): Wraps the provided pattern with start (^) and end ($) anchors.in_group(pattern: re.Pattern): Wraps the provided pattern in a capture group(...).
validators.py
This module contains validator factories. These functions return a validator (Callable[[str], bool]) that is passed as the validator argument to valid_input or get_valid_input.
Lists & Collections
is_list_of(pattern, split_by=None): Generates a validator that checks if a string is a list of elements with a specific format.is_in_list(options, case_sensitive=True): Generates a validator that checks if the input string exists in a provided list of options.
Numeric Comparisons
| Function | Logic | Description |
|---|---|---|
is_in_range(start, end) |
start <= x <= end |
Checks if a number is within the inclusive closed range ([start, end]). |
is_between(start, end) |
start < x < end |
Checks if a number is within the exclusive open interval ((start, end)). |
more(limit) |
x > limit |
Checks if the number is strictly greater than the limit (exclusive). |
more_or_equal(limit) |
x >= limit |
Checks if the number is greater than or equal to the limit (inclusive). |
less(limit) |
x < limit |
Checks if the number is strictly less than the limit (exclusive). |
less_or_equal(limit) |
x <= limit |
Checks if the number is less than or equal to the limit (inclusive). |
exceptions.py
This module provides the core exception classes used across the library. A clear exception hierarchy ensures that users can precisely handle user input errors (ValidationError) separately from programmer errors (APIError, ConversionError).
Hierarchy
The exceptions are structured with CLIError as the base class, allowing users to catch all library-related errors easily.
| Class | Inherits From | Description | Responsibility |
|---|---|---|---|
CLIError |
Exception |
Base class for all custom exceptions raised by the cli_tools library. |
General Catch-all |
APIError |
CLIError |
Raised when a function is called with invalid or impossible arguments (e.g., passing an empty list to choose_from_list). |
Programmer Error (API Usage) |
ValidationError |
CLIError |
Raised when the user input is invalid (does not match the RegEx pattern or fails the custom validator). | User Input Error (Caught for Retries) |
ConversionError |
CLIError |
Raised when the designated converter function fails (e.g., int() cannot process a string). Crucially: The valid_input function internally catches standard Python exceptions (ValueError, TypeError) and re-raises them as ConversionError. This signals a programmer error in logic, and it is NOT automatically caught for retries by get_valid_input. |
Programmer Error (Conversion Logic) |
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 cli_tools_by_oleksa-1.2.0.tar.gz.
File metadata
- Download URL: cli_tools_by_oleksa-1.2.0.tar.gz
- Upload date:
- Size: 14.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d80b0565d6d79523a6008adf2cc40ac7466ac911a992428ad71002fc024e786a
|
|
| MD5 |
8ad0fbc2cd11087e11827b9169000afa
|
|
| BLAKE2b-256 |
4757a042c462d28090f1be870a59b23163376be83594c8a383b6eed97124ba62
|
File details
Details for the file cli_tools_by_oleksa-1.2.0-py3-none-any.whl.
File metadata
- Download URL: cli_tools_by_oleksa-1.2.0-py3-none-any.whl
- Upload date:
- Size: 16.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9677c36c386318df0bbb846936a971c7017bcd9365e89e990a0520c1c0123ef5
|
|
| MD5 |
13cc2196cd2a9a80a08b917cec2cd1df
|
|
| BLAKE2b-256 |
40722df0ba4ebdf3d8563b5dc89d784c3a0e78604613afd506d7aab502c1c382
|