Package slack_bolt

A Python framework to build Slack apps in a flash with the latest platform features.Read the getting started guide and look at our code examples to learn how to build apps using Bolt.

Expand source code
"""
A Python framework to build Slack apps in a flash with the latest platform features.Read the [getting started guide](https://slack.dev/bolt-python/tutorial/getting-started) and look at our [code examples](https://github.com/slackapi/bolt-python/tree/main/examples) to learn how to build apps using Bolt.

* Website: https://slack.dev/bolt-python/
* GitHub repository: https://github.com/slackapi/bolt-python
* The class representing a Bolt app: `slack_bolt.app.app`
"""  # noqa: E501
# Don't add async module imports here
from .app import App
from .context import BoltContext
from .context.ack import Ack
from .context.respond import Respond
from .context.say import Say
from .kwargs_injection import Args
from .listener import Listener
from .listener_matcher import CustomListenerMatcher
from .request import BoltRequest
from .response import BoltResponse

__all__ = [
    "App",
    "BoltContext",
    "Ack",
    "Respond",
    "Say",
    "Args",
    "Listener",
    "CustomListenerMatcher",
    "BoltRequest",
    "BoltResponse",
]

Sub-modules

slack_bolt.adapter

Adapter modules for running Bolt apps along with Web frameworks or Socket Mode.

slack_bolt.app

Application interface in Bolt …

slack_bolt.async_app

Module for creating asyncio based apps …

slack_bolt.authorization

Authorization is the process of determining which Slack credentials should be available while processing an incoming Slack event …

slack_bolt.context

All listeners have access to a context dictionary, which can be used to enrich events with additional information. Bolt automatically attaches …

slack_bolt.error

Bolt specific error types.

slack_bolt.kwargs_injection

For middleware/listener arguments, Bolt does flexible data injection in accordance with their names …

slack_bolt.lazy_listener

Lazy listener runner is a beta feature for the apps running on Function-as-a-Service platforms …

slack_bolt.listener

Listeners process an incoming request from Slack if the request's type or data structure matches the predefined conditions of the listener. Typically, …

slack_bolt.listener_matcher

A listener matcher is a simplified version of listener middleware. A listener matcher function returns bool value instead of next() method …

slack_bolt.logger

Bolt for Python relies on the standard logging module.

slack_bolt.middleware

A middleware processes request data and calls next() method if the execution chain should continue running the following middleware …

slack_bolt.oauth

Slack OAuth flow support for building an app that is installable in any workspaces …

slack_bolt.request

Incoming request from Slack through either HTTP request or Socket Mode connection …

slack_bolt.response

This interface represents Bolt's synchronous response to Slack …

slack_bolt.util

Internal utilities for the Bolt framework.

slack_bolt.version

Check the latest version at https://pypi.org/project/slack-bolt/

slack_bolt.workflows

Workflow Steps from Apps enables developers to build their own custom workflow steps …

Classes

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

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

    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 App (*, 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, token_verification_enabled: bool = True, client: Optional[slack_sdk.web.client.WebClient] = None, before_authorize: Union[Middleware, Callable[..., Any], ForwardRef(None)] = None, authorize: Optional[Callable[..., AuthorizeResult]] = None, installation_store: Optional[slack_sdk.oauth.installation_store.installation_store.InstallationStore] = None, installation_store_bot_only: Optional[bool] = None, request_verification_enabled: bool = True, ignoring_self_events_enabled: bool = True, ssl_check_enabled: bool = True, url_verification_enabled: bool = True, oauth_settings: Optional[OAuthSettings] = None, oauth_flow: Optional[OAuthFlow] = None, verification_token: Optional[str] = None, listener_executor: Optional[concurrent.futures._base.Executor] = None)

Bolt App that provides functionalities to register middleware/listeners.

import os
from slack_bolt import App

# Initializes your app with your bot token and signing secret
app = App(
    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")
def message_hello(message, say):
    # say() sends a message to the channel where the event was triggered
    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/tutorial/getting-started 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.
token_verification_enabled
Verifies the validity of the given token if True.
client
The singleton slack_sdk.WebClient 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.
installation_store
The module offering save/find operations of installation data
installation_store_bot_only
Use InstallationStore#find_bot() if True (Default: False)
request_verification_enabled
False if you would like to disable the built-in middleware (Default: True). RequestVerification 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). IgnoringSelfEvents 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).
url_verification_enabled
False if you would like to disable the built-in middleware (Default: True). UrlVerification 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). SslCheck is a built-in middleware that handles ssl_check requests from Slack.
oauth_settings
The settings related to Slack app installation flow (OAuth flow)
oauth_flow
Instantiated OAuthFlow. This is always prioritized over oauth_settings.
verification_token
Deprecated verification mechanism. This can used only for ssl_check requests.
listener_executor
Custom executor to run background tasks. If absent, the default ThreadPoolExecutor will be used.
Expand source code
class App:
    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,
        token_verification_enabled: bool = True,
        client: Optional[WebClient] = None,
        # for multi-workspace apps
        before_authorize: Optional[Union[Middleware, Callable[..., Any]]] = None,
        authorize: Optional[Callable[..., AuthorizeResult]] = None,
        installation_store: Optional[InstallationStore] = 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,
        ssl_check_enabled: bool = True,
        url_verification_enabled: bool = True,
        # for the OAuth flow
        oauth_settings: Optional[OAuthSettings] = None,
        oauth_flow: Optional[OAuthFlow] = None,
        # No need to set (the value is used only in response to ssl_check requests)
        verification_token: Optional[str] = None,
        # Set this one only when you want to customize the executor
        listener_executor: Optional[Executor] = None,
    ):
        """Bolt App that provides functionalities to register middleware/listeners.

            import os
            from slack_bolt import App

            # Initializes your app with your bot token and signing secret
            app = App(
                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")
            def message_hello(message, say):
                # say() sends a message to the channel where the event was triggered
                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/tutorial/getting-started 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.
            token_verification_enabled: Verifies the validity of the given token if True.
            client: The singleton `slack_sdk.WebClient` 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.
            installation_store: The module offering save/find operations of installation data
            installation_store_bot_only: Use `InstallationStore#find_bot()` if True (Default: False)
            request_verification_enabled: False if you would like to disable the built-in middleware (Default: True).
                `RequestVerification` 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).
                `IgnoringSelfEvents` 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).
            url_verification_enabled: False if you would like to disable the built-in middleware (Default: True).
                `UrlVerification` 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).
                `SslCheck` is a built-in middleware that handles ssl_check requests from Slack.
            oauth_settings: The settings related to Slack app installation flow (OAuth flow)
            oauth_flow: Instantiated `slack_bolt.oauth.OAuthFlow`. This is always prioritized over oauth_settings.
            verification_token: Deprecated verification mechanism. This can used only for ssl_check requests.
            listener_executor: Custom executor to run background tasks. If absent, the default `ThreadPoolExecutor` will
                be used.
        """
        signing_secret = signing_secret or 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(App)
        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, WebClient):
                raise BoltError(error_client_invalid_type())
            self._client = client
            self._token = client.token
            if token is not None:
                self._framework_logger.warning(warning_client_prioritized_and_token_skipped())
        else:
            self._client = create_web_client(
                # NOTE: the token here can be None
                token=token,
                logger=self._framework_logger,
            )

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

        self._before_authorize: Optional[Middleware] = None
        if before_authorize is not None:
            if isinstance(before_authorize, Callable):
                self._before_authorize = CustomMiddleware(
                    app_name=self._name,
                    func=before_authorize,
                    base_logger=self._framework_logger,
                )
            elif isinstance(before_authorize, Middleware):
                self._before_authorize = before_authorize

        self._authorize: Optional[Authorize] = None
        if authorize is not None:
            if isinstance(authorize, Authorize):
                # As long as an advanced developer understands what they're doing,
                # bolt-python should not prevent customizing authorize middleware
                self._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._authorize = CallableAuthorize(logger=self._framework_logger, func=authorize)

        self._installation_store: Optional[InstallationStore] = installation_store
        if self._installation_store is not None and self._authorize is None:
            settings = oauth_flow.settings if oauth_flow is not None else oauth_settings
            self._authorize = InstallationStoreAuthorize(
                installation_store=self._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,
                client=self._client,  # for proxy use cases etc.
                user_token_resolution=(settings.user_token_resolution if settings is not None else "authed_user"),
            )

        self._oauth_flow: Optional[OAuthFlow] = 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 = OAuthSettings()

            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 is not None:
            self._oauth_flow = oauth_flow
            installation_store = select_consistent_installation_store(
                client_id=self._oauth_flow.client_id,
                app_store=self._installation_store,
                oauth_flow_store=self._oauth_flow.settings.installation_store,
                logger=self._framework_logger,
            )
            self._installation_store = installation_store
            self._oauth_flow.settings.installation_store = installation_store

            if self._oauth_flow._client is None:
                self._oauth_flow._client = self._client
            if self._authorize is None:
                self._authorize = self._oauth_flow.settings.authorize
        elif oauth_settings is not None:
            installation_store = select_consistent_installation_store(
                client_id=oauth_settings.client_id,
                app_store=self._installation_store,
                oauth_flow_store=oauth_settings.installation_store,
                logger=self._framework_logger,
            )
            self._installation_store = installation_store
            oauth_settings.installation_store = installation_store
            self._oauth_flow = OAuthFlow(client=self.client, logger=self.logger, settings=oauth_settings)
            if self._authorize is None:
                self._authorize = self._oauth_flow.settings.authorize
            self._authorize.token_rotation_expiration_minutes = oauth_settings.token_rotation_expiration_minutes

        if (self._installation_store is not None or self._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._oauth_flow is not None:
            app_bot_only = installation_store_bot_only or False
            oauth_flow_bot_only = self._oauth_flow.settings.installation_store_bot_only
            if app_bot_only != oauth_flow_bot_only:
                self.logger.warning(warning_bot_only_conflicts())
                self._oauth_flow.settings.installation_store_bot_only = app_bot_only
                self._authorize.bot_only = app_bot_only

        self._tokens_revocation_listeners: Optional[TokenRevocationListeners] = None
        if self._installation_store is not None:
            self._tokens_revocation_listeners = TokenRevocationListeners(self._installation_store)

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

        self._middleware_list: List[Middleware] = []
        self._listeners: List[Listener] = []

        if listener_executor is None:
            listener_executor = ThreadPoolExecutor(max_workers=5)

        self._process_before_response = process_before_response
        self._listener_runner = ThreadListenerRunner(
            logger=self._framework_logger,
            process_before_response=process_before_response,
            listener_error_handler=DefaultListenerErrorHandler(logger=self._framework_logger),
            listener_start_handler=DefaultListenerStartHandler(logger=self._framework_logger),
            listener_completion_handler=DefaultListenerCompletionHandler(logger=self._framework_logger),
            listener_executor=listener_executor,
            lazy_listener_runner=ThreadLazyListenerRunner(
                logger=self._framework_logger,
                executor=listener_executor,
            ),
        )
        self._middleware_error_handler = DefaultMiddlewareErrorHandler(
            logger=self._framework_logger,
        )

        self._init_middleware_list_done = False
        self._init_middleware_list(
            token_verification_enabled=token_verification_enabled,
            request_verification_enabled=request_verification_enabled,
            ignoring_self_events_enabled=ignoring_self_events_enabled,
            ssl_check_enabled=ssl_check_enabled,
            url_verification_enabled=url_verification_enabled,
        )

    def _init_middleware_list(
        self,
        token_verification_enabled: bool = True,
        request_verification_enabled: bool = True,
        ignoring_self_events_enabled: bool = True,
        ssl_check_enabled: bool = True,
        url_verification_enabled: bool = True,
    ):
        if self._init_middleware_list_done:
            return
        if ssl_check_enabled is True:
            self._middleware_list.append(
                SslCheck(
                    verification_token=self._verification_token,
                    base_logger=self._base_logger,
                )
            )
        if request_verification_enabled is True:
            self._middleware_list.append(RequestVerification(self._signing_secret, base_logger=self._base_logger))

        if self._before_authorize is not None:
            self._middleware_list.append(self._before_authorize)

        # As authorize is required for making a Bolt app function, we don't offer the flag to disable this
        if self._oauth_flow is None:
            if self._token is not None:
                try:
                    auth_test_result = None
                    if token_verification_enabled:
                        # This API call is for eagerly validating the token
                        auth_test_result = self._client.auth_test(token=self._token)
                    self._middleware_list.append(
                        SingleTeamAuthorization(
                            auth_test_result=auth_test_result,
                            base_logger=self._base_logger,
                        )
                    )
                except SlackApiError as err:
                    raise BoltError(error_auth_test_failure(err.response))
            elif self._authorize is not None:
                self._middleware_list.append(
                    MultiTeamsAuthorization(authorize=self._authorize, base_logger=self._base_logger)
                )
            else:
                raise BoltError(error_token_required())
        else:
            self._middleware_list.append(
                MultiTeamsAuthorization(
                    authorize=self._authorize,
                    base_logger=self._base_logger,
                    user_token_resolution=self._oauth_flow.settings.user_token_resolution,
                )
            )
        if ignoring_self_events_enabled is True:
            self._middleware_list.append(IgnoringSelfEvents(base_logger=self._base_logger))
        if url_verification_enabled is True:
            self._middleware_list.append(UrlVerification(base_logger=self._base_logger))
        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[OAuthFlow]:
        """Configured `OAuthFlow` object if exists."""
        return self._oauth_flow

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

    @property
    def client(self) -> WebClient:
        """The singleton `slack_sdk.WebClient` instance in this app."""
        return self._client

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

    @property
    def listener_runner(self) -> ThreadListenerRunner:
        """The thread executor for asynchronously running listeners."""
        return self._listener_runner

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

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

    def start(
        self,
        port: int = 3000,
        path: str = "/slack/events",
        http_server_logger_enabled: bool = True,
    ) -> None:
        """Starts a web server for local development.

            # With the default settings, `http://localhost:3000/slack/events`
            # is available for handling incoming requests from Slack
            app.start()

        This method internally starts a Web server process built with the `http.server` module.
        For production, consider using a production-ready WSGI server such as Gunicorn.

        Args:
            port: The port to listen on (Default: 3000)
            path: The path to handle request from Slack (Default: `/slack/events`)
            http_server_logger_enabled: The flag to enable http.server logging if True (Default: True)
        """
        self._development_server = SlackAppDevelopmentServer(
            port=port,
            path=path,
            app=self,
            oauth_flow=self.oauth_flow,
            http_server_logger_enabled=http_server_logger_enabled,
        )
        self._development_server.start()

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

    def dispatch(self, req: BoltRequest) -> 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}

        def middleware_next():
            middleware_state["next_called"] = True

        try:
            for middleware in self._middleware_list:
                middleware_state["next_called"] = False
                if self._framework_logger.level <= logging.DEBUG:
                    self._framework_logger.debug(debug_applying_middleware(middleware.name))
                resp = middleware.process(req=req, resp=resp, next=middleware_next)
                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:
                                self._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._listeners:
                listener_name = get_name_for_callable(listener.ack_function)
                self._framework_logger.debug(debug_checking_listener(listener_name))
                if listener.matches(req=req, resp=resp):
                    # run all the middleware attached to this listener first
                    middleware_resp, next_was_not_called = listener.run_middleware(req=req, resp=resp)
                    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] = self._listener_runner.run(
                        request=req,
                        response=resp,
                        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:
                    self._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="")
            self._middleware_error_handler.handle(
                error=error,
                request=req,
                response=resp,
            )
            return resp

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

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

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

        Refer to `App#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
            def middleware_func(logger, body, next):
                logger.info(f"request body: {body}")
                next()

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

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

        To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.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, Middleware):
                middleware: Middleware = middleware_or_callable
                self._middleware_list.append(middleware)
            elif isinstance(middleware_or_callable, Callable):
                self._middleware_list.append(
                    CustomMiddleware(
                        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

    # -------------------------
    # Workflows: Steps from Apps

    def step(
        self,
        callback_id: Union[str, Pattern, WorkflowStep, WorkflowStepBuilder],
        edit: Optional[Union[Callable[..., Optional[BoltResponse]], Listener, Sequence[Callable]]] = None,
        save: Optional[Union[Callable[..., Optional[BoltResponse]], Listener, Sequence[Callable]]] = None,
        execute: Optional[Union[Callable[..., Optional[BoltResponse]], Listener, Sequence[Callable]]] = None,
    ):
        """Registers a new Workflow Step listener.
        Unlike others, this method doesn't behave as a decorator.
        If you want to register a workflow step by a decorator, use `WorkflowStepBuilder`'s methods.

            # Create a new WorkflowStep instance
            from slack_bolt.workflows.step import WorkflowStep
            ws = WorkflowStep(
                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.args`'s API document.

        For further information about WorkflowStep specific function arguments
        such as `configure`, `update`, `complete`, and `fail`,
        refer to `slack_bolt.workflows.step.utilities` API documents.

        Args:
            callback_id: The Callback ID for this workflow step
            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
        """
        step = callback_id
        if isinstance(callback_id, (str, Pattern)):
            step = WorkflowStep(
                callback_id=callback_id,
                edit=edit,
                save=save,
                execute=execute,
                base_logger=self._base_logger,
            )
        elif isinstance(step, WorkflowStepBuilder):
            step = step.build(base_logger=self._base_logger)
        elif not isinstance(step, WorkflowStep):
            raise BoltError(f"Invalid step object ({type(step)})")

        self.use(WorkflowStepMiddleware(step, self.listener_runner))

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

    def error(self, func: Callable[..., Optional[BoltResponse]]) -> Callable[..., 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
            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.args`'s API document.

        Args:
            func: The function that is supposed to be executed
                when getting an unhandled error in Bolt app.
        """
        self._listener_runner.listener_error_handler = CustomListenerErrorHandler(
            logger=self._framework_logger,
            func=func,
        )
        self._middleware_error_handler = CustomMiddlewareErrorHandler(
            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[..., bool]]] = None,
        middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., 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")
            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."
                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.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, 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[..., bool]]] = None,
        middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., 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:")
            def say_hello(message, say):
                user = message['user']
                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.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(
                keyword=keyword, constraints=constraints, base_logger=self._base_logger
            )
            middleware.insert(0, MessageListenerMatches(keyword))
            return self._register_listener(list(functions), primary_matcher, matchers, middleware, True)

        return __call__

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

    def command(
        self,
        command: Union[str, Pattern],
        matchers: Optional[Sequence[Callable[..., bool]]] = None,
        middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., 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")
            def repeat_text(ack, say, command):
                # Acknowledge command request
                ack()
                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.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, 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[..., bool]]] = None,
        middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., 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")
            def open_modal(ack, body, client):
                # Acknowledge the command request
                ack()
                # Call views_open with the built-in client
                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.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, 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[..., bool]]] = None,
        middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., 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, 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[..., bool]]] = None,
        middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., 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, 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[..., bool]]] = None,
        middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., 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")
            def update_message(ack):
                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.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, 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[..., bool]]] = None,
        middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., 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, 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[..., bool]]] = None,
        middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., 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, 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[..., bool]]] = None,
        middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., 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, 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[..., bool]]] = None,
        middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]:
        """Registers a new `dialog_cancellation` 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, 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[..., bool]]] = None,
        middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., 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")
            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:
                    ack(response_action="errors", errors=errors)
                    return
                # Acknowledge the view_submission event and close the modal
                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.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, 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[..., bool]]] = None,
        middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., 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, 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[..., bool]]] = None,
        middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., 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, 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[..., bool]]] = None,
        middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., 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")
            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",
                    },
                ]
                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.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, 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[..., bool]]] = None,
        middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., 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, 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[..., bool]]] = None,
        middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
    ) -> Callable[..., Optional[Callable[..., 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, 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[..., Optional[BoltResponse]]:
        if self._tokens_revocation_listeners is None:
            raise BoltError(error_installation_store_required_for_builtin_listeners())
        return self._tokens_revocation_listeners.handle_tokens_revoked_events

    def default_app_uninstalled_event_listener(
        self,
    ) -> Callable[..., Optional[BoltResponse]]:
        if self._tokens_revocation_listeners is None:
            raise BoltError(error_installation_store_required_for_builtin_listeners())
        return self._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: BoltRequest):
        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: WebClient = WebClient(
            token=self._token,  # this can be None, and it can be set later on
            base_url=self._client.base_url,
            timeout=self._client.timeout,
            ssl=self._client.ssl,
            proxy=self._client.proxy,
            headers=self._client.headers,
            team_id=req.context.team_id,
            retry_handlers=self._client.retry_handlers.copy() if self._client.retry_handlers is not None else None,
        )
        req.context["client"] = client_per_request

    @staticmethod
    def _to_listener_functions(
        kwargs: dict,
    ) -> Optional[Sequence[Callable[..., 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[..., Optional[BoltResponse]]],
        primary_matcher: ListenerMatcher,
        matchers: Optional[Sequence[Callable[..., bool]]],
        middleware: Optional[Sequence[Union[Callable, Middleware]]],
        auto_acknowledgement: bool = False,
    ) -> Optional[Callable[..., 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]

        listener_matchers = [
            CustomListenerMatcher(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, Middleware):
                listener_middleware.append(m)
            elif isinstance(m, Callable):
                listener_middleware.append(CustomMiddleware(app_name=self.name, func=m, base_logger=self._base_logger))
            else:
                raise ValueError(error_unexpected_listener_middleware(type(m)))

        self._listeners.append(
            CustomListener(
                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

Instance variables

var client : slack_sdk.web.client.WebClient

The singleton slack_sdk.WebClient instance in this app.

Expand source code
@property
def client(self) -> WebClient:
    """The singleton `slack_sdk.WebClient` instance in this app."""
    return self._client
var installation_store : Optional[slack_sdk.oauth.installation_store.installation_store.InstallationStore]

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

Expand source code
@property
def installation_store(self) -> Optional[InstallationStore]:
    """The `slack_sdk.oauth.InstallationStore` that can be used in the `authorize` middleware."""
    return self._installation_store
var listener_runnerThreadListenerRunner

The thread executor for asynchronously running listeners.

Expand source code
@property
def listener_runner(self) -> ThreadListenerRunner:
    """The thread executor for asynchronously running listeners."""
    return self._listener_runner
var 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
var 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
var oauth_flow : Optional[OAuthFlow]

Configured OAuthFlow object if exists.

Expand source code
@property
def oauth_flow(self) -> Optional[OAuthFlow]:
    """Configured `OAuthFlow` object if exists."""
    return self._oauth_flow
var 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[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None) ‑> Callable[..., Optional[Callable[..., 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")
def update_message(ack):
    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.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.
Expand source code
def action(
    self,
    constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
    matchers: Optional[Sequence[Callable[..., bool]]] = None,
    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., 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")
        def update_message(ack):
            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.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, 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[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None) ‑> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]

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

Expand source code
def attachment_action(
    self,
    callback_id: Union[str, Pattern],
    matchers: Optional[Sequence[Callable[..., bool]]] = None,
    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., 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, 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[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None) ‑> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]

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

Expand source code
def block_action(
    self,
    constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
    matchers: Optional[Sequence[Callable[..., bool]]] = None,
    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., 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, 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[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None) ‑> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]

Registers a new block_suggestion listener.

Expand source code
def block_suggestion(
    self,
    action_id: Union[str, Pattern],
    matchers: Optional[Sequence[Callable[..., bool]]] = None,
    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., 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, base_logger=self._base_logger)
        return self._register_listener(list(functions), primary_matcher, matchers, middleware)

    return __call__
def command(self, command: Union[str, Pattern], matchers: Optional[Sequence[Callable[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None) ‑> Callable[..., Optional[Callable[..., 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")
def repeat_text(ack, say, command):
    # Acknowledge command request
    ack()
    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.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.
Expand source code
def command(
    self,
    command: Union[str, Pattern],
    matchers: Optional[Sequence[Callable[..., bool]]] = None,
    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., 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")
        def repeat_text(ack, say, command):
            # Acknowledge command request
            ack()
            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.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, base_logger=self._base_logger)
        return self._register_listener(list(functions), primary_matcher, matchers, middleware)

    return __call__
def default_app_uninstalled_event_listener(self) ‑> Callable[..., Optional[BoltResponse]]
Expand source code
def default_app_uninstalled_event_listener(
    self,
) -> Callable[..., Optional[BoltResponse]]:
    if self._tokens_revocation_listeners is None:
        raise BoltError(error_installation_store_required_for_builtin_listeners())
    return self._tokens_revocation_listeners.handle_app_uninstalled_events
def default_tokens_revoked_event_listener(self) ‑> Callable[..., Optional[BoltResponse]]
Expand source code
def default_tokens_revoked_event_listener(
    self,
) -> Callable[..., Optional[BoltResponse]]:
    if self._tokens_revocation_listeners is None:
        raise BoltError(error_installation_store_required_for_builtin_listeners())
    return self._tokens_revocation_listeners.handle_tokens_revoked_events
def dialog_cancellation(self, callback_id: Union[str, Pattern], matchers: Optional[Sequence[Callable[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None) ‑> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]

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

Expand source code
def dialog_cancellation(
    self,
    callback_id: Union[str, Pattern],
    matchers: Optional[Sequence[Callable[..., bool]]] = None,
    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]:
    """Registers a new `dialog_cancellation` 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, 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[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None) ‑> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]

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

Expand source code
def dialog_submission(
    self,
    callback_id: Union[str, Pattern],
    matchers: Optional[Sequence[Callable[..., bool]]] = None,
    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., 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, 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[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None) ‑> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]

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

Expand source code
def dialog_suggestion(
    self,
    callback_id: Union[str, Pattern],
    matchers: Optional[Sequence[Callable[..., bool]]] = None,
    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., 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, base_logger=self._base_logger)
        return self._register_listener(list(functions), primary_matcher, matchers, middleware)

    return __call__
def dispatch(self, req: BoltRequest) ‑> 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

Expand source code
def dispatch(self, req: BoltRequest) -> 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}

    def middleware_next():
        middleware_state["next_called"] = True

    try:
        for middleware in self._middleware_list:
            middleware_state["next_called"] = False
            if self._framework_logger.level <= logging.DEBUG:
                self._framework_logger.debug(debug_applying_middleware(middleware.name))
            resp = middleware.process(req=req, resp=resp, next=middleware_next)
            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:
                            self._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._listeners:
            listener_name = get_name_for_callable(listener.ack_function)
            self._framework_logger.debug(debug_checking_listener(listener_name))
            if listener.matches(req=req, resp=resp):
                # run all the middleware attached to this listener first
                middleware_resp, next_was_not_called = listener.run_middleware(req=req, resp=resp)
                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] = self._listener_runner.run(
                    request=req,
                    response=resp,
                    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:
                self._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="")
        self._middleware_error_handler.handle(
            error=error,
            request=req,
            response=resp,
        )
        return resp
def enable_token_revocation_listeners(self) ‑> None
Expand source code
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 error(self, func: Callable[..., Optional[BoltResponse]]) ‑> Callable[..., 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
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.args's API document.

Args

func
The function that is supposed to be executed when getting an unhandled error in Bolt app.
Expand source code
def error(self, func: Callable[..., Optional[BoltResponse]]) -> Callable[..., 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
        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.args`'s API document.

    Args:
        func: The function that is supposed to be executed
            when getting an unhandled error in Bolt app.
    """
    self._listener_runner.listener_error_handler = CustomListenerErrorHandler(
        logger=self._framework_logger,
        func=func,
    )
    self._middleware_error_handler = CustomMiddlewareErrorHandler(
        logger=self._framework_logger,
        func=func,
    )
    return func
def event(self, event: Union[str, Pattern, Dict[str, Union[str, Sequence[Union[str, Pattern, ForwardRef(None)]], ForwardRef(None)]]], matchers: Optional[Sequence[Callable[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None) ‑> Callable[..., Optional[Callable[..., 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")
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."
    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.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.
Expand source code
def event(
    self,
    event: Union[
        str,
        Pattern,
        Dict[str, Optional[Union[str, Sequence[Optional[Union[str, Pattern]]]]]],
    ],
    matchers: Optional[Sequence[Callable[..., bool]]] = None,
    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., 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")
        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."
            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.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, base_logger=self._base_logger)
        return self._register_listener(list(functions), primary_matcher, matchers, middleware, True)

    return __call__
def global_shortcut(self, callback_id: Union[str, Pattern], matchers: Optional[Sequence[Callable[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None) ‑> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]

Registers a new global shortcut listener.

Expand source code
def global_shortcut(
    self,
    callback_id: Union[str, Pattern],
    matchers: Optional[Sequence[Callable[..., bool]]] = None,
    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., 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, base_logger=self._base_logger)
        return self._register_listener(list(functions), primary_matcher, matchers, middleware)

    return __call__
def message(self, keyword: Union[str, Pattern] = '', matchers: Optional[Sequence[Callable[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None) ‑> Callable[..., Optional[Callable[..., 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:")
def say_hello(message, say):
    user = message['user']
    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.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.
Expand source code
def message(
    self,
    keyword: Union[str, Pattern] = "",
    matchers: Optional[Sequence[Callable[..., bool]]] = None,
    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., 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:")
        def say_hello(message, say):
            user = message['user']
            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.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(
            keyword=keyword, constraints=constraints, base_logger=self._base_logger
        )
        middleware.insert(0, MessageListenerMatches(keyword))
        return self._register_listener(list(functions), primary_matcher, matchers, middleware, True)

    return __call__
def message_shortcut(self, callback_id: Union[str, Pattern], matchers: Optional[Sequence[Callable[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None) ‑> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]

Registers a new message shortcut listener.

Expand source code
def message_shortcut(
    self,
    callback_id: Union[str, Pattern],
    matchers: Optional[Sequence[Callable[..., bool]]] = None,
    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., 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, base_logger=self._base_logger)
        return self._register_listener(list(functions), primary_matcher, matchers, middleware)

    return __call__
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
def middleware_func(logger, body, next):
    logger.info(f"request body: {body}")
    next()

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

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

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

Args

*args
A function that works as a global middleware.
Expand source code
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
        def middleware_func(logger, body, next):
            logger.info(f"request body: {body}")
            next()

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

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

    To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.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, Middleware):
            middleware: Middleware = middleware_or_callable
            self._middleware_list.append(middleware)
        elif isinstance(middleware_or_callable, Callable):
            self._middleware_list.append(
                CustomMiddleware(
                    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 options(self, constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]], matchers: Optional[Sequence[Callable[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None) ‑> Callable[..., Optional[Callable[..., 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")
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",
        },
    ]
    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.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.
Expand source code
def options(
    self,
    constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
    matchers: Optional[Sequence[Callable[..., bool]]] = None,
    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., 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")
        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",
                },
            ]
            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.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, base_logger=self._base_logger)
        return self._register_listener(list(functions), primary_matcher, matchers, middleware)

    return __call__
def shortcut(self, constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]], matchers: Optional[Sequence[Callable[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None) ‑> Callable[..., Optional[Callable[..., 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")
def open_modal(ack, body, client):
    # Acknowledge the command request
    ack()
    # Call views_open with the built-in client
    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.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.
Expand source code
def shortcut(
    self,
    constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
    matchers: Optional[Sequence[Callable[..., bool]]] = None,
    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., 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")
        def open_modal(ack, body, client):
            # Acknowledge the command request
            ack()
            # Call views_open with the built-in client
            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.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, base_logger=self._base_logger)
        return self._register_listener(list(functions), primary_matcher, matchers, middleware)

    return __call__
def start(self, port: int = 3000, path: str = '/slack/events', http_server_logger_enabled: bool = True) ‑> None

Starts a web server for local development.

# With the default settings, `http://localhost:3000/slack/events`
# is available for handling incoming requests from Slack
app.start()

This method internally starts a Web server process built with the http.server module. For production, consider using a production-ready WSGI server such as Gunicorn.

Args

port
The port to listen on (Default: 3000)
path
The path to handle request from Slack (Default: /slack/events)
http_server_logger_enabled
The flag to enable http.server logging if True (Default: True)
Expand source code
def start(
    self,
    port: int = 3000,
    path: str = "/slack/events",
    http_server_logger_enabled: bool = True,
) -> None:
    """Starts a web server for local development.

        # With the default settings, `http://localhost:3000/slack/events`
        # is available for handling incoming requests from Slack
        app.start()

    This method internally starts a Web server process built with the `http.server` module.
    For production, consider using a production-ready WSGI server such as Gunicorn.

    Args:
        port: The port to listen on (Default: 3000)
        path: The path to handle request from Slack (Default: `/slack/events`)
        http_server_logger_enabled: The flag to enable http.server logging if True (Default: True)
    """
    self._development_server = SlackAppDevelopmentServer(
        port=port,
        path=path,
        app=self,
        oauth_flow=self.oauth_flow,
        http_server_logger_enabled=http_server_logger_enabled,
    )
    self._development_server.start()
def step(self, callback_id: Union[str, Pattern, WorkflowStepWorkflowStepBuilder], edit: Union[Callable[..., Optional[BoltResponse]], Listener, Sequence[Callable], ForwardRef(None)] = None, save: Union[Callable[..., Optional[BoltResponse]], Listener, Sequence[Callable], ForwardRef(None)] = None, execute: Union[Callable[..., Optional[BoltResponse]], Listener, Sequence[Callable], ForwardRef(None)] = None)

Registers a new Workflow Step listener. Unlike others, this method doesn't behave as a decorator. If you want to register a workflow step by a decorator, use WorkflowStepBuilder's methods.

# Create a new WorkflowStep instance
from slack_bolt.workflows.step import WorkflowStep
ws = WorkflowStep(
    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.args's API document.

For further information about WorkflowStep specific function arguments such as configure, update, complete, and fail, refer to slack_bolt.workflows.step.utilities API documents.

Args

callback_id
The Callback ID for this workflow step
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
Expand source code
def step(
    self,
    callback_id: Union[str, Pattern, WorkflowStep, WorkflowStepBuilder],
    edit: Optional[Union[Callable[..., Optional[BoltResponse]], Listener, Sequence[Callable]]] = None,
    save: Optional[Union[Callable[..., Optional[BoltResponse]], Listener, Sequence[Callable]]] = None,
    execute: Optional[Union[Callable[..., Optional[BoltResponse]], Listener, Sequence[Callable]]] = None,
):
    """Registers a new Workflow Step listener.
    Unlike others, this method doesn't behave as a decorator.
    If you want to register a workflow step by a decorator, use `WorkflowStepBuilder`'s methods.

        # Create a new WorkflowStep instance
        from slack_bolt.workflows.step import WorkflowStep
        ws = WorkflowStep(
            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.args`'s API document.

    For further information about WorkflowStep specific function arguments
    such as `configure`, `update`, `complete`, and `fail`,
    refer to `slack_bolt.workflows.step.utilities` API documents.

    Args:
        callback_id: The Callback ID for this workflow step
        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
    """
    step = callback_id
    if isinstance(callback_id, (str, Pattern)):
        step = WorkflowStep(
            callback_id=callback_id,
            edit=edit,
            save=save,
            execute=execute,
            base_logger=self._base_logger,
        )
    elif isinstance(step, WorkflowStepBuilder):
        step = step.build(base_logger=self._base_logger)
    elif not isinstance(step, WorkflowStep):
        raise BoltError(f"Invalid step object ({type(step)})")

    self.use(WorkflowStepMiddleware(step, self.listener_runner))
def use(self, *args) ‑> Optional[Callable]

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

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

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

    Refer to `App#middleware()` method's docstring for details."""
    return self.middleware(*args)
def view(self, constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]], matchers: Optional[Sequence[Callable[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None) ‑> Callable[..., Optional[Callable[..., 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")
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:
        ack(response_action="errors", errors=errors)
        return
    # Acknowledge the view_submission event and close the modal
    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.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.
Expand source code
def view(
    self,
    constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
    matchers: Optional[Sequence[Callable[..., bool]]] = None,
    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., 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")
        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:
                ack(response_action="errors", errors=errors)
                return
            # Acknowledge the view_submission event and close the modal
            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.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, 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[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None) ‑> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]

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

Expand source code
def view_closed(
    self,
    constraints: Union[str, Pattern],
    matchers: Optional[Sequence[Callable[..., bool]]] = None,
    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., 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, 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[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None) ‑> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]

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

Expand source code
def view_submission(
    self,
    constraints: Union[str, Pattern],
    matchers: Optional[Sequence[Callable[..., bool]]] = None,
    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., 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, base_logger=self._base_logger)
        return self._register_listener(list(functions), primary_matcher, matchers, middleware)

    return __call__
class Args (*, logger: logging.Logger, client: slack_sdk.web.client.WebClient, req: BoltRequest, resp: BoltResponse, context: BoltContext, body: Dict[str, Any], payload: Dict[str, Any], options: Optional[Dict[str, Any]] = None, shortcut: Optional[Dict[str, Any]] = None, action: Optional[Dict[str, Any]] = None, view: Optional[Dict[str, Any]] = None, command: Optional[Dict[str, Any]] = None, event: Optional[Dict[str, Any]] = None, message: Optional[Dict[str, Any]] = None, ack: Ack, say: Say, respond: Respond, next: Callable[[], None], **kwargs)

All the arguments in this class are available in any middleware / listeners. You can inject the named variables in the argument list in arbitrary order.

@app.action("link_button")
def handle_buttons(ack, respond, logger, context, body, client):
    logger.info(f"request body: {body}")
    ack()
    if context.channel_id is not None:
        respond("Hi!")
    client.views_open(
        trigger_id=body["trigger_id"],
        view={ ... }
    )

Alternatively, you can include a parameter named args and it will be injected with an instance of this class.

@app.action("link_button")
def handle_buttons(args):
    args.logger.info(f"request body: {args.body}")
    args.ack()
    if args.context.channel_id is not None:
        args.respond("Hi!")
    args.client.views_open(
        trigger_id=args.body["trigger_id"],
        view={ ... }
    )
Expand source code
class Args:
    """All the arguments in this class are available in any middleware / listeners.
    You can inject the named variables in the argument list in arbitrary order.

        @app.action("link_button")
        def handle_buttons(ack, respond, logger, context, body, client):
            logger.info(f"request body: {body}")
            ack()
            if context.channel_id is not None:
                respond("Hi!")
            client.views_open(
                trigger_id=body["trigger_id"],
                view={ ... }
            )

    Alternatively, you can include a parameter named `args` and it will be injected with an instance of this class.

        @app.action("link_button")
        def handle_buttons(args):
            args.logger.info(f"request body: {args.body}")
            args.ack()
            if args.context.channel_id is not None:
                args.respond("Hi!")
            args.client.views_open(
                trigger_id=args.body["trigger_id"],
                view={ ... }
            )

    """

    client: WebClient
    """`slack_sdk.web.WebClient` instance with a valid token"""
    logger: Logger
    """Logger instance"""
    req: BoltRequest
    """Incoming request from Slack"""
    resp: BoltResponse
    """Response representation"""
    request: BoltRequest
    """Incoming request from Slack"""
    response: BoltResponse
    """Response representation"""
    context: BoltContext
    """Context data associated with the incoming request"""
    body: Dict[str, Any]
    """Parsed request body data"""
    # payload
    payload: Dict[str, Any]
    """The unwrapped core data in the request body"""
    options: Optional[Dict[str, Any]]  # payload alias
    """An alias for payload in an `@app.options` listener"""
    shortcut: Optional[Dict[str, Any]]  # payload alias
    """An alias for payload in an `@app.shortcut` listener"""
    action: Optional[Dict[str, Any]]  # payload alias
    """An alias for payload in an `@app.action` listener"""
    view: Optional[Dict[str, Any]]  # payload alias
    """An alias for payload in an `@app.view` listener"""
    command: Optional[Dict[str, Any]]  # payload alias
    """An alias for payload in an `@app.command` listener"""
    event: Optional[Dict[str, Any]]  # payload alias
    """An alias for payload in an `@app.event` listener"""
    message: Optional[Dict[str, Any]]  # payload alias
    """An alias for payload in an `@app.message` listener"""
    # utilities
    ack: Ack
    """`ack()` utility function, which returns acknowledgement to the Slack servers"""
    say: Say
    """`say()` utility function, which calls `chat.postMessage` API with the associated channel ID"""
    respond: Respond
    """`respond()` utility function, which utilizes the associated `response_url`"""
    # middleware
    next: Callable[[], None]
    """`next()` utility function, which tells the middleware chain that it can continue with the next one"""
    next_: Callable[[], None]
    """An alias of `next()` for avoiding the Python built-in method overrides in middleware functions"""

    def __init__(
        self,
        *,
        logger: logging.Logger,
        client: WebClient,
        req: BoltRequest,
        resp: BoltResponse,
        context: BoltContext,
        body: Dict[str, Any],
        payload: Dict[str, Any],
        options: Optional[Dict[str, Any]] = None,
        shortcut: Optional[Dict[str, Any]] = None,
        action: Optional[Dict[str, Any]] = None,
        view: Optional[Dict[str, Any]] = None,
        command: Optional[Dict[str, Any]] = None,
        event: Optional[Dict[str, Any]] = None,
        message: Optional[Dict[str, Any]] = None,
        ack: Ack,
        say: Say,
        respond: Respond,
        # As this method is not supposed to be invoked by bolt-python users,
        # the naming conflict with the built-in one affects
        # only the internals of this method
        next: Callable[[], None],
        **kwargs  # noqa
    ):
        self.logger: logging.Logger = logger
        self.client: WebClient = client
        self.request = self.req = req
        self.response = self.resp = resp
        self.context: BoltContext = context

        self.body: Dict[str, Any] = body
        self.payload: Dict[str, Any] = payload
        self.options: Optional[Dict[str, Any]] = options
        self.shortcut: Optional[Dict[str, Any]] = shortcut
        self.action: Optional[Dict[str, Any]] = action
        self.view: Optional[Dict[str, Any]] = view
        self.command: Optional[Dict[str, Any]] = command
        self.event: Optional[Dict[str, Any]] = event
        self.message: Optional[Dict[str, Any]] = message

        self.ack: Ack = ack
        self.say: Say = say
        self.respond: Respond = respond
        self.next: Callable[[], None] = next
        self.next_: Callable[[], None] = next

Class variables

var ackAck

ack() utility function, which returns acknowledgement to the Slack servers

var action : Optional[Dict[str, Any]]

An alias for payload in an @app.action listener

var body : Dict[str, Any]

Parsed request body data

var client : slack_sdk.web.client.WebClient

slack_sdk.web.WebClient instance with a valid token

var command : Optional[Dict[str, Any]]

An alias for payload in an @app.command listener

var contextBoltContext

Context data associated with the incoming request

var event : Optional[Dict[str, Any]]

An alias for payload in an @app.event listener

var logger : logging.Logger

Logger instance

var message : Optional[Dict[str, Any]]

An alias for payload in an @app.message listener

var next : Callable[[], None]

next() utility function, which tells the middleware chain that it can continue with the next one

var next_ : Callable[[], None]

An alias of next() for avoiding the Python built-in method overrides in middleware functions

var options : Optional[Dict[str, Any]]

An alias for payload in an @app.options listener

var payload : Dict[str, Any]

The unwrapped core data in the request body

var reqBoltRequest

Incoming request from Slack

var requestBoltRequest

Incoming request from Slack

var respBoltResponse

Response representation

var respondRespond

respond() utility function, which utilizes the associated response_url

var responseBoltResponse

Response representation

var saySay

say() utility function, which calls chat.postMessage API with the associated channel ID

var shortcut : Optional[Dict[str, Any]]

An alias for payload in an @app.shortcut listener

var view : Optional[Dict[str, Any]]

An alias for payload in an @app.view listener

class BoltContext (*args, **kwargs)

Context object associated with a request from Slack.

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

    def to_copyable(self) -> "BoltContext":
        new_dict = {}
        for prop_name, prop_value in self.items():
            if prop_name in self.standard_property_names:
                # all the standard properties are copiable
                new_dict[prop_name] = prop_value
            else:
                try:
                    copied_value = create_copy(prop_value)
                    new_dict[prop_name] = copied_value
                except TypeError as te:
                    self.logger.warning(
                        f"Skipped setting '{prop_name}' to a copied request for lazy listeners "
                        "due to a deep-copy creation error. Consider passing the value not as part of context object "
                        f"(error: {te})"
                    )
        return BoltContext(new_dict)

    @property
    def client(self) -> Optional[WebClient]:
        """The `WebClient` instance available for this request.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ancestors

Instance variables

var ackAck

ack() function for this request.

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

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

Returns

Callable ack() function

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

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

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

    Returns:
        Callable `ack()` function
    """
    if "ack" not in self:
        self["ack"] = Ack()
    return self["ack"]
var client : Optional[slack_sdk.web.client.WebClient]

The WebClient instance available for this request.

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

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

Returns

WebClient instance

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

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

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

    Returns:
        `WebClient` instance
    """
    if "client" not in self:
        self["client"] = WebClient(token=None)
    return self["client"]
var respond : Optional[Respond]

respond() function for this request.

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

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

Returns

Callable respond() function

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

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

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

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

say() function for this request.

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

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

Returns

Callable say() function

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

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

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

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

Methods

def to_copyable(self) ‑> BoltContext
Expand source code
def to_copyable(self) -> "BoltContext":
    new_dict = {}
    for prop_name, prop_value in self.items():
        if prop_name in self.standard_property_names:
            # all the standard properties are copiable
            new_dict[prop_name] = prop_value
        else:
            try:
                copied_value = create_copy(prop_value)
                new_dict[prop_name] = copied_value
            except TypeError as te:
                self.logger.warning(
                    f"Skipped setting '{prop_name}' to a copied request for lazy listeners "
                    "due to a deep-copy creation error. Consider passing the value not as part of context object "
                    f"(error: {te})"
                )
    return BoltContext(new_dict)

Inherited members

class BoltRequest (*, 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 BoltRequest:
    raw_body: str
    query: Dict[str, Sequence[str]]
    headers: Dict[str, Sequence[str]]
    content_type: Optional[str]
    body: Dict[str, Any]
    context: BoltContext
    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_context(BoltContext(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) -> "BoltRequest":
        body: Union[str, dict] = self.raw_body if self.mode == "http" else self.body
        return BoltRequest(
            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 contextBoltContext
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) ‑> BoltRequest
Expand source code
def to_copyable(self) -> "BoltRequest":
    body: Union[str, dict] = self.raw_body if self.mode == "http" else self.body
    return BoltRequest(
        body=body,
        query=self.query,
        headers=self.headers,
        context=self.context.to_copyable(),
        mode=self.mode,
    )
class BoltResponse (*, status: int, body: Union[str, dict] = '', headers: Optional[Dict[str, Union[str, Sequence[str]]]] = None)

The response from a Bolt app.

Args

status
HTTP status code
body
The response body (dict and str are supported)
headers
The response headers.
Expand source code
class BoltResponse:
    status: int
    body: str
    headers: Dict[str, Sequence[str]]

    def __init__(
        self,
        *,
        status: int,
        body: Union[str, dict] = "",
        headers: Optional[Dict[str, Union[str, Sequence[str]]]] = None,
    ):
        """The response from a Bolt app.

        Args:
            status: HTTP status code
            body: The response body (dict and str are supported)
            headers: The response headers.
        """
        self.status: int = status
        self.body: str = json.dumps(body) if isinstance(body, dict) else body
        self.headers: Dict[str, Sequence[str]] = {}
        if headers is not None:
            for name, value in headers.items():
                if value is None:
                    continue
                if isinstance(value, list):
                    self.headers[name.lower()] = value
                elif isinstance(value, set):
                    self.headers[name.lower()] = list(value)
                else:
                    self.headers[name.lower()] = [str(value)]

        if "content-type" not in self.headers.keys():
            if self.body and self.body.startswith("{"):
                self.headers["content-type"] = ["application/json;charset=utf-8"]
            else:
                self.headers["content-type"] = ["text/plain;charset=utf-8"]

    def first_headers(self) -> Dict[str, str]:
        return {k: list(v)[0] for k, v in self.headers.items()}

    def first_headers_without_set_cookie(self) -> Dict[str, str]:
        return {k: list(v)[0] for k, v in self.headers.items() if k != "set-cookie"}

    def cookies(self) -> Sequence[SimpleCookie]:
        header_values = self.headers.get("set-cookie", [])
        return [self._to_simple_cookie(v) for v in header_values]

    @staticmethod
    def _to_simple_cookie(header_value: str) -> SimpleCookie:
        c = SimpleCookie()
        c.load(header_value)
        return c

Class variables

var body : str
var headers : Dict[str, Sequence[str]]
var status : int

Methods

def cookies(self) ‑> Sequence[http.cookies.SimpleCookie]
Expand source code
def cookies(self) -> Sequence[SimpleCookie]:
    header_values = self.headers.get("set-cookie", [])
    return [self._to_simple_cookie(v) for v in header_values]
def first_headers(self) ‑> Dict[str, str]
Expand source code
def first_headers(self) -> Dict[str, str]:
    return {k: list(v)[0] for k, v in self.headers.items()}
Expand source code
def first_headers_without_set_cookie(self) -> Dict[str, str]:
    return {k: list(v)[0] for k, v in self.headers.items() if k != "set-cookie"}
class CustomListenerMatcher (*, app_name: str, func: Callable[..., bool], base_logger: Optional[logging.Logger] = None)
Expand source code
class CustomListenerMatcher(ListenerMatcher):
    app_name: str
    func: Callable[..., bool]
    arg_names: Sequence[str]
    logger: Logger

    def __init__(self, *, app_name: str, func: Callable[..., 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)

    def matches(self, req: BoltRequest, resp: BoltResponse) -> bool:
        return self.func(
            **build_required_kwargs(
                logger=self.logger,
                required_arg_names=self.arg_names,
                request=req,
                response=resp,
                this_func=self.func,
            )
        )

Ancestors

Class variables

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

Inherited members

class Listener
Expand source code
class Listener(metaclass=ABCMeta):
    matchers: Sequence[ListenerMatcher]
    middleware: Sequence[Middleware]  # type: ignore
    ack_function: Callable[..., BoltResponse]
    lazy_functions: Sequence[Callable[..., None]]
    auto_acknowledgement: bool

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

    def run_middleware(
        self,
        *,
        req: BoltRequest,
        resp: BoltResponse,
    ) -> Tuple[Optional[BoltResponse], bool]:
        """Runs a 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}

            def next_():
                middleware_state["next_called"] = True

            resp = m.process(req=req, resp=resp, next=next_)
            if not middleware_state["next_called"]:
                # next() was not called in this middleware
                return (resp, True)
        return (resp, False)

    @abstractmethod
    def run_ack_function(self, *, request: BoltRequest, 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[..., BoltResponse]
var auto_acknowledgement : bool
var lazy_functions : Sequence[Callable[..., None]]
var matchers : Sequence[ListenerMatcher]
var middleware : Sequence[Middleware]

Methods

def matches(self, *, req: BoltRequest, resp: BoltResponse) ‑> bool
Expand source code
def matches(
    self,
    *,
    req: BoltRequest,
    resp: BoltResponse,
) -> bool:
    is_matched: bool = False
    for matcher in self.matchers:
        is_matched = matcher.matches(req, resp)
        if not is_matched:
            return is_matched
    return is_matched
def run_ack_function(self, *, request: BoltRequest, 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

Expand source code
@abstractmethod
def run_ack_function(self, *, request: BoltRequest, 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()
def run_middleware(self, *, req: BoltRequest, resp: BoltResponse) ‑> Tuple[Optional[BoltResponse], bool]

Runs a middleware.

Args

req
The incoming request
resp
The current response

Returns

A tuple of the processed response and a flag indicating termination

Expand source code
def run_middleware(
    self,
    *,
    req: BoltRequest,
    resp: BoltResponse,
) -> Tuple[Optional[BoltResponse], bool]:
    """Runs a 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}

        def next_():
            middleware_state["next_called"] = True

        resp = m.process(req=req, resp=resp, next=next_)
        if not middleware_state["next_called"]:
            # next() was not called in this middleware
            return (resp, True)
    return (resp, False)
class Respond (*, response_url: Optional[str], proxy: Optional[str] = None, ssl: Optional[ssl.SSLContext] = None)
Expand source code
class Respond:
    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

    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 = WebhookClient(
                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):
                text = text_or_whole_response
                message = _build_message(
                    text=text,
                    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 client.send_dict(message)
            elif isinstance(text_or_whole_response, dict):
                message = _build_message(**text_or_whole_response)
                return client.send_dict(message)
            else:
                raise ValueError(f"The arg is unexpected type ({type(text_or_whole_response)})")
        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 Say (client: Optional[slack_sdk.web.client.WebClient], channel: Optional[str])
Expand source code
class Say:
    client: Optional[WebClient]
    channel: Optional[str]

    def __init__(
        self,
        client: Optional[WebClient],
        channel: Optional[str],
    ):
        self.client = client
        self.channel = channel

    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,
    ) -> SlackResponse:
        if _can_say(self, channel):
            text_or_whole_response: Union[str, dict] = text
            if isinstance(text_or_whole_response, str):
                text = text_or_whole_response
                return self.client.chat_postMessage(
                    channel=channel or self.channel,
                    text=text,
                    blocks=blocks,
                    attachments=attachments,
                    as_user=as_user,
                    thread_ts=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
                return self.client.chat_postMessage(**message)
            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 channel : Optional[str]
var client : Optional[slack_sdk.web.client.WebClient]