[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/pythonnative/pythonnative/main/src/pythonnative/alerts.py [Back]  [Original]

"""Imperative, awaitable system alerts.

Inspired by React Native's ``Alert.alert()`` but designed around
``async`` / ``await`` instead of per-button callbacks. There are three
entry points:

- [`Alert.show`][pythonnative.alerts.Alert.show]: fire-and-forget
  one-button notice (no return value).
- [`Alert.confirm`][pythonnative.alerts.Alert.confirm]: awaitable
  two-button yes/no, resolves to a ``bool``.
- [`Alert.choose`][pythonnative.alerts.Alert.choose]: awaitable
  multi-button picker / action sheet, resolves to the selected
  label (or ``None`` if dismissed).

Example:
    ```python
    import pythonnative as pn


    async def maybe_delete():
        if await pn.Alert.confirm(
            title="Delete item?",
            message="This action cannot be undone.",
            confirm_label="Delete",
            cancel_label="Keep",
        ):
            await delete_item()
    ```
"""

from __future__ import annotations

from typing import Any, Dict, List, Optional, Sequence

from .native_modules.registry import native_module

# ======================================================================
# Internal dispatch helpers
# ======================================================================


async def _present(
    *,
    title: str,
    message: Optional[str],
    buttons: List[Dict[str, Any]],
    style: str,
) -> int:
    """Ask the native ``Alert`` module to present and await the chosen index.

    ``buttons`` is a list of ``{"label": str, "style":
    "default"|"cancel"|"destructive"}`` dicts. The module resolves with
    the index the user picked, or ``-1`` if the dialog was dismissed
    without a selection. Off device the
    [`FallbackAlert`][pythonnative.native_modules.fallback.FallbackAlert]
    implementation records the call and answers from the queue set by
    [`Alert.set_test_response`][pythonnative.alerts.Alert.set_test_response].
    """
    result = await native_module("Alert").call_async(
        "present", title=title, message=message, buttons=buttons, style=style
    )
    try:
        return int(result)
    except (TypeError, ValueError):
        return -1


# ======================================================================
# Public Alert API
# ======================================================================


class Alert:
    """Imperative alert / action-sheet helper.

    All methods are static. Use [`show`][pythonnative.alerts.Alert.show]
    for a fire-and-forget single-button notice,
    [`confirm`][pythonnative.alerts.Alert.confirm] for an awaitable
    yes/no dialog, and
    [`choose`][pythonnative.alerts.Alert.choose] for a multi-option
    picker.
    """

    #: Records every alert call when running off-device. Tests reset
    #: this between cases via ``Alert._test_log.clear()``. Each entry
    #: contains ``title``, ``message``, ``buttons``, and ``style``.
    _test_log: List[Dict[str, Any]] = []

    #: Queue of indices to deliver to upcoming alerts in tests. Set via
    #: [`Alert.set_test_response`][pythonnative.alerts.Alert.set_test_response].
    #: A negative value (or empty queue) simulates a dismiss.
    _test_responses: List[int] = []

    @staticmethod
    def set_test_response(*indices: int) -> None:
        """Queue indices to return from upcoming test alerts.

        Use in async tests to script the user's choices: each pending
        call to [`confirm`][pythonnative.alerts.Alert.confirm] or
        [`choose`][pythonnative.alerts.Alert.choose] pops the next
        queued index. Pass ``-1`` to simulate a dismiss.

        Args:
            *indices: Sequence of button indices to deliver, oldest
                first. Calls beyond the queue length resolve to ``-1``.
        """
        Alert._test_responses[:] = list(indices)

    @staticmethod
    def _next_test_response() -> int:
        if Alert._test_responses:
            return Alert._test_responses.pop(0)
        return -1

    @staticmethod
    def show(
        title: str,
        message: Optional[str] = None,
        *,
        button: str = "OK",
    ) -> None:
        """Display a simple, one-button alert and return immediately.

        Args:
            title: Dialog title.
            message: Optional body text.
            button: Label for the single dismiss button (default
                ``"OK"``).

        This is fire-and-forget. To know what the user did, use
        [`confirm`][pythonnative.alerts.Alert.confirm] or
        [`choose`][pythonnative.alerts.Alert.choose] and ``await``
        the result.
        """
        native_module("Alert").call(
            "show", title=title, message=message, buttons=[{"label": button, "style": "default"}], 
        )

    @staticmethod
    async def confirm(
        title: str,
        message: Optional[str] = None,
        *,
        confirm_label: str = "OK",
        cancel_label: str = "Cancel",
    ) -> bool:
        """Present a two-button yes/no dialog and wait for the choice.

        Args:
            title: Dialog title.
            message: Optional body text.
            confirm_label: Label for the "yes" button (default
                ``"OK"``).
            cancel_label: Label for the "no" button (default
                ``"Cancel"``).

        Returns:
            ``True`` if the user pressed the confirm button, ``False``
            for the cancel button or a dismiss.

        Example:
            ```python
            if await pn.Alert.confirm("Save changes?"):
                await save()
            ```
        """
        index = await _present(
            title=title,
            message=message,
            buttons=[
                {"label": cancel_label, "style": "cancel"},
                {"label": confirm_label, "style": "default"},
            ],
            ,
        )
        return index == 1

    @staticmethod
    async def choose(
        title: str,
        options: Sequence[str],
        *,
        message: Optional[str] = None,
        cancel_label: Optional[str] = None,
        style: str = "action_sheet",
        destructive_labels: Sequence[str] = (),
    ) -> Optional[str]:
        """Present a multi-option picker and wait for the user's choice.

        Args:
            title: Dialog title.
            options: Sequence of option labels (in display order).
            message: Optional body text.
            cancel_label: If provided, adds a "cancel" button with
                this label. Selecting it resolves to ``None``.
            style: ``"action_sheet"`` (default) for an iOS-style
                sheet, or ``"alert"`` for a stacked alert dialog.
            destructive_labels: Labels in ``options`` that should be
                styled destructively (red on iOS).

        Returns:
            The selected label, or ``None`` if the user dismissed or
            tapped the cancel button.

        Example:
            ```python
            choice = await pn.Alert.choose(
                "Photo source",
                options=["Camera", "Gallery"],
                cancel_label="Cancel",
            )
            if choice == "Camera":
                ...
            ```
        """
        if not options:
            raise ValueError("Alert.choose requires at least one option")

        destructive = set(destructive_labels)
        buttons: List[Dict[str, Any]] = [
            {
                "label": opt,
                "style": "destructive" if opt in destructive else "default",
            }
            for opt in options
        ]
        if cancel_label is not None:
            buttons.append({"label": cancel_label, "style": "cancel"})

        index = await _present(title=title, message=message, buttons=buttons, style=style)
        if 0 

Web Proxy Viewer  |  New URL  |  Original Page