def test_filter_kwargs_empty(): def f(): pass kwargs = { "a": 1, "b": 2, } filtered = utils.used_kwargs(kwargs, f) assert filtered == {}
def test_filter_kwargs(): def f(a, b=1, *, c=2): pass kwargs = { "a": 1, "b": 2, "c": 3, "d": 4, } filtered = utils.used_kwargs(kwargs, f) assert "a" in filtered assert "b" in filtered assert "c" in filtered assert "d" not in filtered
def select( message: str, choices: Sequence[Union[str, Choice, Dict[str, Any]]], default: Optional[Union[str, Choice, Dict[str, Any]]] = None, qmark: str = DEFAULT_QUESTION_PREFIX, pointer: Optional[str] = DEFAULT_SELECTED_POINTER, style: Optional[Style] = None, use_shortcuts: bool = False, use_arrow_keys: bool = True, use_indicator: bool = False, use_jk_keys: bool = True, show_selected: bool = False, instruction: Optional[str] = None, **kwargs: Any, ) -> Question: """A list of items to select **one** option from. The user can pick one option and confirm it (if you want to allow the user to select multiple options, use :meth:`questionary.checkbox` instead). Example: >>> import questionary >>> questionary.select( ... "What do you want to do?", ... choices=[ ... "Order a pizza", ... "Make a reservation", ... "Ask for opening hours" ... ]).ask() ? What do you want to do? Order a pizza 'Order a pizza' .. image:: ../images/select.gif This is just a really basic example, the prompt can be customised using the parameters. Args: message: Question text choices: Items shown in the selection, this can contain :class:`Choice` or or :class:`Separator` objects or simple items as strings. Passing :class:`Choice` objects, allows you to configure the item more (e.g. preselecting it or disabling it). default: A value corresponding to a selectable item in the choices, to initially set the pointer position to. qmark: Question prefix displayed in front of the question. By default this is a ``?``. pointer: Pointer symbol in front of the currently highlighted element. By default this is a ``»``. Use ``None`` to disable it. instruction: A hint on how to navigate the menu. It's ``(Use shortcuts)`` if only ``use_shortcuts`` is set to True, ``(Use arrow keys or shortcuts)`` if ``use_arrow_keys`` & ``use_shortcuts`` are set and ``(Use arrow keys)`` by default. style: A custom color and style for the question parts. You can configure colors as well as font types for different elements. use_indicator: Flag to enable the small indicator in front of the list highlighting the current location of the selection cursor. use_shortcuts: Allow the user to select items from the list using shortcuts. The shortcuts will be displayed in front of the list items. Arrow keys, j/k keys and shortcuts are not mutually exclusive. use_arrow_keys: Allow the user to select items from the list using arrow keys. Arrow keys, j/k keys and shortcuts are not mutually exclusive. use_jk_keys: Allow the user to select items from the list using `j` (down) and `k` (up) keys. Arrow keys, j/k keys and shortcuts are not mutually exclusive. show_selected: Display current selection choice at the bottom of list. Returns: :class:`Question`: Question instance, ready to be prompted (using ``.ask()``). """ if not (use_arrow_keys or use_shortcuts or use_jk_keys): raise ValueError( "Some option to move the selection is required. Arrow keys, j/k keys or shortcuts." ) if use_shortcuts and use_jk_keys: if any(getattr(c, "shortcut_key", "") in ["j", "k"] for c in choices): raise ValueError( "A choice is trying to register j/k as a " "shortcut key when they are in use as arrow keys " "disable one or the other." ) if choices is None or len(choices) == 0: raise ValueError("A list of choices needs to be provided.") if use_shortcuts and len(choices) > len(InquirerControl.SHORTCUT_KEYS): raise ValueError( "A list with shortcuts supports a maximum of {} " "choices as this is the maximum number " "of keyboard shortcuts that are available. You" "provided {} choices!" "".format(len(InquirerControl.SHORTCUT_KEYS), len(choices)) ) merged_style = merge_styles([DEFAULT_STYLE, style]) ic = InquirerControl( choices, default, pointer=pointer, use_indicator=use_indicator, use_shortcuts=use_shortcuts, show_selected=show_selected, use_arrow_keys=use_arrow_keys, initial_choice=default, ) def get_prompt_tokens(): # noinspection PyListCreation tokens = [("class:qmark", qmark), ("class:question", " {} ".format(message))] if ic.is_answered: if isinstance(ic.get_pointed_at().title, list): tokens.append( ( "class:answer", "".join([token[1] for token in ic.get_pointed_at().title]), ) ) else: tokens.append(("class:answer", ic.get_pointed_at().title)) else: if instruction: tokens.append(("class:instruction", instruction)) else: if use_shortcuts and use_arrow_keys: instruction_msg = "(Use shortcuts or arrow keys)" elif use_shortcuts and not use_arrow_keys: instruction_msg = "(Use shortcuts)" else: instruction_msg = "(Use arrow keys)" tokens.append(("class:instruction", instruction_msg)) return tokens layout = common.create_inquirer_layout(ic, get_prompt_tokens, **kwargs) bindings = KeyBindings() @bindings.add(Keys.ControlQ, eager=True) @bindings.add(Keys.ControlC, eager=True) def _(event): event.app.exit(exception=KeyboardInterrupt, style="class:aborting") if use_shortcuts: # add key bindings for choices for i, c in enumerate(ic.choices): if c.shortcut_key is None and not c.disabled and not use_arrow_keys: raise RuntimeError( "{} does not have a shortcut and arrow keys " "for movement are disabled. " "This choice is not reachable.".format(c.title) ) if isinstance(c, Separator) or c.shortcut_key is None: continue # noinspection PyShadowingNames def _reg_binding(i, keys): # trick out late evaluation with a "function factory": # https://stackoverflow.com/a/3431699 @bindings.add(keys, eager=True) def select_choice(event): ic.pointed_at = i _reg_binding(i, c.shortcut_key) def move_cursor_down(event): ic.select_next() while not ic.is_selection_valid(): ic.select_next() def move_cursor_up(event): ic.select_previous() while not ic.is_selection_valid(): ic.select_previous() if use_arrow_keys: bindings.add(Keys.Down, eager=True)(move_cursor_down) bindings.add(Keys.Up, eager=True)(move_cursor_up) if use_jk_keys: bindings.add("j", eager=True)(move_cursor_down) bindings.add("k", eager=True)(move_cursor_up) @bindings.add(Keys.ControlM, eager=True) def set_answer(event): ic.is_answered = True event.app.exit(result=ic.get_pointed_at().value) @bindings.add(Keys.Any) def other(event): """Disallow inserting other text. """ pass return Question( Application( layout=layout, key_bindings=bindings, style=merged_style, **utils.used_kwargs(kwargs, Application.__init__), ) )
def select( message: str, choices: Sequence[Union[str, Choice, Dict[str, Any]]], default: Optional[Union[str, Choice, Dict[str, Any]]] = None, qmark: str = DEFAULT_QUESTION_PREFIX, style: Optional[Style] = None, use_shortcuts: bool = False, use_arrow_keys: bool = True, use_indicator: bool = False, use_pointer: bool = True, instruction: Optional[str] = None, **kwargs: Any, ) -> Question: """Prompt the user to select one item from the list of choices. The user can only select one option. Args: message: Question text choices: Items shown in the selection, this can contain `Choice` or or `Separator` objects or simple items as strings. Passing `Choice` objects, allows you to configure the item more (e.g. preselecting it or disabeling it). default: A value corresponding to a selectable item in the choices, to initially set the pointer position to. qmark: Question prefix displayed in front of the question. By default this is a `?` instruction: A hint on how to navigate the menu. It's `(Use shortcuts)` if only `use_shortcuts` is set to True, `(Use arrow keys or shortcuts)` if `use_arrow_keys` & `use_shortcuts` are set and `(Use arrow keys)` by default. style: A custom color and style for the question parts. You can configure colors as well as font types for different elements. use_indicator: Flag to enable the small indicator in front of the list highlighting the current location of the selection cursor. use_shortcuts: Allow the user to select items from the list using shortcuts. The shortcuts will be displayed in front of the list items. use_arrow_keys: Allow usage of arrow keys to select item. use_pointer: Flag to enable the pointer in front of the currently highlighted element. Returns: Question: Question instance, ready to be prompted (using `.ask()`). """ if choices is None or len(choices) == 0: raise ValueError("A list of choices needs to be provided.") if use_shortcuts and len(choices) > len(InquirerControl.SHORTCUT_KEYS): raise ValueError("A list with shortcuts supports a maximum of {} " "choices as this is the maximum number " "of keyboard shortcuts that are available. You" "provided {} choices!" "".format(len(InquirerControl.SHORTCUT_KEYS), len(choices))) merged_style = merge_styles([DEFAULT_STYLE, style]) ic = InquirerControl( choices, default, use_indicator=use_indicator, use_shortcuts=use_shortcuts, use_arrow_keys=use_arrow_keys, use_pointer=use_pointer, initial_choice=default, ) def get_prompt_tokens(): # noinspection PyListCreation tokens = [("class:qmark", qmark), ("class:question", " {} ".format(message))] if ic.is_answered: if isinstance(ic.get_pointed_at().title, list): tokens.append(( "class:answer", "".join([token[1] for token in ic.get_pointed_at().title]), )) else: tokens.append( ("class:answer", " " + ic.get_pointed_at().title)) else: if instruction: tokens.append(("class:instruction", instruction)) else: if use_shortcuts and use_arrow_keys: instruction_msg = " (Use shortcuts or arrow keys)" elif use_shortcuts and not use_arrow_keys: instruction_msg = " (Use shortcuts)" else: instruction_msg = " (Use arrow keys)" tokens.append(("class:instruction", instruction_msg)) return tokens layout = common.create_inquirer_layout(ic, get_prompt_tokens, **kwargs) bindings = KeyBindings() @bindings.add(Keys.ControlQ, eager=True) @bindings.add(Keys.ControlC, eager=True) def _(event): event.app.exit(exception=KeyboardInterrupt, style="class:aborting") if use_shortcuts: # add key bindings for choices for i, c in enumerate(ic.choices): if isinstance(c, Separator): continue # noinspection PyShadowingNames def _reg_binding(i, keys): # trick out late evaluation with a "function factory": # https://stackoverflow.com/a/3431699 @bindings.add(keys, eager=True) def select_choice(event): ic.pointed_at = i _reg_binding(i, c.shortcut_key) if use_arrow_keys or use_shortcuts is False: @bindings.add(Keys.Down, eager=True) @bindings.add("j", eager=True) def move_cursor_down(event): ic.select_next() while not ic.is_selection_valid(): ic.select_next() @bindings.add(Keys.Up, eager=True) @bindings.add("k", eager=True) def move_cursor_up(event): ic.select_previous() while not ic.is_selection_valid(): ic.select_previous() @bindings.add(Keys.ControlM, eager=True) def set_answer(event): ic.is_answered = True event.app.exit(result=ic.get_pointed_at().value) @bindings.add(Keys.Any) def other(event): """Disallow inserting other text. """ pass return Question( Application( layout=layout, key_bindings=bindings, style=merged_style, **utils.used_kwargs(kwargs, Application.__init__), ))
def checkbox( message: str, choices: Sequence[Union[str, Choice, Dict[str, Any]]], default: Optional[str] = None, validate: Callable[[List[str]], Union[bool, str]] = lambda a: True, qmark: str = DEFAULT_QUESTION_PREFIX, style: Optional[Style] = None, use_pointer: bool = True, initial_choice: Optional[Union[str, Choice, Dict[str, Any]]] = None, **kwargs: Any, ) -> Question: """Ask the user to select from a list of items. This is a multiselect, the user can choose one, none or many of the items. Example: >>> import questionary >>> questionary.checkbox( ... 'Select toppings', ... choices=[ ... "Cheese", ... "Tomato", ... "Pineapple", ... ]).ask() ? Select toppings done (2 selections) ['Cheese', 'Pineapple'] .. image:: ../images/checkbox.gif This is just a realy basic example, the prompt can be customised using the parameters. Args: message: Question text choices: Items shown in the selection, this can contain :class:`Choice` or or :class:`Separator` objects or simple items as strings. Passing :class:`Choice` objects, allows you to configure the item more (e.g. preselecting it or disabling it). default: Default return value (single value). If you want to preselect multiple items, use ``Choice("foo", checked=True)`` instead. validate: Require the entered value to pass a validation. The value can not be submitted until the validator accepts it (e.g. to check minimum password length). This should be a function accepting the input and returning a boolean. Alternatively, the return value may be a string (indicating failure), which contains the error message to be displayed. qmark: Question prefix displayed in front of the question. By default this is a ``?``. style: A custom color and style for the question parts. You can configure colors as well as font types for different elements. use_pointer: Flag to enable the pointer in front of the currently highlighted element. initial_choice: A value corresponding to a selectable item in the choices, to initially set the pointer position to. Returns: :class:`Question`: Question instance, ready to be prompted (using ``.ask()``). """ merged_style = merge_styles([ DEFAULT_STYLE, # Disable the default inverted colours bottom-toolbar behaviour (for # the error message). However it can be re-enabled with a custom # style. Style([("bottom-toolbar", "noreverse")]), style, ]) if not callable(validate): raise ValueError("validate must be callable") ic = InquirerControl(choices, default, use_pointer=use_pointer, initial_choice=initial_choice) def get_prompt_tokens() -> List[Tuple[str, str]]: tokens = [] tokens.append(("class:qmark", qmark)) tokens.append(("class:question", " {} ".format(message))) if ic.is_answered: nbr_selected = len(ic.selected_options) if nbr_selected == 0: tokens.append(("class:answer", "done")) elif nbr_selected == 1: if isinstance(ic.get_selected_values()[0].title, list): ts = ic.get_selected_values()[0].title tokens.append(( "class:answer", "".join([token[1] for token in ts]), # type:ignore )) else: tokens.append(( "class:answer", "[{}]".format(ic.get_selected_values()[0].title), )) else: tokens.append(("class:answer", "done ({} selections)".format(nbr_selected))) else: tokens.append(( "class:instruction", "(Use arrow keys to move, " "<space> to select, " "<a> to toggle, " "<i> to invert)", )) return tokens def get_selected_values() -> List[Any]: return [c.value for c in ic.get_selected_values()] def perform_validation(selected_values: List[str]) -> bool: verdict = validate(selected_values) valid = verdict is True if not valid: if verdict is False: error_text = INVALID_INPUT else: error_text = str(verdict) error_message = FormattedText([("class:validation-toolbar", error_text)]) ic.error_message = (error_message if not valid and ic.submission_attempted else None) return valid layout = common.create_inquirer_layout(ic, get_prompt_tokens, **kwargs) bindings = KeyBindings() @bindings.add(Keys.ControlQ, eager=True) @bindings.add(Keys.ControlC, eager=True) def _(event): event.app.exit(exception=KeyboardInterrupt, style="class:aborting") @bindings.add(" ", eager=True) def toggle(_event): pointed_choice = ic.get_pointed_at().value if pointed_choice in ic.selected_options: ic.selected_options.remove(pointed_choice) else: ic.selected_options.append(pointed_choice) perform_validation(get_selected_values()) @bindings.add("i", eager=True) def invert(_event): inverted_selection = [ c.value for c in ic.choices if not isinstance(c, Separator) and c.value not in ic.selected_options and not c.disabled ] ic.selected_options = inverted_selection perform_validation(get_selected_values()) @bindings.add("a", eager=True) def all(_event): all_selected = True # all choices have been selected for c in ic.choices: if (not isinstance(c, Separator) and c.value not in ic.selected_options and not c.disabled): # add missing ones ic.selected_options.append(c.value) all_selected = False if all_selected: ic.selected_options = [] perform_validation(get_selected_values()) @bindings.add(Keys.Down, eager=True) @bindings.add("j", eager=True) def move_cursor_down(_event): ic.select_next() while not ic.is_selection_valid(): ic.select_next() @bindings.add(Keys.Up, eager=True) @bindings.add("k", eager=True) def move_cursor_up(_event): ic.select_previous() while not ic.is_selection_valid(): ic.select_previous() @bindings.add(Keys.ControlM, eager=True) def set_answer(event): selected_values = get_selected_values() ic.submission_attempted = True if perform_validation(selected_values): ic.is_answered = True event.app.exit(result=selected_values) @bindings.add(Keys.Any) def other(_event): """Disallow inserting other text. """ pass return Question( Application( layout=layout, key_bindings=bindings, style=merged_style, **utils.used_kwargs(kwargs, Application.__init__), ))