Skip to content

Prompt API Reference

Auto-generated API documentation for the prompt module.

prompt

Zero-dependency interactive CLI prompts (confirm, select, text).

Part of zerodep: https://github.com/Oaklight/zerodep Copyright (c) 2026 Peng Ding. MIT License.

Provides interactive command-line prompts similar to questionary, using only the Python standard library. Works on Linux, macOS (via termios/tty) and Windows (via msvcrt) with an automatic fallback to plain input() when a TTY is unavailable.

Basic usage::

answer = confirm("Continue?")
choice = select("Pick one:", ["a", "b", "c"])
name   = text("Your name:", validate=lambda s: True if s else "Required")

Style

ANSI styling configuration for prompts.

Users may supply a list of (role, style_string) tuples to customise how each visual element is rendered. Recognised roles are "question", "answer", "pointer", "highlighted", "error", and "instruction".

Example::

style = Style([
    ("question", "fg:#00ff00 bold"),
    ("answer",   "fg:#ffffff"),
])
confirm("Continue?", style=style)
Source code in prompt/prompt.py
class Style:
    """ANSI styling configuration for prompts.

    Users may supply a list of ``(role, style_string)`` tuples to customise
    how each visual element is rendered.  Recognised roles are ``"question"``,
    ``"answer"``, ``"pointer"``, ``"highlighted"``, ``"error"``, and
    ``"instruction"``.

    Example::

        style = Style([
            ("question", "fg:#00ff00 bold"),
            ("answer",   "fg:#ffffff"),
        ])
        confirm("Continue?", style=style)
    """

    def __init__(
        self,
        style_list: list[tuple[str, str]] | None = None,
        *,
        colors: bool | None = None,
    ) -> None:
        self._roles: dict[str, str] = dict(_DEFAULT_STYLES)
        if style_list:
            for role, sstr in style_list:
                self._roles[role] = _parse_style_string(sstr)
        self._colors = _supports_color() if colors is None else colors

    def apply(self, role: str, text: str) -> str:
        """Wrap *text* with the ANSI codes for *role* and a trailing reset.

        Args:
            role: One of the recognised role names.
            text: The text to style.

        Returns:
            The styled string with a trailing ANSI reset sequence,
            or plain *text* when colors are disabled.
        """
        if not self._colors:
            return text
        prefix = self._roles.get(role, "")
        if prefix:
            return f"{prefix}{text}{_ANSI_RESET}"
        return text

    def get(self, role: str) -> str:
        """Return raw ANSI prefix for *role* (empty string if unknown)."""
        return self._roles.get(role, "")

apply(role, text)

Wrap text with the ANSI codes for role and a trailing reset.

Parameters:

Name Type Description Default
role str

One of the recognised role names.

required
text str

The text to style.

required

Returns:

Type Description
str

The styled string with a trailing ANSI reset sequence,

str

or plain text when colors are disabled.

Source code in prompt/prompt.py
def apply(self, role: str, text: str) -> str:
    """Wrap *text* with the ANSI codes for *role* and a trailing reset.

    Args:
        role: One of the recognised role names.
        text: The text to style.

    Returns:
        The styled string with a trailing ANSI reset sequence,
        or plain *text* when colors are disabled.
    """
    if not self._colors:
        return text
    prefix = self._roles.get(role, "")
    if prefix:
        return f"{prefix}{text}{_ANSI_RESET}"
    return text

get(role)

Return raw ANSI prefix for role (empty string if unknown).

Source code in prompt/prompt.py
def get(self, role: str) -> str:
    """Return raw ANSI prefix for *role* (empty string if unknown)."""
    return self._roles.get(role, "")

confirm(message, default=True, style=None, *, input_stream=None, output_stream=None)

Ask a yes/no question.

Parameters:

Name Type Description Default
message str

The question to display.

required
default bool

Pre-selected answer (True → Yes).

True
style Style | None

Optional Style for visual customisation.

None
input_stream TextIO | None

Override for sys.stdin (useful in tests).

None
output_stream TextIO | None

Override for sys.stdout (useful in tests).

None

Returns:

Type Description
bool | None

True for yes, False for no, or None if the user

bool | None

cancelled with Ctrl-C.

Example::

if confirm("Proceed with installation?"):
    install()
Source code in prompt/prompt.py
def confirm(
    message: str,
    default: bool = True,
    style: Style | None = None,
    *,
    input_stream: TextIO | None = None,
    output_stream: TextIO | None = None,
) -> bool | None:
    """Ask a yes/no question.

    Args:
        message: The question to display.
        default: Pre-selected answer (``True`` → Yes).
        style: Optional ``Style`` for visual customisation.
        input_stream: Override for ``sys.stdin`` (useful in tests).
        output_stream: Override for ``sys.stdout`` (useful in tests).

    Returns:
        ``True`` for yes, ``False`` for no, or ``None`` if the user
        cancelled with Ctrl-C.

    Example::

        if confirm("Proceed with installation?"):
            install()
    """
    sty = style or _default_style()
    out = output_stream or sys.stdout
    inp = input_stream or sys.stdin

    hint = "(Y/n)" if default else "(y/N)"
    q_part = sty.apply("question", "? " + message)
    h_part = sty.apply("instruction", hint)
    prompt_str = f"{q_part} {h_part} "

    while True:
        try:
            out.write(prompt_str)
            out.flush()
            line = inp.readline()
            if not line:  # EOF
                return None
            result = _parse_confirm_input(line, default)
            if result is not None:
                answer_text = "Yes" if result else "No"
                out.write(f"{sty.apply('answer', answer_text)}\n")
                out.flush()
                return result
            # Unrecognised → re-prompt
            out.write("  Please answer y or n.\n")
            out.flush()
        except (KeyboardInterrupt, EOFError):
            out.write("\n")
            out.flush()
            return None

select(message, choices, default=None, style=None, *, input_stream=None, output_stream=None)

Show a list of choices with arrow-key navigation.

Parameters:

Name Type Description Default
message str

The question to display above the list.

required
choices list[str] | list[dict[str, str]]

Either plain strings or {"name": …, "value": …} dicts.

required
default str | None

Value to pre-select. Defaults to the first choice.

None
style Style | None

Optional Style for visual customisation.

None
input_stream TextIO | None

Override for sys.stdin (useful in tests).

None
output_stream TextIO | None

Override for sys.stdout (useful in tests).

None

Returns:

Type Description
str | None

The value of the selected choice, or None if cancelled.

Example::

lang = select("Language:", ["Python", "Rust", "Go"])
Source code in prompt/prompt.py
def select(
    message: str,
    choices: list[str] | list[dict[str, str]],
    default: str | None = None,
    style: Style | None = None,
    *,
    input_stream: TextIO | None = None,
    output_stream: TextIO | None = None,
) -> str | None:
    """Show a list of choices with arrow-key navigation.

    Args:
        message: The question to display above the list.
        choices: Either plain strings or ``{"name": …, "value": …}`` dicts.
        default: Value to pre-select.  Defaults to the first choice.
        style: Optional ``Style`` for visual customisation.
        input_stream: Override for ``sys.stdin`` (useful in tests).
        output_stream: Override for ``sys.stdout`` (useful in tests).

    Returns:
        The ``value`` of the selected choice, or ``None`` if cancelled.

    Example::

        lang = select("Language:", ["Python", "Rust", "Go"])
    """
    sty = style or _default_style()
    out: TextIO = output_stream or sys.stdout
    norm = _normalise_choices(choices)

    # Determine starting index
    index = 0
    if default is not None:
        for i, c in enumerate(norm):
            if c["value"] == default:
                index = i
                break

    use_tty = (
        input_stream is None
        and output_stream is None
        and _is_tty()
        and (not _IS_WINDOWS or msvcrt is not None)
        and (_IS_WINDOWS or termios is not None)
    )

    if not use_tty:
        return _select_fallback(message, norm, index, sty, input_stream, out)

    return _select_interactive(message, norm, index, sty, out)

text(message, default='', validate=None, style=None, *, input_stream=None, output_stream=None)

Prompt the user for free-form text input.

Parameters:

Name Type Description Default
message str

The question to display.

required
default str

Default value (used when the user presses Enter without typing anything).

''
validate Callable[[str], bool | str] | None

Optional callable that receives the current input string. Return True to accept, or a string to show as an error message and re-prompt.

None
style Style | None

Optional Style for visual customisation.

None
input_stream TextIO | None

Override for sys.stdin (useful in tests).

None
output_stream TextIO | None

Override for sys.stdout (useful in tests).

None

Returns:

Type Description
str | None

The entered string, or None if cancelled with Ctrl-C.

Example::

name = text("Your name:", validate=lambda s: True if s else "Name required")
Source code in prompt/prompt.py
def text(
    message: str,
    default: str = "",
    validate: Callable[[str], bool | str] | None = None,
    style: Style | None = None,
    *,
    input_stream: TextIO | None = None,
    output_stream: TextIO | None = None,
) -> str | None:
    """Prompt the user for free-form text input.

    Args:
        message: The question to display.
        default: Default value (used when the user presses Enter without
            typing anything).
        validate: Optional callable that receives the current input string.
            Return ``True`` to accept, or a string to show as an error
            message and re-prompt.
        style: Optional ``Style`` for visual customisation.
        input_stream: Override for ``sys.stdin`` (useful in tests).
        output_stream: Override for ``sys.stdout`` (useful in tests).

    Returns:
        The entered string, or ``None`` if cancelled with Ctrl-C.

    Example::

        name = text("Your name:", validate=lambda s: True if s else "Name required")
    """
    sty = style or _default_style()
    out = output_stream or sys.stdout
    inp = input_stream or sys.stdin

    default_hint = f" ({default})" if default else ""
    q_part = sty.apply("question", "? " + message)
    h_part = sty.apply("instruction", default_hint)
    prompt_str = f"{q_part}{h_part} "

    while True:
        try:
            out.write(prompt_str)
            out.flush()
            line = inp.readline()
            if not line:  # EOF
                return None
            value = line.rstrip("\n").rstrip("\r")
            if not value and default:
                value = default

            if validate is not None:
                result = validate(value)
                if result is True:
                    out.write(f"  {sty.apply('answer', value)}\n")
                    out.flush()
                    return value
                elif isinstance(result, str):
                    out.write(f"  {sty.apply('error', '✗ ' + result)}\n")
                    out.flush()
                    continue
                else:
                    # result is False or falsy but not a string → reject silently
                    out.write(f"  {sty.apply('error', '✗ Invalid input')}\n")
                    out.flush()
                    continue
            else:
                out.write(f"  {sty.apply('answer', value)}\n")
                out.flush()
                return value
        except (KeyboardInterrupt, EOFError):
            out.write("\n")
            out.flush()
            return None