Bolt for Python

repository·main·Indexed 23 days ago

https://github.com/slackapi/bolt-python

A framework for building Slack apps quickly, supporting both synchronous and asynchronous programming models. It simplifies the handling of Slack's Events API, Block Kit interactions, slash commands, and more. The library provides decorators for listening to events, actions, commands, messages, shortcuts, views, options, and custom workflow functions. It supports various deployment targets including standard web servers, Socket Mode, AWS Lambda, and Django.

Tokens
75.3K
Snippets
177
Records
229
Agent score
76%

What's inside slack-bolt-python

  1. What is global middleware and how to use it

    main

    Global middleware is a function that executes for every incoming request before any listener middleware is called. You can add any number of global middleware functions to your app using app.use().

    Global middleware functions receive the same arguments as listener functions, plus a next() function. To maintain the execution chain and allow the request to proceed to the next middleware or listener, you must call next() within your middleware function.

  2. Define dynamic options for custom Workflow Builder steps

    main

    To provide builders in Workflow Builder with varying sets of fields based on their selections, you can use dynamic_options on a custom step's input parameter. This property points to another custom step that resolves the available options.

    When defining dynamic_options, you must provide:

    • function: The step reference (e.g., #/functions/get-projects) used to resolve the options.
    • inputs: A mapping of parameters to be passed to the resolution step. You can use the following placeholders in the value field:
      • {{input_parameters.<PARAMETER_NAME>}}: References an input parameter from the current step.
      • {{client.query}}: A placeholder for an input value.
      • {{client.builder_context}}: Injects the slack#/types/user_context of the user building the workflow.
    "dynamic_options": {
        "function": "#/functions/get-projects",
        "inputs": {
            "selected_user_id": {
                "value": "{{input_parameters.user_id}}"
            },
            "query": {
                "value": "{{client.query}}"
            }
        }
    }
  3. How Lazy Listeners work for FaaS environments

    main

    In Function-as-a-Service (FaaS) environments like AWS Lambda, you cannot continue executing code in a thread or process after an HTTP response has been sent. This prevents the standard pattern of calling ack() immediately and then performing long-running tasks.

    To solve this, Bolt for Python provides Lazy Listeners. Instead of using a standard decorator, you pass two specific keyword arguments to your listener (e.g., app.command, app.action, etc.):

    1. ack: A Callable responsible for calling the ack() method to respond to Slack within the required 3-second window.
    2. lazy: A List[Callable] containing functions that handle long-running processes. These functions cannot access or call ack() directly; instead, they receive a respond callback to communicate back to the user.

    Important Configuration: When using Lazy Listeners in a FaaS environment, you must set process_before_response=True when initializing your App instance. This tells Bolt to delay the HTTP response until the listener processing is complete. Note that if the total processing time (including the ack phase) exceeds 3 seconds, Slack will report a timeout error.

    app.command("/start-process")(
        ack=respond_to_slack_within_3_seconds,
        lazy=[run_long_process]
    )
  4. How token rotation works in Bolt for Python

    main

    Starting from Bolt for Python v1.7.0, the framework supports token rotation as defined in OAuth V2 RFC 6749 Section 10.4.

    In standard Slack apps, access tokens are often indefinite. With token rotation enabled, access tokens expire, and you must use a refresh token to obtain a new access token to maintain long-term access.

    If you are using the built-in OAuth functionality in Bolt for Python, Bolt handles the token rotation process automatically.

  5. Listen to and respond to shortcuts

    main

    The shortcut() method allows your app to handle two types of entry points: Global Shortcuts (accessible from the text input area or search window) and Message Shortcuts (accessible from a message's context menu).

    To implement a shortcut listener:

    1. Use @app.shortcut(callback_id) where callback_id is a str or re.Pattern.
    2. You must call ack() to acknowledge the request to Slack.
    3. Use the trigger_id found in the shortcut payload to perform interactive actions, such as opening a modal via client.views_open().

    Important Payload Differences:

    • Message Shortcuts include the channel_id in the payload.
    • Global Shortcuts do not include a channel_id. If you need the channel ID in a global shortcut, you must include a conversations_select element within a modal to allow the user to select a channel.
    @app.shortcut("open_modal")
    def open_modal(ack, shortcut, client):
        # Acknowledge the request
        ack()
        
        # Use trigger_id to open a modal
        client.views_open(
            trigger_id=shortcut["trigger_id"],
            view={
                "type": "modal",
                "title": {"type": "plain_text", "text":"My App"},
                "close": {"type": "plain_text", "text":"Close"},
                "blocks": [
                    {
                        "type": "section",
                        "text": {
                            "type": "mrkdwn",
                            "text":"Hello world!"
                        }
                    }
                ]
            }
        )
  6. Customize OAuth default settings with OAuthSettings

    main

    You can override the default behavior of the OAuth module by passing a customized OAuthSettings object to the App constructor.

    Customizable Fields

    • install_path: Overrides the default path for the "Add to Slack" button.
    • redirect_uri: Overrides the default redirect URL path.
    • callback_options: Provides custom logic for displaying success or failure pages at the end of the OAuth flow using CallbackOptions.
    • state_store: Allows you to use a custom data store instead of the built-in FileOAuthStateStore.
    • installation_store: Allows you to use a custom data store instead of the built-in FileInstallationStore.

    Customizing Success and Failure Callbacks

    To control what the user sees after the OAuth flow completes, define success and failure functions and wrap them in CallbackOptions.

    from slack_bolt.oauth.callback_options import CallbackOptions, SuccessArgs, FailureArgs
    from slack_bolt.response import BoltResponse
    
    def success(args:SuccessArgs) -> BoltResponse:
        assert args.request is not None
        return BoltResponse(
            status=200,
            body="Your own response to end-users here"
        )
    
    def failure(args:FailureArgs) -> BoltResponse:
        assert args.request is not None
        assert args.reason is not None
        return BoltResponse(
            status=args.suggested_status_code,
            body="Your own response to end-users here"
        )
    
    callback_options = CallbackOptions(success=success, failure=failure)
  7. Understand Authorization in Bolt

    main

    Authorization is the process of determining which Slack credentials (such as a bot token) should be used to process an incoming request from Slack.

    There are three primary ways to handle authorization depending on your app's installation scope:

    1. Single Workspace Apps: Pass a single token directly to the App constructor. This is the simplest method.
    2. Multi-Workspace Apps (Built-in OAuth): Use Bolt's built-in OAuth support, which handles the OAuth flow URLs and state validation. Refer to the "Authenticating with OAuth" guide for details.
    3. Multi-Workspace Apps (Custom Authorization): For maximum control, provide an authorize function to the App constructor. This function is called for every incoming request and allows you to look up credentials based on the request context.
  8. Listen to events using the event() method

    main

    You can listen to any Slack Events API event by using the @app.event() decorator. This allows your app to respond to workspace actions, such as a user joining a channel or reacting to a message. The decorator requires an eventType of type str (or a dictionary for advanced filtering).

    @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}>! 🎉 You can introduce yourself in this channel."
        say(text=text, channel=welcome_channel_id)
  9. Filter message events by subtype

    main

    The message() listener is a convenience method equivalent to event("message").

    You can filter message events by providing a subtype key in a dictionary passed to the listener. Common subtypes include bot_message and message_replied. If you want to filter for events that have no subtype, you can explicitly specify None for the subtype key.

    # Match all messages that have the 'message_changed' subtype
    @app.event({
        "type": "message",
        "subtype": "message_changed"
    })
    def log_message_change(logger, event):
        user, text = event["user"], event["text"]
        logger.info(f"The user {user} changed the message to {text}")
  10. Use listener matchers for simplified filtering

    main

    If your filtering logic is simple, you can use listener matchers instead of full middleware. Matchers are passed to the matchers parameter of a listener decorator.

    A matcher is a function that takes the listener arguments and returns a bool. If the function returns True, the listener proceeds; if it returns False, the listener is skipped. Unlike middleware, matchers do not require a next() call.

    # Listener matchers: simplified version of listener middleware
    def no_bot_messages(message) -> bool:
        return "bot_id" not in message
    
    @app.event(
        event="message",
        matchers=[no_bot_messages]
        # or matchers=[lambda message: message.get("subtype") != "bot_message"]
    )
    def log_message(logger, event):
        logger.info(f"(MSG) User: {event['user']}\nMessage: {event['text']}")
  11. Handle multi-workspace authorization with a custom authorize function

    main

    If your app is installed on multiple workspaces, you cannot use a single token in the App constructor. Instead, you can provide a custom authorize function to the App instantiation.

    This function is called for every incoming request. It receives enterprise_id, team_id, and a logger. Your implementation should look up the appropriate credentials for that specific workspace (e.g., from a database) and return an instance of AuthorizeResult.

    Using a custom authorize function allows you to dynamically provide the correct credentials for the workspace that sent the request, enabling features like say() to work correctly for each specific installation.

    import os
    from slack_bolt import App
    from slack_bolt.authorization import AuthorizeResult
    
    # Example installation data (in a real app, this would be in a database)
    installations = [
        {
          "enterprise_id": "E1234A12AB",
          "team_id": "T12345",
          "bot_token": "xoxb-123abc",
          "bot_id": "B1251",
          "bot_user_id": "U12385"
        }
    ]
    
    def authorize(enterprise_id, team_id, logger):
        for team in installations:
            is_valid_enterprise = "enterprise_id" not in team or enterprise_id == team["enterprise_id"]
            if is_valid_enterprise and team["team_id"] == team_id:
              return AuthorizeResult(
                  enterprise_id=enterprise_id,
                  team_id=team_id,
                  bot_token=team["bot_token"],
                  bot_id=team["bot_id"],
                  bot_user_id=team["bot_user_id"]
              )
        logger.error("No authorization information was found")
    
    app = App(
        signing_secret=os.environ["SLACK_SIGNING_SECRET"],
        authorize=authorize
    )
  12. How custom adapters work in Bolt

    main

    Adapters allow Bolt to integrate with different web frameworks by bridging the gap between framework-specific HTTP requests/responses and Bolt's internal abstractions.

    A custom adapter requires two primary components:

    1. Constructor (__init__(app: App)): Accepts and stores an instance of the Bolt App.
    2. Request Handler (handle(req: Request)): A function (conventionally named handle()) that receives incoming framework-specific requests, converts them into a BoltRequest instance, and dispatches them to the stored Bolt app.

    The handle() method should return a BoltResponse (or a framework-specific response derived from one) produced by the Bolt app.