More Examples
select/select_multiple
Functionality
Return index
Selective elements default to return the selected item (in case of select) or list of items (in case of select_multiple). This behavior can be modified by return_index parameter (or return_indices in case of the latter), see example,
result_index = select(options=['I\'ll be returned as 0', 'I\'ll be returned as 1'],
return_index=True)
Starting cursor index
By default cursor is placed on the first element, this can be configured by cursor_index parameter as follows,
results = select(['Not here either', 'Not here', 'Start from here'],
cursor_index=1)
Preticked indices for select_multiple
You can have preticked options using ticked_indices in select_multiple:
loved_children = select_multiple(['Oldest child', 'Middle child', 'Youngest Child'],
ticked_indices=[0,2])
Maximal and minimal count for select_multiple
With select_multiple you can restrict maximum and minimum count of elements using maximal_count and minimal_count respectively,
pizza_toppings = select_multiple(['pineapple', 'olives', 'anchovies', 'mozzarella', 'parma ham']
maximal_count=3,
minimal_count=1)
Styling
Tip
For styling you can leverage numerous styling options provided by rich
Style as text
stylish = select(options = ["red", "on", "white"],
cursor = "x",
cursor_style= "red on white")
Style as hex
selections = select_multiple(options = ["s", "h", "e", "", "b", "e", "l", "i", "e", "v", "e", "d"],
tick_style="#af00ff",
ticked_indices=[1,2,6,7,8,11])
Cursor characters
Emoji as a cursor
Bug
Some emojis can appear as one character instead of two!
result = select(options = ["here", "comes", "the", "sun"],
cursor = "🌞")
Non-ascii as a cursor
result = select(options = ["hardcore", "unicode"],
cursor = "⇉")
Multi-character cursors/ticks
Tip
You can use multiple characters as a cursor
correct_abba_lyric = select_multiple(options = ["queen", "bean"],
tick_character = "dancing")
prompt
Functionality
You can have a default prompt, which will collect the user typed response to the mood variable as string
mood = prompt("How are you today?")
Validation
Additionally, you can validate the input using some sort of Callable validator, for example a lambda expression to make sure input is not numeric,
answer = prompt(prompt="What is the answer to life the universe and everything?"
validator=lambda val: not val.isnumeric())
Type Conversion
You might want to convert types for some sort of downstream functionality using target_type,
Note
Validation is always second to type conversion
number_between_1_and_10 = prompt("Give me a number between 1 and 10",
target_type=int
validator=lambda n: 0 < n <= 10)
Hidden/secure input
For sensitive input, secure flag can be utilized, replacing user entered input with *
very_secret_info = prompt("Type you API key, hehe",
secure=True)
Completion
You can provide a python callable such as Callable[[str], List[str]] to provide completion options. String passed to the callable is the current user input.
favorite_color = prompt("What is your favorite color?",
completion=lambda _: ["pink", "PINK", "P1NK"])
A more complex example with path completion:
from os import listdir
from pathlib import Path
# ugly hacky path completion callable:
def path_completion(str_path: str = ""):
if not str_path:
return []
try:
path = Path(str_path)
rest = ''
if not path.exists():
str_path, rest = str_path.rsplit('/', 1)
path = Path(str_path or '/')
filtered_list_dir = [i for i in listdir(path) if i.startswith(rest)]
if not path.is_absolute():
return ['./'+str(Path(path)/i) for i in filtered_list_dir]
else:
return [str(Path(path)/i) for i in filtered_list_dir]
except Exception as e:
return []
prompt(">", completion=path_completion)
Spinners
Styling
Spinner Animation
There are few built in spinner animations, namely: ARC, ARROWS, BARS, CLOCK, DIAMOND, DOT, DOTS, LINE, LOADING and MOON
Each of these can be used in a spinner:
from beaupy.spinners import Spinner, ARC
spinner = Spinner(ARC, "Doing some heavy work")
spinner.start()
All that "animations" are, is but a list of string, so making your own is as trivial as this:
from beaupy.spinners import Spinner
spinner = Spinner(['whee', 'whe ', 'wh ', 'w ', 'wh ', 'whe ', 'whee'], "Whee!")
spinner.start()
Rich styling
Every text in spinner does accept and respect rich styles, so the following works:
from beaupy.spinners import Spinner
spinner = Spinner(['[red]⬤[/red] ', '[green]⬤[/green] ', '[blue]⬤[/blue] '], '[pink1]Setting[/pink1] colors!')
spinner.start()
Animation speed
Animation speed can be set using refresh_per_second parameter:
from beaupy.spinners import Spinner, LOADING
spinner = Spinner(LOADING, "something", refresh_per_second=4)
spinner.start()
Configuration
Each element accepts a config argument taking a Config instance. Options:
raise_on_interrupt: IfTrue, functions will raiseKeyboardInterruptwhenever one is encountered when waiting for input, otherwise, they will return some sane alternative to their usual return. Forselect,promptandconfirmthis meansNone, while forselect_multipleit means an empty list -[]. Defaults toFalse.raise_on_escape: IfTrue, functions will raiseAbortwhenever the escape key is encountered when waiting for input, otherwise, they will return some sane alternative to their usual return. Forselect,promptandconfirmthis meansNone, while forselect_multipleit means an empty list -[]. Defaults toFalse.transient: IfFalse, elements will remain displayed after their context has ended. Defaults toTrue.console:rich.console.Consolethe elements render to. Defaults toConsole(stderr=True, highlight=False).keys:KeyBindingsused by the elements. Defaults toKeyBindings().
from beaupy import Config, select
config = Config(raise_on_interrupt=True)
try:
result = select(['Option 1', 'Option 2'], config=config)
except KeyboardInterrupt:
print("User pressed Ctrl+C")
Using your own console
Pass the console you already use, e.g. the one of a running rich.live.Live, so the two don't fight over the terminal:
from rich.console import Console
from rich.live import Live
from rich.panel import Panel
from beaupy import Config, select_multiple
console = Console()
with Live(Panel("Foo"), console=console):
select_multiple(list(range(15)), config=Config(console=console))
Keybindings
from beaupy import Config, KeyBindings, select
vim = Config(keys=KeyBindings(up=['k'], down=['j']))
select(['one', 'two'], config=vim)
Title, instructions and sections
select and select_multiple take a title shown above the options. All elements take instructions shown below; pass None to hide it.
select(['red', 'green'], title='Pick a color', instructions='([bold]enter[/bold] to pick)')
Pass a dict to show options in sections. Indices (cursor_index, ticked_indices, return_index, return_indices)
are then always (section_name, index_within_section) tuples; a plain int raises TypeError (and is flagged by type checkers):
select_multiple({'Fruit': ['apple', 'pear'], 'Veg': ['leek']},
ticked_indices=[('Veg', 0)],
return_indices=True) # e.g. [('Veg', 0), ('Fruit', 1)]
Filtering
With filterable=True, typing narrows the options to those whose displayed text contains what was typed (case-insensitive,
markup ignored). Backspace removes the last typed character; the query is shown next to the title.
select(['apple', 'banana', 'cherry'], title='Fruit', filterable=True)
Keys bound in KeyBindings keep their action, so in select_multiple space still ticks and ctrl+a ticks/unticks all visible options.
Ticked options stay ticked while hidden by the filter.
Migrating from 3.x
4.0.0 removes process-wide state and makes every argument after the first keyword-only.
| 3.x | 4.x |
|---|---|
Config.raise_on_escape = True |
select(..., config=Config(raise_on_escape=True)) |
beaupy._beaupy.console = my_console |
select(..., config=Config(console=my_console)) |
DefaultKeys.up.append('k') |
select(..., config=Config(keys=KeyBindings(up=[Keys.UP_ARROW, 'k']))) |
select(options, my_preprocessor) |
select(options, preprocessor=my_preprocessor) |
a ticks/unticks all in select_multiple |
ctrl+a; restore with KeyBindings(select_all=['a']) |
Keys above comes from yakh.key. Using a removed global raises RemovedInV4Error (or AttributeError when assigning on Config/KeyBindings) with a message explaining the replacement.