Module slack_bolt.async_app

Module for creating asyncio based apps

Creating an async app

If you'd prefer to build your app with asyncio, you can import the AIOHTTP library and call the AsyncApp constructor. Within async apps, you can use the async/await pattern.

# Python 3.6+ required
python -m venv .venv
source .venv/bin/activate

pip install -U pip
# aiohttp is required
pip install slack_bolt aiohttp

In async apps, all middleware/listeners must be async functions. When calling utility methods (like ack and say) within these functions, it's required to use the await keyword.

# Import the async app instead of the regular one
from slack_bolt.async_app import AsyncApp

app = AsyncApp()

@app.event("app_mention")
async def event_test(body, say, logger):
    logger.info(body)
    await say("What's up?")

@app.command("/hello-bolt-python")
async def command(ack, body, respond):
    await ack()
    await respond(f"Hi <@{body['user_id']}>!")

if __name__ == "__main__":
    app.start(3000)

If you want to use another async Web framework (e.g., Sanic, FastAPI, Starlette), take a look at the built-in adapters and their examples.

Refer to slack_bolt.app.async_app for more details.

Classes

class AsyncAck
Expand source code
class AsyncAck:
    response: Optional[BoltResponse]

    def __init__(self):
        self.response: Optional[BoltResponse] = None

    async def __call__(
        self,
        text: Union[str, dict] = "",  # text: str or whole_response: dict
        blocks: Optional[Sequence[Union[dict, Block]]] = None,
        attachments: Optional[Sequence[Union[dict, Attachment]]] = None,
        unfurl_links: Optional[bool] = None,
        unfurl_media: Optional[bool] = None,
        response_type: Optional[str] = None,  # in_channel / ephemeral
        # block_suggestion / dialog_suggestion
        options: Optional[Sequence[Union[dict, Option]]] = None,
        option_groups: Optional[Sequence[Union[dict, OptionGroup]]] = None,
        # view_submission
        response_action: Optional[str] = None,  # errors / update / push / clear
        errors: Optional[Dict[str, str]] = None,
        view: Optional[Union[dict, View]] = None,
    ) -> BoltResponse:
        return _set_response(
            self,
            text_or_whole_response=text,
            blocks=blocks,
            attachments=attachments,
            unfurl_links=unfurl_links,
            unfurl_media=unfurl_media,
            response_type=response_type,
            options=options,
            option_groups=option_groups,
            response_action=response_action,
            errors=errors,
            view=view,
        )

Class variables

var response : Optional[BoltResponse]
class AsyncApp (*, logger: Optional[logging.Logger] = None, name: Optional[str] = None, process_before_response: bool = False, raise_error_for_unhandled_request: bool = False, signing_secret: Optional[str] = None, token: Optional[str] = None, client: Optional[slack_sdk.web.async_client.AsyncWebClient] = None, before_authorize: Union[AsyncMiddleware, Callable[..., Awaitable[Any]], ForwardRef(None)] = None, authorize: Optional[Callable[..., Awaitable[AuthorizeResult]]] = None, user_facing_authorize_error_message: Optional[str] = None, installation_store: Optional[slack_sdk.oauth.installation_store.async_installation_store.AsyncInstallationStore] = None, installation_store_bot_only: Optional[bool] = None, request_verification_enabled: bool = True, ignoring_self_events_enabled: bool = True, ignoring_self_assistant_message_events_enabled: bool = True, ssl_check_enabled: bool = True, url_verification_enabled: bool = True, attaching_function_token_enabled: bool = True, oauth_settings: Optional[AsyncOAuthSettings] = None, oauth_flow: Optional[AsyncOAuthFlow] = None, verification_token: Optional[str] = None, assistant_thread_context_store: Optional[AsyncAssistantThreadContextStore] = None)

Bolt App that provides functionalities to register middleware/listeners.

import os
from slack_bolt.async_app import AsyncApp

# Initializes your app with your bot token and signing secret
app = AsyncApp(
    token=os.environ.get("SLACK_BOT_TOKEN"),
    signing_secret=os.environ.get("SLACK_SIGNING_SECRET")
)

# Listens to incoming messages that contain "hello"
@app.message("hello")
async def message_hello(message, say):  # async function
    # say() sends a message to the channel where the event was triggered
    await say(f"Hey there <@{message['user']}>!")

# Start your app
if __name__ == "__main__":
    app.start(port=int(os.environ.get("PORT", 3000)))

Refer to https://slack.dev/bolt-python/concepts#async for details.

If you would like to build an OAuth app for enabling the app to run with multiple workspaces, refer to https://slack.dev/bolt-python/concepts#authenticating-oauth to learn how to configure the app.

Args

logger
The custom logger that can be used in this app.
name
The application name that will be used in logging. If absent, the source file name will be used.
process_before_response
True if this app runs on Function as a Service. (Default: False)
raise_error_for_unhandled_request
True if you want to raise exceptions for unhandled requests and use @app.error listeners instead of the built-in handler, which pints warning logs and returns 404 to Slack (Default: False)
signing_secret
The Signing Secret value used for verifying requests from Slack.
token
The bot/user access token required only for single-workspace app.
client
The singleton slack_sdk.web.async_client.AsyncWebClient instance for this app.
before_authorize
A global middleware that can be executed right before authorize function
authorize
The function to authorize an incoming request from Slack by checking if there is a team/user in the installation data.
user_facing_authorize_error_message
The user-facing error message to display when the app is installed but the installation is not managed by this app's installation store
installation_store
The module offering save/find operations of installation data
installation_store_bot_only
Use AsyncInstallationStore#async_find_bot() if True (Default: False)
request_verification_enabled
False if you would like to disable the built-in middleware (Default: True). AsyncRequestVerification is a built-in middleware that verifies the signature in HTTP Mode requests. Make sure if it's safe enough when you turn a built-in middleware off. We strongly recommend using RequestVerification for better security. If you have a proxy that verifies request signature in front of the Bolt app, it's totally fine to disable RequestVerification to avoid duplication of work. Don't turn it off just for easiness of development.
ignoring_self_events_enabled
False if you would like to disable the built-in middleware (Default: True). AsyncIgnoringSelfEvents is a built-in middleware that enables Bolt apps to easily skip the events generated by this app's bot user (this is useful for avoiding code error causing an infinite loop).
ignoring_self_assistant_message_events_enabled
False if you would like to disable the built-in middleware. IgnoringSelfEvents for this app's bot user message events within an assistant thread This is useful for avoiding code error causing an infinite loop; Default: True
url_verification_enabled
False if you would like to disable the built-in middleware (Default: True). AsyncUrlVerification is a built-in middleware that handles url_verification requests that verify the endpoint for Events API in HTTP Mode requests.
ssl_check_enabled
bool = False if you would like to disable the built-in middleware (Default: True). AsyncSslCheck is a built-in middleware that handles ssl_check requests from Slack.
attaching_function_token_enabled
False if you would like to disable the built-in middleware (Default: True). AsyncAttachingFunctionToken is a built-in middleware that injects the just-in-time workflow-execution token when your app receives function_executed or interactivity events scoped to a custom step.
oauth_settings
The settings related to Slack app installation flow (OAuth flow)
oauth_flow
Instantiated slack_bolt.oauth.AsyncOAuthFlow. This is always prioritized over oauth_settings.
verification_token
Deprecated verification mechanism. This can be used only for ssl_check requests.
assistant_thread_context_store
Custom AssistantThreadContext store (Default: the built-in implementation, which uses a parent message's metadata to store the latest context)
Expand source code
class AsyncApp:
    def __init__(
        self,
        *,
        logger: Optional[logging.Logger] = None,
        # Used in logger
        name: Optional[str] = None,
        # Set True when you run this app on a FaaS platform
        process_before_response: bool = False,
        # Set True if you want to handle an unhandled request as an exception
        raise_error_for_unhandled_request: bool = False,
        # Basic Information > Credentials > Signing Secret
        signing_secret: Optional[str] = None,
        # for single-workspace apps
        token: Optional[str] = None,
        client: Optional[AsyncWebClient] = None,
        # for multi-workspace apps
        before_authorize: Optional[Union[AsyncMiddleware, Callable[..., Awaitable[Any]]]] = None,
        authorize: Optional[Callable[..., Awaitable[AuthorizeResult]]] = None,
        user_facing_authorize_error_message: Optional[str] = None,
        installation_store: Optional[AsyncInstallationStore] = None,
        # for either only bot scope usage or v1.0.x compatibility
        installation_store_bot_only: Optional[bool] = None,
        # for customizing the built-in middleware
        request_verification_enabled: bool = True,
        ignoring_self_events_enabled: bool = True,
        ignoring_self_assistant_message_events_enabled: bool = True,
        ssl_check_enabled: bool = True,
        url_verification_enabled: bool = True,
        attaching_function_token_enabled: bool = True,
        # for the OAuth flow
        oauth_settings: Optional[AsyncOAuthSettings] = None,
        oauth_flow: Optional[AsyncOAuthFlow] = None,
        # No need to set (the value is used only in response to ssl_check requests)
        verification_token: Optional[str] = None,
        # for AI Agents & Assistants
        assistant_thread_context_store: Optional[AsyncAssistantThreadContextStore] = None,
    ):
        """Bolt App that provides functionalities to register middleware/listeners.

            import os
            from slack_bolt.async_app import AsyncApp

            # Initializes your app with your bot token and signing secret
            app = AsyncApp(
                token=os.environ.get("SLACK_BOT_TOKEN"),
                signing_secret=os.environ.get("SLACK_SIGNING_SECRET")
            )

            # Listens to incoming messages that contain "hello"
            @app.message("hello")
            async def message_hello(message, say):  # async function
                # say() sends a message to the channel where the event was triggered
                await say(f"Hey there <@{message['user']}>!")

            # Start your app
            if __name__ == "__main__":
                app.start(port=int(os.environ.get("PORT", 3000)))

        Refer to https://slack.dev/bolt-python/concepts#async for details.

        If you would like to build an OAuth app for enabling the app to run with multiple workspaces,
        refer to https://slack.dev/bolt-python/concepts#authenticating-oauth to learn how to configure the app.

        Args:
            logger: The custom logger that can be used in this app.
            name: The application name that will be used in logging. If absent, the source file name will be used.
            process_before_response: True if this app runs on Function as a Service. (Default: False)
            raise_error_for_unhandled_request: True if you want to raise exceptions for unhandled requests
                and use @app.error listeners instead of
                the built-in handler, which pints warning logs and returns 404 to Slack (Default: False)
            signing_secret: The Signing Secret value used for verifying requests from Slack.
            token: The bot/user access token required only for single-workspace app.
            client: The singleton `slack_sdk.web.async_client.AsyncWebClient` instance for this app.
            before_authorize: A global middleware that can be executed right before authorize function
            authorize: The function to authorize an incoming request from Slack
                by checking if there is a team/user in the installation data.
            user_facing_authorize_error_message: The user-facing error message to display
                when the app is installed but the installation is not managed by this app's installation store
            installation_store: The module offering save/find operations of installation data
            installation_store_bot_only: Use `AsyncInstallationStore#async_find_bot()` if True (Default: False)
            request_verification_enabled: False if you would like to disable the built-in middleware (Default: True).
                `AsyncRequestVerification` is a built-in middleware that verifies the signature in HTTP Mode requests.
                Make sure if it's safe enough when you turn a built-in middleware off.
                We strongly recommend using RequestVerification for better security.
                If you have a proxy that verifies request signature in front of the Bolt app,
                it's totally fine to disable RequestVerification to avoid duplication of work.
                Don't turn it off just for easiness of development.
            ignoring_self_events_enabled: False if you would like to disable the built-in middleware (Default: True).
                `AsyncIgnoringSelfEvents` is a built-in middleware that enables Bolt apps to easily skip the events
                generated by this app's bot user (this is useful for avoiding code error causing an infinite loop).
            ignoring_self_assistant_message_events_enabled: False if you would like to disable the built-in middleware.
                `IgnoringSelfEvents` for this app's bot user message events within an assistant thread
                This is useful for avoiding code error causing an infinite loop; Default: True
            url_verification_enabled: False if you would like to disable the built-in middleware (Default: True).
                `AsyncUrlVerification` is a built-in middleware that handles url_verification requests
                that verify the endpoint for Events API in HTTP Mode requests.
            ssl_check_enabled: bool = False if you would like to disable the built-in middleware (Default: True).
                `AsyncSslCheck` is a built-in middleware that handles ssl_check requests from Slack.
            attaching_function_token_enabled: False if you would like to disable the built-in middleware (Default: True).
                `AsyncAttachingFunctionToken` is a built-in middleware that injects the just-in-time workflow-execution token
                when your app receives `function_executed` or interactivity events scoped to a custom step.
            oauth_settings: The settings related to Slack app installation flow (OAuth flow)
            oauth_flow: Instantiated `slack_bolt.oauth.AsyncOAuthFlow`. This is always prioritized over oauth_settings.
            verification_token: Deprecated verification mechanism. This can be used only for ssl_check requests.
            assistant_thread_context_store: Custom AssistantThreadContext store (Default: the built-in implementation,
                which uses a parent message's metadata to store the latest context)
        """
        if signing_secret is None:
            signing_secret = os.environ.get("SLACK_SIGNING_SECRET", "")
        token = token or os.environ.get("SLACK_BOT_TOKEN")

        self._name: str = name or inspect.stack()[1].filename.split(os.path.sep)[-1]
        self._signing_secret: str = signing_secret
        self._verification_token: Optional[str] = verification_token or os.environ.get("SLACK_VERIFICATION_TOKEN", None)
        # If a logger is explicitly passed when initializing, the logger works as the base logger.
        # The base logger's logging settings will be propagated to all the loggers created by bolt-python.
        self._base_logger = logger
        # The framework logger is supposed to be used for the internal logging.
        # Also, it's accessible via `app.logger` as the app's singleton logger.
        self._framework_logger = logger or get_bolt_logger(AsyncApp)
        self._raise_error_for_unhandled_request = raise_error_for_unhandled_request

        self._token: Optional[str] = token

        if client is not None:
            if not isinstance(client, AsyncWebClient):
                raise BoltError(error_client_invalid_type_async())
            self._async_client = client
            self._token = client.token
            if token is not None:
                self._framework_logger.warning(warning_client_prioritized_and_token_skipped())
        else:
            self._async_client = create_async_web_client(
                # NOTE: the token here can be None
                token=token,
                logger=self._framework_logger,
            )

        # --------------------------------------
        # Authorize & OAuthFlow initialization
        # --------------------------------------

        self._async_before_authorize: Optional[AsyncMiddleware] = None
        if before_authorize is not None:
            if callable(before_authorize):
                self._async_before_authorize = AsyncCustomMiddleware(
                    app_name=self._name,
                    func=before_authorize,
                    base_logger=self._framework_logger,
                )
            elif isinstance(before_authorize, AsyncMiddleware):
                self._async_before_authorize = before_authorize

        self._async_authorize: Optional[AsyncAuthorize] = None
        if authorize is not None:
            if isinstance(authorize, AsyncAuthorize):
                # As long as an advanced developer understands what they're doing,
                # bolt-python should not prevent customizing authorize middleware
                self._async_authorize = authorize
            else:
                if oauth_settings is not None or oauth_flow is not None:
                    # If the given authorize is a simple function,
                    # it does not work along with installation_store.
                    raise BoltError(error_authorize_conflicts())
                self._async_authorize = AsyncCallableAuthorize(logger=self._framework_logger, func=authorize)

        self._async_installation_store: Optional[AsyncInstallationStore] = installation_store
        if self._async_installation_store is not None and self._async_authorize is None:
            settings = oauth_flow.settings if oauth_flow is not None else oauth_settings
            self._async_authorize = AsyncInstallationStoreAuthorize(
                installation_store=self._async_installation_store,
                client_id=settings.client_id if settings is not None else None,
                client_secret=settings.client_secret if settings is not None else None,
                logger=self._framework_logger,
                bot_only=installation_store_bot_only or False,
                client=self._async_client,  # for proxy use cases etc.
                user_token_resolution=(settings.user_token_resolution if settings is not None else "authed_user"),
            )

        self._async_oauth_flow: Optional[AsyncOAuthFlow] = None

        if (
            oauth_settings is None
            and os.environ.get("SLACK_CLIENT_ID") is not None
            and os.environ.get("SLACK_CLIENT_SECRET") is not None
        ):
            # initialize with the default settings
            oauth_settings = AsyncOAuthSettings()

            if oauth_flow is None and installation_store is None:
                # show info-level log for avoiding confusions
                self._framework_logger.info(info_default_oauth_settings_loaded())

        if oauth_flow:
            if not isinstance(oauth_flow, AsyncOAuthFlow):
                raise BoltError(error_oauth_flow_invalid_type_async())

            self._async_oauth_flow = oauth_flow
            installation_store = select_consistent_installation_store(
                client_id=self._async_oauth_flow.client_id,
                app_store=self._async_installation_store,
                oauth_flow_store=self._async_oauth_flow.settings.installation_store,
                logger=self._framework_logger,
            )
            self._async_installation_store = installation_store
            if installation_store is not None:
                self._async_oauth_flow.settings.installation_store = installation_store

            if self._async_oauth_flow._async_client is None:
                self._async_oauth_flow._async_client = self._async_client
            if self._async_authorize is None:
                self._async_authorize = self._async_oauth_flow.settings.authorize
        elif oauth_settings is not None:
            if not isinstance(oauth_settings, AsyncOAuthSettings):
                raise BoltError(error_oauth_settings_invalid_type_async())

            installation_store = select_consistent_installation_store(
                client_id=oauth_settings.client_id,
                app_store=self._async_installation_store,
                oauth_flow_store=oauth_settings.installation_store,
                logger=self._framework_logger,
            )
            self._async_installation_store = installation_store
            if installation_store is not None:
                oauth_settings.installation_store = installation_store

            self._async_oauth_flow = AsyncOAuthFlow(client=self._async_client, logger=self.logger, settings=oauth_settings)
            if self._async_authorize is None:
                self._async_authorize = self._async_oauth_flow.settings.authorize
            self._async_authorize.token_rotation_expiration_minutes = oauth_settings.token_rotation_expiration_minutes  # type: ignore[attr-defined] # noqa: E501

        if (self._async_installation_store is not None or self._async_authorize is not None) and self._token is not None:
            self._token = None
            self._framework_logger.warning(warning_token_skipped())

        # after setting bot_only here, __init__ cannot replace authorize function
        if installation_store_bot_only is not None and self._async_oauth_flow is not None:
            app_bot_only = installation_store_bot_only or False
            oauth_flow_bot_only = self._async_oauth_flow.settings.installation_store_bot_only
            if app_bot_only != oauth_flow_bot_only:
                self.logger.warning(warning_bot_only_conflicts())
                self._async_oauth_flow.settings.installation_store_bot_only = app_bot_only
                self._async_authorize.bot_only = app_bot_only  # type: ignore[union-attr]

        self._async_tokens_revocation_listeners: Optional[AsyncTokenRevocationListeners] = None
        if self._async_installation_store is not None:
            self._async_tokens_revocation_listeners = AsyncTokenRevocationListeners(self._async_installation_store)

        # --------------------------------------
        # Middleware Initialization
        # --------------------------------------

        self._async_middleware_list: List[AsyncMiddleware] = []
        self._async_listeners: List[AsyncListener] = []

        self._assistant_thread_context_store = assistant_thread_context_store

        self._process_before_response = process_before_response
        self._async_listener_runner = AsyncioListenerRunner(
            logger=self._framework_logger,
            process_before_response=process_before_response,
            listener_error_handler=AsyncDefaultListenerErrorHandler(logger=self._framework_logger),
            listener_start_handler=AsyncDefaultListenerStartHandler(logger=self._framework_logger),
            listener_completion_handler=AsyncDefaultListenerCompletionHandler(logger=self._framework_logger),
            lazy_listener_runner=AsyncioLazyListenerRunner(
                logger=self._framework_logger,
            ),
        )
        self._async_middleware_error_handler: AsyncMiddlewareErrorHandler = AsyncDefaultMiddlewareErrorHandler(
            logger=self._framework_logger,
        )

        self._init_middleware_list_done = False
        self._init_async_middleware_list(
            request_verification_enabled=request_verification_enabled,
            ignoring_self_events_enabled=ignoring_self_events_enabled,
            ignoring_self_assistant_message_events_enabled=ignoring_self_assistant_message_events_enabled,
            ssl_check_enabled=ssl_check_enabled,
            url_verification_enabled=url_verification_enabled,
            attaching_function_token_enabled=attaching_function_token_enabled,
            user_facing_authorize_error_message=user_facing_authorize_error_message,
        )

        self._server: Optional[AsyncSlackAppServer] = None

    def _init_async_middleware_list(
        self,
        request_verification_enabled: bool = True,
        ignoring_self_events_enabled: bool = True,
        ignoring_self_assistant_message_events_enabled: bool = True,
        ssl_check_enabled: bool = True,
        url_verification_enabled: bool = True,
        attaching_function_token_enabled: bool = True,
        user_facing_authorize_error_message: Optional[str] = None,
    ):
        if self._init_middleware_list_done:
            return
        if ssl_check_enabled is True:
            self._async_middleware_list.append(
                AsyncSslCheck(
                    verification_token=self._verification_token,
                    base_logger=self._base_logger,
                )
            )
        if request_verification_enabled is True:
            self._async_middleware_list.append(AsyncRequestVerification(self._signing_secret, base_logger=self._base_logger))

        if self._async_before_authorize is not None:
            self._async_middleware_list.append(self._async_before_authorize)

        # As authorize is required for making a Bolt app function, we don't offer the flag to disable this
        if self._async_oauth_flow is None:
            if self._token:
                self._async_middleware_list.append(
                    AsyncSingleTeamAuthorization(
                        base_logger=self._base_logger,
                        user_facing_authorize_error_message=user_facing_authorize_error_message,
                    )
                )
            elif self._async_authorize is not None:
                self._async_middleware_list.append(
                    AsyncMultiTeamsAuthorization(
                        authorize=self._async_authorize,
                        base_logger=self._base_logger,
                        user_facing_authorize_error_message=user_facing_authorize_error_message,
                    )
                )
            else:
                raise BoltError(error_token_required())
        elif self._async_authorize is not None:
            self._async_middleware_list.append(
                AsyncMultiTeamsAuthorization(
                    authorize=self._async_authorize,
                    base_logger=self._base_logger,
                    user_token_resolution=self._async_oauth_flow.settings.user_token_resolution,
                    user_facing_authorize_error_message=user_facing_authorize_error_message,
                )
            )
        else:
            raise BoltError(error_oauth_flow_or_authorize_required())

        if ignoring_self_events_enabled is True:
            self._async_middleware_list.append(
                AsyncIgnoringSelfEvents(
                    base_logger=self._base_logger,
                    ignoring_self_assistant_message_events_enabled=ignoring_self_assistant_message_events_enabled,
                )
            )
        if url_verification_enabled is True:
            self._async_middleware_list.append(AsyncUrlVerification(base_logger=self._base_logger))
        if attaching_function_token_enabled is True:
            self._async_middleware_list.append(AsyncAttachingFunctionToken())
        self._init_middleware_list_done = True

    # -------------------------
    # accessors

    @property
    def name(self) -> str:
        """The name of this app (default: the filename)"""
        return self._name

    @property
    def oauth_flow(self) -> Optional[AsyncOAuthFlow]:
        """Configured `OAuthFlow` object if exists."""
        return self._async_oauth_flow

    @property
    def client(self) -> AsyncWebClient:
        """The singleton `slack_sdk.web.async_client.AsyncWebClient` instance in this app."""
        return self._async_client

    @property
    def logger(self) -> logging.Logger:
        """The logger this app uses."""
        return self._framework_logger

    @property
    def installation_store(self) -> Optional[AsyncInstallationStore]:
        """The `slack_sdk.oauth.AsyncInstallationStore` that can be used in the `authorize` middleware."""
        return self._async_installation_store

    @property
    def listener_runner(self) -> AsyncioListenerRunner:
        """The asyncio-based executor for asynchronously running listeners."""
        return self._async_listener_runner

    @property
    def process_before_response(self) -> bool:
        return self._process_before_response or False

    # -------------------------
    # standalone server

    from .async_server import AsyncSlackAppServer

    def server(
        self,
        port: int = 3000,
        path: str = "/slack/events",
        host: Optional[str] = None,
    ) -> AsyncSlackAppServer:
        """Configure a web server using AIOHTTP.
        Refer to https://docs.aiohttp.org/ for more details about AIOHTTP.

        Args:
            port: The port to listen on (Default: 3000)
            path: The path to handle request from Slack (Default: `/slack/events`)
            host: The hostname to serve the web endpoints. (Default: 0.0.0.0)
        """
        if self._server is None or self._server.port != port or self._server.path != path:
            self._server = AsyncSlackAppServer(
                port=port,
                path=path,
                app=self,
                host=host,
            )
        return self._server

    def web_app(self, path: str = "/slack/events", port: int = 3000) -> web.Application:
        """Returns a `web.Application` instance for aiohttp-devtools users.

            from slack_bolt.async_app import AsyncApp
            app = AsyncApp()

            @app.event("app_mention")
            async def event_test(body, say, logger):
                logger.info(body)
                await say("What's up?")

            def app_factory():
                return app.web_app()

            # adev runserver --port 3000 --app-factory app_factory async_app.py

        Args:
            path: The path to receive incoming requests from Slack
            port: The port to listen on (Default: 3000)
        """
        return self.server(path=path, port=port).web_app

    def start(self, port: int = 3000, path: str = "/slack/events", host: Optional[str] = None) -> None:
        """Start a web server using AIOHTTP.
        Refer to https://docs.aiohttp.org/ for more details about AIOHTTP.

        Args:
            port: The port to listen on (Default: 3000)
            path: The path to handle request from Slack (Default: `/slack/events`)
            host: The hostname to serve the web endpoints. (Default: 0.0.0.0)
        """
        self.server(port=port, path=path, host=host).start()

    # -------------------------
    # main dispatcher

    async def async_dispatch(self, req: AsyncBoltRequest) -> BoltResponse:
        """Applies all middleware and dispatches an incoming request from Slack to the right code path.

        Args:
            req: An incoming request from Slack.

        Returns:
            The response generated by this Bolt app.
        """
        starting_time = time.time()
        self._init_context(req)

        resp: Optional[BoltResponse] = BoltResponse(status=200, body="")
        middleware_state = {"next_called": False}

        async def async_middleware_next():
            middleware_state["next_called"] = True

        try:
            for middleware in self._async_middleware_list:
                middleware_state["next_called"] = False
                if self._framework_logger.level <= logging.DEBUG:
                    self._framework_logger.debug(f"Applying {middleware.name}")
                resp = await middleware.async_process(
                    req=req, resp=resp, next=async_middleware_next  # type: ignore[arg-type]
                )
                if not middleware_state["next_called"]:
                    if resp is None:
                        # next() method was not called without providing the response to return to Slack
                        # This should not be an intentional handling in usual use cases.
                        resp = BoltResponse(status=404, body={"error": "no next() calls in middleware"})
                        if self._raise_error_for_unhandled_request is True:
                            try:
                                raise BoltUnhandledRequestError(
                                    request=req,
                                    current_response=resp,
                                    last_global_middleware_name=middleware.name,
                                )
                            except BoltUnhandledRequestError as e:
                                await self._async_listener_runner.listener_error_handler.handle(
                                    error=e,
                                    request=req,
                                    response=resp,
                                )
                            return resp
                        self._framework_logger.warning(warning_unhandled_by_global_middleware(middleware.name, req))
                        return resp
                    return resp

            for listener in self._async_listeners:
                listener_name = get_name_for_callable(listener.ack_function)
                self._framework_logger.debug(debug_checking_listener(listener_name))
                if await listener.async_matches(req=req, resp=resp):  # type: ignore[arg-type]
                    # run all the middleware attached to this listener first
                    (middleware_resp, next_was_not_called) = await listener.run_async_middleware(
                        req=req, resp=resp  # type: ignore[arg-type]
                    )
                    if next_was_not_called:
                        if middleware_resp is not None:
                            if self._framework_logger.level <= logging.DEBUG:
                                debug_message = debug_return_listener_middleware_response(
                                    listener_name,
                                    middleware_resp.status,
                                    middleware_resp.body,
                                    starting_time,
                                )
                                self._framework_logger.debug(debug_message)
                            return middleware_resp
                        # The last listener middleware didn't call next() method.
                        # This means the listener is not for this incoming request.
                        continue

                    if middleware_resp is not None:
                        resp = middleware_resp

                    self._framework_logger.debug(debug_running_listener(listener_name))
                    listener_response: Optional[BoltResponse] = await self._async_listener_runner.run(
                        request=req,
                        response=resp,  # type: ignore[arg-type]
                        listener_name=listener_name,
                        listener=listener,
                    )
                    if listener_response is not None:
                        return listener_response

            if resp is None:
                resp = BoltResponse(status=404, body={"error": "unhandled request"})
            if self._raise_error_for_unhandled_request is True:
                try:
                    raise BoltUnhandledRequestError(
                        request=req,
                        current_response=resp,
                    )
                except BoltUnhandledRequestError as e:
                    await self._async_listener_runner.listener_error_handler.handle(
                        error=e,
                        request=req,
                        response=resp,
                    )
                return resp
            return self._handle_unmatched_requests(req, resp)

        except Exception as error:
            resp = BoltResponse(status=500, body="")
            await self._async_middleware_error_handler.handle(
                error=error,
                request=req,
                response=resp,
            )
            return resp

    def _handle_unmatched_requests(self, req: AsyncBoltRequest, resp: BoltResponse) -> BoltResponse:
        self._framework_logger.warning(warning_unhandled_request(req))
        return resp

    # -------------------------
    # middleware

    def use(self, *args) -> Optional[Callable]:
        """Refer to `AsyncApp#middleware()` method's docstring for details."""
        return self.middleware(*args)

    def middleware(self, *args) -> Optional[Callable]:
        """Registers a new middleware to this app.
        This method can be used as either a decorator or a method.

            # Use this method as a decorator
            @app.middleware
            async def middleware_func(logger, body, next):
                logger.info(f"request body: {body}")
                await next()

            # Pass a function to this method
            app.middleware(middleware_func)

        To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document.

        Args:
            *args: A function that works as a global middleware.
        """
        if len(args) > 0:
            middleware_or_callable = args[0]
            if isinstance(middleware_or_callable, AsyncMiddleware):
                middleware: AsyncMiddleware = middleware_or_callable
                self._async_middleware_list.append(middleware)
                if isinstance(middleware, AsyncAssistant) and middleware.thread_context_store is not None:
                    self._assistant_thread_context_store = middleware.thread_context_store
            elif callable(middleware_or_callable):
                self._async_middleware_list.append(
                    AsyncCustomMiddleware(
                        app_name=self.name,
                        func=middleware_or_callable,
                        base_logger=self._base_logger,
                    )
                )
                return middleware_or_callable
            else:
                raise BoltError(f"Unexpected type for a middleware ({type(middleware_or_callable)})")
        return None

    def assistant(self, assistant: AsyncAssistant) -> Optional[Callable]:
        return self.middleware(assistant)

    # -------------------------
    # Workflows: Steps from apps

    def step(
        self,
        callback_id: Union[str, Pattern, AsyncWorkflowStep, AsyncWorkflowStepBuilder],
        edit: Optional[Union[Callable[..., Optional[BoltResponse]], AsyncListener, Sequence[Callable]]] = None,
        save: Optional[Union[Callable[..., Optional[BoltResponse]], AsyncListener, Sequence[Callable]]] = None,
        execute: Optional[Union[Callable[..., Optional[BoltResponse]], AsyncListener, Sequence[Callable]]] = None,
    ):
        """
        Deprecated:
            Steps from apps for legacy workflows are now deprecated.
            Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

        Registers a new step from app listener.

        Unlike others, this method doesn't behave as a decorator.
        If you want to register a step from app by a decorator, use `AsyncWorkflowStepBuilder`'s methods.

            # Create a new WorkflowStep instance
            from slack_bolt.workflows.async_step import AsyncWorkflowStep
            ws = AsyncWorkflowStep(
                callback_id="add_task",
                edit=edit,
                save=save,
                execute=execute,
            )
            # Pass Step to set up listeners
            app.step(ws)

        Refer to https://api.slack.com/workflows/steps for details of steps from apps.

        To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document.
        For further information about AsyncWorkflowStep specific function arguments
        such as `configure`, `update`, `complete`, and `fail`,
        refer to the `async` prefixed ones in `slack_bolt.workflows.step.utilities` API documents.

        Args:
            callback_id: The Callback ID for this step from app
            edit: The function for displaying a modal in the Workflow Builder
            save: The function for handling configuration in the Workflow Builder
            execute: The function for handling the step execution
        """
        warnings.warn(
            (
                "Steps from apps for legacy workflows are now deprecated. "
                "Use new custom steps: https://api.slack.com/automation/functions/custom-bolt"
            ),
            category=DeprecationWarning,
        )
        step = callback_id
        if isinstance(callback_id, (str, Pattern)):
            step = AsyncWorkflowStep(
                callback_id=callback_id,
                edit=edit,  # type: ignore[arg-type]
                save=save,  # type: ignore[arg-type]
                execute=execute,  # type: ignore[arg-type]
                base_logger=self._base_logger,
            )
        elif isinstance(step, AsyncWorkflowStepBuilder):
            step = step.build(base_logger=self._base_logger)
        elif not isinstance(step, AsyncWorkflowStep):
            raise BoltError(f"Invalid step object ({type(step)})")

        self.use(AsyncWorkflowStepMiddleware(step))

    # -------------------------
    # global error handler

    def error(
        self, func: Callable[..., Awaitable[Optional[BoltResponse]]]
    ) -> Callable[..., Awaitable[Optional[BoltResponse]]]:
        """Updates the global error handler. This method can be used as either a decorator or a method.

            # Use this method as a decorator
            @app.error
            async def custom_error_handler(error, body, logger):
                logger.exception(f"Error: {error}")
                logger.info(f"Request body: {body}")

            # Pass a function to this method
            app.error(custom_error_handler)

        To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document.

        Args:
            func: The function that is supposed to be executed
                when getting an unhandled error in Bolt app.
        """
        if not is_callable_coroutine(func):
            name = get_name_for_callable(func)
            raise BoltError(error_listener_function_must_be_coro_func(name))
        self._async_listener_runner.listener_error_handler = AsyncCustomListenerErrorHandler(
            logger=self._framework_logger,
            func=func,
        )
        self._async_middleware_error_handler = AsyncCustomMiddlewareErrorHandler(
            logger=self._framework_logger,
            func=func,
        )
        return func

    # -------------------------
    # events

    def event(
        self,
        event: Union[
            str,
            Pattern,
            Dict[str, Optional[Union[str, Sequence[Optional[Union[str, Pattern]]]]]],
        ],
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None,
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
        """Registers a new event listener. This method can be used as either a decorator or a method.

            # Use this method as a decorator
            @app.event("team_join")
            async def ask_for_introduction(event, say):
                welcome_channel_id = "C12345"
                user_id = event["user"]
                text = f"Welcome to the team, <@{user_id}>! :tada: You can introduce yourself in this channel."
                await say(text=text, channel=welcome_channel_id)

            # Pass a function to this method
            app.event("team_join")(ask_for_introduction)

        Refer to https://api.slack.com/apis/connections/events-api for details of Events API.

        To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document.

        Args:
            event: The conditions that match a request payload.
                If you pass a dict for this, you can have type, subtype in the constraint.
            matchers: A list of listener matcher functions.
                Only when all the matchers return True, the listener function can be invoked.
            middleware: A list of lister middleware functions.
                Only when all the middleware call `next()` method, the listener function can be invoked.
        """

        def __call__(*args, **kwargs):
            functions = self._to_listener_functions(kwargs) if kwargs else list(args)
            primary_matcher = builtin_matchers.event(event, True, base_logger=self._base_logger)
            return self._register_listener(list(functions), primary_matcher, matchers, middleware, True)

        return __call__

    def message(
        self,
        keyword: Union[str, Pattern] = "",
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None,
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
        """Registers a new message event listener. This method can be used as either a decorator or a method.
        Check the `App#event` method's docstring for details.

            # Use this method as a decorator
            @app.message(":wave:")
            async def say_hello(message, say):
                user = message['user']
                await say(f"Hi there, <@{user}>!")

            # Pass a function to this method
            app.message(":wave:")(say_hello)

        Refer to https://api.slack.com/events/message for details of `message` events.

        To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document.

        Args:
            keyword: The keyword to match
            matchers: A list of listener matcher functions.
                Only when all the matchers return True, the listener function can be invoked.
            middleware: A list of lister middleware functions.
                Only when all the middleware call `next()` method, the listener function can be invoked.
        """
        matchers = list(matchers) if matchers else []
        middleware = list(middleware) if middleware else []

        def __call__(*args, **kwargs):
            functions = self._to_listener_functions(kwargs) if kwargs else list(args)
            constraints = {
                "type": "message",
                "subtype": (
                    # In most cases, new message events come with no subtype.
                    None,
                    # As of Jan 2021, most bot messages no longer have the subtype bot_message.
                    # By contrast, messages posted using classic app's bot token still have the subtype.
                    "bot_message",
                    # If an end-user posts a message with "Also send to #channel" checked,
                    # the message event comes with this subtype.
                    "thread_broadcast",
                    # If an end-user posts a message with attached files,
                    # the message event comes with this subtype.
                    "file_share",
                ),
            }
            primary_matcher = builtin_matchers.message_event(
                constraints=constraints,
                keyword=keyword,
                asyncio=True,
                base_logger=self._base_logger,
            )
            middleware.insert(0, AsyncMessageListenerMatches(keyword))
            return self._register_listener(list(functions), primary_matcher, matchers, middleware, True)

        return __call__

    def function(
        self,
        callback_id: Union[str, Pattern],
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None,
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
        auto_acknowledge: bool = True,
    ) -> Callable[..., Optional[Callable[..., Awaitable[BoltResponse]]]]:
        """Registers a new Function listener.
        This method can be used as either a decorator or a method.

            # Use this method as a decorator
            @app.function("reverse")
            async def reverse_string(ack: AsyncAck, inputs: dict, complete: AsyncComplete, fail: AsyncFail):
                try:
                    await ack()
                    string_to_reverse = inputs["stringToReverse"]
                    await complete({"reverseString": string_to_reverse[::-1]})
                except Exception as e:
                    await fail(f"Cannot reverse string (error: {e})")
                    raise e

            # Pass a function to this method
            app.function("reverse")(reverse_string)

        To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document.

        Args:
            callback_id: The callback id to identify the function
            matchers: A list of listener matcher functions.
                Only when all the matchers return True, the listener function can be invoked.
            middleware: A list of lister middleware functions.
                Only when all the middleware call `next()` method, the listener function can be invoked.
        """

        matchers = list(matchers) if matchers else []
        middleware = list(middleware) if middleware else []

        def __call__(*args, **kwargs):
            functions = self._to_listener_functions(kwargs) if kwargs else list(args)
            primary_matcher = builtin_matchers.function_executed(
                callback_id=callback_id, base_logger=self._base_logger, asyncio=True
            )
            return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge)

        return __call__

    # -------------------------
    # slash commands

    def command(
        self,
        command: Union[str, Pattern],
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None,
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
        """Registers a new slash command listener.
        This method can be used as either a decorator or a method.

            # Use this method as a decorator
            @app.command("/echo")
            async def repeat_text(ack, say, command):
                # Acknowledge command request
                await ack()
                await say(f"{command['text']}")

            # Pass a function to this method
            app.command("/echo")(repeat_text)

        Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands.

        To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document.

        Args:
            command: The conditions that match a request payload
            matchers: A list of listener matcher functions.
                Only when all the matchers return True, the listener function can be invoked.
            middleware: A list of lister middleware functions.
                Only when all the middleware call `next()` method, the listener function can be invoked.
        """

        def __call__(*args, **kwargs):
            functions = self._to_listener_functions(kwargs) if kwargs else list(args)
            primary_matcher = builtin_matchers.command(command, asyncio=True, base_logger=self._base_logger)
            return self._register_listener(list(functions), primary_matcher, matchers, middleware)

        return __call__

    # -------------------------
    # shortcut

    def shortcut(
        self,
        constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None,
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
        """Registers a new shortcut listener.
        This method can be used as either a decorator or a method.

            # Use this method as a decorator
            @app.shortcut("open_modal")
            async def open_modal(ack, body, client):
                # Acknowledge the command request
                await ack()
                # Call views_open with the built-in client
                await client.views_open(
                    # Pass a valid trigger_id within 3 seconds of receiving it
                    trigger_id=body["trigger_id"],
                    # View payload
                    view={ ... }
                )

            # Pass a function to this method
            app.shortcut("open_modal")(open_modal)

        Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts.

        To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document.

        Args:
            constraints: The conditions that match a request payload.
            matchers: A list of listener matcher functions.
                Only when all the matchers return True, the listener function can be invoked.
            middleware: A list of lister middleware functions.
                Only when all the middleware call `next()` method, the listener function can be invoked.
        """

        def __call__(*args, **kwargs):
            functions = self._to_listener_functions(kwargs) if kwargs else list(args)
            primary_matcher = builtin_matchers.shortcut(constraints, asyncio=True, base_logger=self._base_logger)
            return self._register_listener(list(functions), primary_matcher, matchers, middleware)

        return __call__

    def global_shortcut(
        self,
        callback_id: Union[str, Pattern],
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None,
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
        """Registers a new global shortcut listener."""

        def __call__(*args, **kwargs):
            functions = self._to_listener_functions(kwargs) if kwargs else list(args)
            primary_matcher = builtin_matchers.global_shortcut(callback_id, asyncio=True, base_logger=self._base_logger)
            return self._register_listener(list(functions), primary_matcher, matchers, middleware)

        return __call__

    def message_shortcut(
        self,
        callback_id: Union[str, Pattern],
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None,
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
        """Registers a new message shortcut listener."""

        def __call__(*args, **kwargs):
            functions = self._to_listener_functions(kwargs) if kwargs else list(args)
            primary_matcher = builtin_matchers.message_shortcut(callback_id, asyncio=True, base_logger=self._base_logger)
            return self._register_listener(list(functions), primary_matcher, matchers, middleware)

        return __call__

    # -------------------------
    # action

    def action(
        self,
        constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None,
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
        """Registers a new action listener. This method can be used as either a decorator or a method.

            # Use this method as a decorator
            @app.action("approve_button")
            async def update_message(ack):
                await ack()

            # Pass a function to this method
            app.action("approve_button")(update_message)

        * Refer to https://api.slack.com/reference/interaction-payloads/block-actions for actions in `blocks`.
        * Refer to https://api.slack.com/legacy/message-buttons for actions in `attachments`.
        * Refer to https://api.slack.com/dialogs for actions in dialogs.

        To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document.

        Args:
            constraints: The conditions that match a request payload
            matchers: A list of listener matcher functions.
                Only when all the matchers return True, the listener function can be invoked.
            middleware: A list of lister middleware functions.
                Only when all the middleware call `next()` method, the listener function can be invoked.
        """

        def __call__(*args, **kwargs):
            functions = self._to_listener_functions(kwargs) if kwargs else list(args)
            primary_matcher = builtin_matchers.action(constraints, asyncio=True, base_logger=self._base_logger)
            return self._register_listener(list(functions), primary_matcher, matchers, middleware)

        return __call__

    def block_action(
        self,
        constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None,
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
        """Registers a new `block_actions` action listener.
        Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details.
        """

        def __call__(*args, **kwargs):
            functions = self._to_listener_functions(kwargs) if kwargs else list(args)
            primary_matcher = builtin_matchers.block_action(constraints, asyncio=True, base_logger=self._base_logger)
            return self._register_listener(list(functions), primary_matcher, matchers, middleware)

        return __call__

    def attachment_action(
        self,
        callback_id: Union[str, Pattern],
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None,
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
        """Registers a new `interactive_message` action listener.
        Refer to https://api.slack.com/legacy/message-buttons for details."""

        def __call__(*args, **kwargs):
            functions = self._to_listener_functions(kwargs) if kwargs else list(args)
            primary_matcher = builtin_matchers.attachment_action(callback_id, asyncio=True, base_logger=self._base_logger)
            return self._register_listener(list(functions), primary_matcher, matchers, middleware)

        return __call__

    def dialog_submission(
        self,
        callback_id: Union[str, Pattern],
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None,
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
        """Registers a new `dialog_submission` listener.
        Refer to https://api.slack.com/dialogs for details."""

        def __call__(*args, **kwargs):
            functions = self._to_listener_functions(kwargs) if kwargs else list(args)
            primary_matcher = builtin_matchers.dialog_submission(callback_id, asyncio=True, base_logger=self._base_logger)
            return self._register_listener(list(functions), primary_matcher, matchers, middleware)

        return __call__

    def dialog_cancellation(
        self,
        callback_id: Union[str, Pattern],
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None,
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
        """Registers a new `dialog_submission` listener.
        Refer to https://api.slack.com/dialogs for details."""

        def __call__(*args, **kwargs):
            functions = self._to_listener_functions(kwargs) if kwargs else list(args)
            primary_matcher = builtin_matchers.dialog_cancellation(callback_id, asyncio=True, base_logger=self._base_logger)
            return self._register_listener(list(functions), primary_matcher, matchers, middleware)

        return __call__

    # -------------------------
    # view

    def view(
        self,
        constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None,
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
        """Registers a new `view_submission`/`view_closed` event listener.
        This method can be used as either a decorator or a method.

            # Use this method as a decorator
            @app.view("view_1")
            async def handle_submission(ack, body, client, view):
                # Assume there's an input block with `block_c` as the block_id and `dreamy_input`
                hopes_and_dreams = view["state"]["values"]["block_c"]["dreamy_input"]
                user = body["user"]["id"]
                # Validate the inputs
                errors = {}
                if hopes_and_dreams is not None and len(hopes_and_dreams) <= 5:
                    errors["block_c"] = "The value must be longer than 5 characters"
                if len(errors) > 0:
                    await ack(response_action="errors", errors=errors)
                    return
                # Acknowledge the view_submission event and close the modal
                await ack()
                # Do whatever you want with the input data - here we're saving it to a DB

            # Pass a function to this method
            app.view("view_1")(handle_submission)

        Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads.

        To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document.

        Args:
            constraints: The conditions that match a request payload
            matchers: A list of listener matcher functions.
                Only when all the matchers return True, the listener function can be invoked.
            middleware: A list of lister middleware functions.
                Only when all the middleware call `next()` method, the listener function can be invoked.
        """

        def __call__(*args, **kwargs):
            functions = self._to_listener_functions(kwargs) if kwargs else list(args)
            primary_matcher = builtin_matchers.view(constraints, asyncio=True, base_logger=self._base_logger)
            return self._register_listener(list(functions), primary_matcher, matchers, middleware)

        return __call__

    def view_submission(
        self,
        constraints: Union[str, Pattern],
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None,
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
        """Registers a new `view_submission` listener.
        Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details."""

        def __call__(*args, **kwargs):
            functions = self._to_listener_functions(kwargs) if kwargs else list(args)
            primary_matcher = builtin_matchers.view_submission(constraints, asyncio=True, base_logger=self._base_logger)
            return self._register_listener(list(functions), primary_matcher, matchers, middleware)

        return __call__

    def view_closed(
        self,
        constraints: Union[str, Pattern],
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None,
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
        """Registers a new `view_closed` listener.
        Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details."""

        def __call__(*args, **kwargs):
            functions = self._to_listener_functions(kwargs) if kwargs else list(args)
            primary_matcher = builtin_matchers.view_closed(constraints, asyncio=True, base_logger=self._base_logger)
            return self._register_listener(list(functions), primary_matcher, matchers, middleware)

        return __call__

    # -------------------------
    # options

    def options(
        self,
        constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None,
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
        """Registers a new options listener.
        This method can be used as either a decorator or a method.

            # Use this method as a decorator
            @app.options("menu_selection")
            async def show_menu_options(ack):
                options = [
                    {
                        "text": {"type": "plain_text", "text": "Option 1"},
                        "value": "1-1",
                    },
                    {
                        "text": {"type": "plain_text", "text": "Option 2"},
                        "value": "1-2",
                    },
                ]
                await ack(options=options)

            # Pass a function to this method
            app.options("menu_selection")(show_menu_options)

        Refer to the following documents for details:

        * https://api.slack.com/reference/block-kit/block-elements#external_select
        * https://api.slack.com/reference/block-kit/block-elements#external_multi_select

        To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document.

        Args:
            matchers: A list of listener matcher functions.
                Only when all the matchers return True, the listener function can be invoked.
            middleware: A list of lister middleware functions.
                Only when all the middleware call `next()` method, the listener function can be invoked.
        """

        def __call__(*args, **kwargs):
            functions = self._to_listener_functions(kwargs) if kwargs else list(args)
            primary_matcher = builtin_matchers.options(constraints, asyncio=True, base_logger=self._base_logger)
            return self._register_listener(list(functions), primary_matcher, matchers, middleware)

        return __call__

    def block_suggestion(
        self,
        action_id: Union[str, Pattern],
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None,
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
        """Registers a new `block_suggestion` listener."""

        def __call__(*args, **kwargs):
            functions = self._to_listener_functions(kwargs) if kwargs else list(args)
            primary_matcher = builtin_matchers.block_suggestion(action_id, asyncio=True, base_logger=self._base_logger)
            return self._register_listener(list(functions), primary_matcher, matchers, middleware)

        return __call__

    def dialog_suggestion(
        self,
        callback_id: Union[str, Pattern],
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None,
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
        """Registers a new `dialog_suggestion` listener.
        Refer to https://api.slack.com/dialogs for details."""

        def __call__(*args, **kwargs):
            functions = self._to_listener_functions(kwargs) if kwargs else list(args)
            primary_matcher = builtin_matchers.dialog_suggestion(callback_id, asyncio=True, base_logger=self._base_logger)
            return self._register_listener(list(functions), primary_matcher, matchers, middleware)

        return __call__

    # -------------------------
    # built-in listener functions

    def default_tokens_revoked_event_listener(
        self,
    ) -> Callable[..., Awaitable[Optional[BoltResponse]]]:
        if self._async_tokens_revocation_listeners is None:
            raise BoltError(error_installation_store_required_for_builtin_listeners())
        return self._async_tokens_revocation_listeners.handle_tokens_revoked_events

    def default_app_uninstalled_event_listener(
        self,
    ) -> Callable[..., Awaitable[Optional[BoltResponse]]]:
        if self._async_tokens_revocation_listeners is None:
            raise BoltError(error_installation_store_required_for_builtin_listeners())
        return self._async_tokens_revocation_listeners.handle_app_uninstalled_events

    def enable_token_revocation_listeners(self) -> None:
        self.event("tokens_revoked")(self.default_tokens_revoked_event_listener())
        self.event("app_uninstalled")(self.default_app_uninstalled_event_listener())

    # -------------------------

    def _init_context(self, req: AsyncBoltRequest):
        req.context["logger"] = get_bolt_app_logger(app_name=self.name, base_logger=self._base_logger)
        req.context["token"] = self._token
        # Prior to version 1.15, when the token is static, self._client was passed to `req.context`.
        # The intention was to avoid creating a new instance per request
        # in the interest of runtime performance/memory footprint optimization.
        # However, developers may want to replace the token held by req.context.client in some situations.
        # In this case, this behavior can result in thread-unsafe data modification on `self._client`.
        # (`self._client` a.k.a. `app.client` is a singleton object per an App instance)
        # Thus, we've changed the behavior to create a new instance per request regardless of token argument
        # in the App initialization starting v1.15.
        # The overhead brought by this change is slight so that we believe that it is ignorable in any cases.
        client_per_request: AsyncWebClient = AsyncWebClient(
            token=self._token,  # this can be None, and it can be set later on
            base_url=self._async_client.base_url,
            timeout=self._async_client.timeout,
            ssl=self._async_client.ssl,
            proxy=self._async_client.proxy,
            session=self._async_client.session,
            trust_env_in_session=self._async_client.trust_env_in_session,
            headers=self._async_client.headers,
            team_id=req.context.team_id,
            retry_handlers=(
                self._async_client.retry_handlers.copy() if self._async_client.retry_handlers is not None else None
            ),
        )
        req.context["client"] = client_per_request

        # Most apps do not need this "listener_runner" instance.
        # It is intended for apps that start lazy listeners from their custom global middleware.
        req.context["listener_runner"] = self.listener_runner

        # For AI Agents & Assistants
        if is_assistant_event(req.body):
            assistant = AsyncAssistantUtilities(
                payload=to_event(req.body),  # type:ignore[arg-type]
                context=req.context,
                thread_context_store=self._assistant_thread_context_store,
            )
            req.context["say"] = assistant.say
            req.context["set_status"] = assistant.set_status
            req.context["set_title"] = assistant.set_title
            req.context["set_suggested_prompts"] = assistant.set_suggested_prompts
            req.context["get_thread_context"] = assistant.get_thread_context
            req.context["save_thread_context"] = assistant.save_thread_context

    @staticmethod
    def _to_listener_functions(
        kwargs: dict,
    ) -> Optional[Sequence[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
        if kwargs:
            functions = [kwargs["ack"]]
            for sub in kwargs["lazy"]:
                functions.append(sub)
            return functions
        return None

    def _register_listener(
        self,
        functions: Sequence[Callable[..., Awaitable[Optional[BoltResponse]]]],
        primary_matcher: AsyncListenerMatcher,
        matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]],
        middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]],
        auto_acknowledgement: bool = False,
    ) -> Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]:
        value_to_return = None
        if not isinstance(functions, list):
            functions = list(functions)
        if len(functions) == 1:
            # In the case where the function is registered using decorator,
            # the registration should return the original function.
            value_to_return = functions[0]

        for func in functions:
            if not is_callable_coroutine(func):
                name = get_name_for_callable(func)
                raise BoltError(error_listener_function_must_be_coro_func(name))

        listener_matchers: List[AsyncListenerMatcher] = [
            AsyncCustomListenerMatcher(app_name=self.name, func=f, base_logger=self._base_logger) for f in (matchers or [])
        ]
        listener_matchers.insert(0, primary_matcher)
        listener_middleware = []
        for m in middleware or []:
            if isinstance(m, AsyncMiddleware):
                listener_middleware.append(m)
            elif callable(m) and is_callable_coroutine(m):
                listener_middleware.append(AsyncCustomMiddleware(app_name=self.name, func=m, base_logger=self._base_logger))
            else:
                raise ValueError(error_unexpected_listener_middleware(type(m)))

        self._async_listeners.append(
            AsyncCustomListener(
                app_name=self.name,
                ack_function=functions.pop(0),
                lazy_functions=functions,
                matchers=listener_matchers,
                middleware=listener_middleware,
                auto_acknowledgement=auto_acknowledgement,
                base_logger=self._base_logger,
            )
        )

        return value_to_return

Class variables

var AsyncSlackAppServer

Instance variables

prop client : slack_sdk.web.async_client.AsyncWebClient

The singleton slack_sdk.web.async_client.AsyncWebClient instance in this app.

Expand source code
@property
def client(self) -> AsyncWebClient:
    """The singleton `slack_sdk.web.async_client.AsyncWebClient` instance in this app."""
    return self._async_client
prop installation_store : Optional[slack_sdk.oauth.installation_store.async_installation_store.AsyncInstallationStore]

The slack_sdk.oauth.AsyncInstallationStore that can be used in the authorize middleware.

Expand source code
@property
def installation_store(self) -> Optional[AsyncInstallationStore]:
    """The `slack_sdk.oauth.AsyncInstallationStore` that can be used in the `authorize` middleware."""
    return self._async_installation_store
prop listener_runnerAsyncioListenerRunner

The asyncio-based executor for asynchronously running listeners.

Expand source code
@property
def listener_runner(self) -> AsyncioListenerRunner:
    """The asyncio-based executor for asynchronously running listeners."""
    return self._async_listener_runner
prop logger : logging.Logger

The logger this app uses.

Expand source code
@property
def logger(self) -> logging.Logger:
    """The logger this app uses."""
    return self._framework_logger
prop name : str

The name of this app (default: the filename)

Expand source code
@property
def name(self) -> str:
    """The name of this app (default: the filename)"""
    return self._name
prop oauth_flow : Optional[AsyncOAuthFlow]

Configured OAuthFlow object if exists.

Expand source code
@property
def oauth_flow(self) -> Optional[AsyncOAuthFlow]:
    """Configured `OAuthFlow` object if exists."""
    return self._async_oauth_flow
prop process_before_response : bool
Expand source code
@property
def process_before_response(self) -> bool:
    return self._process_before_response or False

Methods

def action(self, constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]], matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None) ‑> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]

Registers a new action listener. This method can be used as either a decorator or a method.

# Use this method as a decorator
@app.action("approve_button")
async def update_message(ack):
    await ack()

# Pass a function to this method
app.action("approve_button")(update_message)

To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

Args

constraints
The conditions that match a request payload
matchers
A list of listener matcher functions. Only when all the matchers return True, the listener function can be invoked.
middleware
A list of lister middleware functions. Only when all the middleware call next() method, the listener function can be invoked.
def assistant(self, assistant: AsyncAssistant) ‑> Optional[Callable]
async def async_dispatch(self, req: AsyncBoltRequest) ‑> BoltResponse

Applies all middleware and dispatches an incoming request from Slack to the right code path.

Args

req
An incoming request from Slack.

Returns

The response generated by this Bolt app.

def attachment_action(self, callback_id: Union[str, Pattern], matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None) ‑> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]

Registers a new interactive_message action listener. Refer to https://api.slack.com/legacy/message-buttons for details.

def block_action(self, constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]], matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None) ‑> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]

Registers a new block_actions action listener. Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details.

def block_suggestion(self, action_id: Union[str, Pattern], matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None) ‑> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]

Registers a new block_suggestion listener.

def command(self, command: Union[str, Pattern], matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None) ‑> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]

Registers a new slash command listener. This method can be used as either a decorator or a method.

# Use this method as a decorator
@app.command("/echo")
async def repeat_text(ack, say, command):
    # Acknowledge command request
    await ack()
    await say(f"{command['text']}")

# Pass a function to this method
app.command("/echo")(repeat_text)

Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands.

To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

Args

command
The conditions that match a request payload
matchers
A list of listener matcher functions. Only when all the matchers return True, the listener function can be invoked.
middleware
A list of lister middleware functions. Only when all the middleware call next() method, the listener function can be invoked.
def default_app_uninstalled_event_listener(self) ‑> Callable[..., Awaitable[Optional[BoltResponse]]]
def default_tokens_revoked_event_listener(self) ‑> Callable[..., Awaitable[Optional[BoltResponse]]]
def dialog_cancellation(self, callback_id: Union[str, Pattern], matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None) ‑> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]

Registers a new dialog_submission listener. Refer to https://api.slack.com/dialogs for details.

def dialog_submission(self, callback_id: Union[str, Pattern], matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None) ‑> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]

Registers a new dialog_submission listener. Refer to https://api.slack.com/dialogs for details.

def dialog_suggestion(self, callback_id: Union[str, Pattern], matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None) ‑> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]

Registers a new dialog_suggestion listener. Refer to https://api.slack.com/dialogs for details.

def enable_token_revocation_listeners(self) ‑> None
def error(self, func: Callable[..., Awaitable[Optional[BoltResponse]]]) ‑> Callable[..., Awaitable[Optional[BoltResponse]]]

Updates the global error handler. This method can be used as either a decorator or a method.

# Use this method as a decorator
@app.error
async def custom_error_handler(error, body, logger):
    logger.exception(f"Error: {error}")
    logger.info(f"Request body: {body}")

# Pass a function to this method
app.error(custom_error_handler)

To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

Args

func
The function that is supposed to be executed when getting an unhandled error in Bolt app.
def event(self, event: Union[str, Pattern, Dict[str, Union[str, Sequence[Union[str, Pattern, ForwardRef(None)]], ForwardRef(None)]]], matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None) ‑> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]

Registers a new event listener. This method can be used as either a decorator or a method.

# Use this method as a decorator
@app.event("team_join")
async def ask_for_introduction(event, say):
    welcome_channel_id = "C12345"
    user_id = event["user"]
    text = f"Welcome to the team, <@{user_id}>! :tada: You can introduce yourself in this channel."
    await say(text=text, channel=welcome_channel_id)

# Pass a function to this method
app.event("team_join")(ask_for_introduction)

Refer to https://api.slack.com/apis/connections/events-api for details of Events API.

To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

Args

event
The conditions that match a request payload. If you pass a dict for this, you can have type, subtype in the constraint.
matchers
A list of listener matcher functions. Only when all the matchers return True, the listener function can be invoked.
middleware
A list of lister middleware functions. Only when all the middleware call next() method, the listener function can be invoked.
def function(self, callback_id: Union[str, Pattern], matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, auto_acknowledge: bool = True) ‑> Callable[..., Optional[Callable[..., Awaitable[BoltResponse]]]]

Registers a new Function listener. This method can be used as either a decorator or a method.

# Use this method as a decorator
@app.function("reverse")
async def reverse_string(ack: AsyncAck, inputs: dict, complete: AsyncComplete, fail: AsyncFail):
    try:
        await ack()
        string_to_reverse = inputs["stringToReverse"]
        await complete({"reverseString": string_to_reverse[::-1]})
    except Exception as e:
        await fail(f"Cannot reverse string (error: {e})")
        raise e

# Pass a function to this method
app.function("reverse")(reverse_string)

To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

Args

callback_id
The callback id to identify the function
matchers
A list of listener matcher functions. Only when all the matchers return True, the listener function can be invoked.
middleware
A list of lister middleware functions. Only when all the middleware call next() method, the listener function can be invoked.
def global_shortcut(self, callback_id: Union[str, Pattern], matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None) ‑> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]

Registers a new global shortcut listener.

def message(self, keyword: Union[str, Pattern] = '', matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None) ‑> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]

Registers a new message event listener. This method can be used as either a decorator or a method. Check the App#event method's docstring for details.

# Use this method as a decorator
@app.message(":wave:")
async def say_hello(message, say):
    user = message['user']
    await say(f"Hi there, <@{user}>!")

# Pass a function to this method
app.message(":wave:")(say_hello)

Refer to https://api.slack.com/events/message for details of message events.

To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

Args

keyword
The keyword to match
matchers
A list of listener matcher functions. Only when all the matchers return True, the listener function can be invoked.
middleware
A list of lister middleware functions. Only when all the middleware call next() method, the listener function can be invoked.
def message_shortcut(self, callback_id: Union[str, Pattern], matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None) ‑> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]

Registers a new message shortcut listener.

def middleware(self, *args) ‑> Optional[Callable]

Registers a new middleware to this app. This method can be used as either a decorator or a method.

# Use this method as a decorator
@app.middleware
async def middleware_func(logger, body, next):
    logger.info(f"request body: {body}")
    await next()

# Pass a function to this method
app.middleware(middleware_func)

To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

Args

*args
A function that works as a global middleware.
def options(self, constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]], matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None) ‑> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]

Registers a new options listener. This method can be used as either a decorator or a method.

# Use this method as a decorator
@app.options("menu_selection")
async def show_menu_options(ack):
    options = [
        {
            "text": {"type": "plain_text", "text": "Option 1"},
            "value": "1-1",
        },
        {
            "text": {"type": "plain_text", "text": "Option 2"},
            "value": "1-2",
        },
    ]
    await ack(options=options)

# Pass a function to this method
app.options("menu_selection")(show_menu_options)

Refer to the following documents for details:

To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

Args

matchers
A list of listener matcher functions. Only when all the matchers return True, the listener function can be invoked.
middleware
A list of lister middleware functions. Only when all the middleware call next() method, the listener function can be invoked.
def server(self, port: int = 3000, path: str = '/slack/events', host: Optional[str] = None) ‑> AsyncSlackAppServer

Configure a web server using AIOHTTP. Refer to https://docs.aiohttp.org/ for more details about AIOHTTP.

Args

port
The port to listen on (Default: 3000)
path
The path to handle request from Slack (Default: /slack/events)
host
The hostname to serve the web endpoints. (Default: 0.0.0.0)
def shortcut(self, constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]], matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None) ‑> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]

Registers a new shortcut listener. This method can be used as either a decorator or a method.

# Use this method as a decorator
@app.shortcut("open_modal")
async def open_modal(ack, body, client):
    # Acknowledge the command request
    await ack()
    # Call views_open with the built-in client
    await client.views_open(
        # Pass a valid trigger_id within 3 seconds of receiving it
        trigger_id=body["trigger_id"],
        # View payload
        view={ ... }
    )

# Pass a function to this method
app.shortcut("open_modal")(open_modal)

Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts.

To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

Args

constraints
The conditions that match a request payload.
matchers
A list of listener matcher functions. Only when all the matchers return True, the listener function can be invoked.
middleware
A list of lister middleware functions. Only when all the middleware call next() method, the listener function can be invoked.
def start(self, port: int = 3000, path: str = '/slack/events', host: Optional[str] = None) ‑> None

Start a web server using AIOHTTP. Refer to https://docs.aiohttp.org/ for more details about AIOHTTP.

Args

port
The port to listen on (Default: 3000)
path
The path to handle request from Slack (Default: /slack/events)
host
The hostname to serve the web endpoints. (Default: 0.0.0.0)
def step(self, callback_id: Union[str, Pattern, AsyncWorkflowStepAsyncWorkflowStepBuilder], edit: Union[Callable[..., Optional[BoltResponse]], AsyncListener, Sequence[Callable], ForwardRef(None)] = None, save: Union[Callable[..., Optional[BoltResponse]], AsyncListener, Sequence[Callable], ForwardRef(None)] = None, execute: Union[Callable[..., Optional[BoltResponse]], AsyncListener, Sequence[Callable], ForwardRef(None)] = None)

Deprecated

Steps from apps for legacy workflows are now deprecated. Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

Registers a new step from app listener.

Unlike others, this method doesn't behave as a decorator. If you want to register a step from app by a decorator, use AsyncWorkflowStepBuilder's methods.

# Create a new WorkflowStep instance
from slack_bolt.workflows.async_step import AsyncWorkflowStep
ws = AsyncWorkflowStep(
    callback_id="add_task",
    edit=edit,
    save=save,
    execute=execute,
)
# Pass Step to set up listeners
app.step(ws)

Refer to https://api.slack.com/workflows/steps for details of steps from apps.

To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document. For further information about AsyncWorkflowStep specific function arguments such as configure, update, complete, and fail, refer to the async prefixed ones in slack_bolt.workflows.step.utilities API documents.

Args

callback_id
The Callback ID for this step from app
edit
The function for displaying a modal in the Workflow Builder
save
The function for handling configuration in the Workflow Builder
execute
The function for handling the step execution
def use(self, *args) ‑> Optional[Callable]

Refer to AsyncApp#middleware() method's docstring for details.

def view(self, constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]], matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None) ‑> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]

Registers a new view_submission/view_closed event listener. This method can be used as either a decorator or a method.

# Use this method as a decorator
@app.view("view_1")
async def handle_submission(ack, body, client, view):
    # Assume there's an input block with <code>block\_c</code> as the block_id and <code>dreamy\_input</code>
    hopes_and_dreams = view["state"]["values"]["block_c"]["dreamy_input"]
    user = body["user"]["id"]
    # Validate the inputs
    errors = {}
    if hopes_and_dreams is not None and len(hopes_and_dreams) <= 5:
        errors["block_c"] = "The value must be longer than 5 characters"
    if len(errors) > 0:
        await ack(response_action="errors", errors=errors)
        return
    # Acknowledge the view_submission event and close the modal
    await ack()
    # Do whatever you want with the input data - here we're saving it to a DB

# Pass a function to this method
app.view("view_1")(handle_submission)

Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads.

To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

Args

constraints
The conditions that match a request payload
matchers
A list of listener matcher functions. Only when all the matchers return True, the listener function can be invoked.
middleware
A list of lister middleware functions. Only when all the middleware call next() method, the listener function can be invoked.
def view_closed(self, constraints: Union[str, Pattern], matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None) ‑> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]

Registers a new view_closed listener. Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details.

def view_submission(self, constraints: Union[str, Pattern], matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None) ‑> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]

Registers a new view_submission listener. Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details.

def web_app(self, path: str = '/slack/events', port: int = 3000) ‑> aiohttp.web_app.Application

Returns a web.Application instance for aiohttp-devtools users.

from slack_bolt.async_app import AsyncApp
app = AsyncApp()

@app.event("app_mention")
async def event_test(body, say, logger):
    logger.info(body)
    await say("What's up?")

def app_factory():
    return app.web_app()

# adev runserver --port 3000 --app-factory app_factory async_app.py

Args

path
The path to receive incoming requests from Slack
port
The port to listen on (Default: 3000)
class AsyncAssistant (*, app_name: str = 'assistant', thread_context_store: Optional[AsyncAssistantThreadContextStore] = None, logger: Optional[logging.Logger] = None)

A middleware can process request data before other middleware and listener functions.

Expand source code
class AsyncAssistant(AsyncMiddleware):
    _thread_started_listeners: Optional[List[AsyncListener]]
    _user_message_listeners: Optional[List[AsyncListener]]
    _bot_message_listeners: Optional[List[AsyncListener]]
    _thread_context_changed_listeners: Optional[List[AsyncListener]]

    thread_context_store: Optional[AsyncAssistantThreadContextStore]
    base_logger: Optional[logging.Logger]

    def __init__(
        self,
        *,
        app_name: str = "assistant",
        thread_context_store: Optional[AsyncAssistantThreadContextStore] = None,
        logger: Optional[logging.Logger] = None,
    ):
        self.app_name = app_name
        self.thread_context_store = thread_context_store
        self.base_logger = logger

        self._thread_started_listeners = None
        self._thread_context_changed_listeners = None
        self._user_message_listeners = None
        self._bot_message_listeners = None

    def thread_started(
        self,
        *args,
        matchers: Optional[Union[Callable[..., bool], AsyncListenerMatcher]] = None,
        middleware: Optional[Union[Callable, AsyncMiddleware]] = None,
        lazy: Optional[List[Callable[..., None]]] = None,
    ):
        if self._thread_started_listeners is None:
            self._thread_started_listeners = []
        all_matchers = self._merge_matchers(
            build_listener_matcher(
                func=is_assistant_thread_started_event,
                asyncio=True,
                base_logger=self.base_logger,
            ),  # type:ignore[arg-type]
            matchers,
        )
        if is_used_without_argument(args):
            func = args[0]
            self._thread_started_listeners.append(
                self.build_listener(
                    listener_or_functions=func,
                    matchers=all_matchers,
                    middleware=middleware,  # type:ignore[arg-type]
                )
            )
            return func

        def _inner(func):
            functions = [func] + (lazy if lazy is not None else [])
            self._thread_started_listeners.append(
                self.build_listener(
                    listener_or_functions=functions,
                    matchers=all_matchers,
                    middleware=middleware,
                )
            )

            @wraps(func)
            def _wrapper(*args, **kwargs):
                return func(*args, **kwargs)

            return _wrapper

        return _inner

    def user_message(
        self,
        *args,
        matchers: Optional[Union[Callable[..., bool], AsyncListenerMatcher]] = None,
        middleware: Optional[Union[Callable, AsyncMiddleware]] = None,
        lazy: Optional[List[Callable[..., None]]] = None,
    ):
        if self._user_message_listeners is None:
            self._user_message_listeners = []
        all_matchers = self._merge_matchers(
            build_listener_matcher(
                func=is_user_message_event_in_assistant_thread,
                asyncio=True,
                base_logger=self.base_logger,
            ),  # type:ignore[arg-type]
            matchers,
        )
        if is_used_without_argument(args):
            func = args[0]
            self._user_message_listeners.append(
                self.build_listener(
                    listener_or_functions=func,
                    matchers=all_matchers,
                    middleware=middleware,  # type:ignore[arg-type]
                )
            )
            return func

        def _inner(func):
            functions = [func] + (lazy if lazy is not None else [])
            self._user_message_listeners.append(
                self.build_listener(
                    listener_or_functions=functions,
                    matchers=all_matchers,
                    middleware=middleware,
                )
            )

            @wraps(func)
            def _wrapper(*args, **kwargs):
                return func(*args, **kwargs)

            return _wrapper

        return _inner

    def bot_message(
        self,
        *args,
        matchers: Optional[Union[Callable[..., bool], AsyncListenerMatcher]] = None,
        middleware: Optional[Union[Callable, AsyncMiddleware]] = None,
        lazy: Optional[List[Callable[..., None]]] = None,
    ):
        if self._bot_message_listeners is None:
            self._bot_message_listeners = []
        all_matchers = self._merge_matchers(
            build_listener_matcher(
                func=is_bot_message_event_in_assistant_thread,
                asyncio=True,
                base_logger=self.base_logger,
            ),  # type:ignore[arg-type]
            matchers,
        )
        if is_used_without_argument(args):
            func = args[0]
            self._bot_message_listeners.append(
                self.build_listener(
                    listener_or_functions=func,
                    matchers=all_matchers,
                    middleware=middleware,  # type:ignore[arg-type]
                )
            )
            return func

        def _inner(func):
            functions = [func] + (lazy if lazy is not None else [])
            self._bot_message_listeners.append(
                self.build_listener(
                    listener_or_functions=functions,
                    matchers=all_matchers,
                    middleware=middleware,
                )
            )

            @wraps(func)
            def _wrapper(*args, **kwargs):
                return func(*args, **kwargs)

            return _wrapper

        return _inner

    def thread_context_changed(
        self,
        *args,
        matchers: Optional[Union[Callable[..., bool], AsyncListenerMatcher]] = None,
        middleware: Optional[Union[Callable, AsyncMiddleware]] = None,
        lazy: Optional[List[Callable[..., None]]] = None,
    ):
        if self._thread_context_changed_listeners is None:
            self._thread_context_changed_listeners = []
        all_matchers = self._merge_matchers(
            build_listener_matcher(
                func=is_assistant_thread_context_changed_event,
                asyncio=True,
                base_logger=self.base_logger,
            ),  # type:ignore[arg-type]
            matchers,
        )
        if is_used_without_argument(args):
            func = args[0]
            self._thread_context_changed_listeners.append(
                self.build_listener(
                    listener_or_functions=func,
                    matchers=all_matchers,
                    middleware=middleware,  # type:ignore[arg-type]
                )
            )
            return func

        def _inner(func):
            functions = [func] + (lazy if lazy is not None else [])
            self._thread_context_changed_listeners.append(
                self.build_listener(
                    listener_or_functions=functions,
                    matchers=all_matchers,
                    middleware=middleware,
                )
            )

            @wraps(func)
            def _wrapper(*args, **kwargs):
                return func(*args, **kwargs)

            return _wrapper

        return _inner

    @staticmethod
    def _merge_matchers(
        primary_matcher: Union[Callable[..., bool], AsyncListenerMatcher],
        custom_matchers: Optional[Union[Callable[..., bool], AsyncListenerMatcher]],
    ):
        return [primary_matcher] + (custom_matchers or [])  # type:ignore[operator]

    @staticmethod
    async def default_thread_context_changed(save_thread_context: AsyncSaveThreadContext, payload: dict):
        new_context: dict = payload["assistant_thread"]["context"]
        await save_thread_context(new_context)

    async def async_process(  # type:ignore[return]
        self,
        *,
        req: AsyncBoltRequest,
        resp: BoltResponse,
        next: Callable[[], Awaitable[BoltResponse]],
    ) -> Optional[BoltResponse]:
        if self._thread_context_changed_listeners is None:
            self.thread_context_changed(self.default_thread_context_changed)

        listener_runner: AsyncioListenerRunner = req.context.listener_runner
        for listeners in [
            self._thread_started_listeners,
            self._thread_context_changed_listeners,
            self._user_message_listeners,
            self._bot_message_listeners,
        ]:
            if listeners is not None:
                for listener in listeners:
                    if listener is not None and await listener.async_matches(req=req, resp=resp):
                        return await listener_runner.run(
                            request=req,
                            response=resp,
                            listener_name="assistant_listener",
                            listener=listener,
                        )
        if is_other_message_sub_event_in_assistant_thread(req.body):
            # message_changed, message_deleted, etc.
            return await req.context.ack()

        await next()

    def build_listener(
        self,
        listener_or_functions: Union[AsyncListener, Callable, List[Callable]],
        matchers: Optional[List[Union[AsyncListenerMatcher, Callable[..., Awaitable[bool]]]]] = None,
        middleware: Optional[List[AsyncMiddleware]] = None,
        base_logger: Optional[Logger] = None,
    ) -> AsyncListener:
        if isinstance(listener_or_functions, Callable):  # type:ignore[arg-type]
            listener_or_functions = [listener_or_functions]  # type:ignore[list-item]

        if isinstance(listener_or_functions, AsyncListener):
            return listener_or_functions
        elif isinstance(listener_or_functions, list):
            middleware = middleware if middleware else []
            functions = listener_or_functions
            ack_function = functions.pop(0)

            matchers = matchers if matchers else []
            listener_matchers: List[AsyncListenerMatcher] = []
            for matcher in matchers:
                if isinstance(matcher, AsyncListenerMatcher):
                    listener_matchers.append(matcher)
                else:
                    listener_matchers.append(
                        build_listener_matcher(
                            func=matcher,  # type:ignore[arg-type]
                            asyncio=True,
                            base_logger=base_logger,
                        )
                    )
            return AsyncCustomListener(
                app_name=self.app_name,
                matchers=listener_matchers,
                middleware=middleware,
                ack_function=ack_function,
                lazy_functions=functions,
                auto_acknowledgement=True,
                base_logger=base_logger or self.base_logger,
            )
        else:
            raise BoltError(f"Invalid listener: {type(listener_or_functions)} detected")

Ancestors

Class variables

var base_logger : Optional[logging.Logger]
var thread_context_store : Optional[AsyncAssistantThreadContextStore]

Static methods

async def default_thread_context_changed(save_thread_context: AsyncSaveThreadContext, payload: dict)

Methods

def bot_message(self, *args, matchers: Union[Callable[..., bool], AsyncListenerMatcher, ForwardRef(None)] = None, middleware: Union[Callable, AsyncMiddleware, ForwardRef(None)] = None, lazy: Optional[List[Callable[..., None]]] = None)
def build_listener(self, listener_or_functions: Union[AsyncListener, Callable, List[Callable]], matchers: Optional[List[Union[AsyncListenerMatcher, Callable[..., Awaitable[bool]]]]] = None, middleware: Optional[List[AsyncMiddleware]] = None, base_logger: Optional[logging.Logger] = None) ‑> AsyncListener
def thread_context_changed(self, *args, matchers: Union[Callable[..., bool], AsyncListenerMatcher, ForwardRef(None)] = None, middleware: Union[Callable, AsyncMiddleware, ForwardRef(None)] = None, lazy: Optional[List[Callable[..., None]]] = None)
def thread_started(self, *args, matchers: Union[Callable[..., bool], AsyncListenerMatcher, ForwardRef(None)] = None, middleware: Union[Callable, AsyncMiddleware, ForwardRef(None)] = None, lazy: Optional[List[Callable[..., None]]] = None)
def user_message(self, *args, matchers: Union[Callable[..., bool], AsyncListenerMatcher, ForwardRef(None)] = None, middleware: Union[Callable, AsyncMiddleware, ForwardRef(None)] = None, lazy: Optional[List[Callable[..., None]]] = None)

Inherited members

class AsyncBoltContext (*args, **kwargs)

Context object associated with a request from Slack.

Expand source code
class AsyncBoltContext(BaseContext):
    """Context object associated with a request from Slack."""

    def to_copyable(self) -> "AsyncBoltContext":
        new_dict = {}
        for prop_name, prop_value in self.items():
            if prop_name in self.copyable_standard_property_names:
                # all the standard properties are copiable
                new_dict[prop_name] = prop_value
            elif prop_name in self.non_copyable_standard_property_names:
                # Do nothing with this property (e.g., listener_runner)
                continue
            else:
                try:
                    copied_value = create_copy(prop_value)
                    new_dict[prop_name] = copied_value
                except TypeError as te:
                    self.logger.debug(
                        f"Skipped setting '{prop_name}' to a copied request for lazy listeners "
                        f"as it's not possible to make a deep copy (error: {te})"
                    )
        return AsyncBoltContext(new_dict)

    # The return type is intentionally string to avoid circular imports
    @property
    def listener_runner(self) -> "AsyncioListenerRunner":  # type: ignore[name-defined]
        """The properly configured listener_runner that is available for middleware/listeners."""
        return self["listener_runner"]

    @property
    def client(self) -> AsyncWebClient:
        """The `AsyncWebClient` instance available for this request.

            @app.event("app_mention")
            async def handle_events(context):
                await context.client.chat_postMessage(
                    channel=context.channel_id,
                    text="Thanks!",
                )

            # You can access "client" this way too.
            @app.event("app_mention")
            async def handle_events(client, context):
                await client.chat_postMessage(
                    channel=context.channel_id,
                    text="Thanks!",
                )

        Returns:
            `AsyncWebClient` instance
        """
        if "client" not in self:
            self["client"] = AsyncWebClient(token=None)
        return self["client"]

    @property
    def ack(self) -> AsyncAck:
        """`ack()` function for this request.

            @app.action("button")
            async def handle_button_clicks(context):
                await context.ack()

            # You can access "ack" this way too.
            @app.action("button")
            async def handle_button_clicks(ack):
                await ack()

        Returns:
            Callable `ack()` function
        """
        if "ack" not in self:
            self["ack"] = AsyncAck()
        return self["ack"]

    @property
    def say(self) -> AsyncSay:
        """`say()` function for this request.

            @app.action("button")
            async def handle_button_clicks(context):
                await context.ack()
                await context.say("Hi!")

            # You can access "ack" this way too.
            @app.action("button")
            async def handle_button_clicks(ack, say):
                await ack()
                await say("Hi!")

        Returns:
            Callable `say()` function
        """
        if "say" not in self:
            self["say"] = AsyncSay(client=self.client, channel=self.channel_id, thread_ts=self.thread_ts)
        return self["say"]

    @property
    def respond(self) -> Optional[AsyncRespond]:
        """`respond()` function for this request.

            @app.action("button")
            async def handle_button_clicks(context):
                await context.ack()
                await context.respond("Hi!")

            # You can access "ack" this way too.
            @app.action("button")
            async def handle_button_clicks(ack, respond):
                await ack()
                await respond("Hi!")

        Returns:
            Callable `respond()` function
        """
        if "respond" not in self:
            self["respond"] = AsyncRespond(
                response_url=self.response_url,
                proxy=self.client.proxy,
                ssl=self.client.ssl,
            )
        return self["respond"]

    @property
    def complete(self) -> AsyncComplete:
        """`complete()` function for this request. Once a custom function's state is set to complete,
        any outputs the function returns will be passed along to the next step of its housing workflow,
        or complete the workflow if the function is the last step in a workflow. Additionally,
        any interactivity handlers associated to a function invocation will no longer be invocable.

            @app.function("reverse")
            async def handle_button_clicks(ack, complete):
                await ack()
                await complete(outputs={"stringReverse":"olleh"})

            @app.function("reverse")
            async def handle_button_clicks(context):
                await context.ack()
                await context.complete(outputs={"stringReverse":"olleh"})

        Returns:
            Callable `complete()` function
        """
        if "complete" not in self:
            self["complete"] = AsyncComplete(client=self.client, function_execution_id=self.function_execution_id)
        return self["complete"]

    @property
    def fail(self) -> AsyncFail:
        """`fail()` function for this request. Once a custom function's state is set to error,
        its housing workflow will be interrupted and any provided error message will be passed
        on to the end user through SlackBot. Additionally, any interactivity handlers associated
        to a function invocation will no longer be invocable.

            @app.function("reverse")
            async def handle_button_clicks(ack, fail):
                await ack()
                await fail(error="something went wrong")

            @app.function("reverse")
            async def handle_button_clicks(context):
                await context.ack()
                await context.fail(error="something went wrong")

        Returns:
            Callable `fail()` function
        """
        if "fail" not in self:
            self["fail"] = AsyncFail(client=self.client, function_execution_id=self.function_execution_id)
        return self["fail"]

    @property
    def set_title(self) -> Optional[AsyncSetTitle]:
        return self.get("set_title")

    @property
    def set_status(self) -> Optional[AsyncSetStatus]:
        return self.get("set_status")

    @property
    def set_suggested_prompts(self) -> Optional[AsyncSetSuggestedPrompts]:
        return self.get("set_suggested_prompts")

    @property
    def get_thread_context(self) -> Optional[AsyncGetThreadContext]:
        return self.get("get_thread_context")

    @property
    def save_thread_context(self) -> Optional[AsyncSaveThreadContext]:
        return self.get("save_thread_context")

Ancestors

Instance variables

prop ackAsyncAck

ack() function for this request.

@app.action("button")
async def handle_button_clicks(context):
    await context.ack()

# You can access "ack" this way too.
@app.action("button")
async def handle_button_clicks(ack):
    await ack()

Returns

Callable ack() function

Expand source code
@property
def ack(self) -> AsyncAck:
    """`ack()` function for this request.

        @app.action("button")
        async def handle_button_clicks(context):
            await context.ack()

        # You can access "ack" this way too.
        @app.action("button")
        async def handle_button_clicks(ack):
            await ack()

    Returns:
        Callable `ack()` function
    """
    if "ack" not in self:
        self["ack"] = AsyncAck()
    return self["ack"]
prop client : slack_sdk.web.async_client.AsyncWebClient

The AsyncWebClient instance available for this request.

@app.event("app_mention")
async def handle_events(context):
    await context.client.chat_postMessage(
        channel=context.channel_id,
        text="Thanks!",
    )

# You can access "client" this way too.
@app.event("app_mention")
async def handle_events(client, context):
    await client.chat_postMessage(
        channel=context.channel_id,
        text="Thanks!",
    )

Returns

AsyncWebClient instance

Expand source code
@property
def client(self) -> AsyncWebClient:
    """The `AsyncWebClient` instance available for this request.

        @app.event("app_mention")
        async def handle_events(context):
            await context.client.chat_postMessage(
                channel=context.channel_id,
                text="Thanks!",
            )

        # You can access "client" this way too.
        @app.event("app_mention")
        async def handle_events(client, context):
            await client.chat_postMessage(
                channel=context.channel_id,
                text="Thanks!",
            )

    Returns:
        `AsyncWebClient` instance
    """
    if "client" not in self:
        self["client"] = AsyncWebClient(token=None)
    return self["client"]
prop completeAsyncComplete

complete() function for this request. Once a custom function's state is set to complete, any outputs the function returns will be passed along to the next step of its housing workflow, or complete the workflow if the function is the last step in a workflow. Additionally, any interactivity handlers associated to a function invocation will no longer be invocable.

@app.function("reverse")
async def handle_button_clicks(ack, complete):
    await ack()
    await complete(outputs={"stringReverse":"olleh"})

@app.function("reverse")
async def handle_button_clicks(context):
    await context.ack()
    await context.complete(outputs={"stringReverse":"olleh"})

Returns

Callable complete() function

Expand source code
@property
def complete(self) -> AsyncComplete:
    """`complete()` function for this request. Once a custom function's state is set to complete,
    any outputs the function returns will be passed along to the next step of its housing workflow,
    or complete the workflow if the function is the last step in a workflow. Additionally,
    any interactivity handlers associated to a function invocation will no longer be invocable.

        @app.function("reverse")
        async def handle_button_clicks(ack, complete):
            await ack()
            await complete(outputs={"stringReverse":"olleh"})

        @app.function("reverse")
        async def handle_button_clicks(context):
            await context.ack()
            await context.complete(outputs={"stringReverse":"olleh"})

    Returns:
        Callable `complete()` function
    """
    if "complete" not in self:
        self["complete"] = AsyncComplete(client=self.client, function_execution_id=self.function_execution_id)
    return self["complete"]
prop failAsyncFail

fail() function for this request. Once a custom function's state is set to error, its housing workflow will be interrupted and any provided error message will be passed on to the end user through SlackBot. Additionally, any interactivity handlers associated to a function invocation will no longer be invocable.

@app.function("reverse")
async def handle_button_clicks(ack, fail):
    await ack()
    await fail(error="something went wrong")

@app.function("reverse")
async def handle_button_clicks(context):
    await context.ack()
    await context.fail(error="something went wrong")

Returns

Callable fail() function

Expand source code
@property
def fail(self) -> AsyncFail:
    """`fail()` function for this request. Once a custom function's state is set to error,
    its housing workflow will be interrupted and any provided error message will be passed
    on to the end user through SlackBot. Additionally, any interactivity handlers associated
    to a function invocation will no longer be invocable.

        @app.function("reverse")
        async def handle_button_clicks(ack, fail):
            await ack()
            await fail(error="something went wrong")

        @app.function("reverse")
        async def handle_button_clicks(context):
            await context.ack()
            await context.fail(error="something went wrong")

    Returns:
        Callable `fail()` function
    """
    if "fail" not in self:
        self["fail"] = AsyncFail(client=self.client, function_execution_id=self.function_execution_id)
    return self["fail"]
prop get_thread_context : Optional[AsyncGetThreadContext]
Expand source code
@property
def get_thread_context(self) -> Optional[AsyncGetThreadContext]:
    return self.get("get_thread_context")
prop listener_runner : AsyncioListenerRunner

The properly configured listener_runner that is available for middleware/listeners.

Expand source code
@property
def listener_runner(self) -> "AsyncioListenerRunner":  # type: ignore[name-defined]
    """The properly configured listener_runner that is available for middleware/listeners."""
    return self["listener_runner"]
prop respond : Optional[AsyncRespond]

respond() function for this request.

@app.action("button")
async def handle_button_clicks(context):
    await context.ack()
    await context.respond("Hi!")

# You can access "ack" this way too.
@app.action("button")
async def handle_button_clicks(ack, respond):
    await ack()
    await respond("Hi!")

Returns

Callable respond() function

Expand source code
@property
def respond(self) -> Optional[AsyncRespond]:
    """`respond()` function for this request.

        @app.action("button")
        async def handle_button_clicks(context):
            await context.ack()
            await context.respond("Hi!")

        # You can access "ack" this way too.
        @app.action("button")
        async def handle_button_clicks(ack, respond):
            await ack()
            await respond("Hi!")

    Returns:
        Callable `respond()` function
    """
    if "respond" not in self:
        self["respond"] = AsyncRespond(
            response_url=self.response_url,
            proxy=self.client.proxy,
            ssl=self.client.ssl,
        )
    return self["respond"]
prop save_thread_context : Optional[AsyncSaveThreadContext]
Expand source code
@property
def save_thread_context(self) -> Optional[AsyncSaveThreadContext]:
    return self.get("save_thread_context")
prop sayAsyncSay

say() function for this request.

@app.action("button")
async def handle_button_clicks(context):
    await context.ack()
    await context.say("Hi!")

# You can access "ack" this way too.
@app.action("button")
async def handle_button_clicks(ack, say):
    await ack()
    await say("Hi!")

Returns

Callable say() function

Expand source code
@property
def say(self) -> AsyncSay:
    """`say()` function for this request.

        @app.action("button")
        async def handle_button_clicks(context):
            await context.ack()
            await context.say("Hi!")

        # You can access "ack" this way too.
        @app.action("button")
        async def handle_button_clicks(ack, say):
            await ack()
            await say("Hi!")

    Returns:
        Callable `say()` function
    """
    if "say" not in self:
        self["say"] = AsyncSay(client=self.client, channel=self.channel_id, thread_ts=self.thread_ts)
    return self["say"]
prop set_status : Optional[AsyncSetStatus]
Expand source code
@property
def set_status(self) -> Optional[AsyncSetStatus]:
    return self.get("set_status")
prop set_suggested_prompts : Optional[AsyncSetSuggestedPrompts]
Expand source code
@property
def set_suggested_prompts(self) -> Optional[AsyncSetSuggestedPrompts]:
    return self.get("set_suggested_prompts")
prop set_title : Optional[AsyncSetTitle]
Expand source code
@property
def set_title(self) -> Optional[AsyncSetTitle]:
    return self.get("set_title")

Methods

def to_copyable(self) ‑> AsyncBoltContext

Inherited members

class AsyncBoltRequest (*, body: Union[str, dict], query: Union[str, Dict[str, str], Dict[str, Sequence[str]], ForwardRef(None)] = None, headers: Optional[Dict[str, Union[str, Sequence[str]]]] = None, context: Optional[Dict[str, Any]] = None, mode: str = 'http')

Request to a Bolt app.

Args

body
The raw request body (only plain text is supported for "http" mode)
query
The query string data in any data format.
headers
The request headers.
context
The context in this request.
mode
The mode used for this request. (either "http" or "socket_mode")
Expand source code
class AsyncBoltRequest:
    raw_body: str
    body: Dict[str, Any]
    query: Dict[str, Sequence[str]]
    headers: Dict[str, Sequence[str]]
    content_type: Optional[str]
    context: AsyncBoltContext
    lazy_only: bool
    lazy_function_name: Optional[str]
    mode: str  # either "http" or "socket_mode"

    def __init__(
        self,
        *,
        body: Union[str, dict],
        query: Optional[Union[str, Dict[str, str], Dict[str, Sequence[str]]]] = None,
        headers: Optional[Dict[str, Union[str, Sequence[str]]]] = None,
        context: Optional[Dict[str, Any]] = None,
        mode: str = "http",  # either "http" or "socket_mode"
    ):
        """Request to a Bolt app.

        Args:
            body: The raw request body (only plain text is supported for "http" mode)
            query: The query string data in any data format.
            headers: The request headers.
            context: The context in this request.
            mode: The mode used for this request. (either "http" or "socket_mode")
        """

        if mode == "http":
            # HTTP Mode
            if body is not None and not isinstance(body, str):
                raise BoltError(error_message_raw_body_required_in_http_mode())
            self.raw_body = body if body is not None else ""
        else:
            # Socket Mode
            if body is not None and isinstance(body, str):
                self.raw_body = body
            else:
                # We don't convert the dict value to str
                # as doing so does not guarantee to keep the original structure/format.
                self.raw_body = ""

        self.query = parse_query(query)
        self.headers = build_normalized_headers(headers)
        self.content_type = extract_content_type(self.headers)

        if isinstance(body, str):
            self.body = parse_body(self.raw_body, self.content_type)
        elif isinstance(body, dict):
            self.body = body
        else:
            self.body = {}

        self.context = build_async_context(AsyncBoltContext(context if context else {}), self.body)
        self.lazy_only = bool(self.headers.get("x-slack-bolt-lazy-only", [False])[0])
        self.lazy_function_name = self.headers.get("x-slack-bolt-lazy-function-name", [None])[0]
        self.mode = mode

    def to_copyable(self) -> "AsyncBoltRequest":
        body: Union[str, dict] = self.raw_body if self.mode == "http" else self.body
        return AsyncBoltRequest(
            body=body,
            query=self.query,
            headers=self.headers,
            context=self.context.to_copyable(),
            mode=self.mode,
        )

Class variables

var body : Dict[str, Any]
var content_type : Optional[str]
var contextAsyncBoltContext
var headers : Dict[str, Sequence[str]]
var lazy_function_name : Optional[str]
var lazy_only : bool
var mode : str
var query : Dict[str, Sequence[str]]
var raw_body : str

Methods

def to_copyable(self) ‑> AsyncBoltRequest
class AsyncCustomListenerMatcher (*, app_name: str, func: Callable[..., Awaitable[bool]], base_logger: Optional[logging.Logger] = None)
Expand source code
class AsyncCustomListenerMatcher(AsyncListenerMatcher):
    app_name: str
    func: Callable[..., Awaitable[bool]]
    arg_names: Sequence[str]
    logger: Logger

    def __init__(self, *, app_name: str, func: Callable[..., Awaitable[bool]], base_logger: Optional[Logger] = None):
        self.app_name = app_name
        self.func = func
        self.arg_names = get_arg_names_of_callable(func)
        self.logger = get_bolt_app_logger(self.app_name, self.func, base_logger)

    async def async_matches(self, req: AsyncBoltRequest, resp: BoltResponse) -> bool:
        return await self.func(
            **build_async_required_kwargs(
                logger=self.logger,
                required_arg_names=self.arg_names,  # type: ignore[arg-type]
                request=req,
                response=resp,
                this_func=self.func,
            )
        )

Ancestors

Class variables

var app_name : str
var arg_names : Sequence[str]
var func : Callable[..., Awaitable[bool]]
var logger : logging.Logger

Inherited members

class AsyncGetThreadContext (thread_context_store: AsyncAssistantThreadContextStore, channel_id: str, thread_ts: str, payload: dict)
Expand source code
class AsyncGetThreadContext:
    thread_context_store: AsyncAssistantThreadContextStore
    payload: dict
    channel_id: str
    thread_ts: str

    _thread_context: Optional[AssistantThreadContext]
    thread_context_loaded: bool

    def __init__(
        self,
        thread_context_store: AsyncAssistantThreadContextStore,
        channel_id: str,
        thread_ts: str,
        payload: dict,
    ):
        self.thread_context_store = thread_context_store
        self.payload = payload
        self.channel_id = channel_id
        self.thread_ts = thread_ts
        self._thread_context: Optional[AssistantThreadContext] = None
        self.thread_context_loaded = False

    async def __call__(self) -> Optional[AssistantThreadContext]:
        if self.thread_context_loaded is True:
            return self._thread_context

        if self.payload.get("assistant_thread") is not None:
            # assistant_thread_started
            thread = self.payload["assistant_thread"]
            self._thread_context = (
                AssistantThreadContext(thread["context"])
                if thread.get("context", {}).get("channel_id") is not None
                else None
            )
            # for this event, the context will never be changed
            self.thread_context_loaded = True
        elif self.payload.get("channel") is not None and self.payload.get("thread_ts") is not None:
            # message event
            self._thread_context = await self.thread_context_store.find(channel_id=self.channel_id, thread_ts=self.thread_ts)

        return self._thread_context

Class variables

var channel_id : str
var payload : dict
var thread_context_loaded : bool
var thread_context_storeAsyncAssistantThreadContextStore
var thread_ts : str
class AsyncListener
Expand source code
class AsyncListener(metaclass=ABCMeta):
    matchers: Sequence[AsyncListenerMatcher]
    middleware: Sequence[AsyncMiddleware]
    ack_function: Callable[..., Awaitable[BoltResponse]]
    lazy_functions: Sequence[Callable[..., Awaitable[None]]]
    auto_acknowledgement: bool

    async def async_matches(
        self,
        *,
        req: AsyncBoltRequest,
        resp: BoltResponse,
    ) -> bool:
        is_matched: bool = False
        for matcher in self.matchers:
            is_matched = await matcher.async_matches(req, resp)
            if not is_matched:
                return is_matched
        return is_matched

    async def run_async_middleware(
        self,
        *,
        req: AsyncBoltRequest,
        resp: BoltResponse,
    ) -> Tuple[Optional[BoltResponse], bool]:
        """Runs an async middleware.

        Args:
            req: The incoming request
            resp: The current response

        Returns:
            A tuple of the processed response and a flag indicating termination
        """
        for m in self.middleware:
            middleware_state = {"next_called": False}

            async def _next():
                middleware_state["next_called"] = True

            resp = await m.async_process(req=req, resp=resp, next=_next)  # type: ignore[assignment]
            if not middleware_state["next_called"]:
                # next() was not called in this middleware
                return (resp, True)
        return (resp, False)

    @abstractmethod
    async def run_ack_function(self, *, request: AsyncBoltRequest, response: BoltResponse) -> Optional[BoltResponse]:
        """Runs all the registered middleware and then run the listener function.

        Args:
            request: The incoming request
            response: The current response

        Returns:
            The processed response
        """
        raise NotImplementedError()

Subclasses

Class variables

var ack_function : Callable[..., Awaitable[BoltResponse]]
var auto_acknowledgement : bool
var lazy_functions : Sequence[Callable[..., Awaitable[None]]]
var matchers : Sequence[AsyncListenerMatcher]
var middleware : Sequence[AsyncMiddleware]

Methods

async def async_matches(self, *, req: AsyncBoltRequest, resp: BoltResponse) ‑> bool
async def run_ack_function(self, *, request: AsyncBoltRequest, response: BoltResponse) ‑> Optional[BoltResponse]

Runs all the registered middleware and then run the listener function.

Args

request
The incoming request
response
The current response

Returns

The processed response

async def run_async_middleware(self, *, req: AsyncBoltRequest, resp: BoltResponse) ‑> Tuple[Optional[BoltResponse], bool]

Runs an async middleware.

Args

req
The incoming request
resp
The current response

Returns

A tuple of the processed response and a flag indicating termination

class AsyncRespond (*, response_url: Optional[str], proxy: Optional[str] = None, ssl: Optional[ssl.SSLContext] = None)
Expand source code
class AsyncRespond:
    response_url: Optional[str]
    proxy: Optional[str]
    ssl: Optional[SSLContext]

    def __init__(
        self,
        *,
        response_url: Optional[str],
        proxy: Optional[str] = None,
        ssl: Optional[SSLContext] = None,
    ):
        self.response_url = response_url
        self.proxy = proxy
        self.ssl = ssl

    async def __call__(
        self,
        text: Union[str, dict] = "",
        blocks: Optional[Sequence[Union[dict, Block]]] = None,
        attachments: Optional[Sequence[Union[dict, Attachment]]] = None,
        response_type: Optional[str] = None,
        replace_original: Optional[bool] = None,
        delete_original: Optional[bool] = None,
        unfurl_links: Optional[bool] = None,
        unfurl_media: Optional[bool] = None,
        thread_ts: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> WebhookResponse:
        if self.response_url is not None:
            client = AsyncWebhookClient(
                url=self.response_url,
                proxy=self.proxy,
                ssl=self.ssl,
            )
            text_or_whole_response: Union[str, dict] = text
            if isinstance(text_or_whole_response, str):
                message = _build_message(
                    text=text,  # type: ignore[arg-type]
                    blocks=blocks,
                    attachments=attachments,
                    response_type=response_type,
                    replace_original=replace_original,
                    delete_original=delete_original,
                    unfurl_links=unfurl_links,
                    unfurl_media=unfurl_media,
                    thread_ts=thread_ts,
                    metadata=metadata,
                )
                return await client.send_dict(message)
            elif isinstance(text_or_whole_response, dict):
                whole_response: dict = text_or_whole_response
                message = _build_message(**whole_response)
                return await client.send_dict(message)
            else:
                raise ValueError(f"The arg is unexpected type ({type(text)})")
        else:
            raise ValueError("respond is unsupported here as there is no response_url")

Class variables

var proxy : Optional[str]
var response_url : Optional[str]
var ssl : Optional[ssl.SSLContext]
class AsyncSaveThreadContext (thread_context_store: AsyncAssistantThreadContextStore, channel_id: str, thread_ts: str)
Expand source code
class AsyncSaveThreadContext:
    thread_context_store: AsyncAssistantThreadContextStore
    channel_id: str
    thread_ts: str

    def __init__(
        self,
        thread_context_store: AsyncAssistantThreadContextStore,
        channel_id: str,
        thread_ts: str,
    ):
        self.thread_context_store = thread_context_store
        self.channel_id = channel_id
        self.thread_ts = thread_ts

    async def __call__(self, new_context: Dict[str, str]) -> None:
        await self.thread_context_store.save(
            channel_id=self.channel_id,
            thread_ts=self.thread_ts,
            context=new_context,
        )

Class variables

var channel_id : str
var thread_context_storeAsyncAssistantThreadContextStore
var thread_ts : str
class AsyncSay (client: Optional[slack_sdk.web.async_client.AsyncWebClient], channel: Optional[str], thread_ts: Optional[str] = None, build_metadata: Optional[Callable[[], Awaitable[Union[Dict, slack_sdk.models.metadata.Metadata]]]] = None)
Expand source code
class AsyncSay:
    client: Optional[AsyncWebClient]
    channel: Optional[str]
    thread_ts: Optional[str]
    build_metadata: Optional[Callable[[], Awaitable[Union[Dict, Metadata]]]]

    def __init__(
        self,
        client: Optional[AsyncWebClient],
        channel: Optional[str],
        thread_ts: Optional[str] = None,
        build_metadata: Optional[Callable[[], Awaitable[Union[Dict, Metadata]]]] = None,
    ):
        self.client = client
        self.channel = channel
        self.thread_ts = thread_ts
        self.build_metadata = build_metadata

    async def __call__(
        self,
        text: Union[str, dict] = "",
        blocks: Optional[Sequence[Union[Dict, Block]]] = None,
        attachments: Optional[Sequence[Union[Dict, Attachment]]] = None,
        channel: Optional[str] = None,
        as_user: Optional[bool] = None,
        thread_ts: Optional[str] = None,
        reply_broadcast: Optional[bool] = None,
        unfurl_links: Optional[bool] = None,
        unfurl_media: Optional[bool] = None,
        icon_emoji: Optional[str] = None,
        icon_url: Optional[str] = None,
        username: Optional[str] = None,
        mrkdwn: Optional[bool] = None,
        link_names: Optional[bool] = None,
        parse: Optional[str] = None,  # none, full
        metadata: Optional[Union[Dict, Metadata]] = None,
        **kwargs,
    ) -> AsyncSlackResponse:
        if _can_say(self, channel):
            if metadata is None and self.build_metadata is not None:
                metadata = await self.build_metadata()
            text_or_whole_response: Union[str, dict] = text
            if isinstance(text_or_whole_response, str):
                text = text_or_whole_response
                return await self.client.chat_postMessage(  # type: ignore[union-attr]
                    channel=channel or self.channel,  # type: ignore[arg-type]
                    text=text,
                    blocks=blocks,
                    attachments=attachments,
                    as_user=as_user,
                    thread_ts=thread_ts or self.thread_ts,
                    reply_broadcast=reply_broadcast,
                    unfurl_links=unfurl_links,
                    unfurl_media=unfurl_media,
                    icon_emoji=icon_emoji,
                    icon_url=icon_url,
                    username=username,
                    mrkdwn=mrkdwn,
                    link_names=link_names,
                    parse=parse,
                    metadata=metadata,
                    **kwargs,
                )
            elif isinstance(text_or_whole_response, dict):
                message: dict = create_copy(text_or_whole_response)
                if "channel" not in message:
                    message["channel"] = channel or self.channel
                if "thread_ts" not in message:
                    message["thread_ts"] = thread_ts or self.thread_ts
                if "metadata" not in message:
                    message["metadata"] = metadata
                return await self.client.chat_postMessage(**message)  # type: ignore[union-attr]
            else:
                raise ValueError(f"The arg is unexpected type ({type(text_or_whole_response)})")
        else:
            raise ValueError("say without channel_id here is unsupported")

Class variables

var build_metadata : Optional[Callable[[], Awaitable[Union[Dict, slack_sdk.models.metadata.Metadata]]]]
var channel : Optional[str]
var client : Optional[slack_sdk.web.async_client.AsyncWebClient]
var thread_ts : Optional[str]
class AsyncSetStatus (client: slack_sdk.web.async_client.AsyncWebClient, channel_id: str, thread_ts: str)
Expand source code
class AsyncSetStatus:
    client: AsyncWebClient
    channel_id: str
    thread_ts: str

    def __init__(
        self,
        client: AsyncWebClient,
        channel_id: str,
        thread_ts: str,
    ):
        self.client = client
        self.channel_id = channel_id
        self.thread_ts = thread_ts

    async def __call__(self, status: str) -> AsyncSlackResponse:
        return await self.client.assistant_threads_setStatus(
            status=status,
            channel_id=self.channel_id,
            thread_ts=self.thread_ts,
        )

Class variables

var channel_id : str
var client : slack_sdk.web.async_client.AsyncWebClient
var thread_ts : str
class AsyncSetSuggestedPrompts (client: slack_sdk.web.async_client.AsyncWebClient, channel_id: str, thread_ts: str)
Expand source code
class AsyncSetSuggestedPrompts:
    client: AsyncWebClient
    channel_id: str
    thread_ts: str

    def __init__(
        self,
        client: AsyncWebClient,
        channel_id: str,
        thread_ts: str,
    ):
        self.client = client
        self.channel_id = channel_id
        self.thread_ts = thread_ts

    async def __call__(self, prompts: List[Union[str, Dict[str, str]]]) -> AsyncSlackResponse:
        prompts_arg: List[Dict[str, str]] = []
        for prompt in prompts:
            if isinstance(prompt, str):
                prompts_arg.append({"title": prompt, "message": prompt})
            else:
                prompts_arg.append(prompt)

        return await self.client.assistant_threads_setSuggestedPrompts(
            channel_id=self.channel_id,
            thread_ts=self.thread_ts,
            prompts=prompts_arg,
        )

Class variables

var channel_id : str
var client : slack_sdk.web.async_client.AsyncWebClient
var thread_ts : str
class AsyncSetTitle (client: slack_sdk.web.async_client.AsyncWebClient, channel_id: str, thread_ts: str)
Expand source code
class AsyncSetTitle:
    client: AsyncWebClient
    channel_id: str
    thread_ts: str

    def __init__(
        self,
        client: AsyncWebClient,
        channel_id: str,
        thread_ts: str,
    ):
        self.client = client
        self.channel_id = channel_id
        self.thread_ts = thread_ts

    async def __call__(self, title: str) -> AsyncSlackResponse:
        return await self.client.assistant_threads_setTitle(
            title=title,
            channel_id=self.channel_id,
            thread_ts=self.thread_ts,
        )

Class variables

var channel_id : str
var client : slack_sdk.web.async_client.AsyncWebClient
var thread_ts : str